A região do endereço da mem de carregamento da biblioteca compartilhada depende da arquitetura?

2

Vejo que no x86, na VM de um processo, as bibliotecas compartilhadas são carregadas entre o heap e a pilha, ou seja, a área do mmap, conforme observado na maioria dos artigos on-line. Mas em uma caixa Linux PowerPC, vejo que todas as bibliotecas são carregadas abaixo de onde o próprio programa é carregado. "strace" mostra que o endereço de carregamento da biblioteca é pré-determinado (assumindo por ld) antes de carregar / mapear a biblioteca.

Eu me pergunto se isso é uma coisa dependente de arquitetura? Existe algum documento on-line sobre isso?

mapa de memória (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 (em 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, "7ELF
> 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 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, "7ELF%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%560%pre%%pre%%pre%04"..., 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
%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%560%pre%%pre%%pre%04"..., 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
    
por wei 11.09.2015 / 22:25

2 respostas

0

Eu responderei a minha própria pergunta depois de algumas investigações. Em suma, isso é uma coisa dependente do arco. O PowerPC32 define uma função de endereço de carga preferida, enquanto a maioria dos outros arcos, incluindo o x86, não define isso.

/* 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.  */
}
    
por 21.09.2015 / 02:15
1
A disposição (preferida) da pilha de tempo de execução difere entre as arquiteturas de hardware. Mas, quanto às bibliotecas apropriadas e compartilhadas do programa, para um executável vinculado dinamicamente, a localização da memória de regiões mapeadas é determinada pelo vinculador. Geralmente, não é um trabalho do kernel decidir onde os componentes de um programa de área de usuário devem ser localizados. Nem a ordem é implícita pela arquitetura da CPU. No mesmo hardware, e mesmo dentro de um sistema operacional (ou seja, kernel), podemos imaginar diferentes linkers organizando-o de forma diferente (exec chamada Linux extrai o nome do linker do arquivo ELF; ver elf_interpreter variable em load_elf_binary() em fs/binfmt_elf.c ).

No Linux, o vinculador dinâmico padrão ld-linux é uma parte do glibc. A forma como ele tenta mapear objetos pode ser vista na função _dl_map_object_from_fd() em elf/dl-load.c de seu código-fonte. Às vezes, as preferências no executável são contabilizadas (que presumidamente dependem do compilador e do linker que criou o executável) e, em alguns casos, a disposição de um mapa de memória é decidida pelo kernel.

Existem algumas informações úteis sobre vinculadores dinâmicos e sua dependência de arquitetura, como:

por 11.09.2015 / 23:01