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-asllc之类的也不行。在另一台机器上编译也不行。这让我相信我的机器或 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).

之后释放分配的蹦床