【问题标题】:Debugging the C runtime调试 C 运行时
【发布时间】:2011-12-20 01:23:36
【问题描述】:

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

【问题讨论】:

  • 你为什么要这样做?你认为libc 有错误吗?
  • 出于学习目的,是的。我不怀疑 libc 中的错误 :)
  • 值得重申的是,这有用的知识,特别是在处理嵌入式系统时,C 运行时启动很可能是芯片复位后执行的操作。构建此类系统的一半工作是获得工具链,让您编写代码来打开设备和接口,从而提供您将在其中执行的内存。

标签: c debugging gdb runtime glibc


【解决方案1】:

您无需在调试器中启动。

当操作系统加载您的可执行文件时,它会将控制权传递给其入口点,该入口点不是名为 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 下启动的(我没仔细看)。因此它假设该进程已经创建并且代码和数据段已经加载并准备好使用。它将命令行参数从操作系统提供的格式转换为熟悉的arvcargv[] 调用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);
}

我在那个草图中遗漏了一些细节,但大纲应该大部分是真实的。函数指针名为initfini 的有趣业务主要是为了支持C++ 程序中全局对象的构造函数和析构函数。对于普通的 C 链接,这些指针将为 NULL,并且没有任何作用。

【讨论】:

  • 感谢您的提示,RB!我更愿意实际使用调试器,而不是挖掘源代码。还有一些其他的东西,特别是动态符号解析,我需要看看。
  • 恕我直言,这两种方法都很有用。我省略了很多的条件编译和创建草图代码片段的灵活性。通常,找出您真正在运行什么的唯一方法是查看调试器(或反汇编器)中的实际可执行文件。您还需要了解链接器在其中发挥的作用,以及用于在链接时编写函数 init()fini() 的有趣技巧。
【解决方案2】:

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

  • 安装 `glibc` 包的调试信息(here 是 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 <main>, argc=1,...) at libc-start.c:226
#2  _start ()

【讨论】:

  • 谢谢,正是我要找的东西
猜你喜欢
  • 2018-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-17
  • 2010-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多