【问题标题】:How do I find symbol offset in .so from runtime address如何从运行时地址中找到 .so 中的符号偏移量
【发布时间】:2017-04-30 20:33:45
【问题描述】:

我需要能够在程序终止后将指令指针(来自回溯)转换为文件和行号。在执行过程中,我可以存储稍后进行离线分析所需的任何附加信息,但分析必须离线进行。

现在,除了发出指令指针之外,我还在发出一组结构,其中包含来自dl_iterate_phdr 的一些信息。我包括模块的名称,基地址的dlpi_addr 字段,以及第一个phdr 标记为可执行文件的p_memsz 字段。

然后,在我的离线工具中,我使用我保存的大小和地址表来识别给定地址的模块。我从指令地址中减去dlpi_addr 并将其传递给addr2line

这似乎在大多数情况下都有效,但有时我认为它有点不对劲(即,它找到了一个相邻的符号——我知道行号信息并不总是正确的)。主可执行文件也完全失败。我很确定我只是没有正确处理所有的偏移。

我需要做些什么才能使其正常工作?我知道有几个库可以做到这一点(lldblibdwarf),但它们没有很好的文档记录。我还试图在主程序和离线工具之间传递尽可能少的信息,所以我不想将内存映射转储为字符串。

以下是一些示例数据:

dl_iterate_phdr 回调代码:

for ( ElfW(Half) i = 0; i < info->dlpi_phnum; i++ )
{
    const ElfW(Phdr)& header = info->dlpi_phdr[i];

    // Find the executable segment
    if ( (header.p_flags & PF_X) && (header.p_type == PT_LOAD) )
    {
        if ( info->dlpi_addr == getauxval(AT_SYSINFO_EHDR) )
        {
            // vdso section.
            continue;
        }

        printf("Module %d\n", modules->m_Count);
        printf("Module name: %s\n", info->dlpi_name);
        printf("Start address: %p\n", info->dlpi_addr);
        printf("Length: %jx\n", (uintmax_t)header.p_memsz);
        ...
    }
}

输出

Module 0
Module name:
Start address: (nil)
Length: 7276
Module 1
Module name: /lib/x86_64-linux-gnu/libdl.so.2
Start address: 0x7ff933d63000
Length: 2060
Module 2
Module name: /lib/x86_64-linux-gnu/libpthread.so.0
Start address: 0x7ff933b45000
Length: 188c4
Module 3
Module name: /usr/lib/x86_64-linux-gnu/libstdc++.so.6
Start address: 0x7ff933841000
Length: e549d
Module 4
Module name: /lib/x86_64-linux-gnu/libm.so.6
Start address: 0x7ff93353b000
Length: 1042cc
Module 5
Module name: /lib/x86_64-linux-gnu/libgcc_s.so.1
Start address: 0x7ff933325000
Length: 1548c
Module 6
Module name: /lib/x86_64-linux-gnu/libc.so.6
Start address: 0x7ff932f60000
Length: 1b9cc0
Module 7
Module name: /lib64/ld-linux-x86-64.so.2
Start address: 0x7ff933f67000
Length: 22118
Module 8
Module name: /path/to/another.so
Start address: 0x7ff932cc3000
Length: b682
Module 9
Module name: /path/to/yet/another.so
Start address: 0x7ff932a1c000
Length: 164fc

cat /proc//maps

00400000-00408000 r-xp 00000000 fc:01 56098817                           /path/to/main/executable
00607000-00608000 r--p 00007000 fc:01 56098817                           /path/to/main/executable
00608000-00609000 rw-p 00008000 fc:01 56098817                           /path/to/main/executable
010cc000-010ed000 rw-p 00000000 00:00 0                                  [heap]
7ff924000000-7ff924021000 rw-p 00000000 00:00 0
7ff924021000-7ff928000000 ---p 00000000 00:00 0
7ff92c000000-7ff92c021000 rw-p 00000000 00:00 0
7ff92c021000-7ff930000000 ---p 00000000 00:00 0
7ff931a1a000-7ff931a1b000 ---p 00000000 00:00 0
7ff931a1b000-7ff93221b000 rw-p 00000000 00:00 0
7ff93221b000-7ff93221c000 ---p 00000000 00:00 0
7ff93221c000-7ff932a1c000 rw-p 00000000 00:00 0
7ff932a1c000-7ff932a33000 r-xp 00000000 fc:01 56360961                   /path/to/yet/another.so
7ff932a33000-7ff932c32000 ---p 00017000 fc:01 56360961                   /path/to/yet/another.so
7ff932c32000-7ff932c33000 r--p 00016000 fc:01 56360961                   /path/to/yet/another.so
7ff932c33000-7ff932cc3000 rw-p 00017000 fc:01 56360961                   /path/to/yet/another.so
7ff932cc3000-7ff932ccf000 r-xp 00000000 fc:01 56229891                   /path/to/another.so
7ff932ccf000-7ff932ece000 ---p 0000c000 fc:01 56229891                   /path/to/another.so
7ff932ece000-7ff932ecf000 r--p 0000b000 fc:01 56229891                   /path/to/another.so
7ff932ecf000-7ff932ed0000 rw-p 0000c000 fc:01 56229891                   /path/to/another.so
7ff932ed0000-7ff932f60000 rw-p 00000000 00:00 0
7ff932f60000-7ff93311a000 r-xp 00000000 fc:01 17039726                   /lib/x86_64-linux-gnu/libc-2.19.so
7ff93311a000-7ff93331a000 ---p 001ba000 fc:01 17039726                   /lib/x86_64-linux-gnu/libc-2.19.so
7ff93331a000-7ff93331e000 r--p 001ba000 fc:01 17039726                   /lib/x86_64-linux-gnu/libc-2.19.so
7ff93331e000-7ff933320000 rw-p 001be000 fc:01 17039726                   /lib/x86_64-linux-gnu/libc-2.19.so
7ff933320000-7ff933325000 rw-p 00000000 00:00 0 
7ff933325000-7ff93333b000 r-xp 00000000 fc:01 17039378                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7ff93333b000-7ff93353a000 ---p 00016000 fc:01 17039378                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7ff93353a000-7ff93353b000 rw-p 00015000 fc:01 17039378                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7ff93353b000-7ff933640000 r-xp 00000000 fc:01 17039591                   /lib/x86_64-linux-gnu/libm-2.19.so
7ff933640000-7ff93383f000 ---p 00105000 fc:01 17039591                   /lib/x86_64-linux-gnu/libm-2.19.so
7ff93383f000-7ff933840000 r--p 00104000 fc:01 17039591                   /lib/x86_64-linux-gnu/libm-2.19.so
7ff933840000-7ff933841000 rw-p 00105000 fc:01 17039591                   /lib/x86_64-linux-gnu/libm-2.19.so
7ff933841000-7ff933927000 r-xp 00000000 fc:01 8390197                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
7ff933927000-7ff933b26000 ---p 000e6000 fc:01 8390197                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
7ff933b26000-7ff933b2e000 r--p 000e5000 fc:01 8390197                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
7ff933b2e000-7ff933b30000 rw-p 000ed000 fc:01 8390197                    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19
7ff933b30000-7ff933b45000 rw-p 00000000 00:00 0
7ff933b45000-7ff933b5e000 r-xp 00000000 fc:01 17039713                   /lib/x86_64-linux-gnu/libpthread-2.19.so
7ff933b5e000-7ff933d5d000 ---p 00019000 fc:01 17039713                   /lib/x86_64-linux-gnu/libpthread-2.19.so
7ff933d5d000-7ff933d5e000 r--p 00018000 fc:01 17039713                   /lib/x86_64-linux-gnu/libpthread-2.19.so
7ff933d5e000-7ff933d5f000 rw-p 00019000 fc:01 17039713                   /lib/x86_64-linux-gnu/libpthread-2.19.so
7ff933d5f000-7ff933d63000 rw-p 00000000 00:00 0
7ff933d63000-7ff933d66000 r-xp 00000000 fc:01 17039596                   /lib/x86_64-linux-gnu/libdl-2.19.so
7ff933d66000-7ff933f65000 ---p 00003000 fc:01 17039596                   /lib/x86_64-linux-gnu/libdl-2.19.so
7ff933f65000-7ff933f66000 r--p 00002000 fc:01 17039596                   /lib/x86_64-linux-gnu/libdl-2.19.so
7ff933f66000-7ff933f67000 rw-p 00003000 fc:01 17039596                   /lib/x86_64-linux-gnu/libdl-2.19.so
7ff933f67000-7ff933f8a000 r-xp 00000000 fc:01 17039716                   /lib/x86_64-linux-gnu/ld-2.19.so
7ff934047000-7ff934049000 rw-p 00000000 00:00 0
7ff93405e000-7ff93407e000 rw-p 00000000 00:00 0
7ff9340b2000-7ff9340c0000 rw-p 00000000 00:00 0
7ff9340dc000-7ff934152000 rw-p 00000000 00:00 0
7ff93415f000-7ff934166000 rw-p 00000000 00:00 0
7ff934172000-7ff934175000 rw-p 00000000 00:00 0
7ff93417b000-7ff93417c000 rw-p 00000000 00:00 0
7ff934185000-7ff934189000 rw-p 00000000 00:00 0
7ff934189000-7ff93418a000 r--p 00022000 fc:01 17039716                   /lib/x86_64-linux-gnu/ld-2.19.so
7ff93418a000-7ff93418b000 rw-p 00023000 fc:01 17039716                   /lib/x86_64-linux-gnu/ld-2.19.so
7ff93418b000-7ff93418c000 rw-p 00000000 00:00 0
7ffd31892000-7ffd318b6000 rw-p 00000000 00:00 0                          [stack]
7ffd31913000-7ffd31915000 r--p 00000000 00:00 0                          [vvar]
7ffd31915000-7ffd31917000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

readelf -l 可执行文件

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000007276 0x0000000000007276  R E    200000
  LOAD           0x0000000000007db8 0x0000000000607db8 0x0000000000607db8
                 0x0000000000000389 0x00000000000004c0  RW     200000
  DYNAMIC        0x0000000000007dd8 0x0000000000607dd8 0x0000000000607dd8
                 0x0000000000000220 0x0000000000000220  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000004a40 0x0000000000404a40 0x0000000000404a40
                 0x000000000000078c 0x000000000000078c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000007db8 0x0000000000607db8 0x0000000000607db8
                 0x0000000000000248 0x0000000000000248  R      1

数学示例:

  1. 主可执行文件中有一个名为“DoSomeWork”的函数,位于 0x401329。但是,0x401329 不在 0x0 和 0x7276d 之间。不过,它确实在 0x400000 的 0x7276 范围内,这就是为什么我对主模块的处理特别有效的原因。 (我对使用 __etext 的看法被证明是胡言乱语——代码从来没有工作过,但也没有受到伤害。)如果我在 0x401329 上运行 addr2line,它就可以工作。但是,如果我像对待所有其他模块一样对待这个模块,我将无法将地址与任何文件相关联,因为它不在 (startAddress, startAddress + length) 之间。
  2. 我用作测试的 libm 的一个示例是 lgamma(float),在此运行中位于 0x7ff933561620。这完全落在与内存映射和来自 dl_iterate_phdr 的数据相匹配的范围内 (0x7ff93353b000, 0x7ff93353b000 + 0x1042cc)。因此,当我从函数地址中减去起始地址时,我得到偏移量 0x26620,当传递给 libm 的 addr2line 时,它​​可以正常工作(它返回 w_lgamma 的文件/行号)。

谢谢!

【问题讨论】:

  • 只是想知道,这不是地址空间布局随机化应该防止的那种分析吗?这应该意味着加载的程序没有以可预测的方式布局在内存中,AFAIK。
  • 不,目标不是预测 /future/ 运行,而是分析以前的运行。即使有了这一切,您也无法预测符号在未来运行时会落在哪里。

标签: c linux elf


【解决方案1】:

我需要做什么才能正常工作?

你所描述的是正确的,应该工作。

很遗憾,您没有提供任何详细信息来让我们猜测是什么导致这对您不起作用。

主可执行文件也完全失败。

不应该(对于非PIE 可执行文件,dlpi_addr 应该始终为 0)。

您应该尝试创建一个minimal example,这样我们就可以告诉您您做错了什么。从一个简单的主二进制文件开始,显示您获得的地址、nmdlpi_addr 的输出等。

【讨论】:

  • 我希望我只是错过了一个主要部分,比如处理一些偏移量。只要我能找到一台合适的计算机,我就会得到一个复制品,或者至少是一些示例数据和代码 sn-ps(不幸的是,我现在正在旅行,所以可能不会马上)。
  • 我相信一个例子是,为了获得主可执行文件的正确地址,我不得不假装将模块大小计算为 __etext - 0x40000,但同时,我必须 /not/将 0x40000 视为模块的起始地址(即 0)。我相信(从记忆中)phdr 信息声称起始偏移量为 0x40040。也就是说,我知道我说得非常模糊。我会尽快得到更多细节。
【解决方案2】:

dlpi_addr 不是映射的起始地址,而是相对于 p_vaddr 地址映射到的PT_LOAD 程序头中的地址的偏移量。将这些值相加,您将获得绝对虚拟地址范围。

【讨论】:

  • 这是有道理的,听起来它可以解决从主可执行文件中正确检测符号的情况。要计算符号的偏移量以便使用 addr2line 查找它们,我是否只需减去 dlpi_addr,而忽略 p_vaddr?那么逻辑是:对于 (dlpi_addr + p_vaddr, dlpi_addr + p_vaddr + p_memsz) 中的符号,通过简单地减去 dlpi_addr(不考虑 p_vaddr)来计算 addr2line 的偏移量?
  • @Falcon:对。 ELF 文件寻址(例如符号表)中的虚拟地址与运行时虚拟地址之间的转换纯粹是 dlpi_addr 的加法/减法。
  • 顺便说一句,我认为您对主要非 PIE 程序的差异也适用于预链接处理的共享库,尽管我自己从未使用过预链接并且可能会弄错。
  • 既然我回到办公室,我今天就试试这个公式。如果它不起作用,我会找到一个看起来错误的示例符号以获取更多信息。关于 PIE,通常需要一个特殊的构建标志,不是吗?我绝对没有明确启用它。
  • 使用上面的计算解决了这个问题。主可执行文件是 186 个常见 linux 库中 p_vaddr 为 != 0 的唯一示例。感谢您的帮助!
猜你喜欢
  • 2021-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-11
  • 2023-03-31
  • 2013-08-28
  • 2016-05-23
  • 2015-06-27
相关资源
最近更新 更多