调试C运行时

我想详细了解使用GDB在main()之前和之后发生的事情。 仅仅用-g重新编译glibc并链接到那个就足够了吗?

如果你想使用调试器,你可以这样使用GDB:

  • 安装`glibc`包的debug-info( 这是用Fedora做的方法,我不知道其他的发行版)
  • 或者将GDB指向一致的调试文件目录:
 (gdb) show debug-file-directory The directory where separate debug symbols are searched for is "/usr/lib/debug". (gdb) set debug-file-directory ... 

(在我的系统中是/usr/lib/debug/lib64/libc-2.14.so.debug

  • 告诉GDB在你的`main`之前显示回溯:
 (gdb) show backtrace past-entry Whether backtraces should continue past the entry point of a program is off. (gdb) set backtrace past-entry on 
  • 那么你应该看看你在寻找什么,并浏览它:
 (gdb) where #0 main () at test.c:4 #1 __libc_start_main (main=0x40050f 
, argc=1,...) at libc-start.c:226 #2 _start ()

您不需要在调试器中启动。

当操作系统加载您的可执行文件时,它会将控制权传递给其入口点,该入口点不是名为main()的函数。 在GCC和glibc中,真正的入口点通常名为_start ,但您的里程可能因您的平台而异。 当然,如果你没有使用glibc,或者正在使用不同的C编译器,那么它可能会有更多变化。

_start代码的关键工作是初始化所需的所有内容,以便创建main()期望的条件。 请注意,这对于C ++来说复杂得多 ,而且由于GCC支持这两种语言,真正的启动代码将具有额外的function,其唯一目的是支持C ++的要求。

_start源代码几乎总是用汇编语言编写,并且是高度特定于平台的。 对于32位x86平台,可以在glibc源代码树中的sysdeps/i386/elf/start.S下找到一个示例。

虽然您可能永远不需要在桌面操作系统上调试普通代码,但在处理小型嵌入式系统时,通常需要很好地理解运行时环境的初始化方式。 特别是,许多嵌入式系统直接从系统重置启动到此启动代码的版本。 在这样的系统上,必须打开将要使用的存储器或正确配置CPU的主时钟源并将第一个堆栈指针设置为合理的东西,然后才能担心更高级别的概念,这一点并不罕见。 .text.data.bss段。

start.S的版本假定它是在unix或linux的某种风格下启动的(我看起来不太仔细)。 因此,它假设已经创建了该进程,并且代码和数据段已经加载并可以使用。 它将命令行参数从操作系统提供的格式转换为调用main()所需的熟悉的arvcargv[] ,但它通过在__libc_start_main() csu/libc-start.c找到的名为__libc_start_main()的glibc源中的其他地方提供的包装器进行__libc_start_main() csu/libc-start.c

由于支持各种function的条件兼容指令的丰富性,该function的来源变得非常复杂。 但实际上,对于一个常见的情况,它归结为类似下面的内容:

 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); } 

我在该草图中遗漏了一些细节,但大纲应该是正确的。 带有名为initfini函数指针的有趣业务主要是支持C ++程序中全局对象的构造函数和析构函数。 对于普通C链接,这些指针将为NULL,并且不会产生任何影响。