mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-28 15:55:38 +08:00
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:
@@ -26,7 +26,7 @@ from ghidratrace.client import (MethodRegistry, ParamDesc, Address,
|
|||||||
from pybag import pydbg # type: ignore
|
from pybag import pydbg # type: ignore
|
||||||
from pybag.dbgeng import core as DbgEng, exception # type: ignore
|
from pybag.dbgeng import core as DbgEng, exception # type: ignore
|
||||||
|
|
||||||
from . import util, commands
|
from . import arch, util, commands
|
||||||
|
|
||||||
|
|
||||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(
|
REGISTRY = MethodRegistry(ThreadPoolExecutor(
|
||||||
@@ -846,8 +846,11 @@ def write_reg(frame: StackFrame, name: str, value: bytes) -> None:
|
|||||||
"""Write a register."""
|
"""Write a register."""
|
||||||
f = find_frame_by_obj(frame)
|
f = find_frame_by_obj(frame)
|
||||||
util.select_frame(f.FrameNumber)
|
util.select_frame(f.FrameNumber)
|
||||||
nproc = pydbg.selected_process()
|
nproc = util.selected_process()
|
||||||
dbg().reg._set_register(name, value)
|
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)
|
@REGISTRY.method(display='Refresh Events (custom)', condition=util.dbg.IS_TRACE)
|
||||||
|
|||||||
@@ -309,6 +309,9 @@
|
|||||||
</schema>
|
</schema>
|
||||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||||
<attribute name="_order" schema="INT" hidden="yes" />
|
<attribute name="_order" schema="INT" hidden="yes" />
|
||||||
<attribute schema="VOID" />
|
<attribute schema="Register" />
|
||||||
|
</schema>
|
||||||
|
<schema name="Register">
|
||||||
|
<interface name="Register" />
|
||||||
</schema>
|
</schema>
|
||||||
</context>
|
</context>
|
||||||
@@ -270,7 +270,10 @@
|
|||||||
</schema>
|
</schema>
|
||||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||||
<attribute name="_order" schema="INT" hidden="yes" />
|
<attribute name="_order" schema="INT" hidden="yes" />
|
||||||
<attribute schema="VOID" />
|
<attribute schema="Register" />
|
||||||
|
</schema>
|
||||||
|
<schema name="Register">
|
||||||
|
<interface name="Register" />
|
||||||
</schema>
|
</schema>
|
||||||
|
|
||||||
<schema name="ExdiProcessContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
<schema name="ExdiProcessContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||||
|
|||||||
@@ -225,6 +225,9 @@
|
|||||||
<schema name="RegisterValueContainer" attributeResync="NEVER">
|
<schema name="RegisterValueContainer" attributeResync="NEVER">
|
||||||
<interface name="RegisterContainer" />
|
<interface name="RegisterContainer" />
|
||||||
<attribute name="_order" schema="INT" hidden="yes" />
|
<attribute name="_order" schema="INT" hidden="yes" />
|
||||||
<attribute schema="VOID" />
|
<attribute schema="Register" />
|
||||||
|
</schema>
|
||||||
|
<schema name="Register">
|
||||||
|
<interface name="Register" />
|
||||||
</schema>
|
</schema>
|
||||||
</context>
|
</context>
|
||||||
@@ -308,6 +308,8 @@ class DefaultRegisterMapper(object):
|
|||||||
|
|
||||||
def map_value_back(self, proc: lldb.SBProcess, name: str,
|
def map_value_back(self, proc: lldb.SBProcess, name: str,
|
||||||
value: bytes) -> RegVal:
|
value: bytes) -> RegVal:
|
||||||
|
if self.byte_order == 'little':
|
||||||
|
value = bytes(reversed(value))
|
||||||
return RegVal(self.map_name_back(proc, name), value)
|
return RegVal(self.map_name_back(proc, name), value)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ def find_proc_by_modules_obj(object: TraceObject) -> lldb.SBProcess:
|
|||||||
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
|
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:
|
def find_thread_by_num(proc: lldb.SBThread, tnum: int) -> lldb.SBThread:
|
||||||
for t in proc.threads:
|
for t in proc.threads:
|
||||||
if t.GetThreadID() == tnum:
|
if t.GetThreadID() == tnum:
|
||||||
@@ -731,11 +735,22 @@ def write_mem(process: Process, address: Address, data: bytes) -> None:
|
|||||||
@REGISTRY.method()
|
@REGISTRY.method()
|
||||||
def write_reg(frame: StackFrame, name: str, value: bytes) -> None:
|
def write_reg(frame: StackFrame, name: str, value: bytes) -> None:
|
||||||
"""Write a register."""
|
"""Write a register."""
|
||||||
|
proc = find_proc_by_frame(frame)
|
||||||
|
util.get_debugger().SetSelectedTarget(proc.target)
|
||||||
f = find_frame_by_obj(frame)
|
f = find_frame_by_obj(frame)
|
||||||
f.select()
|
exec_convert_errors(f'frame select {f.idx}')
|
||||||
proc = lldb.selected_process()
|
rv = frame.trace.extra.require_rm().map_value_back(proc, name, value)
|
||||||
mname, mval = frame.trace.extra.require_rm().map_value_back(proc, name, value)
|
reg = f.registers[0].GetChildMemberWithName(name)
|
||||||
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
|
error = lldb.SBError()
|
||||||
arr = '{' + ','.join(str(b) for b in mval) + '}'
|
data = lldb.SBData()
|
||||||
exec_convert_errors(
|
tgt = util.get_target()
|
||||||
f'expr ((unsigned char[{size}])${mname}) = {arr};')
|
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')
|
||||||
|
|||||||
@@ -260,6 +260,9 @@
|
|||||||
</schema>
|
</schema>
|
||||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||||
<attribute name="_order" schema="INT" hidden="yes" />
|
<attribute name="_order" schema="INT" hidden="yes" />
|
||||||
<attribute schema="VOID" />
|
<attribute schema="Register" />
|
||||||
|
</schema>
|
||||||
|
<schema name="Register">
|
||||||
|
<interface name="Register" />
|
||||||
</schema>
|
</schema>
|
||||||
</context>
|
</context>
|
||||||
+103
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user