From ef5aa7a9b58116a6943d47e3b5a18aa02ac0205a Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Fri, 31 Oct 2025 12:21:35 +0100 Subject: [PATCH] Add more functionality to remill-lift to make debugging easier --- bin/lift/Lift.cpp | 506 +++++++++++++++++++++++++++++++++------------- 1 file changed, 371 insertions(+), 135 deletions(-) diff --git a/bin/lift/Lift.cpp b/bin/lift/Lift.cpp index 3cc0afbc..96ea7135 100644 --- a/bin/lift/Lift.cpp +++ b/bin/lift/Lift.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -54,35 +55,38 @@ DEFINE_string(os, REMILL_OS, "Operating system name of the code being " "translated. Valid OSes: linux, macos, windows, solaris."); -DEFINE_string(arch, REMILL_ARCH, +DEFINE_string(arch, "", "Architecture of the code being translated. " "Valid architectures: x86, amd64 (with or without " "`_avx` or `_avx512` appended), aarch64, aarch32"); -DEFINE_uint64(address, 0, - "Address at which we should assume the bytes are" +DEFINE_uint64(address, -1, + "Address at which we should assume the bytes are " "located in virtual memory."); -DEFINE_uint64(entry_address, 0, +DEFINE_uint64(entry_address, -1, "Address of instruction that should be " "considered the entrypoint of this code. " - "Defaults to the value of --address."); + "Defaults to the value of -address."); DEFINE_string(bytes, "", "Hex-encoded byte string to lift."); +DEFINE_string( + ir_pre_out, "", + "Path to the file where the LLVM IR (before optimization) should be saved"); + DEFINE_string(ir_out, "", "Path to file where the LLVM IR should be saved."); DEFINE_string(bc_out, "", "Path to file where the LLVM bitcode should be " "saved."); -DEFINE_string(slice_inputs, "", - "Comma-separated list of registers to treat as inputs."); -DEFINE_string(slice_outputs, "", - "Comma-separated list of registers to treat as outputs."); +DEFINE_string(signature, "", "Function signature \"reg_out(reg_in,...)\""); +DEFINE_bool(mute_state_escape, false, "Mute state escape"); +DEFINE_bool(symbolic_regs, false, "Set registers to a symbolic value"); using Memory = std::map; -// Unhexlify the data passed to `--bytes`, and fill in `memory` with each +// Unhexlify the data passed to `-bytes`, and fill in `memory` with each // such byte. static Memory UnhexlifyInputBytes(uint64_t addr_mask) { Memory memory; @@ -94,24 +98,24 @@ static Memory UnhexlifyInputBytes(uint64_t addr_mask) { if (parsed_to != &(nibbles[2])) { std::cerr << "Invalid hex byte value '" << nibbles - << "' specified in --bytes." << std::endl; + << "' specified in -bytes." << std::endl; exit(EXIT_FAILURE); } auto byte_addr = FLAGS_address + (i / 2); auto masked_addr = byte_addr & addr_mask; - // Make sure that if a really big number is specified for `--address`, + // Make sure that if a really big number is specified for `-address`, // that we don't accidentally wrap around and start filling out low // byte addresses. if (masked_addr < byte_addr) { - std::cerr << "Too many bytes specified to --bytes, would result " - << "in a 32-bit overflow."; + std::cerr + << "Too many bytes specified to -bytes, would result in a 32-bit overflow."; exit(EXIT_FAILURE); } else if (masked_addr < FLAGS_address) { - std::cerr << "Too many bytes specified to --bytes, would result " - << "in a 64-bit overflow."; + std::cerr + << "Too many bytes specified to -bytes, would result in a 64-bit overflow."; exit(EXIT_FAILURE); } @@ -121,13 +125,20 @@ static Memory UnhexlifyInputBytes(uint64_t addr_mask) { return memory; } -class SimpleTraceManager : public remill::TraceManager { - public: - virtual ~SimpleTraceManager(void) = default; +struct SimpleTraceManager : remill::TraceManager { + const remill::Arch *arch = nullptr; + llvm::Module *module = nullptr; + Memory &memory; + uint64_t entry = 0; + std::unordered_map traces; - explicit SimpleTraceManager(Memory &memory_) : memory(memory_) {} + SimpleTraceManager(const remill::Arch *arch, llvm::Module *module, + Memory &memory, uint64_t entry) + : arch(arch), + module(module), + memory(memory), + entry(entry) {} - protected: // Called when we have lifted, i.e. defined the contents, of a new trace. // The derived class is expected to do something useful with this. void SetLiftedTraceDefinition(uint64_t addr, @@ -135,6 +146,26 @@ class SimpleTraceManager : public remill::TraceManager { traces[addr] = lifted_func; } + // Get a definition for a lifted trace. + // + // NOTE: This is permitted to return a function from an arbitrary module. + llvm::Function *GetLiftedTraceDefinition(uint64_t addr) override { + + // The entry function needs to be lifted by the TraceLifter + if (addr == entry) { + return nullptr; + } + + // The get_trace_decl in TraceLifter creates a declaration for us. + // Instead of providing an implementation, we keep it extern. + auto name = TraceName(addr); + auto fn = module->getFunction(name); + if (fn == nullptr) { + fn = arch->DeclareLiftedFunction(name, module); + } + return fn; + } + // Get a declaration for a lifted trace. The idea here is that a derived // class might have additional global info available to them that lets // them declare traces ahead of time. In order to distinguish between @@ -143,19 +174,7 @@ class SimpleTraceManager : public remill::TraceManager { // // NOTE: This is permitted to return a function from an arbitrary module. llvm::Function *GetLiftedTraceDeclaration(uint64_t addr) override { - auto trace_it = traces.find(addr); - if (trace_it != traces.end()) { - return trace_it->second; - } else { - return nullptr; - } - } - - // Get a definition for a lifted trace. - // - // NOTE: This is permitted to return a function from an arbitrary module. - llvm::Function *GetLiftedTraceDefinition(uint64_t addr) override { - return GetLiftedTraceDeclaration(addr); + return remill::TraceManager::GetLiftedTraceDeclaration(addr); } // Try to read an executable byte of memory. Returns `true` of the byte @@ -164,16 +183,14 @@ class SimpleTraceManager : public remill::TraceManager { bool TryReadExecutableByte(uint64_t addr, uint8_t *byte) override { auto byte_it = memory.find(addr); if (byte_it != memory.end()) { - *byte = byte_it->second; + if (byte != nullptr) { + *byte = byte_it->second; + } return true; } else { return false; } } - - public: - Memory &memory; - std::unordered_map traces; }; // Looks for calls to a function like `__remill_function_return`, and @@ -219,6 +236,62 @@ static void SetVersion(void) { google::SetVersionString(ss.str()); } +struct Argument { + bool is_memory = false; + size_t size = 0; + std::string reg; + int64_t offset = 0; + + static int64_t parse_hex(const std::string &argument) { + int64_t hex_value = 0; + std::istringstream iss(argument); + iss >> std::hex >> hex_value; + return hex_value; + } + + static Argument parse(const std::string &argument) { + Argument out; + auto mem_idx = argument.find('['); + if (mem_idx != std::string::npos) { + out.is_memory = true; + if (mem_idx > 0) { + out.size = parse_hex(argument.substr(0, mem_idx)); + } else { + out.size = 0; + } + auto sign_idx = argument.find_first_of("+-"); + if (sign_idx == std::string::npos) { + out.reg = argument.substr(mem_idx + 1, argument.size() - mem_idx - 2); + out.offset = 0; + } else { + out.reg = argument.substr(mem_idx + 1, sign_idx - mem_idx - 1); + out.offset = parse_hex( + argument.substr(sign_idx, argument.size() - sign_idx - 1)); + } + } else { + out.reg = argument; + } + for (auto &ch : out.reg) { + if (ch >= 'a' && ch <= 'z') { + ch -= 'a' - 'A'; + } + } + return out; + } + + void dump() { + if (is_memory) { + if (offset < 0) { + printf("%zu:['%s'%ld]\n", size, reg.c_str(), offset); + } else { + printf("%zu:['%s'+%ld]\n", size, reg.c_str(), offset); + } + } else { + printf("%s\n", reg.c_str()); + } + } +}; + int main(int argc, char *argv[]) { SetVersion(); google::ParseCommandLineFlags(&argc, &argv, true); @@ -226,38 +299,49 @@ int main(int argc, char *argv[]) { if (FLAGS_bytes.empty()) { - std::cerr << "Please specify a sequence of hex bytes to --bytes." + std::cerr << "Please specify a sequence of hex bytes to -bytes." + << std::endl; + return EXIT_FAILURE; + } else if (FLAGS_bytes.size() % 2) { + std::cerr << "Please specify an even number of nibbles to -bytes." << std::endl; return EXIT_FAILURE; } - if (FLAGS_bytes.size() % 2) { - std::cerr << "Please specify an even number of nibbles to --bytes." - << std::endl; + if (FLAGS_arch.empty()) { + std::cerr + << "No architecture specified. Valid architectures: x86, amd64 (with or without " + "`_avx` or `_avx512` appended), aarch64, aarch32" + << std::endl; return EXIT_FAILURE; } - if (!FLAGS_entry_address) { + if (FLAGS_address == (uint64_t) -1) { + FLAGS_address = 0; + } + + if (FLAGS_entry_address == (uint64_t) -1) { FLAGS_entry_address = FLAGS_address; } - // Make sure `--address` and `--entry_address` are in-bounds for the target + // Make sure `-address` and `-entry_address` are in-bounds for the target // architecture's address size. llvm::LLVMContext context; - auto arch = remill::Arch::Get(context, FLAGS_os, FLAGS_arch); + auto arch = remill::Arch::Get( + context, FLAGS_os, + FLAGS_arch); // TODO: what happens with invalid arguments? const uint64_t addr_mask = ~0ULL >> (64UL - arch->address_size); if (FLAGS_address != (FLAGS_address & addr_mask)) { std::cerr << "Value " << std::hex << FLAGS_address - << " passed to --address does not fit into 32-bits. Did mean" - << " to specify a 64-bit architecture to --arch?" << std::endl; + << " passed to -address does not fit into 32-bits. Did mean" + << " to specify a 64-bit architecture to -arch?" << std::endl; return EXIT_FAILURE; } if (FLAGS_entry_address != (FLAGS_entry_address & addr_mask)) { - std::cerr - << "Value " << std::hex << FLAGS_entry_address - << " passed to --entry_address does not fit into 32-bits. Did mean" - << " to specify a 64-bit architecture to --arch?" << std::endl; + std::cerr << "Value " << std::hex << FLAGS_entry_address + << " passed to -entry_address does not fit into 32-bits. Did mean" + << " to specify a 64-bit architecture to -arch?" << std::endl; return EXIT_FAILURE; } @@ -266,7 +350,13 @@ int main(int argc, char *argv[]) { const auto mem_ptr_type = arch->MemoryPointerType(); Memory memory = UnhexlifyInputBytes(addr_mask); - SimpleTraceManager manager(memory); + SimpleTraceManager manager(arch.get(), module.get(), memory, + FLAGS_entry_address); + if (!manager.TryReadExecutableByte(FLAGS_entry_address, nullptr)) { + std::cerr << "No executable code at address 0x" << std::hex + << FLAGS_entry_address << std::endl; + return EXIT_FAILURE; + } remill::IntrinsicTable intrinsics(module.get()); @@ -274,10 +364,75 @@ int main(int argc, char *argv[]) { remill::TraceLifter trace_lifter(arch.get(), manager); - // Lift all discoverable traces starting from `--entry_address` into + // Lift all discoverable traces starting from `-entry_address` into // `module`. trace_lifter.Lift(FLAGS_entry_address); + // Remove llvm.compiler.used to not preserve unused semantics + auto compilerUsed = module->getGlobalVariable("llvm.compiler.used", true); + if (compilerUsed != nullptr) { + compilerUsed->eraseFromParent(); + } + + // Remove ISEL_ globals that contain pointers to the semantic functions + std::vector erase; + for (auto &G : module->globals()) { + if (G.getName().find("ISEL_") == 0) { + erase.push_back(&G); + } + } + for (auto G : erase) { + G->eraseFromParent(); + } + + // Remove function that keeps the references to unused intrinsics + auto remillIntrinsics = module->getFunction("__remill_intrinsics"); + if (remillIntrinsics != nullptr) { + remillIntrinsics->eraseFromParent(); + } + + // Remove the implementation of the __remill_sync_hyper_call from the bitcode, because + // after inlining things get very confusing if this is actually called. + // TODO: this should probably be removed + auto hyperCall = module->getFunction("__remill_sync_hyper_call"); + if (hyperCall != nullptr) { + auto name = hyperCall->getName(); + auto ty = hyperCall->getFunctionType(); + auto newFn = module->getOrInsertFunction(name.str() + "_", ty); + hyperCall->replaceAllUsesWith(newFn.getCallee()); + hyperCall->eraseFromParent(); + newFn.getCallee()->setName(name); + } + + // A lot of intrinsic functions are (incorrectly) marked as [[gnu::const]]. + // This causes problems where optimizer's assumptions are violated when an + // implementation is provided. To work around this we remove these attributes + // from the functions and from the call sites. + // Another workaround is to first do a separate inline pass and then O3. + // NOTE: This was fixed in https://github.com/lifting-bits/remill/commit/7f091d42 + for (auto &function : module->functions()) { + if (function.getName().find("__remill_") != 0) { + continue; + } + + function.removeFnAttr(llvm::Attribute::ReadNone); + for (auto &argument : function.args()) { + argument.removeAttr(llvm::Attribute::ReadNone); + } + for (auto user : function.users()) { + if (auto call = llvm::dyn_cast(user)) { + call->removeFnAttr(llvm::Attribute::ReadNone); + } + } + } + + // Dump the pre-optimization IR + if (!FLAGS_ir_pre_out.empty()) { + if (!remill::StoreModuleIRToFile(module.get(), FLAGS_ir_pre_out, true)) { + LOG(ERROR) << "Could not save LLVM IR to " << FLAGS_ir_pre_out; + } + } + // Optimize the module, but with a particular focus on only the functions // that we actually lifted. remill::OptimizationGuide guide = {}; @@ -290,8 +445,6 @@ int main(int argc, char *argv[]) { arch->PrepareModuleDataLayout(&dest_module); llvm::Function *entry_trace = nullptr; - const auto make_slice = - !FLAGS_slice_inputs.empty() || !FLAGS_slice_outputs.empty(); // Move the lifted code into a new module. This module will be much smaller // because it won't be bogged down with all of the semantics definitions. @@ -305,7 +458,7 @@ int main(int argc, char *argv[]) { // If we are providing a prototype, then we'll be re-optimizing the new // module, and we want everything to get inlined. - if (make_slice) { + if (!FLAGS_signature.empty()) { lifted_entry.second->setLinkage(llvm::GlobalValue::InternalLinkage); lifted_entry.second->removeFnAttr(llvm::Attribute::NoInline); lifted_entry.second->addFnAttr(llvm::Attribute::InlineHint); @@ -314,84 +467,171 @@ int main(int argc, char *argv[]) { } // We have a prototype, so go create a function that will call our entrypoint. - if (make_slice) { + if (!FLAGS_signature.empty()) { CHECK_NOTNULL(entry_trace); - llvm::SmallVector input_reg_names; - llvm::SmallVector output_reg_names; - llvm::StringRef(FLAGS_slice_inputs) - .split(input_reg_names, ',', -1, false /* KeepEmpty */); - llvm::StringRef(FLAGS_slice_outputs) - .split(output_reg_names, ',', -1, false /* KeepEmpty */); + // Set the entry trace as internal so it can be removed during optimizations + entry_trace->setLinkage(llvm::Function::InternalLinkage); - CHECK(!(input_reg_names.empty() && output_reg_names.empty())) - << "Empty lists passed to both --slice_inputs and --slice_outputs"; + std::string signature; + for (auto ch : FLAGS_signature) { + if (ch >= 'a' && ch <= 'z') { + ch -= 'a' - 'A'; + } + if (ch != ' ') { + signature.push_back(ch); + } + } + auto paren_idx = signature.find('('); + CHECK(paren_idx != std::string::npos && signature.back() == ')') + << "Invalid function signature"; + + auto output_reg_name = signature.substr(0, paren_idx); + if (output_reg_name == "void") { + output_reg_name.clear(); + } + std::vector input_args; + std::string temp; + for (size_t i = paren_idx + 1; i < signature.size() - 1; i++) { + auto ch = signature[i]; + if (ch == ',') { + input_args.push_back(Argument::parse(temp)); + temp.clear(); + } else { + temp.push_back(ch); + } + } + if (!temp.empty()) { + input_args.push_back(Argument::parse(temp)); + } // Use the registers to build a function prototype. llvm::SmallVector arg_types; - arg_types.push_back(mem_ptr_type); + for (auto &arg : input_args) { + const auto input_reg = arch->RegisterByName(arg.reg); + CHECK(input_reg != nullptr) + << "Invalid register name '" << arg.reg << "' used in signature '" + << FLAGS_signature << "'"; - for (auto ®_name : input_reg_names) { - const auto reg = arch->RegisterByName(reg_name.str()); - CHECK(reg != nullptr) - << "Invalid register name '" << reg_name.str() - << "' used in input slice list '" << FLAGS_slice_inputs << "'"; - - arg_types.push_back(reg->type); + if (arg.size == 0) { + arg.size = input_reg->size; + } + auto arg_type = llvm::Type::getIntNTy(context, arg.size * 8); + arg_types.push_back(arg_type); } - const auto first_output_reg_index = arg_types.size(); - - // Outputs are "returned" by pointer through arguments. - for (auto ®_name : output_reg_names) { - const auto reg = arch->RegisterByName(reg_name.str()); - CHECK(reg != nullptr) - << "Invalid register name '" << reg_name.str() - << "' used in output slice list '" << FLAGS_slice_outputs << "'"; - - arg_types.push_back(llvm::PointerType::get(context, 0)); + auto return_type = llvm::Type::getVoidTy(context); + if (!output_reg_name.empty()) { + const auto output_reg = arch->RegisterByName(output_reg_name); + CHECK(output_reg != nullptr) + << "Invalid register name '" << output_reg_name << "'"; + return_type = output_reg->type; } - - const auto state_type = arch->StateStructType(); const auto func_type = - llvm::FunctionType::get(mem_ptr_type, arg_types, false); - const auto func = llvm::Function::Create( - func_type, llvm::GlobalValue::ExternalLinkage, "slice", &dest_module); + llvm::FunctionType::get(return_type, arg_types, false); + const auto func = + llvm::Function::Create(func_type, llvm::GlobalValue::ExternalLinkage, + "call_" + entry_trace->getName(), &dest_module); + + // HACK: This is a workaround for the issue with the DSEPass making false assumptions + func->addFnAttr("disable-tail-calls", "true"); + + // Get the program counter and stack pointer registers. + const remill::Register *pc_reg = + arch->RegisterByName(arch->ProgramCounterRegisterName()); + const remill::Register *sp_reg = + arch->RegisterByName(arch->StackPointerRegisterName()); + + CHECK(pc_reg != nullptr) + << "Could not find the register in the state structure " + << "associated with the program counter."; + + CHECK(sp_reg != nullptr) + << "Could not find the register in the state structure " + << "associated with the stack pointer."; // Store all of the function arguments (corresponding with specific registers) // into the stack-allocated `State` structure. auto entry = llvm::BasicBlock::Create(context, "", func); llvm::IRBuilder<> ir(entry); + const auto state_type = arch->StateStructType(); const auto state_ptr = ir.CreateAlloca(state_type); - const remill::Register *pc_reg = - arch->RegisterByName(arch->ProgramCounterRegisterName()); + auto CreateSymbolicReg = [&](const remill::Register *reg, + const std::string &name) { + std::string symbol_name = "symbolic_" + name; + auto symbolic_fn = dest_module.getOrInsertFunction( + "__remill_" + symbol_name, llvm::FunctionType::get(reg->type, false)); + auto fn = llvm::dyn_cast(symbolic_fn.getCallee()); - CHECK(pc_reg != nullptr) - << "Could not find the register in the state structure " - << "associated with the program counter."; + // Allow the optimizer to delete calls if the result is not used + fn->setDoesNotAccessMemory(); + fn->setDoesNotThrow(); + fn->addFnAttr(llvm::Attribute::WillReturn); + + auto call = ir.CreateCall(symbolic_fn, {}, symbol_name); + const auto reg_ptr = reg->AddressOf(state_ptr, entry); + ir.CreateStore(call, reg_ptr); + }; + + // Store symbolic values into general purpose registers + if (FLAGS_symbolic_regs) { + arch->ForEachRegister([&](const remill::Register *reg) { + if (reg->parent == nullptr) { + CreateSymbolicReg(reg, reg->name); + } + }); + } // Store the program counter into the state. - const auto pc_reg_ptr = pc_reg->AddressOf(state_ptr, entry); const auto trace_pc = llvm::ConstantInt::get(pc_reg->type, FLAGS_entry_address, false); ir.SetInsertPoint(entry); - ir.CreateStore(trace_pc, pc_reg_ptr); + ir.CreateStore(trace_pc, pc_reg->AddressOf(state_ptr, entry)); - auto args_it = func->arg_begin(); - for (auto ®_name : input_reg_names) { - const auto reg = arch->RegisterByName(reg_name.str()); - auto &arg = *++args_it; // Pre-increment, as first arg is memory pointer. - arg.setName(reg_name); - CHECK_EQ(arg.getType(), reg->type); - auto reg_ptr = reg->AddressOf(state_ptr, entry); - ir.SetInsertPoint(entry); - ir.CreateStore(&arg, reg_ptr); + // Set up symbolic globals + CreateSymbolicReg(sp_reg, "STACK"); + auto gsbase_reg = arch->RegisterByName("GSBASE"); + if (gsbase_reg != nullptr) { + CreateSymbolicReg(gsbase_reg, "GSBASE"); + } + auto fsbase_reg = arch->RegisterByName("FSBASE"); + if (fsbase_reg != nullptr) { + CreateSymbolicReg(fsbase_reg, "FSBASE"); } - llvm::Value *mem_ptr = &*func->arg_begin(); + llvm::Value *mem_ptr = llvm::UndefValue::get(mem_ptr_type); + // Store the argument registers into the state + auto args_it = func->arg_begin(); + for (auto &input_arg : input_args) { + const auto reg = arch->RegisterByName(input_arg.reg); + auto reg_ptr = reg->AddressOf(state_ptr, entry); + auto &arg = *args_it++; + + ir.SetInsertPoint(entry); + if (input_arg.is_memory) { + arg.setName("arg_mem_" + input_arg.reg + "_" + + llvm::utohexstr(input_arg.offset)); + auto helper_name = + "__remill_write_memory_" + std::to_string(input_arg.size * 8); + auto orig_memory_helper = module->getFunction(helper_name); + CHECK(orig_memory_helper != nullptr) + << "Could not find memory helper for " << helper_name; + auto memory_helper = dest_module.getOrInsertFunction( + helper_name, orig_memory_helper->getFunctionType()); + auto reg_value = ir.CreateLoad(reg->type, reg_ptr); + auto arg_ptr = ir.CreateAdd( + reg_value, llvm::ConstantInt::get(reg->type, input_arg.offset)); + ir.CreateCall(memory_helper, {mem_ptr, arg_ptr, &arg}); + } else { + arg.setName("arg_" + input_arg.reg); + ir.CreateStore(&arg, reg_ptr); + } + } + + // Call the lifted function llvm::Value *trace_args[remill::kNumBlockArgs] = {}; trace_args[remill::kStatePointerArgNum] = state_ptr; trace_args[remill::kMemoryPointerArgNum] = mem_ptr; @@ -401,41 +641,37 @@ int main(int argc, char *argv[]) { mem_ptr = ir.CreateCall(entry_trace, trace_args); - // Go read all output registers out of the state and store them - // into the output parameters. - args_it = func->arg_begin(); - for (size_t i = 0, j = 0; i < func->arg_size(); ++i, ++args_it) { - if (i < first_output_reg_index) { - continue; - } - - const auto ®_name = output_reg_names[j++]; - const auto reg = arch->RegisterByName(reg_name.str()); - auto &arg = *args_it; - arg.setName(reg_name + "_output"); - - auto reg_ptr = reg->AddressOf(state_ptr, entry); - ir.SetInsertPoint(entry); - ir.CreateStore(ir.CreateLoad(reg->type, reg_ptr), &arg); + // Read and return the output register + if (!output_reg_name.empty()) { + const auto out_reg = arch->RegisterByName(output_reg_name); + auto out_reg_ptr = out_reg->AddressOf(state_ptr, entry); + ir.CreateRet(ir.CreateLoad(out_reg->type, out_reg_ptr)); + } else { + ir.CreateRetVoid(); } - // Return the memory pointer, so that all memory accesses are - // preserved. - ir.CreateRet(mem_ptr); - - // We want the stack-allocated `State` to be subject to scalarization - // and mem2reg, but to "encourage" that, we need to prevent the - // `alloca`d `State` from escaping. - MuteStateEscape(&dest_module, "__remill_error"); - MuteStateEscape(&dest_module, "__remill_function_call"); - MuteStateEscape(&dest_module, "__remill_function_return"); - MuteStateEscape(&dest_module, "__remill_jump"); - MuteStateEscape(&dest_module, "__remill_missing_block"); + // NOTE: Doing this prevents the helpers implementation from working properly, + // which is why this is disabled per default. + if (FLAGS_mute_state_escape) { + // We want the stack-allocated `State` to be subject to scalarization + // and mem2reg, but to "encourage" that, we need to prevent the + // `alloca`d `State` from escaping. + MuteStateEscape(&dest_module, "__remill_error"); + MuteStateEscape(&dest_module, "__remill_function_call"); + MuteStateEscape(&dest_module, "__remill_function_return"); + MuteStateEscape(&dest_module, "__remill_jump"); + MuteStateEscape(&dest_module, "__remill_missing_block"); + } + // Optimize the module to inline everything guide.slp_vectorize = true; guide.loop_vectorize = true; - CHECK(remill::VerifyModule(&dest_module)); + auto check = remill::VerifyModuleMsg(&dest_module); + if (check) { + llvm::errs() << "Verification error: " << *check; + CHECK(false); + } remill::OptimizeBareModule(&dest_module, guide); }