Опубликовано: Sept. 8, 2020, 9:32 p.m.
Hello world под Linux без libc (на x86-64)
Disclaimer: код на GitHub.
В этой заметке хочу зафиксировать свои знания, полученные в процессе сборки простейшей программы без libc.
Сборка пустой программы без libc хорошо описана в этом посте на хабре. Будем считать, что у нас уже есть два файла со следующим содержанием:
start.S main.c
int main()
{
return 0;
}
Таблицу с номерами системных вызовов под x86-64 можно посмотреть на этой странице; благо, ChromiumOS основана на Linux. На той же странице есть таблицы для других архитектур, в том числе x86. Стоит отметить, что номера одних и тех же системных вызовов в них отличаются (как и регистры, через которые происходит передача параметров).
Создадим функцию hello, не принимающую и не возвращающую ничего и выводящую константную строчку. Ассемблерный файл с ней будет выглядеть так:
hello.SВ main.c добавим описание данной функции и ее вызов:
main.c
extern 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