diff mbox

[RFC,0/3] Pretty-printing for errno

Message ID edcfb9d0-248c-fa10-583d-9a0512e4b091@redhat.com
State New
Headers show

Commit Message

Pedro Alves Sept. 6, 2017, 1:31 p.m. UTC
On 09/06/2017 02:05 PM, Zack Weinberg wrote:
> On Tue, Sep 5, 2017 at 6:31 PM, Pedro Alves <palves@redhat.com> wrote:
>> On 09/05/2017 10:15 PM, Zack Weinberg wrote:
>>>
>>> I don't understand why thread-local variables are inaccessible on my
>>> perfectly ordinary x86_64-unknown-linux-gnu workstation (the base OS
>>> is Debian 'stretch').  Do you have any idea what might be wrong?
>>
>> I assume your test program isn't actually linked with -pthread?
> 
> That is correct.
> 
>> When you do "print errno" in this situation, because there's no
>> "#define errno ..." in sight, gdb ends up finding the real "errno" symbol,
>> which, even though the program isn't threaded, is a TLS symbol, and as such has
>> a DWARF location expression describing its location as an offset into the
>> thread-local block for the current thread.  GDB needs to resolve that address, and
>> for threaded programs that is normally done with assistance from libthread_db.so.
>> The problem is then that libthread_db.so only works with programs that
>> link with libpthread.so, and if your test program is actually non-threaded,
>> it doesn't link with libpthread.so.
> 
> I am not familiar with the glibc-side TLS implementation, nor with
> libthread_db.so, nor the code in GDB that uses libthread_db.so.
> However, reading the implementation of td_thr_tls_get_addr leads me to
> believe that that function is *supposed* to work even if libpthread.so
> has not been loaded into the 'inferior'.  If it doesn't, perhaps that
> is a bug on our side.  Do you know if GDB even tries? It's not obvious
> to me looking at linux-thread-db.c.

GDB only tries to load libthread_db.so if it detects libpthread.so loaded
in the inferior.  gdb/linux-thread-db.c:thread_db_new_objfile is called for
every shared library found in the inferior.

However, if we hack gdb like this to force it to always try to
load libthread_db.so:

we get:

 (gdb) set debug libthread-db 1
 (gdb) nosharedlibrary 
 (gdb) sharedlibrary 
 Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libc-2.22.so.debug...done.
 done.
 Trying host libthread_db library: libthread_db.so.1.
 Host libthread_db.so.1 resolved to: /lib64/libthread_db.so.1.
 td_ta_new failed: application not linked with libthread
 thread_db_load_search returning 0
 ...

That "td_ta_new failed: application not linked with libthread"
error is output by thread_db_err_str in linux-thread-db.c.  It's
just pretty-printing TD_NOLIBTHREAD.  I.e., opening a connection
to libthread_db.so fails:

  /* Now attempt to open a connection to the thread library.  */
  err = info->td_ta_new_p (&info->proc_handle, &info->thread_agent);
  if (err != TD_OK)
    {

Because lithread_db.so itself "rejects" the inferior.

If we repeat the exercise with a breakpoint on gdb's ps_pglobal_lookup
(from proc-service.c, which implements the libthread_db.so -> gdb
callback interface), we see:

[note to self: IWBN to make "set debug libthread-db 1" print these
look ups]

...
 (gdb) sharedlibrary 
 Reading symbols from /lib64/libc.so.6...Reading symbols from /usr/lib/debug/usr/lib64/libc-2.22.so.debug...done.
 done.
 Trying host libthread_db library: libthread_db.so.1.
 Host libthread_db.so.1 resolved to: /lib64/libthread_db.so.1.

 Breakpoint 3, ps_pglobal_lookup (ph=0x1f04100, obj=0x7fffe58c50f8 "libpthread.so.0", name=0x7fffe58c5221 "nptl_version", sym_addr=0x7fffffffcf78)
     at src/gdb/proc-service.c:113
 113       struct inferior *inf = find_inferior_ptid (ph->ptid);
 (top-gdb) c
 Continuing.
 td_ta_new failed: application not linked with libthread
 thread_db_load_search returning 0
...

So in order to check whether the inferior is running a matching
glibc version, libthread_db.so looks for a "nptl_version" symbol,
which is only defined in libpthread.so, so not found in the inferior.

I did not try to hack gdb to return some address that happens
to point at a value that happens to match the expected version.
I think that for that experiment, hacking libthread_db.so itself
to skip the checks would likely be easier [and I can't afford to
do it right now].

> 
>> A workaround specifically for errno, and only for live-process debugging [*]
>> is the "macro define" trick I had suggested before:
>>
>>  (gdb) macro define errno (*__errno_location ())
>>
>> After that, "p errno" ends up calling __errno_location just
>> like when you compile the test program with -g3.
> 
> Again, is it possible to do (the equivalent of) this from the
> initialization code of a pretty-printer module?  Specifically, there
> already exists a Python function that's doing this:
> 
> def register(objfile):
>     """Register pretty printers for the current objfile."""
> 
>     printer = gdb.printing.RegexpCollectionPrettyPrinter("glibc-errno")
>     printer.add_printer('error_t', r'^(?:__)?error_t', ErrnoPrinter)
> 
>     if objfile == None:
>         objfile = gdb
> 
>     gdb.printing.register_pretty_printer(objfile, printer)
> 
> called when the module is loaded; what would I need to add to that so
> that the macro is defined (if it isn't already)?

I'm hoping that other people more experienced with the gdb
Python API can chime in.  My idea was just to call
  gdb.execute ("macro define errno (*(int *) __errno_location ())")
somewhere around your Python code.

Thanks,
Pedro Alves

Comments

Zack Weinberg Sept. 6, 2017, 9:03 p.m. UTC | #1
On Wed, Sep 6, 2017 at 9:31 AM, Pedro Alves <palves@redhat.com> wrote:
> On 09/06/2017 02:05 PM, Zack Weinberg wrote:
>> I am not familiar with the glibc-side TLS implementation, nor with
>> libthread_db.so, nor the code in GDB that uses libthread_db.so.
>> However, reading the implementation of td_thr_tls_get_addr leads me to
>> believe that that function is *supposed* to work even if libpthread.so
>> has not been loaded into the 'inferior'.  If it doesn't, perhaps that
>> is a bug on our side.  Do you know if GDB even tries? It's not obvious
>> to me looking at linux-thread-db.c.
>
> GDB only tries to load libthread_db.so if it detects libpthread.so loaded
> in the inferior.  gdb/linux-thread-db.c:thread_db_new_objfile is called for
> every shared library found in the inferior.
>
> However, if we hack gdb like this to force it to always try to
> load libthread_db.so:
...

> That "td_ta_new failed: application not linked with libthread"
> error is output by thread_db_err_str in linux-thread-db.c.  It's
> just pretty-printing TD_NOLIBTHREAD.  I.e., opening a connection
> to libthread_db.so fails:
>
>   /* Now attempt to open a connection to the thread library.  */
>   err = info->td_ta_new_p (&info->proc_handle, &info->thread_agent);
>   if (err != TD_OK)
>     {
>
> Because lithread_db.so itself "rejects" the inferior.

So, changes to both gdb and libthread_db seem to be required here.  I
do think that _in principle_ it ought to be possible to use
libthread_db to retrieve the address of thread-local data even if the
inferior is not linked with libpthread; glibc has quite a few
thread-specific variables (errno most prominent, of course, but also
h_errno, _res, etc), and so might any library which can be used from
both single- and multithreaded programs.

This is really not code I feel comfortable hacking up, though, and
it's probably more of a project than I have time for, in any case.

...
>> called when the module is loaded; what would I need to add to that so
>> that the macro is defined (if it isn't already)?
>
> I'm hoping that other people more experienced with the gdb
> Python API can chime in.  My idea was just to call
>   gdb.execute ("macro define errno (*(int *) __errno_location ())")
> somewhere around your Python code.

I'll tinker with that.  Thanks.

zw
Pedro Alves Sept. 7, 2017, 11:34 a.m. UTC | #2
On 09/06/2017 10:03 PM, Zack Weinberg wrote:

> So, changes to both gdb and libthread_db seem to be required here.  I
> do think that _in principle_ it ought to be possible to use
> libthread_db to retrieve the address of thread-local data even if the
> inferior is not linked with libpthread; glibc has quite a few
> thread-specific variables (errno most prominent, of course, but also
> h_errno, _res, etc), and so might any library which can be used from
> both single- and multithreaded programs.
> 
> This is really not code I feel comfortable hacking up, though, and
> it's probably more of a project than I have time for, in any case.

Sounds like a promising approach though.  I'd like to see this path
explored a bit more.  I'll keep this in my TODO, even though it's
not likely to bubble up very soon.  Thanks for the discussion/ideas!

> 
> ...
>>> called when the module is loaded; what would I need to add to that so
>>> that the macro is defined (if it isn't already)?
>>
>> I'm hoping that other people more experienced with the gdb
>> Python API can chime in.  My idea was just to call
>>   gdb.execute ("macro define errno (*(int *) __errno_location ())")
>> somewhere around your Python code.
> 
> I'll tinker with that.  Thanks.

Thanks,
Pedro Alves
diff mbox

Patch

diff --git a/gdb/linux-thread-db.c b/gdb/linux-thread-db.c
index 6d98135..6cf634e 100644
--- a/gdb/linux-thread-db.c
+++ b/gdb/linux-thread-db.c
@@ -1000,8 +1000,11 @@  thread_db_new_objfile (struct objfile *objfile)
         For dynamically linked executables, libpthread can be near the end
         of the list of shared libraries to load, and in an app of several
         thousand shared libraries, this can otherwise be painful.  */
+#if 0
       && ((objfile->flags & OBJF_MAINLINE) != 0
-         || libpthread_name_p (objfile_name (objfile))))
+         || libpthread_name_p (objfile_name (objfile)))
+#endif
+      )
     check_for_thread_db ();
 }