Процесс линковки

Разрабатывая стандартную библиотеку для своего языка, столкнулся с неочевидной задачей: как связывать код написанный на C с ассембли. Первый подход – компиляция C в ассембли и ручное копирование кода – оказался не самым удобным. Две проблемы этого способа это несовместимость синтаксиса GCC и Nasm и постоянное дублирование кода при малейших изменениях.

Решение

Теперь расскажу о способе, который является оптимальным – линковке объектных файлов.

Пример

Приведу пример из моего языка – функция для печати целых чисел.

debug.c

extern void write_i64(long long value) {
    // тело функции в конце статьи
}

Важно, что функция объявлена с модификатором extern, то есть доступна глобально.


Также, в него нужно включить заголовочный файл, в котором будут объявлены все сигнатуры функций.

debug.c

#include "debug.h"

// ...

debug.h

#ifndef DEBUG_H
#define DEBUG_H

void write_i64(long long value);

#endif

Теперь, создаём объектный файл.

gcc -nostdlib -no-pie -fno-stack-protector -c debug.c -o debug.o

Флаги -no-pie и -fno-stack-protector нужны для совместимости с ассембли.

main.asm

section .text
global _start

_start:
    extern write_i64
    mov rdi, 999
    call write_i64 ; печатает: 999
    mov rax, 60
    mov rdi, 0
    syscall

Компилируем и компонуем с объектным файлом стандартной библиотеки

nasm -f elf64 main.asm -o main.o
gcc -nostdlib -no-pie main.o debug.o -o main

Получаем одиночный бинарный файл, в котором включены и стандартная библиотека и главный файл.


P.S: Тело функции

extern void write_i64(long long value) {
      if (value == 0) {
            write_c('0');
            return;
      }

      char buffer[20];
      int size = 0;
      int negative = value < 0;

      if (negative)
            value = -value;

      while (value > 0) {
            int digit = value % 10;
            buffer[size++] = digit + '0';
            value = (value - digit) / 10;
      }

      if (negative)
            buffer[size++] = '-';

      for (int index = size - 1; index >= 0; index--)
            write_c(buffer[index]);
}