【问题标题】:OS/X 64-bit assembly code generates bus errorOS/X 64 位汇编代码生成总线错误
【发布时间】:2016-10-21 02:52:21
【问题描述】:

我正在尝试在我的 64 位 Macbook Pro 上学习 NASM。我有以下代码,我试图将变量的值分配给初始化变量。

global start
default rel

section .data
a:      dq      1

section .bss

b:      resq    1

section .text

    start:
        mov rax, a
        mov [b], rax

代码编译并链接,但在运行时会生成bus error。有没有人对如何克服这个有任何想法?

【问题讨论】:

  • 您需要正确结束程序。另外,学习使用调试器。
  • 只需使用作为 OS X 一部分的lldb
  • 在 OS/X 上,您应该将 lldb 视为调试器(至少从一开始)。您可能还想了解 OS/X 64-bit syscalls 。从该信息中,您将了解到要退出程序,您将执行类似 mov eax, 0x2000001 xor edi, edi ; Return exit value 0 to system syscall 之类的操作。同样mov rax, a 这一行可能应该是mov rax, [a],因为我认为您打算将四字值移动到内存地址a 而不是它的地址。
  • syscall 成功了,我一定会调查lldb
  • 顺便说一句,当您看到BUS ERROR 时,这通常意味着您正在尝试访问系统保留内存中程序可用范围之外的内存地址。有多种方法可以导致这种情况发生(正如迈克尔的回答解释了未能退出的结果)。您还会经常看到这种间接错误(例如,如果您尝试使用指针的值作为其地址等)

标签: macos assembly nasm x86-64 bus-error


【解决方案1】:

要回答关于BUS ERROR 的具体问题,它的发生是因为您没有正确退出应用程序,并且处理器开始执行代码中最后一条指令后内存中的内容。最终导致故障。当处理器到达包含您的代码的可执行页面的末尾并开始执行.data 部分时,可能会发生BUS ERROR.data 部分是不可执行的,因此很可能导致您观察到的错误。这只是一个有根据的猜测,因为它高度依赖于内存的内容和布局。

您似乎正在绕过 C 运行时,因此您不能使用 RET 返回操作系统。您需要调用 64 位 OS/X SYSCALLs 之一。

可以在Apple's site 上找到 64 位 OS/X 系统调用的列表。您可以从此tutorial(在 64 位部分)学习基础知识。退出系统调用有一个入口:

1    AUE_EXIT   ALL   { void exit(int rval); } 

从教程中,参数传递约定被描述为:

  • 参数在寄存器 rdi、rsi、rdx、r10、r8 和 r9 上传递 rax 寄存器中的系统调用号
  • 调用是通过系统调用指令完成的
  • OS X 的贡献在于您必须将 0x20000000 添加到系统调用号(仍然必须弄清楚原因)

完整的调用约定在64-bit System V ABI 中进行了描述。关于 SYSCALL 的另一个重要说明是:

  • 系统调用是通过系统调用指令完成的。内核破坏 注册 %rcx 和 %r11。

考虑到这一切,我们想致电 1 AUE_EXIT ALL { void exit(int rval); } 。系统调用号在第一列。在 64 位 OS/X 上,我们将 0x2000000 添加到它并在 RAX 中传递它。退出系统调用有一个参数,所以它是在 RDI 中传递的。这是退出值。使用 exit 系统调用并返回 0 的代码如下所示:

mov eax, 0x2000001 
xor edi, edi ; Return exit value 0 to system 
syscall

调试

正如@paulsm4 在他删除的答案中正确指出的那样:

最后,我不确定您的“总线错误”来自何处。但是调试器会告诉你

要查找 SIGBUSSIGSEGV 错误,最好使用调试器逐步执行汇编指令并找出故障所在。在这种情况下,您会发现在 mov [b], rax 之后调用了意外指令。

OS/X 上最常用的命令行调试器是 LLDB。您可以在LLDB tutorial 中找到有关它使用的更多信息。

【讨论】:

  • OS X 何时生成 SIGSEGV 而不是 SIGBUS?根据我的经验(Linux + Solaris),SIGBUS 发生在 SPARC Solaris 上的未对齐内存访问。 x86 Linux 为几乎所有内容(AFAIK)提供 SIGSEGV,包括未对齐的movdqa,或跳转到未映射的页面。 (此处的 OP 代码很可能只是在加载/存储时出错,例如在执行到达包含 _start 的页面末尾之前在 00 00 add BYTE PTR [rax],al 上)
  • 我必须检查所有场景,但在 OS/X 中生成它的不仅仅是英特尔上的未对齐访问。我的观察是,由于 OPs 代码将 a 的地址加载到 RAX(有效内存位置)中,并且代码段中的所有内存可能都初始化为 0,因此它可能相当于连续的 add [rax], al 。鉴于数据部分可能在 .text 之后,当指令跨越可执行/非可执行内存或者是数据区域中的第一条指令时,可能会发生总线错误。正如我所说,我正在对此做出有根据的猜测。
  • 我预感在不可执行的内存页面中执行代码是 BSD 引发 SIGBUS 而不是 SIGSEGV 的场景之一,过去的经验与 Linux 引发的不同。
  • 哦,是的,rax 在这里有一个有效的地址,所以它可以去任何一种方式。
  • @PeterCordes 今天早上我有机会确认在英特尔 OS/X 上的不可执行内存页面中执行代码会生成 SIGBUS 而不是 SIGSEGV,这就是这里发生的情况。同样,这个 C 代码也会引发 SIGBUS: static unsigned char a = 0xc3; int main() { ((void (*)())&a)(); } 。是的,这是未定义的行为,但调试器还显示,仅尝试执行编码的 RETQ (0xc3) 指令就会引发 SIGBUS。在 Linux 上,RETQ 引发了 SIGSEGV。
猜你喜欢
  • 1970-01-01
  • 2017-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-04
  • 2011-01-29
  • 1970-01-01
相关资源
最近更新 更多