LLVM MCJIT / SEH异常处理

时间:2016-08-30 10:10:21

标签: exception-handling llvm llvm-ir seh

最近,我一直试图让SEH异常处理与MCJIT一起在LLVM(3.8.1)中工作。到目前为止没有任何运气。

根据我从网站(http://llvm.org/docs/ExceptionHandling.html)的理解,这几乎是应该如何实现的。使用clang编译一小段代码可以得到几乎相同的LLVM IR代码。但是,当我尝试它时,程序崩溃了一个令人讨厌的Stack cookie instrumentation code detected a stack-based buffer overrun.

为了说明我一直试图做的事情,我已经创建了一个最小的测试用例(我为代码量道歉......):

#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);
}

这会生成以下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" }
问:我错过了什么,为什么不工作以及如何解决?

1 个答案:

答案 0 :(得分:1)

在llvm开发人员列表上发布此问题后,我得到了一个友好的回复,解释了此问题与已知错误的关联:https://llvm.org/bugs/show_bug.cgi?id=24233

基本上发生的事情是LLVM没有实现Windows(更具体地说:SEH和调试)处理堆栈帧所需的代码。我并不是这方面的专家,但在实施之前,SEH不知道该怎么做,这意味着C ++例外基本上不会起作用。

一个明显的解决方法当然是在函数调用期间将对象作为指针传递并执行if-then-else。这样就避免了异常。但是,这非常令人讨厌,可能会给性能带来严重损失。而且,这使得编译器中的流程以及生成的程序更加复杂。换句话说:我们只是说我不愿意。

我将问题保持开放;如果有人碰巧发现黑客或找出解决方法,我很乐意接受它。