Буду писать о Си для linux
И так как обычно пишут программы на си?
int main() {
return 0;
}
Простой каркас, в нем объявленная функция main которая возвращает значение - 0.
И программа заканчивается возвращая значение которое вернула функция main, но на самом деле это все илюзия, mai не точка входа, это все дело glibc, а как же на чистом Си все делается?
Настоящая точка входа в программу функция _start() и если вы просто зделаете:
int _start() { return 0; }
И скомпилируете с параметром -nostdlib то вы не получите работающих программу.
Вы получите ошибку сегментации, потому что программа не полная, точка входа есть, а завершения программы нет, что вы думмали завершить программу тоже не просто.
Завершается программа системным вызовом, что такое системный вызов можно прочесть в википедии, но вкраце системные вызовы это API к ядру
Так как же завершить программу правильно?
Завершить её с помощи Си нельзя бо системные вызовы это низкоуровненая штука к которой можно получить доступ только через Ассемблер и машинный код.
Нам нужен системный вызов #1 это системный вызов exit, у него есть один аргумент - код возврата, это тот код с которым закончится программа.
Так давайте же сделаем системный вызов.
Для этого нам нужно обратится к Асмемблеру.
Это можно сделать с Си кода, называется Асемблерна вставка, а делается она так:
asm("Код на Ассемблере");
А код выхода выглядеть так:
asm(
"movl $1, %eax;" //системный вызов № 1 — sys_exit
"xorl %ebx, %ebx;" //выход с кодом 0
"int $0x80;" //вызов ядра
);
Не тестировал но должно работать.
Давайте же разберёмся немного что мы сделали.
Мы поместили в регистр номер системного вызова 1 - exit
Далее указали параметр системному вызову - 0, это код успешного завершения
Далее обратились к ядру linux и оно выполнено прерывание
Перекусил немного.
Я обычно код тестирую на своём arm ноуте, да и пишу тамже, он у меня как тестовый полигон, поэтому я привык к arm синтаксису GNU Assembler, x86 не юзаю поэтому мне намного комфортней показывать примеры с arm ассемблером, последующие Ассемблерные вставки для arm процессора, переделать их вообще не сложно
Итоговая рабочая программа для arm будет выглядеть так:
void _start() {
//Тут код программы
// Выход
__asm__(
"mov r0, #0;" // Возращаемое значение - 0
"mov r7, #1;" // Номер системного вызова - 1 (exit)
"swi 0;" // Системный вызов ядра
);
}
Можно скомпилировать, только обязательно с опцией -nostdlib, эта опция убирает glibc
И запустить, все что сделает программа это за пуститься и остановится с кодом 0
Чего ж мы hello world то не написали, на самом деле просто вывести hello world нельзя, функции printf и puts, отсутствуют, да и вообще у нас есть всего несколько функций, одна из них __asm__ которая делает вставку кода на асме, абсолютно все нужно делать самому, все что у нас есть это системные вызовы, все делается через них, linux имеет более трёхсот системных вызовов и exit среди них.
Короче все, навеселился.
Пошел отдыхать
Кстати кто сможет посчитать длину данной переменной: char *str string = "tratata"; без вызова любых функций?
Дам +5 в карму тому кто справится