返回

Linux 下如何让 backtrace() 和 backtrace_symbols() 打印函数名称?

Linux

如何在 Linux 中使用 backtrace() 和 backtrace_symbols() 打印函数名称

概述

backtrace() 和 backtrace_symbols() 是 Linux 中强大的函数,用于生成程序的调用轨迹。但是,默认情况下,它们只打印函数地址,不打印函数名称。本文将详细介绍如何通过使用 -rdynamic 编译标志和 dladdr() 函数来让这些函数打印函数名称。

问题

当使用 backtrace() 和 backtrace_symbols() 打印调用轨迹时,你可能会注意到它们只输出函数地址。这是因为这些函数需要符号信息来解析地址并查找函数名称。默认情况下,此信息在编译时不可用。

解决方案

要解决此问题,我们需要遵循以下步骤:

1. 使用 -rdynamic 编译标志

-rdynamic 编译标志告诉编译器在运行时加载符号表信息。这将使 backtrace_symbols() 能够解析地址并打印函数名称。

2. 使用 dladdr() 函数

dladdr() 函数将地址转换为符号信息。我们可以使用它来获取函数名称,然后将其打印出来。

代码示例

以下代码演示了如何使用 -rdynamic 标志和 dladdr() 函数在 backtrace() 和 backtrace_symbols() 中打印函数名称:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) && (errno != EINTR))
                        break;

                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

void print_backtrace(void)
{
        static const char start[] = "BACKTRACE ------------\n";
        static const char end[] = "----------------------\n";

        void *bt[1024];
        int bt_size;
        char **bt_syms;
        int i;

        bt_size = backtrace(bt, 1024);
        bt_syms = backtrace_symbols(bt, bt_size);
        full_write(STDERR_FILENO, start, strlen(start));
        for (i = 1; i < bt_size; i++) {
                Dl_info info;

                if (dladdr(bt[i], &info) != 0 && info.dli_sname != NULL) {
                        full_write(STDERR_FILENO, info.dli_sname, strlen(info.dli_sname));
                } else {
                        full_write(STDERR_FILENO, bt_syms[i], len);
                }
                full_write(STDERR_FILENO, "\n", 1);
        }
        full_write(STDERR_FILENO, end, strlen(end));
    free(bt_syms);
}

结论

通过使用 -rdynamic 编译标志和 dladdr() 函数,你可以让 backtrace() 和 backtrace_symbols() 函数在 Linux 中打印函数名称。这对于调试和分析程序非常有用。

常见问题解答

  1. 为什么 -g 和 -ggdb 编译标志不能打印函数名称?
    -g 和 -ggdb 编译标志生成调试信息,但它们不会加载符号表信息。因此,它们不能用于打印函数名称。

  2. dladdr() 函数是否对所有平台都可用?
    dladdr() 函数在大多数 Linux 平台上都可用。但是,它在其他操作系统上的可用性可能因实现而异。

  3. 是否可以在没有 dladdr() 函数的情况下打印函数名称?
    是的,可以在某些情况下在没有 dladdr() 函数的情况下打印函数名称。例如,如果你使用的是调试器,它可能能够解析地址并打印函数名称。

  4. 如何使用其他语言中的类似函数打印函数名称?
    其他语言可能具有类似于 backtrace() 和 backtrace_symbols() 的函数。例如,在 C++ 中,你可以使用 backtrace() 和 backtrace_symbols() 函数。在 Java 中,你可以使用 Thread.dumpStack() 方法。

  5. 打印函数名称有什么好处?
    打印函数名称可以帮助你更轻松地识别和分析调用轨迹。它对于调试和分析程序特别有用。