总线错误: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】:

您收到总线错误的原因是您在汇编代码中调用了retret 使程序控制转移到堆栈顶部的返回地址,您可以通过使用pushpop 来操作它。我强烈建议在英特尔指令集参考中查找 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将参数引用到doStuffflags 更改为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

如何在 Mac OS X 10.8 / Xcode 4.4 上使用/安装 gcc

Mac OS X 上的 GCC 标志 -Os 来自哪里?

Qt 的编译警告 - 仅限 Mac OS X:<class> 已经是 <class> 的朋友