bhushan created this revision. bhushan added reviewers: clayborg, tberghammer. bhushan added subscribers: lldb-commits, jaydeep, nitesh.jain, mohit.bhakkad, sagar. bhushan set the repository for this revision to rL LLVM.
This patch handles atomic sequences during single step. LLDB should treat all instructions in the atomic sequence as if they are a single instruction i.e as a "single instruction block". So while single stepping, LLDB should detect and skip such sequence by placing breakpoints only outside of such sequence i.e at the end of the sequence. In MIPS, atomic sequence starts with LL (linked load) instruction and end with SC (store conditional) instruction. Example of atomic sequence in MIPS: 0x400c08 <+372>: ll $4, 0($2) ----> Start of sequence 0x400c0c <+376>: bne $4, $5, 28 ---> Atomic sequence can contain branches that jump outside sequence range. 0x400c10 <+380>: addiu $3, $zero, 0 0x400c14 <+384>: move $1, $6 0x400c18 <+388>: sc $1, 0($2) -----> End of sequence 0x400c1c <+392>: beqz $1, -20 0x400c20 <+396>: addiu $3, $zero, 1 0x400c24 <+400>: sync 0x400c28 <+404>: bnez $3, 16 ---> Target of branch from sequence Considering above example, while single stepping from LL instruction, LLDB should stop at instruction after the end of sequence (instead of stopping at instruction next to LL). There can be multiple exit/end points to atomic sequence. 1. SC instruction 2. Branch instructions in atomic sequence that jump outside sequence. So to handle this, LLDB should place breakpoints at multiple locations: 1. Breakpoint at instruction after SC instruction (i.e at 0x400c1c in above ex) 2. Breakpoints at target addresses of branch instructions (if the branch target address is outside the sequence) i.e at 0x400c28, target of "bne $4, $5, 28" instruction. These breakpoint addresses are determined by EmulateInstruction. This patch makes few assumptions: 1. Assumes that no atomic sequence for mips is longer than 16 instructions. i.e scan upto maximum 16 instructions from LL to find end of sequence. 2. Assumes that the atomic sequence ends with a sc/scd instruction. So if we dont find "sc/scd" instruction then do not put any breakpoint. i.e fallback to the standard single-step code. Testcase: This patch also adds a testcase "TestStepInAtomicSequence.py" to test this change (currently enabled for MIPS only). The test finds starting instruction of atomic sequence, runs till that instruction then does a single step and finally verifies that we have stopped after end of sequence. Repository: rL LLVM http://reviews.llvm.org/D17535 Files: packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/Makefile packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/TestStepInAtomicSequence.py packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/main.cpp source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.cpp source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.h source/Plugins/Process/Linux/NativeProcessLinux.cpp source/Plugins/Process/Linux/NativeProcessLinux.h
Index: packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/main.cpp =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/main.cpp @@ -0,0 +1,21 @@ +#include <atomic> +using namespace std; + +std::atomic<int> ai; + +int tst_val= 4; +int new_val= 5; +bool exchanged= false; + +int main() +{ + ai= 3; + + // tst_val != ai ==> tst_val is modified + exchanged= ai.compare_exchange_strong( tst_val, new_val ); + + // tst_val == ai ==> ai is modified + exchanged= ai.compare_exchange_strong( tst_val, new_val ); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/TestStepInAtomicSequence.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/TestStepInAtomicSequence.py @@ -0,0 +1,108 @@ +""" +Test to single step from atomic sequence +""" + +from __future__ import print_function + +import os, time +import re +import unittest2 +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class StepInAtomicSequenceAPITestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIf(archs=no_match(re.compile('mips*'))) + def test(self): + self.build() + exe = os.path.join(os.getcwd(), "a.out") + self.expect("file " + exe, + patterns = [ "Current executable set to .*a.out.*" ]) + + # Create a target by the debugger. + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + breakpoint = target.BreakpointCreateByName('main', 'a.out') + self.assertTrue(breakpoint and + breakpoint.GetNumLocations() == 1, + VALID_BREAKPOINT) + + # Now launch the process, and do not stop at entry point. + process = target.LaunchSimple (None, None, self.get_process_working_directory()) + self.assertTrue(process, PROCESS_IS_VALID) + + threads = lldbutil.get_threads_stopped_at_breakpoint(process, breakpoint) + self.assertEqual(len(threads), 1) + self.thread = threads[0] + + arch = self.getArchitecture() + if re.match("mips" , arch): + atomic_seq_start_opcodes = ["ll", "lld"] + atomic_seq_end_opcodes = ["sc", "scd"] + else: + # TODO please add your arch here + self.fail('unimplemented for arch = "{arch}"'.format(arch=self.getArchitecture())) + + self.atomic_start_insns = atomic_seq_start_opcodes + self.atomic_end_insns = atomic_seq_end_opcodes + + list = target.FindFunctions('main', lldb.eFunctionNameTypeAuto) + self.assertTrue(list.GetSize() == 1) + sc = list.GetContextAtIndex(0) + self.assertTrue(sc.GetSymbol().GetName() == "main") + function = sc.GetFunction() + self.assertTrue(function) + self.function(function, target) + + def function (self, function, target): + """Stop at start of atomic sequence and single step""" + # Get the list of all instructions in the function + insts = function.GetInstructions(target) + print(insts) + i = 0 + for inst in insts: + inst_opcode = inst.GetMnemonic(target) + if inst_opcode in self.atomic_start_insns: + # Get the address of instruction starting atomic sequence + start_of_seq = inst.GetAddress().GetLoadAddress(target) + + # We have found starting instruction of atomic sequence + # now scan more instructions to find end of atomic sequence + # Assume that no atomic sequence is longer than 16 instructions + for j in range(1,16): + next_inst = insts.GetInstructionAtIndex(i+j) + next_inst_opcode = next_inst.GetMnemonic(target) + if next_inst_opcode in self.atomic_end_insns: + end_of_seq = next_inst.GetAddress().GetLoadAddress(target) + break + + # Stop at start of atomic sequence + self.thread.RunToAddress(start_of_seq) + + # Step one instruction + self.thread.StepInstruction(False) + + # Get the PC after single step + frame = self.thread.GetFrameAtIndex(0) + current_pc = frame.GetPC() + + print ("Sequence Starts at 0x%x:"% start_of_seq) + print ("Sequence ends at 0x%x:"% end_of_seq) + print ("After step stopped at 0x%x:"% current_pc) + + # Check if single step have skipped entire atomic sequence + self.assertTrue(current_pc > end_of_seq) + i += 1 + else: + i += 1 + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() Index: packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/single_step_atomic_sequence/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules Index: source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.h +++ source/Plugins/Process/Linux/NativeProcessLinux.h @@ -147,7 +147,7 @@ // List of thread ids stepping with a breakpoint with the address of // the relevan breakpoint - std::map<lldb::tid_t, lldb::addr_t> m_threads_stepping_with_breakpoint; + std::multimap<lldb::tid_t, lldb::addr_t> m_threads_stepping_with_breakpoint; /// @class LauchArgs /// Index: source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1522,6 +1522,7 @@ // eRegisterKindDWARF -> RegsiterValue std::unordered_map<uint32_t, RegisterValue> m_register_values; + std::vector<lldb::addr_t> next_pc_addresses; EmulatorBaton(NativeProcessLinux* process, NativeRegisterContext* reg_context) : m_process(process), m_reg_context(reg_context) {} @@ -1581,6 +1582,10 @@ { EmulatorBaton* emulator_baton = static_cast<EmulatorBaton*>(baton); emulator_baton->m_register_values[reg_info->kinds[eRegisterKindDWARF]] = reg_value; + if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC) + { + emulator_baton->next_pc_addresses.push_back(reg_value.GetAsUInt64()); + } return true; } @@ -1632,6 +1637,7 @@ auto pc_it = baton.m_register_values.find(reg_info_pc->kinds[eRegisterKindDWARF]); auto flags_it = baton.m_register_values.find(reg_info_flags->kinds[eRegisterKindDWARF]); + const auto arch_machine = m_arch.GetMachine(); lldb::addr_t next_pc; lldb::addr_t next_flags; @@ -1662,7 +1668,12 @@ return Error ("Instruction emulation failed unexpectedly."); } - if (m_arch.GetMachine() == llvm::Triple::arm) + if (baton.next_pc_addresses.empty()) + { + baton.next_pc_addresses.push_back(next_pc); + } + + if (arch_machine == llvm::Triple::arm) { if (next_flags & 0x20) { @@ -1675,11 +1686,18 @@ error = SetSoftwareBreakpoint(next_pc, 4); } } - else if (m_arch.GetMachine() == llvm::Triple::mips64 - || m_arch.GetMachine() == llvm::Triple::mips64el - || m_arch.GetMachine() == llvm::Triple::mips - || m_arch.GetMachine() == llvm::Triple::mipsel) - error = SetSoftwareBreakpoint(next_pc, 4); + else if (arch_machine == llvm::Triple::mips64 || + arch_machine == llvm::Triple::mips64el || + arch_machine == llvm::Triple::mips || + arch_machine == llvm::Triple::mipsel) + { + for (const auto addr: baton.next_pc_addresses) + { + error = SetSoftwareBreakpoint(addr, 4); + if (error.Fail()) + return error; + } + } else { // No size hint is given for the next breakpoint @@ -1689,7 +1707,12 @@ if (error.Fail()) return error; - m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc}); + for (const auto addr: baton.next_pc_addresses) + { + m_threads_stepping_with_breakpoint.insert({thread.GetID(), addr}); + } + + baton.next_pc_addresses.clear(); return Error(); } Index: source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.h =================================================================== --- source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.h +++ source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.h @@ -118,6 +118,18 @@ GetOpcodeForInstruction (const char *op_name); bool + IsBranch (llvm::MCInst& insn); + + bool + GetInstructionAtAddress (lldb::addr_t insn_addr, llvm::MCInst& mc_insn); + + void + AddBreakPointAddress (lldb::addr_t bp_addr); + + bool + WriteBreakPointAddresses (); + + bool Emulate_DADDiu (llvm::MCInst& insn); bool @@ -133,6 +145,9 @@ Emulate_LDST_Reg (llvm::MCInst& insn); bool + Emulate_LL (llvm::MCInst& insn); + + bool Emulate_BXX_3ops (llvm::MCInst& insn); bool @@ -238,6 +253,11 @@ std::unique_ptr<llvm::MCAsmInfo> m_asm_info; std::unique_ptr<llvm::MCContext> m_context; std::unique_ptr<llvm::MCInstrInfo> m_insn_info; + bool m_auto_advance_pc; + lldb::addr_t m_atomic_seq_start; + lldb::addr_t m_atomic_seq_end; + uint32_t m_bytes_from_atomic_seq_start; + std::vector<lldb::addr_t> m_breakpoint_addresses; }; #endif // EmulateInstructionMIPS64_h_ Index: source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.cpp =================================================================== --- source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.cpp +++ source/Plugins/Instruction/MIPS64/EmulateInstructionMIPS64.cpp @@ -152,6 +152,11 @@ m_disasm.reset (target->createMCDisassembler (*m_subtype_info, *m_context)); assert (m_disasm.get()); + + m_auto_advance_pc = false; + m_atomic_seq_start = 0; + m_atomic_seq_end = 0; + m_bytes_from_atomic_seq_start = 0; } void @@ -510,7 +515,8 @@ { "LHE", &EmulateInstructionMIPS64::Emulate_LDST_Imm, "LHE rt, offset(base)" }, { "LHU", &EmulateInstructionMIPS64::Emulate_LDST_Imm, "LHU rt, offset(base)" }, { "LHUE", &EmulateInstructionMIPS64::Emulate_LDST_Imm, "LHUE rt, offset(base)" }, - { "LL", &EmulateInstructionMIPS64::Emulate_LDST_Imm, "LL rt, offset(base)" }, + { "LL", &EmulateInstructionMIPS64::Emulate_LL, "LL rt, offset(base)" }, + { "LLD", &EmulateInstructionMIPS64::Emulate_LL, "LLD rt, offset(base)" }, { "LLE", &EmulateInstructionMIPS64::Emulate_LDST_Imm, "LLE rt, offset(base)" }, { "LUXC1", &EmulateInstructionMIPS64::Emulate_LDST_Reg, "LUXC1 fd, index (base)" }, { "LW", &EmulateInstructionMIPS64::Emulate_LDST_Imm, "LW rt, offset(rs)" }, @@ -648,6 +654,74 @@ } bool +EmulateInstructionMIPS64::IsBranch (llvm::MCInst& insn) +{ + return m_insn_info->get(insn.getOpcode()).isBranch(); +} + +bool +EmulateInstructionMIPS64::GetInstructionAtAddress (lldb::addr_t insn_addr, llvm::MCInst& mc_insn) +{ + DataExtractor data; + Opcode opcode; + uint64_t insn_size; + bool success = false; + + Context read_inst_context; + read_inst_context.type = eContextReadOpcode; + read_inst_context.SetNoArgs (); + + opcode.SetOpcode32 (ReadMemoryUnsigned (read_inst_context, insn_addr, 4, 0, &success), GetByteOrder()); + + if (!success) + return false; + + // Get mc_insn + if (opcode.GetData (data)) + { + llvm::MCDisassembler::DecodeStatus decode_status; + llvm::ArrayRef<uint8_t> raw_insn (data.GetDataStart(), data.GetByteSize()); + + decode_status = m_disasm->getInstruction (mc_insn, insn_size, raw_insn, insn_addr, llvm::nulls(), llvm::nulls()); + + if (decode_status == llvm::MCDisassembler::Success) + return true; + } + return false; +} + +void +EmulateInstructionMIPS64::AddBreakPointAddress (lldb::addr_t bp_addr) +{ + // Do not add duplicate addresses + if (std::find(m_breakpoint_addresses.begin(), m_breakpoint_addresses.end(), bp_addr) == m_breakpoint_addresses.end()) + { + m_breakpoint_addresses.push_back(bp_addr); + } +} + +bool +EmulateInstructionMIPS64::WriteBreakPointAddresses () +{ + Context bad_vaddr_context; + bad_vaddr_context.type = eContextInvalid; + + if (!m_breakpoint_addresses.empty()) + { + for (const auto addr: m_breakpoint_addresses) + { + // skip breakpoint placed in atomic sequence + if (addr > m_atomic_seq_start && addr <= m_atomic_seq_end) + continue; + if (!WriteRegisterUnsigned (bad_vaddr_context, eRegisterKindDWARF, dwarf_pc_mips, addr)) + return false; + } + } + + return true; +} + +bool EmulateInstructionMIPS64::EvaluateInstruction (uint32_t evaluate_options) { bool success = false; @@ -681,12 +755,12 @@ MipsOpcode *opcode_data = GetOpcodeForInstruction (op_name); if (opcode_data == NULL) - return false; + return false; uint64_t old_pc = 0, new_pc = 0; - const bool auto_advance_pc = evaluate_options & eEmulateInstructionOptionAutoAdvancePC; + m_auto_advance_pc = evaluate_options & eEmulateInstructionOptionAutoAdvancePC; - if (auto_advance_pc) + if (m_auto_advance_pc) { old_pc = ReadRegisterUnsigned (eRegisterKindDWARF, dwarf_pc_mips64, 0, &success); if (!success) @@ -698,7 +772,7 @@ if (!success) return false; - if (auto_advance_pc) + if (m_auto_advance_pc) { new_pc = ReadRegisterUnsigned (eRegisterKindDWARF, dwarf_pc_mips64, 0, &success); if (!success) @@ -929,6 +1003,14 @@ if (!success) return false; + // Consider branch instructions in atomic sequence. + if (m_atomic_seq_start) + { + target = pc + offset + m_bytes_from_atomic_seq_start; + AddBreakPointAddress ((lldb::addr_t)target); + return true; + } + rs_val = (int64_t) ReadRegisterUnsigned (eRegisterKindDWARF, dwarf_zero_mips64 + rs, 0, &success); if (!success) return false; @@ -1176,6 +1258,14 @@ if (!success) return false; + // Consider branch instructions in atomic sequence. + if (m_atomic_seq_start) + { + target = pc + offset; + AddBreakPointAddress ((lldb::addr_t)target); + return true; + } + rs_val = (int64_t) ReadRegisterUnsigned (eRegisterKindDWARF, dwarf_zero_mips64 + rs, 0, &success); if (!success) return false; @@ -1278,6 +1368,14 @@ if (!success) return false; + // Consider branch instructions in atomic sequence. + if (m_atomic_seq_start) + { + target = pc + offset + 4; + AddBreakPointAddress ((lldb::addr_t)target); + return true; + } + rs_val = (int64_t) ReadRegisterUnsigned (eRegisterKindDWARF, dwarf_zero_mips64 + rs, 0, &success); if (!success) return false; @@ -1374,6 +1472,14 @@ if (!success) return false; + // Consider branch instructions in atomic sequence. + if (m_atomic_seq_start) + { + target = pc + offset + 4; + AddBreakPointAddress ((lldb::addr_t)target); + return true; + } + rs_val = (int64_t) ReadRegisterUnsigned (eRegisterKindDWARF, dwarf_zero_mips64 + rs, 0, &success); if (!success) return false; @@ -2045,3 +2151,69 @@ return true; } + +bool +EmulateInstructionMIPS64::Emulate_LL (llvm::MCInst& insn) +{ + // Emulate this instruction only for single step + if (!m_auto_advance_pc) + return true; + + bool success = false; + uint32_t atomic_sequence_length = 16; // Assume that no atomic sequence is longer than 16 instructions. + lldb::addr_t insn_addr = m_addr; + + // Mark start of atomic sequence + m_atomic_seq_start = insn_addr; + + for (uint32_t insn_count = 0; insn_count < atomic_sequence_length; ++insn_count) + { + llvm::MCInst mc_insn; + insn_addr = insn_addr + 4; + + // Get the instruction + success = GetInstructionAtAddress (insn_addr, mc_insn); + if (!success) + return false; + + // Keep the track of how much bytes we have come from LL instruction. + m_bytes_from_atomic_seq_start += 4; + + const char *op_name = m_insn_info->getName (mc_insn.getOpcode ()); + + // Check if it is branch instruction. + if (IsBranch (mc_insn)) + { + MipsOpcode *opcode_data = GetOpcodeForInstruction (op_name); + if (opcode_data != NULL) + { + /* Let the existing emulation function do the PC write */ + success = (this->*opcode_data->callback) (mc_insn); + } + } + else + { + if (!strcasecmp (op_name, "SC") || + !strcasecmp (op_name, "SCD")) + { + // SC/SCD is the end of atomic sequence, add breakpoint at next instruction. + AddBreakPointAddress (insn_addr + 4); + // Address of sc/scd instruction + m_atomic_seq_end = insn_addr; + break; + } + } + } + + // Place the breakpoints If and only if we found end of atomic sequence. + if (m_atomic_seq_end) + WriteBreakPointAddresses (); + + // Reset the counters + m_atomic_seq_start = 0; + m_atomic_seq_end = 0; + m_bytes_from_atomic_seq_start = 0; + m_breakpoint_addresses.clear(); + + return true; +}
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits