GP-6084: Fix register writing for lldb (schema and code)

Some work toward dbgeng as well, but not yet resolved.
This commit is contained in:
Dan
2025-11-05 18:48:21 +00:00
parent a125b8784d
commit 3965c8edf0
8 changed files with 149 additions and 14 deletions

View File

@@ -26,7 +26,7 @@ from ghidratrace.client import (MethodRegistry, ParamDesc, Address,
from pybag import pydbg # type: ignore
from pybag.dbgeng import core as DbgEng, exception # type: ignore
from . import util, commands
from . import arch, util, commands
REGISTRY = MethodRegistry(ThreadPoolExecutor(
@@ -846,8 +846,11 @@ def write_reg(frame: StackFrame, name: str, value: bytes) -> None:
"""Write a register."""
f = find_frame_by_obj(frame)
util.select_frame(f.FrameNumber)
nproc = pydbg.selected_process()
dbg().reg._set_register(name, value)
nproc = util.selected_process()
trace: Trace[commands.Extra] = frame.trace
rv = trace.extra.require_rm().map_value_back(nproc, name, value)
rval = int.from_bytes(rv.value, signed=False)
dbg().reg._set_register(name, rval)
@REGISTRY.method(display='Refresh Events (custom)', condition=util.dbg.IS_TRACE)

View File

@@ -309,6 +309,9 @@
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
<attribute schema="Register" />
</schema>
<schema name="Register">
<interface name="Register" />
</schema>
</context>

View File

@@ -270,7 +270,10 @@
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
<attribute schema="Register" />
</schema>
<schema name="Register">
<interface name="Register" />
</schema>
<schema name="ExdiProcessContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">

View File

@@ -225,6 +225,9 @@
<schema name="RegisterValueContainer" attributeResync="NEVER">
<interface name="RegisterContainer" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
<attribute schema="Register" />
</schema>
<schema name="Register">
<interface name="Register" />
</schema>
</context>

View File

@@ -308,6 +308,8 @@ class DefaultRegisterMapper(object):
def map_value_back(self, proc: lldb.SBProcess, name: str,
value: bytes) -> RegVal:
if self.byte_order == 'little':
value = bytes(reversed(value))
return RegVal(self.map_name_back(proc, name), value)

View File

@@ -106,6 +106,10 @@ def find_proc_by_modules_obj(object: TraceObject) -> lldb.SBProcess:
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
def find_proc_by_frame(object: TraceObject) -> lldb.SBProcess:
return find_proc_by_pattern(object, FRAME_PATTERN, "a StaclFrame")
def find_thread_by_num(proc: lldb.SBThread, tnum: int) -> lldb.SBThread:
for t in proc.threads:
if t.GetThreadID() == tnum:
@@ -731,11 +735,22 @@ def write_mem(process: Process, address: Address, data: bytes) -> None:
@REGISTRY.method()
def write_reg(frame: StackFrame, name: str, value: bytes) -> None:
"""Write a register."""
proc = find_proc_by_frame(frame)
util.get_debugger().SetSelectedTarget(proc.target)
f = find_frame_by_obj(frame)
f.select()
proc = lldb.selected_process()
mname, mval = frame.trace.extra.require_rm().map_value_back(proc, name, value)
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
arr = '{' + ','.join(str(b) for b in mval) + '}'
exec_convert_errors(
f'expr ((unsigned char[{size}])${mname}) = {arr};')
exec_convert_errors(f'frame select {f.idx}')
rv = frame.trace.extra.require_rm().map_value_back(proc, name, value)
reg = f.registers[0].GetChildMemberWithName(name)
error = lldb.SBError()
data = lldb.SBData()
tgt = util.get_target()
for b in rv.value:
bv = tgt.EvaluateExpression(f"(char){b}")
if bv.error.fail:
raise Exception(bv.error.description)
if not data.Append(bv.GetData()):
raise Exception(f"Could not build data for register value {rv.value}")
if not reg.SetData(data, error):
raise Exception(error.description)
with commands.open_tracked_tx(f'Write Register {name}'):
exec_convert_errors('ghidra trace putreg')

View File

@@ -260,6 +260,9 @@
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
<attribute schema="Register" />
</schema>
<schema name="Register">
<interface name="Register" />
</schema>
</context>

View File

@@ -0,0 +1,103 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.tracermi;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import db.Transaction;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiTarget.FoundRegister;
import ghidra.app.services.DebuggerControlService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.path.KeyPath;
import ghidra.trace.model.target.schema.SchemaContext;
import ghidra.trace.model.target.schema.XmlSchemaContext;
import ghidra.trace.model.thread.TraceThread;
public class TraceRmiTargetTest extends AbstractGhidraHeadedDebuggerTest {
class MyTraceRmiConnection extends TestTraceRmiConnection {
@Override
protected DebuggerTraceManagerService getTraceManager() {
return null;
}
@Override
protected DebuggerControlService getControlService() {
return null;
}
}
@Test
public void testSearchForRegistersInGroups() throws Exception {
SchemaContext ctx = XmlSchemaContext.deserialize("""
<context>
<schema name="root">
<interface name="Aggregate" />
<attribute name="Threads" schema="ThreadContainer" />
</schema>
<schema name="ThreadContainer" canonical="yes">
<element schema="Thread" />
</schema>
<schema name="Thread">
<interface name="Thread" />
<interface name="Aggregate" />
<attribute name="Registers" schema="RegisterValueContainer" />
</schema>
<schema name="RegisterValueContainer">
<interface name="RegisterContainer" />
<attribute name="General Purpose" schema="RegisterBank" />
</schema>
<schema name="RegisterBank" canonical="yes">
<attribute schema="Register" />
<interface name="Aggregate" />
</schema>
<schema name="Register">
<interface name="Register" />
</schema>
</context>
""");
PluginTool tool = env.getTool();
createTrace("x86:LE:64:default");
Register regRax = tb.reg("RAX");
try (Transaction tx = tb.startTransaction()) {
tb.createRootObject(ctx, "root");
TraceObject objRax = tb.trace.getObjectManager()
.createObject(KeyPath.parse("Threads[1].Registers.General Purpose.RAX"));
objRax.insert(Lifespan.ALL, ConflictResolution.DENY);
}
try (TraceRmiConnection cx = new MyTraceRmiConnection()) {
TraceRmiTarget target = new TraceRmiTarget(tool, cx, tb.trace);
TraceThread thread = tb.obj("Threads[1]").queryInterface(TraceThread.class);
FoundRegister found = target.findRegister(thread, 0, regRax);
assertEquals("Threads[1].Registers.General Purpose.RAX",
found.value().getCanonicalPath().toString());
}
}
}