分段错误 11 链接 os x 32 位汇编程序 [英] Segmentation Fault 11 linking os x 32-bit assembler

查看:26
本文介绍了分段错误 11 链接 os x 32 位汇编程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新:果然是最新版nasm的一个bug.我降级"并按照我接受的答案中所示修复了我的代码后,一切正常.谢谢大家!

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

一、代码:

section .data你好数据库你好,世界",0x0a,0x00节.text默认关系全局_main外部_printf,_exit_主要的:子 esp, 12 ;16 字节对齐堆栈推你好调用_printf推0呼叫 _exit

它会组装和链接,但是当我运行可执行文件时,它会因分段错误而崩溃:11.

组装和链接的命令行是:

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

我知道 -o 不是 100% 必要的

链接:

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

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

 (lldb) s进程 1029 停止*线程#1: tid = 0x97a4, 0x00001fac hello32x`main + 8, queue = 'com.apple.main-thread', 停止原因 = 指令步入帧 #0:0x00001fac hello32x`main + 8你好32x`主要:->0x1fac <+8>:调用 0xffffffff991e381e0x1fb1 <+13>:pushl $0x00x1fb3 <+15>:调用 0xffffffff991fec840x1fb8: addl %eax, (%eax)(lldb) s进程 1029 停止*线程#1:tid = 0x97a4, 0x991e381e libsystem_c.dylib`vfprintf + 49, queue = 'com.apple.main-thread', stop reason = 指令步入帧 #0:0x991e381e libsystem_c.dylib`vfprintf + 49libsystem_c.dylib`vfprintf:->0x991e381e <+49>: xchgb %ah, -0x76f580080x991e3824 <+55>:popl %esp0x991e3825 <+56>: andb $0x14, %al0x991e3827 <+58>: movl 0xc(%ebp), %ecx(lldb) s进程 1029 停止*线程#1:tid = 0x97a4, 0x991e381e libsystem_c.dylib`vfprintf + 49, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x890a7ff8)帧 #0:0x991e381e libsystem_c.dylib`vfprintf + 49libsystem_c.dylib`vfprintf:->0x991e381e <+49>: xchgb %ah, -0x76f580080x991e3824 <+55>:popl %esp0x991e3825 <+56>: andb $0x14, %al0x991e3827 <+58>: movl 0xc(%ebp), %ecx

正如您在底部看到的那样,它由于访问错误而停止.

解决方案

16-byte Stack Alignment

您的代码的一个严重问题是堆栈对齐.32 位 OS/X 代码在您进行 CALL 时需要 16 字节堆栈对齐.Apple IA-32 调用约定 是这样说的:

<块引用>

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

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

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

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

section .data你好数据库你好,世界",0x0a,0x00节.text默认关系全局_main外部_printf,_exit_主要的:子 esp, 8推你好;4(返回地址)+ 8 + 4 = 16 字节堆栈对齐调用_printf添加 esp, 4 ;删除参数推 0 ;4 + 8 + 4 = 16 字节再次对齐调用 _exit ;这将不会返回,因此无需在之后删除参数

上述代码有效是因为我们可以利用两个函数(exitprintf)都需要放置一个 DWORD 的事实在参数堆栈上.main 的返回地址为 4 个字节,我们进行的堆栈调整为 8 个字节,DWORD 参数为 4 个字节 = 16 字节对齐.

<小时>

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

现在可以将参数直接移到堆栈中,而不是使用 PUSH 将参数放入堆栈中,从而进入我们保留的空间.因为我们没有 PUSH 堆栈不会错位.由于我们没有使用 PUSH,我们不需要在函数调用后修复 ESP.代码可能如下所示:

section .data你好数据库你好,世界",0x0a,0x00节.text默认关系全局_main外部_printf,_exit_主要的:子 esp, 12 ;16 字节对齐堆栈 + 传递参数的空间;我们调用的函数mov [esp],dword hello ;esp+0 处的第一个参数调用_printfmov [esp], 双字 0 ;esp+0 处的第一个参数呼叫 _exit

如果你想传递多个参数,你可以手动将它们放在堆栈上,就像我们对单个参数所做的那样.如果我们想在调用 printf 时打印一个整数 42,我们可以这样做:

section .data你好数据库你好,世界 %d", 0x0a, 0x00节.text默认关系全局_main外部_printf,_exit_主要的:子 esp, 12 ;16 字节对齐堆栈 + 传递参数的空间;我们调用的函数mov [esp+4], 双字 42 ;esp+4 处的第二个参数mov [esp],dword hello ;esp+0 处的第一个参数调用_printfmov [esp], 双字 0 ;esp+0 处的第一个参数呼叫 _exit

运行时我们应该得到:

<块引用>

你好,世界 42

<小时>

16 字节堆栈对齐和堆栈帧

如果您希望创建一个具有典型堆栈帧的函数,则必须调整上一节中的代码.在进入 32 位应用程序中的函数时,堆栈会错位 4 个字节,因为返回地址已放置在堆栈上.典型的堆栈帧序言如下所示:

push ebpmov ebp, esp

在进入您的函数后将 EBP 推入堆栈仍然会导致未对齐的堆栈,但现在已未对齐 8 个字节 (4 + 4).

因为代码必须从 ESP 中减去 8 而不是 12.此外,在确定保存参数、局部堆栈变量和用于对齐的填充字节所需的空间时,堆栈分配大小将具有可以被 8 整除,而不是被 12 整除.具有堆栈帧的代码可能如下所示:

section .data你好数据库你好,世界 %d", 0x0a, 0x00节.text默认关系全局_main外部_printf,_exit_主要的:推ebp移动 ebp, esp ;设置栈帧子 esp, 8 ;16 字节对齐堆栈 + 传递参数的空间;我们调用的函数mov [esp+4], 双字 42 ;esp+4 处的第二个参数mov [esp],dword hello ;esp+0 处的第一个参数调用_printf异或 eax, eax ;返回值 = 0mov esp, ebp流行 ebp ;移除堆栈帧退;我们链接了调用_main的C库;初始化后.我们可以做一个RET;返回到 C 运行时代码;退出程序并返回EAX中的值;我们可以这样做而不是调用 _exit

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

<小时>

又一个潜在的 NASM 错误?

我发现通过 MacPorts 似乎为 _printf_exit 生成不正确的重定位条目,并且链接到最终可执行文件时代码没有按预期工作.我观察到几乎与您对原始代码所做的相同的错误.

我的答案的第一部分仍然适用于堆栈对齐,但是您似乎还需要解决 NASM 问题.一种方法是安装最新的 XCode 命令行工具附带的 NASM.这个版本要旧得多,只支持 Macho-32,不支持 default 指令.使用我之前的堆栈对齐代码,这应该可以工作:

section .data你好数据库你好,世界 %d", 0x0a, 0x00节.text;默认关系;旧版本的 NASM 不支持此指令全局_main外部_printf,_exit_主要的:子 esp, 12 ;16 字节对齐堆栈mov [esp+4], 双字 42 ;esp+4 处的第二个参数mov [esp],dword hello ;esp+0 处的第一个参数调用_printfmov [esp], 双字 0 ;esp+0 处的第一个参数呼叫 _exit

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

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

或者,您可以与 GCC 链接:

/usr/bin/nasm -f macho hello32x.asm -o hello32x.ogcc -m32 -Wl,-no_pie -o hello32x hello32x.o

/usr/bin/nasm 是 Apple 分发的 NASM 的 XCode 命令行工具版本的位置.我在 El Capitan 上使用最新的 XCode 命令行工具的版本是:

<块引用>

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

我不推荐 NASM 2.11.08 版,因为它有一个严重的错误与macho64 格式有关.我推荐 2.11.09rc2.我已经在这里测试了那个版本,它似乎与上面的代码一起正常工作.

UPDATE: Sure enough, it was a bug in the latest version of nasm. I "downgraded" and after fixing my code as shown in the answer I accepted, everything is working properly. Thanks, everyone!

I'm having problems with what should be a very simple program in 32-bit assembler on OS X.

First, the code:

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

It assembles and links, but when I run the executable it crashes with a segmentation fault: 11.

The command lines to assemble and link are:

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

I know the -o there is not 100 percent necessary

Linking:

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

When I run it into lldb to debug it, everything is fine until it enters into the call to _printf, where it crashes as shown below:

  (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

As you can see toward the bottom, it stops due to a bad access error.

解决方案

16-byte Stack Alignment

One serious issue with your code is stack alignment. 32-bit OS/X code requires 16-byte stack alignment at the point you make a CALL. The Apple IA-32 Calling Convention says this:

The function calling conventions used in the IA-32 environment are the same as those used in the System V IA-32 ABI, with the following exceptions:

  • Different rules for returning structures
  • The stack is 16-byte aligned at the point of function calls
  • Large data types (larger than 4 bytes) are kept at their natural alignment
  • Most floating-point operations are carried out using the SSE unit instead of the x87 FPU, except when operating on long double values. (The IA-32 environment defaults to 64-bit internal precision for the x87 FPU.)

You subtract 12 from ESP to align the stack to a 16 byte boundary (4 bytes for return address + 12 = 16). The problem is that when you make a CALL to a function the stack MUST be 16 bytes aligned just prior to the CALL itself. Unfortunately you push 4 bytes before the call to printf and exit. This misaligns the stack by 4, when it should be aligned to 16 bytes. You'll have to rework the code with proper alignment. As well you must clean up the stack after you make a call. If you use PUSH to put parameters on the stack you need to adjust ESP after your CALL to restore the stack to its previous state.

One naive way (not my recommendation) to fix the code would be to do this:

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

The code above works because we can take advantage of the fact that both functions (exit and printf) require exactly one DWORD being placed on the stack for parameters. 4 bytes for main's return address, 8 for the stack adjustment we made, 4 for the DWORD parameter = 16 byte alignment.


A better way to do this is to compute the amount of stack space you will need for all your stack based local variables (in this case 0) in your main function, plus the maximum number of bytes you will need for any parameters to function calls made by main and then make sure you pad enough bytes to make the value evenly divisible by 12. In our case the maximum number of bytes needed to be pushed for any one given function call is 4 bytes. We then add 8 to 4 (8+4=12) to become evenly divisible by 12. We then subtract 12 from ESP at the start of our function.

Instead of using PUSH to put parameters on the stack you can now move the parameters directly onto the stack into the space we have reserved. Because we don't PUSH the stack doesn't get misaligned. Since we didn't use PUSH we don't need to fix ESP after our function calls. The code could then look something like:

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

If you wanted to pass multiple parameters you place them manually on the stack as we did with a single parameter. If we wanted to print an integer 42 as part of our call to printf we could do it this way:

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

When run we should get:

Hello, world 42


16-byte Stack Alignment and a Stack Frame

If you are looking to create a function with a typical stack frame then the code in the previous section has to be adjusted. Upon entry to a function in a 32-bit application the stack is misaligned by 4 bytes because the return address was placed on the stack. A typical stack frame prologue looks like:

push ebp
mov  ebp, esp

Pushing EBP into the stack after entry to your function still results in a misaligned stack, but it is misaligned now by 8 bytes (4 + 4).

Because of that the code must subtract 8 from ESP rather than 12. As well when determining the space needed to hold parameters, local stack variables, and pad bytes for alignment the stack allocation size will have to be evenly divisible by 8, not by 12. Code with a stack frame could look like:

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

Because you link with the C library on OS/X it will provide an entry point and do initialization before calling _main. You can call _exit but you can also do a RET instruction with the program's return value in EAX.


Yet Another Potential NASM Bug?

I discovered that NASM v2.12 installed via MacPorts on El Capitan seems to generate incorrect relocation entries for _printf and _exit, and when linked to a final executable the code doesn't work as expected. I observed almost the identical errors you did with your original code.

The first part of my answer still applies about stack alignment, however it appears you will need to work around the NASM issue as well. One way to do this install the NASM that comes with the latest XCode command line tools. This version is much older and only supports Macho-32, and doesn't support the default directive. Using my previous stack aligned code this should work:

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

To assemble with NASM and link with LD you could use:

/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 

Alternatively you could link with GCC:

/usr/bin/nasm -f macho hello32x.asm -o hello32x.o
gcc -m32 -Wl,-no_pie -o hello32x hello32x.o

/usr/bin/nasm is the location of the XCode command line tools version of NASM that Apple distributes. The version I have on El Capitan with latest XCode command line tools is:

NASM version 0.98.40 (Apple Computer, Inc. build 11) compiled on Jan 14 2016

I don't recommend NASM version 2.11.08 because it has a serious bug related to macho64 format. I recommend 2.11.09rc2. I have tested that version here and it does seem to work properly with the code above.

这篇关于分段错误 11 链接 os x 32 位汇编程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆