You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
4.8 KiB
146 lines
4.8 KiB
# DExTer : Debugging Experience Tester
|
|
# ~~~~~~ ~ ~~ ~ ~~
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
from ctypes import *
|
|
|
|
from . import client
|
|
from . import control
|
|
from . import symbols
|
|
from .probe_process import probe_state
|
|
from .utils import *
|
|
|
|
class STARTUPINFOA(Structure):
|
|
_fields_ = [
|
|
('cb', c_ulong),
|
|
('lpReserved', c_char_p),
|
|
('lpDesktop', c_char_p),
|
|
('lpTitle', c_char_p),
|
|
('dwX', c_ulong),
|
|
('dwY', c_ulong),
|
|
('dwXSize', c_ulong),
|
|
('dwYSize', c_ulong),
|
|
('dwXCountChars', c_ulong),
|
|
('dwYCountChars', c_ulong),
|
|
('dwFillAttribute', c_ulong),
|
|
('wShowWindow', c_ushort),
|
|
('cbReserved2', c_ushort),
|
|
('lpReserved2', c_char_p),
|
|
('hStdInput', c_void_p),
|
|
('hStdOutput', c_void_p),
|
|
('hStdError', c_void_p)
|
|
]
|
|
|
|
class PROCESS_INFORMATION(Structure):
|
|
_fields_ = [
|
|
('hProcess', c_void_p),
|
|
('hThread', c_void_p),
|
|
('dwProcessId', c_ulong),
|
|
('dwThreadId', c_ulong)
|
|
]
|
|
|
|
def fetch_local_function_syms(Symbols, prefix):
|
|
syms = Symbols.get_all_functions()
|
|
|
|
def is_sym_in_src_dir(sym):
|
|
name, data = sym
|
|
symdata = Symbols.GetLineByOffset(data.Offset)
|
|
if symdata is not None:
|
|
srcfile, line = symdata
|
|
if prefix in srcfile:
|
|
return True
|
|
return False
|
|
|
|
syms = [x for x in syms if is_sym_in_src_dir(x)]
|
|
return syms
|
|
|
|
def break_on_all_but_main(Control, Symbols, main_offset):
|
|
mainfile, _ = Symbols.GetLineByOffset(main_offset)
|
|
prefix = '\\'.join(mainfile.split('\\')[:-1])
|
|
|
|
for name, rec in fetch_local_function_syms(Symbols, prefix):
|
|
if name == "main":
|
|
continue
|
|
bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True)
|
|
|
|
# All breakpoints are currently discarded: we just sys.exit for cleanup
|
|
return
|
|
|
|
def setup_everything(binfile):
|
|
from . import client
|
|
from . import symbols
|
|
Client = client.Client()
|
|
|
|
Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK
|
|
|
|
Client.CreateProcessAndAttach2(binfile)
|
|
|
|
# Load lines as well as general symbols
|
|
sym_opts = Client.Symbols.GetSymbolOptions()
|
|
sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES
|
|
Client.Symbols.SetSymbolOptions(sym_opts)
|
|
|
|
# Need to enter the debugger engine to let it attach properly.
|
|
res = Client.Control.WaitForEvent(timeout=1000)
|
|
if res == S_FALSE:
|
|
# The debugee apparently didn't do anything at all. Rather than risk
|
|
# hanging, bail out at this point.
|
|
client.TerminateProcesses()
|
|
raise Exception("Debuggee did not start in a timely manner")
|
|
|
|
# Enable line stepping.
|
|
Client.Control.Execute("l+t")
|
|
# Enable C++ expression interpretation.
|
|
Client.Control.SetExpressionSyntax(cpp=True)
|
|
|
|
# We've requested to break into the process at the earliest opportunity,
|
|
# and WaitForEvent'ing means we should have reached that break state.
|
|
# Now set a breakpoint on the main symbol, and "go" until we reach it.
|
|
module_name = Client.Symbols.get_exefile_module_name()
|
|
offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name))
|
|
breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True)
|
|
Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO)
|
|
|
|
# Problem: there is no guarantee that the client will ever reach main,
|
|
# something else exciting could happen in that time, the host system may
|
|
# be very loaded, and similar. Wait for some period, say, five seconds, and
|
|
# abort afterwards: this is a trade-off between spurious timeouts and
|
|
# completely hanging in the case of a environmental/programming error.
|
|
res = Client.Control.WaitForEvent(timeout=5000)
|
|
if res == S_FALSE:
|
|
client.TerminateProcesses()
|
|
raise Exception("Debuggee did not reach main function in a timely manner")
|
|
|
|
break_on_all_but_main(Client.Control, Client.Symbols, offset)
|
|
|
|
# Set the default action on all exceptions to be "quit and detach". If we
|
|
# don't, dbgeng will merrily spin at the exception site forever.
|
|
filts = Client.Control.GetNumberEventFilters()
|
|
for x in range(filts[0], filts[0] + filts[1]):
|
|
Client.Control.SetExceptionFilterSecondCommand(x, "qd")
|
|
|
|
return Client
|
|
|
|
def step_once(client):
|
|
client.Control.Execute("p")
|
|
try:
|
|
client.Control.WaitForEvent()
|
|
except Exception as e:
|
|
if client.Control.GetExecutionStatus() == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE:
|
|
return None # Debuggee has gone away, likely due to an exception.
|
|
raise e
|
|
# Could assert here that we're in the "break" state
|
|
client.Control.GetExecutionStatus()
|
|
return probe_state(client)
|
|
|
|
def main_loop(client):
|
|
res = True
|
|
while res is not None:
|
|
res = step_once(client)
|
|
|
|
def cleanup(client):
|
|
client.TerminateProcesses()
|