Ядро OpenBSD становится уже очень старым — ну просто очень — ему около сорока пяти лет. Оно, следовательно, не любит сюрпризов. Поэтому программы должны сообщать ему о том, где находятся их системные вызовы. В сегодняшнем выпуске «Вежливого программиста» мы поговорим о том, как это делать по всем правилам этикета.

Если вы программируете на C — всё это делается автоматически. Поэтому мы будем писать код только на языке, который совершенно нельзя назвать C, и при этом обойдёмся без линковки с libc
.
Начало
Начнём с простой программы hello
:
void
start()
{
w("hello\n", 6);
x();
}
typedef unsigned long size_t;
int
w(void *what, size_t len) {
__asm(
" mov x2, x1;"
" mov x1, x0;"
" mov w0, #1;"
" mov x8, #4;"
" svc #0;"
);
return 0;
}
void
x() {
__asm(
" mov x8, #1;"
" svc #0;"
);
}
В «совершенно не C» мы начинаем выполнение программы с функции start
и выводим сообщения с помощью функции w
(её исходный код включён в листинг). А после этого, в итоге, выходим из программы, вызывая функцию x
, так как, если работа завершится в нижней части функции start
— это будет настоящая жесть.
Вудуистика
Нам ещё понадобится немного магии вуду — чтобы сообщить ядру о том, что это — настоящая программа, а не плод бредовых фантазий Зимнего безмолвия.
__asm(" .section \".note.openbsd.ident\", \"a\"\n"
" .p2align 2\n"
" .long 8\n"
" .long 4\n"
" .long 1\n"
" .ascii \"OpenBSD\\0\"\n"
" .long 0\n"
" .previous\n");
Системные вызовы
А теперь, наконец, самое интересное. Мы сообщаем ядру об используемых нами системных вызовах, делая это с помощью небольшой таблицы, описывающей то, что происходит в программе. Как бы ни строги были правила, применяемые при построении этой таблицы, их соблюдение больших усилий не требует.
struct whats {
unsigned int offset;
unsigned int sysno;
} happening[] __attribute__((section(".openbsd.syscalls"))) = {
{ 0x104f4, 4 },
{ 0x10530, 1 },
};
Если вы изучали операционные системы — вы можете вспомнить, что write
— это системный вызов #4
, а exit
— системный вызов #1
. Что касается шестнадцатеричных смещений — ну — мы, всё же, пишем на языке, который нельзя безоговорочно назвать «совершенно не C», но разобраться с этим не так уж и сложно. Загляните в мануал.
Честно говоря, я не знаю, что ещё можно сделать, чтобы лучше раскрыть этот простой пример, разве что — скомпилировать код и запустить objdump
. Указатели функций не являются константами времени компиляции, они не дадут нам смещение секции elf
без дополнительных беззаконных умственных упражнений. Но это — то, что может достаточно легко раскрыть используемый вами набор инструментов. Сложности добавляет лишь то, что эти инструменты пытаются запихать это всё в «одноразовую» демонстрацию. А libc
просто использует немного больше «вудуистики» в asm-секциях, но всё это слишком мутно, чтобы тут воспроизводить.
Компиляция
Всё готово.
$ cc -c where.c
$ ld -e start --eh-frame-hdr -Bstatic -o where where.o
$ ./where
hello
Привет! Перед нами — согласованная работа программы и системных вызовов.
О, а приходите к нам работать? 🤗 💰
Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.
Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.