调试C运行时

调试C运行时,c,debugging,gdb,runtime,glibc,C,Debugging,Gdb,Runtime,Glibc,我想详细了解一下使用GDB前后的情况。仅仅用-g重新编译glibc并链接到它就足够了吗?您不需要在调试器中启动 当操作系统加载可执行文件时,它会将控制权传递给它的入口点,该入口点不是名为main()的函数。在GCC和glibc中,真正的入口点通常命名为\u start,但您的里程数可能因平台而异。当然,如果您不使用glibc,或者使用不同的C编译器,那么它的变化可能会更大 \u start代码的关键工作是初始化所需的一切,以创建main()所需的条件。注意,这对于C++来说要复杂得多,而且由于G

我想详细了解一下使用GDB前后的情况。仅仅用
-g
重新编译glibc并链接到它就足够了吗?

您不需要在调试器中启动

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

\u start
代码的关键工作是初始化所需的一切,以创建
main()
所需的条件。注意,这对于C++来说要复杂得多,而且由于GCC支持两种语言,真正的启动代码将具有额外的特性,其唯一目的是支持C++的要求。
\u start
的源代码几乎总是用汇编语言编写的,并且是高度特定于平台的。对于32位x86平台,可以在glibc源代码树中的下找到一个示例

虽然在桌面操作系统上调试普通代码可能永远不需要看到这一点,但在小型嵌入式系统上工作时,通常需要很好地理解运行时环境是如何初始化的。特别是,许多嵌入式系统直接从system reset引导到该启动代码的版本中。在这样的系统上,在可能担心诸如
.text
.data
.bss
段之类的更高级概念之前,必须打开将要使用的内存或正确配置CPU的主时钟源,并将第一个堆栈指针设置为合理的值,这并不罕见

链接到的
start.S
版本假定它是在某种unix或linux风格下启动的(我没有仔细查看)。因此,它假设流程已经创建,代码和数据段已经加载并准备好使用。它将命令行参数从操作系统提供的格式转换为调用
main()
所需的熟悉的
arvc
argv[]
,但它是通过在中找到的名为
\u libc\u start\u main()
的glibc源代码中的其他地方提供的包装来实现的

由于大量支持各种功能的条件编译指令,该函数的源代码显得非常复杂。但本质上,对于一个常见的情况,它归结为以下几点:

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 < /COD>和 Fini主要是支持C++程序中全局对象的构造函数和析构函数。对于普通C链接,这些指针将为NULL,不会有任何效果。

如果您想使用调试器,可以通过以下方式使用GDB:

  • 安装'glibc'包的调试信息(这是Fedora的方法,我不知道其他发行版)
  • 或将GDB指向一致的调试文件目录:
(在我的系统中是
/usr/lib/debug/lib64/libc-2.14.so.debug

  • 告诉GDB在“main”之前显示回溯:
  • 然后,您应该看到您要查找的内容,并在其中导航:
(gdb)其中
#0 main()位于test.c:4
#1 uuu libc_start_main(main=0x40050f,argc=1,…)位于libc start。c:226
#2 _开始()

您为什么要这样做?你认为
libc
中有一个bug吗?出于学习目的,是的。我不怀疑libc中有错误:)值得重复的是,这是有用的知识,特别是在处理嵌入式系统时,其中C运行时启动可能是芯片复位后执行的操作。构建这样的系统的一半任务是让工具链让您编写代码,打开提供您将在其中执行的内存的设备和接口。谢谢提示,RB!不过,我更愿意实际使用调试器,而不是通过源代码进行挖掘。还有一些其他的东西,特别是动态符号解析,我需要看一下。我省略了许多条件编译和灵活性来创建略图代码片段。通常,了解您真正运行的是什么的唯一方法是查看调试器(或反汇编程序)中的实际可执行文件。您还需要了解链接器在其中扮演的角色,以及在链接时编写函数
init()
fini()
时使用的有趣技巧。谢谢,这正是我想要的
(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "/usr/lib/debug".
(gdb) set debug-file-directory ...
(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 <main>, argc=1,...) at libc-start.c:226
#2  _start ()