Опубликовано: Sept. 8, 2020, 9:32 p.m.
Hello world под Linux без libc (на x86-64)
Disclaimer: код на GitHub.
В этой заметке хочу зафиксировать свои знания, полученные в процессе сборки простейшей программы без libc.
Сборка пустой программы без libc хорошо описана в этом посте на хабре. Будем считать, что у нас уже есть два файла со следующим содержанием:
start.S main.cint main() { return 0; }
Таблицу с номерами системных вызовов под x86-64 можно посмотреть на этой странице; благо, ChromiumOS основана на Linux. На той же странице есть таблицы для других архитектур, в том числе x86. Стоит отметить, что номера одних и тех же системных вызовов в них отличаются (как и регистры, через которые происходит передача параметров).
Создадим функцию hello, не принимающую и не возвращающую ничего и выводящую константную строчку. Ассемблерный файл с ней будет выглядеть так:
hello.SВ main.c добавим описание данной функции и ее вызов:
main.cextern void hello(); int main() { hello(); return 0; }
Собрать код можно командой:
gcc -nostdlib -no-pie start.S -o wolibc hello.S main.c
Вывод программы:
Assembly
Отлично, мы научились выводить константную строчку. Тем не менее, это не очень интересно, и хочется иметь что-то похожее на обычный системный вызов write, которому можно передать параметры. Оказывается, что код получится даже проще из-за того, что нужные значения автоматически оказываются в регистрах rdi, rsi и rdx:
mywrite.S main.c#include "types.h" #define STDOUT 1 extern ssize_t mywrite(int fd, const void* buf, size_t count); int main() { mywrite(STDOUT, "Hello ", 6); mywrite(STDOUT, "without libc!\n", 14); return 0; }
Кроме того, добавился один новый заголовок, в котором определяются size_t и ssize_t:
types.h#ifndef TYPES_H #define TYPES_H #if SIZE_MAX == UINT_MAX // common 32 bit case typedef int ssize_t; typedef unsigned int size_t; #elif SIZE_MAX == ULONG_MAX // linux 64 bits typedef long ssize_t; typedef unsigned long size_t; #elif SIZE_MAX == ULLONG_MAX // windows 64 bits typedef long long ssize_t; typedef unsigned long long size_t; #elif SIZE_MAX == USHRT_MAX // is this even possible? typedef short ssize_t; typedef unsigned short size_t; #else #error Platform has exotic SIZE_MAX #endif #endif // TYPES_H
В качестве бонуса я написал функцию write_num для вывода целочисленных типов в десятичном формате и при помощи нее проверил, что mywrite возвращает корректные значения. Для корректной работы write_num с разными целочисленными типами удобно воспользоваться шаблонами из C++, поэтому код был переписан на этот язык.
main.cppВывод программы:
Hello without libc! Results: Got 6, expected 6 Got 14, expected 14 Got -9, expected -9