И ассемблер, и компилятор переводят исходные файлы в объектные файлы.
Объектные файлы фактически являются промежуточным этапом перед окончательным выводом исполняемого файла (сгенерированным компоновщиком).
Компоновщик принимает указанные объектные файлы и библиотеки (которые являются пакетами объектных файлов) и разрешает записи перемещения (или исправления).
Эти записи о перемещении создаются, когда компилятор / ассемблер не знает адрес функции или переменной, используемой в исходном коде, и генерирует для него ссылку по имени, которая может быть разрешена компоновщиком.
Например, скажем, вы хотите, чтобы программа выводила сообщение на экран, разделив его на два исходных файла, и вы хотите собрать их отдельно и связать их (например, с помощью системных вызовов Linux x86-64) -
main.asm:
bits 64
section .text
extern do_message
global _start
_start:
call do_message
mov rax, 1
int 0x80
message.asm:
bits 64
section .text
global do_message
do_message:
mov rdi, message
mov rcx, dword -1
xor rax, rax
repnz scasb
sub rdi, message
mov rax, 4
mov rbx, 1
mov rcx, message
mov rdx, rdi
int 0x80
ret
section .data
message: db "hello world",10,0
Если вы соберете их и посмотрите на выходной файл объекта main.asm (например, objdump -d main.o), вы заметите, что у 'call do_message' есть адрес 00 00 00 00 - который недопустим.
0000000000000000 <_start>:
0: e8 00 00 00 00 callq 5 <_start+0x5>
5: 48 c7 c0 01 00 00 00 mov $0x1,%rax
c: cd 80 int $0x80
Но для 4 байтов адреса делается запись о перемещении:
$ objdump -r main.o
main.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000001 R_X86_64_PC32 do_message+0xfffffffffffffffc
000000000000000d R_X86_64_32 .data
Смещение равно «1», а тип - «R_X86_64_PC32», который указывает компоновщику разрешить эту ссылку и поместить разрешенный адрес в указанное смещение.
Когда вы связываете окончательную программу с 'ld -o program main.o message.o', все перемещения разрешаются, и если ничего не разрешено, у вас остается исполняемый файл.
Когда мы 'objdump -d' исполняем, мы видим разрешенный адрес:
00000000004000f0 <_start>:
4000f0: e8 0b 00 00 00 callq 400100 <do_message>
4000f5: 48 c7 c0 01 00 00 00 mov $0x1,%rax
4000fc: cd 80 int $0x80
Перемещения такого же типа используются как для переменных, так и для функций.
Тот же процесс происходит, когда вы связываете свою программу с несколькими большими библиотеками, такими как libc - вы определяете функцию с именем 'main', к которой у libc есть внешняя ссылка - тогда libc запускается перед вашей программой и вызывает вашу функцию 'main', когда вы запускаете исполняемый файл.