Index: include/lldb/API/SBSourceManager.h =================================================================== --- include/lldb/API/SBSourceManager.h +++ include/lldb/API/SBSourceManager.h @@ -30,6 +30,11 @@ const lldb::SBFileSpec &file, uint32_t line, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, lldb::SBStream &s); + size_t DisplaySourceLinesWithLineNumbersAndColumn( + const lldb::SBFileSpec &file, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, lldb::SBStream &s); + protected: friend class SBCommandInterpreter; friend class SBDebugger; Index: include/lldb/Core/Debugger.h =================================================================== --- include/lldb/Core/Debugger.h +++ include/lldb/Core/Debugger.h @@ -238,6 +238,8 @@ bool SetUseColor(bool use_color); + bool GetStopShowColumn() const; + uint32_t GetStopSourceLineCount(bool before) const; StopDisassemblyType GetStopDisassemblyDisplay() const; Index: include/lldb/Core/Disassembler.h =================================================================== --- include/lldb/Core/Disassembler.h +++ include/lldb/Core/Disassembler.h @@ -398,15 +398,16 @@ struct SourceLine { FileSpec file; uint32_t line; + uint32_t column; - SourceLine() : file(), line(LLDB_INVALID_LINE_NUMBER) {} + SourceLine() : file(), line(LLDB_INVALID_LINE_NUMBER), column(0) {} bool operator==(const SourceLine &rhs) const { - return file == rhs.file && line == rhs.line; + return file == rhs.file && line == rhs.line && rhs.column == column; } bool operator!=(const SourceLine &rhs) const { - return file != rhs.file || line != rhs.line; + return file != rhs.file || line != rhs.line || column != rhs.column; } bool IsValid() const { return line != LLDB_INVALID_LINE_NUMBER; } @@ -456,6 +457,7 @@ SourceLine sl; sl.file = line.file; sl.line = line.line; + sl.column = line.column; return ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, sl); }; Index: include/lldb/Core/SourceManager.h =================================================================== --- include/lldb/Core/SourceManager.h +++ include/lldb/Core/SourceManager.h @@ -31,13 +31,15 @@ const SourceManager::File &rhs); public: - File(const FileSpec &file_spec, Target *target); + File(const FileSpec &file_spec, Target *target, + lldb::DebuggerSP debugger_sp); ~File(); void UpdateIfNeeded(); - size_t DisplaySourceLines(uint32_t line, uint32_t context_before, - uint32_t context_after, Stream *s); + size_t DisplaySourceLines(uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + Stream *s); void FindLinesMatchingRegex(RegularExpression ®ex, uint32_t start_line, uint32_t end_line, std::vector &match_lines); @@ -76,6 +78,7 @@ lldb::DataBufferSP m_data_sp; typedef std::vector LineOffsets; LineOffsets m_offsets; + lldb::DebuggerWP m_debugger_wp; }; #endif // SWIG @@ -114,14 +117,16 @@ FileSP GetLastFile() { return m_last_file_sp; } - size_t DisplaySourceLinesWithLineNumbers( - const FileSpec &file, uint32_t line, uint32_t context_before, - uint32_t context_after, const char *current_line_cstr, Stream *s, - const SymbolContextList *bp_locs = nullptr); + size_t + DisplaySourceLinesWithLineNumbers(const FileSpec &file, uint32_t line, + uint32_t column, uint32_t context_before, + uint32_t context_after, + const char *current_line_cstr, Stream *s, + const SymbolContextList *bp_locs = nullptr); // This variant uses the last file we visited. size_t DisplaySourceLinesWithLineNumbersUsingLastFile( - uint32_t start_line, uint32_t count, uint32_t curr_line, + uint32_t start_line, uint32_t count, uint32_t curr_line, uint32_t column, const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs = nullptr); Index: packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- packages/Python/lldbsuite/test/lldbtest.py +++ packages/Python/lldbsuite/test/lldbtest.py @@ -228,6 +228,10 @@ "Unable to find '%s' within file %s" % (string_to_match, filename)) +def get_line(filename, line_number): + """Return the text of the line at the 1-based line number.""" + with io.open(filename, mode='r', encoding="utf-8") as f: + return f.readlines()[line_number - 1] def pointer_size(): """Return the pointer size of the host system.""" Index: packages/Python/lldbsuite/test/settings/TestSettings.py =================================================================== --- packages/Python/lldbsuite/test/settings/TestSettings.py +++ packages/Python/lldbsuite/test/settings/TestSettings.py @@ -527,6 +527,7 @@ "stop-disassembly-display", "stop-line-count-after", "stop-line-count-before", + "stop-show-column", "term-width", "thread-format", "use-external-editor", Index: packages/Python/lldbsuite/test/source-manager/TestSourceManager.py =================================================================== --- packages/Python/lldbsuite/test/source-manager/TestSourceManager.py +++ packages/Python/lldbsuite/test/source-manager/TestSourceManager.py @@ -10,7 +10,7 @@ """ from __future__ import print_function - +import re import lldb from lldbsuite.test.decorators import * @@ -18,19 +18,37 @@ from lldbsuite.test import lldbutil +def ansi_underline_surround_regex(inner_regex_text): + # return re.compile(r"\[4m%s\[0m" % inner_regex_text) + return "4.+\033\\[4m%s\033\\[0m" % inner_regex_text + + class SourceManagerTestCase(TestBase): mydir = TestBase.compute_mydir(__file__) + SOURCE_FILE = 'main.c' + + NO_DEBUG_INFO_TESTCASE = True + def setUp(self): # Call super's setUp(). TestBase.setUp(self) # Find the line number to break inside main(). - self.line = line_number('main.c', '// Set break point at this line.') - - @add_test_categories(['pyapi']) - def test_display_source_python(self): - """Test display of source using the SBSourceManager API.""" + self.line = line_number(self.SOURCE_FILE, '// Set break point at this line.') + + def get_expected_stop_column_number(self): + """Return the 1-based column number of the first non-whitespace + character in the breakpoint source line.""" + stop_line = get_line(self.SOURCE_FILE, self.line) + # The number of spaces that must be skipped to get to the first non- + # whitespace character --- where we expect the debugger breakpoint + # column to be --- is equal to the number of characters that get + # stripped off the front when we lstrip it, plus one to specify + # the character column after the initial whitespace. + return len(stop_line) - len(stop_line.lstrip()) + 1 + + def do_display_source_python_api(self, use_color, column_marker_regex): self.build() exe = os.path.join(os.getcwd(), "a.out") self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) @@ -39,24 +57,32 @@ self.assertTrue(target, VALID_TARGET) # Launch the process, and do not stop at the entry point. + args = None + envp = None process = target.LaunchSimple( - None, None, self.get_process_working_directory()) + args, envp, self.get_process_working_directory()) + self.assertIsNotNone(process) # # Exercise Python APIs to display source lines. # + # Setup whether we should use ansi escape sequences, including color + # and styles such as underline. + self.dbg.SetUseColor(use_color) + # Create the filespec for 'main.c'. filespec = lldb.SBFileSpec('main.c', False) source_mgr = self.dbg.GetSourceManager() # Use a string stream as the destination. stream = lldb.SBStream() - source_mgr.DisplaySourceLinesWithLineNumbers(filespec, - self.line, - 2, # context before - 2, # context after - "=>", # prefix for current line - stream) + column = self.get_expected_stop_column_number() + context_before = 2 + context_after = 2 + current_line_prefix = "=>" + source_mgr.DisplaySourceLinesWithLineNumbersAndColumn( + filespec, self.line, column, context_before, context_after, + current_line_prefix, stream) # 2 # 3 int main(int argc, char const *argv[]) { @@ -65,12 +91,28 @@ # 6 } self.expect(stream.GetData(), "Source code displayed correctly", exe=False, - patterns=['=> %d.*Hello world' % self.line]) + patterns=['=> %d.*Hello world' % self.line, + column_marker_regex]) # Boundary condition testings for SBStream(). LLDB should not crash! stream.Print(None) stream.RedirectToFile(None, True) + @add_test_categories(['pyapi']) + def test_display_source_python_dumb_terminal(self): + """Test display of source using the SBSourceManager API, using a + dumb terminal and thus no color support (the default).""" + use_color = False + self.do_display_source_python_api(use_color, r"\s+\^") + + @add_test_categories(['pyapi']) + def test_display_source_python_ansi_terminal(self): + """Test display of source using the SBSourceManager API, using a + dumb terminal and thus no color support (the default).""" + use_color = True + underline_regex = ansi_underline_surround_regex(r".") + self.do_display_source_python_api(use_color, underline_regex) + def test_move_and_then_display_source(self): """Test that target.source-map settings work by moving main.c to hidden/main.c.""" self.build() Index: scripts/interface/SBSourceManager.i =================================================================== --- scripts/interface/SBSourceManager.i +++ scripts/interface/SBSourceManager.i @@ -49,6 +49,13 @@ uint32_t context_after, const char* current_line_cstr, lldb::SBStream &s); + size_t + DisplaySourceLinesWithLineNumbersAndColumn (const lldb::SBFileSpec &file, + uint32_t line, uint32_t column, + uint32_t context_before, + uint32_t context_after, + const char* current_line_cstr, + lldb::SBStream &s); }; } // namespace lldb Index: source/API/SBSourceManager.cpp =================================================================== --- source/API/SBSourceManager.cpp +++ source/API/SBSourceManager.cpp @@ -37,7 +37,7 @@ } size_t DisplaySourceLinesWithLineNumbers(const lldb_private::FileSpec &file, - uint32_t line, + uint32_t line, uint32_t column, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, @@ -48,14 +48,15 @@ lldb::TargetSP target_sp(m_target_wp.lock()); if (target_sp) { return target_sp->GetSourceManager().DisplaySourceLinesWithLineNumbers( - file, line, context_before, context_after, current_line_cstr, s); + file, line, column, context_before, context_after, current_line_cstr, + s); } else { lldb::DebuggerSP debugger_sp(m_debugger_wp.lock()); if (debugger_sp) { return debugger_sp->GetSourceManager() - .DisplaySourceLinesWithLineNumbers(file, line, context_before, - context_after, current_line_cstr, - s); + .DisplaySourceLinesWithLineNumbers(file, line, column, + context_before, context_after, + current_line_cstr, s); } } return 0; @@ -96,10 +97,20 @@ size_t SBSourceManager::DisplaySourceLinesWithLineNumbers( const SBFileSpec &file, uint32_t line, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, SBStream &s) { + const uint32_t column = 0; + return DisplaySourceLinesWithLineNumbersAndColumn( + file.ref(), line, column, context_before, context_after, + current_line_cstr, s); +} + +size_t SBSourceManager::DisplaySourceLinesWithLineNumbersAndColumn( + const SBFileSpec &file, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, SBStream &s) { if (m_opaque_ap.get() == NULL) return 0; return m_opaque_ap->DisplaySourceLinesWithLineNumbers( - file.ref(), line, context_before, context_after, current_line_cstr, - s.get()); + file.ref(), line, column, context_before, context_after, + current_line_cstr, s.get()); } Index: source/Commands/CommandObjectSource.cpp =================================================================== --- source/Commands/CommandObjectSource.cpp +++ source/Commands/CommandObjectSource.cpp @@ -896,8 +896,10 @@ result.AppendMessageWithFormat("File: %s\n", start_file.GetPath().c_str()); + // We don't care about the column here. + const uint32_t column = 0; return target->GetSourceManager().DisplaySourceLinesWithLineNumbers( - start_file, line_no, 0, m_options.num_lines, "", + start_file, line_no, 0, m_options.num_lines, column, "", &result.GetOutputStream(), GetBreakpointLocations()); } else { result.AppendErrorWithFormat( @@ -1150,8 +1152,12 @@ size_t lines_to_back_up = m_options.num_lines >= 10 ? 5 : m_options.num_lines / 2; + const uint32_t column = + m_interpreter.GetDebugger().GetStopShowColumn() + ? sc.line_entry.column + : 0; target->GetSourceManager().DisplaySourceLinesWithLineNumbers( - sc.comp_unit, sc.line_entry.line, lines_to_back_up, + sc.comp_unit, sc.line_entry.line, lines_to_back_up, column, m_options.num_lines - lines_to_back_up, "->", &result.GetOutputStream(), GetBreakpointLocations()); result.SetStatus(eReturnStatusSuccessFinishResult); @@ -1187,12 +1193,14 @@ } else m_breakpoint_locations.Clear(); + const uint32_t column = 0; if (target->GetSourceManager() .DisplaySourceLinesWithLineNumbersUsingLastFile( m_options.start_line, // Line to display m_options.num_lines, // Lines after line to UINT32_MAX, // Don't mark "line" - "", // Don't mark "line" + column, + "", // Don't mark "line" &result.GetOutputStream(), GetBreakpointLocations())) { result.SetStatus(eReturnStatusSuccessFinishResult); } @@ -1269,10 +1277,10 @@ if (m_options.num_lines == 0) m_options.num_lines = 10; - + const uint32_t column = 0; target->GetSourceManager().DisplaySourceLinesWithLineNumbers( - sc.comp_unit, m_options.start_line, 0, m_options.num_lines, "", - &result.GetOutputStream(), GetBreakpointLocations()); + sc.comp_unit, m_options.start_line, 0, m_options.num_lines, + column, "", &result.GetOutputStream(), GetBreakpointLocations()); result.SetStatus(eReturnStatusSuccessFinishResult); } else { Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -173,6 +173,10 @@ {"stop-line-count-before", OptionValue::eTypeSInt64, true, 3, nullptr, nullptr, "The number of sources lines to display that come before the " "current source line when displaying a stopped context."}, + {"stop-show-column", OptionValue::eTypeBoolean, true, true, nullptr, + nullptr, "If true, LLDB will use the column information from the debug " + "info to mark the current position when displaying a stopped " + "context."}, {"term-width", OptionValue::eTypeSInt64, true, 80, nullptr, nullptr, "The maximum number of columns to use for displaying text."}, {"thread-format", OptionValue::eTypeFormatEntity, true, 0, @@ -210,6 +214,7 @@ ePropertyStopDisassemblyDisplay, ePropertyStopLineCountAfter, ePropertyStopLineCountBefore, + ePropertyStopShowColumn, ePropertyTerminalWidth, ePropertyThreadFormat, ePropertyUseExternalEditor, @@ -371,6 +376,12 @@ return ret; } +bool Debugger::GetStopShowColumn() const { + const uint32_t idx = ePropertyStopShowColumn; + return m_collection_sp->GetPropertyAtIndexAsBoolean( + nullptr, idx, g_properties[idx].default_uint_value != 0); +} + uint32_t Debugger::GetStopSourceLineCount(bool before) const { const uint32_t idx = before ? ePropertyStopLineCountBefore : ePropertyStopLineCountAfter; Index: source/Core/Disassembler.cpp =================================================================== --- source/Core/Disassembler.cpp +++ source/Core/Disassembler.cpp @@ -289,6 +289,9 @@ func_decl_file == prologue_end_line.original_file) { decl_line.file = func_decl_file; decl_line.line = func_decl_line; + // TODO do we care about column on these entries? If so, we need to + // plumb that through GetStartLineSourceInfo. + decl_line.column = 0; } } return decl_line; @@ -448,6 +451,7 @@ SourceLine this_line; this_line.file = sc.line_entry.file; this_line.line = sc.line_entry.line; + this_line.column = sc.line_entry.column; if (ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, this_line) == false) AddLineToSourceLineTables(this_line, source_lines_seen); @@ -613,7 +617,7 @@ line_highlight = "**"; } source_manager.DisplaySourceLinesWithLineNumbers( - ln.file, ln.line, 0, 0, line_highlight, &strm); + ln.file, ln.line, ln.column, 0, 0, line_highlight, &strm); } if (source_lines_to_display.print_source_context_end_eol) strm.EOL(); Index: source/Core/SourceManager.cpp =================================================================== --- source/Core/SourceManager.cpp +++ source/Core/SourceManager.cpp @@ -22,6 +22,7 @@ #include "lldb/Symbol/Function.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Target/Target.h" +#include "lldb/Utility/AnsiTerminal.h" using namespace lldb; using namespace lldb_private; @@ -71,7 +72,7 @@ // If file_sp is no good or it points to a non-existent file, reset it. if (!file_sp || !file_sp->GetFileSpec().Exists()) { - file_sp.reset(new File(file_spec, target_sp.get())); + file_sp.reset(new File(file_spec, target_sp.get(), debugger_sp)); if (debugger_sp) debugger_sp->GetSourceFileCache().AddSourceFile(file_sp); @@ -80,7 +81,7 @@ } size_t SourceManager::DisplaySourceLinesWithLineNumbersUsingLastFile( - uint32_t start_line, uint32_t count, uint32_t curr_line, + uint32_t start_line, uint32_t count, uint32_t curr_line, uint32_t column, const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs) { if (count == 0) @@ -123,7 +124,24 @@ return_value += s->Printf("%s%2.2s %-4u\t", prefix, line == curr_line ? current_line_cstr : "", line); - size_t this_line_size = m_last_file_sp->DisplaySourceLines(line, 0, 0, s); + // We only add the line position caret underneath if we don't have an + // ANSI-enabled terminal. ANSI terminals will highlight the character + // by underlining it. + size_t this_line_size = m_last_file_sp->DisplaySourceLines( + line, line == curr_line ? column : 0, 0, 0, s); + auto debugger_sp = m_debugger_wp.lock(); + const bool use_ansi_color = debugger_sp && debugger_sp->GetUseColor(); + if (!use_ansi_color && column != 0 && line == curr_line) { + // Display caret cursor. + std::string src_line; + m_last_file_sp->GetLine(line, src_line); + return_value += s->Printf(" \t"); + // Insert a space for every non-tab character in the source line. + for (int i = 0; i < column - 1 && i < src_line.length(); ++i) + return_value += s->PutChar(src_line[i] == '\t' ? '\t' : ' '); + // Now add the caret. + return_value += s->Printf("^\n"); + } if (this_line_size == 0) { m_last_line = UINT32_MAX; break; @@ -135,8 +153,9 @@ } size_t SourceManager::DisplaySourceLinesWithLineNumbers( - const FileSpec &file_spec, uint32_t line, uint32_t context_before, - uint32_t context_after, const char *current_line_cstr, Stream *s, + const FileSpec &file_spec, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs) { FileSP file_sp(GetFile(file_spec)); @@ -153,7 +172,7 @@ m_last_file_sp = file_sp; } return DisplaySourceLinesWithLineNumbersUsingLastFile( - start_line, count, line, current_line_cstr, s, bp_locs); + start_line, count, line, column, current_line_cstr, s, bp_locs); } size_t SourceManager::DisplayMoreWithLineNumbers( @@ -193,8 +212,9 @@ } else m_last_line = 1; + const uint32_t column = 0; return DisplaySourceLinesWithLineNumbersUsingLastFile( - m_last_line, m_last_count, UINT32_MAX, "", s, bp_locs); + m_last_line, m_last_count, UINT32_MAX, column, "", s, bp_locs); } return 0; } @@ -272,10 +292,12 @@ match_lines); } -SourceManager::File::File(const FileSpec &file_spec, Target *target) +SourceManager::File::File(const FileSpec &file_spec, Target *target, + lldb::DebuggerSP debugger_sp) : m_file_spec_orig(file_spec), m_file_spec(file_spec), m_mod_time(file_spec.GetModificationTime()), m_source_map_mod_id(0), - m_data_sp(), m_offsets() { + m_data_sp(), m_offsets(), + m_debugger_wp(debugger_sp) { if (!m_mod_time.IsValid()) { if (target) { m_source_map_mod_id = target->GetSourcePathMap().GetModificationID(); @@ -418,7 +440,7 @@ } } -size_t SourceManager::File::DisplaySourceLines(uint32_t line, +size_t SourceManager::File::DisplaySourceLines(uint32_t line, uint32_t column, uint32_t context_before, uint32_t context_after, Stream *s) { @@ -440,7 +462,20 @@ if (start_line_offset < end_line_offset) { size_t count = end_line_offset - start_line_offset; const uint8_t *cstr = m_data_sp->GetBytes() + start_line_offset; - bytes_written = s->Write(cstr, count); + auto debugger_sp = m_debugger_wp.lock(); + const bool use_ansi_color = debugger_sp && debugger_sp->GetUseColor(); + if (column && (column < count) && use_ansi_color) { + // Mark the current column with an underline. + std::string ctl; + bytes_written = s->Write(cstr, column - 1); + ctl = lldb_utility::ansi::FormatAnsiTerminalCodes("${ansi.underline}"); + bytes_written += s->Write(ctl.c_str(), ctl.length()); + bytes_written += s->Write(cstr + column - 1, 1); + ctl = lldb_utility::ansi::FormatAnsiTerminalCodes("${ansi.normal}"); + bytes_written += s->Write(ctl.c_str(), ctl.length()); + bytes_written += s->Write(cstr + column, count - column); + } else + bytes_written = s->Write(cstr, count); if (!is_newline_char(cstr[count - 1])) bytes_written += s->EOL(); } Index: source/Target/StackFrame.cpp =================================================================== --- source/Target/StackFrame.cpp +++ source/Target/StackFrame.cpp @@ -1875,7 +1875,8 @@ size_t num_lines = target->GetSourceManager().DisplaySourceLinesWithLineNumbers( m_sc.line_entry.file, m_sc.line_entry.line, - source_lines_before, source_lines_after, "->", &strm); + m_sc.line_entry.column, source_lines_before, + source_lines_after, "->", &strm); if (num_lines != 0) have_source = true; // TODO: Give here a one time warning if source file is missing.