blob: e40745c54bebf04aedca8b1fc1d1569e6945c94b [file] [edit]
"""
Test that lldb ignores the Windows loader breakpoint when attaching to a
process, but still stops at a genuine breakpoint instruction (an int3) that
lives in the program's own code.
"""
import ctypes
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class WindowsAttachLoaderBreakpointTestCase(TestBase):
def create_suspended_process(self, exe):
"""Create ``exe`` suspended and return its (pid, hProcess, hThread).
This mirrors what the lldb-dap runInTerminal launcher does on Windows:
the debuggee is started with CREATE_SUSPENDED so lldb can attach while
the process is still being initialized by the loader. The launcher then
resumes the main thread once the debugger has attached.
"""
from ctypes import wintypes
CREATE_SUSPENDED = 0x00000004
class STARTUPINFOW(ctypes.Structure):
_fields_ = [
("cb", wintypes.DWORD),
("lpReserved", wintypes.LPWSTR),
("lpDesktop", wintypes.LPWSTR),
("lpTitle", wintypes.LPWSTR),
("dwX", wintypes.DWORD),
("dwY", wintypes.DWORD),
("dwXSize", wintypes.DWORD),
("dwYSize", wintypes.DWORD),
("dwXCountChars", wintypes.DWORD),
("dwYCountChars", wintypes.DWORD),
("dwFillAttribute", wintypes.DWORD),
("dwFlags", wintypes.DWORD),
("wShowWindow", wintypes.WORD),
("cbReserved2", wintypes.WORD),
("lpReserved2", ctypes.POINTER(ctypes.c_byte)),
("hStdInput", wintypes.HANDLE),
("hStdOutput", wintypes.HANDLE),
("hStdError", wintypes.HANDLE),
]
class PROCESS_INFORMATION(ctypes.Structure):
_fields_ = [
("hProcess", wintypes.HANDLE),
("hThread", wintypes.HANDLE),
("dwProcessId", wintypes.DWORD),
("dwThreadId", wintypes.DWORD),
]
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.CreateProcessW.argtypes = [
wintypes.LPCWSTR,
wintypes.LPWSTR,
ctypes.c_void_p,
ctypes.c_void_p,
wintypes.BOOL,
wintypes.DWORD,
ctypes.c_void_p,
wintypes.LPCWSTR,
ctypes.POINTER(STARTUPINFOW),
ctypes.POINTER(PROCESS_INFORMATION),
]
kernel32.CreateProcessW.restype = wintypes.BOOL
startupinfo = STARTUPINFOW()
startupinfo.cb = ctypes.sizeof(STARTUPINFOW)
process_information = PROCESS_INFORMATION()
# CreateProcessW may modify the command-line buffer, so it must be
# writable.
command_line = ctypes.create_unicode_buffer('"{}"'.format(exe))
if not kernel32.CreateProcessW(
exe,
command_line,
None,
None,
False,
CREATE_SUSPENDED,
None,
None,
ctypes.byref(startupinfo),
ctypes.byref(process_information),
):
raise OSError(ctypes.get_last_error(), "CreateProcessW failed")
return (
process_information.dwProcessId,
process_information.hProcess,
process_information.hThread,
)
@skipUnlessWindows
def test_attach_ignores_loader_breakpoint(self):
"""
lldb must not report the loader's int3 (raised in a system module while
attaching) as a user-visible stop, but must still stop at the
__builtin_debugtrap() in the program's own code.
"""
self.build()
exe = self.getBuildArtifact("a.out")
pid, hProcess, hThread = self.create_suspended_process(exe)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.ResumeThread.argtypes = [ctypes.c_void_p]
kernel32.ResumeThread.restype = ctypes.c_uint
def cleanup():
kernel32.TerminateProcess(hProcess, 0)
kernel32.CloseHandle(hThread)
kernel32.CloseHandle(hProcess)
self.addTearDownHook(cleanup)
self.dbg.SetAsync(False)
# Attach while the process is suspended and still being initialized.
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)
error = lldb.SBError()
process = target.AttachToProcessWithID(self.dbg.GetListener(), pid, error)
self.assertSuccess(error, "attach to the suspended process")
self.assertState(process.GetState(), lldb.eStateStopped)
self.assertNotEqual(
kernel32.ResumeThread(hThread), 0xFFFFFFFF, "ResumeThread failed"
)
# Continuing must run past the loader breakpoint (skipped because it is
# raised in a system module) and stop at the program's own
# __builtin_debugtrap().
process.Continue()
self.assertState(process.GetState(), lldb.eStateStopped)
thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonException)
self.assertIsNotNone(
thread, "the process should stop at the user __builtin_debugtrap()"
)
function_name = thread.GetFrameAtIndex(0).GetFunctionName()
self.assertIn(
"main",
function_name,
"expected to stop at the program's __builtin_debugtrap(), but "
"stopped in '{}' (a spurious loader breakpoint in a system module "
"was not skipped)".format(function_name),
)
process.Continue()
self.assertState(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)