最小 64 位 Windows 可执行文件因 gcc 启用的尾调用优化而崩溃
Minimal 64-bit Windows executable crashes with tail-call optimization enabled by gcc
我正在尝试创建一个最小的 64 位 Windows 可执行文件,以更好地理解 Windows 可执行文件格式的工作原理。
我写了如下非常基本的汇编和 C 代码。
hi.s
section .text
hi:
db "hi", 0
global sayHi
align 16
sayHi:
lea rax, [rel hi]
ret
start.c
extern int puts();
extern const char *sayHi();
void start() {
puts(sayHi());
}
编译为
nasm -fwin64 hi.s
gcc -c -ostart.obj -O3 -fno-optimize-sibling-calls start.c
# I will explain the flag
并与
链接
golink /fo r.exe /console start.obj hi.obj msvcrt.dll
# create a console application `r.exe`
# the default entry point is `start`
程序运行良好并打印 hi
,但请注意 gcc
标志 -fno-optimize-sibling-calls
。该标志禁用尾调用优化,以便程序始终分配堆栈 space 和 call
s 一个函数。没有标志,程序会崩溃。
这是没有尾调用优化的反汇编结果。不知道为什么 gcc
放一个 nop
在那里,但除此之外它非常简单并且运行良好。
0000000000401000 <.text>:
401000: 48 83 ec 28 sub rsp,0x28
401004: e8 27 00 00 00 call 0x401030 # sayHi
401009: 48 89 c1 mov rcx,rax
40100c: e8 ff 2f 00 00 call 0x404010 # puts
401011: 90 nop
401012: 48 83 c4 28 add rsp,0x28
401016: c3 ret
...
401020: 68 69 00 90 90 push 0xffffffff90900069 # "hi"
...
401030: 48 8d 05 e9 ff ff ff lea rax,[rip+0xffffffffffffffe9] # 0x401020
401037: c3 ret
这是启用尾调用选项时,程序崩溃。
0000000000401000 <.text>:
401000: 48 83 ec 28 sub rsp,0x28
401004: e8 27 00 00 00 call 0x401030 # sayHi
401009: 48 89 c1 mov rcx,rax
40100c: 48 83 c4 28 add rsp,0x28
401010: e9 eb 2f 00 00 jmp 0x404000 # puts
...
401020: 68 69 00 90 90 push 0xffffffff90900069 # "hi"
...
401030: 48 8d 05 e9 ff ff ff lea rax,[rip+0xffffffffffffffe9] # 0x401020
401037: c3 ret
现在程序不会在 puts
之前分配堆栈 space,而只是执行 jmp
而不是 call
。
我进一步调查以查看在调用 puts
.
时它究竟跳转到哪里
在无尾调用的情况下,.idata
段中被调用的地址0x404010
有指令jmp QWORD PTR [rip+0xffffffffffffffea] # 0x404000
,而0x404000
似乎包含地址至 puts
.
然而在尾调用的情况下,被调用的地址0x404000
有54 40 00 00
,这是没有意义的指令。调试器说程序在 0x404003
处出现段错误,所以我很确定程序在尝试执行垃圾指令时会卡住。
我一定是做错了什么,但我不确定是哪一个,所以你能解释一下尾调用案例失败的原因以及如何让它发挥作用吗?
问题是 golink
没有正确处理 tail-calls。我搜索了一段时间,使 GNU ld
link 程序具有与 golink
.
相同的选项
您可以使用此命令创建 GNU ld
console-mode Windows 可执行文件。
ld -o... --subsystem=console object-files...
--subsystem console
或-subsystem=console
也是同一个意思。使用 --subsystem=windows
创建 GUI 应用程序。
GNU ld
也处理 Windows dll
文件,所以在这种情况下,只需从系统文件夹中给 ld
一个 msvcrt.dll
的副本就可以了.
我正在尝试创建一个最小的 64 位 Windows 可执行文件,以更好地理解 Windows 可执行文件格式的工作原理。
我写了如下非常基本的汇编和 C 代码。
hi.s
section .text
hi:
db "hi", 0
global sayHi
align 16
sayHi:
lea rax, [rel hi]
ret
start.c
extern int puts();
extern const char *sayHi();
void start() {
puts(sayHi());
}
编译为
nasm -fwin64 hi.s
gcc -c -ostart.obj -O3 -fno-optimize-sibling-calls start.c
# I will explain the flag
并与
链接golink /fo r.exe /console start.obj hi.obj msvcrt.dll
# create a console application `r.exe`
# the default entry point is `start`
程序运行良好并打印 hi
,但请注意 gcc
标志 -fno-optimize-sibling-calls
。该标志禁用尾调用优化,以便程序始终分配堆栈 space 和 call
s 一个函数。没有标志,程序会崩溃。
这是没有尾调用优化的反汇编结果。不知道为什么 gcc
放一个 nop
在那里,但除此之外它非常简单并且运行良好。
0000000000401000 <.text>:
401000: 48 83 ec 28 sub rsp,0x28
401004: e8 27 00 00 00 call 0x401030 # sayHi
401009: 48 89 c1 mov rcx,rax
40100c: e8 ff 2f 00 00 call 0x404010 # puts
401011: 90 nop
401012: 48 83 c4 28 add rsp,0x28
401016: c3 ret
...
401020: 68 69 00 90 90 push 0xffffffff90900069 # "hi"
...
401030: 48 8d 05 e9 ff ff ff lea rax,[rip+0xffffffffffffffe9] # 0x401020
401037: c3 ret
这是启用尾调用选项时,程序崩溃。
0000000000401000 <.text>:
401000: 48 83 ec 28 sub rsp,0x28
401004: e8 27 00 00 00 call 0x401030 # sayHi
401009: 48 89 c1 mov rcx,rax
40100c: 48 83 c4 28 add rsp,0x28
401010: e9 eb 2f 00 00 jmp 0x404000 # puts
...
401020: 68 69 00 90 90 push 0xffffffff90900069 # "hi"
...
401030: 48 8d 05 e9 ff ff ff lea rax,[rip+0xffffffffffffffe9] # 0x401020
401037: c3 ret
现在程序不会在 puts
之前分配堆栈 space,而只是执行 jmp
而不是 call
。
我进一步调查以查看在调用 puts
.
在无尾调用的情况下,.idata
段中被调用的地址0x404010
有指令jmp QWORD PTR [rip+0xffffffffffffffea] # 0x404000
,而0x404000
似乎包含地址至 puts
.
然而在尾调用的情况下,被调用的地址0x404000
有54 40 00 00
,这是没有意义的指令。调试器说程序在 0x404003
处出现段错误,所以我很确定程序在尝试执行垃圾指令时会卡住。
我一定是做错了什么,但我不确定是哪一个,所以你能解释一下尾调用案例失败的原因以及如何让它发挥作用吗?
问题是 golink
没有正确处理 tail-calls。我搜索了一段时间,使 GNU ld
link 程序具有与 golink
.
您可以使用此命令创建 GNU ld
console-mode Windows 可执行文件。
ld -o... --subsystem=console object-files...
--subsystem console
或-subsystem=console
也是同一个意思。使用 --subsystem=windows
创建 GUI 应用程序。
GNU ld
也处理 Windows dll
文件,所以在这种情况下,只需从系统文件夹中给 ld
一个 msvcrt.dll
的副本就可以了.