Перейти к основному содержимому

Несколько модулей и линковка

Разработчику

Контекст: 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 symbolglobal на одно имя в двух .asm
undefined reference to _startнет global _start или не тот файл первым
Разные -f elf32 / elf64смешение 32- и 64-битных объектников
Вызов без совпадения ABIаргументы не в rdi/rsi/... — см. подпрограммы

Дальше

Стык с кодом на C — взаимодействие с C. Разбор готового бинарника — чтение листинга.