【问题标题】:Why does backtrace not contain Objective-C symbols regardless of -rdynamic?为什么 backtrace 不包含 Objective-C 符号而不管 -rdynamic?
【发布时间】:2026-02-10 09:30:01
【问题描述】:

更新:我正在使用 Linux 上的 GNU 运行时。该问题不会在具有 Apple 运行时的 MacOS 上发生。

更新 2: 我在 MacOS 上编译了 GNU 运行时并用它构建了示例。在具有 GNU 运行时的 MacOS 上不会发生该错误。我会说问题在于 glibc(因为 backtracebacktrace_symbols 是 glibc 扩展)。

在使用 backtracebacktrace_symbols 在 GCC 编译的 Objective-C 应用程序中打印回溯时,我没有得到任何 Objective-C 符号。仅显示文件名、地址和 C 符号。

我用-g编译并用-rdynamic链接。

我的测试应用:

void _printTrace()
{
    void *addr[1024];
    int aCount = backtrace(addr, 1024);
    char **frameStrings = backtrace_symbols(addr, aCount);
    for (int i = 0; i < aCount; i++) {
        printf("%s\n", frameStrings[i]);
    }
    free(frameStrings);
}

@interface TheObject
+ (void)_printTrace;
+ (void)printTrace;
@end

@implementation TheObject
+ (void)_printTrace
{
    _printTrace();
}

+ (void)printTrace
{
    [self _printTrace];
}
@end

void printTrace()
{
    [TheObject printTrace];
}

int main(int argc, char **argv)
{
    printTrace();
    return 0;
}

它的输出:

./test.bin(_printTrace+0x1f) [0x8048e05]
./test.bin() [0x8048e60]
./test.bin() [0x8048e8b]
./test.bin(printTrace+0x34) [0x8048ec5]
./test.bin(main+0xf) [0x8048eda]
/lib/libc.so.6(__libc_start_main+0xe5) [0xb7643bb5]
./test.bin() [0x8048b51]

有没有办法让 Objective-C 符号出现在这个回溯中?

【问题讨论】:

    标签: objective-c linux gcc stack-trace glibc


    【解决方案1】:

    dladdr() 只报告全局和弱符号。但是所有 Objective-C 函数符号都是局部的:

    $ readelf -s so_backtrace
    
    Symbol table '.dynsym' contains 29 entries:
    …
    
    Symbol table '.symtab' contains 121 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
    …
        49: 08048a01    13 FUNC    LOCAL  DEFAULT   14 _c_TheObject___printTrace
        50: 08048a0e    47 FUNC    LOCAL  DEFAULT   14 _c_TheObject__printTrace
    …
    

    您可以通过自己查看GNU libc source code 来验证永远不会返回本地符号。 backtrace_symbols() 在 sysdeps/generic/elf/backtracesyms.c 中定义。它依赖于在 elf/dl-addr.c 中定义的_dl_addr() 为其提供符号名称。最终调用determine_info()。如果可以,它使用GNU hash table,它不包含设计的本地符号:

    49       /* We look at all symbol table entries referenced by the hash
    50          table.  */
    …
    60                   /* The hash table never references local symbols so
    61                      we can omit that test here.  */
    

    如果 GNU 哈希表不存在,它将回退到标准哈希表。这包括所有符号,但determine_info() 代码过滤掉了除全局符号和弱符号之外的所有符号:

    90         if ((ELFW(ST_BIND) (symtab->st_info) == STB_GLOBAL
    91              || ELFW(ST_BIND) (symtab->st_info) == STB_WEAK)
    

    要对 Objective-C 函数地址进行符号化,您必须自己执行查找,而不是过滤掉本地函数符号。此外,您必须对 Objective-C 函数符号进行 demangle 以将 _c_TheObject___printTrace 恢复为 +[TheObject _printTrace]

    【讨论】:

    • 很好的答案,但我的应用程序必须使用标准 glibc 运行,并且由于某些细节(例如 link_map 结构的 l_map_start 成员是私有的),我无法复制和更改 glibc 代码。
    • 要知道为什么我接受了你的回答但没有给你赏金,请阅读我对 dreamlax 回答的评论。
    【解决方案2】:

    GNUstep 的NSException 实现不使用backtrace,而是使用libbfd(二进制文件描述符)。我认为实际完成工作的函数称为static void find_address,您可以查看here。使用这个简单的例子,我得到了以下结果。

    #include <Foundation/Foundation.h>
    
    @interface Test : NSObject {}
    + (void) test;
    @end
    
    @implementation Test
    + (void) test
    {
        Class GSStackTrace = objc_getClass("GSStackTrace");
    
        id stack = [GSStackTrace currentStack];
    
        for (int i = 0; i < [stack frameCount]; i++)
        {
            NSLog (@"%@", [[stack frameAt:i] function]);
        }
    }
    @end
    
    int main(int argc, char **argv)
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
        [Test test];
    
        [pool release];
    
        return 0;
    }
    

    输出(使用调试符号编译时):

    2010-10-18 14:14:46.188 a.out[29091] +[GSStackTrace currentStack]
    2010-10-18 14:14:46.190 a.out[29091] +[Test test]
    2010-10-18 14:14:46.190 a.out[29091] main
    2010-10-18 14:14:46.190 a.out[29091] __libc_start_main
    

    您也许可以将GSStackTrace 分开。它是一个“私有”类(这就是我需要使用objc_getClass 的原因,您还会收到很多无法识别的选择器警告),但它似乎包含读取Objective-C 类名所需的所有代码。

    在 Ubuntu 9.04 上测试,GNUstep 配置为 --enable-debug(因此 GSFunctionInfo 包含在构建中)。

    【讨论】:

    • 您赢得了赏金,因为您的回答使我实现了无需高级 3rd 方库(如 GNUStep ;))的有用堆栈跟踪。但我会接受杰里米的回答,因为我犯了两个问题:1.为什么堆栈跟踪不包括 Obj-C 符号和 2.我怎样才能有一个带有 Obj-C 符号的堆栈跟踪。显然,我开始赏金是为了得到第二个问题的答案。但第一个是标题,所以我会接受杰里米的回答。
    【解决方案3】:

    我希望您需要向 ObjC 运行时询问地址以获取符号信息。例如,从 backtrace() 返回的地址可能会传递给 object_getClass() 之类的东西来获取类。我还没有尝试过任何这些,但在这种情况下,这是我接下来要看的地方。

    【讨论】:

    • 哇,这对我来说似乎很投机。 backtrace(和 backtrace_symbols)是 glibc 扩展,后者依赖于链接器 -rdynamic 链接的符号。如果backtrace 返回的地址适用于 Objective-C 运行时的任何内容,我会感到惊讶,但我还是会尝试一下。
    • 好的,我查了运行时 API。我正在使用 GNU 运行时,但没有找到用于获取符号地址的类或方法的函数。在 GNU 运行时中没有 object_getClass() 而是一个 object_get_class() 获取对象。但我很确定,backtrace 的地址并没有指向对象。
    • 现在我确定:backtrace 的地址既不是 对象也不是 类。
    • 如果我知道您使用的是 GNU 运行时,我将不会回答,因为我不知道那里有什么可用或不可用。你应该从一开始就明确这一点。我可以提出其他建议,但不能提出与您的环境相关的建议。
    • 嗯,我认为运行时并不重要。尽管如此,我今天将在 Mac 上尝试这个示例,如果它确实在那里工作,我会澄清这个问题。
    最近更新 更多