【问题标题】:Understanding assembly language _start label in a C program理解 C 程序中的汇编语言 _start 标签
【发布时间】:2016-06-01 20:37:15
【问题描述】:

我编写了一个简单的 c 程序,并试图使用 GDB 来调试程序。我了解在主要功能中使用以下内容:

进入时

push   %ebp
mov    %esp,%ebp

退出时

leave
ret

然后我在 _start 上尝试了 gdb,得到了以下结果

xor    %ebp,%ebp
pop    %esi
mov    %esp,%ecx
and    $0xfffffff0,%esp
push   %eax
push   %esp
push   %edx
push   $0x80484d0
push   $0x8048470
push   %ecx
push   %esi
push   $0x8048414
call   0x8048328 <__libc_start_main@plt>
hlt
nop
nop
nop
nop

我无法理解这些台词,以及这背后的逻辑。

有人可以提供任何指导来帮助解释_start的代码吗?

【问题讨论】:

  • 当使用 GCC 进行编译和链接时,_startC 运行时提供的用于初始化的标签。 C 启动初始化完成后,它会调用标签main。也许您打算打破标签main 而不是_start?您看到的代码似乎是 C 运行时初始化代码,最终调用 main
  • 初始化代码不遵循典型的调用约定,并且还根据 Linux ABI 处理在命令行上传递的参数,并将它们传递给main。它还确保堆栈正确对齐。
  • 这里有一个非常好的_start 教程:_start is, oddly enough, where we start
  • @Michael Petch - 感谢您提供这份令人兴奋的文件。我一定会经历的
  • 实际上,我不能相信。另一个迈克尔 - @MichaelBurr 发布了它;-)

标签: linux gcc assembly x86 glibc


【解决方案1】:

这是您发布的代码中注释良好的assembly source

总之,它做了以下事情:

  1. 使用 ebp = 0 建立一个标记堆栈帧,以便遍历堆栈的代码可以轻松找到其结束
  2. 将命令行参数的数量弹出到esi,以便我们可以将它们传递给__libc_start_main
  3. 将堆栈指针对齐为 16 位的倍数,以符合 ABI。在某些版本的 Linux 中不保证会出现这种情况,因此必须手动完成以防万一。
  4. __libc_csu_fini__libc_csu_init的地址、参数向量、参数个数和main的地址作为参数推送到__libc_start_main
  5. __libc_start_main 被调用。这个函数(源代码here)设置了一些glibc内部变量,最终调用main。它永远不会回来。
  6. 如果出于任何原因__libc_start_main 应该返回,则在之后放置一条hlt 指令。此指令在用户代码中是不允许的,应该会导致程序崩溃(希望如此)。
  7. nop 指令的最后系列是由汇编器插入的填充,因此下一个函数以 16 字节的倍数开始,以获得更好的性能。它在正常执行中永远不会到达。

【讨论】:

    【解决方案2】:

    对于 gnu 工具,_start 标签是程序的入口点。为了使 C 语言正常工作,您需要有一个堆栈,您需要将一些内存/变量归零,并将一些设置为您选择的值:

    int x = 5;
    int y;
    
    int fun ( void )
    {
       static int z;
    }
    

    这三个变量 x,y,z 本质上都是全局的,一个是局部全局的。因为我们是这样写的,所以我们假设当我们的程序启动时 x 包含值 5 并且假设 y 为零。为了让这些事情发生,需要一些引导代码,这就是 _start 和 main() 之间发生的(以及更多)。

    其他工具链可能会选择使用不同的标签来定义入口/起点,但 gnu 工具使用 _start。在 main() 被称为 C++ 之前,您的工具可能还需要其他一些东西,例如需要比 C 更多的东西。

    【讨论】:

    • 这根本不是这段代码在做什么。请注意,将全局变量归零不是由“某些启动代码”完成的。事实上,它可能根本没有完成。相反,当加载程序将二进制文件映射到 RAM 中时,匿名页面将映射到程序头中描述的 BSS 区域。然后内核在第一次使用时映射并清零这些,但不是更早。我对你的回答投了反对票,因为它根本没有真正回答这个问题。
    • 以及这背后的逻辑。
    • 我不太确定您的评论想要表达什么。
    • 只有动态初始化的东西需要构造函数在main()之前运行。 FUZxxl 是正确的:静态初始化程序发生在编译时,并影响 .data 部分中的内容。 (如果初始值设定项为零,则在.bss 中保留空间)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    • 2012-04-06
    • 1970-01-01
    相关资源
    最近更新 更多