Как общая библиотека находит раздел GOT?

Пока я читал http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/#id1 пришел вопрос:

Как разделяемая библиотека PIC после загрузки где-то в виртуальном адресном пространстве процесса знает, как ссылаться на внешние переменные?

Вот код рассматриваемой разделяемой библиотеки:

#include <stdio.h>

extern long var;

void
shara_func(void)
{
        printf("%ld\n", var);
}

Создайте объектный код, затем общий объект (библиотеку):

gcc -fPIC -c lib1.c                    # produce PIC lib1.o
gcc -fPIC -shared lib1.o -o liblib1.so # produce PIC shared library

Дизассемблировать shara_func в разделяемой библиотеке:

objdump -d liblib1.so
 ...
 00000000000006d0 <shara_func>:
 6d0:   55                      push   %rbp
 6d1:   48 89 e5                mov    %rsp,%rbp
 6d4:   48 8b 05 fd 08 20 00    mov    0x2008fd(%rip),%rax        # 200fd8 <_DYNAMIC+0x1c8>
 6db:   48 8b 00                mov    (%rax),%rax
 6de:   48 89 c6                mov    %rax,%rsi
 6e1:   48 8d 3d 19 00 00 00    lea    0x19(%rip),%rdi        # 701 <_fini+0x9>
 6e8:   b8 00 00 00 00          mov    $0x0,%eax
 6ed:   e8 be fe ff ff          callq  5b0 <printf@plt>
 6f2:   90                      nop
 6f3:   5d                      pop    %rbp
 6f4:   c3                      retq   
...

Я вижу, что инструкция по адресу 0x6d4 перемещает некоторый адрес, относящийся к ПК, на rax, я полагаю, что это запись в GOT, GOT ссылается относительно PC< /em>, чтобы получить адрес внешней переменной var во время выполнения (он разрешается во время выполнения в зависимости от того, где была загружена var). Затем, после выполнения инструкции по адресу 0x6db, мы получаем фактическое содержимое внешней переменной, помещенное в rax, затем перемещаем значение из rax в rsi > - второй параметр функции, переданный в регистр.

Я думал, что в памяти процесса есть только один GOT, однако видите, что библиотека ссылается на GOT? Как разделяемая библиотека знает смещение для GOT процесса, когда она (библиотека PIC) не знает, где в памяти процесса она будет загружена? Или у каждой разделяемой библиотеки есть свой GOT, который загружается вместе с ней? Буду очень рад, если вы проясните мое замешательство.


person Bulat M.    schedule 30.09.2016    source источник
comment
Булат, ты проверял objdump -r и objdump -R поиском RELOC (перемещения) в своем ELF - man7.org/linux/man-pages/man1/objdump.1.html?   -  person osgx    schedule 02.10.2016
comment
@osgx, да, это 0x200fd8 R_X86_64_GLOB_DAT var + 0 перемещение. Относительно ПК: 0x2008fd + 0x6db = 0x200fd8. Не понимаю, как компоновщик вычислил смещение 0x2008fd, откуда он знает, где будет находиться GOT процесса во время выполнения.   -  person Bulat M.    schedule 02.10.2016
comment
Булат, правда: есть два линкера: один ld команда, а другой рантайм линкер, RTLD, ld-linux.so.2 программа в линуксе (он прописан как интерпретатор динамических ELF - stackoverflow.com/questions/5130654). Таким образом, R_X86_64_GLOB_DAT, созданный компоновщиком ld, будет разрешен компоновщиком времени выполнения RTLD: osxr.org:8080/glibc/source/sysdeps/x86_64/dl-machine.h#0304. Это тот, кто открывает файл lib; mmсопоставляет его с правильным местом, создает настоящий GOT и разрешает перемещения, чтобы указать на настоящий GOT.   -  person osgx    schedule 02.10.2016
comment
@osgs, каждая общая библиотека сопоставляется компоновщиком времени выполнения (RTLD) с виртуальным адресным пространством процесса с соответствующими разделяемыми библиотеками GOT и PLT (при необходимости) в его разделе данных, так что этот процесс может иметь несколько GOT для нескольких общих библиотек?   -  person Bulat M.    schedule 02.10.2016
comment
Булат, Если я понимаю eli. thegreenplace.net/2011/11/03/ и technovelty.org/linux/ правильно, да, если GOT загружается как .data каждой библиотеки, существует несколько GOT. И компоновщик поместит реальный абсолютный адрес окончательно выбранной переменной (она может разрешаться в другую библиотеку или в исполняемый файл) в нужную запись в GOT (есть ли какие-либо дополнительные точки RELOC внутри секции .got библиотеки? или это _GLOB_DAT?) . 0x6db будет использовать этот указатель для получения var.   -  person osgx    schedule 02.10.2016
comment
@osgx, да, любые дополнительные ссылки на внешние переменные приводят к перемещению _GLOB_DAT.   -  person Bulat M.    schedule 02.10.2016


Ответы (1)


Я думал, что в памяти процесса есть только один GOT, однако видите, что библиотека ссылается на GOT?

Мы ясно видим раздел .got как часть библиотеки. С помощью readelf мы можем узнать, какие разделы есть в библиотеке и как они загружаются:

readelf -e liblib1.so
...
Section Headers:
  [21] .got              PROGBITS         0000000000200fd0  00000fd0
       0000000000000030  0000000000000008  WA       0     0     8
...

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x000000000000078c 0x000000000000078c  R E    200000
  LOAD           0x0000000000000df8 0x0000000000200df8 0x0000000000200df8
                 0x0000000000000230 0x0000000000000238  RW     200000
...
 Section to Segment mapping:
  Segment Sections...
   00     ... .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   01     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   02     .dynamic 

Итак, есть раздел .got, но компоновщик времени выполнения ld-linux.so.2 (зарегистрированный как интерпретатор для динамических ELF) не загружает разделы; он загружает сегменты, как описано в заголовке программы с типом LOAD. .got является частью сегмента 01 LOAD с флагами RW. Другая библиотека будет иметь свой GOT (подумайте о том, чтобы скомпилировать liblib2.so из аналогичного исходника, он ничего не будет знать о liblib1.so и будет иметь свой GOT); так что это "Глобальный" только для библиотеки; а не ко всему образу программы в памяти после загрузки.

Как разделяемая библиотека знает смещение для GOT процесса, когда она (библиотека PIC) не знает, где в памяти процесса она будет загружена?

Это делается статическим компоновщиком, когда он берет несколько объектов ELF и объединяет их все в одну библиотеку. Линкер сгенерирует раздел .got и поместит его в какое-то место с известным смещением от кода библиотеки (относительно ПК, относительно рипа). Он записывает инструкции в заголовок программы, поэтому относительный адрес известен, и это единственный адрес, необходимый для доступа к собственному GOT.

Когда objdump используется с флагами -r/-R, он будет печатать информацию о перемещениях (статических/динамических), записанных в ELF-файле или библиотеке; его можно комбинировать с флагом -d. здесь был перемещен объект lib1.o; нет известного смещения для GOT, mov имеет все нули:

$ objdump -dr lib1.o 
lib1.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <shara_func>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax        # b <shara_func+0xb>
            7: R_X86_64_REX_GOTPCRELX   var-0x4
   b:   48 8b 00                mov    (%rax),%rax
   e:   48 89 c6                mov    %rax,%rsi

В файле библиотеки это было преобразовано в относительный адрес с помощью gcc -shared (он вызывает ld вариант collect2 внутри):

$ objdump -d liblib1.so 

liblib1.so:     file format elf64-x86-64

00000000000006d0 <shara_func>:
 6d0:   55                      push   %rbp
 6d1:   48 89 e5                mov    %rsp,%rbp
 6d4:   48 8b 05 fd 08 20 00    mov    0x2008fd(%rip),%rax        # 200fd8 <_DYNAMIC+0x1c8>

И, наконец, динамическое перемещение в GOT с указанием фактического адреса var (сделано rtld - ld-linux.so.2):

$ objdump -R liblib1.so 

liblib1.so:     file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
...
0000000000200fd8 R_X86_64_GLOB_DAT  var

Давайте воспользуемся вашей библиотекой, добавив исполняемый файл с определением, скомпилировав его и запустив с включенной отладкой rtld:

$ cat main.c 
long var;
int main(){
    shara_func();
    return 0;
}
$ gcc main.c -llib1 -L. -o main -Wl,-rpath=`pwd`
$ LD_DEBUG=all ./main 2>&1 |less
...
   311:     symbol=var;  lookup in file=./main [0]
   311:     binding file /test3/liblib1.so [0] to ./main [0]: normal symbol `var'

Итак, компоновщик смог привязать релокацию для var к "основному" файлу ELF, где он определен:

$ gdb -q ./main 
Reading symbols from ./main...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x4006da
(gdb) r
Starting program: /test3/main 

Breakpoint 1, 0x00000000004006da in main ()
(gdb) disassemble shara_func
Dump of assembler code for function shara_func:
   0x00007ffff7bd56d0 <+0>: push   %rbp
   0x00007ffff7bd56d1 <+1>: mov    %rsp,%rbp
   0x00007ffff7bd56d4 <+4>: mov    0x2008fd(%rip),%rax        # 0x7ffff7dd5fd8
   0x00007ffff7bd56db <+11>:    mov    (%rax),%rax
   0x00007ffff7bd56de <+14>:    mov    %rax,%rsi

Никаких изменений в mov в вашем func. rax после func+4 равен 0x601040, это третье отображение ./main в соответствии с /proc/$pid/maps:

00601000-00602000 rw-p 00001000 08:07 6691394                            /test3/main

И он был загружен из основного после этого заголовка программы (readelf -e ./main)

LOAD           0x0000000000000df0 0x0000000000600df0 0x0000000000600df0
               0x0000000000000248 0x0000000000000258  RW     200000

Это часть раздела .bss:

 [26] .bss              NOBITS           0000000000601038  00001038
      0000000000000010  0000000000000000  WA       0     0     8

После перехода к func+11 мы можем проверить значение в GOT:

(gdb) b shara_func
(gdb) r
(gdb) si
0x00007ffff7bd56db in shara_func () from /test3/liblib1.so
1: x/i $pc
=> 0x7ffff7bd56db <shara_func+11>:  mov    (%rax),%rax
(gdb) p $rip+0x2008fd
$6 = (void (*)()) 0x7ffff7dd5fd8
(gdb) x/2x 0x7ffff7dd5fd8
0x7ffff7dd5fd8: 0x00601040  0x00000000

Кто записал правильное значение в эту запись GOT?

(gdb) watch *0x7ffff7dd5fd8
Hardware watchpoint 2: *0x7ffff7dd5fd8
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /test3/main 

Hardware watchpoint 2: *0x7ffff7dd5fd8

Old value = <unreadable>
New value = 6295616
0x00007ffff7de36bf in elf_machine_rela (..) at ../sysdeps/x86_64/dl-machine.h:435
(gdb) bt
#0  0x00007ffff7de36bf in elf_machine_rela (...) at ../sysdeps/x86_64/dl-machine.h:435
#1  elf_dynamic_do_Rela (...) at do-rel.h:137
#2  _dl_relocate_object (...) at dl-reloc.c:258
#3  0x00007ffff7ddaf5b in dl_main (...)        at rtld.c:2072
#4  0x00007ffff7df0462 in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fffffffde20, 
    dl_main=dl_main@entry=0x7ffff7dd89a0 <dl_main>) at ../elf/dl-sysdep.c:249
#5  0x00007ffff7ddbe7a in _dl_start_final (arg=0x7fffffffde20) at rtld.c:307
#6  _dl_start (arg=0x7fffffffde20) at rtld.c:413
#7  0x00007ffff7dd7cc8 in _start () from /lib64/ld-linux-x86-64.so.2

(gdb) x/2x 0x7ffff7dd5fd8
0x7ffff7dd5fd8: 0x00601040  0x00000000

Компоновщик времени выполнения glibc сделал (rtld.c), непосредственно перед вызовом main - вот исходный код (немного другая версия) - http://code.metager.de/source/xref/gnu/glibc/sysdeps/x86_64/dl-machine.h

329 case R_X86_64_GLOB_DAT:
330 case R_X86_64_JUMP_SLOT:
331   *reloc_addr = value + reloc->r_addend;
332   break;

С помощью обратного шага мы можем получить историю кода и старое значение = 0:

(gdb) b _dl_relocate_object 
(gdb) r
(gdb) dis 3
(gdb) target record-full
(gdb) c
(gdb) disp/i $pc
(gdb) rsi
(gdb) rsi
(gdb) rsi
(gdb) x/2x 0x7ffff7dd5fd8
0x7ffff7dd5fd8: 0x00000000  0x00000000


=> 0x7ffff7de36b8 <_dl_relocate_object+1560>:   add    0x10(%rbx),%rax
=> 0x7ffff7de36bc <_dl_relocate_object+1564>:   mov    %rax,(%r10)
=> 0x7ffff7de36bf <_dl_relocate_object+1567>:   nop
person osgx    schedule 02.10.2016
comment
Читая в середине, как можно найти PID процесса, который отлаживается в gdb? - person Bulat M.; 02.10.2016
comment
Можно открыть другую консоль (терминал) и найти ps вывод для процесса. Также pidof main, если тестовая программа называется main и выполняется только один процесс программы. - person osgx; 02.10.2016
comment
Булат, не могли бы вы удалить свой комментарий о рандомизации пространства va из stackoverflow.com/questions/5130654/ и переместить вопрос сюда? Его можно отключить в вашем ядре во время сборки (для ядер без MMU... или слишком старых ядер... или каким-то сторонним патчем); /proc можно не монтировать; значение можно получить с помощью sysctl - sysctl -a|grep randomize; Вы также можете попытаться setarch x86_64 -R ./program отключить его (askubuntu.com/questions/318315). - person osgx; 02.10.2016
comment
Что вы имеете в виду под переносом вопроса сюда, как это сделать? Я думаю, что вопрос актуален и по существу, мы могли бы ответить на него в этом месте. Действительно, у меня есть MMU и относительно свежее ядро ​​с смонтированным /proc. - person Bulat M.; 03.10.2016
comment
У вас есть каталог '/proc/sys/kernel/'? Можете ли вы опубликовать свою версию ядра и список /proc/sys/kernel/*rand* файлов? Есть ли рандомизация в выводе sysctl -a | grep randomize? - person osgx; 03.10.2016
comment
Перепроверил, файлы /proc/sys/*rand* на месте. Удаленный комментарий. - person Bulat M.; 03.10.2016