LLVM解释器在哪里查找外部函数(库)?

3

我正在研究LLVM IR,但无法通过谷歌和文档解决一个问题:LLVM解释器lli在哪里查找外部(未显式定义的函数,即基本系统函数)。例如,如果我想编写一个不依赖其他库,在Linux上可以将一些内容输出到屏幕上的简单程序,我可以像这样做:

@message = private constant [12 x i8] c"hello world\0A"
define i32 @puts(i8* %s) {
    call i32 asm sideeffect "movl $$0x2, %edi\0Amovl $$0xC, %edx\0Amovl$$1, %eax\0Asyscall\0A", "=A,{si}"(i8* %s) #1
    ret i32 %1
}
define void @exit(i32 %c) {
    call i32 asm sideeffect "movl $$60, %eax\0Asyscall\0A", "=A,{di}"(i32 %c) #1
    ret void
}
define void @main() {
    getelementptr [12 x i8], [12 x i8]* @message, i64 0, i64 0
    call i32 @puts(i8* %1)
    call void @exit(i32 0)
    ret void
}
define void @_start() {
    call void @main()
    ret void
}

main_start是为了与ldlli的交叉兼容而存在的。因此,以上代码在两者中都可以正常工作。我可以使用lli来运行它,代码将从main开始执行;或者使用llc编译,然后用ld链接,同样会工作,并且代码将从预期的_start开始执行。现在,如果我写下以下代码:

@formatString = private constant [4 x i8] c"%d\0A\00" 
declare i32 @printf(i8*, ...)
define i32 @main() {
    %d = shl i32 2, 3
    %s = getelementptr [4 x i8], [4 x i8]* @formatString, i64 0, i64 0
    %call = call i32 (i8*, ...) @printf(i8* %s, i32 %d)
    ret i32 0
}

它 在 lli 中也可以工作,但我无法使用 ld 来对其进行链接,因为 ld 不知道引用的 printf。预处理一下 ld 参数可以让代码工作,或者直接使用 gcc file.o -o file,但这不是我的重点。我的重点是如何使 lli 不包含任何外部库(比如 libc),仅运行我的代码,并且可能定义自己的入口点,这样我就可以根据需要包含任何准备好的 libc 或其他库,我知道我可以重写函数名称来达到这个目的,但那样我就无法确定哪些内容已被重写,所以如果 printf 被使用但未定义,我会很高兴看到 lli 抛出错误。或者我可能理解错了,lli 不能在这种裸环境中运行。

1
我发现使用 lli -entry-function=_start file.ll 可以选择任何入口点。但是我仍然不知道如何剥离预加载代码的解释器。有一个选项 -extra-object=<object>,但我找不到禁用默认加载的所有对象的选项。 - mucka
1个回答

3
TL;DR: 使用不带JIT的lli,应该可以正常工作:
lli -force-interpreter main.bc

更多信息:
总的来说,这取决于你在底层使用的JIT引擎类型(如果有的话)。
我无法谈论MCJIT(-jit-kind=mcjit),因为我不熟悉它,但我可以向您保证,如果您使用ORC JIT(-jit-kind=orc-mcjit-jit-kind=orc-lazy),那么这是不可能的。然而,这仅适用于lli,如果您决定自己使用ORC,则可以控制此行为。
lli的上下文中,ORC不仅加载您的模块/外部对象,还加载整个程序的地址空间。这意味着您获得了所有的libc和整个LLVM本身。
根据您的需求,您可以尝试将lli真正用作解释器,尽管速度会变慢,因为不再涉及JIT。
只需添加-force-interpreter选项,您就可以开始使用了,但有一些例外情况。这些函数仍将正常执行:
void Interpreter::initializeExternalFunctions() {
  sys::ScopedLock Writer(*FunctionsLock);
  (*FuncNames)["lle_X_atexit"]       = lle_X_atexit;
  (*FuncNames)["lle_X_exit"]         = lle_X_exit;
  (*FuncNames)["lle_X_abort"]        = lle_X_abort;

  (*FuncNames)["lle_X_printf"]       = lle_X_printf;
  (*FuncNames)["lle_X_sprintf"]      = lle_X_sprintf;
  (*FuncNames)["lle_X_sscanf"]       = lle_X_sscanf;
  (*FuncNames)["lle_X_scanf"]        = lle_X_scanf;
  (*FuncNames)["lle_X_fprintf"]      = lle_X_fprintf;
  (*FuncNames)["lle_X_memset"]       = lle_X_memset;
  (*FuncNames)["lle_X_memcpy"]       = lle_X_memcpy;
}

很不幸,我已经尝试过了,也许问题在于我正在使用直接从官方 git 获取的 LLVM 版本 5.0.0svn。我担心的是,问题可能是我可以访问解释器为其预加载的所有函数。无论如何,感谢您的回答! - mucka
我在想是否可以使用 mcjit-remote-process 实现更加简洁的环境,但是“标准”的远程进程 https://github.com/llvm-mirror/llvm/blob/master/tools/lli/ChildTarget/ChildTarget.cpp 看起来并不可行。 - mucka
我建议您设置自定义JIT堆栈。非常基本的自定义lli相当容易且小巧。如果需要帮助,请随时给我发电子邮件1101.debian@gmail.com - AlexDenisov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接