为什么动态链接器*减去*虚拟地址以找出加载的共享库可执行文件在内存中的位置?

Why does the dynamic linker *subtract* the virtual address to find out location of loaded shared library executable in memory?

根据dl_main中的ld源代码here。如果没有 phdr 传递给 dl_main 的上下文,我对为什么 main_map 的加载地址是通过减去虚拟地址推导出来有点困惑。

我查过的代码:

1124 static void
1125 dl_main (const ElfW(Phdr) *phdr,
1126          ElfW(Word) phnum,
1127          ElfW(Addr) *user_entry,
1128          ElfW(auxv_t) *auxv)
1129 {
1130   const ElfW(Phdr) *ph;
1131   enum mode mode;
1132   struct link_map *main_map;
1133   size_t file_size;
1134   char *file;
1135   bool has_interp = false;
1136   unsigned int i;
1137   bool prelinked = false;
1138   bool rtld_is_main = false;
1139   void *tcbp = NULL;
...    // Before this else, it thinks you're calling `ld.so.<version>` directly. This is not usually the case.
1366   else
1367     {
1368       /* Create a link_map for the executable itself.
1369          This will be what dlopen on "" returns.  */
1370       main_map = _dl_new_object ((char *) "", "", lt_executable, NULL,
1371                                  __RTLD_OPENEXEC, LM_ID_BASE);
1372       assert (main_map != NULL);
1373       main_map->l_phdr = phdr;
1374       main_map->l_phnum = phnum;
1375       main_map->l_entry = *user_entry;
1376 
1377       /* Even though the link map is not yet fully initialized we can add
1378          it to the map list since there are no possible users running yet.  */
1379       _dl_add_to_namespace_list (main_map, LM_ID_BASE);
1380       assert (main_map == GL(dl_ns)[LM_ID_BASE]._ns_loaded);
1399     }
...    // Loops through program headers loaded in sequence from the ELF header.
1409   for (ph = phdr; ph < &phdr[phnum]; ++ph)
1410     switch (ph->p_type)
1411       {
1412       case PT_PHDR:
1413         /* Find out the load address.  */
1414         main_map->l_addr = (ElfW(Addr)) phdr - ph->p_vaddr;
1415         break;

那么这里为什么要减去加载地址呢?

特别是在第 1414 行,我们看到 main_map->l_addr = (ElfW(Addr)) phdr - ph->p_vaddr;。从为 main_map 定义类型 link_map 的文件 link.h 中,我看到 link_map 是描述已加载共享对象的 structl_addr 字段用于描述当您 运行 readelf:

❯ readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x0000000000000268 0x0000000000000268  R      0x8
  INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000004c0 0x00000000000004c0  R      0x1000
...

这意味着 l_addr 不是 load 地址。它实际上是您需要从当前内存中添加以访问共享对象内容的 offset