1

在阅读了使用蹦床在 LLVM 中生成闭包之后,我尝试编译一些在互联网上漂浮的蹦床示例(特别是这个)。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))。

经过一些测试,事实证明“蹦床”函数的调用是导致 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
4

2 回答 2

3

好吧,一年多后,在@user855 的帮助下,我终于有了一个工作示例。

正如 user855 在评论中指出的那样,代码失败是因为用于存储蹦床的内存不可执行。这可以通过使用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).

于 2016-07-06T11:16:42.087 回答
2

这是完全可以预料的。LLVM trampoline 内在函数并不是真正用于随机前端。

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

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

该示例甚至没有说明它应该用于什么目标,更不用说自从它针对任何 LLVM 版本编写以来事情可能发生了怎样的变化,等等。

于 2015-06-06T22:30:19.207 回答