LLVM JIT教程代码崩溃与简单的参数化函数.为什么
LLVM JIT tutorial code crashes with simple parameterized function. Why?
我正在尝试学习LLVM基础设施的方法。我已经在Windows上安装了LLVM二进制文件。
我正在遵循在LLVM网站上找到的关于所谓万花筒语言的教程。我有一个源文件,有完全代码清单在本页的末尾。
此外,如果它很重要,我正在使用以下标志(通过llvm-config
提前获得,因为Windows shell没有非常舒适的替换语法):
clang++ -g -O3 kaleido.cpp -o kaleido.exe -IC:/MinGW/include -DNDEBUG -D__NO_CTYPE_INLINE -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -LC:/MinGW/lib -lLLVMCore -lLLVMSupport -lpthread -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMMCParser -lLLVMX86Desc -lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMX86Utils -lLLVMJIT -lLLVMRuntimeDyld -lLLVMExecutionEngine -lLLVMCodeGen -lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -lLLVMipa -lLLVMAnalysis -lLLVMTarget -lLLVMMC -lLLVMObject -lLLVMCore -lLLVMSupport -lm -limagehlp -lpsapi
使用在链接代码中实现的提议语言,我正在测试一些顶级表达式。第一个字面值:
ready> 5 + 3;
ready> Read top-level expression:
define double @0() {
entry:
ret double 8.000000e+00
}
Evaluated to 8.000000
…工作如预期。然后是带有常量结果的函数定义:
ready> def f(x) 12;
ready> Read function definition:
define double @f(double %x) {
entry:
ret double 1.200000e+01
}
…再一次,按预期工作。对任何输入调用此方法都会得到一个固定的结果:
ready> f(5);
ready> Read top-level expression:
define double @1() {
entry:
%calltmp = call double @f(double 5.000000e+00)
ret double %calltmp
}
Evaluated to 12.000000
…没有惊喜。然后,使用参数
执行操作的函数定义:ready> def g(x) x + 1;
ready> Read function definition:
define double @g(double %x) {
entry:
%addtmp = fadd double 1.000000e+00, %x
ret double %addtmp
}
…看起来没问题,字节码生成了。现在,调用它:
ready> g(5);
ready> Read top-level expression:
define double @2() {
entry:
%calltmp = call double @g(double 5.000000e+00)
ret double %calltmp
}
0x00D400A4 (0x0000000A 0x00000000 0x0028FF28 0x00D40087) <unknown module>
0x00C7A5E0 (0x01078A28 0x010CF040 0x0028FEF0 0x40280000)
0x004023F1 (0x00000001 0x01072FD0 0x01071B10 0xFFFFFFFF)
0x004010B9 (0x00000001 0x00000000 0x00000000 0x00000000)
0x00401284 (0x7EFDE000 0x0028FFD4 0x77E59F42 0x7EFDE000)
0x75693677 (0x7EFDE000 0x7B3361A2 0x00000000 0x00000000), BaseThreadInitThunk() + 0x12 bytes(s)
0x77E59F42 (0x0040126C 0x7EFDE000 0x00000000 0x00000000), RtlInitializeExceptionChain() + 0x63 bytes(s)
0x77E59F15 (0x0040126C 0x7EFDE000 0x00000000 0x78746341), RtlInitializeExceptionChain() + 0x36 bytes(s)
…崩溃。
通过一些基本的调试,我开始相信所涉及的代码片段,即顶级表达式的代码片段(带参数5的g(x)
调用)和被调用函数的代码片段,都可以成功地进行jit编译。我相信这是这种情况,因为我在崩溃之前得到函数指针(并且我假设执行引擎在成功编译函数后仅返回函数指针)。更准确地说,崩溃正好发生在函数指针运行的地方,这意味着我的源文件(在HandleTopLevelExpression()
中)中的这一行:
fprintf(stderr, "Evaluated to %fn", FP());
很可能该行本身是无辜的,因为它成功地运行了其他函数。罪魁祸首很可能在上面最后一个例子中FP
所指向的函数中的某个地方,但由于该代码是在运行时生成的,因此我的cpp
文件中没有它。
有什么想法,为什么它可能会崩溃在这个特定的场景?
UPDATE #1:通过gdb运行进程显示在崩溃点:
程序收到信号SIGILL,非法指令。
和一个不告诉我任何事情的跟踪:
0x00ee0044 in ?? ()
updat# 2:为了更清楚地说明这一点,下面是崩溃前后的程序集:
00D70068 55 PUSH EBP
00D70069 89E5 MOV EBP,ESP
00D7006B 81E4 F8FFFFFF AND ESP,FFFFFFF8
00D70071 83EC 08 SUB ESP,8
00D70074 C5FB LDS EDI,EBX ; Here! ; Illegal use of register
00D70076 1045 08 ADC BYTE PTR SS:[EBP+8],AL
00D70079 C5FB LDS EDI,EBX ; Illegal use of register
00D7007B 58 POP EAX
00D7007C 05 6000D700 ADD EAX,0D70060
00D70081 C5FB LDS EDI,EBX ; Illegal use of register
00D70083 110424 ADC DWORD PTR SS:[ESP],EAX
00D70086 DD0424 FLD QWORD PTR SS:[ESP]
00D70089 89EC MOV ESP,EBP
00D7008B 5D POP EBP
00D7008C C3 RETN
崩溃发生在00D70074
,指令是LDS EDI,EBX
。它比FP
所指向的地址高几个地址(这让我相信这可能都是jit发出的代码,但请对这个结论持保留态度,因为我在这里无法理解)。
可以看到,反汇编程序也在这一行和后面的类似行上加了注释,说这是非法使用寄存器。老实说,我不知道为什么这个特定的扩展寄存器对对于这个指令是非法的,但是如果它是非法的,为什么它在那里,我们如何使编译器产生合法的代码?
显然LLVM正在为您生成带有vex前缀的AVX指令,但是您的处理器不支持该指令集(您的反汇编器也不支持)。
JIT字节的avx感知解码给出以下有效代码: 0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 81 e4 f8 ff ff ff and esp,0xfffffff8
9: 83 ec 08 sub esp,0x8
c: c5 fb 10 45 08 vmovsd xmm0,QWORD PTR [ebp+0x8]
11: c5 fb 58 05 60 00 d7 vaddsd xmm0,xmm0,QWORD PTR ds:0xd70060
18: 00
19: c5 fb 11 04 24 vmovsd QWORD PTR [esp],xmm0
1e: dd 04 24 fld QWORD PTR [esp]
21: 89 ec mov esp,ebp
23: 5d pop ebp
24: c3 ret
如果LLVM错误地检测了您的本地架构,或者如果您只是想覆盖它,您可以更改示例代码中使用的EngineBuilder
,例如:
TheExecutionEngine = EngineBuilder(TheModule).setErrorStr(&ErrStr).setMCPU("i386").create();
您还可以设置架构或提供属性。
- 将可变参数函数的参数封装在类实例中
- QML 使用带有参数C++函数
- 使用可变参数函数作为模板参数
- 如何在C++中伪造虚拟可变参数函数模板?
- 为什么可变参数函数不适用于模板
- C++ std::functional 中的可变参数函数模板
- 可变参数函数指针的定义对于VxWorks spyLib来说不清楚
- 使用可变参数函数覆盖具有不同函数签名的虚函数
- 考虑引用和常量的可变参数函数包装器
- 使用可变参数函数将整数和/或整数数组放入单个 int 数组中
- 在可变参数函数中转发特定范围的参数
- 通过引用传递参数;函数返回类型是否必须为 VOID?
- 使用带有一个参数函数的递归找到数字的平方
- 可变参数函数模板不能很好地使用 std::function 作为参数
- 多个可变参数函数的单个模板参数包?
- 参数数据类型未知的可变参数函数
- 可变参数函数参数包扩展
- 使用模板可变参数函数将多个参数传递给另一个函数
- 对可变参数函数的递归调用的链接器错误
- 通过像printf这样的可变参数函数传递一个带有常量字符*转换函数的类