Однако, если я перенесу функцию aux () в другой модуль компиляции, функция get_pc_thunk останется вызываемой даже с -O2, и, опять же, ее возвращаемое значение игнорируется.
IIR C, точка EBX = GOT предполагается / требуется самим PLT, и вызов должен быть go через PLT, потому что известно при компиляции этого модуля компиляции, что aux
определение будет статически связано с ним. (https://godbolt.org/z/Yere9o показывает этот эффект для main только с прототипом для aux()
, а не с определением, которое он может встроить.)
С атрибутом видимости "hidden"
ELF мы можем получить это на go, потому что компилятор знает, что ему не нужно косвенно через PLT, потому что call rel32
будет известен во время связи c без необходимости перемещения времени выполнения: https://godbolt.org/z/73dGKq
__attribute__((visibility("hidden"))) int aux(void);
int _start(){
int a = aux();
return a;
}
gcc10.1 -O2 -m32 -fpie
_start:
jmp aux
IMO имеет смысл иметь вызов в объектных файлах, сгенерированных для модулей компиляции, которые вызывают внешние функции, но я не понимаю, почему компоновщик (или «поток») не удаляет их в финальном двоичный.
@ felipeek: Хороший вопрос. Компоновщик не знает, когда он может ослабить вызов foo@plt для вызова foo, потому что это также отключает взаимное расположение символов. Даже если в этом разделяемом объекте ELF есть определение foo, определение в одном из загруженных ранее может переопределить его / иметь приоритет. Я думаю, что эта «проблема» связана с тем, что исполняемые файлы P IE возникли в результате своего рода взлома: поместите точку входа в общий объект, и компоновщик Dynami c захочет запустить его. т.е. на уровне ELF исполняемые файлы P IE такие же, как .so, а -fpie
и -fPIC
выглядят одинаково для компоновщика.
Компоновщик может go наоборот, хотя : при создании обычного исполняемого файла, отличного от P IE (тип ELF = EXE C), он может превратить вызов foo в вызов foo@plt, но сам PLT не обязательно должен быть PIE / PI C, поэтому ему не требуется EBX = GOT.
Мы говорим, что все вызовы других модулей компиляции будут вызывать совершенно ненужный вызов в конечном двоичном файле, когда требуется P IE?
Нет, только в 32-битном коде P IE, где вы не можете сообщить компилятору, что это «внутренний» символ, используя «скрытую» видимость ELF. У вас даже может быть два имени для одного и того же символа, одно со скрытой видимостью, поэтому вы можете создать функцию, которую библиотеки могут разрешать по имени, но которую вы все равно можете вызывать из исполняемого файла, используя простой call rel32
вместо неуклюжих непрямых вызовов через PLT.
Это один из недостатков P IE. Даже в 64-битном коде без атрибута вы получите jmp aux@PLT
. (Или с -fno-plt
, непрямым вызовом с использованием относительной адресации RIP для записи GOT.)
32-битный P IE действительно отстой для производительности, например, в среднем 15% (измерено некоторое время a go на процессорах в то время, возможно, несколько отличался.) Намного меньший эффект на x86-64, где доступна относительная адресация RIP, например, пара%. 32-битные абсолютные адреса больше не разрешены в x86-64 Linux? содержит ссылки на более подробную информацию.