LLVM JIT教程代码使用简单的参数化函数崩溃.为什么?

The*_*kis 8 c++ assembly jit llvm

我正在努力学习LLVM基础设施.我在MinGW安装上安装了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
Run Code Online (Sandbox Code Playgroud)

......按预期工作.然后是一个具有常量结果的函数定义:

ready> def f(x) 12;
ready> Read function definition:
define double @f(double %x) {
entry:
  ret double 1.200000e+01
}
Run Code Online (Sandbox Code Playgroud)

......再次,按预期工作.对任何输入调用此方法都会得到一个固定的结果:

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
Run Code Online (Sandbox Code Playgroud)

...没有惊喜.然后,使用参数执行某些操作的函数定义:

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
}
Run Code Online (Sandbox Code Playgroud)

......看起来没关系,生成了字节码.现在,称它为:

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)
Run Code Online (Sandbox Code Playgroud)

...崩溃.

通过一些基本的调试,我开始相信所涉及的代码片段,即顶层表达式(对g(x)参数为5 的调用)和被调用函数的代码,都是JIT-编译成功.我相信这是因为我在崩溃之前得到了函数指针(我假设执行引擎只有成功编译函数后才返回一个函数指针).更确切地说,崩溃发生在函数指针运行的位置,这意味着我的源文件(in HandleTopLevelExpression())中的这一行:

  fprintf(stderr, "Evaluated to %f\n", FP());
Run Code Online (Sandbox Code Playgroud)

很可能这条线本身是无辜的,因为它成功运行其他功能.罪魁祸首可能FP出现在上面最后一个示例所指向的函数内部,但由于该代码是运行时生成的,因此我的cpp文件中没有它.

关于为什么它可能会在特定情况下崩溃的任何想法?


更新#1:通过gdb运行进程在崩溃点显示:

程序接收信号SIGILL,非法指令.

还有一条不告诉我任何事情的痕迹:

0x00ee0044 in ?? ()
Run Code Online (Sandbox Code Playgroud)

更新#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
Run Code Online (Sandbox Code Playgroud)

发生了崩溃00D70074,指令正在发生LDS EDI,EBX.它是一些高于指向的地址的地址FP(这让我相信这一切都可能是JIT发出的代码,但请大家带着这个结论,因为我在这里过头).

正如你所看到的,反汇编程序也对此和下一个类似的行发表评论,称这是非法使用寄存器.老实说,我不知道为什么这个特定的扩展寄存器对对于这个指令是非法的,但如果它是非法的,为什么它根本就存在,我们如何让编译器产生合法的代码呢?

Jes*_*ter 5

显然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
Run Code Online (Sandbox Code Playgroud)

如果LLVM错误检测您的本机体系结构,或者您只想覆盖它,则可以更改EngineBuilder示例代码中使用的内容,例如:

TheExecutionEngine = EngineBuilder(TheModule).setErrorStr(&ErrStr).setMCPU("i386").create();
Run Code Online (Sandbox Code Playgroud)

您还可以设置体系结构或提供属性.