# 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()