分段故障11链接os x 32位汇编程序

时间:2016-03-12 22:23:35

标签: macos assembly x86 32-bit libc

更新:果然,这是最新版本的nasm中的一个错误。我“降级”并在修复我的代码后,如我接受的答案所示,一切正常。谢谢大家!

我在OS X上的32位汇编程序中应该是一个非常简单的程序有问题。

首先,代码:

section .data
hello   db  "Hello, world", 0x0a, 0x00

section .text
default rel

global _main
extern _printf, _exit

_main:
    sub esp, 12     ; 16-byte align stack
    push hello
    call _printf

    push 0
    call _exit

它汇编和链接,但当我运行可执行文件时,它崩溃并出现分段错误:11。

汇编和链接的命令行是:

nasm -f macho32 hello32x.asm -o hello32x.o

我知道-o没有必要100%

链接:

ld -lc -arch i386 hello32x.o -o hello32x

当我把它运行到lldb来调试它时,一切都很好,直到它进入对_printf的调用,它会崩溃,如下所示:

  (lldb) s
  Process 1029 stopped
  * thread #1: tid = 0x97a4, 0x00001fac hello32x`main + 8, queue = 'com.apple.main-thread', stop reason = instruction step into
      frame #0: 0x00001fac hello32x`main + 8
  hello32x`main:
  ->  0x1fac <+8>:  calll  0xffffffff991e381e
      0x1fb1 <+13>: pushl  $0x0
      0x1fb3 <+15>: calll  0xffffffff991fec84
      0x1fb8:       addl   %eax, (%eax)
  (lldb) s
  Process 1029 stopped
  * thread #1: tid = 0x97a4, 0x991e381e libsystem_c.dylib`vfprintf + 49, queue = 'com.apple.main-thread', stop reason = instruction step into
      frame #0: 0x991e381e libsystem_c.dylib`vfprintf + 49
  libsystem_c.dylib`vfprintf:
  ->  0x991e381e <+49>: xchgb  %ah, -0x76f58008
      0x991e3824 <+55>: popl   %esp
      0x991e3825 <+56>: andb   $0x14, %al
      0x991e3827 <+58>: movl   0xc(%ebp), %ecx
  (lldb) s
  Process 1029 stopped
  * thread #1: tid = 0x97a4, 0x991e381e libsystem_c.dylib`vfprintf + 49, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x890a7ff8)
      frame #0: 0x991e381e libsystem_c.dylib`vfprintf + 49
  libsystem_c.dylib`vfprintf:
  ->  0x991e381e <+49>: xchgb  %ah, -0x76f58008
      0x991e3824 <+55>: popl   %esp
      0x991e3825 <+56>: andb   $0x14, %al
      0x991e3827 <+58>: movl   0xc(%ebp), %ecx

正如您可以看到的那样,它会因访问错误而停止。

1 个答案:

答案 0 :(得分:4)

16字节堆栈对齐

代码的一个严重问题是堆栈对齐。在进行 CALL 时,32位OS / X代码需要16字节堆栈对齐。 Apple IA-32 Calling Convention说明了这一点:

  

IA-32环境中使用的函数调用约定与System V IA-32 ABI中使用的函数调用约定相同,但以下情况除外:

     
      
  • 返回结构的不同规则
  •   
  • 堆栈在函数调用
  • 处是16字节对齐   
  • 大数据类型(大于4个字节)保持自然对齐
  •   
  • 大多数浮点运算使用SSE单元而不是x87 FPU执行,除非在长双精度值上运行。 (IA-32环境默认为x87 FPU的64位内部精度。)
  •   

ESP 中减去12,使堆栈与16字节边界对齐(4个字节表示返回地址+ 12 = 16)。问题是,当你对一个函数进行 CALL 时,堆栈必须在 CALL 之前对齐16个字节。不幸的是,在调用printfexit之前,您会推送4个字节。当堆栈应该对齐到16个字节时,这会使堆栈错位4。您必须以正确的对齐方式重新编写代码。同样,您必须在打电话后清理堆栈。如果您使用 PUSH 将参数放入堆栈,则需要在 CALL 之后调整 ESP 以将堆栈恢复到之前的状态。

修复代码的一种天真的方式(不是我的建议)就是这样做:

section .data
hello   db  "Hello, world", 0x0a, 0x00

section .text
default rel

global _main
extern _printf, _exit

_main:
    sub esp, 8     
    push hello     ; 4(return address)+ 8 + 4 = 16 bytes stack aligned
    call _printf
    add esp, 4     ; Remove arguments

    push 0         ; 4 + 8 + 4 = 16 byte alignment again
    call _exit     ; This will not return so no need to remove parameters after

上面的代码有效,因为我们可以利用这两个函数(exitprintf)只需要将一个 DWORD 放置在堆栈上的参数。 main返回地址为4个字节,我们进行堆栈调整为8个字节, DWORD 参数为4个字节对齐为4个字节。

更好的方法是计算main函数中所有基于堆栈的局部变量(在本例中为0)所需的堆栈空间量,以及您将使用的最大字节数需要任何参数来调用main进行的调用,然后确保填充足够的字节以使值可以被12整除。在我们的例子中,任何给定函数调用需要推送的最大字节数是4字节。然后我们添加8到4(8 + 4 = 12)以便可以被12整除。然后我们在函数开始时从 ESP 中减去12。

现在,您可以将参数直接移动到堆栈中,而不是使用 PUSH 将参数放入堆栈中,而不是使用 PUSH 。因为我们没有 PUSH ,所以堆栈不会错位。由于我们没有使用 PUSH ,因此我们不需要在函数调用后修复 ESP 。然后代码看起来像:

section .data
hello   db  "Hello, world", 0x0a, 0x00

section .text
default rel

global _main
extern _printf, _exit

_main:
    sub esp, 12           ; 16-byte align stack + room for parameters passed
                          ; to functions we call
    mov [esp],dword hello ; First parameter at esp+0
    call _printf

    mov [esp], dword 0    ; First parameter at esp+0
    call _exit

如果您想传递多个参数,请将它们手动放在堆栈上,就像我们使用单个参数一样。如果我们想要在printf调用中打印整数42,我们可以这样做:

section .data
hello   db  "Hello, world %d", 0x0a, 0x00

section .text
default rel

global _main
extern _printf, _exit

_main:
    sub esp, 12           ; 16-byte align stack + room for parameters passed
                          ; to functions we call

    mov [esp+4], dword 42 ; Second parameter at esp+4
    mov [esp],dword hello ; First parameter at esp+0
    call _printf

    mov [esp], dword 0    ; First parameter at esp+0
    call _exit

运行时我们应该得到:

  

你好,世界42

16字节堆栈对齐和堆栈帧

如果您要创建具有典型堆栈帧的函数,则必须调整上一节中的代码。在进入32位应用程序中的函数时,堆栈未对齐4个字节,因为返回地址被放置在堆栈上。典型的堆栈框架序言如下:

push ebp
mov  ebp, esp

在输入函数后将 EBP 推入堆栈仍会导致堆栈错位,但现在错位8字节(4 + 4)。

因为代码必须从 ESP 而不是12中减去8.当确定保存参数所需的空间,本地堆栈变量和用于对齐的填充字节时,堆栈分配大小将具有可以被8整除,而不是12。具有堆栈框架的代码可能看起来像:

section .data
hello   db  "Hello, world %d", 0x0a, 0x00

section .text
default rel

global _main
extern _printf, _exit

_main:
    push ebp
    mov ebp, esp          ; Set up stack frame
    sub esp, 8            ; 16-byte align stack + room for parameters passed
                          ; to functions we call

    mov [esp+4], dword 42 ; Second parameter at esp+4
    mov [esp],dword hello ; First parameter at esp+0
    call _printf

    xor eax, eax          ; Return value = 0
    mov esp, ebp
    pop ebp               ; Remove stack frame
    ret                   ; We linked with C library that calls _main
                          ; after initialization. We can do a RET to
                          ; return back to the C runtime code that will
                          ; exit the program and return the value in EAX
                          ; We can do this instead of calling _exit

因为您与OS / X上的 C 库链接,它将提供一个入口点并在调用_main之前进行初始化。您可以调用_exit,但也可以使用 EAX 中程序的返回值执行 RET 指令。

另一个潜在的NASM Bug?

我发现 El Capitan 上通过MacPorts安装的 NASM v2.12似乎会为_printf和{{1}生成错误的重定位条目当链接到最终的可执行文件时,代码没有按预期工作。我观察到你用原始代码几乎完全相同的错误。

我的答案的第一部分仍然适用于堆栈对齐,但是看起来您还需要解决 NASM 问题。一种方法是安装最新的XCode命令行工具附带的 NASM 。这个版本更老了,只支持Macho-32,并且不支持_exit指令。使用我以前的堆栈对齐代码,这应该有效:

default

要与 NASM 组合并与 LD 链接,您可以使用:

section .data
hello   db  "Hello, world %d", 0x0a, 0x00

section .text
;default rel              ; This directive isn't supported in older versions of NASM

global _main
extern _printf, _exit

_main:
    sub esp, 12           ; 16-byte align stack
    mov [esp+4], dword 42 ; Second parameter at esp+4
    mov [esp],dword hello ; First parameter at esp+0
    call _printf

    mov [esp], dword 0    ; First parameter at esp+0
    call _exit

或者您可以链接 GCC

/usr/bin/nasm -f macho hello32x.asm -o hello32x.o
ld -macosx_version_min 10.8 -no_pie -arch i386 -o hello32x hello32x.o -lc 

/usr/bin/nasm -f macho hello32x.asm -o hello32x.o gcc -m32 -Wl,-no_pie -o hello32x hello32x.o 是Apple分发的 NASM 的XCode命令行工具版本的位置。我使用最新的XCode命令行工具在 El Capitan 上的版本是:

  

2016年1月14日编译的NASM版本0.98.40(Apple Computer,Inc。build 11)

我不推荐 NASM 版本2.11.08,因为它有一个与macho64格式相关的serious bug。我推荐2.11.09rc2。我在这里测试了这个版本,它似乎与上面的代码一起正常工作。