【问题标题】:Offset in nm symbol value?nm 符号值的偏移量?
【发布时间】:2012-05-12 22:12:28
【问题描述】:

只是为了给您一些背景信息,这就是我想要实现的目标: 我在共享对象文件中嵌入了一个 const char*,以便在 .so 文件本身中有一个版本字符串。我正在做数据分析,这个字符串让我可以让数据知道是哪个版本的软件产生的。这一切都很好。

我遇到的问题是当我尝试直接从 .so 库中读取字符串时。我尝试使用

nm libSMPselection.so | grep _version_info

得到

000000000003d968 D __SMPselection_version_info

这一切都很好并且符合预期(char* 称为_SMPselection_version_info)。 但是我希望现在能够打开文件,寻找 0x3d968 并开始读取我的字符串,但我得到的只是垃圾。

当我打开 .so 文件并简单地搜索字符串的内容(我知道它是如何开始的)时,我可以在地址 0x2e0b4 找到它。在这个地址它在那里,零终止并且如预期的那样。 (我目前正在使用这种方法。)

我不是计算机科学家。有人可以向我解释为什么 nm 显示的符号值不正确,或者不同,如果不是符号的地址,符号值是什么?

(顺便说一句,我正在使用 OSX 10.7 的 Mac)

【问题讨论】:

    标签: c++ macos unix linker nm


    【解决方案1】:

    假设它是 ELF 或类似结构的二进制文件,您必须考虑加载内容的地址,该地址受 ELF 标头中内容的影响。

    在您的二进制文件中使用objdump -Fd,您可以让反汇编程序也显示符号的确切文件偏移量。

    使用objdump -x可以找到这个加载器地址,对于标准的linux可执行文件,通常是0x400000。

    接下来需要注意的是查看它是否是间接字符串,使用objdump -g 可以最轻松地做到这一点。当字符串被发现为间接字符串时,在objdump -Fd 输出的位置,您将找不到字符串,而是地址。从中您需要再次减去加载程序地址。让我向您展示我的一个二进制文件的示例:

    objdump -Fd BIN | grep VersionString
      45152f:       48 8b 1d 9a df 87 00    mov    0x87df9a(%rip),%rbx        # ccf4d0 <acVersionString> (File Offset: 0x8cf4d0)
    
    objdump -x BIN
    ...
    LOAD off    0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**12
    ...
    

    所以我们查看文件中的0x8cf4d0,在hexeditor中找到:

    008C:F4D0 D8 C1 89 00  00 00 00 00  01 00 00 00  FF FF FF FF
    

    所以我们在那里取 0x89C1D8,减去 0x400000 并得到 0x49c1d8,当我们在 hexeditor 中查看时,我们发现:

    0049:C1D0 FF FF 7F 7F  FF FF 7F FF  74 72 75 6E  6B 5F 38 30
    0049:C1E0 34 33 00 00  00 00 00 00  00 00 00 00  00 00 00 00
    

    意思是“trunk_8043”。

    YMMV,尤其是当它是其他一些文件格式时,但这是这些东西如何构造的一般方式,有很多针对特殊情况的缺陷和细节。

    【讨论】:

    • 好的,谢谢,我觉得你已经回答了。我希望的是能够在不扫描整个文件(或分解它)的情况下获取字符串。顺便说一句,我的 objdump 版本没有 -F 选项(我使用的是 GNU objdump 2.17.50.0.6-20.el5 20061020)。
    • @Simon:那是一个相当古老的 objdump 版本(我什至不记得 2006 年是什么样子了)。您可以通过从0xccf4d0 中减去相同的0x400000 偏移量自行获得此文件偏移量。也许还有一个工具可以为您完成所有这些事情,或者您可以自己编写一个小脚本..
    【解决方案2】:

    没有人建议最简单的方法:做一个动态加载你的库的二进制文件(在命令行上给它命名)并为你的符号执行 dlsym() (或者它也可以在命令行上得到它)将它转换为字符串指针并将其打印到标准输出。

    【讨论】:

    • 这是个好主意。我现在正在尝试。只有一个问题:我正在测试的库对其他库有相当长的依赖链。如果我尝试使用 dlopen 加载它们,我会收到 Symbol-not-found 错误。我感兴趣的版本字符串当然没有依赖关系。如何让 dl 忽略依赖项?
    • 我已经检查过了。如果我加载了所有依赖项(这是我的两个用例之一),这将非常有用。谢谢你的想法。
    【解决方案3】:

    在 Linux 上,您可以使用“字符串”命令从二进制文件中提取字符串。

    http://linux.about.com/library/cmd/blcmdl1_strings.htm

    在 HPUX 中(我认为在其他 Unix 风格中也是如此)有一个类似的命令,称为“what”。它只提取以“@(#)”开头的字符串,但如果您控制字符串的内容,这不是问题。

    【讨论】:

    • 这将如何帮助他获取特定符号的内容?
    • "what" 很好,但我真的希望我的字符串是多行并且在换行处停止。 strings 命令打印所有字符串,但不告诉我自己的字符串在哪里结束。此外,它似乎只是阅读了整个文件,这正是我所做的。如果我可以读取符号条目并直接跳转到字符串,这似乎更优雅。
    【解决方案4】:

    为什么你会期望nm 显示的偏移量是 .so 文件? .so 文件不仅仅是内存映像;他们包含 还有很多其他信息,并且或多或少有些复杂 格式。在 Unix 下(至少在大多数 Unices 下),共享对象使用 精灵格式。要查找信息,您必须解释 文件中的各个字段,以查找您想要的符号在哪里 位于,在哪个段中,以及该段在文件中的开始位置。 (您可能会找到一个可以简化阅读它们的库。)

    另外,如果您说嵌入了char const* 是正确的, 即您的代码包含以下内容:

    char const* version = "...";
    

    那么version的地址或偏移量就是 指针,而不是它指向的字符串数据。定义为:

    char const version[] = "...";
    

    会解决这个问题的。

    最后,最简单的解决方案可能是确保 字符串有一些高度可识别的模式,并扫描整个文件 线性寻找这种模式。

    【讨论】:

    • 扫描整个文件正是我的工作。它似乎不那么优雅,我想学习一些东西,所以我问了这个问题。声明数组而不是指针使得从 nm 显示的符号列表中消失。
    • @Simon 好吧,正确解析文件更优雅,但也需要更多工作。至于声明数组而不是指针,它消失的原因是因为 C++ 的一个微妙之处:一个 const 对象默认具有内部链接。如果您声明它extern char const version[] = "...",则不会发生这种情况; extern 强制外部链接,初始化使其成为定义,而不是声明。
    • 谢谢,我当然忘了联动!使用extern 关键字,字符串现在出现在符号表中,我从nm 获得的地址实际上与字符串的位置匹配,它现在可以工作了。我可以通过查找从nm 获得的地址来获取字符串!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 2014-12-20
    相关资源
    最近更新 更多