Несколько модулей и линковка
Контекст: NASM → объектный файл ELF →
ldилиgcc. Linux x86-64. См. Первая программа про цепочку сборки.
Несколько модулей - линковка объектников
Один огромный .asm трудно сопровождать. Разделение по смыслу:
main.asm— точка входа, системные вызовы;math.asm— вычисления;io.asm— вывод/ввод;macros.inc— только макросы (%include), без объектного кода.
Каждый .asm с кодом собирается в отдельный .o. Линкер склеивает объектники и разрешает ссылки "вызвать символ из другого файла".
Экспорт и импорт символов
Экспорт — метка видна снаружи модуля:
; math.asm
section .text
global add_u64
add_u64:
mov rax, rdi
add rax, rsi
ret
Разбор:
section .textпереключает вывод в сегмент исполняемого кода.global add_u64экспортирует символ для других объектных файлов.- Метка
add_u64:задаёт точку входа функции. - По ABI аргументы приходят в
RDIиRSI. mov rax, rdiкладёт первый аргумент в регистр возврата.add rax, rsiдобавляет второй аргумент и формирует итог.retвозвращает управление вызывающему коду.
Импорт — используем чужую метку:
; main.asm
section .text
global _start
extern add_u64
_start:
mov rdi, 100
mov rsi, 23
call add_u64 ; rax = 123
; ...
Разбор:
global _startобъявляет точку входа программы без libc.extern add_u64сообщает линкеру, что реализация функции находится в другом модуле.mov rdi, 100иmov rsi, 23подготавливают аргументы функции по ABI.call add_u64делает переход и сохраняет адрес возврата на стек.- После возврата функция оставляет результат в
RAX(123в примере). - Дальше код может использовать
RAXили передать его в syscall/другую функцию.
Правила имен:
global имя— в одном модуле определение (реализация).extern имя— объявление "определено в другом.o".- Точка входа "голой" программы без libc:
global _startи линковка с_startкак entry (см. ниже).
Сборка и линковка
nasm -f elf64 math.asm -o math.o
nasm -f elf64 main.asm -o main.o
ld math.o main.o -o app
Разбор:
- Первая команда ассемблирует
math.asmв объектный файлmath.o. - Вторая команда делает то же для
main.asm. - Ключ
-f elf64задаёт формат объектников под 64-битный ELF. - Команда
ldсвязывает объектники, разрешаетextern/globalи создаёт исполняемый файлapp. - На этапе линковки проверяется наличие всех используемых символов.
Или через GCC как драйвер линкера (удобно, если позже подключите C):
gcc -nostdlib math.o main.o -o app
Разбор:
gccздесь выступает как удобная обёртка над линкером.-nostdlibотключает стандартные runtime-компоненты libc (crt, стандартные библиотеки).- В итоговый бинарник попадут только переданные объектные файлы и необходимые системные зависимости ядра.
- Такой вариант подходит для программ с
_startи прямымиsyscall.
-nostdlib — без crt0 и libc; для чистого _start + syscall.
Порядок .o для ld обычно не критичен, если все символы разрешены. Неопределённый символ — ошибка линкера "undefined reference".
Данные в разных модулях
Строку можно держать в io.asm:
; io.asm
section .data
global msg
msg db 'OK', 10
msg_len equ $ - msg
Разбор:
section .dataразмещает изменяемые/инициализированные данные.global msgэкспортирует адрес строки для других модулей.msg db 'OK', 10создаёт байты —O,K, перевод строки.msg_len equ $ - msgвычисляет длину как разницу текущей позиции и адресаmsg.equсоздаёт константу времени сборки, а не переменную в памяти.- Поэтому
msg_lenв таком виде обычно недоступен как внешний символ для линкера.
В main.asm:
extern msg
extern msg_len ; константа equ не экспортируется как символ — msg_len лучше
; передавать через отдельный global или вычислять в main
Разбор:
extern msgкорректно импортирует символ, который реально существует в таблице символов объектника.extern msg_lenпоказывает типичную ошибку: значение изequобычно не становится линкерным символом.- Линковка может завершиться ошибкой
undefined referenceдляmsg_len. - Рабочий вариант: хранить длину как отдельные данные (
dq) сglobal, либо вычислять длину в модуле-потребителе.
equ не создаёт символ в объектнике. Для экспорта длины используют global msg_len и msg_len: dq $ - msg или функцию get_msg_len.
Корректный экспорт длины из io.asm:
; io.asm
section .data
global msg, msg_len
msg db 'OK', 10
msg_len dq $ - msg
; main.asm
extern msg, msg_len
section .text
global _start
_start:
mov rdx, [rel msg_len] ; длина для syscall write
lea rsi, [rel msg] ; адрес буфера
; далее: mov rax,1; mov rdi,1; syscall
Разбор:
msg_len dq $ - msgсоздаёт реальный символ данных в объектнике, а не константуequ.global msg_lenделает длину видимой для линкера так же, как иmsg.extern msg, msg_lenвmain.asmкорректно импортирует оба символа.mov rdx, [rel msg_len]читает длину из памяти в регистр третьего аргументаwrite.lea rsi, [rel msg]загружает адрес строки, а не первый байт черезmov.- Такой шаблон безопасен при линковке нескольких модулей.
Минимальный main.asm, связывающий модули и завершающий программу:
Код ITЗагрузка примера кода…
Разбор:
- Сначала вызывается
add_u64с аргументами40и2, результат остаётся вRAX(здесь не используется дальше). - Затем готовятся регистры для syscall
write— номер1, дескриптор1, буфер и длина из модуляio. mov rdx, [rel msg_len]берёт длину из экспортированногоdq, а не изequ.- Завершающий
syscallсrax=60иrdi=0завершает процесс. - Файл показывает полный цикл: вызов функции из другого
.o→ вывод строки → exit.
%include и отдельный .o
| Механизм | Результат |
|---|---|
%include "file.asm" | один объектный файл, общая область меток |
отдельный .asm + global/extern | несколько .o, инкапсуляция, переиспользование |
Макросы и константы — в .inc с %include. Подпрограммы с фиксированным ABI — в отдельных .asm.
Статические библиотеки
ar rcs libmath.a math.o
gcc -nostdlib main.o -L. -lmath -o app
Разбор:
ar rcs libmath.a math.oсоздаёт статическую библиотеку-архив из объектного файла.- Флаг
rдобавляет/обновляет объекты,cсоздаёт архив при отсутствии,sстроит индекс символов. gcc ... -L. -lmathдобавляет текущую папку в путь поиска библиотек и подключаетlibmath.a.- Линкер подтягивает из архива только те объектные модули, чьи символы реально нужны.
ar упаковывает .o в архив; линкер вытаскивает нужные объектники по неразрешённым символам.
Типичные ошибки линковки
| Ошибка | Причина |
|---|---|
duplicate symbol | global на одно имя в двух .asm |
undefined reference to _start | нет global _start или не тот файл первым |
Разные -f elf32 / elf64 | смешение 32- и 64-битных объектников |
| Вызов без совпадения ABI | аргументы не в rdi/rsi/... — см. подпрограммы |
Дальше
Стык с кодом на C — взаимодействие с C. Разбор готового бинарника — чтение листинга.