Re: [RFA] nameless LOAD_DLL_DEBUG_EVENT causes ntdll.dll to be missing

Pedro Alves
2013-12-03T19:51:05Z

On 12/03/2013 11:29 AM, Joel Brobecker wrote:
> Hello,
>
> We observed on Windows 2012 that we were unable to unwind past
> exception handlers. For instance, with any Ada program raising
> an exception that does not get handled:
>
> % gnatmake -g a -bargs -shared
> % gdb a
> (gdb) start
> (gdb) catch exception unhandled
> Catchpoint 2: unhandled Ada exceptions
> (gdb) c
> Catchpoint 2, unhandled CONSTRAINT_ERROR at <__gnat_unhandled_exception> (
> e=0x645ff820 <constraint_error>) at s-excdeb.adb:53
> 53 s-excdeb.adb: No such file or directory.
>
> At this point, we can already see that something went wrong, since
> the frame selected by the debugger corresponds to a runtime function
> rather than the function in the user code that caused the exception
> to be raised (in our case procedure A).
>
> This is further confirmed by the fact that we are unable to unwind
> all the way to procedure A:
>
> (gdb) bt
> #0 <__gnat_unhandled_exception> (e=0x645ff820 <constraint_error>)
> at s-excdeb.adb:53
> #1 0x000000006444e9a3 in <__gnat_notify_unhandled_exception> (excep=0x284d20)
> at a-exextr.adb:144
> #2 0x00000000645f106a in __gnat_personality_imp ()
> from C:\[...]\libgnat-7.3.dll
> #3 0x000000006144d1b7 in _GCC_specific_handler (ms_exc=0x242fab0,
> this_frame=0x242fe60, ms_orig_context=0x242f5c0, ms_disp=0x242ef70,
> gcc_per=0x645f0960 <__gnat_personality_imp>)
> at ../../../src/libgcc/unwind-seh.c:289
> #4 0x00000000645f1211 in __gnat_personality_seh0 ()
> from C:\[...]\libgnat-7.3.dll
> #5 0x000007fad3879f4d in ?? ()
> Backtrace stopped: previous frame inner to this frame (corrupt stack?)
>
> It turns out that the unwinder has been doing its job flawlessly
> up until frame #5. The address in frame #5 is correct, but GDB
> is not able to associate it with any symbol or unwind record.
>
> And this is because this address is inside ntdll.dll, and when
> we received the LOAD_DLL_DEBUG_EVENT for that DLL, the system
> was not able to tell us the name of the library, thus causing us
> to silently ignoring the event. Because GDB does not know about
> ntdll.dll, it is unable to access the unwind information from it.
> And because the function at that address does not use a frame
> pointer, the unwinding becomes impossible.
>
> This patch helps recovering ntdll.dll at the end of the "run/attach"
> phase, simply by trying to locate that specific DLL again.

Does this happen only on "attach", or also with "run"? I ask
because of this:

static char *
get_image_name (HANDLE h, void *address, int unicode)
{
...
/* Attempt to read the name of the dll that was detected.
This is documented to work only when actively debugging
a program. It will not work for attached processes. */
if (address == NULL)
return NULL;

And it sounds like get_module_name somehow is failing
during the startup phase too? Must be, because
get_module_name also uses EnumProcessModules. Can
you share for the archives what exactly in get_module_name
fails (that doesn't fail for other dlls)?

> In terms of our medium to long term planning, it seems to me that
> we should be able to simplify the code by ignoring LOAD_DLL_DEBUG_EVENT
> during the startup phase, and modify windows_ensure_ntdll_loaded
> to then detect and report all shared libraries after we've finished
> inferior creation. But for a change just before 7.7 branch creation,
> I thought it was safest to just handle ntdll.dll specifically. This
> is less intrusive, and ntdll is the only DLL affected by the problem
> I know so far.

EnumProcessModules used to be exported by psapi.dll in older Windows
versions (I think prior to Windows 7), I don't think we can assume
that API/dll is always around.

> + int i;
...
> + for (i = 0; i < (int) (cb_needed / sizeof (HMODULE)); i++)

Use size_t, and then you don't need the cast.

Does gdbserver need this too? I'd almost bet it doesn't,
due to the extra fallback on toolhelp (despite
the 32 in the toolhelp API names, it works on 64-bit):

static void
handle_load_dll (void)
{
LOAD_DLL_DEBUG_INFO *event = &current_event.u.LoadDll;
char dll_buf[MAX_PATH + 1];
char *dll_name = NULL;
CORE_ADDR load_addr;

dll_buf[0] = dll_buf[sizeof (dll_buf) - 1] = '\0';

/* Windows does not report the image name of the dlls in the debug
event on attaches. We resort to iterating over the list of
loaded dlls looking for a match by image base. */
if (!psapi_get_dll_name (event->lpBaseOfDll, dll_buf))
{
if (!server_waiting)
/* On some versions of Windows and Windows CE, we can't create
toolhelp snapshots while the inferior is stopped in a
LOAD_DLL_DEBUG_EVENT due to a dll load, but we can while
Windows is reporting the already loaded dlls. */
toolhelp_get_dll_name (event->lpBaseOfDll, dll_buf);
}

--
Pedro Alves