Почему сборка nasm с неквалифицированными ссылками на _GLOBAL_OFFSET_TABLE_, очевидно, может быть собрана и связана как PIC?

Почему я могу собрать и связать get_got.asm как независимый от позиции код, если он содержит ссылку на абсолютный адрес своего GOT?

get_got.asm

extern _GLOBAL_OFFSET_TABLE_

section .text
global get_got
get_got:
        mov     rax, _GLOBAL_OFFSET_TABLE_
        ret

main.c

#include <stdio.h>

void* get_got(void);

int main(int argc, char* argv[]) {
    printf("%p\n", get_got());
}

сборка, компиляция, компоновка, запуск:

nasm -felf64 -o get_got.o get_got.asm
gcc -fPIC -shared -o get_got.so get_got.o
gcc -Wl,-rpath=\$ORIGIN -o main main.c get_got.so
./main
0x148ba1cba000

Что тут происходит? Мне кажется, что у get_got.so каким-то образом есть запеченный абсолютный адрес для GOT, у которого не будет известного адреса до времени выполнения. Дизассемблирование get_got.so показывает, что mov действительно содержит немедленное (0x201000). Очевидно, у меня есть серьезное недопонимание чего-то. Я ожидал, что это заставит nasm сгенерировать перемещение, которым компоновщик захлебнется.


person Praxeolitic    schedule 18.12.2017    source источник
comment
Обратите внимание, что для PIC вам разрешено иметь абсолютные ссылки на данные. Теперь попробуйте вместо этого -pie. Затем вы должны увидеть запись о динамическом перемещении в окончательном двоичном выводе. РЕДАКТИРОВАТЬ: о, вы сделали .so ... ну, тогда вы должны увидеть перемещение там.   -  person Jester    schedule 18.12.2017
comment
Конечно, вам обычно не требуется _GLOBAL_OFFSET_TABLE_ в 64-битном коде, вы просто используете относительную адресацию с GOTPCREL.   -  person Jester    schedule 18.12.2017
comment
Вам все еще нужен GOT для глобальных переменных, импортированных из разделяемых библиотек, не так ли?   -  person zneak    schedule 18.12.2017
comment
У вас будет GOT, но вы обращаетесь к нему по-разному, например можно написать mov eax, [rel foo wrt ..got].   -  person Jester    schedule 18.12.2017


Ответы (1)


Я создал ваш код и модифицированную версию, используя lea rax, [rel _GLOBAL_OFFSET_TABLE_].

Я сравнил результат readelf -a. А вот с разных адресов много шума.
readelf -a get_got.so | diff -u - <(readelf -a get_got_rel.so) | less

Самое интересное отличие:

--- readelf -a get_got.so
+++ readelf -a get_got_rel.so
....

-Dynamic section at offset 0xe40 contains 22 entries:
+Dynamic section at offset 0xe50 contains 21 entries:
...

- 0x0000000000000016 (TEXTREL)            0x0
  0x000000006ffffffe (VERNEED)            0x3b0
  0x000000006fffffff (VERNEEDNUM)         1
  0x000000006ffffff0 (VERSYM)             0x398
- 0x000000006ffffff9 (RELACOUNT)          4
+ 0x000000006ffffff9 (RELACOUNT)          3

Итак, в абсолютной версии есть перемещение текста. Я не знал, что при динамической компоновке Linux / ELF могут применяться исправления после сопоставления разделяемых библиотек. Но, видимо, может. (Лучше не делать этого, потому что это загрязняет страницу памяти, поэтому она больше не поддерживается файлом на диске.)

Но я проверил с GDB, и вот что происходит: установите точку останова в get_got и запустите ее:

(gdb) disas
Dump of assembler code for function get_got:
=> 0x00007f9e77b235b0 <+0>:     movabs rax,0x7f9e77d24000
   0x00007f9e77b235ba <+10>:    ret    

objdump -dRC -Mintel get_got.so: (обратите внимание на перенос строки без -w):

00000000000005b0 <get_got>:
 5b0:   48 b8 00 10 20 00 00    movabs rax,0x201000
 5b7:   00 00 00 
                        5b2: R_X86_64_RELATIVE  *ABS*+0x201000
 5ba:   c3                      ret    

Спасибо @Jester за -R совет; Обычно я использую objdump -dr ..., а не -R, и строчная буква r не выводит никаких перемещений для .so.
На get_got.o, -r показывает movabs rax,0x0 2: R_X86_64_64 _GLOBAL_OFFSET_TABLE_.


gcc -nostdlib -pie также свяжет 64-битные абсолютные перемещения с исполняемыми файлами PIE. (Исполняемый файл PIE является общим объектом ELF).

В PIC / PIE запрещено 32-битное абсолютное перемещение: 32-битные абсолютные адреса больше не разрешены в x86-64 Linux?. Вы получаете ошибку компоновщика. Режимы адресации, такие как array[rcx*4], не могут использоваться в коде PIC / PIE, вам нужна отдельная инструкция для ввода адреса в регистр.

lea rdi, [rel array] - намного лучший выбор, чем 64-битный немедленный абсолют, потому что он меньше по размеру и более удобен для кэша uop и не требует исправления при загрузке.

person Peter Cordes    schedule 18.12.2017
comment
objdump -dR делает это очевидным. - person Jester; 18.12.2017
comment
Это отвечает на вопрос, но, безусловно, вызывает у меня вопросы о последствиях подобного исправления. Спасибо. - person Praxeolitic; 18.12.2017
comment
Вот почему вы избегаете этого, как я уже сказал. Я считаю, что macos этого не позволяет. - person Jester; 18.12.2017
comment
@Jester: спасибо, я забыл, что есть -R отдельно от -r, которые я обычно использую в своих alias disas='objdump -drwC -Mintel' - person Peter Cordes; 18.12.2017