在C中调用NASM函数

4
我正在学习x86汇编语言,并希望在C中调用NASM函数。当我运行程序时,会出现以下错误:
“段错误(核心已转储)”
我已尝试多种变体的简单测试函数,但每次都在同一位置停止。
以下是我的asm和c文件:
div.asm:
global _test

_test:
    push    ebp
    mov     ebp, esp
    push    ebx
    mov     eax, [ebp+8]
    mov     ebx, [ebp+12]
    div     ebx
    pop     ebp
    ret

main.c:

#include <stdio.h>

extern unsigned int test (unsigned int, unsigned int);

int main(void)
{
    printf("%d\n", div(85,5));
    return 0;
}

我使用以下方式编译和链接文件:

我编译并链接这些文件:

nasm -f elf -o div.o div.asm
gcc -m32 -c -o main.o main.c
gcc -m32 -o run div.o main.o

我在一台虚拟机中使用了64位Linux

这里有什么错误,应该如何解决?


4
使用调试器找出导致错误的指令。请参阅 x86 标签 wiki 的 FAQ 部分(http://stackoverflow.com/tags/x86/info),其中有一些常见问题:push/pop 不匹配和 div 问题。 - Peter Cordes
1
哦,说得好,是的,Linux不会破坏以 _ 开头的名称。你正在调用的 test 函数甚至不是你组装的 _test 函数!既然它链接了,那么这个符号肯定存在于某个库中,我猜?再次强调,调试器非常重要。使用 -g 编译。像这样:gcc -m32 -Wall -Wextra -Og -g main.c div.o -o run - Peter Cordes
3
等一下。你调用了一个名为div的函数,但是在你的代码中没有定义它。你在汇编中定义的函数叫做“test”。 - Leandros
1
浮点异常是因为您正在将EDX:EAX除以EBX,商可能不适合32位寄存器。您没有清除_EDX_。在进行div之前尝试放置xor edx, edx以确保被除数的高32位为零。 - Michael Petch
1
@PaulOgilvie:他在谈论C代码。它有一个test()的原型,但调用了div() - Peter Cordes
显示剩余8条评论
2个回答

4
您忘记了弹出 ebx 寄存器(或者至少将堆栈恢复为原始状态):
push    ebp
mov     ebp, esp
push    ebx         ; you push it here
mov     eax, [ebp+8]
mov     ebx, [ebp+12]
xor     edx,edx     ; ..and you must zero edx
div     ebx
pop     ebx         ; forgot to pop it here
pop     ebp
ret

2
你需要恢复调用者的 ebx!最好的方法是一开始就不要使用保留调用寄存器(例如使用 ecx)。更好的方法是 div [ebp+12] - Peter Cordes
1
@PaulOgilvie:pop ebx 的加载部分是必要的。当然,add esp, 4(而不是 add sp, 4),或者传统的 mov esp, ebp 部分的 leave 可以恢复 esp,这样 pop ebp / ret 就可以工作了,但仍然存在严重的破坏 ebx 的错误。这不是一个有效的建议。 - Peter Cordes
1
但是保罗,由于他在Linux上创建了一个32位应用程序,CDECL指定某些寄存器需要被函数保留。特别地:_寄存器EAX、ECX和EDX是调用者保存的,其余的是被调用者保存的_。如果_EBX_实际上没有恢复,你以后可能会遇到一些讨厌的错误。开始时它可能看起来可以工作,但最终那个错误会咬你的屁股。 - Michael Petch
3
你展示了堆栈错误,但你的建议"不要使用pop ebx,而是用add sp,4"是错误的。函数需要保留EBX的值,所以在返回之前必须恢复EBX的值。根据32位代码的CDECL调用约定add sp,4修复了堆栈,但不会恢复EBX。 - Michael Petch
1
@MichaelPetch,我明白了。我从我的答案中删除了这行代码。 - Paul Ogilvie
显示剩余6条评论

4

不清楚您是否解决了问题。除了其他问题之外,您需要确保在main.c中调用函数与div.asm中的调用匹配。例如,如果您创建了一个汇编函数_test,则需要将其声明为extern并在main中实际使用该函数。例如:

#include <stdio.h>

extern unsigned int _test (unsigned int, unsigned int);

int main(void)
{
    printf("%d\n", _test (85,5));    /* you are calling div here, not _test */
    return 0;
}

你的函数名称并不是你的汇编对象文件div.o的名称 -- 正如评论中指出的,div是一个无符号除法,在stdlib.h中与ldivlldiv声明。

你在汇编函数文件中的global声明必须与你在main中声明的名称匹配,例如:

    global _test

_test:
    push    ebp
    mov     ebp, esp
    mov     eax, [ebp+8]
    xor     edx, edx
    div     dword [ebp+12]
    mov     esp, ebp
    pop     ebp
    ret

现在,您可以编译、链接并运行您的测试文件:
$ nasm -f elf -o div.o div.asm
$ gcc -m32 -c -o main.o main.c
$ gcc -m32 -o run div.o main.o
$./run
17

或者对于编译/链接,只需简单执行以下操作:
$ nasm -f elf -o div.o div.asm
$ gcc -m32 -o run main.c div.o

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接