Учебный код LLVM JIT падает с простой параметризованной функцией. Зачем?

Я пытаюсь научиться обходить инфраструктуру LLVM. Я установил двоичные файлы LLVM для Windows на установку MinGW.

Я следую руководству, найденному на сайте LLVM, о так называемом языке калейдоскопа. У меня есть исходный файл, который имеет именно так листинг кода в конце этой страницы.

Кроме того, если это имеет какое-либо значение, я строю, используя следующие флаги (полученные через llvm-config заранее, потому что оболочка Windows не имеет очень удобного синтаксиса замещения):

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)

…Сбои.

Через некоторую элементарную отладку я пришел к выводу, что задействованы фрагменты кода, то есть тот, который используется для выражения верхнего уровня (вызов g(x) с аргументом 5) и для вызываемой функции, JIT-компилируются успешно. Я считаю, что это так, потому что я получаю указатель на функцию до сбой (и я предполагаю, что механизм выполнения возвращает только указатель на функцию после он успешно скомпилировал функцию). Чтобы быть более точным, сбой происходит именно в точке, где выполняется указатель функции, то есть эта строка в моем исходном файле (в HandleTopLevelExpression()):

  fprintf(stderr, "Evaluated to %f\n", FP());

Скорее всего, сама строка невинна, потому что она успешно работает для других функций. Преступник, скорее всего, где-то внутри функции, указанной FP в последнем из приведенных выше примеров, но так как этот код генерируется во время выполнения, у меня его нет в моем cpp файл.

Любые идеи о том, почему это может произойти сбой в этом конкретном сценарии?


ОБНОВЛЕНИЕ № 1: Запуск процесса через GDB показывает это в точке сбоя:

Программа получила сигнал SIGILL, незаконная инструкция.

И след, который ничего не говорит мне:

0x00ee0044 in ?? ()

ОБНОВЛЕНИЕ № 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, но, пожалуйста, примите этот вывод с недоверием, так как я над головой здесь).

Как вы можете видеть, дизассемблер также разместил комментарий к этой и следующим аналогичным строкам, говоря, что это незаконное использование регистра. Если честно, я не знаю Зачем эта конкретная пара расширенных регистров недопустима для этой инструкции, но если это незаконно, почему оно вообще существует и как мы можем заставить компилятор создавать корректный код?

8

Решение

Очевидно, LLVM генерирует инструкции AVX с префиксом VEX для вас, но ваш процессор не поддерживает этот набор команд (как и ваш дизассемблер).

AVX-совместимое декодирование ваших байтов JIT дает следующий действительный код:

   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();

Вы также можете установить архитектуру или предоставить атрибуты.

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]