xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py (revision f98ee40f4b5d7474fc67e82824bf6abbaedb7b1c)
1# DExTer : Debugging Experience Tester
2# ~~~~~~   ~         ~~         ~   ~~
3#
4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5# See https://llvm.org/LICENSE.txt for license information.
6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7
8from ctypes import *
9
10from . import client
11from . import control
12from . import symbols
13from .probe_process import probe_state
14from .utils import *
15
16
17class STARTUPINFOA(Structure):
18    _fields_ = [
19        ("cb", c_ulong),
20        ("lpReserved", c_char_p),
21        ("lpDesktop", c_char_p),
22        ("lpTitle", c_char_p),
23        ("dwX", c_ulong),
24        ("dwY", c_ulong),
25        ("dwXSize", c_ulong),
26        ("dwYSize", c_ulong),
27        ("dwXCountChars", c_ulong),
28        ("dwYCountChars", c_ulong),
29        ("dwFillAttribute", c_ulong),
30        ("wShowWindow", c_ushort),
31        ("cbReserved2", c_ushort),
32        ("lpReserved2", c_char_p),
33        ("hStdInput", c_void_p),
34        ("hStdOutput", c_void_p),
35        ("hStdError", c_void_p),
36    ]
37
38
39class PROCESS_INFORMATION(Structure):
40    _fields_ = [
41        ("hProcess", c_void_p),
42        ("hThread", c_void_p),
43        ("dwProcessId", c_ulong),
44        ("dwThreadId", c_ulong),
45    ]
46
47
48def fetch_local_function_syms(Symbols, prefix):
49    syms = Symbols.get_all_functions()
50
51    def is_sym_in_src_dir(sym):
52        name, data = sym
53        symdata = Symbols.GetLineByOffset(data.Offset)
54        if symdata is not None:
55            srcfile, line = symdata
56            if prefix in srcfile:
57                return True
58        return False
59
60    syms = [x for x in syms if is_sym_in_src_dir(x)]
61    return syms
62
63
64def break_on_all_but_main(Control, Symbols, main_offset):
65    mainfile, _ = Symbols.GetLineByOffset(main_offset)
66    prefix = "\\".join(mainfile.split("\\")[:-1])
67
68    for name, rec in fetch_local_function_syms(Symbols, prefix):
69        if name == "main":
70            continue
71        bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True)
72
73    # All breakpoints are currently discarded: we just sys.exit for cleanup
74    return
75
76
77def setup_everything(binfile):
78    from . import client
79    from . import symbols
80
81    Client = client.Client()
82
83    Client.Control.SetEngineOptions(0x20)  # DEBUG_ENGOPT_INITIAL_BREAK
84
85    Client.CreateProcessAndAttach2(binfile)
86
87    # Load lines as well as general symbols
88    sym_opts = Client.Symbols.GetSymbolOptions()
89    sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES
90    Client.Symbols.SetSymbolOptions(sym_opts)
91
92    # Need to enter the debugger engine to let it attach properly.
93    res = Client.Control.WaitForEvent(timeout=1000)
94    if res == S_FALSE:
95        # The debugee apparently didn't do anything at all. Rather than risk
96        # hanging, bail out at this point.
97        client.TerminateProcesses()
98        raise Exception("Debuggee did not start in a timely manner")
99
100    # Enable line stepping.
101    Client.Control.Execute("l+t")
102    # Enable C++ expression interpretation.
103    Client.Control.SetExpressionSyntax(cpp=True)
104
105    # We've requested to break into the process at the earliest opportunity,
106    # and WaitForEvent'ing means we should have reached that break state.
107    # Now set a breakpoint on the main symbol, and "go" until we reach it.
108    module_name = Client.Symbols.get_exefile_module_name()
109    offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name))
110    breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True)
111    Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO)
112
113    # Problem: there is no guarantee that the client will ever reach main,
114    # something else exciting could happen in that time, the host system may
115    # be very loaded, and similar. Wait for some period, say, five seconds, and
116    # abort afterwards: this is a trade-off between spurious timeouts and
117    # completely hanging in the case of a environmental/programming error.
118    res = Client.Control.WaitForEvent(timeout=5000)
119    if res == S_FALSE:
120        client.TerminateProcesses()
121        raise Exception("Debuggee did not reach main function in a timely manner")
122
123    break_on_all_but_main(Client.Control, Client.Symbols, offset)
124
125    # Set the default action on all exceptions to be "quit and detach". If we
126    # don't, dbgeng will merrily spin at the exception site forever.
127    filts = Client.Control.GetNumberEventFilters()
128    for x in range(filts[0], filts[0] + filts[1]):
129        Client.Control.SetExceptionFilterSecondCommand(x, "qd")
130
131    return Client
132
133
134def step_once(client):
135    client.Control.Execute("p")
136    try:
137        client.Control.WaitForEvent()
138    except Exception as e:
139        if (
140            client.Control.GetExecutionStatus()
141            == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE
142        ):
143            return None  # Debuggee has gone away, likely due to an exception.
144        raise e
145    # Could assert here that we're in the "break" state
146    client.Control.GetExecutionStatus()
147    return probe_state(client)
148
149
150def main_loop(client):
151    res = True
152    while res is not None:
153        res = step_once(client)
154
155
156def cleanup(client):
157    client.TerminateProcesses()
158