mib updated this revision to Diff 216747. mib marked 16 inline comments as done. mib added a comment.
Add doxygen documentation. Move `ArgumentMetadata` struct inside `BreakpointInjectedSite`. Change string format for logging. Make `$__lldb_create_args_struct` generation architecture-independent. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D66249/new/ https://reviews.llvm.org/D66249 Files: lldb/include/lldb/API/SBBreakpoint.h lldb/include/lldb/API/SBBreakpointLocation.h lldb/include/lldb/Breakpoint/Breakpoint.h lldb/include/lldb/Breakpoint/BreakpointInjectedSite.h lldb/include/lldb/Breakpoint/BreakpointLocation.h lldb/include/lldb/Breakpoint/BreakpointOptions.h lldb/include/lldb/Breakpoint/BreakpointSite.h lldb/include/lldb/Core/Disassembler.h lldb/include/lldb/Core/Opcode.h lldb/include/lldb/Expression/Expression.h lldb/include/lldb/Expression/ExpressionVariable.h lldb/include/lldb/Expression/LLVMUserExpression.h lldb/include/lldb/Symbol/VariableList.h lldb/include/lldb/Target/ABI.h lldb/include/lldb/Target/ExecutionContextScope.h lldb/include/lldb/Target/Process.h lldb/include/lldb/Target/Target.h lldb/include/lldb/lldb-enumerations.h lldb/include/lldb/lldb-forward.h lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_conditions/TestBreakpointConditions.py lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/Makefile lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/TestFastConditionalBreakpoints.py lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/main.c lldb/scripts/interface/SBBreakpoint.i lldb/scripts/interface/SBBreakpointLocation.i lldb/source/API/SBBreakpoint.cpp lldb/source/API/SBBreakpointLocation.cpp lldb/source/Breakpoint/Breakpoint.cpp lldb/source/Breakpoint/BreakpointInjectedSite.cpp lldb/source/Breakpoint/BreakpointLocation.cpp lldb/source/Breakpoint/BreakpointOptions.cpp lldb/source/Breakpoint/BreakpointSite.cpp lldb/source/Breakpoint/CMakeLists.txt lldb/source/Commands/CommandObjectBreakpoint.cpp lldb/source/Commands/Options.td lldb/source/Expression/LLVMUserExpression.cpp lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.cpp lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.h lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h lldb/source/Symbol/ClangASTContext.cpp lldb/source/Target/Process.cpp
Index: lldb/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -1612,9 +1612,22 @@ return error; } +lldb::break_id_t +Process::FallbackToRegularBreakpointSite(const BreakpointLocationSP &owner, + bool use_hardware, Log *log, + const char *error) { + LLDB_LOG(log, error); + LLDB_LOG(log, "Disabling JIT-ed condition and falling back to regular " + "conditional breakpoint"); + owner->SetInjectCondition(false); + return CreateBreakpointSite(owner, use_hardware); +} + lldb::break_id_t Process::CreateBreakpointSite(const BreakpointLocationSP &owner, bool use_hardware) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_JIT_LOADER)); + addr_t load_addr = LLDB_INVALID_ADDRESS; bool show_error = true; @@ -1675,11 +1688,72 @@ if (bp_site_sp) { bp_site_sp->AddOwner(owner); + + if (owner->GetInjectCondition()) { + BreakpointSite *bp_site = bp_site_sp.get(); + + BreakpointInjectedSite *bp_jitted_site_sp = + llvm::dyn_cast<BreakpointInjectedSite>(bp_site); + bp_jitted_site_sp->BuildConditionExpression(); + } + owner->SetBreakpointSite(bp_site_sp); return bp_site_sp->GetID(); } else { - bp_site_sp.reset(new BreakpointSite(&m_breakpoint_site_list, owner, - load_addr, use_hardware)); + if (owner->GetInjectCondition() && GetABI()->ImplementsJIT()) { + // Build user expression's IR from condition + BreakpointInjectedSite *bp_injected_site = new BreakpointInjectedSite( + &m_breakpoint_site_list, owner, load_addr); + + std::string error; + // Setup a call before the copied instructions + if (!bp_injected_site->BuildConditionExpression()) { + error = "FCB: Couldn't build the condition expression"; + return FallbackToRegularBreakpointSite(owner, use_hardware, log, + error.c_str()); + } + + size_t instrs_size = SaveInstructions(owner->GetAddress()); + + if (!instrs_size) { + error = "FCB: Couldn't save instructions"; + + return FallbackToRegularBreakpointSite(owner, use_hardware, log, + error.c_str()); + } + + const lldb::addr_t cond_expr_addr = + bp_injected_site->GetConditionExpressionAddress(); + const lldb::addr_t util_func_addr = + bp_injected_site->GetUtilityFunctionAddress(); + + if (!GetABI()->SetupFastConditionalBreakpointTrampoline( + instrs_size, m_overriden_instructions, load_addr, + util_func_addr, cond_expr_addr)) { + error = "FCB: Couldn't setup trampoline"; + + return FallbackToRegularBreakpointSite(owner, use_hardware, log, + error.c_str()); + } + + addr_t trap_addr = bp_injected_site->GetTrapAddress(); + + if (trap_addr == LLDB_INVALID_ADDRESS) { + error = "FCB: Couldn't get trap address"; + return FallbackToRegularBreakpointSite(owner, use_hardware, log, + error.c_str()); + } + + // bp_site_sp.reset(bp_jitted_site); + bp_site_sp.reset(new BreakpointSite(&m_breakpoint_site_list, owner, + trap_addr, use_hardware)); + + bp_site_sp->AddOwner(owner); + } else { + + bp_site_sp.reset(new BreakpointSite(&m_breakpoint_site_list, owner, + load_addr, use_hardware)); + } if (bp_site_sp) { Status error = EnableBreakpointSite(bp_site_sp.get()); if (error.Success()) { @@ -1702,6 +1776,84 @@ return LLDB_INVALID_BREAK_ID; } +size_t Process::SaveInstructions(Address &address) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_JIT_LOADER)); + + TargetSP target_sp = m_target_wp.lock(); + const char *plugin_name = nullptr; + const char *flavor = nullptr; + const bool prefer_file_cache = true; + + Function *function = address.CalculateSymbolContextFunction(); + + if (!function) { + LLDB_LOG(log, "JIT: No function in the SymbolContext"); + return 0; + } + + lldb::addr_t addr = address.GetCallableLoadAddress(target_sp.get()); + + const AddressRange disasm_range(addr, + function->GetAddressRange().GetByteSize()); + + DisassemblerSP disassembler_sp = Disassembler::DisassembleRange( + target_sp->GetArchitecture(), plugin_name, flavor, this, disasm_range, + prefer_file_cache); + + if (!disassembler_sp) { + LLDB_LOG(log, "JIT: Couldn't disassemble '{}' function", + function->GetName()); + return 0; + } + + InstructionList *instructions = &disassembler_sp->GetInstructionList(); + + DataExtractor data; + + size_t instructions_count = 0; + size_t instructions_size = 0; + + for (size_t i = 0; i < instructions->GetSize(); i++) { + InstructionSP instruction = instructions->GetInstructionAtIndex(i); + + instruction->GetData(data); + uint32_t size = instruction->Decode(*disassembler_sp.get(), data, 0); + + if (instructions_size < GetABI()->GetJumpSize()) { + instructions_size += size; + instructions_count++; + } + + const ExecutionContext exe_ctx(this); + + LLDB_LOGV(log, "%#llx <+%llu>: %s, %s\t\t(%u)", + instruction->GetAddress().GetLoadAddress(target_sp.get()), + instruction->GetAddress().GetOffset(), + instruction->GetMnemonic(&exe_ctx), + instruction->GetOperands(&exe_ctx), size); + } + + LLDB_LOGV(log, "JIT: Instruction count: {}", instructions_count); + + Status error; + m_overriden_instructions = std::calloc(instructions_size, sizeof(uint8_t)); + + if (!m_overriden_instructions) { + LLDB_LOG(log, "JIT: Couldn't allocate instruction buffer"); + return 0; + } + + size_t memory_read = + ReadMemory(addr, m_overriden_instructions, instructions_size, error); + + if (memory_read != instructions_size || error.Fail()) { + LLDB_LOG(log, "JIT: Couldn't copy instruction to buffer"); + return 0; + } + + return memory_read; +} + void Process::RemoveOwnerFromBreakpointSite(lldb::user_id_t owner_id, lldb::user_id_t owner_loc_id, BreakpointSiteSP &bp_site_sp) { Index: lldb/source/Symbol/ClangASTContext.cpp =================================================================== --- lldb/source/Symbol/ClangASTContext.cpp +++ lldb/source/Symbol/ClangASTContext.cpp @@ -250,7 +250,7 @@ // We have an object already read from process memory, // so just extract VTable pointer from it - DataExtractor data; + lldb_private::DataExtractor data; Status err; auto size = valobj.GetData(data, err); if (err.Fail() || vbtable_ptr_offset + data.GetAddressByteSize() > size) Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -324,7 +324,7 @@ bool ParsePythonTargetDefinition(const FileSpec &target_definition_fspec); - DataExtractor GetAuxvData() override; + lldb_private::DataExtractor GetAuxvData() override; StructuredData::ObjectSP GetExtendedInfoForThread(lldb::tid_t tid); Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -4041,7 +4041,7 @@ return error; } -DataExtractor ProcessGDBRemote::GetAuxvData() { +lldb_private::DataExtractor ProcessGDBRemote::GetAuxvData() { DataBufferSP buf; if (m_gdb_comm.GetQXferAuxvReadSupported()) { std::string response_string; @@ -4051,7 +4051,7 @@ buf = std::make_shared<DataBufferHeap>(response_string.c_str(), response_string.length()); } - return DataExtractor(buf, GetByteOrder(), GetAddressByteSize()); + return lldb_private::DataExtractor(buf, GetByteOrder(), GetAddressByteSize()); } StructuredData::ObjectSP Index: lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -549,6 +549,8 @@ ResetDeclMap(exe_ctx, m_result_delegate, keep_result_in_memory); auto on_exit = llvm::make_scope_exit([this]() { ResetDeclMap(); }); + if (m_options.GetInjectCondition()) + on_exit.release(); if (!DeclMap()->WillParse(exe_ctx, m_materializer_up.get())) { diagnostic_manager.PutString( Index: lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h +++ lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h @@ -76,7 +76,7 @@ bool RewriteExpression(DiagnosticManager &diagnostic_manager) override; - /// Ready an already-parsed expression for execution, possibly evaluating it + /// Read an already-parsed expression for execution, possibly evaluating it /// statically. /// /// \param[out] func_addr Index: lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h +++ lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.h @@ -304,6 +304,8 @@ CompilerDeclContext &namespace_decl, unsigned int current_id); + ExpressionVariableList &GetStructMembers(void) { return m_struct_members; } + private: ExpressionVariableList m_found_entities; ///< All entities that were looked up for the parser. Index: lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.h =================================================================== --- lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.h +++ lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.h @@ -9,6 +9,53 @@ #ifndef liblldb_ABISysV_x86_64_h_ #define liblldb_ABISysV_x86_64_h_ +#define X64_JMP_OPCODE (0xE9) +#define X64_JMP_SIZE (5) +#define X64_CALL_OPCODE (0xE8) +#define X64_CALL_SIZE (5) + +#define X64_PUSH_OPCODE (0x50) +#define X64_POP_OPCODE (0x58) + +#define X64_MOV_OPCODE (0x89) +#define X64_MOV_SIZE (3) + +#define X64_REXB_OPCODE (0x41) +#define X64_REXW_OPCODE (0x48) + +#define X64_SAVED_REGS (16) +#define X64_VOLATILE_REGS (8) + +#define X64_REGS_CTX_STR "typedef struct {\n" \ + " intptr_t r15;\n" \ + " intptr_t r14;\n" \ + " intptr_t r13;\n" \ + " intptr_t r12;\n" \ + " intptr_t r11;\n" \ + " intptr_t r10;\n" \ + " intptr_t r9;\n" \ + " intptr_t r8;\n" \ + " intptr_t rdi;\n" \ + " intptr_t rsi;\n" \ + " intptr_t rbp;\n" \ + " intptr_t rsp;\n" \ + " intptr_t rbx;\n" \ + " intptr_t rdx;\n" \ + " intptr_t rcx;\n" \ + " intptr_t rax;\n" \ + "} register_context;\n\n" + +#define X64_MACH_TYPES " typedef unsigned int uint32_t;\n" \ + " typedef unsigned long long uint64_t ;\n" \ + " typedef unsigned long uintptr_t;\n" \ + " typedef uint32_t mach_port_t;\n" \ + " typedef mach_port_t vm_map_t;\n" \ + " typedef int kern_return_t;\n" \ + " typedef uintptr_t vm_offset_t;\n" \ + " typedef uint64_t mach_vm_address_t;\n" \ + " typedef vm_offset_t vm_address_t;\n" \ + " typedef uint64_t mach_vm_size_t;\n" + #include "lldb/Target/ABI.h" #include "lldb/lldb-private.h" @@ -72,6 +119,45 @@ bool GetPointerReturnRegister(const char *&name) override; + /// Allocate a memory stub for the fast condition breakpoint trampoline, and + /// build it by saving the register context, calling the argument structure + /// builder, passing the resulting structure to the condition checker, + /// restoring the register context, running the copied instructions and] + /// jumping back to the user source code. + /// + /// \param[in] instrs_size + /// The size in bytes of the copied instructions. + /// + /// \param[in] data + /// The copied instructions buffer. + /// + /// \param[in] jmp_addr + /// The address of the source . + /// + /// \param[in] util_func_addr + /// The address of the JIT-ed argument structure builder. + /// + /// \param[in] cond_expr_addr + /// The address of the JIT-ed condition checker. + /// + bool SetupFastConditionalBreakpointTrampoline( + size_t instrs_size, void *data, lldb::addr_t &jmp_addr, + lldb::addr_t util_func_addr, lldb::addr_t cond_expr_addr) override; + + llvm::ArrayRef<uint8_t> GetJumpOpcode() override { return X64_JMP_OPCODE; } + + size_t GetJumpSize() override { return X64_JMP_SIZE; } + + llvm::ArrayRef<uint8_t> GetCallOpcode() override { return X64_JMP_OPCODE; } + + size_t GetCallSize() override { return X64_JMP_SIZE; } + + llvm::StringRef GetRegisterContextAsString() { return X64_REGS_CTX_STR; } + + llvm::StringRef GetMachTypesAsString() { return X64_MACH_TYPES; } + + bool ImplementsJIT() override { return true; } + // Static Functions static void Initialize(); Index: lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.cpp =================================================================== --- lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.cpp +++ lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.cpp @@ -18,6 +18,7 @@ #include "lldb/Core/ValueObjectConstResult.h" #include "lldb/Core/ValueObjectMemory.h" #include "lldb/Core/ValueObjectRegister.h" +#include "lldb/Expression/IRMemoryMap.h" #include "lldb/Symbol/UnwindPlan.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" @@ -214,6 +215,148 @@ return true; } +bool ABISysV_x86_64::SetupFastConditionalBreakpointTrampoline( + size_t instrs_size, void *data, addr_t &jmp_addr, addr_t util_func_addr, + addr_t cond_expr_addr) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_JIT_LOADER)); + + ProcessSP process_sp = m_process_wp.lock(); + + // Copy saved instructions to the inferior memory buffer. + Status error; + uint8_t alignment = 8; + uint32_t permission = + ePermissionsReadable | ePermissionsWritable | ePermissionsExecutable; + IRMemoryMap::AllocationPolicy policy = IRMemoryMap::eAllocationPolicyMirror; + + IRMemoryMap memory_map(process_sp->GetTarget().shared_from_this()); + + size_t context_size = X64_SAVED_REGS + X64_VOLATILE_REGS; + + size_t expected_trampoline_size = context_size; // Save registers + expected_trampoline_size += X64_MOV_SIZE; // Pass register addr as arg + expected_trampoline_size += X64_CALL_SIZE; // Create arg struct + expected_trampoline_size += X64_MOV_SIZE; // Pass arg struct to jit expr + expected_trampoline_size += X64_CALL_SIZE; // Call jit expr + expected_trampoline_size += context_size; // Restore registers + expected_trampoline_size += instrs_size; // Run copied instrs + expected_trampoline_size += X64_JMP_SIZE; // Return to user code + + addr_t trampoline_addr = memory_map.Malloc( + expected_trampoline_size, alignment, permission, policy, true, error); + + if (trampoline_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOG(log, "JIT: Couldn't allocate trampoline buffer"); + return false; + } + + size_t trampoline_size = 0; + + uint8_t trampoline_buffer[expected_trampoline_size]; + + uint8_t regs_ctx[context_size]; + // Save registers + for (size_t i = 0; i < 8; i++) + regs_ctx[i] = X64_PUSH_OPCODE + i; + for (size_t i = 0; i < 8; i++) { + size_t offset = X64_VOLATILE_REGS + 2 * i; + regs_ctx[offset] = X64_REXB_OPCODE; + regs_ctx[offset + 1] = X64_PUSH_OPCODE + i; + } + std::memcpy(trampoline_buffer, ®s_ctx, context_size); + trampoline_size += context_size; + + // Pass the register address as an argument. + uint8_t mov_buffer[X64_MOV_SIZE]; + mov_buffer[0] = X64_REXW_OPCODE; + mov_buffer[1] = X64_MOV_OPCODE; + // %rsp SIB Bytes + mov_buffer[2] = 0xE7; + std::memcpy(&trampoline_buffer[trampoline_size], &mov_buffer, X64_MOV_SIZE); + trampoline_size += X64_MOV_SIZE; + + // Call to create_arg_struct. + uint8_t call_buffer[X64_CALL_SIZE]; + uint32_t call_offset = + -X64_CALL_SIZE - trampoline_size - trampoline_addr + util_func_addr; + call_buffer[0] = X64_CALL_OPCODE; + std::memcpy(&call_buffer[1], &call_offset, sizeof(uint32_t)); + std::memcpy(&trampoline_buffer[trampoline_size], call_buffer, X64_CALL_SIZE); + trampoline_size += X64_CALL_SIZE; + + // Pass the argument structure to condition checker. + // %rdi SIB Bytes + mov_buffer[2] = 0xC7; + std::memcpy(&trampoline_buffer[trampoline_size], &mov_buffer, X64_MOV_SIZE); + trampoline_size += X64_MOV_SIZE; + + // Copy condition checker call in trampoline buffer. + call_offset = + -X64_CALL_SIZE - trampoline_size - trampoline_addr + cond_expr_addr; + call_buffer[0] = X64_CALL_OPCODE; + std::memcpy(&call_buffer[1], &call_offset, sizeof(uint32_t)); + std::memcpy(&trampoline_buffer[trampoline_size], call_buffer, X64_CALL_SIZE); + trampoline_size += X64_CALL_SIZE; + + // Restore registers. + for (size_t i = 0; i < 8; i++) { + regs_ctx[2 * i] = X64_REXB_OPCODE; + regs_ctx[2 * i + 1] = X64_POP_OPCODE + X64_VOLATILE_REGS - i - 1; + } + for (size_t i = 0; i < 8; i++) + regs_ctx[X64_SAVED_REGS + i] = X64_POP_OPCODE + X64_VOLATILE_REGS - i - 1; + std::memcpy(&trampoline_buffer[trampoline_size], ®s_ctx, context_size); + trampoline_size += context_size; + + // Copy saved instruction in trampoline buffer. + std::memcpy(&trampoline_buffer[trampoline_size], data, instrs_size); + trampoline_size += instrs_size; + + // Copy jump back instruction in trampoline buffer. + uint8_t jmp_buffer[X64_JMP_SIZE]; + uint32_t jmp_offset = jmp_addr - trampoline_addr - trampoline_size; + + jmp_buffer[0] = X64_JMP_OPCODE; + std::memcpy(&jmp_buffer[1], &jmp_offset, sizeof(uint32_t)); + std::memcpy(&trampoline_buffer[trampoline_size], jmp_buffer, X64_JMP_SIZE); + trampoline_size += X64_JMP_SIZE; + + if (trampoline_size != expected_trampoline_size) { + LLDB_LOG(log, "JIT: Trampoline size ({}) is not the one expected ({})", + trampoline_size, expected_trampoline_size); + return false; + } + + size_t written_bytes = process_sp->WriteMemory( + trampoline_addr, &trampoline_buffer, trampoline_size, error); + + if (written_bytes != trampoline_size || error.Fail()) { + LLDB_LOG(log, "JIT: Couldn't write trampoline buffer to inferior"); + return false; + } + + // Overwrite current instruction with JMP. + jmp_offset = trampoline_addr - jmp_addr - X64_JMP_SIZE; + + jmp_buffer[0] = X64_JMP_OPCODE; + std::memcpy(&jmp_buffer[1], &jmp_offset, sizeof(uint32_t)); + + for (size_t i = 0; i < X64_JMP_SIZE; i++) + LLDB_LOGV(log, "0x{:x}", jmp_buffer[i]); + + written_bytes = + process_sp->WriteMemory(jmp_addr, jmp_buffer, X64_JMP_SIZE, error); + + if (written_bytes != X64_JMP_SIZE || error.Fail()) { + LLDB_LOG(log, "JIT: Couldn't override instruction with branching"); + return false; + } + + jmp_addr = trampoline_addr; + + return true; +} + size_t ABISysV_x86_64::GetRedZoneSize() const { return 128; } // Static Functions Index: lldb/source/Expression/LLVMUserExpression.cpp =================================================================== --- lldb/source/Expression/LLVMUserExpression.cpp +++ lldb/source/Expression/LLVMUserExpression.cpp @@ -77,7 +77,7 @@ lldb::addr_t struct_address = LLDB_INVALID_ADDRESS; if (!PrepareToExecuteJITExpression(diagnostic_manager, exe_ctx, - struct_address)) { + struct_address, options)) { diagnostic_manager.Printf( eDiagnosticSeverityError, "errored out in %s, couldn't PrepareToExecuteJITExpression", @@ -88,7 +88,7 @@ lldb::addr_t function_stack_bottom = LLDB_INVALID_ADDRESS; lldb::addr_t function_stack_top = LLDB_INVALID_ADDRESS; - if (m_can_interpret) { + if (m_can_interpret && !options.GetInjectCondition()) { llvm::Module *module = m_execution_unit_sp->GetModule(); llvm::Function *function = m_execution_unit_sp->GetFunction(); @@ -285,7 +285,7 @@ bool LLVMUserExpression::PrepareToExecuteJITExpression( DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx, - lldb::addr_t &struct_address) { + lldb::addr_t &struct_address, const EvaluateExpressionOptions options) { lldb::TargetSP target; lldb::ProcessSP process; lldb::StackFrameSP frame; @@ -302,8 +302,9 @@ Status alloc_error; IRMemoryMap::AllocationPolicy policy = - m_can_interpret ? IRMemoryMap::eAllocationPolicyHostOnly - : IRMemoryMap::eAllocationPolicyMirror; + m_can_interpret && !options.GetInjectCondition() + ? IRMemoryMap::eAllocationPolicyHostOnly + : IRMemoryMap::eAllocationPolicyMirror; const bool zero_memory = false; Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -87,6 +87,9 @@ Arg<"Command">, Desc<"A command to run when the breakpoint is hit, can be provided more " "than once, the commands will get run in order left to right.">; + def breakpoint_modify_inject_condition : Option<"inject-condition", "I">, + Desc<"The breakpoint injects the condition expression into the process " + "machine code. Enables Fast Conditional Breakpoints.">; } let Command = "breakpoint dummy" in { Index: lldb/source/Commands/CommandObjectBreakpoint.cpp =================================================================== --- lldb/source/Commands/CommandObjectBreakpoint.cpp +++ lldb/source/Commands/CommandObjectBreakpoint.cpp @@ -102,6 +102,13 @@ m_bp_opts.SetIgnoreCount(ignore_count); } break; + case 'I': { + if (!m_bp_opts.IsOptionSet(BreakpointOptions::eCondition)) + error.SetErrorString("inject-condition option only available for " + "conditional breakpoints"); + else + m_bp_opts.SetInjectCondition(true); + } break; case 'o': { bool value, success; value = OptionArgParser::ToBoolean(option_arg, false, &success); Index: lldb/source/Breakpoint/CMakeLists.txt =================================================================== --- lldb/source/Breakpoint/CMakeLists.txt +++ lldb/source/Breakpoint/CMakeLists.txt @@ -2,6 +2,7 @@ Breakpoint.cpp BreakpointID.cpp BreakpointIDList.cpp + BreakpointInjectedSite.cpp BreakpointList.cpp BreakpointLocation.cpp BreakpointLocationCollection.cpp Index: lldb/source/Breakpoint/BreakpointSite.cpp =================================================================== --- lldb/source/Breakpoint/BreakpointSite.cpp +++ lldb/source/Breakpoint/BreakpointSite.cpp @@ -20,14 +20,15 @@ BreakpointSite::BreakpointSite(BreakpointSiteList *list, const BreakpointLocationSP &owner, - lldb::addr_t addr, bool use_hardware) + lldb::addr_t addr, bool use_hardware, + BreakpointSiteKind kind) : StoppointLocation(GetNextID(), addr, 0, use_hardware), m_type(eSoftware), // Process subclasses need to set this correctly using // SetType() m_saved_opcode(), m_trap_opcode(), m_enabled(false), // Need to create it disabled, so the first enable turns // it on. - m_owners(), m_owners_mutex() { + m_owners(), m_owners_mutex(), m_kind(kind) { m_owners.Add(owner); } Index: lldb/source/Breakpoint/BreakpointOptions.cpp =================================================================== --- lldb/source/Breakpoint/BreakpointOptions.cpp +++ lldb/source/Breakpoint/BreakpointOptions.cpp @@ -109,8 +109,8 @@ const char *BreakpointOptions::g_option_names[( size_t)BreakpointOptions::OptionNames::LastOptionName]{ - "ConditionText", "IgnoreCount", - "EnabledState", "OneShotState", "AutoContinue"}; + "ConditionText", "IgnoreCount", "EnabledState", + "OneShotState", "AutoContinue", "JITCondition"}; bool BreakpointOptions::NullCallback(void *baton, StoppointCallbackContext *context, @@ -124,25 +124,23 @@ : m_callback(BreakpointOptions::NullCallback), m_callback_baton_sp(), m_baton_is_command_baton(false), m_callback_is_synchronous(false), m_enabled(true), m_one_shot(false), m_ignore_count(0), m_thread_spec_up(), - m_condition_text(), m_condition_text_hash(0), m_auto_continue(false), - m_set_flags(0) { + m_condition_text(), m_condition_text_hash(0), m_inject_condition(false), + m_auto_continue(false), m_set_flags(0) { if (all_flags_set) m_set_flags.Set(~((Flags::ValueType)0)); } BreakpointOptions::BreakpointOptions(const char *condition, bool enabled, - int32_t ignore, bool one_shot, - bool auto_continue) + int32_t ignore, bool one_shot, + bool auto_continue, bool inject_condition) : m_callback(nullptr), m_baton_is_command_baton(false), m_callback_is_synchronous(false), m_enabled(enabled), - m_one_shot(one_shot), m_ignore_count(ignore), - m_condition_text_hash(0), m_auto_continue(auto_continue) -{ - m_set_flags.Set(eEnabled | eIgnoreCount | eOneShot - | eAutoContinue); - if (condition && *condition != '\0') { - SetCondition(condition); - } + m_one_shot(one_shot), m_ignore_count(ignore), m_condition_text_hash(0), + m_inject_condition(inject_condition), m_auto_continue(auto_continue) { + m_set_flags.Set(eEnabled | eIgnoreCount | eOneShot | eAutoContinue); + if (condition && *condition != '\0') { + SetCondition(condition); + } } // BreakpointOptions copy constructor @@ -152,6 +150,7 @@ m_callback_is_synchronous(rhs.m_callback_is_synchronous), m_enabled(rhs.m_enabled), m_one_shot(rhs.m_one_shot), m_ignore_count(rhs.m_ignore_count), m_thread_spec_up(), + m_inject_condition(rhs.m_inject_condition), m_auto_continue(rhs.m_auto_continue), m_set_flags(rhs.m_set_flags) { if (rhs.m_thread_spec_up != nullptr) m_thread_spec_up.reset(new ThreadSpec(*rhs.m_thread_spec_up)); @@ -175,6 +174,7 @@ m_condition_text_hash = rhs.m_condition_text_hash; m_auto_continue = rhs.m_auto_continue; m_set_flags = rhs.m_set_flags; + m_inject_condition = rhs.m_inject_condition; return *this; } @@ -210,12 +210,18 @@ m_condition_text.clear(); m_condition_text_hash = 0; m_set_flags.Clear(eCondition); + m_inject_condition = false; } else { m_condition_text = incoming.m_condition_text; m_condition_text_hash = incoming.m_condition_text_hash; m_set_flags.Set(eCondition); + m_inject_condition = incoming.m_inject_condition; } } + if (incoming.m_set_flags.Test(eInjectCondition)) { + m_inject_condition = incoming.m_inject_condition; + m_set_flags.Set(eInjectCondition); + } if (incoming.m_set_flags.Test(eAutoContinue)) { m_auto_continue = incoming.m_auto_continue; @@ -374,10 +380,14 @@ if (m_set_flags.Test(eIgnoreCount)) options_dict_sp->AddIntegerItem(GetKey(OptionNames::IgnoreCount), m_ignore_count); - if (m_set_flags.Test(eCondition)) + if (m_set_flags.Test(eCondition)) { options_dict_sp->AddStringItem(GetKey(OptionNames::ConditionText), m_condition_text); - + if (m_set_flags.Test(eInjectCondition)) + options_dict_sp->AddBooleanItem(GetKey(OptionNames::InjectCondition), + m_inject_condition); + } + if (m_set_flags.Test(eCallback) && m_baton_is_command_baton) { auto cmd_baton = std::static_pointer_cast<CommandBaton>(m_callback_baton_sp); @@ -464,6 +474,10 @@ return m_callback != BreakpointOptions::NullCallback; } +bool BreakpointOptions::GetInjectCondition() const { + return m_inject_condition; +} + bool BreakpointOptions::GetCommandLineCallbacks(StringList &command_list) { if (!HasCallback()) return false; @@ -482,6 +496,7 @@ if (!condition || condition[0] == '\0') { condition = ""; m_set_flags.Clear(eCondition); + m_set_flags.Clear(eInjectCondition); } else m_set_flags.Set(eCondition); @@ -571,8 +586,9 @@ } if (!m_condition_text.empty()) { if (level != eDescriptionLevelBrief) { + std::string fast = (m_inject_condition) ? " (FAST)" : ""; s->EOL(); - s->Printf("Condition: %s\n", m_condition_text.c_str()); + s->Printf("Condition: %s%s\n", m_condition_text.c_str(), fast.c_str()); } } } @@ -671,4 +687,5 @@ m_callback_is_synchronous = false; m_enabled = false; m_condition_text.clear(); + m_inject_condition = false; } Index: lldb/source/Breakpoint/BreakpointLocation.cpp =================================================================== --- lldb/source/Breakpoint/BreakpointLocation.cpp +++ lldb/source/Breakpoint/BreakpointLocation.cpp @@ -226,6 +226,19 @@ ->GetConditionText(hash); } +bool BreakpointLocation::GetInjectCondition() const { + if (m_options_up && + m_options_up->IsOptionSet(BreakpointOptions::eInjectCondition)) + return m_options_up->GetInjectCondition(); + return m_owner.GetInjectCondition(); +} + +void BreakpointLocation::SetInjectCondition(bool inject_condition) { + m_owner.SetInjectCondition(inject_condition); + GetLocationOptions()->SetInjectCondition(inject_condition); + SendBreakpointLocationChangedEvent(eBreakpointEventTypeInjectedCondition); +} + bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx, Status &error) { Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS); @@ -240,6 +253,14 @@ return false; } + bool inject_condition = GetInjectCondition(); + + if (inject_condition) { + // TODO: Evalutates condition is case of multi-condition + // BreakpointInjectSite + return true; + } + error.Clear(); DiagnosticManager diagnostics; @@ -263,9 +284,12 @@ return true; } - if (!m_user_expression_sp->Parse(diagnostics, exe_ctx, - eExecutionPolicyOnlyWhenNeeded, true, - false)) { + ExecutionPolicy execution_policy = inject_condition + ? eExecutionPolicyAlways + : eExecutionPolicyOnlyWhenNeeded; + + if (!m_user_expression_sp->Parse(diagnostics, exe_ctx, execution_policy, + true, false)) { error.SetErrorStringWithFormat( "Couldn't parse conditional expression:\n%s", diagnostics.GetString().c_str()); @@ -287,6 +311,7 @@ options.SetTryAllThreads(true); options.SetResultIsInternal( true); // Don't generate a user variable for condition expressions. + options.SetInjectCondition(inject_condition); Status expr_error; Index: lldb/source/Breakpoint/BreakpointInjectedSite.cpp =================================================================== --- /dev/null +++ lldb/source/Breakpoint/BreakpointInjectedSite.cpp @@ -0,0 +1,449 @@ +//===-- BreakpointInjectedSite.cpp ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Breakpoint/BreakpointInjectedSite.h" + +#include "Plugins/ExpressionParser/Clang/ClangExpressionVariable.h" +#include "Plugins/ExpressionParser/Clang/ClangUserExpression.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Target/Language.h" + +#include "lldb/Target/ABI.h" + +#include "llvm/Support/DataExtractor.h" + +using namespace lldb; +using namespace lldb_private; + +BreakpointInjectedSite::BreakpointInjectedSite( + BreakpointSiteList *list, const BreakpointLocationSP &owner, + lldb::addr_t addr) + : BreakpointSite(list, owner, addr, false, eKindBreakpointInjectedSite), + m_target_sp(owner->GetTarget().shared_from_this()), + m_real_addr(owner->GetAddress()), m_trap_addr(LLDB_INVALID_ADDRESS) {} + +BreakpointInjectedSite::~BreakpointInjectedSite() {} + +bool BreakpointInjectedSite::BuildConditionExpression(void) { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_JIT_LOADER); + + Status error; + + std::string trap; + std::string condition_text; + bool single_condition = true; + + LanguageType language = eLanguageTypeUnknown; + + for (BreakpointLocationSP loc_sp : m_owners.BreakpointLocations()) { + + // Stop building the expression if a location condition is not JIT-ed + if (!loc_sp->GetInjectCondition()) { + LLDB_LOG(log, "FCB: BreakpointLocation ({}) condition is not JIT-ed", + loc_sp->GetConditionText()); + return false; + } + + std::string condition = loc_sp->GetConditionText(); + // See if we can figure out the language from the frame, otherwise use the + // default language: + CompileUnit *comp_unit = + loc_sp->GetAddress().CalculateSymbolContextCompileUnit(); + if (comp_unit) + language = comp_unit->GetLanguage(); + + if (language == eLanguageTypeSwift) { + trap += "Builtin.int_trap()"; + } else if (Language::LanguageIsCFamily(language)) { + trap = "__builtin_debugtrap()"; + } else { + LLDB_LOG(log, "FCB: Language {} not supported", + Language::GetNameForLanguageType(language)); + m_condition_expression_sp.reset(); + return false; + } + + condition_text += "static int hit_count = 0;\n\thit_count++;\n\t"; + condition_text += (single_condition) ? "if (" : " || "; + condition_text += condition; + + single_condition = false; + } + + condition_text += ") {\n\t"; + + condition_text += trap + ";\n}"; + + LLDB_LOGV(log, "Injected Condition:\n{}\n", condition_text.c_str()); + + DiagnosticManager diagnostics; + + EvaluateExpressionOptions options; + options.SetInjectCondition(true); + options.SetKeepInMemory(true); + options.SetGenerateDebugInfo(true); + + m_condition_expression_sp.reset(m_target_sp->GetUserExpressionForLanguage( + condition_text, llvm::StringRef(), language, Expression::eResultTypeAny, + EvaluateExpressionOptions(options), nullptr, error)); + + if (error.Fail()) { + if (log) + log->Printf("Error getting condition expression: %s.", error.AsCString()); + m_condition_expression_sp.reset(); + return false; + } + + diagnostics.Clear(); + + ThreadSP thread_sp = m_target_sp->GetProcessSP() + ->GetThreadList() + .GetExpressionExecutionThread(); + + user_id_t frame_idx = -1; + user_id_t concrete_frame_idx = -1; + addr_t cfa = LLDB_INVALID_ADDRESS; + bool cfa_is_valid = false; + addr_t pc = LLDB_INVALID_ADDRESS; + StackFrame::Kind frame_kind = StackFrame::Kind::Regular; + bool zeroth_frame = false; + SymbolContext sc; + m_real_addr.CalculateSymbolContext(&sc); + + StackFrameSP frame_sp = + make_shared<StackFrame>(thread_sp, frame_idx, concrete_frame_idx, cfa, + cfa_is_valid, pc, frame_kind, zeroth_frame, &sc); + + m_owner_exe_ctx = ExecutionContext(frame_sp); + ExecutionPolicy execution_policy = eExecutionPolicyAlways; + bool keep_result_in_memory = true; + bool generate_debug_info = true; + + if (!m_condition_expression_sp->Parse(diagnostics, m_owner_exe_ctx, + execution_policy, keep_result_in_memory, + generate_debug_info)) { + LLDB_LOG(log, "Couldn't parse conditional expression:\n{}", + diagnostics.GetString().c_str()); + m_condition_expression_sp.reset(); + return false; + } + + const AddressRange &jit_addr_range = + m_condition_expression_sp->GetJITAddressRange(); + + error.Clear(); + + void *buffer = std::calloc(jit_addr_range.GetByteSize(), sizeof(uint8_t)); + + lldb::addr_t jit_addr = + jit_addr_range.GetBaseAddress().GetCallableLoadAddress(m_target_sp.get()); + + size_t memory_read = m_target_sp->GetProcessSP()->ReadMemory( + jit_addr, buffer, jit_addr_range.GetByteSize(), error); + + if (memory_read != jit_addr_range.GetByteSize() || error.Fail()) { + m_condition_expression_sp.reset(); + error.SetErrorString("Couldn't read jit memory"); + return false; + } + + PlatformSP platform_sp = m_target_sp->GetPlatform(); + + if (!platform_sp) { + error.SetErrorString("Couldn't get running platform"); + return false; + } + + if (!platform_sp->GetSoftwareBreakpointTrapOpcode(*m_target_sp.get(), this)) { + error.SetErrorString("Couldn't get current architecture trap opcode"); + return false; + } + + if (!ResolveTrapAddress(buffer, memory_read)) { + error.SetErrorString("Couldn't find trap in jitter expression"); + return false; + } + + if (!GatherArgumentsMetadata()) { + LLDB_LOG(log, "FCB: Couldn't gather argument metadata"); + return false; + } + + if (!CreateArgumentsStructure()) { + LLDB_LOG(log, "FCB: Couldn't create argument structure"); + return false; + } + + return true; +} + +bool BreakpointInjectedSite::ResolveTrapAddress(void *jit, size_t size) { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_JIT_LOADER); + + const ArchSpec &arch = m_target_sp->GetArchitecture(); + const char *plugin_name = nullptr; + const char *flavor = nullptr; + const ExecutionContext exe_ctx(m_target_sp, true); + const bool prefer_file_cache = true; + + m_disassembler_sp = Disassembler::DisassembleRange( + arch, plugin_name, flavor, exe_ctx, + m_condition_expression_sp->GetJITAddressRange(), prefer_file_cache); + + if (!m_disassembler_sp) { + LLDB_LOG(log, "FCB: Couldn't disassemble JIT-ed expression"); + return false; + } + + InstructionList &instructions = m_disassembler_sp->GetInstructionList(); + + if (!instructions.GetSize()) { + LLDB_LOG(log, "FCB: No instructions found for JIT-ed expression"); + return false; + } + + for (size_t i = 0; i < instructions.GetSize(); i++) { + InstructionSP instr = instructions.GetInstructionAtIndex(i); + const void *instr_opcode = instr->GetOpcode().GetOpcodeDataBytes(); + + DataExtractor data; + instr->GetData(data); + + const size_t trap_size = instr->Decode(*m_disassembler_sp.get(), data, 0); + + if (!instr_opcode) { + return false; + } + + if (!memcmp(instr_opcode, m_trap_opcode, trap_size)) { + addr_t addr = instr->GetAddress().GetOpcodeLoadAddress(m_target_sp.get()); + m_trap_addr = addr; + LLDB_LOGV(log, "Injected trap address: {0:X+}", addr); + return true; + } + } + return false; +} + +bool BreakpointInjectedSite::GatherArgumentsMetadata() { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_JIT_LOADER); + + LanguageType native_language = m_condition_expression_sp->Language(); + + if (!Language::LanguageIsCFamily(native_language)) { + LLDB_LOG(log, "FCB: {} language does not support Injected Conditional \ + Breapoint", + Language::GetNameForLanguageType(native_language)); + return false; + } + + ClangUserExpression *clang_expr = + llvm::dyn_cast<ClangUserExpression>(m_condition_expression_sp.get()); + + ClangExpressionDeclMap *decl_map = clang_expr->DeclMap(); + if (!decl_map) { + LLDB_LOG(log, "FCB: Couldn't find DeclMap for JIT-ed expression"); + return false; + } + + uint32_t num_elements; + size_t size; + offset_t alignment; + + if (!decl_map->GetStructInfo(num_elements, size, alignment)) { + LLDB_LOG(log, "FCB: Couldn't fetch arguments info from DeclMap"); + return false; + } + + for (uint32_t i = 0; i < num_elements; ++i) { + const clang::NamedDecl *decl = nullptr; + llvm::Value *value = nullptr; + lldb::offset_t offset; + lldb_private::ConstString name; + + if (!decl_map->GetStructElement(decl, value, offset, name, i)) { + LLDB_LOG(log, "FCB: Couldn't fetch element from DeclMap"); + return false; + } + if (!value) { + LLDB_LOG(log, "FCB: Couldn't find value for element {}/{}", i, + num_elements); + return false; + } + } + + ExpressionVariableList &members = decl_map->GetStructMembers(); + + for (ExpressionVariableSP expr_var : members.Variables()) { + ValueObjectSP val_obj_sp = expr_var->GetValueObject(); + + if (!val_obj_sp->GetVariable()) { + // if Expression Variable does not have ValueObject, skip it + continue; + } + + VariableSP var_sp = val_obj_sp->GetVariable(); + + DWARFExpression lldb_dwarf_expr = var_sp->LocationExpression(); + DataExtractor lldb_data; + if (!lldb_dwarf_expr.GetExpressionData(lldb_data)) { + return false; + } + + llvm::StringRef data(lldb_data.PeekCStr(0)); + bool is_le = (lldb_data.GetByteOrder() == lldb::eByteOrderLittle); + uint32_t data_addr_size = lldb_data.GetAddressByteSize(); + llvm::DataExtractor llvm_data = + llvm::DataExtractor(data, is_le, data_addr_size); + + llvm::DWARFExpression::Operation::DwarfVersion version = + llvm::DWARFExpression::Operation::Dwarf5; + uint8_t addr_size = m_target_sp->GetArchitecture().GetAddressByteSize(); + + auto size = var_sp->GetType()->GetByteSize(); + if (!size) { + LLDB_LOG(log, "FCB: Variable {} has invalid size", + var_sp->GetName().GetCString()); + return false; + } + + VariableMetadata metadata(expr_var->GetName().GetStringRef(), + size.getValue(), llvm_data, version, addr_size); + + m_metadatas.push_back(metadata); + } + + clang_expr->ResetDeclMap(); + + return true; +} + +bool BreakpointInjectedSite::CreateArgumentsStructure() { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_JIT_LOADER); + + Status error; + std::string expr; + expr.reserve(2048); + std::string name = "$__lldb_create_args_struct"; + + ABISP abi_sp = m_owner_exe_ctx.GetProcessSP()->GetABI(); + + expr += "extern unsigned int mach_task_self_;\n"; + + expr += "extern \"C\"\n" + "{\n" + " /*\n" + " * mach defines\n" + " */\n" + "\n"; + + expr += abi_sp->GetMachTypesAsString(); + + expr += "\n" + " void* memcpy (void *dest, const void *src, size_t count);\n" + " kern_return_t mach_vm_allocate (vm_map_t target, " + "mach_vm_address_t *address, mach_vm_size_t size, int flags);\n" + " kern_return_t mach_vm_deallocate (vm_map_t target, " + "mach_vm_address_t address, mach_vm_size_t size);\n" + "}\n\n"; + + expr += "#define KERN_SUCCESS 0\n" + "#define KERN_INVALID_ADDRESS 1\n\n"; + + // Get structure size + std::size_t size = m_metadatas.size() * 8; + expr += "static mach_vm_size_t size = " + std::to_string(size) + ";\n\n"; + + expr += abi_sp->GetRegisterContextAsString(); + + expr += + "mach_vm_address_t $__lldb_create_args_struct(register_context* regs) {\n" + " mach_vm_address_t address = (vm_address_t) NULL;\n" + " int flags = 1;\n" + " \n" + " kern_return_t kr = mach_vm_allocate(mach_task_self_, &address, " + "size, flags);\n" + " if (kr != KERN_SUCCESS) {\n" + " return KERN_INVALID_ADDRESS;\n" + " }\n\n" + " void *src_addr = NULL;\n" + " void *dst_addr = NULL;\n" + " size_t count = sizeof(void*);\n\n" + "\n"; + + for (size_t index = 0; index < m_metadatas.size(); index++) { + expr += ParseDWARFExpression(index, error); + if (error.Fail()) { + LLDB_LOG(log, "FCB: Couldn't parse DWARFExpression ({}/{})", index, + m_metadatas.size()); + return false; + } + } + + expr += " return address;\n" + "}\n"; + + m_create_args_struct_function_sp.reset( + m_target_sp->GetUtilityFunctionForLanguage(expr.c_str(), eLanguageTypeC, + name.c_str(), error)); + + if (error.Fail()) { + LLDB_LOG(log, "Error getting utility function: {1}.", error); + m_create_args_struct_function_sp.reset(); + return false; + } + + DiagnosticManager diagnostics; + ExecutionContext exe_ctx(m_target_sp->GetProcessSP()); + + if (!m_create_args_struct_function_sp->Install(diagnostics, exe_ctx)) { + error.SetErrorStringWithFormat("Couldn't install utility function:\n%s", + diagnostics.GetString().c_str()); + m_create_args_struct_function_sp.reset(); + return false; + } + + return true; +} + +std::string BreakpointInjectedSite::ParseDWARFExpression(size_t index, + Status &error) { + std::string expr; + + for (auto op : m_metadatas[index].dwarf) { + switch (op.getCode()) { + case DW_OP_addr: { + int64_t operand = op.getRawOperand(0); + expr += " src_addr = " + std::to_string(operand) + + ";\n" + " dst_addr = (void*) (address + " + + std::to_string(index * 8) + + ");\n" + " memcpy(dst_addr, &src_addr, count);\n"; + break; + } + case DW_OP_fbreg: { + int64_t operand = op.getRawOperand(0); + expr += " src_addr = (void*) (regs->rbp + " + std::to_string(operand) + + ");\n" + " dst_addr = (void*) (address + " + + std::to_string(index * 8) + + ");\n" + " memcpy(dst_addr, &src_addr, count);\n"; + break; + } + default: { + error.Clear(); + error.SetErrorToErrno(); + break; + } + } + } + + return expr; +} Index: lldb/source/Breakpoint/Breakpoint.cpp =================================================================== --- lldb/source/Breakpoint/Breakpoint.cpp +++ lldb/source/Breakpoint/Breakpoint.cpp @@ -418,6 +418,14 @@ return m_options_up->GetConditionText(); } +void Breakpoint::SetInjectCondition(bool inject_condition) { + m_options_up->SetInjectCondition(inject_condition); +} + +bool Breakpoint::GetInjectCondition() const { + return m_options_up->GetInjectCondition(); +} + // This function is used when "baton" doesn't need to be freed void Breakpoint::SetCallback(BreakpointHitCallback callback, void *baton, bool is_synchronous) { Index: lldb/source/API/SBBreakpointLocation.cpp =================================================================== --- lldb/source/API/SBBreakpointLocation.cpp +++ lldb/source/API/SBBreakpointLocation.cpp @@ -206,6 +206,32 @@ return false; } +void SBBreakpointLocation::SetInjectCondition(bool inject_condition) { + LLDB_RECORD_METHOD(void, SBBreakpointLocation, SetInjectCondition, (bool), + inject_condition); + + BreakpointLocationSP loc_sp = GetSP(); + if (!loc_sp) + return; + + std::lock_guard<std::recursive_mutex> guard( + loc_sp->GetTarget().GetAPIMutex()); + loc_sp->SetInjectCondition(inject_condition); + loc_sp->GetBreakpoint().SetInjectCondition(inject_condition); +} + +bool SBBreakpointLocation::GetInjectCondition() { + LLDB_RECORD_METHOD_NO_ARGS(bool, SBBreakpointLocation, GetInjectCondition); + + BreakpointLocationSP loc_sp = GetSP(); + if (!loc_sp) + return false; + + std::lock_guard<std::recursive_mutex> guard( + loc_sp->GetTarget().GetAPIMutex()); + return loc_sp->GetInjectCondition(); +} + void SBBreakpointLocation::SetScriptCallbackFunction( const char *callback_function_name) { LLDB_RECORD_METHOD(void, SBBreakpointLocation, SetScriptCallbackFunction, @@ -480,6 +506,8 @@ LLDB_REGISTER_METHOD(const char *, SBBreakpointLocation, GetCondition, ()); LLDB_REGISTER_METHOD(void, SBBreakpointLocation, SetAutoContinue, (bool)); LLDB_REGISTER_METHOD(bool, SBBreakpointLocation, GetAutoContinue, ()); + LLDB_REGISTER_METHOD(void, SBBreakpointLocation, SetInjectCondition, (bool)); + LLDB_REGISTER_METHOD(bool, SBBreakpointLocation, GetInjectCondition, ()); LLDB_REGISTER_METHOD(void, SBBreakpointLocation, SetScriptCallbackFunction, (const char *)); LLDB_REGISTER_METHOD(lldb::SBError, SBBreakpointLocation, Index: lldb/source/API/SBBreakpoint.cpp =================================================================== --- lldb/source/API/SBBreakpoint.cpp +++ lldb/source/API/SBBreakpoint.cpp @@ -312,6 +312,31 @@ return false; } +void SBBreakpoint::SetInjectCondition(bool inject_condition) { + LLDB_RECORD_METHOD(void, SBBreakpoint, SetInjectCondition, (bool), + inject_condition); + + BreakpointSP bkpt_sp = GetSP(); + if (!bkpt_sp) + return; + + std::lock_guard<std::recursive_mutex> guard( + bkpt_sp->GetTarget().GetAPIMutex()); + bkpt_sp->SetInjectCondition(inject_condition); +} + +bool SBBreakpoint::GetInjectCondition() { + LLDB_RECORD_METHOD_NO_ARGS(bool, SBBreakpoint, GetInjectCondition); + + BreakpointSP bkpt_sp = GetSP(); + if (!bkpt_sp) + return false; + + std::lock_guard<std::recursive_mutex> guard( + bkpt_sp->GetTarget().GetAPIMutex()); + return bkpt_sp->GetInjectCondition(); +} + uint32_t SBBreakpoint::GetHitCount() const { LLDB_RECORD_METHOD_CONST_NO_ARGS(uint32_t, SBBreakpoint, GetHitCount); @@ -967,6 +992,8 @@ LLDB_REGISTER_METHOD(const char *, SBBreakpoint, GetCondition, ()); LLDB_REGISTER_METHOD(void, SBBreakpoint, SetAutoContinue, (bool)); LLDB_REGISTER_METHOD(bool, SBBreakpoint, GetAutoContinue, ()); + LLDB_REGISTER_METHOD(void, SBBreakpoint, SetInjectCondition, (bool)); + LLDB_REGISTER_METHOD(bool, SBBreakpoint, GetInjectCondition, ()); LLDB_REGISTER_METHOD_CONST(uint32_t, SBBreakpoint, GetHitCount, ()); LLDB_REGISTER_METHOD_CONST(uint32_t, SBBreakpoint, GetIgnoreCount, ()); LLDB_REGISTER_METHOD(void, SBBreakpoint, SetThreadID, (lldb::tid_t)); Index: lldb/scripts/interface/SBBreakpointLocation.i =================================================================== --- lldb/scripts/interface/SBBreakpointLocation.i +++ lldb/scripts/interface/SBBreakpointLocation.i @@ -72,6 +72,15 @@ void SetAutoContinue(bool auto_continue); + %feature("docstring", " + *EXPERIMENTAL* Check if the condition expression is injected and checked in-process.") GetInjectCondition; + bool GetInjectCondition(); + + %feature("docstring", " + *EXPERIMENTAL* The condition expression in injected and checked in-process. + Enables Fast Conditional Breakpoint.") SetInjectCondition; + void SetInjectCondition(bool inject_condition); + %feature("docstring", " Set the callback to the given Python function name.") SetScriptCallbackFunction; void Index: lldb/scripts/interface/SBBreakpoint.i =================================================================== --- lldb/scripts/interface/SBBreakpoint.i +++ lldb/scripts/interface/SBBreakpoint.i @@ -150,6 +150,15 @@ bool GetAutoContinue(); + %feature("docstring", " + *EXPERIMENTAL* Check if the condition expression is injected and checked in-process.") GetInjectCondition; + bool GetInjectCondition(); + + %feature("docstring", " + *EXPERIMENTAL* The condition expression in injected and checked in-process. + Toggles Fast Conditional Breakpoint.") SetInjectCondition; + void SetInjectCondition(bool inject_condition); + void SetThreadID (lldb::tid_t sb_thread_id); Index: lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/main.c =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/main.c @@ -0,0 +1,24 @@ +// This simple program is to demonstrate the capability of the lldb command +// "breakpoint set -c "condition() == 999999" -f main.c -l 29 -I" or +// "breakpoint set -c "local_count == 999999" -f main.c -l 29 -I" to break +// the condition for an inject breakpoint evaluate to true. + +#include <stdio.h> + +static int global_count = 0; + +int condition(void) { + printf("global_count = %d\n", global_count); + return global_count++; +} + +int main(int argc, char *argv[]) +{ + int local_count = 0; + for (int i = 0; i < 10000000; i++) { + printf("local_count = %d\n", local_count++); // Find the line number of condition breakpoint for local_count + } + + return 0; +} + Index: lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/TestFastConditionalBreakpoints.py =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/TestFastConditionalBreakpoints.py @@ -0,0 +1,191 @@ +""" +Test Fast Conditional Breakpoints. +""" + +from __future__ import print_function + +import os +import time +import re +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class FastConditionalBreakpoitsTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + self.file = lldb.SBFileSpec("main.c") + self.comment = "Find the line number of condition breakpoint for local_count" + self.condition = '"local_count == 9"' + self.extra_options = "-c " + self.condition + " -I" + self.binary = "a.out" + + @skipIfWindows + def test_fast_conditional_breakpoint_flag_interpreter(self): + """Enable fast conditional breakpoints with 'breakpoint modify -c <expr> id -I'.""" + self.build() + self.enable_fast_conditional_breakpoint(use_interpreter=True) + + @skipIfWindows + @add_test_categories(["pyapi"]) + def test_fast_conditional_breakpoint_flag_api(self): + """Exercise fast conditional breakpoints with SB API""" + self.build() + self.enable_fast_conditional_breakpoint(use_interpreter=False) + + @skipIfWindows + @add_test_categories(["pyapi"]) + def test_fast_conditional_breakpoint(self): + """Exercice injected breakpoint conditions""" + self.build() + self.inject_fast_conditional_breakpoint() + + @skipIfWindows + @add_test_categories(["pyapi"]) + def test_invalid_fast_conditional_breakpoint(self): + """Exercice invalid injected breakpoint conditions""" + self.build() + self.inject_invalid_fast_conditional_breakpoint() + + def enable_fast_conditional_breakpoint(self, use_interpreter): + exe = self.getBuildArtifact(self.binary) + self.target = self.dbg.CreateTarget(exe) + self.assertTrue(self.target, VALID_TARGET) + + if use_interpreter: + lldbutil.run_break_set_by_source_regexp( + self, self.comment, self.extra_options + ) + + self.runCmd("breakpoint modify " + self.condition + " 1") + + self.expect("breakpoint list -f", substrs=["(FAST)"]) + else: + # Now create a breakpoint on main.c by source regex'. + breakpoint = self.target.BreakpointCreateBySourceRegex( + self.comment, self.file + ) + self.assertTrue( + breakpoint and breakpoint.GetNumLocations() == 1, + VALID_BREAKPOINT) + + # We didn't associate a thread index with the breakpoint, so it should + # be invalid. + self.assertTrue( + breakpoint.GetThreadIndex() == lldb.UINT32_MAX, + "the thread index should be invalid", + ) + # The thread name should be invalid, too. + self.assertTrue( + breakpoint.GetThreadName() is None, + "the thread name should be invalid") + + # Let's set the thread index for this breakpoint and verify that it is, + # indeed, being set correctly and there's only one thread for the + # process. + breakpoint.SetThreadIndex(1) + self.assertTrue( + breakpoint.GetThreadIndex() == 1, + "the thread index has been set correctly", + ) + + # Get the breakpoint location from breakpoint after we verified that, + # indeed, it has one location. + location = breakpoint.GetLocationAtIndex(0) + self.assertTrue( + location and location.IsEnabled(), VALID_BREAKPOINT_LOCATION + ) + + # Set the condition on the breakpoint. + location.SetCondition(self.condition) + self.expect( + location.GetCondition(), + exe=False, + startstr=self.condition) + + # Set condition on the breakpoint to be injected in-process. + location.SetInjectCondition(True) + self.assertTrue( + location.GetInjectCondition(), + VALID_BREAKPOINT_LOCATION) + + return self.target.GetBreakpointAtIndex(0) + + def inject_fast_conditional_breakpoint(self): + # now launch the process, and do not stop at entry point. + breakpoint = self.enable_fast_conditional_breakpoint( + use_interpreter=False) + process = self.target.LaunchSimple( + None, None, self.get_process_working_directory() + ) + self.assertTrue(process, PROCESS_IS_VALID) + + # frame #0 should be on self.line and the break condition should hold. + from lldbsuite.test.lldbutil import get_stopped_thread + + thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) + self.assertTrue( + thread and thread.IsValid(), + "there should be a thread stopped due to breakpoint condition", + ) + + frame0 = thread.GetFrameAtIndex(0) + expected_fn_name = "$__lldb_expr(void*)" + self.assertTrue(frame0 and frame0.GetFunctionName() + == expected_fn_name) + + # the hit count for the breakpoint should be 1. + self.assertTrue(breakpoint.GetHitCount() == 1) + + def inject_invalid_fast_conditional_breakpoint(self): + # now create a breakpoint on main.c by source regex'. + exe = self.getBuildArtifact(self.binary) + self.target = self.dbg.CreateTarget(exe) + self.assertTrue(self.target, VALID_TARGET) + breakpoint = self.target.BreakpointCreateBySourceRegex( + self.comment, self.file) + self.assertTrue( + breakpoint and breakpoint.GetNumLocations() == 1, VALID_BREAKPOINT + ) + + # set the condition on the breakpoint. + breakpoint.SetCondition("no_such_variable == not_this_one_either") + self.expect( + breakpoint.GetCondition(), + exe=False, + startstr="no_such_variable == not_this_one_either", + ) + # get the breakpoint location from breakpoint after we verified that, + # indeed, it has one location. + location = breakpoint.GetLocationAtIndex(0) + self.assertTrue( + location and location.IsEnabled(), + VALID_BREAKPOINT_LOCATION) + + # set condition on the breakpoint to be injected. + location.SetInjectCondition(True) + self.assertTrue( + location.GetInjectCondition(), + VALID_BREAKPOINT_LOCATION) + + # now launch the process, and do not stop at entry point. + process = self.target.LaunchSimple( + None, None, self.get_process_working_directory() + ) + self.assertTrue(process, PROCESS_IS_VALID) + + # frame #0 should be on self.line1 and the break condition should hold. + from lldbsuite.test.lldbutil import get_stopped_thread + + # FCB is disabled because the condition is not valid. + self.assertFalse( + location.GetInjectCondition(), + VALID_BREAKPOINT_LOCATION) + # FCB falls back to regular conditional breakpoint that get hit once. + self.assertTrue(breakpoint.GetHitCount() == 1) Index: lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/Makefile =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/fast_conditional_breakpoints/Makefile @@ -0,0 +1,6 @@ +LEVEL = ../../../make + +C_SOURCES := main.c +CFLAGS_EXTRAS += -std=c99 + +include $(LEVEL)/Makefile.rules Index: lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_conditions/TestBreakpointConditions.py =================================================================== --- lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_conditions/TestBreakpointConditions.py +++ lldb/packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_conditions/TestBreakpointConditions.py @@ -15,30 +15,22 @@ mydir = TestBase.compute_mydir(__file__) - # Requires EE to support COFF on Windows (http://llvm.org/pr22232) - @skipIfWindows def test_breakpoint_condition_and_run_command(self): """Exercise breakpoint condition with 'breakpoint modify -c <expr> id'.""" self.build() self.breakpoint_conditions() - # Requires EE to support COFF on Windows (http://llvm.org/pr22232) - @skipIfWindows def test_breakpoint_condition_inline_and_run_command(self): """Exercise breakpoint condition inline with 'breakpoint set'.""" self.build() self.breakpoint_conditions(inline=True) - # Requires EE to support COFF on Windows (http://llvm.org/pr22232) - @skipIfWindows @add_test_categories(['pyapi']) def test_breakpoint_condition_and_python_api(self): """Use Python APIs to set breakpoint conditions.""" self.build() self.breakpoint_conditions_python() - # Requires EE to support COFF on Windows (http://llvm.org/pr22232) - @skipIfWindows @add_test_categories(['pyapi']) def test_breakpoint_invalid_condition_and_python_api(self): """Use Python APIs to set breakpoint conditions.""" Index: lldb/include/lldb/lldb-forward.h =================================================================== --- lldb/include/lldb/lldb-forward.h +++ lldb/include/lldb/lldb-forward.h @@ -40,6 +40,7 @@ class BreakpointPrecondition; class BreakpointResolver; class BreakpointSite; +class BreakpointInjectedSite; class BreakpointSiteList; class BroadcastEventSpec; class Broadcaster; @@ -297,6 +298,10 @@ typedef std::weak_ptr<lldb_private::Breakpoint> BreakpointWP; typedef std::shared_ptr<lldb_private::BreakpointSite> BreakpointSiteSP; typedef std::weak_ptr<lldb_private::BreakpointSite> BreakpointSiteWP; +typedef std::shared_ptr<lldb_private::BreakpointInjectedSite> + BreakpointInjectedSiteSP; +typedef std::weak_ptr<lldb_private::BreakpointInjectedSite> + BreakpointInjectedSiteWP; typedef std::shared_ptr<lldb_private::BreakpointLocation> BreakpointLocationSP; typedef std::weak_ptr<lldb_private::BreakpointLocation> BreakpointLocationWP; typedef std::shared_ptr<lldb_private::BreakpointPrecondition> BreakpointPreconditionSP; Index: lldb/include/lldb/lldb-enumerations.h =================================================================== --- lldb/include/lldb/lldb-enumerations.h +++ lldb/include/lldb/lldb-enumerations.h @@ -402,7 +402,8 @@ eBreakpointEventTypeConditionChanged = (1u << 9), eBreakpointEventTypeIgnoreChanged = (1u << 10), eBreakpointEventTypeThreadChanged = (1u << 11), - eBreakpointEventTypeAutoContinueChanged = (1u << 12)}; + eBreakpointEventTypeAutoContinueChanged = (1u << 12), + eBreakpointEventTypeInjectedCondition = (1u << 13)}; FLAGS_ENUM(WatchpointEventType){ eWatchpointEventTypeInvalidType = (1u << 0), Index: lldb/include/lldb/Target/Target.h =================================================================== --- lldb/include/lldb/Target/Target.h +++ lldb/include/lldb/Target/Target.h @@ -290,6 +290,12 @@ m_ignore_breakpoints = ignore; } + void SetInjectCondition(bool inject_condition) { + m_inject_condition = inject_condition; + } + + bool GetInjectCondition() const { return m_inject_condition; } + bool DoesKeepInMemory() const { return m_keep_in_memory; } void SetKeepInMemory(bool keep = true) { m_keep_in_memory = keep; } @@ -403,6 +409,7 @@ bool m_ansi_color_errors = false; bool m_result_is_internal = false; bool m_auto_apply_fixits = true; + bool m_inject_condition = false; /// True if the executed code should be treated as utility code that is only /// used by LLDB internally. bool m_running_utility_expression = false; Index: lldb/include/lldb/Target/Process.h =================================================================== --- lldb/include/lldb/Target/Process.h +++ lldb/include/lldb/Target/Process.h @@ -21,8 +21,10 @@ #include <unordered_set> #include <vector> +#include "lldb/Breakpoint/BreakpointInjectedSite.h" #include "lldb/Breakpoint/BreakpointSiteList.h" #include "lldb/Core/Communication.h" +#include "lldb/Core/Disassembler.h" #include "lldb/Core/LoadedModuleInfoList.h" #include "lldb/Core/PluginInterface.h" #include "lldb/Core/ThreadSafeValue.h" @@ -2030,6 +2032,13 @@ lldb::break_id_t CreateBreakpointSite(const lldb::BreakpointLocationSP &owner, bool use_hardware); + lldb::break_id_t + FallbackToRegularBreakpointSite(const lldb::BreakpointLocationSP &owner, + bool use_hardware, Log *log, + const char *error); + + size_t SaveInstructions(Address &address); + Status DisableBreakpointSiteByID(lldb::user_id_t break_id); Status EnableBreakpointSiteByID(lldb::user_id_t break_id); @@ -2743,6 +2752,8 @@ std::unique_ptr<UtilityFunction> m_dlopen_utility_func_up; std::once_flag m_dlopen_utility_func_flag_once; + void *m_overriden_instructions = nullptr; + size_t RemoveBreakpointOpcodesFromBuffer(lldb::addr_t addr, size_t size, uint8_t *buf) const; Index: lldb/include/lldb/Target/ExecutionContextScope.h =================================================================== --- lldb/include/lldb/Target/ExecutionContextScope.h +++ lldb/include/lldb/Target/ExecutionContextScope.h @@ -13,7 +13,7 @@ namespace lldb_private { -/// @class ExecutionContextScope ExecutionContextScope.h +/// \class ExecutionContextScope ExecutionContextScope.h /// "lldb/Target/ExecutionContextScope.h" Inherit from this if your object can /// reconstruct its /// execution context. Index: lldb/include/lldb/Target/ABI.h =================================================================== --- lldb/include/lldb/Target/ABI.h +++ lldb/include/lldb/Target/ABI.h @@ -133,6 +133,47 @@ virtual bool GetPointerReturnRegister(const char *&name) { return false; } + /// Allocate a memory stub for the fast condition breakpoint trampoline, and + /// build it by saving the register context, calling the argument structure + /// builder, passing the resulting structure to the condition checker, + /// restoring the register context, running the copied instructions and] + /// jumping back to the user source code. + /// + /// \param[in] instrs_size + /// The size in bytes of the copied instructions. + /// + /// \param[in] data + /// The copied instructions buffer. + /// + /// \param[in] jmp_addr + /// The address of the source . + /// + /// \param[in] util_func_addr + /// The address of the JIT-ed argument structure builder. + /// + /// \param[in] cond_expr_addr + /// The address of the JIT-ed condition checker. + /// + virtual bool SetupFastConditionalBreakpointTrampoline( + size_t instrs_size, void *data, lldb::addr_t &jmp_addr, + lldb::addr_t util_func_addr, lldb::addr_t cond_expr_addr) { + return false; + } + + virtual llvm::ArrayRef<uint8_t> GetJumpOpcode() { return 0; } + + virtual size_t GetJumpSize() { return 0; } + + virtual llvm::ArrayRef<uint8_t> GetCallOpcode() { return 0; } + + virtual size_t GetCallSize() { return 0; } + + virtual llvm::StringRef GetRegisterContextAsString() { return ""; } + + virtual llvm::StringRef GetMachTypesAsString() { return ""; } + + virtual bool ImplementsJIT() { return false; } + static lldb::ABISP FindPlugin(lldb::ProcessSP process_sp, const ArchSpec &arch); protected: Index: lldb/include/lldb/Symbol/VariableList.h =================================================================== --- lldb/include/lldb/Symbol/VariableList.h +++ lldb/include/lldb/Symbol/VariableList.h @@ -11,6 +11,7 @@ #include "lldb/Symbol/SymbolContext.h" #include "lldb/Symbol/Variable.h" +#include "lldb/Utility/Iterable.h" #include "lldb/lldb-private.h" namespace lldb_private { @@ -75,6 +76,13 @@ private: // For VariableList only DISALLOW_COPY_AND_ASSIGN(VariableList); + +public: + typedef AdaptedIterable<collection, lldb::VariableSP, vector_adapter> + VariableListCollectionIterable; + VariableListCollectionIterable Variables() { + return VariableListCollectionIterable(m_variables); + } }; } // namespace lldb_private Index: lldb/include/lldb/Expression/LLVMUserExpression.h =================================================================== --- lldb/include/lldb/Expression/LLVMUserExpression.h +++ lldb/include/lldb/Expression/LLVMUserExpression.h @@ -81,7 +81,8 @@ bool PrepareToExecuteJITExpression(DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx, - lldb::addr_t &struct_address); + lldb::addr_t &struct_address, + const EvaluateExpressionOptions options); virtual bool AddArguments(ExecutionContext &exe_ctx, std::vector<lldb::addr_t> &args, Index: lldb/include/lldb/Expression/ExpressionVariable.h =================================================================== --- lldb/include/lldb/Expression/ExpressionVariable.h +++ lldb/include/lldb/Expression/ExpressionVariable.h @@ -16,6 +16,7 @@ #include "lldb/Core/ValueObject.h" #include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Iterable.h" #include "lldb/lldb-public.h" namespace lldb_private { @@ -199,7 +200,19 @@ void Clear() { m_variables.clear(); } private: - std::vector<lldb::ExpressionVariableSP> m_variables; + typedef std::vector<lldb::ExpressionVariableSP> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + + collection m_variables; + +public: + typedef AdaptedIterable<collection, lldb::ExpressionVariableSP, + vector_adapter> + ExpressionVariableListCollectionIterable; + ExpressionVariableListCollectionIterable Variables() { + return ExpressionVariableListCollectionIterable(m_variables); + } }; class PersistentExpressionState : public ExpressionVariableList { Index: lldb/include/lldb/Expression/Expression.h =================================================================== --- lldb/include/lldb/Expression/Expression.h +++ lldb/include/lldb/Expression/Expression.h @@ -13,7 +13,7 @@ #include <string> #include <vector> - +#include "lldb/Core/AddressRange.h" #include "lldb/Expression/ExpressionTypeSystemHelper.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private.h" @@ -92,7 +92,11 @@ /// LLVM-style RTTI support. ExpressionKind getKind() const { return m_kind; } - + + const AddressRange GetJITAddressRange(void) const { + return AddressRange(m_jit_start_addr, m_jit_end_addr - m_jit_start_addr); + } + private: /// LLVM-style RTTI support. const ExpressionKind m_kind; Index: lldb/include/lldb/Core/Opcode.h =================================================================== --- lldb/include/lldb/Core/Opcode.h +++ lldb/include/lldb/Core/Opcode.h @@ -221,9 +221,6 @@ // Get the opcode exactly as it would be laid out in memory. uint32_t GetData(DataExtractor &data) const; -protected: - friend class lldb::SBInstruction; - const void *GetOpcodeDataBytes() const { switch (m_type) { case Opcode::eTypeInvalid: @@ -243,6 +240,9 @@ return nullptr; } +protected: + friend class lldb::SBInstruction; + lldb::ByteOrder GetDataByteOrder() const; bool GetEndianSwap() const { Index: lldb/include/lldb/Core/Disassembler.h =================================================================== --- lldb/include/lldb/Core/Disassembler.h +++ lldb/include/lldb/Core/Disassembler.h @@ -20,6 +20,7 @@ #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Iterable.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-enumerations.h" @@ -311,10 +312,16 @@ private: typedef std::vector<lldb::InstructionSP> collection; + collection m_instructions; + +public: typedef collection::iterator iterator; typedef collection::const_iterator const_iterator; - - collection m_instructions; + typedef AdaptedIterable<collection, lldb::InstructionSP, vector_adapter> + InstructionListCollectionIterable; + InstructionListCollectionIterable Instructions() { + return InstructionListCollectionIterable(m_instructions); + } }; class PseudoInstruction : public Instruction { Index: lldb/include/lldb/Breakpoint/BreakpointSite.h =================================================================== --- lldb/include/lldb/Breakpoint/BreakpointSite.h +++ lldb/include/lldb/Breakpoint/BreakpointSite.h @@ -45,6 +45,16 @@ // display any breakpoint opcodes. }; + /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) + enum BreakpointSiteKind { + eKindBreakpointSite, + eKindBreakpointInjectedSite, + }; + + static bool classof(const BreakpointSite *bp_site) { + return bp_site->getKind() == eKindBreakpointSite; + } + ~BreakpointSite() override; // This section manages the breakpoint traps @@ -147,7 +157,7 @@ /// \param[in] thread /// The thread against which to test. /// - /// return + /// \return /// \b true if the collection contains at least one location that /// would be valid for this thread, false otherwise. bool ValidForThisThread(Thread *thread); @@ -187,6 +197,9 @@ BreakpointSite::Type GetType() const { return m_type; } + /// LLVM-style RTTI support. + BreakpointSiteKind getKind() const { return m_kind; } + void SetType(BreakpointSite::Type type) { m_type = type; } private: @@ -196,6 +209,7 @@ // a site, so let it be the one to manage setting the location hit count once // and only once. friend class StopInfoBreakpoint; + friend class BreakpointInjectedSite; void BumpHitCounts(); @@ -221,13 +235,16 @@ std::recursive_mutex m_owners_mutex; ///< This mutex protects the owners collection. + const BreakpointSiteKind m_kind; + static lldb::break_id_t GetNextID(); // Only the Process can create breakpoint sites in // Process::CreateBreakpointSite (lldb::BreakpointLocationSP &, bool). BreakpointSite(BreakpointSiteList *list, const lldb::BreakpointLocationSP &owner, lldb::addr_t m_addr, - bool use_hardware); + bool use_hardware, + BreakpointSiteKind kind = eKindBreakpointSite); DISALLOW_COPY_AND_ASSIGN(BreakpointSite); }; Index: lldb/include/lldb/Breakpoint/BreakpointOptions.h =================================================================== --- lldb/include/lldb/Breakpoint/BreakpointOptions.h +++ lldb/include/lldb/Breakpoint/BreakpointOptions.h @@ -32,15 +32,16 @@ public: enum OptionKind { - eCallback = 1 << 0, - eEnabled = 1 << 1, - eOneShot = 1 << 2, - eIgnoreCount = 1 << 3, - eThreadSpec = 1 << 4, - eCondition = 1 << 5, - eAutoContinue = 1 << 6, - eAllOptions = (eCallback | eEnabled | eOneShot | eIgnoreCount | eThreadSpec - | eCondition | eAutoContinue) + eCallback = 1 << 0, + eEnabled = 1 << 1, + eOneShot = 1 << 2, + eIgnoreCount = 1 << 3, + eThreadSpec = 1 << 4, + eCondition = 1 << 5, + eAutoContinue = 1 << 6, + eInjectCondition = 1 << 7, + eAllOptions = (eCallback | eEnabled | eOneShot | eIgnoreCount | + eThreadSpec | eCondition | eAutoContinue | eInjectCondition) }; struct CommandData { CommandData() @@ -113,9 +114,12 @@ /// \param[in] auto_continue /// Should this breakpoint auto-continue after running its commands. /// + /// \param[in] inject_condition + /// Should the condition be injected and checked in-process. + /// BreakpointOptions(const char *condition, bool enabled = true, int32_t ignore = 0, bool one_shot = false, - bool auto_continue = false); + bool auto_continue = false, bool inject_condition = false); /// Breakpoints make options with all flags set. Locations and Names make /// options with no flags set. @@ -347,7 +351,20 @@ bool AnySet() const { return m_set_flags.AnySet(eAllOptions); } - + + /// Check if the breakpoint condition should be injected + /// + /// \return + /// If condition is injected \b true, \b false otherwise. + bool GetInjectCondition() const; + + /// If \a inject_condition is \b true, inject the breakpoint condition in the + /// process. + void SetInjectCondition(bool inject_condition) { + m_inject_condition = inject_condition; + m_set_flags.Set(eInjectCondition); + } + protected: // Classes that inherit from BreakpointOptions can see and modify these bool IsOptionSet(OptionKind kind) @@ -361,6 +378,7 @@ EnabledState, OneShotState, AutoContinue, + InjectCondition, LastOptionName }; static const char *g_option_names[(size_t)OptionNames::LastOptionName]; Index: lldb/include/lldb/Breakpoint/BreakpointLocation.h =================================================================== --- lldb/include/lldb/Breakpoint/BreakpointLocation.h +++ lldb/include/lldb/Breakpoint/BreakpointLocation.h @@ -131,6 +131,16 @@ // condition has been set. const char *GetConditionText(size_t *hash = nullptr) const; + /// Check if the breakpoint condition should be injected + /// + /// \return + /// If condition is injected \b true, \b false otherwise. + bool GetInjectCondition() const; + + /// If \a inject_condition is \b true, inject the breakpoint condition in the + /// process. + void SetInjectCondition(bool inject_condition); + bool ConditionSaysStop(ExecutionContext &exe_ctx, Status &error); /// Set the valid thread to be checked when the breakpoint is hit. @@ -273,6 +283,7 @@ protected: friend class BreakpointSite; + friend class BreakpointInjectedSite; friend class BreakpointLocationList; friend class Process; friend class StopInfoBreakpoint; Index: lldb/include/lldb/Breakpoint/BreakpointInjectedSite.h =================================================================== --- /dev/null +++ lldb/include/lldb/Breakpoint/BreakpointInjectedSite.h @@ -0,0 +1,198 @@ +//===-- BreakpointInjectedSite.h --------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_BreakpointInjectedSite_h_ +#define liblldb_BreakpointInjectedSite_h_ + +#include "lldb/lldb-forward.h" + +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/BreakpointLocationCollection.h" +#include "lldb/Breakpoint/BreakpointSite.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/DataEncoder.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Logging.h" + +#include "llvm/DebugInfo/DWARF/DWARFExpression.h" + +namespace lldb_private { + +/// \class BreakpointInjectedSite BreakpointInjectedSite.h +/// Class that setup fast conditional breakpoints. +/// +/// Fast conditional breakpoints have a different way of evaluating the +/// condition expression by doing the check in-process, which saves the cost +/// of doing several context switches between the inferior and LLDB. +/// +/// +class BreakpointInjectedSite : public BreakpointSite { +public: + /// LLVM-style RTTI support. + static bool classof(const BreakpointSite *bp_site) { + return bp_site->getKind() == eKindBreakpointInjectedSite; + } + + // Destructor + ~BreakpointInjectedSite() override; + + /// Fetch each breakpoint location's condition and build the JIT-ed condition + /// checker with the injected trap. + /// + /// \return + /// \b true if building the condition checker succeeded, + /// \b false otherwise. + bool BuildConditionExpression(); + + lldb_private::ExecutionContext GetOwnerExecutionContext() { + return m_owner_exe_ctx; + } + + lldb::addr_t GetConditionExpressionAddress() { + return m_condition_expression_sp->StartAddress(); + } + + lldb::addr_t GetUtilityFunctionAddress() { + return m_create_args_struct_function_sp->StartAddress(); + } + + lldb::addr_t GetTrapAddress() { + return m_trap_addr.GetLoadAddress(m_target_sp.get()); + } + + /// \struct ArgumentMetadata BreakpointInjectedSite.h + /// "lldb/Breakpoint/BreakpointInjectedSite.h" Struct that contains debugging + /// information for the variable used in the condition expression. + struct VariableMetadata { + + // Constructor + + /// This constructor stores the variable name and type size and create a + /// DWARF Expression from the DataExtractor containing the DWARF Operation + /// and its operands. + /// + /// \param[in] name + /// The name of the variable. + /// + /// \param[in] size + /// The type size of the variable. + /// + /// \param[in] data + /// The buffer containing the variable DWARF Expression data. + /// + /// \param[in] version + /// The version of DWARF that is used for the DWARF Expression. + /// + /// \param[in] address_size + /// The size in bytes for the address of the current architecture. + /// + VariableMetadata(std::string name, size_t size, llvm::DataExtractor data, + uint16_t version, uint8_t address_size) + : name(std::move(name)), size(size), + dwarf(data, version, address_size) {} + + /// The variable name. + std::string name; + /// The variable size. + size_t size; + /// The variable DWARF Expression. + llvm::DWARFExpression dwarf; + }; + +private: + friend class Process; + + // Constructor + + /// This constructor stores the variable name and type size and create a + /// DWARF Expression from the DataExtractor containing the DWARF Operation + /// and its operands. + /// + /// \param[in] list + /// The list of the breakpoint sites already set. + /// + /// \param[in] owner + /// The breakpoint location holding this breakpoint site. + /// + /// \param[in] addr + /// The breakpoint site load address. + /// + BreakpointInjectedSite(BreakpointSiteList *list, + const lldb::BreakpointLocationSP &owner, + lldb::addr_t addr); + + /// Scan the JIT-ed condition expression instructions and look for the + /// injected trap instruction. + /// + /// \param[in] jit + /// The buffer containing the JIT-ed condition expression. + /// + /// \param[in] size + /// The size of the JIT-ed condition expression in memory. + /// + /// \return + /// \b true if the injected trap instruction is found, \b false otherwise. + bool ResolveTrapAddress(void *jit, size_t size); + + /// Iterate over the JIT-ed condition expression variable and build a metadata + /// vector used to resolve variables when checking the condition. + /// + /// \return + /// \b true if the metadata gathering succeeded, \b false otherwise. + bool GatherArgumentsMetadata(); + + /// Build the argument structure used by the JIT-ed condition expression. + /// Allocate dynamically the structure and using the variable metadata vector, + /// write the variable address in the argument structure. + /// + /// \return + /// \b true if building the argument structure succeeded, + /// \b false otherwise. + bool CreateArgumentsStructure(); + + /// Parse the variable's DWARF Expression and return the proper source code, + /// according to the DWARF Operation. + /// + /// \param[in] index + /// The index of the variable in the metadata vector. + /// + /// \param[in] error + /// The thread against which to test. + /// + /// \return + /// The source code needed to copy the variable in the argument structure. + std::string ParseDWARFExpression(size_t index, Status &error); + +private: + /// The target that hold the breakpoint. + lldb::TargetSP m_target_sp; + /// The breakpoint location load address. + Address m_real_addr; + /// The injected trap instruction address. + Address m_trap_addr; + /// The breakpoint location execution context. + lldb_private::ExecutionContext m_owner_exe_ctx; + /// The disassembler used to resolve the injected trap address. + lldb::DisassemblerSP m_disassembler_sp; + /// The JIT-ed condition checker. + lldb::UserExpressionSP m_condition_expression_sp; + /// The JIT-ed argument structure builder. + lldb::UtilityFunctionSP m_create_args_struct_function_sp; + /// The variable metadata vector. + std::vector<VariableMetadata> m_metadatas; +}; + +} // namespace lldb_private + +#endif // liblldb_BreakpointInjectedSite_h_ Index: lldb/include/lldb/Breakpoint/Breakpoint.h =================================================================== --- lldb/include/lldb/Breakpoint/Breakpoint.h +++ lldb/include/lldb/Breakpoint/Breakpoint.h @@ -63,6 +63,7 @@ /// \b Ignore Count /// \b Callback /// \b Condition +/// \b Inject Condition /// Note, these options can be set on the breakpoint, and they can also be set /// on the individual locations. The options set on the breakpoint take /// precedence over the options set on the individual location. So for @@ -399,6 +400,16 @@ // condition has been set. const char *GetConditionText() const; + /// If \a inject_condition is \b true, inject the breakpoint condition in the + /// process. + void SetInjectCondition(bool inject_condition); + + /// Check if the breakpoint condition should be injected + /// + /// \return + /// If condition is injected \b true, \b false otherwise. + bool GetInjectCondition() const; + // The next section are various utility functions. /// Return the number of breakpoint locations that have resolved to actual Index: lldb/include/lldb/API/SBBreakpointLocation.h =================================================================== --- lldb/include/lldb/API/SBBreakpointLocation.h +++ lldb/include/lldb/API/SBBreakpointLocation.h @@ -53,6 +53,10 @@ bool GetAutoContinue(); + void SetInjectCondition(bool inject_condition); + + bool GetInjectCondition(); + void SetScriptCallbackFunction(const char *callback_function_name); SBError SetScriptCallbackBody(const char *script_body_text); Index: lldb/include/lldb/API/SBBreakpoint.h =================================================================== --- lldb/include/lldb/API/SBBreakpoint.h +++ lldb/include/lldb/API/SBBreakpoint.h @@ -74,6 +74,10 @@ bool GetAutoContinue(); + void SetInjectCondition(bool inject_condition); + + bool GetInjectCondition(); + void SetThreadID(lldb::tid_t sb_thread_id); lldb::tid_t GetThreadID();
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits