总线错误:Mac OS X 上带有 GCC 的内联 x86 程序集
Posted
技术标签:
【中文标题】总线错误:Mac OS X 上带有 GCC 的内联 x86 程序集【英文标题】:Bus error: Inline x86 assembly with GCC on Mac OS X 【发布时间】:2011-04-26 00:50:59 【问题描述】:当我尝试在 Snow Leopard 上运行使用 gcc 4.2.1 编译的这段代码时,出现“总线错误”
#include <stdio.h>
/*__declspec(naked)*/ void
doStuff(unsigned long int val, unsigned long int flags, unsigned char *result)
__asm
push eax
push ebx
push ecx
push edx
mov eax, dword ptr[esp + 24]//val
mov ebx, dword ptr[esp + 28]//flags
//mov ecx, dword ptr[esp + 32]//result
and eax, ebx
mov result, eax
pop edx
pop ecx
pop ebx
pop eax
ret
int main(int argc, char *argv[])
unsigned long val = 0xAA00A1F2;
unsigned long flags = 0x00100001;
unsigned char result = 0x0;
doStuff(val, flags, &result);
printf("Result is: %2Xh\n", result);
return 0;
我正在使用以下命令编译gcc -fasm-blocks -m32 -o so so.c
,没有任何错误或警告。我正在尝试在 doStuff() 函数中运行一些汇编指令并将答案分配给结果。我做错了什么?
注意:这在 Windows 上的 Visual Studio 中运行良好,但我必须注释掉 declspec(naked) 才能让 gcc 在 Mac 上编译它。
【问题讨论】:
@styfle - 也许你的 mac os 是 64 位的,堆栈上的每个元素都是 8 字节长?你能检查一下吗? @MByD 我有一个 64 位 CPU(Core 2 Duo),但 64 位内核未启用,这就是为什么我在编译时需要 -m32 参数,否则我会得到suffix or operands invalid for push
错误。
@MByD 或者它可能已启用,这就是我需要 -m32 参数的原因。现在我很困惑。另外,如果字长有问题,编译/汇编时会不会出错?
@styfle - 我不知道,我只是想给一个方向:)
@MByD 谢谢你的意见。你能在你的机器上编译和运行它吗?
【参考方案1】:
您收到总线错误的原因是您在汇编代码中调用了ret
。 ret
使程序控制转移到堆栈顶部的返回地址,您可以通过使用push
和pop
来操作它。我强烈建议在英特尔指令集参考中查找 ret
的作用。
以下是我在运行 Mac OS X 10.6.7 的 iMac 上编译并成功运行的代码。
#include <stdio.h>
/*__declspec(naked)*/ void
doStuff(unsigned long int val, unsigned long int flags, unsigned char *result)
__asm
push eax
push ebx
push ecx
mov eax, dword ptr[ebp + 8] //val
mov ebx, dword ptr[ebp + 12] //flags
mov ecx, dword ptr[ebp + 16] //result
and eax, ebx
mov [ecx], eax
pop ecx
pop ebx
pop eax
int main(int argc, char *argv[])
unsigned long val = 0xAA00A1F2;
unsigned long flags = 0x00100002;
unsigned char result = 0x0;
doStuff(val, flags, &result);
printf("Result is: %2Xh\n", result);
return 0;
值得注意的变化是:
-
在内联程序集中删除
ret
使用寄存器ebp
而不是esp
将参数引用到doStuff
将flags
更改为0x00100002
更改 (1) 修复了总线错误,(2) 使引用参数更加一致,(3) 只是确保函数按预期工作的快速方法。
最后,我强烈建议您熟悉 GNU 调试器 GDB(如果您还没有的话)。您可以在项目页面http://www.gnu.org/software/gdb/ 找到有关它的更多信息,以及在http://developer.apple.com/library/mac/#documentation/DeveloperTools/gdb/gdb/gdb_toc.html 找到有关 Mac 实现和教程的信息。
编辑:添加到 GDB 的基本信息/链接,
【讨论】:
ret
的使用在 Visual Studio 中有效,我认为这是必需的,因为函数调用应该将返回地址推入堆栈并 ret 将其弹出,以便它知道去哪里函数调用。我也从未见过ebp。我发现 ebp 与 esp 不同,因为“基指针仅被显式操作”。最后,我不知道 gdb 是什么。你能提供一个链接吗?谢谢。
我刚刚用一些指向 GDB 的链接更新了答案。我无法确认这一点(我在这台机器上没有 Visual Studio),但我怀疑在 VS 中使用 ret
的原因是编译器如何将你的程序集插入到 doStuff()
中。尝试使用gcc -S ...
编译您的代码以查看编译器生成的程序集。如您所见,ebp
是基指针,它指向栈底,而esp
指向栈顶。只要你得到正确的偏移量,你使用哪一个并不重要。【参考方案2】:
编译器向函数调用添加序言和尾声,这些序言和尾声负责设置堆栈帧、为局部变量保留堆栈空间、销毁堆栈帧并返回给调用者。
使用帧指针时没有局部变量的函数的典型序言可能如下所示:
push ebp
mov ebp, esp
这会将调用者的帧指针保存在堆栈中,并使当前帧指针等于函数进入时的堆栈指针。
相应的尾声是:
pop ebp
ret
恢复前一帧指针并返回给调用者。
如果您告诉 gcc 不要使用帧指针 (-fomit-frame-pointer
),则相应的序言将为空,而结尾将仅包含一个 ret
。
__declspec(naked)
可能类似于 gcc 的 __attribute__((naked))
(gcc's function attributes),它只适用于某些架构,不适用于 x86。因此,在 gcc 上,您最好将调用者返回给编译器,正如 Dean Pucsek 建议的那样。
【讨论】:
当你说 prologue 和 epilogue 时,你是在谈论编译器自动执行的函数的开头和结尾发生的事情,还是程序员通常会做的事情?还是我走远了? 前者(即编译器添加它们)。以上是关于总线错误:Mac OS X 上带有 GCC 的内联 x86 程序集的主要内容,如果未能解决你的问题,请参考以下文章
在 Mac OS X 上组装项目时出现“没有这样的指令”错误
MAC OS X 中 gcc 4.3 或更高版本的 OpenCV 编译错误
如何在 Mac OS X 主机上为 MIPS 目标构建 GCC 4.8.x