Символ @ GOTOFF обращается к самой переменной относительно базы GOT (в качестве удобного, но произвольного выбора привязки) . lea
из этого дает вам адрес символа, mov
даст вам данные в символе. (Первые несколько байтов строки в этом случае.)
символ @ GOT дает вам смещение (в пределах GOT) записи GOT для этого символа. mov
загрузка оттуда дает вам адрес символа. (Записи GOT заполняются динамическим компоновщиком).
Зачем использовать глобальную таблицу смещений для символов, определенных в самой общей библиотеке? * У 1011 * есть пример доступа к переменной extern
, котораядействительно приводит к получению своего адреса из GOT и затем разыменовывает его.
Кстати, это независимый от позиции код. Ваш GCC настроен таким образом по умолчанию. Если бы вы использовали -fno-pie -no-pie
, чтобы сделать традиционную позицию - зависимой исполняемой, вы бы просто получили нормальный эффективный pushl $.LC0
. (32-битная отсутствует RIP-относительная адресация, поэтому она довольно неэффективна.)
В не-PIE (или в 64-битном PIE) GOT практически не используется. Основной исполняемый файл определяет пространство для символов, поэтому он может получить к ним доступ, не проходя через GOT. В любом случае код libc использует GOT (в основном из-за взаимного расположения символов в 64-битном коде), поэтому предоставление основному исполняемому файлу символа обеспечивает ничего не требующее, и ускоряет выполнение не-PIE.
Мы можем получитьнеисполняемый PIE для непосредственного использования GOT для адресов функций совместно используемой библиотеки с -fno-plt
вместо вызова в PLT и использования GOT.
#include <stdio.h>
void foo() { putchar('\n'); }
gcc9.2 -O3 -m32 -fno-plt
на Godbolt (-fno-pie
по умолчанию в проводнике компилятора Godbolt, в отличие от вашей системы.)
foo():
sub esp, 20 # gcc loves to waste an extra 16 bytes of stack
push DWORD PTR stdout # [disp32] absolute address
push 10
call [DWORD PTR _IO_putc@GOT]
add esp, 28
ret
Оба push
и call
имеют операнд памяти, использующий 32-битный абсолютныйадрес. push
загружает значение FILE*
, равное stdout
, с известного адреса (константа-время-ссылка). (Нет перемещения текста для него.)
call
загружает указатель функции, сохраненный динамическим компоновщиком из GOT. (И загружает его прямо в EIP.)