I'm experimenting with changing how the mixed source & assembly
authorJason Molenda <jmolenda@apple.com>
Thu, 8 Sep 2016 05:12:41 +0000 (05:12 +0000)
committerJason Molenda <jmolenda@apple.com>
Thu, 8 Sep 2016 05:12:41 +0000 (05:12 +0000)
mode in lldb works.  I've been discussing this with Jim Ingham,
Greg Clayton, and Kate Stone for the past week or two.

Previously lldb would print three source lines (centered on the
line table entry line for the current line) followed by the assembly.
It would print the context information (module`function + offset)
before those three lines of source.

Now lldb will print up to two lines before/after the line table
entry.  It prints two '*' characters for the line table line to
make it clear what line is showing assembly.  There is one line of
whitespace before/after the source lines so the separation between
source & assembly is clearer.  I don't print the context line
(module`function + offset).  I stop printing context lines if it's
a different line table entry, or if it's a source line I've already
printed as context to another source line.  If I have two line table
entries one after another for the same source line (I get these often
with clang - with different column information in them), I only print
the source line once.

I'm also using the target.process.thread.step-avoid-regexp setting
(which keeps you from stepping into STL functions that have been inlined
into your own code) and avoid printing any source lines from functions
that match that regexp.

When lldb disassembles into a new function, it will try to find the
declaration line # for the function and print all of the source lines
between the decl and the first line table entry (usually a { curly brace)
so we have a good chance of including the arguments, at least with the
debug info emitted by clang.

Finally, the # of source lines of context to show has been separated
from whether we're doing mixed source & assembly or not.  Previously
specifying 0 lines of context would turn off mixed source & assembly.

I think there's room for improvement, and maybe some bugs I haven't
found yet, but it's in good enough shape to upstream and iterate at
this point.

I'm not sure how best to indicate which source line is the actual line
table # versus context lines.  I'm using '**' right now.  Both Kate
and Greg had the initial idea to reuse '->' (normally used to indicate
"currently executing source line") - I tried it but I wasn't thrilled,
I'm too used to the established meaning of ->.

Greg had the interesting idea of avoiding context source lines only
in two line table entries in the same source file.  So we'd print
two lines before & after a source line, and then the next line table
entry (if it was on the next source line after those two context lines)
we'd display only the following two lines -- the previous two had just
been printed.  If an inline source line was printed between these two,
though, we'd print the context lines for both of them.  It's an
interesting idea, and I want to see how it works with both -O0 and -O3
codegen where we have different amounts of inlining.

<rdar://problem/27961419>

llvm-svn: 280906

lldb/include/lldb/Core/Disassembler.h
lldb/source/Commands/CommandObjectDisassemble.cpp
lldb/source/Core/Disassembler.cpp
lldb/source/Target/StackFrame.cpp

index 55c4c04..c90e3ea 100644 (file)
@@ -22,7 +22,9 @@
 #include "lldb/Core/EmulateInstruction.h"
 #include "lldb/Core/Opcode.h"
 #include "lldb/Core/PluginInterface.h"
+#include "lldb/Host/FileSpec.h"
 #include "lldb/Interpreter/OptionValue.h"
+#include "lldb/Symbol/LineEntry.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
@@ -314,6 +316,7 @@ public:
                           const char *plugin_name, const char *flavor,
                           const ExecutionContext &exe_ctx,
                           const AddressRange &range, uint32_t num_instructions,
+                          bool mixed_source_and_assembly,
                           uint32_t num_mixed_context_lines, uint32_t options,
                           Stream &strm);
 
@@ -321,6 +324,7 @@ public:
                           const char *plugin_name, const char *flavor,
                           const ExecutionContext &exe_ctx, const Address &start,
                           uint32_t num_instructions,
+                          bool mixed_source_and_assembly,
                           uint32_t num_mixed_context_lines, uint32_t options,
                           Stream &strm);
 
@@ -328,22 +332,21 @@ public:
   Disassemble(Debugger &debugger, const ArchSpec &arch, const char *plugin_name,
               const char *flavor, const ExecutionContext &exe_ctx,
               SymbolContextList &sc_list, uint32_t num_instructions,
-              uint32_t num_mixed_context_lines, uint32_t options, Stream &strm);
+              bool mixed_source_and_assembly, uint32_t num_mixed_context_lines,
+              uint32_t options, Stream &strm);
 
-  static bool Disassemble(Debugger &debugger, const ArchSpec &arch,
-                          const char *plugin_name, const char *flavor,
-                          const ExecutionContext &exe_ctx,
-                          const ConstString &name, Module *module,
-                          uint32_t num_instructions,
-                          uint32_t num_mixed_context_lines, uint32_t options,
-                          Stream &strm);
+  static bool
+  Disassemble(Debugger &debugger, const ArchSpec &arch, const char *plugin_name,
+              const char *flavor, const ExecutionContext &exe_ctx,
+              const ConstString &name, Module *module,
+              uint32_t num_instructions, bool mixed_source_and_assembly,
+              uint32_t num_mixed_context_lines, uint32_t options, Stream &strm);
 
-  static bool Disassemble(Debugger &debugger, const ArchSpec &arch,
-                          const char *plugin_name, const char *flavor,
-                          const ExecutionContext &exe_ctx,
-                          uint32_t num_instructions,
-                          uint32_t num_mixed_context_lines, uint32_t options,
-                          Stream &strm);
+  static bool
+  Disassemble(Debugger &debugger, const ArchSpec &arch, const char *plugin_name,
+              const char *flavor, const ExecutionContext &exe_ctx,
+              uint32_t num_instructions, bool mixed_source_and_assembly,
+              uint32_t num_mixed_context_lines, uint32_t options, Stream &strm);
 
   //------------------------------------------------------------------
   // Constructors and Destructors
@@ -359,6 +362,7 @@ public:
                                 const ArchSpec &arch,
                                 const ExecutionContext &exe_ctx,
                                 uint32_t num_instructions,
+                                bool mixed_source_and_assembly,
                                 uint32_t num_mixed_context_lines,
                                 uint32_t options, Stream &strm);
 
@@ -388,6 +392,73 @@ public:
                                       const char *flavor) = 0;
 
 protected:
+  // SourceLine and SourceLinesToDisplay structures are only used in
+  // the mixed source and assembly display methods internal to this class.
+
+  struct SourceLine {
+    FileSpec file;
+    uint32_t line;
+
+    SourceLine() : file(), line(LLDB_INVALID_LINE_NUMBER) {}
+
+    bool operator==(const SourceLine &rhs) const {
+      return file == rhs.file && line == rhs.line;
+    }
+
+    bool operator!=(const SourceLine &rhs) const {
+      return file != rhs.file || line != rhs.line;
+    }
+
+    bool IsValid() const { return line != LLDB_INVALID_LINE_NUMBER; }
+  };
+
+  struct SourceLinesToDisplay {
+    std::vector<SourceLine> lines;
+
+    // index of the "current" source line, if we want to highlight that
+    // when displaying the source lines.  (as opposed to the surrounding
+    // source lines provided to give context)
+    size_t current_source_line;
+
+    // Whether to print a blank line at the end of the source lines.
+    bool print_source_context_end_eol;
+
+    SourceLinesToDisplay()
+        : lines(), current_source_line(-1), print_source_context_end_eol(true) {
+    }
+  };
+
+  // Get the function's declaration line number, hopefully a line number earlier
+  // than the opening curly brace at the start of the function body.
+  static SourceLine GetFunctionDeclLineEntry(const SymbolContext &sc);
+
+  // Add the provided SourceLine to the map of filenames-to-source-lines-seen.
+  static void AddLineToSourceLineTables(
+      SourceLine &line,
+      std::map<FileSpec, std::set<uint32_t>> &source_lines_seen);
+
+  // Given a source line, determine if we should print it when we're doing
+  // mixed source & assembly output.
+  // We're currently using the target.process.thread.step-avoid-regexp setting
+  // (which is used for stepping over inlined STL functions by default) to
+  // determine what source lines to avoid showing.
+  //
+  // Returns true if this source line should be elided (if the source line
+  // should
+  // not be displayed).
+  static bool
+  ElideMixedSourceAndDisassemblyLine(const ExecutionContext &exe_ctx,
+                                     const SymbolContext &sc, SourceLine &line);
+
+  static bool
+  ElideMixedSourceAndDisassemblyLine(const ExecutionContext &exe_ctx,
+                                     const SymbolContext &sc, LineEntry &line) {
+    SourceLine sl;
+    sl.file = line.file;
+    sl.line = line.line;
+    return ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, sl);
+  };
+
   //------------------------------------------------------------------
   // Classes that inherit from Disassembler can see and modify these
   //------------------------------------------------------------------
index 7a1291b..8dbcc3e 100644 (file)
@@ -313,7 +313,7 @@ bool CommandObjectDisassemble::DoExecute(Args &command,
   }
 
   if (m_options.show_mixed && m_options.num_lines_context == 0)
-    m_options.num_lines_context = 1;
+    m_options.num_lines_context = 2;
 
   // Always show the PC in the disassembly
   uint32_t options = Disassembler::eOptionMarkPCAddress;
@@ -336,7 +336,7 @@ bool CommandObjectDisassemble::DoExecute(Args &command,
             m_interpreter.GetDebugger(), m_options.arch, plugin_name,
             flavor_string, m_exe_ctx, name,
             nullptr, // Module *
-            m_options.num_instructions,
+            m_options.num_instructions, m_options.show_mixed,
             m_options.show_mixed ? m_options.num_lines_context : 0, options,
             result.GetOutputStream())) {
       result.SetStatus(eReturnStatusSuccessFinishResult);
@@ -499,7 +499,7 @@ bool CommandObjectDisassemble::DoExecute(Args &command,
         if (Disassembler::Disassemble(
                 m_interpreter.GetDebugger(), m_options.arch, plugin_name,
                 flavor_string, m_exe_ctx, cur_range.GetBaseAddress(),
-                m_options.num_instructions,
+                m_options.num_instructions, m_options.show_mixed,
                 m_options.show_mixed ? m_options.num_lines_context : 0, options,
                 result.GetOutputStream())) {
           result.SetStatus(eReturnStatusSuccessFinishResult);
@@ -547,6 +547,7 @@ bool CommandObjectDisassemble::DoExecute(Args &command,
         if (Disassembler::Disassemble(
                 m_interpreter.GetDebugger(), m_options.arch, plugin_name,
                 flavor_string, m_exe_ctx, cur_range, m_options.num_instructions,
+                m_options.show_mixed,
                 m_options.show_mixed ? m_options.num_lines_context : 0, options,
                 result.GetOutputStream())) {
           result.SetStatus(eReturnStatusSuccessFinishResult);
index ba60bb9..0a399cf 100644 (file)
@@ -29,6 +29,7 @@
 #include "lldb/Interpreter/OptionValue.h"
 #include "lldb/Interpreter/OptionValueArray.h"
 #include "lldb/Interpreter/OptionValueDictionary.h"
+#include "lldb/Interpreter/OptionValueRegex.h"
 #include "lldb/Interpreter/OptionValueString.h"
 #include "lldb/Interpreter/OptionValueUInt64.h"
 #include "lldb/Symbol/Function.h"
@@ -120,6 +121,7 @@ size_t Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
                                  const ExecutionContext &exe_ctx,
                                  SymbolContextList &sc_list,
                                  uint32_t num_instructions,
+                                 bool mixed_source_and_assembly,
                                  uint32_t num_mixed_context_lines,
                                  uint32_t options, Stream &strm) {
   size_t success_count = 0;
@@ -136,8 +138,8 @@ size_t Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
          sc.GetAddressRange(scope, range_idx, use_inline_block_range, range);
          ++range_idx) {
       if (Disassemble(debugger, arch, plugin_name, flavor, exe_ctx, range,
-                      num_instructions, num_mixed_context_lines, options,
-                      strm)) {
+                      num_instructions, mixed_source_and_assembly,
+                      num_mixed_context_lines, options, strm)) {
         ++success_count;
         strm.EOL();
       }
@@ -151,6 +153,7 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
                                const ExecutionContext &exe_ctx,
                                const ConstString &name, Module *module,
                                uint32_t num_instructions,
+                               bool mixed_source_and_assembly,
                                uint32_t num_mixed_context_lines,
                                uint32_t options, Stream &strm) {
   SymbolContextList sc_list;
@@ -169,8 +172,8 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
 
   if (sc_list.GetSize()) {
     return Disassemble(debugger, arch, plugin_name, flavor, exe_ctx, sc_list,
-                       num_instructions, num_mixed_context_lines, options,
-                       strm);
+                       num_instructions, mixed_source_and_assembly,
+                       num_mixed_context_lines, options, strm);
   }
   return false;
 }
@@ -221,6 +224,7 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
                                const ExecutionContext &exe_ctx,
                                const AddressRange &disasm_range,
                                uint32_t num_instructions,
+                               bool mixed_source_and_assembly,
                                uint32_t num_mixed_context_lines,
                                uint32_t options, Stream &strm) {
   if (disasm_range.GetByteSize()) {
@@ -239,8 +243,8 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
         return false;
 
       return PrintInstructions(disasm_sp.get(), debugger, arch, exe_ctx,
-                               num_instructions, num_mixed_context_lines,
-                               options, strm);
+                               num_instructions, mixed_source_and_assembly,
+                               num_mixed_context_lines, options, strm);
     }
   }
   return false;
@@ -251,6 +255,7 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
                                const ExecutionContext &exe_ctx,
                                const Address &start_address,
                                uint32_t num_instructions,
+                               bool mixed_source_and_assembly,
                                uint32_t num_mixed_context_lines,
                                uint32_t options, Stream &strm) {
   if (num_instructions > 0) {
@@ -265,17 +270,95 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
       if (bytes_disassembled == 0)
         return false;
       return PrintInstructions(disasm_sp.get(), debugger, arch, exe_ctx,
-                               num_instructions, num_mixed_context_lines,
-                               options, strm);
+                               num_instructions, mixed_source_and_assembly,
+                               num_mixed_context_lines, options, strm);
     }
   }
   return false;
 }
 
+Disassembler::SourceLine
+Disassembler::GetFunctionDeclLineEntry(const SymbolContext &sc) {
+  SourceLine decl_line;
+  if (sc.function && sc.line_entry.IsValid()) {
+    LineEntry prologue_end_line = sc.line_entry;
+    FileSpec func_decl_file;
+    uint32_t func_decl_line;
+    sc.function->GetStartLineSourceInfo(func_decl_file, func_decl_line);
+    if (func_decl_file == prologue_end_line.file ||
+        func_decl_file == prologue_end_line.original_file) {
+      decl_line.file = func_decl_file;
+      decl_line.line = func_decl_line;
+    }
+  }
+  return decl_line;
+}
+
+void Disassembler::AddLineToSourceLineTables(
+    SourceLine &line,
+    std::map<FileSpec, std::set<uint32_t>> &source_lines_seen) {
+  if (line.IsValid()) {
+    auto source_lines_seen_pos = source_lines_seen.find(line.file);
+    if (source_lines_seen_pos == source_lines_seen.end()) {
+      std::set<uint32_t> lines;
+      lines.insert(line.line);
+      source_lines_seen.emplace(line.file, lines);
+    } else {
+      source_lines_seen_pos->second.insert(line.line);
+    }
+  }
+}
+
+bool Disassembler::ElideMixedSourceAndDisassemblyLine(
+    const ExecutionContext &exe_ctx, const SymbolContext &sc,
+    SourceLine &line) {
+
+  // TODO: should we also check target.process.thread.step-avoid-libraries ?
+
+  const RegularExpression *avoid_regex = nullptr;
+
+  // Skip any line #0 entries - they are implementation details
+  if (line.line == 0)
+    return false;
+
+  ThreadSP thread_sp = exe_ctx.GetThreadSP();
+  if (thread_sp) {
+    avoid_regex = thread_sp->GetSymbolsToAvoidRegexp();
+  } else {
+    TargetSP target_sp = exe_ctx.GetTargetSP();
+    if (target_sp) {
+      Error error;
+      OptionValueSP value_sp = target_sp->GetDebugger().GetPropertyValue(
+          &exe_ctx, "target.process.thread.step-avoid-regexp", false, error);
+      if (value_sp && value_sp->GetType() == OptionValue::eTypeRegex) {
+        OptionValueRegex *re = value_sp->GetAsRegex();
+        if (re) {
+          avoid_regex = re->GetCurrentValue();
+        }
+      }
+    }
+  }
+  if (avoid_regex && sc.symbol != nullptr) {
+    const char *function_name =
+        sc.GetFunctionName(Mangled::ePreferDemangledWithoutArguments)
+            .GetCString();
+    if (function_name) {
+      RegularExpression::Match regex_match(1);
+      if (avoid_regex->Execute(function_name, &regex_match)) {
+        // skip this source line
+        return true;
+      }
+    }
+  }
+  // don't skip this source line
+  return false;
+}
+
 bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
                                      Debugger &debugger, const ArchSpec &arch,
                                      const ExecutionContext &exe_ctx,
                                      uint32_t num_instructions,
+                                     bool mixed_source_and_assembly,
                                      uint32_t num_mixed_context_lines,
                                      uint32_t options, Stream &strm) {
   // We got some things disassembled...
@@ -289,7 +372,7 @@ bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
   uint32_t offset = 0;
   SymbolContext sc;
   SymbolContext prev_sc;
-  AddressRange sc_range;
+  AddressRange current_source_line_range;
   const Address *pc_addr_ptr = nullptr;
   StackFrame *frame = exe_ctx.GetFramePtr();
 
@@ -317,6 +400,16 @@ bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
   // First pass: step through the list of instructions,
   // find how long the initial addresses strings are, insert padding
   // in the second pass so the opcodes all line up nicely.
+
+  // Also build up the source line mapping if this is mixed source & assembly
+  // mode.
+  // Calculate the source line for each assembly instruction (eliding inlined
+  // functions
+  // which the user wants to skip).
+
+  std::map<FileSpec, std::set<uint32_t>> source_lines_seen;
+  Symbol *previous_symbol = nullptr;
+
   size_t address_text_size = 0;
   for (size_t i = 0; i < num_instructions_found; ++i) {
     Instruction *inst =
@@ -325,8 +418,9 @@ bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
       const Address &addr = inst->GetAddress();
       ModuleSP module_sp(addr.GetModule());
       if (module_sp) {
-        const uint32_t resolve_mask =
-            eSymbolContextFunction | eSymbolContextSymbol;
+        const uint32_t resolve_mask = eSymbolContextFunction |
+                                      eSymbolContextSymbol |
+                                      eSymbolContextLineEntry;
         uint32_t resolved_mask =
             module_sp->ResolveSymbolContextForAddress(addr, resolve_mask, sc);
         if (resolved_mask) {
@@ -336,18 +430,46 @@ bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
           size_t cur_line = strmstr.GetSizeOfLastLine();
           if (cur_line > address_text_size)
             address_text_size = cur_line;
+
+          // Add entries to our "source_lines_seen" map+set which list which
+          // sources lines occur in this disassembly session.  We will print
+          // lines of context around a source line, but we don't want to print
+          // a source line that has a line table entry of its own - we'll leave
+          // that source line to be printed when it actually occurs in the
+          // disassembly.
+
+          if (mixed_source_and_assembly && sc.line_entry.IsValid()) {
+            if (sc.symbol != previous_symbol) {
+              SourceLine decl_line = GetFunctionDeclLineEntry(sc);
+              if (ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, decl_line) ==
+                  false)
+                AddLineToSourceLineTables(decl_line, source_lines_seen);
+            }
+            if (sc.line_entry.IsValid()) {
+              SourceLine this_line;
+              this_line.file = sc.line_entry.file;
+              this_line.line = sc.line_entry.line;
+              if (ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, this_line) ==
+                  false)
+                AddLineToSourceLineTables(this_line, source_lines_seen);
+            }
+          }
         }
         sc.Clear(false);
       }
     }
   }
 
+  previous_symbol = nullptr;
+  SourceLine previous_line;
   for (size_t i = 0; i < num_instructions_found; ++i) {
     Instruction *inst =
         disasm_ptr->GetInstructionList().GetInstructionAtIndex(i).get();
+
     if (inst) {
       const Address &addr = inst->GetAddress();
       const bool inst_is_at_pc = pc_addr_ptr && addr == *pc_addr_ptr;
+      SourceLinesToDisplay source_lines_to_display;
 
       prev_sc = sc;
 
@@ -356,26 +478,121 @@ bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
         uint32_t resolved_mask = module_sp->ResolveSymbolContextForAddress(
             addr, eSymbolContextEverything, sc);
         if (resolved_mask) {
-          if (num_mixed_context_lines) {
-            if (!sc_range.ContainsFileAddress(addr)) {
-              sc.GetAddressRange(scope, 0, use_inline_block_range, sc_range);
-
-              if (sc != prev_sc) {
-                if (offset != 0)
-                  strm.EOL();
-
-                sc.DumpStopContext(&strm, exe_ctx.GetProcessPtr(), addr, false,
-                                   true, false, false, true);
-                strm.EOL();
-
-                if (sc.comp_unit && sc.line_entry.IsValid()) {
-                  source_manager.DisplaySourceLinesWithLineNumbers(
-                      sc.line_entry.file, sc.line_entry.line,
-                      num_mixed_context_lines, num_mixed_context_lines,
-                      ((inst_is_at_pc && (options & eOptionMarkPCSourceLine))
-                           ? "->"
-                           : ""),
-                      &strm);
+          if (mixed_source_and_assembly) {
+
+            // If we've started a new function (non-inlined), print all of the
+            // source lines from the
+            // function declaration until the first line table entry - typically
+            // the opening curly brace of
+            // the function.
+            if (previous_symbol != sc.symbol) {
+              // The default disassembly format puts an extra blank line between
+              // functions - so
+              // when we're displaying the source context for a function, we
+              // don't want to add
+              // a blank line after the source context or we'll end up with two
+              // of them.
+              if (previous_symbol != nullptr)
+                source_lines_to_display.print_source_context_end_eol = false;
+
+              previous_symbol = sc.symbol;
+              if (sc.function && sc.line_entry.IsValid()) {
+                LineEntry prologue_end_line = sc.line_entry;
+                if (ElideMixedSourceAndDisassemblyLine(
+                        exe_ctx, sc, prologue_end_line) == false) {
+                  FileSpec func_decl_file;
+                  uint32_t func_decl_line;
+                  sc.function->GetStartLineSourceInfo(func_decl_file,
+                                                      func_decl_line);
+                  if (func_decl_file == prologue_end_line.file ||
+                      func_decl_file == prologue_end_line.original_file) {
+                    // Add all the lines between the function declaration
+                    // and the first non-prologue source line to the list
+                    // of lines to print.
+                    for (uint32_t lineno = func_decl_line;
+                         lineno <= prologue_end_line.line; lineno++) {
+                      SourceLine this_line;
+                      this_line.file = func_decl_file;
+                      this_line.line = lineno;
+                      source_lines_to_display.lines.push_back(this_line);
+                    }
+                    // Mark the last line as the "current" one.  Usually
+                    // this is the open curly brace.
+                    if (source_lines_to_display.lines.size() > 0)
+                      source_lines_to_display.current_source_line =
+                          source_lines_to_display.lines.size() - 1;
+                  }
+                }
+              }
+              sc.GetAddressRange(scope, 0, use_inline_block_range,
+                                 current_source_line_range);
+            }
+
+            // If we've left a previous source line's address range, print a new
+            // source line
+            if (!current_source_line_range.ContainsFileAddress(addr)) {
+              sc.GetAddressRange(scope, 0, use_inline_block_range,
+                                 current_source_line_range);
+
+              if (sc != prev_sc && sc.comp_unit && sc.line_entry.IsValid()) {
+                SourceLine this_line;
+                this_line.file = sc.line_entry.file;
+                this_line.line = sc.line_entry.line;
+
+                if (ElideMixedSourceAndDisassemblyLine(exe_ctx, sc,
+                                                       this_line) == false) {
+                  // Only print this source line if it is different from the
+                  // last source line we printed.  There may have been inlined
+                  // functions between these lines that we elided, resulting in
+                  // the same line being printed twice in a row for a contiguous
+                  // block of assembly instructions.
+                  if (this_line != previous_line) {
+
+                    std::vector<uint32_t> previous_lines;
+                    for (int i = 0;
+                         i < num_mixed_context_lines &&
+                         (this_line.line - num_mixed_context_lines) > 0;
+                         i++) {
+                      uint32_t line =
+                          this_line.line - num_mixed_context_lines + i;
+                      auto pos = source_lines_seen.find(this_line.file);
+                      if (pos != source_lines_seen.end()) {
+                        if (pos->second.count(line) == 1) {
+                          previous_lines.clear();
+                        } else {
+                          previous_lines.push_back(line);
+                        }
+                      }
+                    }
+                    for (size_t i = 0; i < previous_lines.size(); i++) {
+                      SourceLine previous_line;
+                      previous_line.file = this_line.file;
+                      previous_line.line = previous_lines[i];
+                      auto pos = source_lines_seen.find(previous_line.file);
+                      if (pos != source_lines_seen.end()) {
+                        pos->second.insert(previous_line.line);
+                      }
+                      source_lines_to_display.lines.push_back(previous_line);
+                    }
+
+                    source_lines_to_display.lines.push_back(this_line);
+                    source_lines_to_display.current_source_line =
+                        source_lines_to_display.lines.size() - 1;
+
+                    for (int i = 0; i < num_mixed_context_lines; i++) {
+                      SourceLine next_line;
+                      next_line.file = this_line.file;
+                      next_line.line = this_line.line + i + 1;
+                      auto pos = source_lines_seen.find(next_line.file);
+                      if (pos != source_lines_seen.end()) {
+                        if (pos->second.count(next_line.line) == 1)
+                          break;
+                        pos->second.insert(next_line.line);
+                      }
+                      source_lines_to_display.lines.push_back(next_line);
+                    }
+                  }
+                  previous_line = this_line;
                 }
               }
             }
@@ -385,6 +602,24 @@ bool Disassembler::PrintInstructions(Disassembler *disasm_ptr,
         }
       }
 
+      if (source_lines_to_display.lines.size() > 0) {
+        strm.EOL();
+        for (size_t idx = 0; idx < source_lines_to_display.lines.size();
+             idx++) {
+          SourceLine ln = source_lines_to_display.lines[idx];
+          const char *line_highlight = "";
+          if (inst_is_at_pc && (options & eOptionMarkPCSourceLine)) {
+            line_highlight = "->";
+          } else if (idx == source_lines_to_display.current_source_line) {
+            line_highlight = "**";
+          }
+          source_manager.DisplaySourceLinesWithLineNumbers(
+              ln.file, ln.line, 0, 0, line_highlight, &strm);
+        }
+        if (source_lines_to_display.print_source_context_end_eol)
+          strm.EOL();
+      }
+
       const bool show_bytes = (options & eOptionShowBytes) != 0;
       inst->Dump(&strm, max_opcode_byte_size, true, show_bytes, &exe_ctx, &sc,
                  &prev_sc, nullptr, address_text_size);
@@ -401,6 +636,7 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
                                const char *plugin_name, const char *flavor,
                                const ExecutionContext &exe_ctx,
                                uint32_t num_instructions,
+                               bool mixed_source_and_assembly,
                                uint32_t num_mixed_context_lines,
                                uint32_t options, Stream &strm) {
   AddressRange range;
@@ -422,7 +658,8 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
   }
 
   return Disassemble(debugger, arch, plugin_name, flavor, exe_ctx, range,
-                     num_instructions, num_mixed_context_lines, options, strm);
+                     num_instructions, mixed_source_and_assembly,
+                     num_mixed_context_lines, options, strm);
 }
 
 Instruction::Instruction(const Address &address, AddressClass addr_class)
index e491c59..66c672e 100644 (file)
@@ -229,7 +229,7 @@ const char *StackFrame::Disassemble() {
       const char *flavor = nullptr;
       Disassembler::Disassemble(target->GetDebugger(),
                                 target->GetArchitecture(), plugin_name, flavor,
-                                exe_ctx, 0, 0, 0, m_disassembly);
+                                exe_ctx, 0, false, 0, 0, m_disassembly);
     }
     if (m_disassembly.GetSize() == 0)
       return nullptr;
@@ -853,10 +853,9 @@ ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
                 } else {
                   ValueObjectSP synthetic = valobj_sp->GetSyntheticValue();
                   if (no_synth_child /* synthetic is forbidden */ ||
-                      !synthetic /* no synthetic */
-                      ||
-                      synthetic == valobj_sp) /* synthetic is the same as the
-                                                 original object */
+                      !synthetic                 /* no synthetic */
+                      || synthetic == valobj_sp) /* synthetic is the same as the
+                                                    original object */
                   {
                     valobj_sp->GetExpressionPath(var_expr_path_strm, false);
                     error.SetErrorStringWithFormat(
@@ -1906,10 +1905,11 @@ bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source,
                                  target_arch.GetMaximumOpcodeByteSize());
             const char *plugin_name = nullptr;
             const char *flavor = nullptr;
-            Disassembler::Disassemble(target->GetDebugger(), target_arch,
-                                      plugin_name, flavor, exe_ctx, pc_range,
-                                      disasm_lines, 0,
-                                      Disassembler::eOptionMarkPCAddress, strm);
+            const bool mixed_source_and_assembly = false;
+            Disassembler::Disassemble(
+                target->GetDebugger(), target_arch, plugin_name, flavor,
+                exe_ctx, pc_range, disasm_lines, mixed_source_and_assembly, 0,
+                Disassembler::eOptionMarkPCAddress, strm);
           }
         }
         break;