This makes it possible to debug Wine application in GDB almost like any other Unix application. Split from https://gitlab.winehq.org/wine/wine/-/merge_requests/1074.
-- v3: ntdll: Maintain a PE module link map and expose it to GDB.
From: Rémi Bernon rbernon@codeweavers.com
To make sure gdb will unwind through it, and ignore that the syscall frame is inner its caller frame on the thread stack. --- dlls/ntdll/unix/signal_i386.c | 2 ++ dlls/ntdll/unix/signal_x86_64.c | 2 ++ 2 files changed, 4 insertions(+)
diff --git a/dlls/ntdll/unix/signal_i386.c b/dlls/ntdll/unix/signal_i386.c index d665e281176..23441273d0f 100644 --- a/dlls/ntdll/unix/signal_i386.c +++ b/dlls/ntdll/unix/signal_i386.c @@ -2537,6 +2537,7 @@ __ASM_GLOBAL_FUNC( signal_exit_thread, * __wine_syscall_dispatcher */ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, + __ASM_CFI(".cfi_signal_frame\n\t") "movl %fs:0x1f8,%ecx\n\t" /* x86_thread_data()->syscall_frame */ "movw $0,0x02(%ecx)\n\t" /* frame->restore_flags */ "popl 0x08(%ecx)\n\t" /* frame->eip */ @@ -2727,6 +2728,7 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, * __wine_unix_call_dispatcher */ __ASM_GLOBAL_FUNC( __wine_unix_call_dispatcher, + __ASM_CFI(".cfi_signal_frame\n\t") "movl %fs:0x1f8,%ecx\n\t" /* x86_thread_data()->syscall_frame */ "movw $0,0x02(%ecx)\n\t" /* frame->restore_flags */ "popl 0x08(%ecx)\n\t" /* frame->eip */ diff --git a/dlls/ntdll/unix/signal_x86_64.c b/dlls/ntdll/unix/signal_x86_64.c index fa8660ca914..478ed731661 100644 --- a/dlls/ntdll/unix/signal_x86_64.c +++ b/dlls/ntdll/unix/signal_x86_64.c @@ -2628,6 +2628,7 @@ __ASM_GLOBAL_FUNC( signal_exit_thread, * __wine_syscall_dispatcher */ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, + __ASM_CFI(".cfi_signal_frame\n\t") #ifdef __APPLE__ "movq %gs:0x30,%rcx\n\t" "movq 0x328(%rcx),%rcx\n\t" @@ -2850,6 +2851,7 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, * __wine_unix_call_dispatcher */ __ASM_GLOBAL_FUNC( __wine_unix_call_dispatcher, + __ASM_CFI(".cfi_signal_frame\n\t") "movq %rcx,%r10\n\t" #ifdef __APPLE__ "movq %gs:0x30,%rcx\n\t"
From: Rémi Bernon rbernon@codeweavers.com
Effectively supporting dynamically loaded libraries when running Wine under GDB without WINELOADERNOEXEC=1.
Credits to Jinoh Kang for the idea. --- loader/main.c | 88 +++++++++++++++++++++++++++++++++++++++++++++- loader/preloader.c | 15 ++++++++ 2 files changed, 102 insertions(+), 1 deletion(-)
diff --git a/loader/main.c b/loader/main.c index 242ff15accd..b7805473da8 100644 --- a/loader/main.c +++ b/loader/main.c @@ -33,13 +33,99 @@ #ifdef HAVE_SYS_SYSCTL_H # include <sys/sysctl.h> #endif +#ifdef HAVE_LINK_H +# include <link.h> +#endif +#ifdef HAVE_SYS_LINK_H +# include <sys/link.h> +#endif
#include "main.h"
extern char **environ;
-/* the preloader will set this variable */ +/* the preloader will set these variables */ const struct wine_preload_info *wine_main_preload_info = NULL; +void (*wine_dl_debug_state)(void) = NULL; +struct r_debug *wine_r_debug = NULL; + +#ifdef __linux__ + +static struct link_map so_link_map = {.l_name = (char *)""}; +static pthread_mutex_t link_map_lock = PTHREAD_MUTEX_INITIALIZER; + +static void sync_wine_link_map(void) +{ + static struct r_debug *_r_debug; + struct link_map *next = &so_link_map, *prev = NULL, **rtld_map, **wine_map; + + if (!_r_debug) _r_debug = dlsym( RTLD_NEXT, "_r_debug" ); + rtld_map = &_r_debug->r_map; + wine_map = &next; + + pthread_mutex_lock( &link_map_lock ); + + while (*rtld_map) + { + if (!*wine_map) + { + if (!(*wine_map = calloc( 1, sizeof(struct link_map) ))) break; + (*wine_map)->l_prev = prev; + } + + prev = *wine_map; + (*wine_map)->l_addr = (*rtld_map)->l_addr; + (*wine_map)->l_name = strdup( (*rtld_map)->l_name ); + (*wine_map)->l_ld = (*rtld_map)->l_ld; + rtld_map = &(*rtld_map)->l_next; + wine_map = &(*wine_map)->l_next; + } + + /* remove the remaining wine entries */ + next = *wine_map; + *wine_map = NULL; + + while (next) + { + struct link_map *prev = next; + wine_map = &next->l_next; + next = *wine_map; + *wine_map = NULL; + free( prev->l_name ); + free( prev ); + } + + pthread_mutex_unlock( &link_map_lock ); + + if (wine_r_debug) wine_r_debug->r_map = &so_link_map; + if (wine_dl_debug_state) wine_dl_debug_state(); +} + +void *dlopen( const char *file, int mode ) +{ + static typeof(dlopen) *rtld_dlopen; + void *ret; + + if (!rtld_dlopen) rtld_dlopen = dlsym( RTLD_NEXT, "dlopen" ); + ret = rtld_dlopen( file, mode ); + + sync_wine_link_map(); + return ret; +} + +int dlclose( void *handle ) +{ + static typeof(dlclose) *rtld_dlclose; + int ret; + + if (!rtld_dlclose) rtld_dlclose = dlsym( RTLD_NEXT, "dlclose" ); + ret = rtld_dlclose( handle ); + + sync_wine_link_map(); + return ret; +} + +#endif /* __linux__ */
/* canonicalize path and return its directory name */ static char *realpath_dirname( const char *name ) diff --git a/loader/preloader.c b/loader/preloader.c index 72556f09720..9fa00786281 100644 --- a/loader/preloader.c +++ b/loader/preloader.c @@ -1362,6 +1362,9 @@ static void set_process_name( int argc, char *argv[] ) for (i = 1; i < argc; i++) argv[i] -= off; }
+/* GDB hooks integration */ +struct r_debug _r_debug = {0}; +void _dl_debug_state(void) {}
/* * wld_start @@ -1378,6 +1381,8 @@ void* wld_start( void **stack ) struct wld_auxv new_av[8], delete_av[3], *av; struct wld_link_map main_binary_map, ld_so_map; struct wine_preload_info **wine_main_preload_info; + void (**wine_dl_debug_state)(void); + struct r_debug **wine_r_debug;
pargc = *stack; argv = (char **)pargc + 1; @@ -1450,6 +1455,16 @@ void* wld_start( void **stack ) if (wine_main_preload_info) *wine_main_preload_info = preload_info; else wld_printf( "wine_main_preload_info not found\n" );
+ /* provide r_debug to inform GDB of loaded modules */ + wine_r_debug = find_symbol( &main_binary_map, "wine_r_debug", STT_OBJECT ); + if (wine_r_debug) *wine_r_debug = &_r_debug; + else wld_printf( "wine_r_debug not found\n" ); + + /* provide _dl_debug_state callback to trigger GDB hooks */ + wine_dl_debug_state = find_symbol( &main_binary_map, "wine_dl_debug_state", STT_OBJECT ); + if (wine_dl_debug_state) *wine_dl_debug_state = _dl_debug_state; + else wld_printf( "wine_dl_debug_state not found\n" ); + #define SET_NEW_AV(n,type,val) new_av[n].a_type = (type); new_av[n].a_un.a_val = (val); SET_NEW_AV( 0, AT_PHDR, (unsigned long)main_binary_map.l_phdr ); SET_NEW_AV( 1, AT_PHENT, sizeof(ElfW(Phdr)) );
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/ntdll/unix/loader.c | 26 +++++++++ dlls/ntdll/unix/virtual.c | 4 ++ loader/main.c | 113 +++++++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 3 deletions(-)
diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 7243be49489..dadc7f16948 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1439,6 +1439,30 @@ static inline char *prepend_build_dir_path( char *ptr, const char *ext, const ch }
+static void notify_gdb_dll_loaded( void *module, const char *unix_path ) +{ + static void (*wine_gdb_dll_loaded)( const void *module, const char *unix_path ); + if (!wine_gdb_dll_loaded) wine_gdb_dll_loaded = dlsym( RTLD_DEFAULT, "wine_gdb_dll_loaded" ); + if (wine_gdb_dll_loaded) wine_gdb_dll_loaded( module, unix_path ); +} + +static void notify_gdb_native_dll_loaded( void *module, UNICODE_STRING *nt_name ) +{ + OBJECT_ATTRIBUTES attr; + UNICODE_STRING redir; + char *unix_path; + + InitializeObjectAttributes( &attr, (void *)nt_name, OBJ_CASE_INSENSITIVE, 0, 0 ); + get_redirect( &attr, &redir ); + + if (!nt_to_unix_file_name( &attr, &unix_path, FILE_OPEN )) + notify_gdb_dll_loaded( module, unix_path ); + + free( redir.Buffer ); + free( unix_path ); +} + + /*********************************************************************** * open_dll_file * @@ -1489,6 +1513,7 @@ static NTSTATUS open_builtin_pe_file( const char *name, OBJECT_ATTRIBUTES *attr, { status = virtual_map_builtin_module( mapping, module, size, image_info, zero_bits, machine, prefer_native ); NtClose( mapping ); + if (!status) notify_gdb_dll_loaded( *module, name ); } return status; } @@ -1619,6 +1644,7 @@ static NTSTATUS find_builtin_dll( UNICODE_STRING *nt_name, void **module, SIZE_T
if (found_image) status = STATUS_IMAGE_MACHINE_TYPE_MISMATCH; WARN( "cannot find builtin library for %s\n", debugstr_us(nt_name) ); + if (!status) notify_gdb_native_dll_loaded( *module, nt_name ); done: if (status >= 0 && ext) { diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index 2a00ac52811..75245885781 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -4774,6 +4774,10 @@ NTSTATUS WINAPI NtUnmapViewOfSection( HANDLE process, PVOID addr ) SERVER_END_REQ; if (!status) { + static void (*wine_gdb_dll_unload)( const void *module ); + if (!wine_gdb_dll_unload) wine_gdb_dll_unload = dlsym( RTLD_DEFAULT, "wine_gdb_dll_unload" ); + if (wine_gdb_dll_unload) wine_gdb_dll_unload( view->base ); + if (view->protect & SEC_IMAGE) release_builtin_module( view->base ); delete_view( view ); } diff --git a/loader/main.c b/loader/main.c index b7805473da8..adbe952e96c 100644 --- a/loader/main.c +++ b/loader/main.c @@ -20,6 +20,13 @@
#include "config.h"
+#include <stdarg.h> +#include <stddef.h> + +#include "windef.h" +#include "winbase.h" +#include "winnt.h" + #include <fcntl.h> #include <pthread.h> #include <stdio.h> @@ -40,6 +47,8 @@ # include <sys/link.h> #endif
+#include "wine/list.h" + #include "main.h"
extern char **environ; @@ -51,13 +60,23 @@ struct r_debug *wine_r_debug = NULL;
#ifdef __linux__
+struct link_map_entry +{ + struct link_map map; + const void *module; + struct list entry; +}; + +static struct list pe_link_map_entries = LIST_INIT( pe_link_map_entries ); +static struct link_map so_link_map; +static struct link_map pe_link_map = {.l_prev = &so_link_map, .l_name = (char *)""}; static struct link_map so_link_map = {.l_name = (char *)""}; static pthread_mutex_t link_map_lock = PTHREAD_MUTEX_INITIALIZER;
static void sync_wine_link_map(void) { static struct r_debug *_r_debug; - struct link_map *next = &so_link_map, *prev = NULL, **rtld_map, **wine_map; + struct link_map *next = &so_link_map, **rtld_map, **wine_map;
if (!_r_debug) _r_debug = dlsym( RTLD_NEXT, "_r_debug" ); rtld_map = &_r_debug->r_map; @@ -65,15 +84,18 @@ static void sync_wine_link_map(void)
pthread_mutex_lock( &link_map_lock );
+ /* unlink PE link map */ + pe_link_map.l_prev->l_next = NULL; + while (*rtld_map) { if (!*wine_map) { if (!(*wine_map = calloc( 1, sizeof(struct link_map) ))) break; - (*wine_map)->l_prev = prev; + (*wine_map)->l_prev = pe_link_map.l_prev; }
- prev = *wine_map; + pe_link_map.l_prev = *wine_map; (*wine_map)->l_addr = (*rtld_map)->l_addr; (*wine_map)->l_name = strdup( (*rtld_map)->l_name ); (*wine_map)->l_ld = (*rtld_map)->l_ld; @@ -95,12 +117,97 @@ static void sync_wine_link_map(void) free( prev ); }
+ /* link PE link map back */ + pe_link_map.l_prev->l_next = &pe_link_map; + pthread_mutex_unlock( &link_map_lock );
if (wine_r_debug) wine_r_debug->r_map = &so_link_map; if (wine_dl_debug_state) wine_dl_debug_state(); }
+static void add_dll_to_pe_link_map( const void *module, const char *unix_path ) +{ + const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *)module; + const IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)((const BYTE *)dos + dos->e_lfanew); + struct link_map_entry *entry; + struct link_map *map_end; + struct list *tail; + + if (!(entry = calloc( 1, sizeof(*entry) ))) return; + + entry->module = module; + entry->map.l_addr = (char *)module - (char *)nt->OptionalHeader.ImageBase; + entry->map.l_name = strdup( unix_path ); + + if ((tail = list_tail( &pe_link_map_entries ))) + { + entry->map.l_prev = &LIST_ENTRY( tail, struct link_map_entry, entry )->map; + entry->map.l_prev->l_next = &entry->map; + } + else + { + map_end = &pe_link_map; + while (map_end->l_next) map_end = map_end->l_next; + + map_end->l_next = &entry->map; + map_end->l_next->l_prev = map_end; + } + + list_add_tail( &pe_link_map_entries, &entry->entry ); +} + +void wine_gdb_dll_loaded( const void *module, const char *unix_path ) +{ + struct link_map_entry *entry; + + pthread_mutex_lock( &link_map_lock ); + + LIST_FOR_EACH_ENTRY( entry, &pe_link_map_entries, struct link_map_entry, entry ) + if (entry->module == module) break; + + if (&entry->entry == &pe_link_map_entries) + add_dll_to_pe_link_map( module, unix_path ); + else + { + const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *)module; + const IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)((const BYTE *)dos + dos->e_lfanew); + entry->map.l_addr = (char *)module - (char *)nt->OptionalHeader.ImageBase; + } + + pthread_mutex_unlock( &link_map_lock ); + + sync_wine_link_map(); +} + +void wine_gdb_dll_unload( const void *module ) +{ + struct link_map_entry *entry; + + pthread_mutex_lock( &link_map_lock ); + + LIST_FOR_EACH_ENTRY( entry, &pe_link_map_entries, struct link_map_entry, entry ) + if (entry->module == module) break; + + if (&entry->entry == &pe_link_map_entries) + { + pthread_mutex_unlock( &link_map_lock ); + return; + } + + list_remove( &entry->entry ); + + if (entry->map.l_prev) entry->map.l_prev->l_next = entry->map.l_next; + if (entry->map.l_next) entry->map.l_next->l_prev = entry->map.l_prev; + + pthread_mutex_unlock( &link_map_lock ); + + sync_wine_link_map(); + + free( entry->map.l_name ); + free( entry ); +} + void *dlopen( const char *file, int mode ) { static typeof(dlopen) *rtld_dlopen;
v3: Fix a crash when no builtin image was found and module is NULL.
On Mon Dec 5 17:45:23 2022 +0000, Rémi Bernon wrote:
v3: Fix a crash when no builtin image was found and module is NULL.
Although that fixes the crashes but I'm not sure it does what I intended, which is notify of the native module. This could be handy when debugging third-party libraries like dxvk or applications. I'll have a better look.