【问题标题】:How to call a C function from ASM in C?如何在 C 中从 ASM 调用 C 函数?
【发布时间】:2013-12-24 01:00:34
【问题描述】:

我正在使用这段代码,但它在调试时给了我Segmentation fault。我正在使用 MinGW 进行编译:

#include <stdio.h>
#include <stdlib.h>

int CallMe(int a, int b)
{
    printf("Called.");
    return a + b;
}

int main()
{
    printf("CallMe at 0x00%x\n", (unsigned int)&CallMe);
    printf("Calling CallMe... ");

    asm (
        "movl $5, (%eax);\r\n"
        "movl $4, (%ebx);\r\n"
        "call 0x00401334;\r\n"
    );

    printf("\n");

    return 0;
}

函数地址没问题。有什么想法吗?

提前致谢。

【问题讨论】:

  • 尝试将 -S 选项传递给编译器以查看生成的程序集(输出将是 .s 文件)。将此与您尝试直接在 C 中调用函数(不使用 asm 块)进行比较。这应该给你一个很好的线索。如果没有,请在此处发布两个输出。
  • 你怎么确定“函数地址没问题”?首先它可能不太好,如果操作系统使用地址空间布局随机化 (ASLR),它甚至可能在运行时随机变化。
  • @datenwolf,代码打印函数地址。我假设它根据调用指令打印相同的地址。
  • @davmac:嗯,实际上 printf 调用是错误的。它应该使用%p 格式并转换为void* 才有用。此外,GCC 内联汇编允许在调用指令中使用符号,从而避免了硬编码的需要。
  • 为什么不直接调用函数名呢?

标签: c function assembly


【解决方案1】:

正如@EricZ 指出的那样,第一个问题是调用。

但是,修复该问题并不一定会使段错误消失。你这两个动作也出问题了:

movl $5, (%eax)
movl $4, (%ebx)

这是将值54 移动到地址分别位于%eax%ebx 中的位置。在这个特定的程序中,这些地址尚未定义。如果要将它们作为参数传递,则需要将它们压入堆栈。

pushl $5
pushl $4
call CallMe     % With some compilers, this may need to be _CallMe
addl  $8,%esp   % assuming a 32-bit processor

或者,如果函数期望它们在寄存器中(我不是说在这种情况下会,而是 if...),删除括号:

movl $5, %eax
movl $4, %ebx

您可能需要阅读调用约定,例如,http://en.wikipedia.org/wiki/X86_calling_conventions

【讨论】:

  • 您好,感谢您的回答。但我无法编译,请参阅上面的评论 (stackoverflow.com/questions/20753444/…)。另外,哪个是 64 位处理器的解决方案?
  • 您需要以与asm 语句相同的格式放置它。为了清楚起见,我将其显示为直接 ASM,而不是用于简单的复制/粘贴。我认为你可以做一些“翻译”工作。 :) 对于 64 位处理器,push 可能会推送一个 8 字节的值,在这种情况下,堆栈调整将为 addl $16, %esp
  • @cdonts 你所拥有的,使用我的建议,编译良好并且运行没有分段错误。
  • 只是为了补充答案,如果已经创建了参数的空间,则可以执行以下操作:movl $4, (%esp)movl $5, 4(%esp)call &lt;address&gt;
  • @mbratch 它对我不起作用,无法编译。我在使用 MinGW 有什么想法吗?
【解决方案2】:

这是在您的 main() 函数中真正执行的操作:

0x401268 <main>:    push   %ebp
0x401269 <main+1>:  mov    %esp,%ebp
0x40126b <main+3>:  sub    $0x8,%esp
0x40126e <main+6>:  call   0x401350 <__main>
0x401273 <main+11>: add    $0xfffffff8,%esp
0x401276 <main+14>: push   $0x401218  <--- address of CallMe
0x40127b <main+19>: push   $0x401242
0x401280 <main+24>: call   0x4013c0 <printf>
0x401285 <main+29>: add    $0x10,%esp
0x401288 <main+32>: add    $0xfffffff4,%esp
0x40128b <main+35>: push   $0x401250
0x401290 <main+40>: call   0x4013c0 <printf>
0x401295 <main+45>: add    $0x10,%esp
0x401298 <main+48>: movl   $0x5,(%eax)  <--- begin of your ASM block
0x40129e <main+54>: movl   $0x4,(%ebx)
0x4012a4 <main+60>: call   0x8024c1  <--- eewww! wrong address

所以现在的问题是:为什么将CALL 0x00401218 组装成CALL 0x008024C1

当然,您还会看到参数不是通过寄存器传递,而是通过将它们压入堆栈。

事实上,我已经用这个得到了它:

asm (
    "pushl $5;\r\n"
    "pushl $4;\r\n"
    "call _CallMe;\r\n"
    "addl %esp,$8;\r\n"
);

我之前遇到的关于堆栈不平衡的问题是由我的代码中的错误引起的,该错误使用addl %esp,8 而不是addl %esp,$8。前者(试图)将地址8的内存内容添加到ESP,而不是将常量8添加到ESP

如果您想收集函数的结果并将其保存在变量中,例如,您可以这样做:

int res;
...
...

asm (
    "pushl $5;\r\n"
    "pushl $4;\r\n"
    "call _CallMe;\r\n"
    "addl $8,%%esp;\r\n"
    "movl %%eax,%0;\r\n" : "=m" (res)
);

如果你真的想用它的地址调用函数,你可以通过以下几种方式:

通过将地址放入变量中,例如 void 指针:

void *addr;

addr = (void *)CallMe;
asm (
    "pushl $5;\r\n"
    "pushl $4;\r\n"
    "calll *%1;\r\n"
    "addl $8,%%esp;\r\n"
    "movl %%eax,%0;\r\n" : "=m" (res) : "m" (addr)
);

或者通过将要调用的地址放入寄存器中:

asm (
    "pushl $5;\r\n"
    "pushl $4;\r\n"
    "movl $0x00401218,%%eax;\r\n"
    "calll *%%eax;\r\n"
    "addl $8,%%esp;\r\n"
    "movl %%eax,%0;\r\n" : "=m" (res)
);

【讨论】:

  • 您好,感谢您的回答。尝试编译时,我得到error: memory input 0 is not directly addressable。有什么想法吗?
  • 行得通!谢谢。现在如何通过地址调用函数?
  • 这就是我想要的!我也在想如何读取返回值,太好了!非常感谢你! :-)
【解决方案3】:

一般情况下,不要通过硬编码地址调用函数。尝试调用函数名称。函数名实际上就是函数地址。

call 0x00401334

==>

call CallMe

【讨论】:

  • 这只是因为 C 不会破坏链接器名称,因此当链接器看到“CallMe”时,它知道它应该是什么函数。请注意,您不能在 C++ 中执行此操作,因为函数名称并不总是与您使用的名称匹配(可能取决于编译器)。
  • 虽然这是一个有价值的建议,但它实际上并不是对所提出问题的回答,应该作为评论提出。
  • 感谢您的回答。在编译时使用call CallMe 会给我一个undefined reference to CallMe。无论如何,我需要通过地址而不是名称来调用函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 2011-11-05
  • 2021-11-09
  • 1970-01-01
相关资源
最近更新 更多