LLVM Trampoline 导致 SIGSEGV?
LLVM Trampoline causing SIGSEGV?
在阅读了使用蹦床在 LLVM 中生成闭包之后,我尝试编译了一些在互联网上流传的蹦床示例(特别是 this one)。 gist中给出的LLVM IR如下:
declare void @llvm.init.trampoline(i8*, i8*, i8*);
declare i8* @llvm.adjust.trampoline(i8*);
define i32 @foo(i32* nest %ptr, i32 %val) {
%x = load i32* %ptr
%sum = add i32 %x, %val
ret i32 %sum
}
define i32 @main(i32, i8**) {
%closure = alloca i32
store i32 13, i32* %closure
%closure_ptr = bitcast i32* %closure to i8*
%tramp_buf = alloca [32 x i8], align 4
%tramp_ptr = getelementptr [32 x i8]* %tramp_buf, i32 0, i32 0
call void @llvm.init.trampoline(
i8* %tramp_ptr,
i8* bitcast (i32 (i32*, i32)* @foo to i8*),
i8* %closure_ptr)
%ptr = call i8* @llvm.adjust.trampoline(i8* %tramp_ptr)
%fp = bitcast i8* %ptr to i32(i32)*
%res = call i32 %fp (i32 13)
ret i32 %res
}
使用 clang trampolines.ll
编译并执行它,但是会导致 SIGSEGV
(fish 给出的确切错误是 fish: Job 1, './a.out ' terminated by signal SIGSEGV (Address boundary error)
)。
经过一些测试,发现 "trampolined" 函数的调用是导致 SIGSEGV 的指令,因为注释掉它(并返回一个虚拟值)工作正常。
问题好像也不出在clang
,因为手动运行llvm-as
、llc
之类的也不行。在另一台机器上编译也不行。这让我相信我的机器或 LLVM 做错了什么。
我的 clang 版本:
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix
这完全是预料之中的。 LLVM trampoline 内在函数并不是真正用于随机前端使用。
The tramp argument must point to a sufficiently large and sufficiently aligned block of memory; this memory is written to by the intrinsic. Note that the size and the alignment are target-specific - LLVM currently provides no portable way of determining them, so a front-end that generates this intrinsic needs to have some target-specific knowledge.
这基本上意味着您无法编写保证有效的 trampoline 指令的用法。您不能只是从 Internet 上随机抽取样本。您需要深入了解 trampoline
是如何针对您的特定目标实施的。
该示例甚至没有说明它应该用于什么目标,更不用说自从编写它所针对的任何 LLVM 版本以来事情可能发生了怎样的变化,等等。
好吧,一年多过去了,在@user855 的帮助下,我终于有了一个可用的例子。
正如 user855 在评论中指出的那样,代码失败是因为用于存储蹦床的内存不可执行。这可以通过使用 mmap
来分配可执行内存来规避(请注意,这不是堆栈上的内存,与之前相反)。
代码:
declare void @llvm.init.trampoline(i8*, i8*, i8*)
declare i8* @llvm.adjust.trampoline(i8*)
declare i8* @"_mmap"(i8*, i64, i32, i32, i32, i64)
define i32 @foo(i32* nest %ptr, i32 %val) {
%x = load i32, i32* %ptr
%sum = add i32 %x, %val
ret i32 %sum
}
define i32 @main(i32, i8**) {
%closure = alloca i32
store i32 13, i32* %closure
%closure_ptr = bitcast i32* %closure to i8*
%mmap_ptr = call i8* @"_mmap"(i8* null, i64 72, i32 7, i32 4098, i32 0, i64 0)
call void @llvm.init.trampoline(
i8* %mmap_ptr,
i8* bitcast (i32 (i32*, i32)* @foo to i8*),
i8* %closure_ptr)
%ptr = call i8* @llvm.adjust.trampoline(i8* %mmap_ptr)
%fp = bitcast i8* %ptr to i32(i32)*
%res = call i32 %fp (i32 13)
ret i32 %res
}
mmap调用参数如下:mmap(NULL, 72, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0)
。请注意,mmap 函数名称在我的平台上是 "_mmap"
,在您的平台上可能有所不同。要检查,只需使用 clang -S -emit-llvm
编译一些代码并记下 mmap
调用。
另一个有趣的注意事项是,此代码需要使用 munmap(ptr, 72)
.
之后释放分配的蹦床
在阅读了使用蹦床在 LLVM 中生成闭包之后,我尝试编译了一些在互联网上流传的蹦床示例(特别是 this one)。 gist中给出的LLVM IR如下:
declare void @llvm.init.trampoline(i8*, i8*, i8*);
declare i8* @llvm.adjust.trampoline(i8*);
define i32 @foo(i32* nest %ptr, i32 %val) {
%x = load i32* %ptr
%sum = add i32 %x, %val
ret i32 %sum
}
define i32 @main(i32, i8**) {
%closure = alloca i32
store i32 13, i32* %closure
%closure_ptr = bitcast i32* %closure to i8*
%tramp_buf = alloca [32 x i8], align 4
%tramp_ptr = getelementptr [32 x i8]* %tramp_buf, i32 0, i32 0
call void @llvm.init.trampoline(
i8* %tramp_ptr,
i8* bitcast (i32 (i32*, i32)* @foo to i8*),
i8* %closure_ptr)
%ptr = call i8* @llvm.adjust.trampoline(i8* %tramp_ptr)
%fp = bitcast i8* %ptr to i32(i32)*
%res = call i32 %fp (i32 13)
ret i32 %res
}
使用 clang trampolines.ll
编译并执行它,但是会导致 SIGSEGV
(fish 给出的确切错误是 fish: Job 1, './a.out ' terminated by signal SIGSEGV (Address boundary error)
)。
经过一些测试,发现 "trampolined" 函数的调用是导致 SIGSEGV 的指令,因为注释掉它(并返回一个虚拟值)工作正常。
问题好像也不出在clang
,因为手动运行llvm-as
、llc
之类的也不行。在另一台机器上编译也不行。这让我相信我的机器或 LLVM 做错了什么。
我的 clang 版本:
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix
这完全是预料之中的。 LLVM trampoline 内在函数并不是真正用于随机前端使用。
The tramp argument must point to a sufficiently large and sufficiently aligned block of memory; this memory is written to by the intrinsic. Note that the size and the alignment are target-specific - LLVM currently provides no portable way of determining them, so a front-end that generates this intrinsic needs to have some target-specific knowledge.
这基本上意味着您无法编写保证有效的 trampoline 指令的用法。您不能只是从 Internet 上随机抽取样本。您需要深入了解 trampoline
是如何针对您的特定目标实施的。
该示例甚至没有说明它应该用于什么目标,更不用说自从编写它所针对的任何 LLVM 版本以来事情可能发生了怎样的变化,等等。
好吧,一年多过去了,在@user855 的帮助下,我终于有了一个可用的例子。
正如 user855 在评论中指出的那样,代码失败是因为用于存储蹦床的内存不可执行。这可以通过使用 mmap
来分配可执行内存来规避(请注意,这不是堆栈上的内存,与之前相反)。
代码:
declare void @llvm.init.trampoline(i8*, i8*, i8*)
declare i8* @llvm.adjust.trampoline(i8*)
declare i8* @"_mmap"(i8*, i64, i32, i32, i32, i64)
define i32 @foo(i32* nest %ptr, i32 %val) {
%x = load i32, i32* %ptr
%sum = add i32 %x, %val
ret i32 %sum
}
define i32 @main(i32, i8**) {
%closure = alloca i32
store i32 13, i32* %closure
%closure_ptr = bitcast i32* %closure to i8*
%mmap_ptr = call i8* @"_mmap"(i8* null, i64 72, i32 7, i32 4098, i32 0, i64 0)
call void @llvm.init.trampoline(
i8* %mmap_ptr,
i8* bitcast (i32 (i32*, i32)* @foo to i8*),
i8* %closure_ptr)
%ptr = call i8* @llvm.adjust.trampoline(i8* %mmap_ptr)
%fp = bitcast i8* %ptr to i32(i32)*
%res = call i32 %fp (i32 13)
ret i32 %res
}
mmap调用参数如下:mmap(NULL, 72, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0)
。请注意,mmap 函数名称在我的平台上是 "_mmap"
,在您的平台上可能有所不同。要检查,只需使用 clang -S -emit-llvm
编译一些代码并记下 mmap
调用。
另一个有趣的注意事项是,此代码需要使用 munmap(ptr, 72)
.