2016-08-30 16 views
7

Ostatnio próbowałem uzyskać obsługę wyjątków SEH do pracy w LLVM (3.8.1) razem z MCJIT. Jak dotąd bez szczęścia.LLVM MCJIT/SEH Obsługa wyjątków

Z tego, co rozumiem ze strony internetowej (http://llvm.org/docs/ExceptionHandling.html), wynika to w przybliżeniu z tego, jak należy to zrealizować. Kompilowanie minimalnego fragmentu kodu za pomocą klang daje prawie taki sam kod LLVM IR. Jednak gdy go wypróbuję, program rozbija się na paskudnym Stack cookie instrumentation code detected a stack-based buffer overrun..

Aby zilustrować, co ja próbuje zrobić, Utworzyłem minimalną przypadek testowy (przepraszam za ilość kodu ...):

#include <string> 
#include <iostream> 
#include <exception> 

#pragma warning(push) 
#pragma warning(disable: 4267) 
#pragma warning(disable: 4244) 
#pragma warning(disable: 4800) 
#pragma warning(disable: 4996) 
#pragma warning(disable: 4141) 
#pragma warning(disable: 4146) 
#pragma warning(disable: 4624) 
#pragma warning(disable: 4291) 

#define DONT_GET_PLUGIN_LOADER_OPTION 

#include "llvm/ADT/StringRef.h" 
#include "llvm/ADT/StringMap.h" 
#include "llvm/ADT/Triple.h" 
#include "llvm/PassRegistry.h" 
#include "llvm/IR/DataLayout.h" 
#include "llvm/IR/LegacyPassManager.h" 
#include "llvm/LinkAllPasses.h" 
#include "llvm/Analysis/Passes.h" 
#include "llvm/Analysis/TargetTransformInfo.h" 
#include "llvm/ExecutionEngine/ExecutionEngine.h" 
#include "llvm/ExecutionEngine/GenericValue.h" 
#include "llvm/ExecutionEngine/Interpreter.h" 
#include "llvm/ExecutionEngine/MCJIT.h" 
#include "llvm/IR/Constants.h" 
#include "llvm/IR/Type.h" 
#include "llvm/IR/LLVMContext.h" 
#include "llvm/IR/Module.h" 
#include "llvm/IR/Function.h" 
#include "llvm/IR/BasicBlock.h" 
#include "llvm/IR/IRBuilder.h" 
#include "llvm/IR/DerivedTypes.h" 
#include "llvm/IR/Value.h" 
#include "llvm/Support/Allocator.h" 
#include "llvm/Support/FormattedStream.h" 
#include "llvm/Support/ManagedStatic.h" 
#include "llvm/Support/PluginLoader.h" 
#include "llvm/Support/Host.h" 
#include "llvm/Support/PrettyStackTrace.h" 
#include "llvm/Support/Signals.h" 
#include "llvm/Support/SourceMgr.h" 
#include "llvm/Support/TargetRegistry.h" 
#include "llvm/Support/TargetSelect.h" 
#include "llvm/Support/ToolOutputFile.h" 
#include "llvm/Target/TargetMachine.h" 
#include "llvm/Transforms/Scalar.h" 
#include "llvm/Transforms/IPO/PassManagerBuilder.h" 

#pragma warning(pop) 

static void test() 
{ 
    // You can use this to see that function calls work fine. 
    // std::cout << "Foo!" << std::endl; 

    throw std::exception("Something we should try to catch."); 
} 

int main() 
{ 
    // Initialize LLVM 
    std::cout << "Initializing LLVM." << std::endl; 

    llvm::InitializeNativeTarget(); 
    llvm::InitializeAllTargetMCs(); 
    llvm::InitializeNativeTargetAsmPrinter(); 
    llvm::InitializeNativeTargetAsmParser(); 

    llvm::PassRegistry *Registry = llvm::PassRegistry::getPassRegistry(); 
    llvm::initializeCore(*Registry); 
    llvm::initializeScalarOpts(*Registry); 
    llvm::initializeObjCARCOpts(*Registry); 
    llvm::initializeVectorization(*Registry); 
    llvm::initializeIPO(*Registry); 
    llvm::initializeAnalysis(*Registry); 
    llvm::initializeTransformUtils(*Registry); 
    llvm::initializeInstCombine(*Registry); 
    llvm::initializeInstrumentation(*Registry); 
    llvm::initializeTarget(*Registry); 
    // For codegen passes, only passes that do IR to IR transformation are 
    // supported. 
    llvm::initializeCodeGenPreparePass(*Registry); 
    llvm::initializeAtomicExpandPass(*Registry); 
    llvm::initializeRewriteSymbolsPass(*Registry); 
    llvm::initializeWinEHPreparePass(*Registry); 
    llvm::initializeDwarfEHPreparePass(*Registry); 
    llvm::initializeSjLjEHPreparePass(*Registry); 

    llvm::StringRef MCPU = llvm::sys::getHostCPUName(); 
    std::string MTrip = llvm::sys::getProcessTriple(); 

    static llvm::StringMap<bool, llvm::MallocAllocator> features; 
    llvm::sys::getHostCPUFeatures(features); 

    // Initialize module & context: 
    auto context = std::unique_ptr<llvm::LLVMContext>(new llvm::LLVMContext()); 
    auto module = std::unique_ptr<llvm::Module>(new llvm::Module("native", *context)); 

    // Create 'main' method: 

    llvm::Type* returnType = llvm::Type::getInt32Ty(*context); 
    std::vector<llvm::Type*> arguments; 

    // MCJIT only supports main(int, char**) 
    arguments.push_back(llvm::Type::getInt32Ty(*context)); 
    arguments.push_back(llvm::Type::getInt8PtrTy(*context)->getPointerTo()); 

    llvm::Function *fcn = llvm::cast<llvm::Function>(module->getOrInsertFunction("main", llvm::FunctionType::get(returnType, arguments, false))); 

    // Generate exception handler info for main: 
    llvm::AttrBuilder argBuilder; 
    argBuilder.addAttribute(llvm::Attribute::UWTable); 
    argBuilder.addAttribute("stack-protector-buffer-size", "8"); 
    fcn->addAttributes(llvm::AttributeSet::FunctionIndex, llvm::AttributeSet::get(*context, llvm::AttributeSet::FunctionIndex, argBuilder)); 

    // Exception handling requires a personality function. We want to use the SEH personality handler 
    llvm::Function *personalityHandler = llvm::cast<llvm::Function>(module->getOrInsertFunction("__CxxFrameHandler3", llvm::FunctionType::get(llvm::Type::getInt32Ty(*context), true))); 
    auto personalityPtr = llvm::ConstantExpr::getBitCast(personalityHandler, llvm::Type::getInt8PtrTy(*context)); 
    fcn->setPersonalityFn(personalityPtr); 

    // Create some code. Basically we want to invoke our 'test' method 
    auto block = llvm::BasicBlock::Create(*context, "code", fcn); 
    llvm::IRBuilder<> builder(block); 

    // all other cases might throw an exception 
    auto continueBlock = llvm::BasicBlock::Create(*context, "invoke.cont", fcn); 
    auto catchDispatch = llvm::BasicBlock::Create(*context, "catch.dispatch", fcn); 

    // Register 'test' as an external function: 
    const void* testFunctionPtr = &test; 
    auto testFunctionType = llvm::FunctionType::get(builder.getVoidTy(), false); 
    auto testFunction = llvm::Function::Create(testFunctionType, llvm::Function::ExternalLinkage, "test", module.get()); 

    // %call = invoke i32 @"test"() to label %invoke.cont unwind label %catch.dispatch 
    auto call = builder.CreateInvoke(testFunction, continueBlock, catchDispatch); 

    // return [ 0 from ok, 1 from catch handler ] 
    builder.SetInsertPoint(continueBlock); 
    auto phi = builder.CreatePHI(builder.getInt32Ty(), 2, "result"); 
    phi->addIncoming(builder.getInt32(0), block); 
    builder.CreateRet(phi); 

    // Create exception handler: 

    // Create default catch block. Basically handles the exception and returns '1'. 
    builder.SetInsertPoint(catchDispatch); 
    auto parentPad = llvm::ConstantTokenNone::get(*context); 
    // %0 = catchswitch within none [label %catch] unwind to caller 
    auto catchSwitch = builder.CreateCatchSwitch(parentPad, nullptr, 1); 

    auto catchBlock = llvm::BasicBlock::Create(*context, "catch", fcn); 
    builder.SetInsertPoint(catchBlock); 
    catchSwitch->addHandler(catchBlock); 

    // MSVC code: 
    // %1 = catchpad within %0 [i8* null, i32 64, i8* null] == "catch all" 
    llvm::Value *nullPtr = llvm::ConstantPointerNull::get(llvm::Type::getInt8PtrTy(*context)); 
    auto catchPad = builder.CreateCatchPad(catchSwitch, { nullPtr, builder.getInt32(0x40), nullPtr }); 

    // catchret from %1 to label %return 
    auto const1 = builder.getInt32(1); 
    builder.CreateCatchRet(catchPad, continueBlock); 

    // set 1 for the catch handler 
    phi->addIncoming(builder.getInt32(1), catchBlock); 

    // *DONE* building the code. 

    // Dump the LLVM IR: 
    module->dump(); 

    // Let's JIT the code: 

    std::string error; 
    auto trip = llvm::Triple::normalize(MTrip); 
    llvm::Triple triple(trip); 
    const llvm::Target *target = llvm::TargetRegistry::lookupTarget("x86-64", triple, error); 
    if (!target) 
    { 
     throw error.c_str(); 
    } 

    llvm::TargetOptions Options; 

    std::unique_ptr<llvm::TargetMachine> targetMachine(
     target->createTargetMachine(trip, MCPU, "", Options, llvm::Reloc::Default, llvm::CodeModel::Default, llvm::CodeGenOpt::Aggressive)); 

    if (!targetMachine.get()) 
    { 
     throw "Could not allocate target machine!"; 
    } 

    // Create the target machine; set the module data layout to the correct values. 
    auto DL = targetMachine->createDataLayout(); 
    module->setDataLayout(DL); 
    module->setTargetTriple(trip); 

    // Pass manager builder: 
    llvm::PassManagerBuilder pmbuilder; 
    pmbuilder.OptLevel = 3; 
    pmbuilder.BBVectorize = false; 
    pmbuilder.SLPVectorize = true; 
    pmbuilder.LoopVectorize = true; 
    pmbuilder.Inliner = llvm::createFunctionInliningPass(3, 2); 
    llvm::TargetLibraryInfoImpl *TLI = new llvm::TargetLibraryInfoImpl(triple); 
    pmbuilder.LibraryInfo = TLI; 

    // Generate pass managers: 

    // 1. Function pass manager: 
    llvm::legacy::FunctionPassManager FPM(module.get()); 
    pmbuilder.populateFunctionPassManager(FPM); 

    // 2. Module pass manager: 
    llvm::legacy::PassManager PM; 
    PM.add(llvm::createTargetTransformInfoWrapperPass(targetMachine->getTargetIRAnalysis())); 
    pmbuilder.populateModulePassManager(PM); 

    // 3. Execute passes: 
    // - Per-function passes: 
    FPM.doInitialization(); 
    for (llvm::Module::iterator I = module->begin(), E = module->end(); I != E; ++I) 
    { 
     if (!I->isDeclaration()) 
     { 
      FPM.run(*I); 
     } 
    } 
    FPM.doFinalization(); 

    // - Per-module passes: 
    PM.run(*module); 


    // All done, *RUN*. 
    llvm::EngineBuilder engineBuilder(std::move(module)); 
    engineBuilder.setEngineKind(llvm::EngineKind::JIT); 
    engineBuilder.setMCPU(MCPU); 
    engineBuilder.setMArch("x86-64"); 
    engineBuilder.setUseOrcMCJITReplacement(false); 
    engineBuilder.setOptLevel(llvm::CodeGenOpt::None); 

    llvm::ExecutionEngine* engine = engineBuilder.create(); 

    // Register global 'test' function: 
    engine->addGlobalMapping(testFunction, const_cast<void*>(testFunctionPtr)); // Yuck... 

    // Finalize 
    engine->finalizeObject(); 

    // Invoke: 
    std::vector<llvm::GenericValue> args(2); 
    args[0].IntVal = llvm::APInt(32, static_cast<uint64_t>(0), true); 
    args[1].PointerVal = nullptr; 

    llvm::GenericValue gv = engine->runFunction(fcn, args); 
    auto result = int(gv.IntVal.getSExtValue()); 

    std::cout << "Result after execution: " << result << std::endl; 

    std::string s; 
    std::getline(std::cin, s); 
} 

To daje następujący kod IR:

; ModuleID = 'native' 

; Function Attrs: uwtable 
define i32 @main(i32, i8**) #0 personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) { 
code: 
    invoke void @test() 
      to label %invoke.cont unwind label %catch.dispatch 

invoke.cont:          ; preds = %catch, %code 
    %result = phi i32 [ 0, %code ], [ 1, %catch ] 
    ret i32 %result 

catch.dispatch:         ; preds = %code 
    %2 = catchswitch within none [label %catch] unwind to caller 

catch:           ; preds = %catch.dispatch 
    %3 = catchpad within %2 [i8* null, i32 64, i8* null] 
    catchret from %3 to label %invoke.cont 
} 

declare i32 @__CxxFrameHandler3(...) 

declare void @test() 

attributes #0 = { uwtable "stack-protector-buffer-size"="8" } 

P: Czego mi brakuje, dlaczego to nie działa i jak to naprawić?

+0

Czy dowiedziałeś się, jaki kod spowodował przepełnienie stosu? –

+0

@ShmuelHazan To nie jest przepełnienie stosu. A szczegóły mówią, co powoduje problem. Ponadto, gdybym to rozgrywał, oczywiście nie oddałbym tej nagrody. – atlaste

Odpowiedz

1

Po zamieszczeniu tego problemu na liście programistów LLVM otrzymałem przyjazną odpowiedź wyjaśniającą, w jaki sposób ten problem jest związany ze znanym błędem: https://llvm.org/bugs/show_bug.cgi?id=24233.

Zasadniczo LLVM nie implementuje kodu wymaganego przez system Windows (dokładniej: SEH i debugowanie) do obsługi ramek stosów. W żadnym wypadku nie jestem ekspertem w tej dziedzinie, ale do tego czasu SEH nie będzie wiedział, co robić, co oznacza, że ​​wyjątek C++ w zasadzie nie zadziała.

Oczywistym rozwiązaniem jest oczywiście przekazanie obiektu jako wskaźnika podczas wywołania funkcji i wykonanie instrukcji if-then-else. W ten sposób unikamy wyjątków. Jest to jednak dość nieprzyjemne i prawdopodobnie spowoduje poważne kary za wyniki. Powoduje to również, że przepływ w kompilatorze, jak również wygenerowany program jest znacznie bardziej skomplikowany. Innymi słowy: powiedzmy, że wolałbym nie.

Pozostawię pytanie otwarte; jeśli ktoś znajdzie hack lub wymyśli obejście, z przyjemnością go zaakceptuję.