Организация программы

Последнее обновление: 15.10.2023

Обычно код программы состоит не из одного файла, а из нескольких файлов. Разбиение программы на отдельные файлы позволяет упростить работу над логикой приложения. Функционал из одной файла, например, какие-то отдельные функции, можно легко переностить и повторно использовать в других программах. В связи с этим возникает вопрос организации программы. И тут у нас есть различные варианты.

Включение одних файлов в другие

Пусть у нас есть следующий файл sum.asm с кодом функции, которая вычисляет сумму чисел:

section .text
; Функция возвращает сумму чисел
; Принимает два параметра:
; rdi - первое число 
; rsi - второе число
; Результат функции возвращается через регистр rax
sum:
    mov rax, rdi      ; результат в rax
    add rax, rsi
    ret

Через регистры rdi и rsi функция принимает два числа и через регистр rax возвращает их сумму.

За один раз NASM (по крайней мере на момент написания данной статьи) может компилировать только один файл с исходным кодом. Как быть, если у нас несколько файлов? Первый вариант - использовать директиву %INCLUDE, которая вставляет код одной файла в определенное место другого файла.

Так, пусть главный файл программы (пример на Linux) называется hello.asm, и он использует выше определенную функцию:

global _start

section .text
%INCLUDE "sum.asm"
_start:
    mov rdi, 33
    mov rsi, 44
    
    call sum
    mov rdi, rax         ; для проверки результата помещаем сумму из RAX в RDI
    mov rax, 60
    syscall 

Здесь выражение %INCLUDE "sum.asm" будет вставлять на место данной директивы код файла "sum.asm", который как предполагается, располагается в той же папке.

Компиляция в этом случае происходит как и в общем случае (команда компиляции для Linux):

nasm -f elf64 hello.asm -o hello.o

Аналогичный пример для Windows:

global _start

section .text
%INCLUDE "sum.asm"
_start:
    mov rdi, 33
    mov rsi, 44
    
    call sum
    ret

Раздельная компиляция

Второй подход представляет раздельную компиляцию файлов в разные объектные файлы, а при компоновке они объединяются в один исполняемый файл. Но в этом случае те метки (функции, данные), которые мы хотим сделать видимыми извне и которые должны использоваться во внешнем коде, объявляются с помощью директивы global. Так, определим следующий файл sum.asm:

global sum  ; делаем функцию sum доступной извне

section .text
; Функция возвращает сумму чисел
; Принимает два параметра:
; rdi - первое число 
; rsi - второе число
; Результат функции возвращается через регистр rax
sum:
    mov rax, rdi      ; результат в rax
    add rax, rsi
    ret

Вначале идет директива global, которая делает видимой извне или иначе говоря глобальной функцию sum:

global sum

В файле кода может быть определено множество функций, переменных, констант, но вне файла будут доступны только те из них, которые объявлены с помощью директивы global. Причем подобное объявление с global должно идти до определения функций/переменных/констант.

Чтобы использовать подобные глобальные функции/переменные/константы в другом файлы они должны быть объявлены с помощью директивы extern. Например, используем функцию sum в файле hello.asm:

global _start

extern sum      ; функция sum расположена где-то во вне

section .text
_start:
    mov rdi, 33
    mov rsi, 44
    
    call sum
    mov rdi, rax         ; для проверки результата помещаем сумму из RAX в RDI
    mov rax, 60
    syscall 

Выражение extern sum указывает, что функция sum расположена где-то во вне, а где именно, станет известным на стадии компоновки. Ассемблер NASM увидит это, и не будет выдывать никаких ошибок на стадии компиляции.

Теперь соберем программу на примере Linux. Сначала скомпилируем файл sum.asm:

nasm -f elf64 sum.asm -o sum.o

Затем скомпилируем файл hello.asm:

nasm -f elf64 hello.asm -o hello.o

В итоге у нас получится два разных объектных файла - sum.o и hello.o. Скомпонуем их в один исполняемый файл:

ld hello.o sum.o -o hello

Здесь стандартному компоновщику в Linux - программе ld передаются оба объектных файла, и на выходе мы получаем исполняемый файл "hello", который мы можем запускать. Полный пример с компиляцией/компоновкой и выполнением программы:

root@Eugene:~/asm# nasm -f elf64 sum.asm -o sum.o
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
root@Eugene:~/asm# ld hello.o sum.o -o hello
root@Eugene:~/asm# ./hello
root@Eugene:~/asm# echo $?
77
root@Eugene:~/asm#

На Windows все делается аналогично - сначала создаются два объектных файла, которые затем передаются компоновщику для компоновки в исполняемый файл.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850