summaryrefslogtreecommitdiffstats
path: root/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py
blob: 30a62f6dd42b1de14b86595f7c4fc7826fd239ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# 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 process_creator(binfile):
  Kernel32 = WinDLL("Kernel32")

  # Another flavour of process creation
  startupinfoa = STARTUPINFOA()
  startupinfoa.cb = sizeof(STARTUPINFOA)
  startupinfoa.lpReserved = None
  startupinfoa.lpDesktop = None
  startupinfoa.lpTitle = None
  startupinfoa.dwX = 0
  startupinfoa.dwY = 0
  startupinfoa.dwXSize = 0
  startupinfoa.dwYSize = 0
  startupinfoa.dwXCountChars = 0
  startupinfoa.dwYCountChars = 0
  startupinfoa.dwFillAttribute = 0
  startupinfoa.dwFlags = 0
  startupinfoa.wShowWindow = 0
  startupinfoa.cbReserved2 = 0
  startupinfoa.lpReserved2 = None
  startupinfoa.hStdInput = None
  startupinfoa.hStdOutput = None
  startupinfoa.hStdError = None
  processinformation = PROCESS_INFORMATION()

  # 0x4 below specifies CREATE_SUSPENDED.
  ret = Kernel32.CreateProcessA(binfile.encode("ascii"), None, None, None, False, 0x4, None, None, byref(startupinfoa), byref(processinformation))
  if ret == 0:
    raise Exception('CreateProcess running {}'.format(binfile))

  return processinformation.dwProcessId, processinformation.dwThreadId, processinformation.hProcess, processinformation.hThread

def thread_resumer(hProcess, hThread):
  Kernel32 = WinDLL("Kernel32")

  # For reasons unclear to me, other suspend-references seem to be opened on
  # the opened thread. Clear them all.
  while True:
    ret = Kernel32.ResumeThread(hThread)
    if ret <= 0:
      break
  if ret < 0:
    Kernel32.TerminateProcess(hProcess, 1)
    raise Exception("Couldn't resume process after startup")

  return

def setup_everything(binfile):
  from . import client
  from . import symbols
  Client = client.Client()

  created_pid, created_tid, hProcess, hThread = process_creator(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)

  Client.AttachProcess(created_pid)

  # Need to enter the debugger engine to let it attach properly
  Client.Control.WaitForEvent(timeout=1)
  Client.SysObjects.set_current_thread(created_pid, created_tid)
  Client.Control.Execute("l+t")
  Client.Control.SetExpressionSyntax(cpp=True)

  module_name = Client.Symbols.get_exefile_module_name()
  offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name))
  breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True)
  thread_resumer(hProcess, hThread)
  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:
    Kernel32.TerminateProcess(hProcess, 1)
    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, hProcess

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, hProcess):
  res = client.DetachProcesses()
  Kernel32 = WinDLL("Kernel32")
  Kernel32.TerminateProcess(hProcess, 1)
OpenPOWER on IntegriCloud