您无需在调试器中启动。
当操作系统加载您的可执行文件时,它会将控制权传递给其入口点,该入口点不是名为 main() 的函数。在 GCC 和 glibc 中,真正的入口点通常命名为 _start,但您的里程可能会因您的平台而异。当然,如果你不使用 glibc,或者使用不同的 C 编译器,那么它的变化可能会更大。
_start 处代码的关键工作是初始化所需的一切,以创建main() 期望的条件。请注意,对于 C++,这要复杂得多,而且由于 GCC 支持这两种语言,因此真正的启动代码将具有额外的功能,其唯一目的是支持 C++ 的要求。
_start 的源代码几乎总是用汇编程序编写,并且高度特定于平台。对于 32 位 x86 平台,可以在 glibc 源代码树的sysdeps/i386/elf/start.S 下找到一个示例。
虽然在桌面操作系统上调试普通代码可能永远不需要看到这一点,但在小型嵌入式系统上工作时通常需要很好地了解运行时环境的初始化方式。特别是,许多嵌入式系统直接从系统重置引导到此启动代码的一个版本。在这样的系统上,必须打开将要使用的内存或正确配置 CPU 的主时钟源并将第一个堆栈指针设置为合理的值,然后才可能担心更高级别的概念,例如.text、.data 和 .bss 段。
链接到的start.S 版本假定它是在某种unix 或linux 下启动的(我没仔细看)。因此它假设该进程已经创建并且代码和数据段已经加载并准备好使用。它将命令行参数从操作系统提供的格式转换为熟悉的arvc 和argv[] 调用main() 所需的格式,但它通过在名为__libc_start_main() 的glibc 源中的其他地方提供的包装器来实现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,并且没有任何作用。