🧠 Что такое libc и зачем от неё отказываться
libc — это стандартная библиотека языка C, реализующая функции вроде printf, malloc, memcpy, exit. Почти все программы на C её используют, независимо от платформы: glibc, musl, uClibc, newlib.
Но libc — это не язык. Это интерфейс между программой и OS, часто скрывающий детали вызовов ядра, аллокации и выхода. В системном программировании, bootloader, kernel, initrd, freestanding‑окружениях и embedded‑средах часто требуется отказаться от libc.
⚙️ Что нужно реализовать вручную
Точка входа
Libc определяет main(), но реально выполнение начинается с _start. Без libc программист должен определить _start и завершать программу напрямую через exit syscall или вызов hlt.
__attribute__((naked, section(".text")))
void _start() {
asm volatile (
"mov $60, %rax\n" // syscall: exit
"xor %rdi, %rdi\n" // status: 0
"syscall\n"
);
}Ввод/вывод
Функции printf, puts, read, write зависят от syscall и libc. Без неё — только системные вызовы напрямую:
long write(int fd, const void* buf, unsigned long len) {
long ret;
asm volatile (
"mov $1, %%rax\n" // syscall: write
"syscall"
: "=a"(ret)
: "D"(fd), "S"(buf), "d"(len)
: "rcx", "r11", "memory"
);
return ret;
}Аллокация
Без malloc/free нет автоматического управления памятью. Возможные решения:
-
Предварительное выделение из
.bss -
Ручная реализация bump allocator
✅ Преимущества
-
Полный контроль над кодом, памятью, входом и выходом
-
Снижение размера бинарника до килобайт
-
Возможность писать независимые ELF‑файлы, init‑утилиты, загрузчики
-
Упрощение статического анализа и аудита
❌ Недостатки
-
Потеря удобства: нет форматирования строк, динамической аллокации,
errno -
Нужно самому учитывать ABI, соглашения о вызовах, выравнивание
-
Вся совместимость с C‑библиотеками теряется
-
Сложность портирования и отладки
⚙️ Где используется
-
OSDev, написание ядра и утилит начальной загрузки
-
Bare metal программирование и RTOS
-
Разработка эксплойтов, шеллкодов, минимальных загрузчиков
-
Написание
init,stage1,freestandingпрограмм
🔗 Вывод
Libc — мощный, но тяжёлый слой над ядром. Писать без него — значит взять ответственность за весь путь от точки входа до системных вызовов. Это трудно, но даёт редкий уровень контроля, особенно важный в низкоуровневой и безопасной разработке.