分段错误 11 链接 os x 32 位汇编器
Segmentation Fault 11 linking os x 32-bit assembler
更新:果然,这是 nasm 最新版本中的一个错误。我 "downgraded" 并按照我接受的答案中所示修复我的代码后,一切正常。谢谢大家!
我在 OS X 上的 32 位 assembler 中应该是一个非常简单的程序时遇到问题。
一、代码:
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
它 assembles 和 links,但是当我 运行 可执行文件崩溃时出现分段错误:11.
assemble 和 link 的命令行是:
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 [=14=]x0
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 [=14=]x14, %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 [=14=]x14, %al
0x991e3827 <+58>: movl 0xc(%ebp), %ecx
正如您在底部看到的那样,它因访问错误而停止。
16 字节堆栈对齐
您的代码的一个严重问题是堆栈对齐。 32 位 OS/X 代码在您进行 CALL 时需要 16 字节堆栈对齐。 Apple IA-32 Calling Convention 表示:
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.)
您从 ESP 中减去 12 以将堆栈对齐到 16 字节边界(4 字节用于 return 地址 + 12 = 16)。问题是,当您对函数进行 CALL 时,堆栈必须在 CALL 本身之前对齐 16 字节。不幸的是,您在调用 printf
和 exit
之前压入了 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
上面的代码之所以有效,是因为我们可以利用以下事实:两个函数(exit
和 printf
)都只需要一个 DWORD参数的堆栈。 4 个字节用于 main
的 return 地址,8 个用于我们进行的堆栈调整,4 个用于 DWORD 参数 = 16 字节对齐。
更好的方法是计算 main
函数中所有基于堆栈的局部变量(在本例中为 0)所需的堆栈数量 space,加上main
进行的函数调用的任何参数所需的最大字节数,然后确保填充足够的字节以使该值可以被 12 整除。在我们的例子中,需要推送的最大字节数任何一个给定的函数调用都是 4 个字节。然后我们将 8 加到 4 (8+4=12) 以被 12 整除。然后我们在函数开始时从 ESP 中减去 12。
您现在可以将参数直接移动到我们保留的 space 中,而不是使用 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
当 运行 我们应该得到:
Hello, world 42
16 字节堆栈对齐和堆栈帧
如果您要创建具有典型堆栈帧的函数,则必须调整上一节中的代码。在进入 32 位应用程序中的函数时,堆栈未对齐 4 个字节,因为 return 地址被放置在堆栈上。典型的栈帧序言如下所示:
push ebp
mov ebp, esp
在进入您的函数后将 EBP 压入堆栈仍然会导致堆栈未对齐,但现在已错位 8 个字节 (4 + 4)。
因此,代码必须从 ESP 中减去 8 而不是 12。在确定 space 需要保存参数、局部堆栈变量和填充时用于对齐的字节 堆栈分配大小必须能被 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
因为您 link 使用 OS/X 上的 C 库,它将提供一个入口点并在调用 _main
之前进行初始化。您可以调用 _exit
,但您也可以使用 EAX 中程序的 return 值执行 RET 指令。
又一个潜在的 NASM 错误?
我发现 NASM v2.12 通过 MacPorts 安装在 El Capitan 上似乎生成不正确_printf
和 _exit
的重定位条目,当 linked 到最终的可执行文件时,代码无法按预期工作。我观察到与您使用原始代码所做的错误几乎相同。
我的回答的第一部分仍然适用于堆栈对齐,但看来您还需要解决 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
assemble 与 NASM 和 link 与 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
或者您可以 link 使用 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 version 0.98.40 (Apple Computer, Inc. build 11) compiled on Jan 14 2016
我不推荐 NASM 版本 2.11.08 因为它有一个 related to macho64 format. I recommend 2.11.09rc2。我已经在此处测试了该版本,它似乎与上面的代码一起正常工作。
更新:果然,这是 nasm 最新版本中的一个错误。我 "downgraded" 并按照我接受的答案中所示修复我的代码后,一切正常。谢谢大家!
我在 OS X 上的 32 位 assembler 中应该是一个非常简单的程序时遇到问题。
一、代码:
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
它 assembles 和 links,但是当我 运行 可执行文件崩溃时出现分段错误:11.
assemble 和 link 的命令行是:
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 [=14=]x0
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 [=14=]x14, %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 [=14=]x14, %al
0x991e3827 <+58>: movl 0xc(%ebp), %ecx
正如您在底部看到的那样,它因访问错误而停止。
16 字节堆栈对齐
您的代码的一个严重问题是堆栈对齐。 32 位 OS/X 代码在您进行 CALL 时需要 16 字节堆栈对齐。 Apple IA-32 Calling Convention 表示:
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.)
您从 ESP 中减去 12 以将堆栈对齐到 16 字节边界(4 字节用于 return 地址 + 12 = 16)。问题是,当您对函数进行 CALL 时,堆栈必须在 CALL 本身之前对齐 16 字节。不幸的是,您在调用 printf
和 exit
之前压入了 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
上面的代码之所以有效,是因为我们可以利用以下事实:两个函数(exit
和 printf
)都只需要一个 DWORD参数的堆栈。 4 个字节用于 main
的 return 地址,8 个用于我们进行的堆栈调整,4 个用于 DWORD 参数 = 16 字节对齐。
更好的方法是计算 main
函数中所有基于堆栈的局部变量(在本例中为 0)所需的堆栈数量 space,加上main
进行的函数调用的任何参数所需的最大字节数,然后确保填充足够的字节以使该值可以被 12 整除。在我们的例子中,需要推送的最大字节数任何一个给定的函数调用都是 4 个字节。然后我们将 8 加到 4 (8+4=12) 以被 12 整除。然后我们在函数开始时从 ESP 中减去 12。
您现在可以将参数直接移动到我们保留的 space 中,而不是使用 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
当 运行 我们应该得到:
Hello, world 42
16 字节堆栈对齐和堆栈帧
如果您要创建具有典型堆栈帧的函数,则必须调整上一节中的代码。在进入 32 位应用程序中的函数时,堆栈未对齐 4 个字节,因为 return 地址被放置在堆栈上。典型的栈帧序言如下所示:
push ebp
mov ebp, esp
在进入您的函数后将 EBP 压入堆栈仍然会导致堆栈未对齐,但现在已错位 8 个字节 (4 + 4)。
因此,代码必须从 ESP 中减去 8 而不是 12。在确定 space 需要保存参数、局部堆栈变量和填充时用于对齐的字节 堆栈分配大小必须能被 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
因为您 link 使用 OS/X 上的 C 库,它将提供一个入口点并在调用 _main
之前进行初始化。您可以调用 _exit
,但您也可以使用 EAX 中程序的 return 值执行 RET 指令。
又一个潜在的 NASM 错误?
我发现 NASM v2.12 通过 MacPorts 安装在 El Capitan 上似乎生成不正确_printf
和 _exit
的重定位条目,当 linked 到最终的可执行文件时,代码无法按预期工作。我观察到与您使用原始代码所做的错误几乎相同。
我的回答的第一部分仍然适用于堆栈对齐,但看来您还需要解决 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
assemble 与 NASM 和 link 与 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
或者您可以 link 使用 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 version 0.98.40 (Apple Computer, Inc. build 11) compiled on Jan 14 2016
我不推荐 NASM 版本 2.11.08 因为它有一个