Почему смещение записи GOT отображается неправильно?

Я написал простую общую библиотеку:

extern void some_func(void);

void
function(void)
{
        some_func();
}

Скомпилировано/построено:

gcc -fPIC -mcmodel=large -c test.c -o test.o
gcc -fPIC -shared test.o -o libtest.so

Разобрано, чтобы посмотреть, как ссылаются на some_func:

$ objdump -d libtest.so
00000000000006a0 <function>:
 6a0:   55                      push   %rbp
 6a1:   48 89 e5                mov    %rsp,%rbp
 6a4:   41 57                   push   %r15
 6a6:   48 83 ec 08             sub    $0x8,%rsp
 6aa:   48 8d 05 f9 ff ff ff    lea    -0x7(%rip),%rax        # 6aa <function+0xa>
 6b1:   49 bb 56 09 20 00 00    movabs $0x200956,%r11
 6b8:   00 00 00 
 6bb:   4c 01 d8                add    %r11,%rax
 6be:   49 89 c7                mov    %rax,%r15
 6c1:   48 ba 80 f5 df ff ff    movabs $0xffffffffffdff580,%rdx
 6c8:   ff ff ff 
 6cb:   48 01 c2                add    %rax,%rdx
 6ce:   ff d2                   callq  *%rdx
 6d0:   90                      nop
 6d1:   48 83 c4 08             add    $0x8,%rsp
 6d5:   41 5f                   pop    %r15
 6d7:   5d                      pop    %rbp
 6d8:   c3                      retq

Посмотрел, где находится .got.plt:

$ readelf -S libtest.so
...
[21] .got.plt          PROGBITS         0000000000201000  00001000
       0000000000000020  0000000000000008  WA       0     0     8
...

Что такое переезд:

$ readelf -r libtest.so
Relocation section '.rela.plt' at offset 0x538 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000201018  000400000007 R_X86_64_JUMP_SLO 0000000000000000 some_func + 0


В 6aa-6bb мы получаем абсолютное местоположение GOT: 6aa + 0x200956 = 0x201000 Это согласуется с выводом readelf -S libtest.so.

Мы пропускаем 3 зарезервированных байта в GOT (связанные с функциями) и определяем, что абсолютный адрес some_func должен быть найден со смещением +0x18 (четвертый байт из GOT) во время выполнения.

Это согласуется с readelf -r libtest.so.

Но инструкция 6c1 по дизассемблированию objdump показывает:

  movabs $0xfff...dff580, %rdx  

Я ожидаю, что исходный операнд будет содержать +0x18 (смещение от GOT, его адрес находится в rax), но вместо этого он имеет какое-то большое отрицательное число.

Не могли бы вы объяснить, что он показывает это число, но не 0x18?


person Bulat M.    schedule 02.10.2016    source источник
comment
Попробуйте objdump -dR libtest.so проверить отсутствие перемещений в .text. Ваш случай не просто GOT, это GOT + PLT; поэтому должен быть какой-то код для доступа к таблице PLT. Вы также можете проверить значения времени выполнения и код после загрузки библиотеки с помощью rtld с помощью gdb.   -  person osgx    schedule 04.10.2016
comment
Ах, да, я проверил, и он обращается к таблице PLT, которая находится относительно далеко (отсюда большое отрицательное смещение в инструкции 6c1), затем переходит к 0x201018, где в нем после разрешения будет разрешена функция абсолютного адреса, переходит к заглушке PLT, разрешается.   -  person Bulat M.    schedule 04.10.2016
comment
@osgx, получилось как ты сказал, напиши как ответ.   -  person Bulat M.    schedule 04.10.2016


Ответы (1)


Существует два вида перемещений: статические и динамические ( 1); один для статического компоновщика ld и другой для загрузчика (динамический компоновщик, rtld) - ld-linux.so.2 для Linux glibc 2.* (отметьте Динамическое связывание и загрузка, 1999 или Статические компоновщики и загрузчики динамических ссылок).

Когда вы используете objdump для дампа перемещений, у него есть опция -r для статических перемещений и -R для динамических перемещений.

В вашем случае это не просто GOT, это GOT.PLT - GOT используется для привязки процедур. Этот вид доступа использует динамические перемещения. Итак, вы должны проверить вывод objdump -dR libtest.so, он покажет вам как дизассемблирование, так и динамические перемещения в нем.

Процитированная строка из readelf -r libtest.so предназначена только для таблицы PLT, а не для кода.

http://www.airs.com/blog/archives/41

или вызовы функций, компоновщик программ настроит запись PLT следующим образом:

jmp *offset(%ebx)
pushl #index
jmp first_plt_entry

Компоновщик программы выделяет запись в GOT для каждой записи в PLT. Это создаст динамическое перемещение для записи GOT типа JMP_SLOT. Он инициализирует запись GOT базовым адресом разделяемой библиотеки плюс адрес второй инструкции в кодовой последовательности выше. Когда динамический компоновщик выполняет начальную ленивую привязку к перемещению JMP_SLOT, он просто добавит разницу между адресом загрузки общей библиотеки и базовым адресом общей библиотеки в запись GOT. Эффект заключается в том, что первая инструкция jmp перейдет ко второй инструкции, которая поместит элемент индекса и перейдет к первому элементу PLT. Первая запись PLT особенная и выглядит так:

pushl 4(%ebx)
jmp *8(%ebx)

Это ссылается на вторую и третью записи в GOT. Динамический компоновщик инициализирует их, чтобы они имели соответствующие значения для обратного вызова в самом динамическом компоновщике. Динамический компоновщик будет использовать индекс, переданный первой последовательностью кода, чтобы найти перемещение JMP_SLOT. Когда динамический компоновщик определяет вызываемую функцию, он сохраняет адрес функции в ссылках на записи GOT по первой кодовой последовательности. Таким образом, при следующем вызове функции инструкция jmp перейдет прямо к нужному коду.

person osgx    schedule 05.10.2016
comment
может быть, немного не по теме, и все же, не могли бы вы порекомендовать какой-либо ресурс, где можно было бы изучить и понять глубокие ссылки, чтобы он мог ответить на свои собственные вопросы. - person Bulat M.; 08.10.2016
comment
@BulatM., блог airs.com/blog (линкеры, части 1-20 - airs.com/blog/index.php?s=linkers и некоторые другие посты), Джон Левин. Линкеры и загрузчики, Морган-Кауфман; рукопись с авторскими рисунками: iecc.com/linker. Также - провожу эксперименты с objdump/gdb. - person osgx; 10.10.2016