/*
* Copyright (c) 2020 Trail of Bits, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
#include "mcsema/BC/Callback.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wold-style-cast"
#pragma clang diagnostic ignored "-Wdocumentation"
#pragma clang diagnostic ignored "-Wswitch-enum"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma clang diagnostic pop
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mcsema/Arch/Arch.h"
#include "mcsema/BC/Legacy.h"
#include "mcsema/BC/Segment.h"
#include "mcsema/BC/Util.h"
#include "mcsema/CFG/CFG.h"
DECLARE_string(pc_annotation);
DEFINE_bool(explicit_args, false,
"Should arguments be explicitly passed to external functions. "
"This can be good for static analysis and symbolic execution, "
"but in practice it reduces the portability of the resulting "
"bitcode, especially where floating point argument and return "
"values are concerned.");
DEFINE_uint32(
explicit_args_count, 8,
"Number of explicit (integer) arguments to pass to an unknown "
"function, or to accept from an unknown function. This value is "
"used when calling external functions for which no type "
"information is known, or who take a variable number of arguments.");
DEFINE_uint32(explicit_args_stack_size, 4096 * 256 /* 1 MiB */,
"Size of the stack of the emulated program when the program "
"is lifted using --explicit_args.");
DEFINE_uint32(explicit_args_tls_size, 4 * 4096,
"Number of bytes of thread local storage");
DEFINE_bool(use_native_thread_base, false,
"Try to find and use the native thread base pointer.");
DECLARE_bool(stack_protector);
namespace mcsema {
namespace {
static llvm::Function *GetAttachCallFunc(void) {
static llvm::Function *handler = nullptr;
if (!handler) {
auto void_type = llvm::Type::getVoidTy(*gContext);
auto callback_type = llvm::FunctionType::get(void_type, false);
handler = llvm::Function::Create(callback_type,
llvm::GlobalValue::ExternalLinkage,
"__mcsema_attach_call", gModule.get());
handler->addFnAttr(llvm::Attribute::NoInline);
remill::Annotate(handler);
}
return handler;
}
static llvm::Function *DetachCallValueFunc(void) {
static llvm::Function *handler = nullptr;
if (!handler) {
handler = gModule->getFunction("__remill_function_call");
}
return handler;
}
// Get a callback function for an internal function.
static llvm::Function *
ImplementNativeToLiftedCallback(const NativeObject *cfg_func,
const std::string &callback_name) {
// If the native name of the function doesn't yet exist then add it in.
auto func = gModule->getFunction(cfg_func->lifted_name);
CHECK(func != nullptr) << "Cannot find lifted function "
<< cfg_func->lifted_name;
auto attach_func = GetAttachCallFunc();
// Generate inline assembly that can be used the go from native machine
// state into lifted code. The inline assembly saves a pointer to the lifted
// function and the original lifted function's address (from the CFG), and
// then jumps into `__mcsema_attach_call`, which does the low-level
// marshaling of native register state into the `State` structure.
std::stringstream asm_str;
switch (gArch->arch_name) {
case remill::kArchInvalid:
LOG(FATAL) << "Cannot generate native-to-lifted entrypoint thunk for "
<< "unknown architecture.";
break;
case remill::kArchAMD64:
case remill::kArchAMD64_AVX:
case remill::kArchAMD64_AVX512:
asm_str << "pushq $0;";
if (static_cast(cfg_func->ea) == cfg_func->ea) {
asm_str << "pushq $$0x" << std::hex << cfg_func->ea << ";";
} else {
asm_str << "pushq %rax;"
<< "movq $$0x" << std::hex << cfg_func->ea << ", %rax;"
<< "xchgq (%rsp), %rax;";
}
asm_str << "jmpq *$1;";
break;
case remill::kArchX86:
case remill::kArchX86_AVX:
case remill::kArchX86_AVX512:
asm_str << "pushl $0;"
<< "pushl $$0x" << std::hex << cfg_func->ea << ";"
<< "jmpl *$1;";
break;
case remill::kArchAArch64LittleEndian:
LOG(ERROR) << "TODO: Create a native-to-lifted callback for the "
<< GetArchName(gArch->arch_name) << " instruction set.";
asm_str << "nop;";
break;
default:
LOG(FATAL) << "Cannot create native-to-lifted callback for the "
<< GetArchName(gArch->arch_name) << " instruction set.";
break;
}
auto void_type = llvm::Type::getVoidTy(*gContext);
// Create the callback function that calls the inline assembly.
auto callback_type = llvm::FunctionType::get(void_type, false);
auto callback_func = gModule->getFunction(callback_name);
if (!callback_func) {
callback_func = llvm::Function::Create(
callback_type,
llvm::GlobalValue::InternalLinkage, // Tentative linkage.
callback_name, gModule.get());
}
callback_func->setVisibility(llvm::GlobalValue::DefaultVisibility);
callback_func->addFnAttr(llvm::Attribute::Naked);
callback_func->addFnAttr(llvm::Attribute::NoInline);
callback_func->addFnAttr(llvm::Attribute::NoBuiltin);
// Create the inline assembly. We use memory operands (
std::vector asm_arg_types;
std::vector asm_args;
asm_arg_types.push_back(llvm::PointerType::get(func->getType(), 0));
asm_arg_types.push_back(llvm::PointerType::get(attach_func->getType(), 0));
auto asm_func_type = llvm::FunctionType::get(void_type, asm_arg_types, false);
auto asm_func = llvm::InlineAsm::get(asm_func_type, asm_str.str(),
"*m,*m,~{dirflag},~{fpsr},~{flags}",
true /* hasSideEffects */);
// Make an initializer function that first calls `__mcsema_early_init`,
// then calls the lifted bitcode function. When lifting C++ code, often
// you will get weak functions, e.g. `std::string::data()`, implemented
// in the main binary, and depended on by libraries that are loaded and
// initialized before the main binary's constructors. This results in
// a re-entrancy bug, where the dynamic loader will link a native library's
// use of a C++ symbol against an exported version of it in the lifted
// binary, and then lifted code gets called too early. Normally, the lazy
// initialization of cross-references happens in `__mcsema_constructor`,
// but we need to also have it happen here just in case lifted code gets
// called before `__mcsema_constructor` is invoked.
std::stringstream func_wrapper_name;
func_wrapper_name << callback_name;
func_wrapper_name << "_wrapper";
auto func_wrapper = llvm::Function::Create(
func->getFunctionType(), llvm::GlobalValue::InternalLinkage,
func_wrapper_name.str(), gModule.get());
auto arg_it = func_wrapper->arg_begin();
llvm::IRBuilder<> ir(llvm::BasicBlock::Create(*gContext, "", func_wrapper));
llvm::Value *func_args[3];
func_args[0] = &*arg_it++;
func_args[1] = &*arg_it++;
func_args[2] = &*arg_it++;
ir.CreateCall(GetOrCreateMcSemaInitializer());
auto call = ir.CreateCall(func, func_args);
call->setTailCall(true);
ir.CreateRet(call);
// Back to the asm attach callback thunk...
ir.SetInsertPoint(llvm::BasicBlock::Create(*gContext, "", callback_func));
// It's easier to deal with memory references in inline assembly in static
// and relocatable binaries, but the cost is that we have to produce these
// otherwise useless global variables.
asm_args.push_back(new llvm::GlobalVariable(
*gModule, func_wrapper->getType(), true /* isConstant */,
llvm::GlobalValue::InternalLinkage, func_wrapper));
static llvm::GlobalVariable *attach_func_ptr = nullptr;
if (!attach_func_ptr) {
attach_func_ptr = new llvm::GlobalVariable(
*gModule, attach_func->getType(), true,
llvm::GlobalValue::InternalLinkage, attach_func);
}
asm_args.push_back(attach_func_ptr);
ir.CreateCall(asm_func, asm_args);
if (auto ret_type = callback_func->getReturnType(); ret_type->isVoidTy()) {
ir.CreateRetVoid();
} else {
ir.CreateRet(llvm::UndefValue::get(ret_type));
}
if (!FLAGS_pc_annotation.empty()) {
legacy::AnnotateInsts(callback_func, cfg_func->ea);
}
if (cfg_func->is_exported) {
callback_func->setLinkage(llvm::GlobalValue::ExternalLinkage);
callback_func->setDLLStorageClass(llvm::GlobalValue::DLLExportStorageClass);
}
return callback_func;
}
// Create a stack and a variable that tracks the stack pointer.
static llvm::Constant *InitialStackPointerValue(void) {
unsigned min_frame_size = 512u;
const auto num_bytes =
std::max(FLAGS_explicit_args_stack_size, 4096u + min_frame_size);
auto i8_type = llvm::Type::getInt8Ty(*gContext);
auto stack_type = llvm::ArrayType::get(i8_type, num_bytes);
static llvm::Constant *stack = nullptr;
if (!stack) {
auto stack_var = new llvm::GlobalVariable(
*gModule, stack_type, false, llvm::GlobalValue::InternalLinkage,
llvm::ConstantAggregateZero::get(stack_type), "__mcsema_stack", nullptr,
llvm::GlobalValue::InitialExecTLSModel);
stack = stack_var;
if (stack_var->getType()->getAddressSpace()) {
stack = llvm::ConstantExpr::getAddrSpaceCast(
stack_var, llvm::PointerType::get(stack_type, 0));
}
}
const auto i32_ty = llvm::Type::getInt32Ty(*gContext);
llvm::Constant *indexes[2];
indexes[0] = llvm::ConstantInt::get(i32_ty, 0);
indexes[1] = llvm::ConstantInt::get(i32_ty, num_bytes - min_frame_size);
#if LLVM_VERSION_NUMBER <= LLVM_VERSION(3, 6)
auto gep = llvm::ConstantExpr::getInBoundsGetElementPtr(stack, indexes);
#else
auto gep =
llvm::ConstantExpr::getInBoundsGetElementPtr(nullptr, stack, indexes);
#endif
auto ival = llvm::ConstantExpr::getPtrToInt(gep, gWordType);
if (gArch->IsLinux() || gArch->IsMacOS() || gArch->IsSolaris()) {
// SysV ABI requires that `esp + 4` is 16-byte aligned.
if (gArch->IsX86()) {
ival = llvm::ConstantExpr::getAnd(
ival, llvm::ConstantInt::get(gWordType, ~15u));
ival = llvm::ConstantExpr::getSub(ival,
llvm::ConstantInt::get(gWordType, 4u));
// `rsp` is 16-byte aligned on entry to a function.
} else if (gArch->IsAMD64()) {
ival = llvm::ConstantExpr::getAnd(
ival, llvm::ConstantInt::get(gWordType, ~15ull));
}
}
return ival;
}
static llvm::InlineAsm *ThreadPointerAsm(void) {
auto fty = llvm::FunctionType::get(gWordType, false);
switch (gArch->arch_name) {
case remill::kArchX86:
case remill::kArchX86_AVX:
case remill::kArchX86_AVX512: {
switch (gArch->os_name) {
case remill::kOSLinux:
return llvm::InlineAsm::get(fty, "mov %gs:0, $0", "=r,~{memory}", false,
false, llvm::InlineAsm::AD_ATT);
case remill::kOSmacOS:
LOG(FATAL) << "32-bit macOS targets are not supported";
break;
case remill::kOSWindows:
return llvm::InlineAsm::get(fty, "mov %fs:0, $0", "=r,~{memory}", false,
false, llvm::InlineAsm::AD_ATT);
default: break;
}
break;
}
case remill::kArchAMD64:
case remill::kArchAMD64_AVX:
case remill::kArchAMD64_AVX512: {
switch (gArch->os_name) {
case remill::kOSLinux:
return llvm::InlineAsm::get(fty, "mov %fs:0, $0", "=r,~{memory}",
false, false, llvm::InlineAsm::AD_ATT);
case remill::kOSmacOS:
LOG(FATAL) << "32-bit macOS targets are not supported";
break;
case remill::kOSWindows:
return llvm::InlineAsm::get(fty, "mov %gs:0, $0", "=r,~{memory}",
false, false, llvm::InlineAsm::AD_ATT);
default: break;
}
break;
}
case remill::kArchAArch64LittleEndian:
return llvm::InlineAsm::get(fty, "mov $0, %TPIDR_EL0", "=r,~{memory}",
false, false, llvm::InlineAsm::AD_ATT);
case remill::kArchSparc32:
case remill::kArchSparc64:
return llvm::InlineAsm::get(fty, "mov $0, %g7", "=r,~{memory}", false,
false, llvm::InlineAsm::AD_ATT);
default:
break;
}
LOG(FATAL) << "Cannot determine inline assembly for accessing thread base";
}
static const char *ThreadPointerNameX86(void) {
switch (gArch->os_name) {
case remill::kOSLinux: return "GS_BASE";
case remill::kOSWindows: return "FS_BASE";
default: return nullptr;
}
}
static const char *ThreadPointerNameAMD64(void) {
switch (gArch->os_name) {
case remill::kOSLinux: return "FS_BASE";
case remill::kOSWindows: return "GS_BASE";
default: return nullptr;
}
}
static const char *ThreadPointerName(void) {
const char *tp_name = nullptr;
switch (gArch->arch_name) {
case remill::kArchAArch64LittleEndian: return "TPIDR_EL0";
case remill::kArchX86:
case remill::kArchX86_AVX:
case remill::kArchX86_AVX512: tp_name = ThreadPointerNameX86(); break;
case remill::kArchAMD64:
case remill::kArchAMD64_AVX:
case remill::kArchAMD64_AVX512: tp_name = ThreadPointerNameAMD64(); break;
default: break;
}
LOG_IF(ERROR, !tp_name) << "Can't get thread pointer name for architecture "
<< remill::GetArchName(gArch->arch_name) << " and OS "
<< remill::GetOSName(gArch->os_name);
return tp_name;
}
// Create an array of data for holding thread-local storage.
static llvm::Value *InitialThreadLocalStorage(llvm::IRBuilder<> &ir) {
if (FLAGS_use_native_thread_base) {
return ir.CreateCall(ThreadPointerAsm());
}
// Add some TLS pages.
llvm::ArrayType *tls_type = llvm::ArrayType::get(
gWordType, FLAGS_explicit_args_tls_size / (gArch->address_size / 8));
auto tls_var = new llvm::GlobalVariable(
*gModule, tls_type, false, llvm::GlobalValue::InternalLinkage,
llvm::Constant::getNullValue(tls_type), "__mcsema_tls", nullptr,
llvm::GlobalValue::InitialExecTLSModel);
llvm::Constant *tls = tls_var;
if (tls_var->getType()->getAddressSpace()) {
tls = llvm::ConstantExpr::getAddrSpaceCast(
tls_var, llvm::PointerType::get(tls_type, 0));
}
const auto i32_ty = llvm::Type::getInt32Ty(*gContext);
llvm::Constant *indexes[2];
indexes[0] = llvm::ConstantInt::get(i32_ty, 0);
indexes[1] = indexes[0];
#if LLVM_VERSION_NUMBER <= LLVM_VERSION(3, 6)
tls = llvm::ConstantExpr::getInBoundsGetElementPtr(tls, indexes);
#else
tls = llvm::ConstantExpr::getInBoundsGetElementPtr(nullptr, tls, indexes);
#endif
tls = llvm::ConstantExpr::getPtrToInt(tls, gWordType);
return tls;
}
// NOTE(lukas): We don't need to annotate, it will always be inlined.
static llvm::Function *CreateVerifyRegState(void) {
auto reg_state = GetStatePointer();
auto *func_type = llvm::FunctionType::get(reg_state->getType(), false);
auto new_func =
gModule->getOrInsertFunction("__mcsema_init_reg_state", func_type);
auto func =
llvm::dyn_cast(new_func IF_LLVM_GTE_900(.getCallee()));
CHECK(func != nullptr)
<< "Could not get or create function '__mcsema_init_reg_state'";
const auto sp_name = gArch->StackPointerRegisterName();
auto sp_reg = gArch->RegisterByName(sp_name);
auto entry_block = llvm::BasicBlock::Create(*gContext, "entry", func);
auto is_null_block = llvm::BasicBlock::Create(*gContext, "is_null", func);
auto end_block = llvm::BasicBlock::Create(*gContext, "end", func);
llvm::IRBuilder<> ir(entry_block);
// Need to find out where stack pointer is and known information is
// byte offset in state structure
// auto byte_ty = llvm::Type::getInt8PtrTy(*gContext);
unsigned ptr_size = static_cast(gArch->address_size);
auto reg_ptr_ty = llvm::PointerType::getIntNPtrTy(*gContext, ptr_size);
//TODO(lukas): remove after abi_libraries patch gets merged into master
auto GetConstantInt = [&](unsigned size, uint64_t value) {
return llvm::ConstantInt::get(llvm::Type::getIntNTy(*gContext, size),
value);
};
// auto casted_reg_state = ir.CreateBitCast(reg_state, byte_ty);
auto rsp = sp_reg->AddressOf(reg_state, entry_block);
auto casted_rsp = ir.CreateBitCast(rsp, reg_ptr_ty);
auto rsp_val =
ir.CreateLoad(casted_rsp, llvm::Type::getIntNTy(*gContext, ptr_size));
auto comparison = ir.CreateICmpEQ(rsp_val, GetConstantInt(ptr_size, 0));
ir.CreateCondBr(comparison, is_null_block, end_block);
// Stack pointer is pointing at nothing, so we need to set it up
ir.SetInsertPoint(is_null_block);
ir.CreateStore(InitialStackPointerValue(), casted_rsp);
// Store the address of `__mcsema_tls` into the TLS register.
if (auto tp_name = ThreadPointerName(); tp_name) {
if (auto tp_reg = gArch->RegisterByName(tp_name); tp_reg) {
ir.CreateStore(InitialThreadLocalStorage(ir),
tp_reg->AddressOf(reg_state, is_null_block));
}
}
ir.SetInsertPoint(is_null_block);
// Call the `__mcsema_early_init` function to make sure all lazy cross-
// reference initializers have been installed before any lifted bitcode
// is executed.
ir.CreateCall(GetOrCreateMcSemaInitializer());
ir.CreateBr(end_block);
// Last block just returns void
ir.SetInsertPoint(end_block);
ir.CreateRet(reg_state);
return func;
}
// TODO(lukas): VerifyRegState is probably not the best name.
// Maybe VerifyStackPointer?
// Opened to suggestions.
// Because of possible parallelism, both global stack and state must be
// thread_local. However after new thread is created, its stack and state
// are initialized to default values.
// Which means that state is zero initialized
// This function verifies that the stack pointer points to some location
// and if not then sets it up to point into stack with default offset
static llvm::Function *GetVerifyRegState(void) {
static llvm::Function *func = nullptr;
if (!func) {
func = CreateVerifyRegState();
}
return func;
}
// Implements a stub for an externally defined function in such a way that
// the external is explicitly called, and arguments from the modeled CPU
// state are passed into the external.
static llvm::Function *
ImplementExplicitArgsEntryPoint(const NativeFunction *cfg_func,
const std::string &name) {
auto func = gModule->getFunction(name);
if (!func) {
auto num_args = FLAGS_explicit_args_count;
// Get correct return type -> i32 for main
llvm::Type *ret_type = gWordType;
if (name == "main" || name == "_main") {
num_args = 3;
ret_type = llvm::Type::getInt32Ty(*gContext);
}
LOG(INFO) << "Generating explicit argument entrypoint function for " << name
<< ", calling into " << cfg_func->lifted_name;
std::vector arg_types(num_args, gWordType);
auto func_type = llvm::FunctionType::get(ret_type, arg_types, false);
func = llvm::Function::Create(func_type, llvm::GlobalValue::InternalLinkage,
name, gModule.get());
DCHECK_EQ(func->getName().str(), name);
}
if (!func->isDeclaration()) {
return func;
}
auto maybe_decl = anvill::FunctionDecl::Create(*func, gArch);
if (remill::IsError(maybe_decl)) {
LOG(FATAL) << remill::GetErrorString(maybe_decl);
}
auto &decl = remill::GetReference(maybe_decl);
decl.address = cfg_func->ea;
// We have the decompiled function, or at least, a prefix of it,
// so we'll invent a state structure and a stack frame and we'll
// call the lifted function with that. The lifted function will
// get inlined into this function.
auto block = llvm::BasicBlock::Create(*gContext, "", func);
llvm::IRBuilder<> ir(block);
// Invent a memory pointer.
const auto mem_ptr_type = gArch->MemoryPointerType();
llvm::Value *mem_ptr = llvm::Constant::getNullValue(mem_ptr_type);
const auto state_ptr = ir.CreateCall(GetVerifyRegState());
const auto pc = llvm::ConstantInt::get(gWordType, cfg_func->ea);
static remill::IntrinsicTable intrinsics(gModule);
// Store the function parameters either into the state struct
// or into memory (likely the stack).
auto arg_index = 0u;
for (auto &arg : func->args()) {
const auto ¶m_decl = decl.params[arg_index++];
mem_ptr = anvill::StoreNativeValue(&arg, param_decl, intrinsics, block,
state_ptr, mem_ptr);
}
llvm::Value *lifted_func_args[remill::kNumBlockArgs] = {};
lifted_func_args[remill::kStatePointerArgNum] = state_ptr;
lifted_func_args[remill::kMemoryPointerArgNum] = mem_ptr;
lifted_func_args[remill::kPCArgNum] = pc;
mem_ptr = ir.CreateCall(cfg_func->lifted_function, lifted_func_args);
llvm::Value *ret_val = nullptr;
if (decl.returns.size() == 1) {
ret_val = anvill::LoadLiftedValue(decl.returns.front(), intrinsics, block,
state_ptr, mem_ptr);
ir.SetInsertPoint(block);
} else if (1 < decl.returns.size()) {
ret_val = llvm::UndefValue::get(func->getReturnType());
auto index = 0u;
for (auto &ret_decl : decl.returns) {
auto partial_ret_val = anvill::LoadLiftedValue(ret_decl, intrinsics,
block, state_ptr, mem_ptr);
ir.SetInsertPoint(block);
unsigned indexes[] = {index};
ret_val = ir.CreateInsertValue(ret_val, partial_ret_val, indexes);
index += 1;
}
}
if (ret_val) {
ir.CreateRet(ret_val);
} else {
ir.CreateRetVoid();
}
return func;
}
static llvm::Function *GetOrCreateCallback(const NativeFunction *cfg_func,
const std::string &callback_name) {
if (cfg_func->function) {
return cfg_func->function;
}
CHECK_NOTNULL(cfg_func->lifted_function);
if (FLAGS_explicit_args) {
cfg_func->function =
ImplementExplicitArgsEntryPoint(cfg_func, callback_name);
} else {
cfg_func->function =
ImplementNativeToLiftedCallback(cfg_func, callback_name);
}
return cfg_func->function;
}
// Adapt a variadic function type to have the same signature, but have additional
// explicit "padding" arguments so that the function has at least `num_args`
// parameters.
static llvm::FunctionType *AdaptFunctionType(llvm::FunctionType *type,
unsigned num_args) {
std::vector param_types(
std::max(num_args, type->getNumParams()), gWordType);
auto i = 0u;
for (auto param_type : type->params()) {
param_types[i++] = param_type;
}
return llvm::FunctionType::get(type->getReturnType(), param_types, false);
}
// If `va_func` is not variadic, then this returns `va_func`, otherwise
// `va_func` is wrapped with a function that may take in additional explicit
// integer arguments, and then will pass those to `va_func`.
static llvm::Function *WrapVarArgsFunction(llvm::Function *va_func) {
if (!va_func->isVarArg()) {
return va_func;
}
auto func_type =
AdaptFunctionType(va_func->getFunctionType(), FLAGS_explicit_args_count);
auto wrapper_function =
llvm::Function::Create(func_type, llvm::GlobalValue::InternalLinkage,
va_func->getName() + "_novarargs", gModule.get());
std::vector params;
for (auto &arg : wrapper_function->args()) {
params.push_back(&arg);
}
llvm::IRBuilder<> ir(
llvm::BasicBlock::Create(*gContext, "", wrapper_function));
auto call = ir.CreateCall(va_func, params);
if (func_type->getReturnType()->isVoidTy()) {
ir.CreateRetVoid();
} else {
ir.CreateRet(call);
}
wrapper_function->setCallingConv(va_func->getCallingConv());
return wrapper_function;
}
// Implements a stub for an externally defined function in such a way that,
// when executed, this stub redirects control flow into the actual external
// function.
static void ImplementLiftedToNativeCallback(llvm::Function *callback_func,
llvm::Function *extern_func) {
callback_func->addFnAttr(llvm::Attribute::NoInline);
auto block = llvm::BasicBlock::Create(*gContext, "", callback_func);
// The third argument of lifted functions (including things like
// `__remill_function_call`) is the program counter. Make sure that
// the "real" address of the external is passed in as that third argument
// because it's likely that whatever was in the CFG makes no sense
// in the lifted code.
auto args = remill::LiftedFunctionArgs(block);
args[remill::kPCArgNum] =
llvm::ConstantExpr::getPtrToInt(extern_func, gWordType);
llvm::IRBuilder<> ir(block);
auto handler_call = ir.CreateCall(DetachCallValueFunc(), args);
ir.CreateRet(handler_call);
}
// Implements a stub for an externally defined function in such a way that
// the external is explicitly called, and arguments from the modeled CPU
// state are passed into the external.
static void ImplementExplicitArgsExitPoint(llvm::Function *callback_func,
llvm::Function *extern_func) {
extern_func = WrapVarArgsFunction(extern_func);
auto maybe_decl = anvill::FunctionDecl::Create(*extern_func, gArch);
if (remill::IsError(maybe_decl)) {
LOG(FATAL) << remill::GetErrorString(maybe_decl);
}
const auto &decl = remill::GetReference(maybe_decl);
remill::CloneBlockFunctionInto(callback_func);
auto block = &(callback_func->getEntryBlock());
auto pc = remill::NthArgument(callback_func, remill::kPCArgNum);
auto mem_ptr =
remill::NthArgument(callback_func, remill::kMemoryPointerArgNum);
auto state_ptr =
remill::NthArgument(callback_func, remill::kStatePointerArgNum);
llvm::IRBuilder<> ir(block);
llvm::Value *next_pc_ref = nullptr;
next_pc_ref = ir.CreateAlloca(
gWordType, llvm::Constant::getNullValue(gWordType), "next_pc");
ir.CreateStore(pc, next_pc_ref);
const auto mem_ptr_ref =
ir.CreateAlloca(mem_ptr->getType(), nullptr, "MEMORY");
const auto pc_ref = ir.CreateAlloca(gWordType, nullptr, "PC");
ir.CreateStore(mem_ptr, mem_ptr_ref);
ir.CreateStore(pc, pc_ref);
// Optimization can sometimes result in memory accesses being reordered w.r.t.
// external calls if they are inlined, so we want to prevent their inlining.
callback_func->removeFnAttr(llvm::Attribute::InlineHint);
callback_func->removeFnAttr(llvm::Attribute::AlwaysInline);
callback_func->addFnAttr(llvm::Attribute::NoInline);
if (FLAGS_stack_protector) {
callback_func->addFnAttr(llvm::Attribute::StackProtectReq);
}
callback_func->setLinkage(llvm::GlobalValue::InternalLinkage);
remill::IntrinsicTable intrinsics(gModule);
const auto new_mem_ptr =
decl.CallFromLiftedBlock(extern_func->getName().str(), intrinsics, block,
state_ptr, mem_ptr, true);
ir.CreateRet(new_mem_ptr);
}
} // namespace
llvm::Constant *NativeFunction::Pointer(void) const {
if (function) {
return function;
}
if (name.empty()) {
std::stringstream ss;
ss << "callback_" << lifted_name;
name = ss.str();
}
module->AddNameToAddress(name, ea);
if (decl) {
decl->DeclareInModule(name, *gModule);
}
function = GetOrCreateCallback(this, name);
// Always set as external until we've lifted the data segments.
function->setLinkage(llvm::GlobalValue::ExternalLinkage);
function->removeFnAttr(llvm::Attribute::InlineHint);
function->removeFnAttr(llvm::Attribute::AlwaysInline);
function->removeFnAttr(llvm::Attribute::ReadNone);
function->removeFnAttr(llvm::Attribute::ReadOnly);
function->removeFnAttr(llvm::Attribute::ArgMemOnly);
function->addFnAttr(llvm::Attribute::NoInline);
function->addFnAttr(llvm::Attribute::NoBuiltin);
return function;
}
llvm::Constant *NativeFunction::Address(void) const {
return llvm::ConstantExpr::getPtrToInt(Pointer(), gWordType);
}
// Get a callback function for an external function that can be referenced by
// internal code.
llvm::Function *GetLiftedToNativeExitPoint(const NativeFunction *cfg_func) {
if (cfg_func->callable_lifted_function) {
return cfg_func->callable_lifted_function;
}
// Stub that will marshal lifted state into the native state.
const auto callback_func = llvm::Function::Create(
gArch->LiftedFunctionType(), llvm::GlobalValue::InternalLinkage,
cfg_func->lifted_name, gModule.get());
cfg_func->callable_lifted_function = callback_func;
const auto extern_func = llvm::dyn_cast(cfg_func->Pointer());
// Pass through the memory and state pointers, and pass the destination
// (native external function address) as the PC argument.
if (FLAGS_explicit_args) {
ImplementExplicitArgsExitPoint(callback_func, extern_func);
// We are going from lifted to native code. We don't need an assembly stub
// because `__remill_function_call` already does the right thing.
} else {
ImplementLiftedToNativeCallback(callback_func, extern_func);
}
if (cfg_func->lifted_function && !FLAGS_pc_annotation.empty()) {
legacy::AnnotateInsts(cfg_func->lifted_function, cfg_func->ea);
}
return callback_func;
}
// Get a function that goes from the current lifted state into native state,
// where we don't know where the native destination actually is.
llvm::Function *GetLiftedToNativeExitPoint(ExitPointKind kind) {
if (!FLAGS_explicit_args) {
switch (kind) {
case kExitPointJump: return gModule->getFunction("__remill_jump");
case kExitPointFunctionCall:
return gModule->getFunction("__remill_function_call");
}
}
// Using explicit args mode, so get a callback function that casts the
// program counter into a function pointer and then calls it.
static llvm::Function *callback_func = nullptr;
if (callback_func) {
return callback_func;
}
std::vector arg_types;
arg_types.insert(arg_types.end(), FLAGS_explicit_args_count, gWordType);
// We'll create and destroy this function a few times just for the sake of
// being able to get/use an anvill::FunctionDecl, and later, to get a function
// pointer of the right type.
auto func = llvm::Function::Create(
llvm::FunctionType::get(gWordType, arg_types, false),
llvm::GlobalValue::InternalLinkage, "__mcsema_do_detach_call_value",
gModule.get());
auto maybe_decl = anvill::FunctionDecl::Create(*func, gArch);
func->eraseFromParent();
if (remill::IsError(maybe_decl)) {
LOG(FATAL) << "Unable to create exit point: "
<< remill::GetErrorString(maybe_decl);
return nullptr;
}
const auto &decl = remill::GetReference(maybe_decl);
func = decl.DeclareInModule("__mcsema_do_detach_call_value", *gModule, true);
CHECK_NOTNULL(func);
// Stub that will marshal lifted state into the native state.
callback_func = llvm::Function::Create(
gArch->LiftedFunctionType(), llvm::GlobalValue::PrivateLinkage,
"__mcsema_detach_call_value", gModule.get());
remill::CloneBlockFunctionInto(callback_func);
auto block = &(callback_func->getEntryBlock());
auto pc = remill::NthArgument(callback_func, remill::kPCArgNum);
auto mem_ptr =
remill::NthArgument(callback_func, remill::kMemoryPointerArgNum);
auto state_ptr =
remill::NthArgument(callback_func, remill::kStatePointerArgNum);
llvm::IRBuilder<> ir(block);
auto pc_as_func_ptr = ir.CreateIntToPtr(pc, func->getType());
remill::IntrinsicTable intrinsics(gModule);
const auto new_mem_ptr =
decl.CallFromLiftedBlock("__mcsema_do_detach_call_value", intrinsics,
block, state_ptr, mem_ptr, true);
func->replaceAllUsesWith(pc_as_func_ptr);
func->eraseFromParent();
ir.CreateRet(new_mem_ptr);
remill::Annotate(callback_func);
callback_func->removeFnAttr(llvm::Attribute::NoInline);
callback_func->addFnAttr(llvm::Attribute::InlineHint);
callback_func->addFnAttr(llvm::Attribute::AlwaysInline);
callback_func->addFnAttr(llvm::Attribute::NoUnwind);
return callback_func;
}
} // namespace mcsema