diff --git a/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/commands.py b/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/commands.py index 66ebf20a11..a5d31fbb54 100644 --- a/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/commands.py +++ b/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/commands.py @@ -973,106 +973,111 @@ def ghidra_trace_put_environment(): put_environment() -def put_regions(): - nproc = util.selected_process() - if nproc is None: - return - - try: - regions = prog.loaded_modules() - except Exception as e: - regions = [] - #if len(regions) == 0: - # regions = util.full_mem() - - mapper = STATE.trace.memory_mapper - keys = [] - # r : MEMORY_BASIC_INFORMATION64 - for r in regions: - start = r[0].address_range[0] - end = r[0].address_range[1] - size = end - start + 1 - rpath = REGION_PATTERN.format(procnum=nproc, start=start) - keys.append(REGION_KEY_PATTERN.format(start=start)) - regobj = STATE.trace.create_object(rpath) - (start_base, start_addr) = map_address(start) - regobj.set_value('Range', start_addr.extend(size)) - regobj.set_value('Name', r[0].name) - regobj.set_value('Object File', r[0].loaded_file_path) - regobj.set_value('_readable', True) - regobj.set_value('_writable', True) - regobj.set_value('_executable', True) - regobj.set_value('_display', '{:x} {}'.format(start, r[0].name)) - regobj.insert() - STATE.trace.proxy_object_path( - MEMORY_PATTERN.format(procnum=nproc)).retain_values(keys) - - -def ghidra_trace_put_regions(): - """ - Read the memory map, if applicable, and write to the trace's Regions - """ - - STATE.require_tx() - with STATE.client.batch() as b: - put_regions() - - - -def put_modules(): - nproc = util.selected_process() - if nproc is None: - return +# Detect whether this is supported before defining the command +if hasattr(drgn, 'RelocatableModule'): + def put_regions(): + nproc = util.selected_process() + if nproc is None: + return - try: - modules = prog.modules() - except Exception as e: - return - - mapper = STATE.trace.memory_mapper - mod_keys = [] - for m in modules: - name = m.name - # m[1] : _DEBUG_MODULE_PARAMETERS - base = m.address_range[0] - hbase = hex(base) - size = m.address_range[1] - base - mpath = MODULE_PATTERN.format(procnum=nproc, modpath=hbase) - modobj = STATE.trace.create_object(mpath) - mod_keys.append(MODULE_KEY_PATTERN.format(modpath=hbase)) - base_base, base_addr = mapper.map(nproc, base) - if base_base != base_addr.space: - STATE.trace.create_overlay_space(base_base, base_addr.space) - modobj.set_value('Range', base_addr.extend(size)) - modobj.set_value('Name', name) - modobj.set_value('_display', '{:x} {}'.format(base, name)) - modobj.insert() - attrobj = STATE.trace.create_object(mpath+".Attributes") - attrobj.set_value('BuildId', m.build_id) - attrobj.set_value('DebugBias', m.debug_file_bias) - attrobj.set_value('DebugPath', m.debug_file_path) - attrobj.set_value('DebugStatus', str(m.debug_file_status)) - attrobj.set_value('LoadBias', m.loaded_file_bias) - attrobj.set_value('LoadPath', m.loaded_file_path) - attrobj.set_value('LoadStatus', str(m.loaded_file_status)) - attrobj.insert() - if type(m) == drgn.RelocatableModule: - secobj = STATE.trace.create_object(mpath+".Sections") - secobj.insert() - STATE.trace.proxy_object_path(MODULES_PATTERN.format( - procnum=nproc)).retain_values(mod_keys) + try: + regions = prog.loaded_modules() + except Exception as e: + regions = [] + #if len(regions) == 0: + # regions = util.full_mem() + + mapper = STATE.trace.memory_mapper + keys = [] + # r : MEMORY_BASIC_INFORMATION64 + for r in regions: + start = r[0].address_range[0] + end = r[0].address_range[1] + size = end - start + 1 + rpath = REGION_PATTERN.format(procnum=nproc, start=start) + keys.append(REGION_KEY_PATTERN.format(start=start)) + regobj = STATE.trace.create_object(rpath) + (start_base, start_addr) = map_address(start) + regobj.set_value('Range', start_addr.extend(size)) + regobj.set_value('Name', r[0].name) + regobj.set_value('Object File', r[0].loaded_file_path) + regobj.set_value('_readable', True) + regobj.set_value('_writable', True) + regobj.set_value('_executable', True) + regobj.set_value('_display', '{:x} {}'.format(start, r[0].name)) + regobj.insert() + STATE.trace.proxy_object_path( + MEMORY_PATTERN.format(procnum=nproc)).retain_values(keys) -def ghidra_trace_put_modules(): - """ - Gather object files, if applicable, and write to the trace's Modules - """ - - STATE.require_tx() - with STATE.client.batch() as b: - put_modules() + def ghidra_trace_put_regions(): + """ + Read the memory map, if applicable, and write to the trace's Regions + """ + + STATE.require_tx() + with STATE.client.batch() as b: + put_regions() + +# Detect whether this is supported before defining the command +if hasattr(drgn, 'RelocatableModule'): + def put_modules(): + nproc = util.selected_process() + if nproc is None: + return + + try: + modules = prog.modules() + except Exception as e: + return + + mapper = STATE.trace.memory_mapper + mod_keys = [] + for m in modules: + name = m.name + # m[1] : _DEBUG_MODULE_PARAMETERS + base = m.address_range[0] + hbase = hex(base) + size = m.address_range[1] - base + mpath = MODULE_PATTERN.format(procnum=nproc, modpath=hbase) + modobj = STATE.trace.create_object(mpath) + mod_keys.append(MODULE_KEY_PATTERN.format(modpath=hbase)) + base_base, base_addr = mapper.map(nproc, base) + if base_base != base_addr.space: + STATE.trace.create_overlay_space(base_base, base_addr.space) + modobj.set_value('Range', base_addr.extend(size)) + modobj.set_value('Name', name) + modobj.set_value('_display', '{:x} {}'.format(base, name)) + modobj.insert() + attrobj = STATE.trace.create_object(mpath+".Attributes") + attrobj.set_value('BuildId', m.build_id) + attrobj.set_value('DebugBias', m.debug_file_bias) + attrobj.set_value('DebugPath', m.debug_file_path) + attrobj.set_value('DebugStatus', str(m.debug_file_status)) + attrobj.set_value('LoadBias', m.loaded_file_bias) + attrobj.set_value('LoadPath', m.loaded_file_path) + attrobj.set_value('LoadStatus', str(m.loaded_file_status)) + attrobj.insert() + if type(m) == drgn.RelocatableModule: + secobj = STATE.trace.create_object(mpath+".Sections") + secobj.insert() + STATE.trace.proxy_object_path(MODULES_PATTERN.format( + procnum=nproc)).retain_values(mod_keys) + + + def ghidra_trace_put_modules(): + """ + Gather object files, if applicable, and write to the trace's Modules + """ + + STATE.require_tx() + with STATE.client.batch() as b: + put_modules() + + +# Detect whether this is supported before defining the command if hasattr(drgn, 'RelocatableModule'): def put_sections(m : drgn.RelocatableModule): nproc = util.selected_process() diff --git a/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/methods.py b/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/methods.py index f6fce47484..7a2e5c9ff9 100644 --- a/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/methods.py +++ b/Ghidra/Debug/Debugger-agent-drgn/src/main/py/src/ghidradrgn/methods.py @@ -263,20 +263,22 @@ def refresh_locals(node: sch.Schema('LocalsContainer')): commands.ghidra_trace_put_locals() -@REGISTRY.method(action='refresh', display='Refresh Memory') -def refresh_mappings(node: sch.Schema('Memory')): - """Refresh the list of memory regions for the process.""" - with commands.open_tracked_tx('Refresh Memory Regions'): - commands.ghidra_trace_put_regions() +if hasattr(drgn, 'RelocatableModule'): + @REGISTRY.method(action='refresh', display='Refresh Memory') + def refresh_mappings(node: sch.Schema('Memory')): + """Refresh the list of memory regions for the process.""" + with commands.open_tracked_tx('Refresh Memory Regions'): + commands.ghidra_trace_put_regions() -@REGISTRY.method(action='refresh', display='Refresh Modules') -def refresh_modules(node: sch.Schema('ModuleContainer')): - """ - Refresh the modules list for the process. - """ - with commands.open_tracked_tx('Refresh Modules'): - commands.ghidra_trace_put_modules() +if hasattr(drgn, 'RelocatableModule'): + @REGISTRY.method(action='refresh', display='Refresh Modules') + def refresh_modules(node: sch.Schema('ModuleContainer')): + """ + Refresh the modules list for the process. + """ + with commands.open_tracked_tx('Refresh Modules'): + commands.ghidra_trace_put_modules() @REGISTRY.method(action='activate') diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/AbstractDrgnTraceRmiTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/AbstractDrgnTraceRmiTest.java index eedc4a6af2..fbc6920108 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/AbstractDrgnTraceRmiTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/AbstractDrgnTraceRmiTest.java @@ -230,10 +230,6 @@ public abstract class AbstractDrgnTraceRmiTest extends AbstractGhidraHeadedDebug protected record PythonAndConnection(ExecInDrgn exec, TraceRmiConnection connection) implements AutoCloseable { - protected boolean hasMethod(String name) { - return connection.getMethods().get(name) != null; - } - protected RemoteMethod getMethod(String name) { return Objects.requireNonNull(connection.getMethods().get(name)); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/DrgnMethodsTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/DrgnMethodsTest.java index 351df06f71..c1bcadb221 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/DrgnMethodsTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/drgn/rmi/DrgnMethodsTest.java @@ -17,6 +17,7 @@ package agent.drgn.rmi; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.junit.Assume.*; import java.util.*; @@ -200,9 +201,7 @@ public class DrgnMethodsTest extends AbstractDrgnTraceRmiTest { txCreate(conn, path); String out = conn.executeCapture("print(hasattr(drgn, 'RelocatableModule'))").strip(); - if (out.equals("False")) { - return; - } + assumeFalse(out.equals("False")); RemoteMethod refreshMappings = conn.getMethod("refresh_mappings"); try (ManagedDomainObject mdo = openDomainObject(MDO)) { @@ -227,9 +226,7 @@ public class DrgnMethodsTest extends AbstractDrgnTraceRmiTest { txCreate(conn, path); String out = conn.executeCapture("print(hasattr(drgn, 'RelocatableModule'))").strip(); - if (out.equals("False")) { - return; - } + assumeFalse(out.equals("False")); RemoteMethod refreshModules = conn.getMethod("refresh_modules"); try (ManagedDomainObject mdo = openDomainObject(MDO)) { diff --git a/GhidraDocs/GhidraClass/Debugger/B5-AddingDebuggers.html b/GhidraDocs/GhidraClass/Debugger/B5-AddingDebuggers.html index 5658b290b5..b7263612a1 100644 --- a/GhidraDocs/GhidraClass/Debugger/B5-AddingDebuggers.html +++ b/GhidraDocs/GhidraClass/Debugger/B5-AddingDebuggers.html @@ -132,7 +132,7 @@
The hardest part of writing unit tests is almost always getting the first test to run, and the easiest unit tests, as with the Python files, are those for commands.py. For drgn, as before, we’re using dbgeng as the pattern, but several elements had to be changed. Because the launchers execute a script, we need to amend the runThrowError logic (and, more specifically, the execInPython logic) in AbstractDrgnTraceRmiTest with a ProcessBuilder call that takes a script, rather than writing the script to stdin. While there, we can also trim out the unnecessary helper logic around items like breakpoints, watchpoints, etc. from all of the test classes.
JUnits for methods.py follow a similar pattern, but, again, getting the first one to run is often the most difficult. For drgn, we’ve had to override the timeouts in waitForPass and waitForCondition. After starting with hardcoded paths for the test target, we also had to add logic to re-write the PREAMBLE on-the-fly in execInDrgn. Obviously, with no real hooks.py logic, there’s no need for DrgnHooksTest.
Of note, we’ve used the gdb gcore command to create a core dump for the tests. Both user- and kernel-mode require privileges to run the debugger, and, for testing, that’s not ideal. build.gradle will also need to be modified to include the new debugger package.
Of note, we’ve used the gdb gcore command to create a core dump for the tests. Both user- and kernel-mode require privileges to run the debugger, and, for testing, that’s not ideal. build.gradle for IntegrationTest projext will also need to be modified to include the new debugger package.