GhidraJupyter / ghidra-jupyter-kotlin

MIT License
115 stars 10 forks source link

"Transaction has not been started" Exception when updating db #19

Closed r1fl closed 2 years ago

r1fl commented 2 years ago

When making modifications to the program db on Ghidra 10.0.4 the following exception occurs

Transaction has not been started
db.NoTransactionException: Transaction has not been started

Sample code:

import ghidra.program.model.listing.CodeUnit as CodeUnit

var listing = currentProgram.getListing()
listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra")

Full trace:

Transaction has not been started
db.NoTransactionException: Transaction has not been started
org.jetbrains.kotlinx.jupyter.ReplEvalRuntimeException: Transaction has not been started
    at org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl.eval(InternalEvaluatorImpl.kt:91)
    at org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$$inlined$with$lambda$1.invoke(CellExecutorImpl.kt:59)
    at org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$$inlined$with$lambda$1.invoke(CellExecutorImpl.kt:28)
    at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl.withHost(repl.kt:535)
    at org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl.execute(CellExecutorImpl.kt:58)
    at org.jetbrains.kotlinx.jupyter.repl.CellExecutor$DefaultImpls.execute$default(CellExecutor.kt:20)
    at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl$eval$1.invoke(repl.kt:369)
    at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl$eval$1.invoke(repl.kt:146)
    at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl.withEvalContext(repl.kt:344)
    at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl.eval(repl.kt:359)
    at org.jetbrains.kotlinx.jupyter.ProtocolKt$shellMessagesHandler$res$1.invoke(protocol.kt:290)
    at org.jetbrains.kotlinx.jupyter.ProtocolKt$shellMessagesHandler$res$1.invoke(protocol.kt)
    at org.jetbrains.kotlinx.jupyter.JupyterConnection$runExecution$execThread$1.invoke(connection.kt:162)
    at org.jetbrains.kotlinx.jupyter.JupyterConnection$runExecution$execThread$1.invoke(connection.kt:23)
    at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
Caused by: db.NoTransactionException: Transaction has not been started
    at db.DBHandle.checkTransaction(DBHandle.java:396)
    at db.Table.putRecord(Table.java:938)
    at ghidra.program.database.code.CommentHistoryAdapterV0.createRecord(CommentHistoryAdapterV0.java:75)
    at ghidra.program.database.code.CodeManager.createCommentHistoryRecord(CodeManager.java:3459)
    at ghidra.program.database.code.CodeManager.sendNotification(CodeManager.java:3438)
    at ghidra.program.database.code.CodeUnitDB.setComment(CodeUnitDB.java:493)
    at ghidra.program.database.code.DataDB.setComment(DataDB.java:556)
    at ghidra.program.database.code.CodeManager.setComment(CodeManager.java:3383)
    at ghidra.program.database.ListingDB.setComment(ListingDB.java:756)
    at Line_562.<init>(Line_562.jupyter-kts:6)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at kotlin.script.experimental.jvm.BasicJvmScriptEvaluator.evalWithConfigAndOtherScriptsResults(BasicJvmScriptEvaluator.kt:96)
    at kotlin.script.experimental.jvm.BasicJvmScriptEvaluator.invoke$suspendImpl(BasicJvmScriptEvaluator.kt:41)
    at kotlin.script.experimental.jvm.BasicJvmScriptEvaluator.invoke(BasicJvmScriptEvaluator.kt)
    at kotlin.script.experimental.jvm.BasicJvmReplEvaluator.eval(BasicJvmReplEvaluator.kt:51)
    at org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl$eval$resultWithDiagnostics$1.invokeSuspend(InternalEvaluatorImpl.kt:84)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl.eval(InternalEvaluatorImpl.kt:84)
    ... 14 more
fmagin commented 2 years ago

This is in fact (mostly) intentional. The Jython REPL implicitly wraps every every execution inside a transaction, but the Ghidra Kotlin kernel does not do this, because I needed it so rarely, and I rather make it explicit when a DB modifying action occurs, by manually using startTransaction and endTransaction.

With this context: Would you agree with this sentiment? We considered providing a more elegant wrapper for this, like a line magic to mark a cell as an DB transaction and implicitly wrap it, but that might be a fairly large amount of effort to implement for what I personally consider a negligible benefit.

edit: fixed links to documentation

fmagin commented 2 years ago

So of the top of my head this would be:

import ghidra.program.model.listing.CodeUnit as CodeUnit

var id = currentProgram.startTransaction("Set line comment from Kernel")

var listing = currentProgram.getListing()
listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra")

currentProgram.endTransaction(id)

or even more specific:

import ghidra.program.model.listing.CodeUnit as CodeUnit

var listing = currentProgram.getListing()

var id = currentProgram.startTransaction("Set line comment from Kernel")

listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra")

currentProgram.endTransaction(id)
fmagin commented 2 years ago

I'm closing this for now, under the assumption that you just needed the information about the transaction API. If you run into situations where this API becomes too cumbersome to use in some scenario feel free to reopen this issue. I'm happy to address a concrete case, one simple idea could be an extension function to allow something like this:

import ghidra.program.model.listing.CodeUnit as CodeUnit
var listing = currentProgram.getListing()
currentProgram.runTransaction { listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra") }