LLVM Trampoline导致SIGSEGV?

时间:2015-06-06 22:24:04

标签: closures clang llvm sigsegv trampolines

在阅读了使用蹦床在LLVM中生成闭包之后,我试着编写了一些漂浮在互联网上的蹦床的例子(特别是this one)。要点中给出的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" function是导致SIGSEGV的指令,因为注释掉(并返回一个虚拟值)工作正常。

问题似乎不在于clang,因为手动运行llvm-asllc等也不起作用。在另一台机器上编译也无法正常工作。这让我相信我的机器或LLVM做错了什么。

我的铿锵版:

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

2 个答案:

答案 0 :(得分:2)

好吧,一年多以后,在@ user855的帮助下,我终于有了一个有效的例子。

正如用户855在评论中指出的那样,代码失败是因为用于存储蹦床的内存不可执行。这可以通过使用mmap来分配可执行内存来避开(请注意,这不是堆栈中的内存,而不是之前的内存)。

代码:

declare void @llvm.init.trampoline(i8*, i8*, i8*)
declare i8* @llvm.adjust.trampoline(i8*)
declare i8* @"\01_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* @"\01_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函数名称为"\01_mmap",它可能与您的不同。要检查,只需使用clang -S -emit-llvm编译一些代码并记下mmap调用。

另一个有趣的说明是,此代码需要使用munmap(ptr, 72)释放分配的蹦床。

答案 1 :(得分:1)

完全可以预料到这一点。 LLVM蹦床内在函数并不适用于随机前端使用。

  

tramp参数必须指向足够大且充分对齐的内存块;这个记忆是由内在写的。请注意,大小和对齐是特定于目标的 - LLVM目前不提供确定它们的可移植方式,因此生成此内在函数的前端需要具有一些特定于目标的知识。

这基本上意味着你无法编写保证工作的trampoline指令的使用。您无法从互联网上随机抽取样本。您需要深入了解如何为您的特定目标实施trampoline

该示例甚至没有说出它应该用于什么目标,更不用说自从写入的LLVM版本以来事情可能发生了什么变化等等。