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.
-- v3: server,ntdll,wow64: Move filter of (un)load DLL debug events to client side. kernel32/tests: Extend the tests for load/unload debug events on Wow64.
From: Eric Pouech eric.pouech@gmail.com
Showing that: - load events for 64bit DLLs are generated for a WOW64 process. - unload events for 64bit DLLs are not generated for a WOW64 process (as any other unload event on process teardown). - a 32bit 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 | 153 +++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+)
diff --git a/dlls/kernel32/tests/debugger.c b/dlls/kernel32/tests/debugger.c index 2491b1093f1..64662709e2a 100644 --- a/dlls/kernel32/tests/debugger.c +++ b/dlls/kernel32/tests/debugger.c @@ -1077,6 +1077,158 @@ static void test_debug_loop(int argc, char **argv) ok(ret, "DeleteFileA failed, last error %#lx.\n", GetLastError()); }
+struct find_main_window +{ + DWORD pid; + unsigned count; + HWND windows[5]; +}; + +static BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam) +{ + struct find_main_window* fmw = (struct find_main_window*)lParam; + DWORD pid = 0; + + if (GetWindowThreadProcessId(handle, &pid) && fmw->pid == pid && + !GetWindow(handle, GW_OWNER)) + { + ok(fmw->count < ARRAY_SIZE(fmw->windows), "Too many windows\n"); + if (fmw->count < ARRAY_SIZE(fmw->windows)) + fmw->windows[fmw->count++] = handle; + } + return TRUE; +} + +static void close_main_windows(DWORD pid) +{ + struct find_main_window fmw = {pid, 0}; + unsigned i; + + EnumWindows(enum_windows_callback, (LPARAM)&fmw); + ok(fmw.count, "no window found\n"); + for (i = 0; i < fmw.count; i++) + PostMessageA(fmw.windows[i], WM_CLOSE, 0, 0); +} + +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; + } + + /* gracefully terminates msinfo32 */ + close_main_windows( pi.dwProcessId ); + + /* eat up the remaining events... not generating unload dll events in case of process termination */ + for (;;) + { + DEBUG_EVENT ev; + + ret = WaitForDebugEvent( &ev, 2000 ); + if (!ret || ev.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break; + switch (ev.dwDebugEventCode) + { + default: + ok(0, "Unexpected event: %lu\n", ev.dwDebugEventCode); + /* fall through */ + case EXIT_PROCESS_DEBUG_EVENT: + case EXIT_THREAD_DEBUG_EVENT: + ret = ContinueDebugEvent( ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE ); + ok(ret, "ContinueDebugEvent failed, last error %#lx.\n", GetLastError()); + break; + } + } + + ret = WaitForSingleObject( pi.hProcess, 2000 ); + if (ret != WAIT_OBJECT_0) + { + DWORD ec; + ret = GetExitCodeProcess( pi.hProcess, &ec ); + ok(ret, "GetExitCodeProcess failed: %lu\n", GetLastError()); + ok(ec != STILL_ACTIVE, "GetExitCodeProcess still active\n"); + } + + 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()); + + 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 one ntdll instances\n"); + ok(num_wow64 == 0, "Expecting more no 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 +2436,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
Code is duplicated in ntdll.dll (for 32bit only and old Wow configuration) and wow64.dll for new Wow64 configurations.
Signed-off-by: Eric Pouech eric.pouech@gmail.com --- dlls/ntdll/unix/sync.c | 54 +++++++++++++++++++++++++++++++++- dlls/wow64/sync.c | 66 +++++++++++++++++++++++++++++++++++++++++- server/mapping.c | 3 -- 3 files changed, 118 insertions(+), 5 deletions(-)
diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 22a3555ba3f..831c16130b9 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -1008,15 +1008,48 @@ static NTSTATUS event_data_to_state_change( const debug_event_t *data, DBGUI_WAI info->DebugInfoFileOffset = data->load_dll.dbg_offset; info->DebugInfoSize = data->load_dll.dbg_size; info->NamePointer = wine_server_get_ptr( data->load_dll.name ); + if ((DWORD_PTR)data->load_dll.base != data->load_dll.base) + return STATUS_PARTIAL_COPY; return STATUS_SUCCESS; } case DbgUnloadDllStateChange: state->StateInfo.UnloadDll.BaseAddress = wine_server_get_ptr( data->unload_dll.base ); + if ((DWORD_PTR)data->unload_dll.base != data->unload_dll.base) + return STATUS_PARTIAL_COPY; return STATUS_SUCCESS; } return STATUS_INTERNAL_ERROR; }
+#ifndef _WIN64 +/* helper to NtWaitForDebugEvent; retrive machine from PE image */ +static NTSTATUS get_image_machine( HANDLE handle, USHORT *machine ) +{ + IMAGE_DOS_HEADER dos_hdr; + IMAGE_NT_HEADERS nt_hdr; + IO_STATUS_BLOCK iosb; + LARGE_INTEGER offset; + FILE_POSITION_INFORMATION pos_info; + NTSTATUS status; + + offset.QuadPart = 0; + status = NtReadFile( handle, NULL, NULL, NULL, + &iosb, &dos_hdr, sizeof(dos_hdr), &offset, NULL ); + if (!status) + { + offset.QuadPart = dos_hdr.e_lfanew; + status = NtReadFile( handle, NULL, NULL, NULL, &iosb, + &nt_hdr, FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader), &offset, NULL ); + if (!status) + *machine = nt_hdr.FileHeader.Machine; + /* Reset file pos at beginning of file */ + pos_info.CurrentByteOffset.QuadPart = 0; + NtSetInformationFile( handle, &iosb, &pos_info, sizeof(pos_info), FilePositionInformation ); + } + return status; +} +#endif + /********************************************************************** * NtWaitForDebugEvent (NTDLL.@) */ @@ -1034,8 +1067,9 @@ NTSTATUS WINAPI NtWaitForDebugEvent( HANDLE handle, BOOLEAN alertable, LARGE_INT req->debug = wine_server_obj_handle( handle ); wine_server_set_reply( req, &data, sizeof(data) ); ret = wine_server_call( req ); - if (!ret && !(ret = event_data_to_state_change( &data, state ))) + if (!ret) { + ret = event_data_to_state_change( &data, state ); state->NewState = data.code; state->AppClientId.UniqueProcess = ULongToHandle( reply->pid ); state->AppClientId.UniqueThread = ULongToHandle( reply->tid ); @@ -1043,6 +1077,24 @@ NTSTATUS WINAPI NtWaitForDebugEvent( HANDLE handle, BOOLEAN alertable, LARGE_INT } SERVER_END_REQ;
+#ifndef _WIN64 + /* don't pass 64bit load events to 32bit callers */ + if (!ret && state->NewState == DbgLoadDllStateChange) + { + USHORT machine; + if (!get_image_machine( state->StateInfo.LoadDll.FileHandle, &machine ) && + machine != current_machine) + ret = STATUS_PARTIAL_COPY; + } + if (ret == STATUS_PARTIAL_COPY) + { + if (state->NewState == DbgLoadDllStateChange) + NtClose( state->StateInfo.LoadDll.FileHandle ); + NtDebugContinue( handle, &state->AppClientId, DBG_CONTINUE ); + wait = TRUE; + continue; + } +#endif if (ret != STATUS_PENDING) return ret; if (!wait) return STATUS_TIMEOUT; wait = FALSE; diff --git a/dlls/wow64/sync.c b/dlls/wow64/sync.c index bf42f43b7e6..385477ef5fa 100644 --- a/dlls/wow64/sync.c +++ b/dlls/wow64/sync.c @@ -1490,6 +1490,65 @@ NTSTATUS WINAPI wow64_NtWaitForAlertByThreadId( UINT *args ) }
+/* helper to wow64_NtWaitForDebugEvent; retrive machine from PE image */ +static NTSTATUS get_image_machine( HANDLE handle, USHORT *machine ) +{ + IMAGE_DOS_HEADER dos_hdr; + IMAGE_NT_HEADERS nt_hdr; + IO_STATUS_BLOCK iosb; + LARGE_INTEGER offset; + FILE_POSITION_INFORMATION pos_info; + NTSTATUS status; + + offset.QuadPart = 0; + status = NtReadFile( handle, NULL, NULL, NULL, + &iosb, &dos_hdr, sizeof(dos_hdr), &offset, NULL ); + if (!status) + { + offset.QuadPart = dos_hdr.e_lfanew; + status = NtReadFile( handle, NULL, NULL, NULL, &iosb, + &nt_hdr, FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader), &offset, NULL ); + if (!status) + *machine = nt_hdr.FileHeader.Machine; + /* Reset file pos at beginning of file */ + pos_info.CurrentByteOffset.QuadPart = 0; + NtSetInformationFile( handle, &iosb, &pos_info, sizeof(pos_info), FilePositionInformation ); + } + return status; +} + +/* helper to wow64_NtWaitForDebugEvent; only pass debug events for current machine */ +static BOOL filter_out_state_change( HANDLE handle, DBGUI_WAIT_STATE_CHANGE *state ) +{ + BOOL filter_out; + + switch (state->NewState) + { + case DbgLoadDllStateChange: + filter_out = ((ULONG64)state->StateInfo.LoadDll.BaseOfDll >> 32) != 0; + if (!filter_out) + { + USHORT machine; + filter_out = !get_image_machine( state->StateInfo.LoadDll.FileHandle, &machine) && machine != current_machine; + } + break; + case DbgUnloadDllStateChange: + filter_out = ((ULONG_PTR)state->StateInfo.UnloadDll.BaseAddress >> 32) != 0; + break; + default: + filter_out = FALSE; + break; + } + if (filter_out) + { + if (state->NewState == DbgLoadDllStateChange) + NtClose( state->StateInfo.LoadDll.FileHandle ); + NtDebugContinue( handle, &state->AppClientId, DBG_CONTINUE ); + } + return filter_out; +} + + /********************************************************************** * wow64_NtWaitForDebugEvent */ @@ -1502,7 +1561,12 @@ NTSTATUS WINAPI wow64_NtWaitForDebugEvent( UINT *args )
ULONG i; DBGUI_WAIT_STATE_CHANGE state; - NTSTATUS status = NtWaitForDebugEvent( handle, alertable, timeout, &state ); + NTSTATUS status; + + do + { + status = NtWaitForDebugEvent( handle, alertable, timeout, &state ); + } while (!status && filter_out_state_change( handle, &state ));
if (!status) { 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; }
V3 pushed: Notes: - changed strategy for filtering, esp. on unload events. When these are received from NtWaitForDebugEvent(), the DLL is already unmapped (so not readable from memory, image mapping is no longer present), and no file handle to peruse the machine information. As the unload event are not generated when process terminates, the wow*.dll unload events are hence not generated. So we should be safe here. But I didn't test further on windows by injecting and unloading a 64dll inside a wow64 process to see what happens. I kept anyway the filter on addresses above 4G which will never hurt. I didn't find any docs if windows loads all its 64bit DLLs in a wow64 process above 4G (event if it's what testing shows). So I'm not 100% confident on the filtering of all the unload events. - extended tests to have a graceful exit of child process to test the tear-down part as well