GCC 生成的程序集 - C 函数调用时的段错误

Posted

技术标签:

【中文标题】GCC 生成的程序集 - C 函数调用时的段错误【英文标题】:GCC generated assembly - Segfault at C function call 【发布时间】:2012-09-17 05:07:14 【问题描述】:

我最近在 windows 中使用 GCC 练习 x86 汇编。我目前正在混合汇编和 C 代码以进行测试。我遇到了一些奇怪的东西,以我目前的知识无法解释,所以我向 SO 寻求帮助。

让我们看一个最小的例子。我们在 C 代码中有一个 test_func() 函数,我们希望将其转换为汇编,然后从 C 代码中调用它。这个函数虽然是从项目中调用其他函数,但问题就在这里。从程序集内部对其他 C 代码函数的任何调用都会给我一个分段错误。

我们想在汇编中看到的函数:

#include "rf_string.h"
void test_func()
//nonsense function, just calling another C function of the project
    RF_String s;
    rfString_Init(&s,"anything");

要求 gcc 为此吐出汇编代码给我们:

    .file   "to_asm.c"
    .section .rdata,"dr"
    .align 4
LC0:
    .ascii "anything\0"
    .text
    .globl  _test_func
    .def    _test_func; .scl    2;  .type   32; .endef
_test_func:
LFB6:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $40, %esp
    movl    $LC0, 4(%esp)
    leal    -16(%ebp), %eax
    movl    %eax, (%esp)
    movl    _rfString_Init, %eax
    call    *%eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE6:

然后将其保存到一个名为“asm_test.S”的文件中,并将其包含在项目的构建过程中,它可以构建和链接正常。所以我们试着做一个简单的程序来调用这个函数。

int main()

    test_func();
    return 0;

调试时出现问题。从汇编代码内部调用另一个 C 函数时出现分段错误。我尝试从汇编代码调用的任何其他函数都会发生这种情况,它与我使用的函数没有任何关系。

我认为这与这里的函数调用有关:

 movl    %eax, (%esp)
 movl    _rfString_Init, %eax
 call    *%eax

为什么要这样做,这是什么意思?具体为什么不简单地调用该函数? %eax 寄存器前的 * 是什么?我还需要补充一点,如果我用一个简单的函数调用来替换最后两行,如下所示,一切似乎都工作得很好,这真的让我感到困惑。

 call     _rfString_Init

另一个小问题是关于所有这些 cfi_directives。如果我要根据 SO 中提出的问题的答案来判断,它们似乎是为异常处理目的而生成的。问题是,就代码的功能而言,我可以放心地忽略它们吗?

【问题讨论】:

【参考方案1】:

这里:

 movl    _rfString_Init, %eax
 call    *%eax

请注意,_rfString_Init 之前没有 $(与 movl $LC0, 4(%esp) 不同)。这意味着_rfString_Init 不是直接常量(例如,函数或对象的常量地址或只是一个常量),而是一个内存变量。

因此,movl 会使用名为 _rfString_Init 的变量的内容加载 eax

然后call 调用包含在eax 中的地址。星号是一些语法糖,表示将通过引用/指针间接传递控制。

所以,rfString_Init 确实是一个指向函数的指针。在你的 C 代码中查找它!

当然,如果您尝试将控制权转移到指针中包含的字节,则不会有任何好处,因为它是数据,不应该被解释为代码。此外,底层内存很可能被配置为不可执行。

我对这些指令了解不多。它们也可以用于调试。无论如何,它们不会在您看到它们的地方插入代码。

【讨论】:

划掉我之前的评论。来自我的手机。该函数不是函数指针,但我的问题省略了一些细节。函数是库的一部分。哪个被导出到一个dll中。测试用例没有生成 dll,但错误地忘记了声明中的 dllimport。这导致它在函数符号名称之前添加 _imp。我注意到它并删除它认为它会正常工作。好吧,似乎这种调用函数的方式就是从 dll 中完成的。我会接受你的回答,因为你的 func 指针提示会引导我找到答案 我只是喜欢在这里提问时学习新事物的方式。并不总是通过直接的方式:) DLL 与否,如果机器代码进行间接调用,则使用指针。 嗯,是的,有道理。我在测试时的初衷是不进行间接调用,但是由于忘记了 declspec(import) 这就是正在发生的事情。现在多亏了你,我知道函数指针调用是如何发生的以及如何在汇编中识别它。非常感谢。

以上是关于GCC 生成的程序集 - C 函数调用时的段错误的主要内容,如果未能解决你的问题,请参考以下文章

将结构从 main() 传递给工作函数时的段错误

函数调用时的 C 到 C++ 错误

函数调用中的段错误

从Assembly调用C函数(printf)时的Segfault

推送到成员向量时的段错误

C 内在函数、SSE2 点积和 gcc -O3 生成的程序集