Почему среда выполнения C не вызывает мой выход ()? - PullRequest
3 голосов
/ 03 июля 2019

Стандарт C гласит, что ...

... возврат из начального вызова функции main эквивалентен вызову функции exit со значением, возвращаемымФункция main в качестве аргумента.

Из того, что я вижу, это обычно реализуется кодом поддержки времени выполнения C (crt0.c), делая именно это - вызывая exit с возвращаемым значением изmain.

glibc :

result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
exit (result);

Ulysses Libc :

exit(main(argc, argv, envp));

Однако, когда я пишумоя собственная версия exit, она вызывается , а не :

#include <stdio.h>
#include <stdlib.h>

void exit( int rc )
{
    puts( "ok" );
    fflush( stdout );
}

int main()
{
    return 0;
}

Это не дает ожидаемого результата "ok".Видимо, я что-то здесь упускаю?


Контекст: я реализую стандартную библиотеку C, только часть ISO, т.е. не crt0.c.Я надеялся, что существующая система во время выполнения вызовет мою собственную exit реализацию , поэтому «моя» очистка (например, очистка и закрытие потоков, обработка функций, зарегистрированных в atexit и т. Д.) Будет автоматически выполняться при возврате изmain связано с моей библиотекой.Очевидно, что это не так, я просто не понимаю, почему нет.

1 Ответ

5 голосов
/ 03 июля 2019

Если я правильно понимаю, вы пытаетесь реализовать функции в стандартной библиотеке C, пытаясь использовать некоторые части среды выполнения C (а именно то, что вызывает основную функцию и завершается).

Часть кода, которая обычно делает это, является функцией _start. Обычно это точка входа для двоичных файлов ELF с загрузчиком Linux.

Эта _start функция определена во время выполнения C, которое использует ваш компилятор, и вызов для выхода уже связан (адрес, исправленный в месте вызова). Вполне вероятно, что он просто встроен в функцию _start.

Чтобы функция _start вызывала ваш exit, вам нужно переопределить сам _start. Затем вы должны убедиться, что _start среды выполнения C не используется.

Я бы пошел с чем-то вроде этого -

// Assuming your have included files that declare the puts and fflush functions and the stdout macro. 
int main(int argc, char* argv[]); // This is here just so that there is a declaration before the call
void _start(void) {
    char *argv[] = {"executable"}; // There is a way to get the real arguments, but I think you will have to write some assembly for that. 

    int return_value = main(1, argv);
    exit(return_value);
    // Control should NEVER reach here, because you cannot return from the _start function
}

void exit(int ret) {
    puts("ok"); 
    fflush(stdout); // Assuming your runtime defines these functions and stdout somewhere. 
    // To simulate an exit, we will just spin infinitely - 
    while(1);
}

int main(int argc, char* argv[]) {
    puts("hello world\n");
    return 0;
}

Теперь вы можете скомпилировать и связать файл как -

gcc test.c -o executable -nostdlib

-nostdlib указывает компоновщику не ссылаться на стандартную среду выполнения, которая имеет реализацию _start.

Теперь вы можете выполнить свой исполняемый файл, и он будет вызывать ваш «выход», как вы и ожидали, а затем продолжит цикл навсегда. Вы можете убить его, нажав Ctrl + C или отправив SIGKILL другим способом.

Приложение

Просто для полноты картины я также попытался записать реализацию для остальных функций.

Сначала вы можете добавить следующие объявления и определения вверху вашего кода.

#define stdout (1)
int puts(char *s);
long unsigned int strlen(const char *s) {
        int len = 0;
        while (s[len])
                len++;
        return len;
}
int fflush(int s) {
}
void exit(int n);

strlen определено как ожидается, а fflush не используется, поскольку мы не реализуем буферизацию для наших функций stdio.

Теперь в отдельном файле put.s напишите следующую сборку (предполагая x64 linux. Измените числа и аргументы системного вызова, если ваша платформа отличается).

        .text
        .globl puts
puts:
        movq    %rdi, %r12
        callq    strlen
        movq    $1, %rdi
        movq    %r12, %rsi
        movq    %rax, %rdx
        movq    $1, %rax
        syscall
        retq

Это простейшая реализация puts, которая вызывает функцию strlen, за которой следует системный вызов write.

Теперь вы можете скомпилировать и связать все как -

gcc test.c put.s -o executable -nostdlib

Когда я запускаю произведенный executable, я получаю следующий вывод -

hello world
ok

и тогда процесс просто зависает. Я могу убить его, нажав Ctrl + C.

...