共享库加载内存地址区域是否依赖于体系结构?

共享库加载内存地址区域是否依赖于体系结构?

我看到在 x86 上,在进程的 VM 中,共享库在堆和堆栈之间加载,即大多数在线文章中提到的 mmap 区域。但在 PowerPC linux 机器上,我看到所有库都在程序本身加载的位置下方加载。 “strace”显示库加载地址是在加载/映射库之前预先确定的(假设由 ld 确定)。

我想知道这是否与架构相关?有这方面的在线文档吗?

内存映射(ppc):

> cat /proc/self/maps
00100000-00102000 r-xp 00000000 00:00 0          [vdso]
0fe40000-0ffab000 r-xp 00000000 08:02 147120     /lib/libc-2.11.1.so
0ffab000-0ffbb000 ---p 0016b000 08:02 147120     /lib/libc-2.11.1.so
0ffbb000-0ffbf000 r--p 0016b000 08:02 147120     /lib/libc-2.11.1.so
0ffbf000-0ffc0000 rw-p 0016f000 08:02 147120     /lib/libc-2.11.1.so
0ffc0000-0ffc3000 rw-p 00000000 00:00 0 
0ffd0000-0fff0000 r-xp 00000000 08:02 147113     /lib/ld-2.11.1.so
0fff0000-0fff1000 r--p 00020000 08:02 147113     /lib/ld-2.11.1.so
0fff1000-0fff2000 rw-p 00021000 08:02 147113     /lib/ld-2.11.1.so
10000000-10005000 r-xp 00000000 08:02 195850     /bin/cat
10014000-10015000 rw-p 00004000 08:02 195850     /bin/cat
10015000-10036000 rwxp 00000000 00:00 0          [heap]
24000000-24001000 rw-p 00000000 00:00 0 
24013000-24014000 rw-p 00000000 00:00 0 
bfade000-bfaff000 rw-p 00000000 00:00 0          [stack]

strace(在 ppc 上):

> strace cat
execve("/bin/cat", ["cat"], [/* 26 vars */]) = 0
brk(0)                                  = 0x10015000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x24000000
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(0x3, 0xbfb84558)                = 0
mmap(NULL, 70203, PROT_READ, MAP_PRIVATE, 3, 0) = 0x24001000
close(3)                                = 0
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\24\0\0\0\1\17\345\376\260\0\0\0004"..., 512) = 512
fstat64(0x3, 0xbfb84540)                = 0
mmap(0xfe40000, 1582324, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xfe40000
mprotect(0xffab000, 65536, PROT_NONE)   = 0
mmap(0xffbb000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16b000) = 0xffbb000
mmap(0xffc0000, 9460, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffc0000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x24013000
mprotect(0xffbb000, 16384, PROT_READ)   = 0
mprotect(0xfff0000, 4096, PROT_READ)    = 0
munmap(0x24001000, 70203)               = 0
brk(0)                                  = 0x10015000
brk(0x10036000)                         = 0x10036000
fstat64(0x1, 0xbfb84d00)                = 0
fstat64(0, 0xbfb84d00)                  = 0

答案1

运行时堆栈的(首选)配置因硬件架构而异。但对于程序本身和共享库,对于动态链接的可执行文件,映射区域的内存位置由链接器确定。一般来说,内核的工作不是决定用户态程序的组件应该位于哪里。 CPU 架构也没有暗示该顺序。在相同的硬件上,甚至在一个正在运行的操作系统(即内核)中,我们可以想象不同的链接器以不同的方式排列它(exec Linux 调用从 ELF 文件中提取链接器的名称;请参阅in 中elf_interpreter的变量)。load_elf_binary()fs/binfmt_elf.c

在Linux中,默认的动态链接器ld-linux是glibc的一部分。它如何尝试映射对象可以在其源代码的_dl_map_object_from_fd()函数中看到。elf/dl-load.c有时,会考虑可执行文件中的首选项(大概取决于创建可执行文件的编译器和链接器),并且在某些情况下,内存映射的配置由内核决定。

有一些关于动态链接器及其架构依赖性的谷歌信息,例如:

答案2

经过一番挖掘后,我将回答我自己的问题。总之,这是一个依赖arch的东西。 PowerPC32 定义了首选加载地址函数,而大多数其他体系结构(包括 x86)没有定义此函数。

/* The idea here is that to conform to the ABI, we are supposed to try
   to load dynamic objects between 0x10000 (we actually use 0x40000 as
   the lower bound, to increase the chance of a memory reference from
   a null pointer giving a segfault) and the program's load address;
   this may allow us to use a branch instruction in the PLT rather
   than a computed jump.  The address is only used as a preference for
   mmap, so if we get it wrong the worst that happens is that it gets
   mapped somewhere else.  */

ElfW(Addr)
__elf_preferred_address (struct link_map *loader, size_t maplength,
             ElfW(Addr) mapstartpref)
{
  ElfW(Addr) low, high;
  struct link_map *l;
  Lmid_t nsid;

  /* If the object has a preference, load it there!  */
  if (mapstartpref != 0)
    return mapstartpref;

  /* Otherwise, quickly look for a suitable gap between 0x3FFFF and
     0x70000000.  0x3FFFF is so that references off NULL pointers will
     cause a segfault, 0x70000000 is just paranoia (it should always
     be superseded by the program's load address).  */
  low =  0x0003FFFF;
  high = 0x70000000;
  for (nsid = 0; nsid < DL_NNS; ++nsid)
    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
      {
    ElfW(Addr) mapstart, mapend;
    mapstart = l->l_map_start & ~(GLRO(dl_pagesize) - 1);
    mapend = l->l_map_end | (GLRO(dl_pagesize) - 1);
    assert (mapend > mapstart);

    /* Prefer gaps below the main executable, note that l ==
       _dl_loaded does not work for static binaries loading
       e.g. libnss_*.so.  */
    if ((mapend >= high || l->l_type == lt_executable)
        && high >= mapstart)
      high = mapstart;
    else if (mapend >= low && low >= mapstart)
      low = mapend;
    else if (high >= mapend && mapstart >= low)
      {
        if (high - mapend >= mapstart - low)
          low = mapend;
        else
          high = mapstart;
      }
      }

  high -= 0x10000; /* Allow some room between objects.  */
  maplength = (maplength | (GLRO(dl_pagesize) - 1)) + 1;
  if (high <= low || high - low < maplength )
    return 0;
  return high - maplength;  /* Both high and maplength are page-aligned.  */
}

相关内容