macos之Segmentation Fault 11链接OS X 32位汇编器

softidea 阅读:97 2025-01-19 22:14:33 评论:0

更新:当然,这是最新版本的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 

正如您看到的那样,它由于访问错误而停止。

请您参考如下方法:

16字节堆栈对齐

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

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


  • 返回结构的不同规则
  • 在函数调用
  • 的位置, 堆栈是16字节对齐的
  • 大型数据类型(大于4个字节)保持其自然对齐
  • 除了对长double值进行运算时,大多数浮点运算都是使用SSE单元而不是x87 FPU进行的。 (对于x87 FPU,IA-32环境默认为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个字节为16字节对齐。

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

    现在,您无需使用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中减去8,而不是12。此外,在确定保存参数,局部堆栈变量和填充字节以对齐所需的空间时,堆栈分配大小必须被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错误?

    我发现通过El Capitan上的 MacPorts安装的NASM v2.12似乎为 _printf_exit生成了不正确的重定位条目,并且当链接到最终可执行文件时,该代码无法按预期工作。我观察到几乎与原始代码相同的错误。

    我的答案的第一部分仍然适用于堆栈对齐,但是看来您也需要解决NASM问题。一种执行此方法的方法是安装最新XCode命令行工具随附的NASM。该版本较旧,仅支持Macho-32,不支持 default指令。使用我之前的堆栈对齐代码,这应该可以工作:
    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 
    

    要与NASM组装并与LD链接,可以使用:
    /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  
    

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

    NASM版本0.98.40(Apple Computer,Inc.内部版本11)于2016年1月14日编译

    我不建议使用NASM版本2.11.08,因为它具有与macho64格式有关的 serious bug。我推荐 2.11.09rc2。我已经在这里测试了该版本,它似乎可以与上面的代码一起正常工作。


    标签:程序员
    声明

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    关注我们

    一个IT知识分享的公众号