kbeckmann
kbeckmann

Reputation: 21

Possible to show the absolute addresses within shared libraries in a back trace in GDB?

When showing a call stack that contains dynamic library functions, I want to know the actual locations of the functions in the call stack within the libraries - not the addresses relative to the mapped address.

I know that I can manually calculate this for each call by running info shared and then subtract the offset for where the library was mapped into. But is there an automated way to do this?

This seems like a common problem to me so I just feel that there has to be someone out there who has had this problem before and has a handy solution ready :). I didn't find any scripts that do this which is why I'm posting this.

To illustrate the problem in case it isn't clear enough above:

I've set a breakpoint in write() and then show the callstack using bt.

Breakpoint 2, 0x00007ffff7eb1790 in write () from /usr/lib/libc.so.6
(gdb) bt
#0  0x00007ffff7eb1790 in write () from /usr/lib/libc.so.6
#1  0x00007ffff7e4185d in _IO_new_file_write () from /usr/lib/libc.so.6
#2  0x00007ffff7e40bbf in new_do_write () from /usr/lib/libc.so.6
#3  0x00007ffff7e429d9 in __GI__IO_do_write () from /usr/lib/libc.so.6
#4  0x00007ffff7e42db3 in __GI__IO_file_overflow () from /usr/lib/libc.so.6
#5  0x00007ffff7e37be2 in puts () from /usr/lib/libc.so.6
#6  0x0000555555555050 in main ()
(gdb) info maps
Undefined info command: "maps".  Try "help info".
(gdb) info shared
From                To                  Syms Read   Shared Object Library
0x00007ffff7fd5000  0x00007ffff7ff3784  Yes (*)     /lib64/ld-linux-x86-64.so.2
0x00007ffff7de7450  0x00007ffff7f3083f  Yes (*)     /usr/lib/libc.so.6
(*): Shared library is missing debugging information.
(gdb) 

Here I want to convert e.g. #0 0x00007ffff7eb1790 in write () from /usr/lib/libc.so.6 into 0xca340 automatically or actually 0x22450 + 0xca340 since the .text section starts at 0x22450 in that library.

Upvotes: 0

Views: 904

Answers (2)

kbeckmann
kbeckmann

Reputation: 21

Thanks for your suggestion, Tom.

Here is the filter I came up with. It's a bit hacky since I couldn't find a neat way to 1. get the start address of the loaded libraries in a nice way, and 2. had to use objdump to get the .text section of the libraries.

import gdb
from gdb.FrameDecorator import FrameDecorator
import subprocess


class SharedFrameFilter():

    textOffsets = None

    def __init__(self):
        self.name = "shared_filter"
        self.priority = 100
        self.enabled = True
        self.textOffsets = {}

        # Register this frame filter with the global frame_filters
        # dictionary.
        gdb.frame_filters[self.name] = self

    def getTextOffset(self, libName):
        if libName in self.textOffsets:
            return self.textOffsets[libName]

        out = subprocess.Popen([
            "objdump",
            "--section-headers",
            "--section=.text",
            libName
        ], stdout=subprocess.PIPE).stdout.read()

        lines = out.decode("utf-8").split("\n")
        for line in lines:
            if not ".text" in line:
                continue
            cols = line.split()
            startAddr = int(cols[3], 16)

            self.textOffsets[libName] = startAddr
            return startAddr

    def filter(self, frame_iter):
        for frame in frame_iter:
            address = frame.address()
            libName = gdb.solib_name(address)
            if libName == None:
                yield frame
                continue

            absoluteAddress = 0
            shared = gdb.execute("info shared", False, True)
            for line in shared.split('\n'):
                cols = line.split()
                try:
                    int(cols[0], 16)
                except:
                    continue

                if cols[4] != libName:
                    continue

                libStart = int(cols[0], 16)
                absoluteAddress = address - libStart + \
                    self.getTextOffset(libName)

                break

            frame.filename_orig = frame.filename
            frame.filename = lambda: "0x%08x in %s" % (
                absoluteAddress, frame.filename_orig())
            yield frame


SharedFrameFilter()

Usage in gdb:

(gdb) source SharedFrameFilter.py
(gdb) bt
#0  0x00007ffff7eb1790 in write () at 0x000ec790 in /usr/lib/libc.so.6
#1  0x00007ffff7e4185d in _IO_new_file_write () at 0x0007c85d in /usr/lib/libc.so.6
#2  0x00007ffff7e40bbf in new_do_write () at 0x0007bbbf in /usr/lib/libc.so.6
#3  0x00007ffff7e429d9 in __GI__IO_do_write () at 0x0007d9d9 in /usr/lib/libc.so.6
#4  0x00007ffff7e42db3 in __GI__IO_file_overflow ()
    at 0x0007ddb3 in /usr/lib/libc.so.6
#5  0x00007ffff7e37be2 in puts () at 0x00072be2 in /usr/lib/libc.so.6
#6  0x0000555555555050 in main ()

Upvotes: 1

Tom Tromey
Tom Tromey

Reputation: 22589

There is no built-in way to do this.

One possible way to implement this for yourself would be to write a frame filter in Python. The idea here would be to have the filter notice when a frame comes from a shared object, and in those cases return a new frame with a modified address. I am not sure if you can make this print the 0x22450 + 0xca340 form in the address output (if not it seems like something worth a gdb feature request), but you could print the unadorned address, or stuff extra information into the function- or file-name field.

Upvotes: 0

Related Questions