This series intent is to let winedbg (and dbghelp) load the 64bit PE modules of a wow64 debuggee.
At this stage, all debug info of all modules (PE and ELF) are properly loaded (and displayed with 'info wow share' command). Breakpoint and backtrace in 64bit code of a wow64 debuggee are not available.
The serie contains: - extension of tests to show that 64bit load dll events are generated for a wow64 debuggee (for a 64bit debugger, not for a 32bit one) - change for adapting filtering of events
I opted for doing it in ntdll and passing the machine of the mapping in request's reply. Please advisde if you'd prefer another approach.
-- v2: server: Generate dll events for 64bit DLLs in Wow64. kernel32/tests: Extend the tests for load/unload debug events on Wow64.
From: Eric Pouech eric.pouech@gmail.com
Showing that: - load/unload events for 64bit DLLs are generated for a WOW64 process. - 64bit startup exception is generated as well (with a specific exception code)
Signed-off-by: Eric Pouech eric.pouech@gmail.com --- dlls/kernel32/tests/debugger.c | 94 ++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+)
diff --git a/dlls/kernel32/tests/debugger.c b/dlls/kernel32/tests/debugger.c index 2491b1093f1..f27db2872d1 100644 --- a/dlls/kernel32/tests/debugger.c +++ b/dlls/kernel32/tests/debugger.c @@ -1077,6 +1077,99 @@ static void test_debug_loop(int argc, char **argv) ok(ret, "DeleteFileA failed, last error %#lx.\n", GetLastError()); }
+static void test_debug_loop_wow64(void) +{ + WCHAR buffer[MAX_PATH], *p; + PROCESS_INFORMATION pi; + STARTUPINFOW si; + BOOL ret; + unsigned order = 0, bp_order = 0, bpwx_order = 0, num_ntdll = 0, num_wow64 = 0; + + /* checking conditions for running this test */ + if (GetSystemWow64DirectoryW( buffer, ARRAY_SIZE(buffer) ) && sizeof(void*) > sizeof(int) && pGetMappedFileNameW) + { + wcscat( buffer, L"\msinfo32.exe" ); + ret = GetFileAttributesW( buffer ) != INVALID_FILE_ATTRIBUTES; + } + else ret = FALSE; + if (!ret) + { + skip("Skipping test on incompatible config\n"); + return; + } + memset( &si, 0, sizeof(si) ); + si.cb = sizeof(si); + ret = CreateProcessW( NULL, buffer, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi ); + ok(ret, "CreateProcess failed, last error %#lx.\n", GetLastError()); + + for (;;) + { + DEBUG_EVENT ev; + + ++order; + ret = WaitForDebugEvent( &ev, 2000 ); + if (!ret) break; + + switch (ev.dwDebugEventCode) + { + case CREATE_PROCESS_DEBUG_EVENT: + break; + case LOAD_DLL_DEBUG_EVENT: + if (!pGetMappedFileNameW( pi.hProcess, ev.u.LoadDll.lpBaseOfDll, buffer, ARRAY_SIZE(buffer) )) buffer[0] = L'\0'; + if ((p = wcsrchr( buffer, '\' ))) p++; + else p = buffer; + if (!memcmp( p, L"wow64", 5 * sizeof(WCHAR) )) + { + /* on Win10, wow64cpu's load dll event is received after first exception */ + ok(bpwx_order == 0, "loaddll for wow64 DLLs should appear before exception\n"); + num_wow64++; + } + else if (!wcsicmp( p, L"ntdll.dll" )) + { + ok(bp_order == 0 && bpwx_order == 0, "loaddll on ntdll should appear before exception\n"); + num_ntdll++; + } + break; + case EXCEPTION_DEBUG_EVENT: + if (ev.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) + bp_order = order; + else if (ev.u.Exception.ExceptionRecord.ExceptionCode == STATUS_WX86_BREAKPOINT) + bpwx_order = order; + } + ret = ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE); + ok(ret, "ContinueDebugEvent failed, last error %#lx.\n", GetLastError()); + if (!ret) break; + } + + ret = TerminateProcess( pi.hProcess, 0 ); + ok(ret, "TerminateProcess failed: %lu\n", GetLastError()); + + /* eat up the remaining events */ + for (;;) + { + DEBUG_EVENT ev; + + if (!WaitForDebugEvent( &ev, 2000 )) break; + } + + ret = CloseHandle(pi.hThread); + ok(ret, "CloseHandle failed, last error %#lx.\n", GetLastError()); + ret = CloseHandle(pi.hProcess); + ok(ret, "CloseHandle failed, last error %#lx.\n", GetLastError()); + + todo_wine + { + ok(num_ntdll == 2, "Expecting two ntdll instances\n"); + ok(num_wow64 >= 3, "Expecting more than 3 wow64*.dll\n"); + } + ok(bp_order, "Expecting 1 bp exceptions\n"); + todo_wine + { + ok(bpwx_order, "Expecting 1 bpwx exceptions\n"); + ok(bp_order < bpwx_order, "Out of order bp exceptions\n"); + } +} + static void doChildren(int argc, char **argv) { const char *arguments = "debugger children last"; @@ -2284,6 +2377,7 @@ START_TEST(debugger) test_ExitCode(); test_RemoteDebugger(); test_debug_loop(myARGC, myARGV); + test_debug_loop_wow64(); test_debug_children(myARGV[0], DEBUG_PROCESS, TRUE, FALSE); test_debug_children(myARGV[0], DEBUG_ONLY_THIS_PROCESS, FALSE, FALSE); test_debug_children(myARGV[0], DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS, FALSE, FALSE);
From: Eric Pouech eric.pouech@gmail.com
Signed-off-by: Eric Pouech eric.pouech@gmail.com --- dlls/kernel32/tests/debugger.c | 7 ++++++- dlls/ntdll/unix/sync.c | 27 +++++++++++++++++++++++++++ include/wine/server_protocol.h | 4 +++- server/debugger.c | 25 +++++++++++++++++++++++++ server/mapping.c | 3 --- server/protocol.def | 2 ++ server/request.h | 4 +++- server/trace.c | 1 + 8 files changed, 67 insertions(+), 6 deletions(-)
diff --git a/dlls/kernel32/tests/debugger.c b/dlls/kernel32/tests/debugger.c index f27db2872d1..4879a729169 100644 --- a/dlls/kernel32/tests/debugger.c +++ b/dlls/kernel32/tests/debugger.c @@ -1157,11 +1157,16 @@ static void test_debug_loop_wow64(void) ret = CloseHandle(pi.hProcess); ok(ret, "CloseHandle failed, last error %#lx.\n", GetLastError());
- todo_wine + if (strcmp(winetest_platform, "wine") || num_wow64) /* windows or new wine wow */ { ok(num_ntdll == 2, "Expecting two ntdll instances\n"); ok(num_wow64 >= 3, "Expecting more than 3 wow64*.dll\n"); } + else /* Wine's old wow, or 32/64 bit only configurations */ + { + ok(num_ntdll == 1, "Expecting 1 ntdll instances\n"); + ok(num_wow64 == 0, "Expecting 0 wow64*.dll\n"); + } ok(bp_order, "Expecting 1 bp exceptions\n"); todo_wine { diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 22a3555ba3f..cfcac3735f2 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -1017,6 +1017,26 @@ static NTSTATUS event_data_to_state_change( const debug_event_t *data, DBGUI_WAI return STATUS_INTERNAL_ERROR; }
+/* Don't expose 64bit load/unload dll event for a 32bit caller; helper for NtWaitForDebugEvent */ +static BOOL filter_out_event( HANDLE handle, DBGUI_WAIT_STATE_CHANGE *state, USHORT machine ) +{ + switch (state->NewState) + { + case DbgLoadDllStateChange: + case DbgUnloadDllStateChange: + if (NtCurrentTeb()->WowTebOffset && is_machine_64bit( machine )) + { + if (state->NewState == DbgLoadDllStateChange) + NtClose( state->StateInfo.LoadDll.FileHandle ); + NtDebugContinue( handle, &state->AppClientId, DBG_CONTINUE ); + return TRUE; + } + break; + default: break; + } + return FALSE; +} + /********************************************************************** * NtWaitForDebugEvent (NTDLL.@) */ @@ -1026,6 +1046,7 @@ NTSTATUS WINAPI NtWaitForDebugEvent( HANDLE handle, BOOLEAN alertable, LARGE_INT debug_event_t data; unsigned int ret; BOOL wait = TRUE; + unsigned int machine;
for (;;) { @@ -1039,10 +1060,16 @@ NTSTATUS WINAPI NtWaitForDebugEvent( HANDLE handle, BOOLEAN alertable, LARGE_INT state->NewState = data.code; state->AppClientId.UniqueProcess = ULongToHandle( reply->pid ); state->AppClientId.UniqueThread = ULongToHandle( reply->tid ); + machine = reply->machine; } } SERVER_END_REQ;
+ if (!ret && filter_out_event( handle, state, machine )) + { + wait = TRUE; + continue; + } if (ret != STATUS_PENDING) return ret; if (!wait) return STATUS_TIMEOUT; wait = FALSE; diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index f61918271ff..f3af2f4636d 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -2117,6 +2117,8 @@ struct wait_debug_event_reply struct reply_header __header; process_id_t pid; thread_id_t tid; + unsigned int machine; + int __pad; /* VARARG(event,debug_event); */ };
@@ -6358,7 +6360,7 @@ union generic_reply
/* ### protocol_version begin ### */
-#define SERVER_PROTOCOL_VERSION 762 +#define SERVER_PROTOCOL_VERSION 763
/* ### protocol_version end ### */
diff --git a/server/debugger.c b/server/debugger.c index 48adb244b09..6d0942f5770 100644 --- a/server/debugger.c +++ b/server/debugger.c @@ -531,6 +531,30 @@ void debugger_detach( struct process *process, struct debug_obj *debug_obj ) resume_process( process ); }
+static unsigned int get_machine_from_event( struct debug_event *event ) +{ + struct memory_view *view; + const pe_image_info_t *info; + client_ptr_t base; + + switch (event->data.code) + { + case DbgLoadDllStateChange: + base = event->data.load_dll.base; + break; + case DbgUnloadDllStateChange: + base = event->data.unload_dll.base; + break; + default: + base = 0; + break; + } + if (!base || !(view = find_mapped_view( event->sender->process, base )) || + !(info = get_view_image_info( view, &base ))) + return IMAGE_FILE_MACHINE_UNKNOWN; + return info->machine; +} + /* create a debug object */ DECL_HANDLER(create_debug_obj) { @@ -567,6 +591,7 @@ DECL_HANDLER(wait_debug_event) event->sender->process->debug_event = event; reply->pid = get_process_id( event->sender->process ); reply->tid = get_thread_id( event->sender ); + reply->machine = get_machine_from_event( event ); alloc_event_handles( event, current->process ); set_reply_data( &event->data, min( get_reply_max_size(), sizeof(event->data) )); } diff --git a/server/mapping.c b/server/mapping.c index f14ee11cc62..8c436623600 100644 --- a/server/mapping.c +++ b/server/mapping.c @@ -378,10 +378,7 @@ static void set_process_machine( struct process *process, struct memory_view *vi
static int generate_dll_event( struct thread *thread, int code, struct memory_view *view ) { - unsigned short process_machine = thread->process->machine; - if (!(view->flags & SEC_IMAGE)) return 0; - if (process_machine != native_machine && process_machine != view->image.machine) return 0; generate_debug_event( thread, code, view ); return 1; } diff --git a/server/protocol.def b/server/protocol.def index c857f72ce68..98b6a06b0cd 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1681,6 +1681,8 @@ struct process_info @REPLY process_id_t pid; /* process id */ thread_id_t tid; /* thread id */ + unsigned int machine; + int __pad; VARARG(event,debug_event); /* debug event data */ @END
diff --git a/server/request.h b/server/request.h index b8d4f6a6d5e..8f5218fc3b4 100644 --- a/server/request.h +++ b/server/request.h @@ -1153,7 +1153,9 @@ C_ASSERT( FIELD_OFFSET(struct wait_debug_event_request, debug) == 12 ); C_ASSERT( sizeof(struct wait_debug_event_request) == 16 ); C_ASSERT( FIELD_OFFSET(struct wait_debug_event_reply, pid) == 8 ); C_ASSERT( FIELD_OFFSET(struct wait_debug_event_reply, tid) == 12 ); -C_ASSERT( sizeof(struct wait_debug_event_reply) == 16 ); +C_ASSERT( FIELD_OFFSET(struct wait_debug_event_reply, machine) == 16 ); +C_ASSERT( FIELD_OFFSET(struct wait_debug_event_reply, __pad) == 20 ); +C_ASSERT( sizeof(struct wait_debug_event_reply) == 24 ); C_ASSERT( FIELD_OFFSET(struct queue_exception_event_request, first) == 12 ); C_ASSERT( FIELD_OFFSET(struct queue_exception_event_request, code) == 16 ); C_ASSERT( FIELD_OFFSET(struct queue_exception_event_request, flags) == 20 ); diff --git a/server/trace.c b/server/trace.c index fb006f605b5..3013663d0e7 100644 --- a/server/trace.c +++ b/server/trace.c @@ -2246,6 +2246,7 @@ static void dump_wait_debug_event_reply( const struct wait_debug_event_reply *re { fprintf( stderr, " pid=%04x", req->pid ); fprintf( stderr, ", tid=%04x", req->tid ); + fprintf( stderr, ", machine=%08x", req->machine ); dump_varargs_debug_event( ", event=", cur_size ); }
I opted for doing it in ntdll and passing the machine of the mapping in request's reply. Please advisde if you'd prefer another approach.
It should probably be done in the NtWaitForDebugEvent thunk in wow64.dll.
On Thu Mar 9 17:52:36 2023 +0000, Alexandre Julliard wrote:
I opted for doing it in ntdll and passing the machine of the mapping
in request's reply. Please advisde if you'd prefer another approach. It should probably be done in the NtWaitForDebugEvent thunk in wow64.dll.
that wouldn't work for a mono-arch config as wow64.dll isn't loaded (or you mean keep filtering in server in mono-arch case, and filter in wow64 for multi-arch?)
On Thu Mar 9 17:52:36 2023 +0000, eric pouech wrote:
that wouldn't work for a mono-arch config as wow64.dll isn't loaded (or you mean keep filtering in server in mono-arch case, and filter in wow64 for multi-arch?)
The filtering can be duplicated in ntdll inside a #ifndef _WIN64, to be removed eventually.