Вам не нужно запускаться в отладчике.
Когда ОС загружает ваш исполняемый файл, она передает управление своей точке входа, которая не является функцией с именем main()
.В GCC и glibc истинная точка входа обычно называется _start
, но ваш пробег может варьироваться в зависимости от вашей платформы.Конечно, если вы не используете glibc или используете другой компилятор C, он может отличаться еще больше.
Основная задача кода на _start
состоит в инициализации всего, что требуетсячтобы создать условия, которые main()
ожидает.Обратите внимание, что это на намного сложнее для C ++, и, поскольку GCC поддерживает оба языка, истинный код запуска будет иметь дополнительные функции, единственная цель которых - поддерживать требования C ++.
Исходный коддля _start
почти всегда написано на ассемблере, и это сильно зависит от платформы.Для 32-битной платформы x86 один образец можно найти в дереве исходных текстов glibc в разделе sysdeps/i386/elf/start.S
.
Хотя вполне вероятно, что вам никогда не понадобится это видеть, чтобыдля отладки обычного кода в настольных операционных системах при работе на небольших встроенных системах часто требуется хорошее понимание инициализации среды выполнения.В частности, многие встроенные системы загружаются непосредственно из сброса системы в версию этого кода запуска.В такой системе нет ничего необычного в том, чтобы включать память, которая будет использоваться, или правильно настраивать основные тактовые источники ЦП и устанавливать указатель первого стека на что-то разумное, прежде чем можно будет беспокоиться о концепциях более высокого уровня, таких какСегменты .text
, .data
и .bss
.
Версия start.S
, связанная с, предполагает, что она запускается под некой разновидностью Unix или Linux (я не выглядел слишком внимательно).Таким образом, можно предположить, что процесс создан и что сегменты кода и данных уже загружены и готовы к использованию.Он преобразует параметры командной строки из формата, предоставленного ОС, в привычные arvc
и argv[]
, необходимые для вызова main()
, что он делает, но через оболочку, предоставленную где-то еще в источниках glibc с именем __libc_start_main()
, найденных вcsu/libc-start.c
.
Источник этой функции выглядит чрезвычайно сложным из-за обилия директив компиляции условий, которые поддерживают широкий спектр функций.Но по сути это сводится к следующему для общего случая:
STATIC int
__libc_start_main(int (*main) (int, char **, char **),
int argc, char **av,
int (*init)(int, char **, char **),
void (*fini) (void),
void (*rtld_fini) (void), void *__unbounded stack_end)
{
int result;
/* some basic initializations goes here, then... */
/* initialize some core parts of the library */
__libc_init_first (argc, argv, __environ);
/* arrange to call finalizers at exit if any */
if (fini)
__cxa_atexit ((void (*) (void *)) fini, NULL, NULL);
/* call initializers, if any */
if (init)
init(argc, argv, __environ);
/* call user's actual main, which might not return */
result = main (argc, argv, __environ);
/* if main did return, exit appropriately */
exit (result);
}
В этом наброске я пропустил некоторые детали, но в общих чертах это должно быть верно.Забавный бизнес с указателями на функции init
и fini
в первую очередь заключается в поддержке конструкторов и деструкторов глобальных объектов в программе на C ++.Для простой связи C эти указатели будут иметь значение NULL, и никакого эффекта не будет.