[0/4] Add python method gdb.InferiorThread.thread_handle

Message ID 20180915220836.7dda4a63@pinnacle.lan
Headers show
Series
  • Add python method gdb.InferiorThread.thread_handle
Related show

Message

Kevin Buettner Sept. 16, 2018, 5:08 a.m.
This four part series adds a python method named "thread_handle"
which is used to fetch the thread handle from a thread object.

It will eventually be used as part of the implementation of the
"thread parent" operation for the OpenMP work that I've been doing.

Kevin

Comments

Tom Tromey Sept. 18, 2018, 11:02 a.m. | #1
>>>>> "Kevin" == Kevin Buettner <kevinb@redhat.com> writes:


Kevin> This four part series adds a python method named "thread_handle"
Kevin> which is used to fetch the thread handle from a thread object.

Kevin> It will eventually be used as part of the implementation of the
Kevin> "thread parent" operation for the OpenMP work that I've been doing.

Could you explain more about thread handles -- what they are and why
they are needed?  I do not really understand the motivation for this,
but I would like to.

Tom
Kevin Buettner Sept. 19, 2018, 11:53 p.m. | #2
On Tue, 18 Sep 2018 05:02:40 -0600
Tom Tromey <tom@tromey.com> wrote:

> >>>>> "Kevin" == Kevin Buettner <kevinb@redhat.com> writes:  

> 

> Kevin> This four part series adds a python method named "thread_handle"

> Kevin> which is used to fetch the thread handle from a thread object.  

> 

> Kevin> It will eventually be used as part of the implementation of the

> Kevin> "thread parent" operation for the OpenMP work that I've been doing.  

> 

> Could you explain more about thread handles -- what they are and why

> they are needed?  I do not really understand the motivation for this,

> but I would like to.


Hi Tom,

I'm working on a bug in which GDB is sometimes unable to find and
print variables which should be visible from an OpenMP parallel
region.  During compilation, the parallel region has been transformed
into an outlined function and, when GDB is stopped within that
function, the GDB user is not able to examine all variables that
should be visible.

Part of the solution was a change to the DWARF generated by GCC which
caused outlined OpenMP functions to be placed within a lexical scope
which causes the variables in question to become visible.  But work
was/is also needed on the GDB side.  The variables might not be on the
stack of the current thread, but may instead be found on the stack of
the thread which created the current thread.  I'm calling that thread,
the creator thread, the "thread parent".  GDB needs a way to find the
thread parent.  If the variable in question is not found on the stack
of the current thread, GDB will try to find it in the thread parent
(and other ancestorors too - which is needed for nested parallel
regions).

The libgomp library contains some data structures describing the
threads that it manages.  One would hope that that data structure
contains a thread handle which will allow the debugger examining those
data structures to map the handle to whatever data structures the
debugger is using to represent the threads.  For a pthread based
system, the thread handle will be of type pthread_t.  Such handles are
generally opaque, might not fit in an integer, and sometimes even
require the use of a comparison function to determine whether two
handles are identical.

When examining that data structure (which I'm doing via Python code),
I need to be able find the thread associated with a particular handle. 
To this end, I've already added Inferior.thread_from_thread_handle. 
It is passed a thread handle and will return the corresponding thread
if such can be found.

So... now things start to get complicated.

It turned out the libgomp wasn't storing the thread handle in its
thread management data structures.  I submitted a patch which added
the handle and caused it to be initialized for each thread.  According
to Jakub, some architectures will require that patch, but most
architectures (with Linux as the OS) use -ftls-model=initial-exec TLS
model for libgomp.  Again, according to Jakub, this means that we
should be able derive the handle from the address of gomp_tls_data,
which is the address of a struct in thread local storage.

After some investigation, I verified that this does indeed work, but
in order to make it work, I needed to be able to find the thread handle
associated with the current thread.

Handles in this case are NOT opaque.  My python code needs to be be
able to convert a handle to an integer (or address) and do arithmetic
on it eventually resulting in a different handle.  So my remark in the
documentation patch (part 4) was not correct.  I had written this code
a while back and had forgotten some of these details.

In order to make things more concrete, below is my current implementation
of the thread parent operation.  As should be evident by some of the
comments, it's not completely finished yet, but you'll at least be able
to see how I'm using the two methods which map handles to threads and
vice versa.

# -*- python -*-

""" This module is an implementation of `thread parent'.

    GDB calls thread_parent when it wishes to search ancestor threads for
    variables when debugging an OpenMP program.  (It might be useful in
    other circumstances too, but so far this is the only use case.)

    Given a thread, thread_parent is expected to return either None or the
    thread corresponding to the parent thread.

    At the moment, this module will not (always) find the correct parent
    thread for the nested case.  More work is required to find the parents
    of nested threads.

    This file should be named (at the moment) libgomp.so.1.0.0-gdb.py.  It
    should be placed in the same directory as the libgomp shared library.
    Moreover, libgomp.so.1.0.0 needs to have intact DWARF debug
    symbols.

    It should eventually be made part of libgomp and installed along with
    the libgomp shared library.

"""

import gdb
from gdb.thread_parent import ThreadParent

class GompThreadParent(ThreadParent):

    def __init__(self):
	ThreadParent.__init__(self, "GOMP Thread Parent")
        self.offset = None
	self.no_pthread_id = False
	self.long_long_type = gdb.lookup_type("long long")
	self.pthread_t_type = gdb.lookup_type("pthread_t")

    def parent_from_handle(self, thr, h):
        parent_thr = gdb.selected_inferior().thread_from_thread_handle(h)
        if parent_thr == thr:
            return None
        else:
            return parent_thr

    def thread_parent_via_pthread_id_field(self, thr):
        # Uncomment line below for deployment.
        #ph = gdb.parse_and_eval("gomp_tls_data->thread_pool.threads[0].pthread_id")
        # For testing only: pthread_idddd does not exist.
        ph = gdb.parse_and_eval("gomp_tls_data->thread_pool.threads[0].pthread_idddd")
        return self.parent_from_handle (thr, ph)

    def thread_parent_via_offset(self, thr):
        if self.offset is None:
            gomp_tls_addr =  gdb.parse_and_eval("&gomp_tls_data").cast(self.long_long_type)
            self.offset = gdb.selected_thread().thread_handle(self.pthread_t_type) - gomp_tls_addr
        ph = gdb.parse_and_eval("gomp_tls_data->thread_pool.threads[0]") \
                 .cast(self.long_long_type) \
             + self.offset
        return self.parent_from_handle (thr, ph)

    def thread_parent(self, thr):

        if not self.no_pthread_id:
            try:
                # print("Trying to fetch parent via thread_id")
                parent = self.thread_parent_via_pthread_id_field(thr)
            except gdb.error:
                if self.no_pthread_id:
                    # Clean this up
                    print("Got an exception after already successfully fetching pthread_id")
                self.no_pthread_id = True
            else:
                return parent

        # Should we even bother with the try / except here?  Maybe print a
        # warning if there is an exception?
        try:
            parent = self.thread_parent_via_offset(thr)
        except:
            pass
        else:
            return parent

        return None

    __call__ = thread_parent

gdb.thread_parent.register_thread_parent(None, GompThreadParent())