How to backtrace an separate stack?

Tom Tromey tom@tromey.com
Mon Mar 14 20:30:16 GMT 2022


Tom> IMO this is just a longstanding hole in GDB.  Green threads exist,
Tom> so it would be good for GDB to have a way to inspect them.

I took a stab at implementing this recently.  It's still very rough but
it's good enough to discuss whether it's something I should try to
polish.

For testing the proof of concept, I used vireo, a simple user-space
thread setup based on makecontext.

https://github.com/geofft/vireo

I've appended the Python code that teaches gdb how to find vireo
threads.  It's incomplete, as in, if you re-'run', it will fail.

Here's what a session looks like:

    (gdb) cont
    Continuing.
    [New Vireo Thread 1]
    [New Vireo Thread 2]
    send 0 from 0 to 1

    Thread 1 "pingpong" hit Breakpoint 2, pingpong () at examples/pingpong.c:27
    27			int i = vireo_recv(&who);
    (gdb) info thread
      Id   Target Id                                    Frame 
    * 1    Thread 0x7ffff7cb2b80 (LWP 42208) "pingpong" pingpong () at examples/pingpong.c:27
      2    Vireo Thread 1 "pingpong"                    pingpong () at examples/pingpong.c:27
      3    Vireo Thread 2 "pingpong"                    pingpong () at examples/pingpong.c:27
    (gdb) thread 3
    [Switching to thread 3 (Vireo Thread 2)]
    #0  pingpong () at examples/pingpong.c:27
    27			int i = vireo_recv(&who);
    (gdb) bt
    #0  pingpong () at examples/pingpong.c:27
    #1  0x00007ffff7d329c0 in ?? () from /lib64/libc.so.6
    #2  0x00007ffff7fc20e0 in ?? () from /home/tromey/gdb/vireo/examples/../libvireo.so
    #3  0x0000000000000000 in ?? ()

I realize now, writing this, that the approach to underlying threads
should be improved.  These need to be tracked more actively, so that
breakpoint stops can report the corresponding green thread.  You can see
above that this isn't done.  Also I think the "Frame" info is wrong
right now.

Anyway, the basic idea is to let Python tell gdb about the existence of
green threads, and let gdb mostly treat them identically to OS threads.
Under the hood, things like 'continue' will use the underlying OS
thread.

You can play with this if you want.  It's on 'submit/green-threads' on
my github.  Be warned that I rebase a lot.

Some things to work out:

- Exactly how should the 'underlying thread' concept work?
  Hooking into the inferior's scheduler seems slow, and also
  like it could present a chicken/egg problem.
  Maybe it needs a "green thread provider" object so that on
  a stop we can query that to see if the green thread corresponding
  to an OS thread is already known.

- Do we need a special hook to stop unwinding per-green-thread.
  You may not want to allow unwinding through the scheduler.

Tom


import gdb

thread_map = {}

main_thread = None

# From glibc/sysdeps/unix/sysv/linux/x86/sys/ucontext.h
x8664_regs = [ 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14',
               'r15', 'rdi', 'rsi', 'rbp', 'rbx', 'rdx', 'rax',
               'rcx', 'rsp', 'rip', 'efl', 'csgsfs', 'err',
               'trapno', 'oldmask', 'cr2' ]

def vireo_current():
    return int(gdb.parse_and_eval('curenv')) + 1

class VireoGreenThread:
    def __init__(self, tid):
        self.tid = tid

    def _get_state(self):
        return gdb.parse_and_eval('envs')[self.tid]['state']

    def fetch(self, reg):
        """Fetch REG from memory."""
        global x8664_regs
        global thread_map
        thread = thread[self.tid]
        state = self._get_state()
        gregs = state['uc_mcontext']['gregs']
        for i in range(0, len(x8664_regs)):
            if reg is None or reg == x8664_regs[i]:
                thread.write_register(x8664_regs[i], gregs[i])

    def store(self, reg):
        global x8664_regs
        global thread_map
        thread = thread[self.tid]
        state = self._get_state()
        gregs = state['uc_mcontext']['gregs']
        for i in range(0, len(x8664_regs)):
            if reg is None or reg == x8664_regs[i]:
                gregs[i] = thread.read_register(x8664_regs[i])

    def name(self):
        return "Vireo Thread " + str(self.tid)

    def underlying_thread(self):
        if vireo_current() == self.tid:
            global main_thread
            return main_thread
        return None

class VFinish(gdb.FinishBreakpoint):
    def stop(self):
        tid = int(self.return_value) + 1
        global thread_map
        thread_map[tid] = gdb.create_green_thread(tid, VireoGreenThread(tid))
        return False

class VCreate(gdb.Breakpoint):
    def stop(self):
        VFinish(gdb.newest_frame(), True)
        return False

class VExit(gdb.Breakpoint):
    def stop(self):
        global main_thread
        if main_thread is None:
            main_thread = gdb.selected_thread()
        global thread_map
        tid = vireo_current()
        if tid in thread_map:
            thread_map[tid].set_exited()
            del thread_map[tid]

VCreate('vireo_create', internal=True)
VExit('vireo_exit', internal=True)


More information about the Gdb mailing list