2016-01-16 3 views
2

Мне нужно реализовать библиотеку, которая компилирует код C в байт-код eBPF, используя LLVM/Clang в качестве бэкэнд. Коды будут считаны из памяти, и мне также нужно получить результирующий код сборки в памяти.Создайте сборку из C-кода в памяти с помощью libclang

До сих пор я был в состоянии собрать в LLVM IR, используя следующий код:

#include <string> 
#include <vector> 

#include <clang/Frontend/CompilerInstance.h> 
#include <clang/Basic/DiagnosticOptions.h> 
#include <clang/Frontend/TextDiagnosticPrinter.h> 
#include <clang/CodeGen/CodeGenAction.h> 
#include <clang/Basic/TargetInfo.h> 
#include <llvm/Support/TargetSelect.h> 

using namespace std; 
using namespace clang; 
using namespace llvm; 

int main() { 

    constexpr auto testCodeFileName = "test.cpp"; 
    constexpr auto testCode = "int test() { return 2+2; }"; 

    // Prepare compilation arguments 
    vector<const char *> args; 
    args.push_back(testCodeFileName); 

    // Prepare DiagnosticEngine 
    DiagnosticOptions DiagOpts; 
    TextDiagnosticPrinter *textDiagPrinter = 
      new clang::TextDiagnosticPrinter(errs(), 
             &DiagOpts); 
    IntrusiveRefCntPtr<clang::DiagnosticIDs> pDiagIDs; 
    DiagnosticsEngine *pDiagnosticsEngine = 
      new DiagnosticsEngine(pDiagIDs, 
             &DiagOpts, 
             textDiagPrinter); 

    // Initialize CompilerInvocation 
    CompilerInvocation *CI = new CompilerInvocation(); 
    CompilerInvocation::CreateFromArgs(*CI, &args[0], &args[0] +  args.size(), *pDiagnosticsEngine); 

    // Map code filename to a memoryBuffer 
    StringRef testCodeData(testCode); 
    unique_ptr<MemoryBuffer> buffer = MemoryBuffer::getMemBufferCopy(testCodeData); 
    CI->getPreprocessorOpts().addRemappedFile(testCodeFileName, buffer.get()); 


    // Create and initialize CompilerInstance 
    CompilerInstance Clang; 
    Clang.setInvocation(CI); 
    Clang.createDiagnostics(); 

    // Set target (I guess I can initialize only the BPF target, but I don't know how) 
    InitializeAllTargets(); 
    const std::shared_ptr<clang::TargetOptions> targetOptions = std::make_shared<clang::TargetOptions>(); 
    targetOptions->Triple = string("bpf"); 
    TargetInfo *pTargetInfo = TargetInfo::CreateTargetInfo(*pDiagnosticsEngine,targetOptions); 
    Clang.setTarget(pTargetInfo); 

    // Create and execute action 
    // CodeGenAction *compilerAction = new EmitLLVMOnlyAction(); 
    CodeGenAction *compilerAction = new EmitAssemblyAction(); 
    Clang.ExecuteAction(*compilerAction); 

    buffer.release(); 
} 

Для компиляции я использую следующий CMakeLists.txt:

cmake_minimum_required(VERSION 3.3.2) 
project(clang_backend CXX) 

set(CMAKE_CXX_COMPILER "clang++") 

execute_process(COMMAND llvm-config --cxxflags OUTPUT_VARIABLE LLVM_CONFIG OUTPUT_STRIP_TRAILING_WHITESPACE) 
execute_process(COMMAND llvm-config --libs OUTPUT_VARIABLE LLVM_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE) 

set(CMAKE_CXX_FLAGS ${LLVM_CONFIG}) 

set(CLANG_LIBS clang clangFrontend clangDriver clangSerialization clangParse 
    clangCodeGen clangSema clangAnalysis clangEdit clangAST clangLex 
    clangBasic) 

add_executable(clang_backend main.cpp) 
target_link_libraries(clang_backend ${CLANG_LIBS}) 
target_link_libraries(clang_backend ${LLVM_LIBS}) 

Если я правильно понял, Я должен иметь возможность генерировать код сборки, если я изменяю действие компилятора на EmitAssemblyAction(), но я, вероятно, не инициализирую что-то, поскольку получаю ошибку сегментации в llvm :: TargetPassConfig :: addPassesToHandleExceptions (this = this @ entry = 0x6d8d30) в /tmp/llvm-3.7.1.src/lib/CodeGen/Pas ses.cpp: 419

код на этой линии:

switch (TM->getMCAsmInfo()->getExceptionHandlingType()) { 

Кто-нибудь есть пример или знает, что я не хватает?

+0

Я думаю, вам нужно придумать полный пример, который может быть скомпилирован и протестирован ... –

+0

Компилируемый пример добавлен. –

+0

После исправления опечатки я получаю сообщение об ошибке «Неустранимая ошибка: не удалось переделать файл« test.cpp »в содержимое файла« int test() {return 2 + 2; } '' –

ответ

5

Итак, если вы компиляции LLVM с утверждает далее, ошибка гораздо яснее, и это будет на самом деле сказать вам, что вам нужно сделать:

x: .../src/llvm/lib/CodeGen/LLVMTargetMachine.cpp:63: 
void llvm::LLVMTargetMachine::initAsmInfo(): 
Assertion `TmpAsmInfo && "MCAsmInfo not initialized. " 
"Make sure you include the correct TargetSelect.h" 
"and that InitializeAllTargetMCs() is being invoked!"' failed. 

(я добавил некоторые разрывы строк в том, что, поскольку он печатается как одна длинная строка).

После добавления нужного InitializeAllTargetMCs() в начале main, я получил еще одну ошибку. Глядя на генерацию объектного файла моего компилятора, я «догадался», что это проблема с другим вызовом InitializeAll*. Немного тестирования, и получается, что вам также нужен InitializeAllAsmPrinters(); - что имеет смысл, учитывая, что вы хотите создать ассемблерный код.

Я не совсем уверен, как «видеть» результаты вашего кода, но добавление этих двух в начало main заставляет его работать до завершения, а не утверждать, выходить с ошибкой или сбоем - что обычно является хороший шаг в правильном направлении.

Так вот что main выглядит в «мой» код:

int main() { 

    constexpr auto testCodeFileName = "test.cpp"; 
    constexpr auto testCode = "int test() { return 2+2; }"; 

    InitializeAllTargetMCs(); 
    InitializeAllAsmPrinters(); 

    // Prepare compilation arguments 
    vector<const char *> args; 
    args.push_back(testCodeFileName); 

    // Prepare DiagnosticEngine 
    DiagnosticOptions DiagOpts; 
    TextDiagnosticPrinter *textDiagPrinter = 
      new clang::TextDiagnosticPrinter(errs(), 
             &DiagOpts); 
    IntrusiveRefCntPtr<clang::DiagnosticIDs> pDiagIDs; 
    DiagnosticsEngine *pDiagnosticsEngine = 
      new DiagnosticsEngine(pDiagIDs, 
             &DiagOpts, 
             textDiagPrinter); 

    // Initialize CompilerInvocation 
    CompilerInvocation *CI = new CompilerInvocation(); 
    CompilerInvocation::CreateFromArgs(*CI, &args[0], &args[0] +  args.size(), *pDiagnosticsEngine); 

    // Map code filename to a memoryBuffer 
    StringRef testCodeData(testCode); 
    unique_ptr<MemoryBuffer> buffer = MemoryBuffer::getMemBufferCopy(testCodeData); 
    CI->getPreprocessorOpts().addRemappedFile(testCodeFileName, buffer.get()); 


    // Create and initialize CompilerInstance 
    CompilerInstance Clang; 
    Clang.setInvocation(CI); 
    Clang.createDiagnostics(); 

    // Set target (I guess I can initialize only the BPF target, but I don't know how) 
    InitializeAllTargets(); 
    const std::shared_ptr<clang::TargetOptions> targetOptions = std::make_shared<clang::TargetOptions>(); 
    targetOptions->Triple = string("bpf"); 
    TargetInfo *pTargetInfo = TargetInfo::CreateTargetInfo(*pDiagnosticsEngine,targetOptions); 
    Clang.setTarget(pTargetInfo); 

    // Create and execute action 
    // CodeGenAction *compilerAction = new EmitLLVMOnlyAction(); 
    CodeGenAction *compilerAction = new EmitAssemblyAction(); 
    Clang.ExecuteAction(*compilerAction); 

    buffer.release(); 
} 

Я настоятельно рекомендую, если вы хотите развивать с лязгом & LLVM, что вы создаете отладочную версию Clang & LLVM - это поможет как отслеживать «почему», так и ловить проблемы на ранней стадии и где это более очевидно. Используйте -DCMAKE_BUILD_TYPE=Debug с cmake, чтобы получить этот аромат.

Мой полный сценарий для получения LLVM & Clang построить:

export CC=clang 
export CXX=clang++ 
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr/local/llvm-debug -DLLVM_TAR 
GETS_TO_BUILD=X86 ../llvm 

[Я использовал поздний пре-релиз 3.8, чтобы проверить это, но я очень сомневаюсь, что это сильно отличается от 3.7.1 в этом отношении]

+0

спасибо, что решили мои проблемы. У меня был clang/llvm, скомпилированный с отладочными символами, но без утверждений, что позволило им многое помогло. Я приложу в качестве ответа последний код, который я получил, поскольку мне пришлось изменить несколько вещей для компиляции из/в память. –

0

Если кто-то сталкивается с аналогичной проблемой, я смог скомпилировать из/в память, отправив код через stdin и получив вывод из stdout.

Я не знаю, есть ли другой способ достичь этого, возможно, используя clang :: Driver, но прочитав исходный код Clang/LLVM, я обнаружил, что действие, которое мне нужно выполнить для получения объекта, EmitObjAction() и кажется, что это действие всегда создает файл .o, если вход не получен из stdin.

Поэтому я заменил stdin/stdout на трубы перед выполнением действия и таким образом избегаю генерации файла.

#include <string> 
#include <vector> 
#include <sstream> 
#include <iostream> 
#include <cstdio> 

#include <unistd.h> 
#include <fcntl.h> 

#include <clang/Frontend/CompilerInstance.h> 
#include <clang/Basic/DiagnosticOptions.h> 
#include <clang/Frontend/TextDiagnosticPrinter.h> 
#include <clang/CodeGen/CodeGenAction.h> 
#include <clang/Basic/TargetInfo.h> 
#include <llvm/Support/TargetSelect.h> 
#include <llvm/IR/Module.h> 

using namespace std; 
using namespace clang; 
using namespace llvm; 

int main(int argc, char *argv[]) 
{ 
    // code to compile for the eBPF virtual machine 
    constexpr auto testCode = "int main() { return get_nbs(); }"; 

    // Send code through a pipe to stdin 
    int codeInPipe[2]; 
    pipe2(codeInPipe, O_NONBLOCK); 
    write(codeInPipe[1], (void *) testCode, strlen(testCode)); 
    close(codeInPipe[1]); // We need to close the pipe to send an EOF 
    dup2(codeInPipe[0], STDIN_FILENO); 

    // Prepare reception of code through stdout 
    int codeOutPipe[2]; 
    pipe(codeOutPipe); 
    dup2(codeOutPipe[1], STDOUT_FILENO); 

    // Initialize various LLVM/Clang components 
    InitializeAllTargetMCs(); 
    InitializeAllAsmPrinters(); 
    InitializeAllTargets(); 

    // Prepare compilation arguments 
    vector<const char *> args; 
    args.push_back("--target=bpf"); // Target is bpf assembly 
    args.push_back("-xc"); // Code is in c language 
    args.push_back("-"); // Read code from stdin 

    CompilerInvocation *CI = createInvocationFromCommandLine(makeArrayRef(args) , NULL); 

    // Create CompilerInstance 
    CompilerInstance Clang; 
    Clang.setInvocation(CI); 

    // Initialize CompilerInstace 
    Clang.createDiagnostics(); 

    // Create and execute action 
    CodeGenAction *compilerAction; 
    compilerAction = new EmitObjAction(); 
    Clang.ExecuteAction(*compilerAction); 

    // Get compiled object (be carefull with buffer size) 
    close(codeInPipe[0]); 
    char objBuffer[2048]; 
    read(codeOutPipe[0], objBuffer, 2048); 

    return 0; 
}