mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-23 20:59:58 +00:00
Merge remote-tracking branch 'origin/GP-3891_gdbWineLauncher--SQUASHED'
This commit is contained in:
commit
df29f50fa3
@ -23,7 +23,7 @@
|
||||
#@desc (you may install <tt>gdb-multiarch</tt>), and it must embed the Python 3 interpreter. You
|
||||
#@desc will also need <tt>protobuf</tt> installed for Python 3.
|
||||
#@desc </body></html>
|
||||
#@menu-group qemu
|
||||
#@menu-group cross
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb
|
||||
#@enum StartCmd:str run start starti
|
||||
|
75
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh
Executable file
75
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh
Executable file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title wine + gdb
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with <tt>gdb</tt> and <tt>wine</tt></h3>
|
||||
#@desc <p>This will launch the target on the local machine using <tt>gdb</tt> and <tt>wine</tt>.
|
||||
#@desc GDB and Wine must already be installed on your system, and GDB must embed the Python 3
|
||||
#@desc interpreter. You will also need <tt>protobuf</tt> and <tt>psutil</tt> installed for Python
|
||||
#@desc 3.</p>
|
||||
#@desc <p>This operates by starting GDB on the Wine executable and passing arguments to launch a
|
||||
#@desc Windows target. This may prevent GDB from processing the object file, because it is a PE
|
||||
#@desc file, and most copies of GDB for UNIX will support only ELF. Nevertheless, Ghidra should
|
||||
#@desc recognize the target and map it, giving you symbols and debug info in the front end, even
|
||||
#@desc if not in the GDB CLI.</p>
|
||||
#@desc <p>You will need to locate the <tt>wine</tt> executable on your system, not the script. To
|
||||
#@desc find it, either dissect the <tt>wine</tt> script or consult online documentation for your
|
||||
#@desc distribution of Wine. There are often two executables, one for 32-bit targets and one for
|
||||
#@desc 64-bit targets. You must select the correct one.</p>
|
||||
#@desc </body></html>
|
||||
#@menu-group cross
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_WINE_PATH:str="/usr/lib/wine/wine64" "Path to wine binary" "The path to the wine executable for your target architecture."
|
||||
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
# NOTE: Ghidra will leave TTY_TARGET empty, which gdb takes for the same terminal.
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb.wine" \
|
||||
-ex "file \"$OPT_WINE_PATH\"" \
|
||||
-ex "set args $@" \
|
||||
-ex "set inferior-tty $TTY_TARGET" \
|
||||
-ex "starti" \
|
||||
-ex "ghidra wine run-to-image \"$1\"" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start \"$1\"" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "ghidra trace sync-synth-stopped" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
@ -18,6 +18,7 @@ from ghidratrace.client import Address, RegVal
|
||||
import gdb
|
||||
|
||||
# NOTE: This map is derived from the ldefs using a script
|
||||
# i386 is hand-patched
|
||||
language_map = {
|
||||
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'aarch64:ilp32': ['AARCH64:BE:32:ilp32', 'AARCH64:LE:32:ilp32', 'AARCH64:LE:64:AppleSilicon'],
|
||||
@ -48,6 +49,7 @@ language_map = {
|
||||
'avr:51': ['avr8:LE:16:atmega256'],
|
||||
'avr:6': ['avr8:LE:16:atmega256'],
|
||||
'hppa2.0w': ['pa-risc:BE:32:default'],
|
||||
'i386': ['x86:LE:32:default'],
|
||||
'i386:intel': ['x86:LE:32:default'],
|
||||
'i386:x86-64': ['x86:LE:64:default'],
|
||||
'i386:x86-64:intel': ['x86:LE:64:default'],
|
||||
|
@ -13,7 +13,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import functools
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import gdb
|
||||
|
||||
@ -144,6 +146,27 @@ BRK_STATE = BrkState()
|
||||
INF_STATES = {}
|
||||
|
||||
|
||||
def log_errors(func):
|
||||
'''
|
||||
Wrap a function in a try-except that prints and reraises the
|
||||
exception.
|
||||
|
||||
This is needed because pybag and/or the COM wrappers do not print
|
||||
exceptions that occur during event callbacks.
|
||||
'''
|
||||
|
||||
@functools.wraps(func)
|
||||
def _func(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
return _func
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_inferior(event):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
@ -166,6 +189,7 @@ def on_inferior_selected():
|
||||
commands.activate()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_inferior_deleted(event):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
@ -177,6 +201,7 @@ def on_inferior_deleted(event):
|
||||
commands.put_inferiors() # TODO: Could just delete the one....
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_thread(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -214,6 +239,7 @@ def on_frame_selected():
|
||||
commands.activate()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_syscall_memory():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -221,6 +247,7 @@ def on_syscall_memory():
|
||||
INF_STATES[inf.num].regions = True
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_memory_changed(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -234,6 +261,7 @@ def on_memory_changed(event):
|
||||
pages=False, is_mi=False, from_tty=False)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_register_changed(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -249,6 +277,7 @@ def on_register_changed(event):
|
||||
commands.putreg(event.frame, event.frame.architecture().registers())
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_cont(event):
|
||||
if (HOOK_STATE.check_skip_continue()):
|
||||
return
|
||||
@ -264,6 +293,7 @@ def on_cont(event):
|
||||
state.record_continued()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_stop(event):
|
||||
if hasattr(event, 'breakpoints') and HOOK_STATE.mem_catchpoint in event.breakpoints:
|
||||
HOOK_STATE.skip_continue = True
|
||||
@ -284,6 +314,7 @@ def on_stop(event):
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_exited(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -320,18 +351,22 @@ def modules_changed():
|
||||
INF_STATES[inf.num].modules = True
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_clear_objfiles(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_objfile(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_free_objfile(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_created(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
@ -349,6 +384,7 @@ def on_breakpoint_created(b):
|
||||
ibobj.insert()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_modified(b):
|
||||
if b == HOOK_STATE.mem_catchpoint:
|
||||
return
|
||||
@ -369,10 +405,11 @@ def on_breakpoint_modified(b):
|
||||
# NOTE: Location may not apply to inferior, but whatever.
|
||||
for i in range(new_count, old_count):
|
||||
ikey = commands.INF_BREAK_KEY_PATTERN.format(
|
||||
breaknum=b.number, locnum=i+1)
|
||||
breaknum=b.number, locnum=i + 1)
|
||||
ibobj.set_value(ikey, None)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_deleted(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
@ -390,16 +427,18 @@ def on_breakpoint_deleted(b):
|
||||
trace.proxy_object_path(bpath).remove(tree=True)
|
||||
for i in range(old_count):
|
||||
ikey = commands.INF_BREAK_KEY_PATTERN.format(
|
||||
breaknum=b.number, locnum=i+1)
|
||||
breaknum=b.number, locnum=i + 1)
|
||||
ibobj.set_value(ikey, None)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_before_prompt():
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
# This will be called by a catchpoint
|
||||
class GhidraTraceEventMemoryCommand(gdb.Command):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('hooks-ghidra event-memory', gdb.COMMAND_NONE)
|
||||
|
||||
@ -412,8 +451,11 @@ GhidraTraceEventMemoryCommand()
|
||||
|
||||
|
||||
def cmd_hook(name):
|
||||
|
||||
def _cmd_hook(func):
|
||||
|
||||
class _ActiveCommand(gdb.Command):
|
||||
|
||||
def __init__(self):
|
||||
# It seems we can't hook commands using the Python API....
|
||||
super().__init__(f"hooks-ghidra def-{name}", gdb.COMMAND_USER)
|
||||
@ -432,9 +474,11 @@ def cmd_hook(name):
|
||||
define {name}
|
||||
end
|
||||
""")
|
||||
|
||||
func.hook = _ActiveCommand
|
||||
func.unhook = _unhook_command
|
||||
return func
|
||||
|
||||
return _cmd_hook
|
||||
|
||||
|
||||
@ -463,7 +507,7 @@ def hook_frame_down():
|
||||
on_frame_selected()
|
||||
|
||||
|
||||
# TODO: Checks and workarounds for events missing in gdb 8
|
||||
# TODO: Checks and workarounds for events missing in gdb 9
|
||||
def install_hooks():
|
||||
if HOOK_STATE.installed:
|
||||
return
|
||||
|
@ -289,7 +289,7 @@ class BreakpointLocationInfoReaderV8(object):
|
||||
pass
|
||||
|
||||
def get_locations(self, breakpoint):
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
class BreakpointLocationInfoReaderV13(object):
|
||||
|
@ -0,0 +1,91 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
import gdb
|
||||
|
||||
from . import util
|
||||
from .commands import install, cmd
|
||||
|
||||
|
||||
@install
|
||||
class GhidraWinePrefix(gdb.Command):
|
||||
"""Commands for tracing Wine processes"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('ghidra wine', gdb.COMMAND_SUPPORT, prefix=True)
|
||||
|
||||
|
||||
def is_mapped(pe_file):
|
||||
return pe_file in gdb.execute("info proc mappings", to_string=True)
|
||||
|
||||
|
||||
def set_break(command):
|
||||
breaks_before = set(gdb.breakpoints())
|
||||
gdb.execute(command)
|
||||
return (set(gdb.breakpoints()) - breaks_before).pop()
|
||||
|
||||
|
||||
@cmd('ghidra wine run-to-image', '-ghidra-wine-run-to-image', gdb.COMMAND_SUPPORT, False)
|
||||
def ghidra_wine_run_to_image(pe_file, *, is_mi, **kwargs):
|
||||
mprot_catchpoint = set_break("""
|
||||
catch syscall mprotect
|
||||
commands
|
||||
silent
|
||||
end
|
||||
""".strip())
|
||||
while not is_mapped(pe_file):
|
||||
gdb.execute("continue")
|
||||
mprot_catchpoint.delete()
|
||||
|
||||
|
||||
ORIG_MODULE_INFO_READER = util.MODULE_INFO_READER
|
||||
|
||||
|
||||
class Range(object):
|
||||
|
||||
def expand(self, region):
|
||||
if not hasattr(self, 'min'):
|
||||
self.min = region.start
|
||||
self.max = region.end
|
||||
else:
|
||||
self.min = min(self.min, region.start)
|
||||
self.max = max(self.max, region.end)
|
||||
return self
|
||||
|
||||
|
||||
# There are more, but user can monkey patch this
|
||||
MODULE_SUFFIXES = (".exe", ".dll")
|
||||
|
||||
|
||||
class WineModuleInfoReader(object):
|
||||
|
||||
def get_modules(self):
|
||||
modules = ORIG_MODULE_INFO_READER.get_modules()
|
||||
ranges = dict()
|
||||
for region in util.REGION_INFO_READER.get_regions():
|
||||
if not region.objfile in ranges:
|
||||
ranges[region.objfile] = Range().expand(region)
|
||||
else:
|
||||
ranges[region.objfile].expand(region)
|
||||
for k, v in ranges.items():
|
||||
if k in modules:
|
||||
continue
|
||||
if not k.lower().endswith(MODULE_SUFFIXES):
|
||||
continue
|
||||
modules[k] = util.Module(k, v.min, v.max, {})
|
||||
return modules
|
||||
|
||||
|
||||
util.MODULE_INFO_READER = WineModuleInfoReader()
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
@ -44,6 +45,28 @@ import ghidra.util.layout.PairLayout;
|
||||
|
||||
public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||
implements PropertyChangeListener {
|
||||
|
||||
public static class BigIntEditor extends PropertyEditorSupport {
|
||||
@Override
|
||||
public String getJavaInitializationString() {
|
||||
Object value = getValue();
|
||||
return value == null
|
||||
? "null"
|
||||
: "new BigInteger(\"%s\")".formatted(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
setValue(text == null
|
||||
? null
|
||||
: new BigInteger(text));
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
PropertyEditorManager.registerEditor(BigInteger.class, BigIntEditor.class);
|
||||
}
|
||||
|
||||
private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments";
|
||||
|
||||
static class ChoicesPropertyEditor implements PropertyEditor {
|
||||
|
@ -26,11 +26,12 @@ import ghidra.trace.model.Lifespan;
|
||||
/**
|
||||
* Manages mappings from this trace into static images (Ghida {@link Program}s)
|
||||
*
|
||||
* Most commonly, this is used to map sections listed by a connected debugger to those same sections
|
||||
* of programs already imported into the same Ghidra project. It is vitally important that the image
|
||||
* loaded by the target is an exact copy of the image imported by Ghidra, or else things may not be
|
||||
* aligned.
|
||||
* <p>
|
||||
* Most commonly, this is used to map modules listed by a connected debugger to programs already
|
||||
* imported into the same Ghidra project. It is vitally important that the image loaded by the
|
||||
* target is an exact copy of the image imported by Ghidra, or else things may not be aligned.
|
||||
*
|
||||
* <p>
|
||||
* Note, to best handle mapping ranges to a variety of programs, and to validate the addition of new
|
||||
* entries, it is unlikely a client should consume mapping entries directly. Instead, a service
|
||||
* should track the mappings among all open traces and programs, permitting clients to mutate and
|
||||
@ -42,6 +43,7 @@ public interface TraceStaticMappingManager {
|
||||
/**
|
||||
* Add a new mapping, if not already covered
|
||||
*
|
||||
* <p>
|
||||
* A new mapping may overlap an existing mapping, so long as they agree in address shift.
|
||||
* Furthermore, in such cases, the implementation may coalesce mappings to remove duplication.
|
||||
*
|
||||
@ -50,7 +52,7 @@ public interface TraceStaticMappingManager {
|
||||
* @param toProgramURL the (Ghidra) URL of the static image ("to")
|
||||
* @param toAddress the starting address (in string form) in the static image ("to")
|
||||
* @throws TraceConflictedMappingException if an existing mapping conflicts. See
|
||||
* {@link #isAnyConflicting(AddressRange, Lifespan, URL, String)}
|
||||
* {@link #findAnyConflicting(AddressRange, Lifespan, URL, String)}
|
||||
* @return the new entry, or any entry which subsumes the specified mapping
|
||||
*/
|
||||
TraceStaticMapping add(AddressRange range, Lifespan lifespan, URL toProgramURL,
|
||||
@ -75,14 +77,16 @@ public interface TraceStaticMappingManager {
|
||||
/**
|
||||
* Check if another mapping would conflict with the given prospective mapping
|
||||
*
|
||||
* <p>
|
||||
* Mappings are allowed to overlap, but they must agree on the destination program and address
|
||||
* throughout all overlapping portions.
|
||||
*
|
||||
* TODO: It'd be nice if the manager automatically merged overlapping mappings in agreement or
|
||||
* provided a "deduplicate" method which optimized the entries in the database. This gets
|
||||
* complicated, since we're dealing with overlapping rectangles, not strict one-dimensional
|
||||
* ranges. Look into existing research for optimizing coverage of shapes by rectangles. The same
|
||||
* is needed for property maps in 2 dimensions.
|
||||
* <p>
|
||||
* <b>TODO</b>: It'd be nice if the manager automatically merged overlapping mappings in
|
||||
* agreement or provided a "de-duplicate" method which optimized the entries in the database.
|
||||
* This gets complicated, since we're dealing with overlapping rectangles, not strict
|
||||
* one-dimensional ranges. Look into existing research for optimizing coverage of shapes by
|
||||
* rectangles. The same is needed for property maps in 2 dimensions.
|
||||
*
|
||||
* @param range the range in the trace ("from")
|
||||
* @param lifespan the span of time in the trace
|
||||
@ -96,6 +100,7 @@ public interface TraceStaticMappingManager {
|
||||
/**
|
||||
* Find all mappings which overlap the given adddress range and span of time
|
||||
*
|
||||
* <p>
|
||||
* Note, this returns overlapping entries whether or not they conflict.
|
||||
*
|
||||
* @param range the range in the trace ("from")
|
||||
|
Loading…
Reference in New Issue
Block a user