Index: include/lldb/API/SBTarget.h =================================================================== --- include/lldb/API/SBTarget.h +++ include/lldb/API/SBTarget.h @@ -580,6 +580,12 @@ BreakpointCreateByLocation(const lldb::SBFileSpec &file_spec, uint32_t line, lldb::addr_t offset, SBFileSpecList &module_list); + lldb::SBBreakpoint + BreakpointCreateByLocation(const lldb::SBFileSpec &file_spec, uint32_t line, + uint32_t column, lldb::addr_t offset, + SBFileSpecList &module_list); + + lldb::SBBreakpoint BreakpointCreateByName(const char *symbol_name, const char *module_name = nullptr); Index: include/lldb/Breakpoint/BreakpointResolver.h =================================================================== --- include/lldb/Breakpoint/BreakpointResolver.h +++ include/lldb/Breakpoint/BreakpointResolver.h @@ -200,6 +200,7 @@ Inlines, LanguageName, LineNumber, + Column, ModuleName, NameMaskArray, Offset, @@ -224,8 +225,11 @@ /// number that matches, and then filter down the matching addresses to /// unique entries, and skip the prologue if asked to do so, and then set /// breakpoint locations in this breakpoint for all the resultant addresses. + /// When \p column is nonzero the \p line and \p column args are used to + /// filter the results to find the first breakpoint >= (line, column). void SetSCMatchesByLine(SearchFilter &filter, SymbolContextList &sc_list, - bool skip_prologue, llvm::StringRef log_ident); + bool skip_prologue, llvm::StringRef log_ident, + uint32_t line = 0, uint32_t column = 0); void SetSCMatchesByLine(SearchFilter &, SymbolContextList &, bool, const char *) = delete; Index: include/lldb/Breakpoint/BreakpointResolverFileLine.h =================================================================== --- include/lldb/Breakpoint/BreakpointResolverFileLine.h +++ include/lldb/Breakpoint/BreakpointResolverFileLine.h @@ -28,9 +28,9 @@ class BreakpointResolverFileLine : public BreakpointResolver { public: BreakpointResolverFileLine(Breakpoint *bkpt, const FileSpec &resolver, - uint32_t line_no, lldb::addr_t m_offset, - bool check_inlines, bool skip_prologue, - bool exact_match); + uint32_t line_no, uint32_t column, + lldb::addr_t m_offset, bool check_inlines, + bool skip_prologue, bool exact_match); static BreakpointResolver * CreateFromStructuredData(Breakpoint *bkpt, @@ -65,10 +65,11 @@ void FilterContexts(SymbolContextList &sc_list, bool is_relative); friend class Breakpoint; - FileSpec m_file_spec; // This is the file spec we are looking for. - uint32_t m_line_number; // This is the line number that we are looking for. - bool m_inlines; // This determines whether the resolver looks for inlined - // functions or not. + FileSpec m_file_spec; ///< This is the file spec we are looking for. + uint32_t m_line_number; ///< This is the line number that we are looking for. + uint32_t m_column; ///< This is the column that we are looking for. + /// This determines whether the resolver looks for inlined functions or not. + bool m_inlines; bool m_skip_prologue; bool m_exact_match; Index: include/lldb/Target/Target.h =================================================================== --- include/lldb/Target/Target.h +++ include/lldb/Target/Target.h @@ -549,7 +549,7 @@ // module it is nullptr lldb::BreakpointSP CreateBreakpoint(const FileSpecList *containingModules, const FileSpec &file, uint32_t line_no, - lldb::addr_t offset, + uint32_t column, lldb::addr_t offset, LazyBool check_inlines, LazyBool skip_prologue, bool internal, bool request_hardware, Index: packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_by_line_and_column/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_by_line_and_column/Makefile @@ -0,0 +1,6 @@ +LEVEL = ../../../make + +C_SOURCES := main.c +CFLAGS_EXTRAS += -std=c99 -gcolumn-info + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_by_line_and_column/TestBreakpointByLineAndColumn.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_by_line_and_column/TestBreakpointByLineAndColumn.py @@ -0,0 +1,57 @@ +""" +Test setting a breakpoint by line and column. +""" + +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 BreakpointByLineAndColumnTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def testBreakpointByLineAndColumn(self): + self.build() + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + breakpoint = target.BreakpointCreateByLocation( + lldb.SBFileSpec("main.c"), 20, 50, 0, lldb.SBFileSpecList()) + + process = target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertTrue(breakpoint.GetHitCount() == 1) + self.expect("fr v did_call", substrs='1') + in_then = False + for i in range(breakpoint.GetNumLocations()): + b_loc = breakpoint.GetLocationAtIndex(i).GetAddress().GetLineEntry() + self.assertEqual(b_loc.GetLine(), 20) + in_then |= b_loc.GetColumn() == 50 + self.assertTrue(in_then) + + def testBreakpointByLine(self): + self.build() + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + breakpoint = target.BreakpointCreateByLocation( + lldb.SBFileSpec("main.c"), 20, 0, 0, lldb.SBFileSpecList()) + + process = target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertTrue(breakpoint.GetHitCount() == 1) + self.expect("fr v did_call", substrs='0') + in_condition = False + for i in range(breakpoint.GetNumLocations()): + b_loc = breakpoint.GetLocationAtIndex(i).GetAddress().GetLineEntry() + self.assertEqual(b_loc.GetLine(), 20) + in_condition |= b_loc.GetColumn() < 30 + self.assertTrue(in_condition) Index: packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_by_line_and_column/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_by_line_and_column/main.c @@ -0,0 +1,23 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +int square(int x) +{ + return x * x; +} + +int main (int argc, char const *argv[]) +{ + int did_call = 0; + + // Line 20. v Column 50. + if(square(argc+1) != 0) { did_call = 1; return square(argc); } + // ^ + return square(0); +} Index: packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py =================================================================== --- packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py +++ packages/Python/lldbsuite/test/functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py @@ -133,9 +133,9 @@ patterns=[ "1: file = '.*main.c', line = %d, exact_match = 0, locations = 1" % self.line, - "1.1: .+at main.c:%d, .+unresolved, hit count = 0" % + "1.1: .+at main.c:%d:?[0-9]*, .+unresolved, hit count = 0" % self.line, - "2.1: .+at main.c:%d, .+unresolved, hit count = 0" % + "2.1: .+at main.c:%d:?[0-9]*, .+unresolved, hit count = 0" % self.line]) self.expect("breakpoint command list 1", "Breakpoint 1 command ok", Index: packages/Python/lldbsuite/test/functionalities/breakpoint/serialize/TestBreakpointSerialization.py =================================================================== --- packages/Python/lldbsuite/test/functionalities/breakpoint/serialize/TestBreakpointSerialization.py +++ packages/Python/lldbsuite/test/functionalities/breakpoint/serialize/TestBreakpointSerialization.py @@ -180,7 +180,8 @@ # actually have locations. source_bps = lldb.SBBreakpointList(self.orig_target) - bkpt = self.orig_target.BreakpointCreateByLocation("blubby.c", 666) + bkpt = self.orig_target.BreakpointCreateByLocation( + lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()) bkpt.SetEnabled(False) bkpt.SetOneShot(True) bkpt.SetThreadID(10) @@ -226,7 +227,8 @@ all_bps = lldb.SBBreakpointList(self.orig_target) source_bps = lldb.SBBreakpointList(self.orig_target) - bkpt = self.orig_target.BreakpointCreateByLocation("blubby.c", 666) + bkpt = self.orig_target.BreakpointCreateByLocation( + lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()) bkpt.SetEnabled(False) bkpt.SetOneShot(True) bkpt.SetThreadID(10) @@ -260,7 +262,8 @@ self.check_equivalence(all_bps) def do_check_names(self): - bkpt = self.orig_target.BreakpointCreateByLocation("blubby.c", 666) + bkpt = self.orig_target.BreakpointCreateByLocation( + lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList()) good_bkpt_name = "GoodBreakpoint" write_bps = lldb.SBBreakpointList(self.orig_target) bkpt.AddName(good_bkpt_name) Index: packages/Python/lldbsuite/test/lldbutil.py =================================================================== --- packages/Python/lldbsuite/test/lldbutil.py +++ packages/Python/lldbsuite/test/lldbutil.py @@ -511,7 +511,7 @@ patterns = [ r"^Breakpoint (?P[0-9]+): (?P[0-9]+) locations\.$", r"^Breakpoint (?P[0-9]+): (?Pno) locations \(pending\)\.", - r"^Breakpoint (?P[0-9]+): where = (?P.*)`(?P[+\-]{0,1}[^+]+)( \+ (?P[0-9]+)){0,1}( \[inlined\] (?P.*)){0,1} at (?P[^:]+):(?P[0-9]+), address = (?P
0x[0-9a-fA-F]+)$", + r"^Breakpoint (?P[0-9]+): where = (?P.*)`(?P[+\-]{0,1}[^+]+)( \+ (?P[0-9]+)){0,1}( \[inlined\] (?P.*)){0,1} at (?P[^:]+):(?P[0-9]+)(?P(:[0-9]+)?), address = (?P
0x[0-9a-fA-F]+)$", r"^Breakpoint (?P[0-9]+): where = (?P.*)`(?P.*)( \+ (?P[0-9]+)){0,1}, address = (?P
0x[0-9a-fA-F]+)$"] match_object = test.match(command, patterns) break_results = match_object.groupdict() Index: scripts/interface/SBTarget.i =================================================================== --- scripts/interface/SBTarget.i +++ scripts/interface/SBTarget.i @@ -623,6 +623,11 @@ BreakpointCreateByLocation (const lldb::SBFileSpec &file_spec, uint32_t line, lldb::addr_t offset, SBFileSpecList &module_list); + lldb::SBBreakpoint + BreakpointCreateByLocation (const lldb::SBFileSpec &file_spec, uint32_t line, + uint32_t column, lldb::addr_t offset, + SBFileSpecList &module_list); + lldb::SBBreakpoint BreakpointCreateByName (const char *symbol_name, const char *module_name = NULL); Index: source/API/SBTarget.cpp =================================================================== --- source/API/SBTarget.cpp +++ source/API/SBTarget.cpp @@ -693,6 +693,13 @@ SBTarget::BreakpointCreateByLocation(const SBFileSpec &sb_file_spec, uint32_t line, lldb::addr_t offset, SBFileSpecList &sb_module_list) { + return BreakpointCreateByLocation(sb_file_spec, line, 0, offset, + sb_module_list); +} + +SBBreakpoint SBTarget::BreakpointCreateByLocation( + const SBFileSpec &sb_file_spec, uint32_t line, uint32_t column, + lldb::addr_t offset, SBFileSpecList &sb_module_list) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_API)); SBBreakpoint sb_bp; @@ -710,8 +717,8 @@ module_list = sb_module_list.get(); } sb_bp = target_sp->CreateBreakpoint( - module_list, *sb_file_spec, line, offset, check_inlines, skip_prologue, - internal, hardware, move_to_nearest_code); + module_list, *sb_file_spec, line, column, offset, check_inlines, + skip_prologue, internal, hardware, move_to_nearest_code); } if (log) { Index: source/Breakpoint/BreakpointResolver.cpp =================================================================== --- source/Breakpoint/BreakpointResolver.cpp +++ source/Breakpoint/BreakpointResolver.cpp @@ -44,9 +44,9 @@ const char *BreakpointResolver::g_option_names[static_cast( BreakpointResolver::OptionNames::LastOptionName)] = { - "AddressOffset", "Exact", "FileName", "Inlines", "Language", - "LineNumber", "ModuleName", "NameMask", "Offset", "Regex", - "SectionName", "SkipPrologue", "SymbolNames"}; + "AddressOffset", "Exact", "FileName", "Inlines", "Language", + "LineNumber", "Column", "ModuleName", "NameMask", "Offset", + "Regex", "SectionName", "SkipPrologue", "SymbolNames"}; const char *BreakpointResolver::ResolverTyToName(enum ResolverTy type) { if (type > LastKnownResolverType) @@ -176,18 +176,37 @@ filter.Search(*this); } +namespace { +struct SourceLoc { + uint32_t line = UINT32_MAX; + uint32_t column; + SourceLoc(uint32_t l, uint32_t c) : line(l), column(c ? c : UINT32_MAX) {} + SourceLoc(const SymbolContext &sc) + : line(sc.line_entry.line), + column(sc.line_entry.column ? sc.line_entry.column : UINT32_MAX) {} +}; + +bool operator<(const SourceLoc a, const SourceLoc b) { + if (a.line < b.line) + return true; + if (a.line > b.line) + return false; + uint32_t a_col = a.column ? a.column : UINT32_MAX; + uint32_t b_col = b.column ? b.column : UINT32_MAX; + return a_col < b_col; +} +} // namespace + void BreakpointResolver::SetSCMatchesByLine(SearchFilter &filter, SymbolContextList &sc_list, bool skip_prologue, - llvm::StringRef log_ident) { + llvm::StringRef log_ident, + uint32_t line, uint32_t column) { llvm::SmallVector all_scs; for (uint32_t i = 0; i < sc_list.GetSize(); ++i) all_scs.push_back(sc_list[i]); while (all_scs.size()) { - // ResolveSymbolContext will always return a number that is >= the - // line number you pass in. So the smaller line number is always - // better. uint32_t closest_line = UINT32_MAX; // Move all the elements with a matching file spec to the end. @@ -202,12 +221,40 @@ } return true; }); - - // Within, remove all entries with a larger line number to the end. - auto worklist_end = std::remove_if( - worklist_begin, all_scs.end(), [&](const SymbolContext &sc) { - return closest_line != sc.line_entry.line; - }); + // (worklist_begin, worklist_end) now contains all entries for one filespec. + auto worklist_end = all_scs.end(); + + if (column) { + // If a column was requested, do a more precise match and only + // return the first location that comes after or at the + // requested location. + SourceLoc requested(line, column); + // First, filter out all entries left of the requested column. + worklist_end = std::remove_if( + worklist_begin, worklist_end, + [&](const SymbolContext &sc) { return SourceLoc(sc) < requested; }); + // Sort the remaining entries by (line, column). + std::sort(worklist_begin, worklist_end, + [](const SymbolContext &a, const SymbolContext &b) { + return SourceLoc(a) < SourceLoc(b); + }); + + // Filter out all locations with a source location after the closest match. + if (worklist_begin != worklist_end) + worklist_end = std::remove_if( + worklist_begin, worklist_end, [&](const SymbolContext &sc) { + return SourceLoc(*worklist_begin) < SourceLoc(sc); + }); + } else { + // Remove all entries with a larger line number. + // ResolveSymbolContext will always return a number that is >= + // the line number you pass in. So the smaller line number is + // always better. + worklist_end = std::remove_if(worklist_begin, worklist_end, + [&](const SymbolContext &sc) { + return closest_line != sc.line_entry.line; + }); + } // Sort by file address. std::sort(worklist_begin, worklist_end, @@ -262,18 +309,16 @@ // If the line number is before the prologue end, move it there... bool skipped_prologue = false; - if (skip_prologue) { - if (sc.function) { - Address prologue_addr(sc.function->GetAddressRange().GetBaseAddress()); - if (prologue_addr.IsValid() && (line_start == prologue_addr)) { - const uint32_t prologue_byte_size = sc.function->GetPrologueByteSize(); - if (prologue_byte_size) { - prologue_addr.Slide(prologue_byte_size); - - if (filter.AddressPasses(prologue_addr)) { - skipped_prologue = true; - line_start = prologue_addr; - } + if (skip_prologue && sc.function) { + Address prologue_addr(sc.function->GetAddressRange().GetBaseAddress()); + if (prologue_addr.IsValid() && (line_start == prologue_addr)) { + const uint32_t prologue_byte_size = sc.function->GetPrologueByteSize(); + if (prologue_byte_size) { + prologue_addr.Slide(prologue_byte_size); + + if (filter.AddressPasses(prologue_addr)) { + skipped_prologue = true; + line_start = prologue_addr; } } } Index: source/Breakpoint/BreakpointResolverFileLine.cpp =================================================================== --- source/Breakpoint/BreakpointResolverFileLine.cpp +++ source/Breakpoint/BreakpointResolverFileLine.cpp @@ -28,11 +28,12 @@ //---------------------------------------------------------------------- BreakpointResolverFileLine::BreakpointResolverFileLine( Breakpoint *bkpt, const FileSpec &file_spec, uint32_t line_no, - lldb::addr_t offset, bool check_inlines, bool skip_prologue, - bool exact_match) + uint32_t column, lldb::addr_t offset, bool check_inlines, + bool skip_prologue, bool exact_match) : BreakpointResolver(bkpt, BreakpointResolver::FileLineResolver, offset), - m_file_spec(file_spec), m_line_number(line_no), m_inlines(check_inlines), - m_skip_prologue(skip_prologue), m_exact_match(exact_match) {} + m_file_spec(file_spec), m_line_number(line_no), m_column(column), + m_inlines(check_inlines), m_skip_prologue(skip_prologue), + m_exact_match(exact_match) {} BreakpointResolverFileLine::~BreakpointResolverFileLine() {} @@ -41,6 +42,7 @@ Status &error) { llvm::StringRef filename; uint32_t line_no; + uint32_t column; bool check_inlines; bool skip_prologue; bool exact_match; @@ -62,6 +64,13 @@ return nullptr; } + success = + options_dict.GetValueForKeyAsInteger(GetKey(OptionNames::Column), column); + if (!success) { + error.SetErrorString("BRFL::CFSD: Couldn't find column number entry."); + return nullptr; + } + success = options_dict.GetValueForKeyAsBoolean(GetKey(OptionNames::Inlines), check_inlines); if (!success) { @@ -85,8 +94,8 @@ FileSpec file_spec(filename, false); - return new BreakpointResolverFileLine(bkpt, file_spec, line_no, offset, - check_inlines, skip_prologue, + return new BreakpointResolverFileLine(bkpt, file_spec, line_no, column, + offset, check_inlines, skip_prologue, exact_match); } @@ -99,6 +108,8 @@ m_file_spec.GetPath()); options_dict_sp->AddIntegerItem(GetKey(OptionNames::LineNumber), m_line_number); + options_dict_sp->AddIntegerItem(GetKey(OptionNames::Column), + m_column); options_dict_sp->AddBooleanItem(GetKey(OptionNames::Inlines), m_inlines); options_dict_sp->AddBooleanItem(GetKey(OptionNames::SkipPrologue), m_skip_prologue); @@ -240,7 +251,8 @@ s.Printf("for %s:%d ", m_file_spec.GetFilename().AsCString(""), m_line_number); - SetSCMatchesByLine(filter, sc_list, m_skip_prologue, s.GetString()); + SetSCMatchesByLine(filter, sc_list, m_skip_prologue, s.GetString(), + m_line_number, m_column); return Searcher::eCallbackReturnContinue; } @@ -250,8 +262,11 @@ } void BreakpointResolverFileLine::GetDescription(Stream *s) { - s->Printf("file = '%s', line = %u, exact_match = %d", - m_file_spec.GetPath().c_str(), m_line_number, m_exact_match); + s->Printf("file = '%s', line = %u, ", m_file_spec.GetPath().c_str(), + m_line_number); + if (m_column) + s->Printf("column = %u, ", m_column); + s->Printf("exact_match = %d", m_exact_match); } void BreakpointResolverFileLine::Dump(Stream *s) const {} @@ -259,7 +274,7 @@ lldb::BreakpointResolverSP BreakpointResolverFileLine::CopyForBreakpoint(Breakpoint &breakpoint) { lldb::BreakpointResolverSP ret_sp(new BreakpointResolverFileLine( - &breakpoint, m_file_spec, m_line_number, m_offset, m_inlines, + &breakpoint, m_file_spec, m_line_number, m_column, m_offset, m_inlines, m_skip_prologue, m_exact_match)); return ret_sp; Index: source/Commands/CommandObjectBreakpoint.cpp =================================================================== --- source/Commands/CommandObjectBreakpoint.cpp +++ source/Commands/CommandObjectBreakpoint.cpp @@ -696,7 +696,8 @@ bp_sp = target->CreateBreakpoint(&(m_options.m_modules), file, - m_options.m_line_num, + m_options.m_line_num, + m_options.m_column, m_options.m_offset_addr, check_inlines, m_options.m_skip_prologue, Index: source/Core/IOHandler.cpp =================================================================== --- source/Core/IOHandler.cpp +++ source/Core/IOHandler.cpp @@ -4340,6 +4340,7 @@ m_file_sp->GetFileSpec(), // Source file m_selected_line + 1, // Source line number (m_selected_line is zero based) + 0, // Unspecified column. 0, // No offset eLazyBoolCalculate, // Check inlines using global setting eLazyBoolCalculate, // Skip prologue using global setting, @@ -4379,6 +4380,7 @@ m_file_sp->GetFileSpec(), // Source file m_selected_line + 1, // Source line number (m_selected_line is zero based) + 0, // No column specified. 0, // No offset eLazyBoolCalculate, // Check inlines using global setting eLazyBoolCalculate, // Skip prologue using global setting, Index: source/Symbol/LineEntry.cpp =================================================================== --- source/Symbol/LineEntry.cpp +++ source/Symbol/LineEntry.cpp @@ -50,7 +50,6 @@ } bool LineEntry::DumpStopContext(Stream *s, bool show_fullpaths) const { - bool result = false; if (file) { if (show_fullpaths) file.Dump(s); @@ -59,14 +58,15 @@ if (line) s->PutChar(':'); - result = true; } - if (line) + if (line) { s->Printf("%u", line); - else - result = false; - - return result; + if (column) { + s->PutChar(':'); + s->Printf("%u", column); + } + } + return file || line; } bool LineEntry::Dump(Stream *s, Target *target, bool show_file, Index: source/Target/Target.cpp =================================================================== --- source/Target/Target.cpp +++ source/Target/Target.cpp @@ -322,7 +322,7 @@ BreakpointSP Target::CreateBreakpoint(const FileSpecList *containingModules, const FileSpec &file, uint32_t line_no, - lldb::addr_t offset, + uint32_t column, lldb::addr_t offset, LazyBool check_inlines, LazyBool skip_prologue, bool internal, bool hardware, @@ -366,8 +366,8 @@ move_to_nearest_code = GetMoveToNearestCode() ? eLazyBoolYes : eLazyBoolNo; BreakpointResolverSP resolver_sp(new BreakpointResolverFileLine( - nullptr, remapped_file, line_no, offset, check_inlines, skip_prologue, - !static_cast(move_to_nearest_code))); + nullptr, remapped_file, line_no, column, offset, check_inlines, + skip_prologue, !static_cast(move_to_nearest_code))); return CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); }