Re: [RFC PATCH 0/3] Pretty-printing for errno

Pedro Alves
2017-09-06T13:32:03Z

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:

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 ();
}

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