Динамический "Hello, world!" на GNU ассемблере x86_64 (amd64)

Понадобилась мне программа-заглушка, выводящая "Hello, world!" в стандартный поток вывода. На GNU ассемблере для платформы x86_64 (amd64). И динамически скомпонованная с libc (тут я передаю пламенный привет OpenBSD, в которой теперь нельзя просто взять и вызвать системный вызов).

С одной стороны GNU'тый диалект ассемблера мне знаком плохо. Практика полезна:

    .global main
    .extern write, exit

    .section .rodata
message:
    .ascii  "Hello, world!\n"

    .section .text
main:
    mov     $1, %rdi            /* STDOUT */
    lea     message(%rip), %rsi
    mov     $14, %rdx
    call    write

    xor     %rdi, %rdi
    call    exit

А с другой я никогда "руками" (ну точнее с помощью ld) не собирал работоспособные динамически скомпонованные ELF'ы. Оказалось это тот ещё геморрой с указанием crt и интерпретатора-компоновщика на разных платформах: Debian, Alpine и OpenBSD. Плюнул и для кросс-платформенной сборки использую cc (содержимое hello.s приведено в ассемблерном листинге выше):

$ as -o hello.o hello.s
$ cc -l c -o hello hello.o

Платформозависимый путь (добавлено 2024-11-09)

Для начала при вызове cc указываем опцию детальной печати (-v). Таким образом получаем все опции вызова компоновщика ld.

Затем в ассемблерном файле переименовываем символ main в _my_start:

$ cat hello.s                             
    .global _my_start
    .extern write, exit

    .section .rodata
message:
    .ascii  "Hello, world!\n"

    .section .text
_my_start:
    mov     $1, %rdi            /* STDOUT */
    lea     message(%rip), %rsi
    mov     $14, %rdx
    call    write

    xor     %rdi, %rdi
    call    exit

И далее итеративно выбрасываем лишние опции компоновщика ld. Важно не забыть сменить точку входа (опция -e) на _my_start.

$ as -o hello.o hello.s
$ ld -e _my_start -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o hello -lc hello.o
$ as -o hello.o hello.s
$ ld -e _my_start -dynamic-linker /lib/ld-musl-x86_64.so.1 -o hello -lc hello.o

OpenBSD

Сходу не смог полностью избавиться от crt:

$ as -o hello.o hello.s
$ ld -e _my_start -dynamic-linker /usr/libexec/ld.so -o hello /usr/lib/crtbegin.o -L/usr/lib -lc hello.o

Без /usr/lib/crtbegin.o результирующий ELF файл hello собирается, но не запускается.

Правильный вариант для OpenBSD (добавлено 2024-11-11)

После публикации этой заметки со мной связался обитатель домика на дереве:

Домик на дереве

Искренне спасибо тебе, неравнодушный человек! Оказалось, что на OpenBSD существует обязательная секция `.note.openbsd.ident`. А её отсутствие и приводит к тому, что собранный ELF файл не запускается.

Поэтому правильный листинг на GNU'том ассемблере для OpenBSD выглядит так:

    .global _my_start
    .extern write, exit

    .section ".note.openbsd.ident", "a"
    .p2align 2
    .long 8,4,1
    .ascii "OpenBSD\0"
    .long 0

    .section .rodata
message:
    .ascii  "Hello, world!\n"

    .section .text
_my_start:
    mov     $1, %rdi            /* STDOUT */
    lea     message(%rip), %rsi
    mov     $14, %rdx
    call    write

    xor     %rdi, %rdi
    call    exit

А собирается такой листинг (hello.s) в исполняемый ELF hello на OpenBSD следующей командой:

$ as -o hello.o hello.s
$ ld -e _my_start -dynamic-linker /usr/libexec/ld.so -o hello -L/usr/lib -lc hello.o

По сравнению со сборкой с crtbegin.o ELF "похудел" более чем на два килобайта.

Ссылки по теме:

Hello assembler!

Assembly language on OpenBSD (amd64 && arm64)

C исходном коде OpenBSD:

os-note-elf.h

crtbegin.c (включает os-note-elf.h)