и я понимаю, что фактический сопоставленный адрес двоичного файла, когда он загружен в память и работает, не обязательно будет таким же.
Нет, по этим адресам мы можем видеть, что это исполняемый файл не PIE ELF, связанный по умолчанию с базовым адресом ld
. Это зависимый от позиции исполняемый файл.
Сам исполняемый файл всегда будет загружаться по фиксированному виртуальному адресу, поэтому статические адреса могут быть помещены в регистры с использованием 32-разрядных модулей немедленного действия вместо REA-относительного LEA. ASLR для самого исполняемого файла не разрешен / невозможен.
libc - это «общий объект» ELF, который может быть ALSRed, следовательно, вызов __libc_start_main
через указатель в GOT. В исходном коде gcc для этого стартового кода CRT это, вероятно, выглядит как call *__libc_start_main@GOTPCREL(%rip)
(синтаксис AT & T).
И, кстати, мы можем сказать, что это был рукописный asm, из-за пропущенной оптимизации использования 7-байтового mov rdi, sign_extended_imm32
(такого же размера, как у RIP-относительного LEA) вместо 5-байтового mov edi, imm32
. Модель кода по умолчанию, отличная от PIE, в x86-64 System V ABI помещает весь статический код / данные в низкое 2 ГБ виртуального адресного пространства, поэтому статические адреса могут использоваться с нулевым или знаковым расширением до 64-разрядного.
"Исполняемые файлы ELF", которые могут быть загружены по случайному базовому адресу, называются PIE (Position Independent Executable) . С точки зрения деталей ELF, они используют тот же «тип» ELF, что и разделяемые библиотеки, поэтому они на самом деле являются общими объектами ELF, которые имеют «точку входа» и помечены как исполняемые.
В современных дистрибутивах Linux gcc по умолчанию использует PIE. См. 32-разрядные абсолютные адреса, более не разрешенные в Linux x86-64? (перемещаемые совместно используемые объекты ELF можно перемещать в любое место адресного пространства, не ограничиваясь низким 2 ГБ, поэтому для среды выполнения нет типа перемещения исправления 32-битных абсолютных адресов.)
Существует тип перемещения для 64-битных абсолютных адресов, поэтому таблицы переходов (указателей функций / кодов) все еще возможны, как и 10-байтовые mov rdi, imm64
, но это менее эффективно, чем LEA-относительный LEA, даже если бы не загрузчик программ ELF или динамический компоновщик не должны были изменять текст программы для этих перемещений.
например. readelf -a /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x5ae0
...
Обратите внимание на поле Тип: DYN, такое же, как в реальной библиотеке, такой как readelf -a /lib/libc.so.6
. А точка входа - это относительный адрес относительно базового адреса, в котором она отображается.
Не-PIE исполняемый файл (например, со статической связью или сборка с -fno-pie -no-pie
) выглядит следующим образом:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401000
Обратите внимание на Type: EXEC
и абсолютную точку входа (выбирается во время соединения с помощью ld
).