public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH v3] [gdb/infcmd]: Add next-expression command
@ 2023-05-24 17:52 Simon Farre
  2023-05-26  9:53 ` Eli Zaretskii
  2023-08-15 16:02 ` Guinevere Larsen
  0 siblings, 2 replies; 3+ messages in thread
From: Simon Farre @ 2023-05-24 17:52 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Farre

v3.
Fixed documentation issues requested by Eli. Though further discussion
is required to iron out the details wrt to documentation, I think the
feature is complete at this point and needs code review. It will also be usable from the
DAP-perspective, later on.

Created a new stepping FSM, that extends from the normal
FSM (for n/ni + s/si) instead of polluting the original. Because of
this, all infrun code is untouched and as such, uses previously battle
tested stepping-code, more or less. The majority of this feature is
reflected in infcmd.c

Added two versions of the command, the `next-expression` and
the `step-expression`. The distinction is this:

step-expression will step to positions __

  __foo(__get_param()).__bar([](auto v) { __dosome(v); });

thus stepping inside the lambda (a sub-frame/younger frame).

Where as `next-expression` does not:
  __foo(__get_param()).__bar([](auto v) { dosome(v); });

This reflects the skip/do not skip subroutine behavior of the other
commands.

If there's no more expressions or statements to step to on the current
line, both commands will either default to next - or in the case of
standing inside the sub-frame of the lambda (which is a younger frame
than the "full source line") both commands will step to the "next
logical source line" of the top-most frame at the line foo().bar...

Thus a next-expression at __
  foo().bar([](auto v) { __dosome(v); });

will not step into bar, which is the function that calls the lambda, but
will step until the line after foo().bar... Why? Because if you want to
step out of the lambda into bar() `next` and `step` provides these
instruction-level functionalities already.

This makes a clear distinction between "source level commands" and
"instruction level commands", (although arguably, "next" sort of
straddles the line) where the prior *requires* debug
information of various kinds. I've tested gcc-9/12.3 and clang-9/15 and both
compilers emit column meta data - though GCC is buggy and non-compliant with
the DWARF spec - see issued filed at
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902

Clang-9 up to current, emits DWARF-compliant debug info, as does the
Rust compiler.

Added debug command `set debug colinfo` which turns off column
information printed from print_frame, to be able to handle tests breaking.
There's just too many that relies on "src at line" to update them one by one.

Added `maint info line-table` to output column information. Updated tests that
for this command to also parse the column line.

Added the `next-expression.exp` test under gdb.base which tests
`next-expression` and `step-expression` in various permutations.

Test will vomit an error, giving some pointers to GDB community
what can be done to make test passing & what has been done to GCC,
if GCC ever fixes the buggy or non-compliant DWARF emission. See the
issue referenced above.

v2.

Column information is printed with the `frame` command, but
work also needs to be put in for the TUI to display what column
we're at.

MI reports column when being stopped, since it re-uses the frame
print code.

v1.
This commit adds the source-level "next-expression" command. It utilizes
column metadata emitted by the compiler (if any). Column metadata
has been defined in the DWARF spec since 2.0, I am currently unaware
of how other debug information types work with columns and as such
this patch only introduces support for binaries that has DWARF emitted.

For the other debug info types, column metadata is currently set to 0, to signal that
"we don't have column metadata"; this seems to be default behavior for line information
in other places as well so I continued that tradition.

This command will also map to the DAP request "nextRequest"
with the parameter "stmt"; however, after discussion on IRC,
I was informed that statement is not the correct term here
so I named the command to "next-expression"; though it can
step across statements on the same line.

The column information is also exposed via the Python object
Symtab_and_line. Documentation has been added for this,
as well as the command in gdb.texinfo.

Examples

Example 1:
Say we have
  foo().bar().baz() and we're currently at _foo();

next-expression 2 will land us on _baz()

NOTE ON DEFAULT BEHAVIOR:
If the user types next-expression 10 in the above example
the command will iterate over linetable_entries and find that
there is not 10 columns on this line and fall back to the "next command"
I figured this was a sane default because if a user wants to "next-expression"
they are attempting to reach at some point _on the same source line_; however
I am absolutely willing to change this if anyone has any objections to this default.
---
 gdb/NEWS                                      |   7 +
 gdb/buildsym-legacy.c                         |   4 +-
 gdb/buildsym-legacy.h                         |   2 +-
 gdb/buildsym.c                                |   3 +-
 gdb/buildsym.h                                |   4 +-
 gdb/coffread.c                                |   4 +-
 gdb/dbxread.c                                 |   6 +-
 gdb/doc/gdb.texinfo                           |  33 +-
 gdb/doc/python.texi                           |   6 +
 gdb/dwarf2/read.c                             |  32 +-
 gdb/gdbthread.h                               |   3 +
 gdb/infcmd.c                                  | 327 ++++++++++++++++--
 gdb/infrun.c                                  |   6 +-
 gdb/linespec.c                                |   4 +-
 gdb/linespec.h                                |   2 +-
 gdb/mdebugread.c                              |   2 +-
 gdb/python/py-symtab.c                        |  29 +-
 gdb/stack.c                                   |  22 ++
 gdb/symmisc.c                                 |   4 +-
 gdb/symtab.c                                  |   1 +
 gdb/symtab.h                                  |  22 ++
 gdb/testsuite/gdb.base/annota1.exp            |   4 +-
 gdb/testsuite/gdb.base/annota3.exp            |   6 +-
 gdb/testsuite/gdb.base/dlmopen.exp            |   2 +-
 .../gdb.base/empty-host-env-vars.exp          |   1 +
 gdb/testsuite/gdb.base/gcore.exp              |   1 +
 gdb/testsuite/gdb.base/maint.exp              |  11 +-
 gdb/testsuite/gdb.base/next-expression.c      | 113 ++++++
 gdb/testsuite/gdb.base/next-expression.exp    | 267 ++++++++++++++
 gdb/testsuite/gdb.cp/annota3.exp              |   2 +-
 .../dw2-out-of-range-end-of-seq.exp           |   4 +-
 gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp  |   6 +-
 gdb/testsuite/gdb.mi/mi-async.exp             |   2 +-
 .../process-dies-while-handling-bp.exp        |   2 +-
 gdb/testsuite/lib/gdb-utils.exp               |   2 +
 gdb/testsuite/lib/gdb.exp                     |   2 +-
 36 files changed, 862 insertions(+), 86 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/next-expression.c
 create mode 100644 gdb/testsuite/gdb.base/next-expression.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index b82114d80b0..1fd35ded0e2 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,13 @@
 
 *** Changes since GDB 13
 
+* New commmand "next-expression" ("ne") that steps by expression or statement
+  on a single source line if debug information is available.
+
+* New column attribute of Python type Symtab_and_line to reflect the added
+  column field on "linetable_entry".  Describes what column on the source line
+  that is being executed.  Columns are byte-level coordinates on a source line.
+
 * The AArch64 'org.gnu.gdb.aarch64.pauth' Pointer Authentication feature string
   has been deprecated in favor of the 'org.gnu.gdb.aarch64.pauth_v2' feature
   string.
diff --git a/gdb/buildsym-legacy.c b/gdb/buildsym-legacy.c
index 131d24fe9b5..5036fd2b48d 100644
--- a/gdb/buildsym-legacy.c
+++ b/gdb/buildsym-legacy.c
@@ -205,12 +205,12 @@ finish_block (struct symbol *symbol, struct pending_block *old_blocks,
 }
 
 void
-record_line (struct subfile *subfile, int line, unrelocated_addr pc)
+record_line (struct subfile *subfile, int line, int col, unrelocated_addr pc)
 {
   gdb_assert (buildsym_compunit != nullptr);
   /* Assume every line entry is a statement start, that is a good place to
      put a breakpoint for that line number.  */
-  buildsym_compunit->record_line (subfile, line, pc, LEF_IS_STMT);
+  buildsym_compunit->record_line (subfile, line, col, pc, LEF_IS_STMT);
 }
 
 /* Start a new compunit_symtab for a new source file in OBJFILE.  Called, for
diff --git a/gdb/buildsym-legacy.h b/gdb/buildsym-legacy.h
index 664d6320e54..be44d0f5783 100644
--- a/gdb/buildsym-legacy.h
+++ b/gdb/buildsym-legacy.h
@@ -76,7 +76,7 @@ extern struct context_stack *push_context (int desc, CORE_ADDR valu);
 
 extern struct context_stack pop_context ();
 
-extern void record_line (struct subfile *subfile, int line,
+extern void record_line (struct subfile *subfile, int line, int col,
 			 unrelocated_addr pc);
 
 extern struct compunit_symtab *start_compunit_symtab (struct objfile *objfile,
diff --git a/gdb/buildsym.c b/gdb/buildsym.c
index d12ad2187ab..6b3859ea45e 100644
--- a/gdb/buildsym.c
+++ b/gdb/buildsym.c
@@ -626,7 +626,7 @@ buildsym_compunit::pop_subfile ()
    line vector for SUBFILE.  */
 
 void
-buildsym_compunit::record_line (struct subfile *subfile, int line,
+buildsym_compunit::record_line (struct subfile *subfile, int line, int col,
 				unrelocated_addr pc, linetable_entry_flags flags)
 {
   m_have_line_numbers = true;
@@ -666,6 +666,7 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
 
   subfile->line_vector_entries.emplace_back ();
   linetable_entry &e = subfile->line_vector_entries.back ();
+  e.column = col;
   e.line = line;
   e.is_stmt = (flags & LEF_IS_STMT) != 0;
   e.set_raw_pc (pc);
diff --git a/gdb/buildsym.h b/gdb/buildsym.h
index 98dc8f02874..d3328c630d8 100644
--- a/gdb/buildsym.h
+++ b/gdb/buildsym.h
@@ -239,8 +239,8 @@ struct buildsym_compunit
 
   const char *pop_subfile ();
 
-  void record_line (struct subfile *subfile, int line, unrelocated_addr pc,
-		    linetable_entry_flags flags);
+  void record_line (struct subfile *subfile, int line, int col,
+  		    unrelocated_addr pc, linetable_entry_flags flags);
 
   struct compunit_symtab *get_compunit_symtab ()
   {
diff --git a/gdb/coffread.c b/gdb/coffread.c
index ff4d4ae5313..dfb75b12799 100644
--- a/gdb/coffread.c
+++ b/gdb/coffread.c
@@ -1133,7 +1133,7 @@ coff_symtab_read (minimal_symbol_reader &reader,
 		 other statement-line-number.  */
 	      if (fcn_last_line == 1)
 		record_line
-		  (get_current_subfile (), fcn_first_line,
+		  (get_current_subfile (), fcn_first_line, 0,
 		   unrelocated_addr (gdbarch_addr_bits_remove (gdbarch,
 							       fcn_first_line_addr)));
 	      else
@@ -1462,7 +1462,7 @@ enter_linenos (file_ptr file_offset, int first_line,
 	{
 	  CORE_ADDR addr = lptr.l_addr.l_paddr;
 	  record_line (get_current_subfile (),
-		       first_line + L_LNNO32 (&lptr),
+		       first_line + L_LNNO32 (&lptr), 0,
 		       unrelocated_addr (gdbarch_addr_bits_remove (gdbarch,
 								   addr)));
 	}
diff --git a/gdb/dbxread.c b/gdb/dbxread.c
index 73371edd841..310ee0eef4d 100644
--- a/gdb/dbxread.c
+++ b/gdb/dbxread.c
@@ -2476,7 +2476,7 @@ process_one_symbol (int type, int desc, CORE_ADDR valu, const char *name,
 	      CORE_ADDR addr = last_function_start + valu;
 
 	      record_line
-		(get_current_subfile (), 0,
+		(get_current_subfile (), 0, 0,
 		 unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, addr)
 				   - objfile->text_section_offset ()));
 	    }
@@ -2686,14 +2686,14 @@ process_one_symbol (int type, int desc, CORE_ADDR valu, const char *name,
 			   last_function_start : valu;
 
 	  record_line
-	    (get_current_subfile (), desc,
+	    (get_current_subfile (), desc, 0,
 	     unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, addr)
 			       - objfile->text_section_offset ()));
 	  sline_found_in_function = 1;
 	}
       else
 	record_line
-	  (get_current_subfile (), desc,
+	  (get_current_subfile (), desc, 0,
 	   unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, valu)
 			     - objfile->text_section_offset ()));
       break;
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 531147f6e6b..67884ce0a55 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -6359,6 +6359,37 @@ The @code{next} command only stops at the first instruction of a
 source line.  This prevents multiple stops that could otherwise occur in
 @code{switch} statements, @code{for} loops, etc.
 
+@kindex step-expression
+@kindex se @r{(@code{step-expression})}
+@item step-expression @r{[}@var{count}@r{]}
+Step to the next expression or statement on the current source line
+in the current (innermost) stack frame.  This is a source-level command
+and as such requires that debug information was emitted by the compiler.  If
+no such debug information could be found this command defaults to
+@code{next}.  Column meta data, describing column position of statements,
+expressions or logical breakpoint locations on a source line has been part
+of the DWARF standard for over 30 years, since DWARF2.  GCC and clang both
+emit this information.  Tested versions shown to emit DWARF column meta data
+are GCC-9 and Clang-9, though only Clang is fully compliant with the DWARF
+specification at the moment as GCC emits faulty DWARF debug information.
+
+Columns represent byte-level positions on a source code line.
+
+An argument @var{count} is a repeat count, as for @code{next}.
+If the current source line doesn't have enough expressions to satisfy
+@var{count}, this command will do the same as @code{next}.
+
+@kindex next-expression
+@kindex ne @r{(@code{next-expression})}
+@item next-expression @r{[}@var{count}@r{]}
+This is similar to @code{step-expression} but does not step into
+sub-expressions on the same source line, but steps over them.  Examples
+of sub-expressions might be a callback, like lambdas or closures.
+
+An argument @var{count} is a repeat count, as for @code{next}.
+If the current source line doesn't have enough expressions to satisfy
+@var{count}, this command will do the same as @code{next}.
+
 @kindex set step-mode
 @item set step-mode
 @cindex functions without line info, and stepping
@@ -31313,7 +31344,7 @@ An -exec-until or similar CLI command was accomplished.
 @item watchpoint-scope
 A watchpoint has gone out of scope.
 @item end-stepping-range
-An -exec-next, -exec-next-instruction, -exec-step, -exec-step-instruction or 
+An -exec-next, -exec-next-instruction, -exec-step, -exec-step-instruction or
 similar CLI command was accomplished.
 @item exited-signalled 
 The inferior exited because of a signal.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 5d714ee1ca3..980e6a97c60 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5899,6 +5899,12 @@ Indicates the current line number for this object.  This
 attribute is not writable.
 @end defvar
 
+@defvar Symtab_and_line.column
+Indicates the current column number for this object.  This
+attribute is not writeable.  A source-line column is its byte position
+on that line.
+@end defvar
+
 A @code{gdb.Symtab_and_line} object has the following methods:
 
 @defun Symtab_and_line.is_valid ()
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 4828409222c..23e18c376b2 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -18137,6 +18137,11 @@ class lnp_state_machine
     m_flags |= LEF_PROLOGUE_END;
   }
 
+  void handle_set_column (unsigned int col)
+  {
+    m_column = col;
+  }
+
 private:
   /* Advance the line by LINE_DELTA.  */
   void advance_line (int line_delta)
@@ -18161,6 +18166,7 @@ class lnp_state_machine
   /* The line table index of the current file.  */
   file_name_index m_file = 1;
   unsigned int m_line = 1;
+  unsigned int m_column = 0;
 
   /* These are initialized in the constructor.  */
 
@@ -18223,6 +18229,7 @@ lnp_state_machine::handle_special_opcode (unsigned char op_code)
   advance_line (line_delta);
   record_line (false);
   m_discriminator = 0;
+  m_column = 0;
   m_flags &= ~LEF_PROLOGUE_END;
 }
 
@@ -18311,9 +18318,8 @@ dwarf_record_line_p (struct dwarf2_cu *cu,
 
 static void
 dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
-		     unsigned int line, CORE_ADDR address,
-		     linetable_entry_flags flags,
-		     struct dwarf2_cu *cu)
+		     unsigned int line, unsigned int col, CORE_ADDR address,
+		     linetable_entry_flags flags, struct dwarf2_cu *cu)
 {
   unrelocated_addr addr
     = unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, address));
@@ -18327,7 +18333,7 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
     }
 
   if (cu != nullptr)
-    cu->get_builder ()->record_line (subfile, line, addr, flags);
+    cu->get_builder ()->record_line (subfile, line, (int) col, addr, flags);
 }
 
 /* Subroutine of dwarf_decode_lines_1 to simplify it.
@@ -18350,7 +18356,7 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
 		  paddress (gdbarch, address));
     }
 
-  dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
+  dwarf_record_line_1 (gdbarch, subfile, 0, 0, address, LEF_IS_STMT, cu);
 }
 
 void
@@ -18418,10 +18424,10 @@ lnp_state_machine::record_line (bool end_sequence)
 				   m_last_subfile))
 	    {
 	      buildsym_compunit *builder = m_cu->get_builder ();
-	      dwarf_record_line_1 (m_gdbarch,
-				   builder->get_current_subfile (),
-				   m_line, m_address, lte_flags,
-				   m_currently_recording_lines ? m_cu : nullptr);
+	      dwarf_record_line_1 (m_gdbarch, builder->get_current_subfile (),
+				   m_line, m_column, m_address, lte_flags,
+				   m_currently_recording_lines ? m_cu
+							       : nullptr);
 	    }
 	  m_last_subfile = m_cu->get_builder ()->get_current_subfile ();
 	  m_last_line = m_line;
@@ -18642,8 +18648,12 @@ dwarf_decode_lines_1 (struct line_header *lh, struct dwarf2_cu *cu,
 	      }
 	      break;
 	    case DW_LNS_set_column:
-	      (void) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
-	      line_ptr += bytes_read;
+	      {
+		const auto col
+		  = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
+		state_machine.handle_set_column (col);
+		line_ptr += bytes_read;
+	      }
 	      break;
 	    case DW_LNS_negate_stmt:
 	      state_machine.handle_negate_stmt ();
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 7135515bf45..736b178bd3d 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -171,6 +171,8 @@ struct thread_control_state
      command.  This is used to decide whether "set scheduler-locking
      step" behaves like "on" or "off".  */
   int stepping_command = 0;
+
+  bool step_expression = false;
 };
 
 /* Inferior thread specific part of `struct infcall_suspend_state'.  */
@@ -471,6 +473,7 @@ class thread_info : public refcounted_object,
   }
 
   int current_line = 0;
+  int current_column = 0;
   struct symtab *current_symtab = NULL;
 
   /* Internal stepping state.  */
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index b12b58db9cb..649671030a5 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -19,6 +19,7 @@
 
 #include "defs.h"
 #include "arch-utils.h"
+#include "gdbsupport/gdb_assert.h"
 #include "symtab.h"
 #include "gdbtypes.h"
 #include "frame.h"
@@ -61,7 +62,11 @@
 
 static void until_next_command (int);
 
-static void step_1 (int, int, const char *);
+static void
+step_1 (int, int, bool, const char *);
+
+struct step_command_fsm;
+static int prepare_one_step (thread_info *, step_command_fsm *sm);
 
 #define ERROR_NO_INFERIOR \
    if (!target_has_execution ()) error (_("The program is not being run."));
@@ -231,8 +236,7 @@ strip_bg_char (const char *args, int *bg_char_p)
 void
 post_create_inferior (int from_tty)
 {
-
-  /* Be sure we own the terminal in case write operations are performed.  */ 
+  /* Be sure we own the terminal in case write operations are performed.  */
   target_terminal::ours_for_output ();
 
   infrun_debug_show_threads ("threads in the newly created inferior",
@@ -765,7 +769,7 @@ set_step_frame (thread_info *tp)
 static void
 step_command (const char *count_string, int from_tty)
 {
-  step_1 (0, 0, count_string);
+  step_1 (0, 0, false, count_string);
 }
 
 /* Likewise, but skip over subroutine calls as if single instructions.  */
@@ -773,7 +777,7 @@ step_command (const char *count_string, int from_tty)
 static void
 next_command (const char *count_string, int from_tty)
 {
-  step_1 (1, 0, count_string);
+  step_1 (1, 0, false, count_string);
 }
 
 /* Likewise, but step only one instruction.  */
@@ -781,13 +785,13 @@ next_command (const char *count_string, int from_tty)
 static void
 stepi_command (const char *count_string, int from_tty)
 {
-  step_1 (0, 1, count_string);
+  step_1 (0, 1, false, count_string);
 }
 
 static void
 nexti_command (const char *count_string, int from_tty)
 {
-  step_1 (1, 1, count_string);
+  step_1 (1, 1, false, count_string);
 }
 
 /* Data for the FSM that manages the step/next/stepi/nexti
@@ -804,44 +808,288 @@ struct step_command_fsm : public thread_fsm
   /* If true, this is a stepi/nexti, otherwise a step/step.  */
   int single_inst;
 
-  explicit step_command_fsm (struct interp *cmd_interp)
-    : thread_fsm (cmd_interp)
+  step_command_fsm (interp *cmd_interp, int count, int skip_subroutine,
+		    int single_inst, thread_info *thread)
+    : thread_fsm (cmd_interp), count (count),
+      skip_subroutines (skip_subroutine), single_inst (single_inst)
   {
+    const auto frame = get_current_frame ();
+    if (!single_inst || skip_subroutines)
+      set_longjmp_breakpoint (thread, get_frame_id (frame));
+
+    thread->control.stepping_command = 1;
   }
 
   void clean_up (struct thread_info *thread) override;
   bool should_stop (struct thread_info *thread) override;
   enum async_reply_reason do_async_reply_reason () override;
+
+  virtual bool is_step_expression ()
+  {
+    return false;
+  }
 };
 
-/* Prepare for a step/next/etc. command.  Any target resource
-   allocated here is undone in the FSM's clean_up method.  */
+/* When a user says `next-expression` or `step-expression` we don't want to
+ * collect symtabs and linetable_entries over and over which for a large
+ * project might actual come at a pretty substantial cost. If a user enters
+ * this command the likelihood that she or he wants to enter it again (and
+ * soon) is high. as such, we cache that using next_expression_cmd_cache. */
+class next_expression_cmd_cache
+{
+public:
+  next_expression_cmd_cache () : line_number (-1), file_name (), cache () {}
+  gdb::optional<std::vector<linetable_entry> *>
+  get_cached (const symtab_and_line &sal, frame_info_ptr frame);
+  int line () const
+  {
+    return line_number;
+  }
+  frame_id frame () const
+  {
+    return starting_frame_id;
+  }
 
-static void
-step_command_fsm_prepare (struct step_command_fsm *sm,
-			  int skip_subroutines, int single_inst,
-			  int count, struct thread_info *thread)
+private:
+  frame_id starting_frame_id;
+  int line_number;
+  bool is_cached (const std::string &file_name, int line_number) const;
+  void invalidate_cache ();
+  std::string file_name;
+  std::vector<linetable_entry> cache;
+};
+
+static frame_id
+oldest_frame_at_src_and_line (const char *filename, int line,
+			      frame_info_ptr begin)
 {
-  sm->skip_subroutines = skip_subroutines;
-  sm->single_inst = single_inst;
-  sm->count = count;
+  auto same = begin;
+  auto prev = get_prev_frame (begin);
+  while (prev != nullptr)
+    {
+      const auto sal = find_frame_sal (prev);
+      if (sal.symtab->filename == filename && sal.line == line)
+	same = prev;
+      prev = get_prev_frame (prev);
+    }
+  return get_frame_id (same);
+}
+
+gdb::optional<std::vector<linetable_entry> *>
+next_expression_cmd_cache::get_cached (const symtab_and_line &sal,
+				       frame_info_ptr frame)
+{
+  if (sal.symtab == nullptr)
+    {
+      invalidate_cache ();
+      return {};
+    }
+
+  if (is_cached (sal.symtab->filename, sal.line))
+    return { &cache };
+  else
+    {
+      starting_frame_id
+	= oldest_frame_at_src_and_line (sal.symtab->filename, sal.line, frame);
+      const auto sals
+	= symtabs_from_filename (sal.symtab->filename, sal.pspace);
+      std::unordered_map<int, linetable_entry> ltes;
+
+      for (const auto &s : sals)
+	{
+	  const auto items = s->linetable ()->items ();
+	  for (const auto &entry : items)
+	    if (entry.line == sal.line && entry.column != 0)
+	      ltes[entry.column] = entry;
+	}
+
+      if (ltes.size () <= 1)
+	{
+	  invalidate_cache ();
+	  return {};
+	}
+
+      cache.clear ();
+      cache.reserve (ltes.size ());
+      for (const auto kvp : ltes)
+	cache.push_back (kvp.second);
+      line_number = sal.line;
+      file_name = sal.symtab->filename;
+      return { &cache };
+    }
+}
 
-  /* Leave the si command alone.  */
-  if (!sm->single_inst || sm->skip_subroutines)
-    set_longjmp_breakpoint (thread, get_frame_id (get_current_frame ()));
+void
+next_expression_cmd_cache::invalidate_cache ()
+{
+  file_name = "";
+  line_number = -1;
+  cache.clear ();
+}
 
-  thread->control.stepping_command = 1;
+bool
+next_expression_cmd_cache::is_cached (const std::string &file_name,
+				      int line_number) const
+{
+  return this->file_name == file_name && line_number == this->line_number;
 }
 
-static int prepare_one_step (thread_info *, struct step_command_fsm *sm);
+struct step_expr_fsm : public step_command_fsm
+{
+  step_expr_fsm (interp *cmd_interp, int count, int skip_subroutine,
+		 int single_inst, thread_info *thread,
+		 std::vector<breakpoint_up> &&bps)
+    : step_command_fsm (cmd_interp, count, skip_subroutine, single_inst,
+			thread),
+      bps (std::move (bps)), is_step (single_inst)
+  {
+  }
+
+  static std::unique_ptr<step_command_fsm> default_to_next (interp *interp,
+							    thread_info *thr);
+
+  bool should_stop (struct thread_info *thread) override;
+  void clean_up (thread_info *) override;
+
+  bool is_step_expression () override
+  {
+    return true;
+  }
+
+  static next_expression_cmd_cache *cache;
+  std::vector<breakpoint_up> bps;
+  bool is_step;
+};
+
+next_expression_cmd_cache* step_expr_fsm::cache = new next_expression_cmd_cache{};
+
+/* static */
+std::unique_ptr<step_command_fsm>
+step_expr_fsm::default_to_next (interp *interp, thread_info *ti)
+{
+  return std::make_unique<step_command_fsm> (interp, 1, 1, 0, ti);
+}
+
+void
+step_expr_fsm::clean_up (thread_info *tp)
+{
+  step_command_fsm::clean_up (tp);
+  bps.clear ();
+}
+
+bool
+step_expr_fsm::should_stop (thread_info *tp)
+{
+  auto stopped_at_bp
+    = std::find_if (bps.begin (), bps.end (), [tp] (auto &bp) {
+	return bpstat_find_breakpoint (tp->control.stop_bpstat, bp.get ())
+	       != nullptr;
+      });
+
+  if (stopped_at_bp != std::end (bps))
+    {
+      const auto frame = frame_find_by_id (cache->frame ());
+      if (not is_step)
+	{
+	  const auto current_frame = get_current_frame ();
+	  auto fid = get_frame_id (current_frame);
+	  bps.erase (stopped_at_bp);
+	  if (fid != cache->frame ())
+	    return prepare_one_step (tp, this);
+	}
+      if (--count > 0)
+	return prepare_one_step (tp, this);
+      else
+	{
+	  set_finished ();
+	  return true;
+	}
+    }
+
+  if (tp->control.stop_step)
+    {
+      const auto frame = frame_find_by_id (cache->frame ());
+
+      if (frame == nullptr)
+	{
+	  set_finished ();
+	  return true;
+	}
+
+      const auto sal = find_frame_sal (frame);
+      if (sal.line != cache->line ())
+	{
+	  set_finished ();
+	  return true;
+	}
+      return prepare_one_step (tp, this);
+    }
+  return true;
+}
+
+static std::unique_ptr<step_command_fsm>
+create_step_fsm (int count, int single_ins, int skip_subroutine,
+		 bool step_expression, thread_info *thread)
+{
+  const auto interp = command_interp ();
+
+  /* n/ni, s/si fsm created here */
+  if (!step_expression)
+    return std::make_unique<step_command_fsm> (interp, count, skip_subroutine,
+					       single_ins, thread);
+  else
+    {
+      const auto frame = get_current_frame ();
+      const auto sal = find_frame_sal (frame);
+      std::vector<breakpoint_up> bps;
+      const auto entries = step_expr_fsm::cache->get_cached (sal, frame);
+      if (entries.has_value ())
+	{
+	  const std::vector<linetable_entry> &ltes = **entries;
+	  if (!single_ins && ltes.size () < count)
+	    return step_expr_fsm::default_to_next (interp, thread);
+
+	  for (const auto &entry : ltes)
+	    {
+	      if (!single_ins && entry.column == sal.col)
+		continue;
+	      const auto column_rel_addr
+		= entry.pc (sal.pspace->symfile_object_file);
+	      const auto frame_gdbarch = get_frame_arch (frame);
+	      auto bp_loc = set_momentary_breakpoint_at_pc (
+		frame_gdbarch, column_rel_addr, bp_single_step);
+	      bps.push_back (std::move (bp_loc));
+	    }
+
+	  if (!bps.empty ())
+	    return std::make_unique<step_expr_fsm> (
+	      interp, count, true, single_ins, thread, std::move (bps));
+	}
+      return step_expr_fsm::default_to_next (interp, thread);
+    }
+  gdb_assert (false && "This should never happen");
+  return nullptr;
+}
 
 static void
-step_1 (int skip_subroutines, int single_inst, const char *count_string)
+next_expression (const char *count_str, int from_tty)
+{
+  step_1 (true, false, true, count_str);
+}
+
+static void
+step_expression (const char *count_str, int from_tty)
+{
+  step_1 (true, true, true, count_str);
+}
+
+static void
+step_1 (int skip_subroutines, int single_inst, bool step_expression,
+	const char *count_string)
 {
   int count;
   int async_exec;
   struct thread_info *thr;
-  struct step_command_fsm *step_sm;
 
   ERROR_NO_INFERIOR;
   ensure_not_tfind_mode ();
@@ -861,22 +1109,20 @@ step_1 (int skip_subroutines, int single_inst, const char *count_string)
   /* Setup the execution command state machine to handle all the COUNT
      steps.  */
   thr = inferior_thread ();
-  step_sm = new step_command_fsm (command_interp ());
-  thr->set_thread_fsm (std::unique_ptr<thread_fsm> (step_sm));
+  auto step_sm = create_step_fsm (count, single_inst, skip_subroutines,
+				  step_expression, thr);
+  auto fsm_ptr = step_sm.get ();
 
-  step_command_fsm_prepare (step_sm, skip_subroutines,
-			    single_inst, count, thr);
+  thr->set_thread_fsm (std::move (step_sm));
 
   /* Do only one step for now, before returning control to the event
      loop.  Let the continuation figure out how many other steps we
      need to do, and handle them one at the time, through
      step_once.  */
-  if (!prepare_one_step (thr, step_sm))
+  if (!prepare_one_step (thr, fsm_ptr))
     proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT);
   else
     {
-      /* Stepped into an inline frame.  Pretend that we've
-	 stopped.  */
       thr->thread_fsm ()->clean_up (thr);
       bool proceeded = normal_stop ();
       if (!proceeded)
@@ -1033,6 +1279,7 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
       if (sm->skip_subroutines)
 	tp->control.step_over_calls = STEP_OVER_ALL;
 
+      tp->control.step_expression = sm->is_step_expression();
       return 0;
     }
 
@@ -3311,6 +3558,22 @@ Argument N means step N times (or till program stops for another \
 reason)."));
   add_com_alias ("s", step_cmd, class_run, 1);
 
+  cmd_list_element *nexpr_cmd
+    = add_com ("next-expression", class_run, next_expression, _ ("\
+Step program by expressions, proceeding through subroutine calls.\n\
+Usage: next-expression [N]\n\
+This is a source-level command and if no debug information was emitted by\n\
+the compiler this command will default to behaving like 'next'"));
+  add_com_alias ("ne", nexpr_cmd, class_run, 1);
+
+  cmd_list_element *stexpr_cmd
+    = add_com ("step-expression", class_run, step_expression, _ ("\
+Step program by expressions, stepping into subroutines if they are in-line.\n\
+Usage: step-expression [N]\n\
+This is a source-level command and if no debug information was emitted by\n\
+the compiler this command will default to behaving like 'next'"));
+  add_com_alias ("se", stexpr_cmd, class_run, 1);
+
   cmd_list_element *until_cmd
     = add_com ("until", class_run, until_command, _("\
 Execute until past the current line or past a LOCATION.\n\
diff --git a/gdb/infrun.c b/gdb/infrun.c
index efe2c00c489..32000b633c7 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2887,6 +2887,7 @@ clear_proceed_status_thread (struct thread_info *tp)
   tp->control.proceed_to_finish = 0;
 
   tp->control.stepping_command = 0;
+  tp->control.step_expression = false;
 
   /* Discard any remaining commands or status from previous stop.  */
   bpstat_clear (&tp->control.stop_bpstat);
@@ -4493,6 +4494,7 @@ set_step_info (thread_info *tp, frame_info_ptr frame,
 
   tp->current_symtab = sal.symtab;
   tp->current_line = sal.line;
+  tp->current_column = sal.col;
 
   infrun_debug_printf
     ("symtab = %s, line = %d, step_frame_id = %s, step_stack_frame_id = %s",
@@ -7564,7 +7566,9 @@ process_event_stop_test (struct execution_control_state *ecs)
   bool refresh_step_info = true;
   if ((ecs->event_thread->stop_pc () == stop_pc_sal.pc)
       && (ecs->event_thread->current_line != stop_pc_sal.line
-	  || ecs->event_thread->current_symtab != stop_pc_sal.symtab))
+	  || ecs->event_thread->current_symtab != stop_pc_sal.symtab
+	  || (ecs->event_thread->control.step_expression
+	      && ecs->event_thread->current_column != stop_pc_sal.col)))
     {
       /* We are at a different line.  */
 
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..6abb885a4e5 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -362,8 +362,6 @@ static std::vector<symtab_and_line> decode_objc (struct linespec_state *self,
 						 linespec *ls,
 						 const char *arg);
 
-static std::vector<symtab *> symtabs_from_filename
-  (const char *, struct program_space *pspace);
 
 static std::vector<block_symbol> find_label_symbols
   (struct linespec_state *self,
@@ -3720,7 +3718,7 @@ collect_symtabs_from_filename (const char *file,
 /* Return all the symtabs associated to the FILENAME.  If SEARCH_PSPACE is
    not NULL, the search is restricted to just that program space.  */
 
-static std::vector<symtab *>
+std::vector<symtab *>
 symtabs_from_filename (const char *filename,
 		       struct program_space *search_pspace)
 {
diff --git a/gdb/linespec.h b/gdb/linespec.h
index d5e7334fe2d..bab4b93bf56 100644
--- a/gdb/linespec.h
+++ b/gdb/linespec.h
@@ -22,7 +22,7 @@ struct symtab;
 #include "location.h"
 
 /* Flags to pass to decode_line_1 and decode_line_full.  */
-
+std::vector<symtab *> symtabs_from_filename (const char *filename, struct program_space *search_pspace);
 enum decode_line_flags
   {
     /* Set this flag if you want the resulting SALs to describe the
diff --git a/gdb/mdebugread.c b/gdb/mdebugread.c
index 697ce0b5b1a..aaf4878de25 100644
--- a/gdb/mdebugread.c
+++ b/gdb/mdebugread.c
@@ -4012,7 +4012,7 @@ mdebug_expand_psymtab (legacy_psymtab *pst, struct objfile *objfile)
 		{
 		  /* Handle encoded stab line number.  */
 		  record_line
-		    (get_current_subfile (), sh.index,
+		    (get_current_subfile (), sh.index, 0,
 		     unrelocated_addr (gdbarch_addr_bits_remove (gdbarch,
 								 valu)));
 		}
diff --git a/gdb/python/py-symtab.c b/gdb/python/py-symtab.c
index 26aa8b2fb04..89a86cc136b 100644
--- a/gdb/python/py-symtab.c
+++ b/gdb/python/py-symtab.c
@@ -286,8 +286,12 @@ salpy_str (PyObject *self)
       filename = symtab_to_filename_for_display (symtab);
     }
 
-  return PyUnicode_FromFormat ("symbol and line for %s, line %d", filename,
-			       sal->line);
+  if (sal->col > 0)
+    return PyUnicode_FromFormat ("symbol and line:col for %s, line %d:%d",
+				 filename, sal->line, sal->col);
+  else
+    return PyUnicode_FromFormat ("symbol and line for %s, line %d", filename,
+				 sal->line);
 }
 
 static void
@@ -343,6 +347,16 @@ salpy_get_line (PyObject *self, void *closure)
   return gdb_py_object_from_longest (sal->line).release ();
 }
 
+static PyObject *
+salpy_get_col (PyObject *self, void *closure)
+{
+  struct symtab_and_line *sal = NULL;
+
+  SALPY_REQUIRE_VALID (self, sal);
+
+  return gdb_py_object_from_longest (sal->col).release ();
+}
+
 static PyObject *
 salpy_get_symtab (PyObject *self, void *closure)
 {
@@ -597,11 +611,12 @@ PyTypeObject symtab_object_type = {
 static gdb_PyGetSetDef sal_object_getset[] = {
   { "symtab", salpy_get_symtab, NULL, "Symtab object.", NULL },
   { "pc", salpy_get_pc, NULL, "Return the symtab_and_line's pc.", NULL },
-  { "last", salpy_get_last, NULL,
-    "Return the symtab_and_line's last address.", NULL },
-  { "line", salpy_get_line, NULL,
-    "Return the symtab_and_line's line.", NULL },
-  {NULL}  /* Sentinel */
+  { "last", salpy_get_last, NULL, "Return the symtab_and_line's last address.",
+    NULL },
+  { "line", salpy_get_line, NULL, "Return the symtab_and_line's line.", NULL },
+  { "column", salpy_get_col, NULL, "Return the symtab_and_line's column.",
+    NULL },
+  { NULL } /* Sentinel */
 };
 
 static PyMethodDef sal_object_methods[] = {
diff --git a/gdb/stack.c b/gdb/stack.c
index b1b25aa1c7e..bba89b02ec6 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -57,6 +57,14 @@
 #include "cli/cli-style.h"
 #include "gdbsupport/buildargv.h"
 
+bool colinfo = true;
+static void
+show_debug_col_info (struct ui_file *file, int from_tty,
+		   struct cmd_list_element *c, const char *value)
+{
+  gdb_printf (file, _("Printing column position is %s.\n"), value);
+}
+
 /* The possible choices of "set print frame-arguments", and the value
    of this setting.  */
 
@@ -1418,6 +1426,13 @@ print_frame (const frame_print_options &fp_opts,
 	uiout->text (":");
 	annotate_frame_source_line ();
 	uiout->field_signed ("line", sal.line);
+
+	/* Only print column if we have column meta data. */
+	if (colinfo && sal.col > 0)
+	  {
+	    uiout->text (":");
+	    uiout->field_signed ("column", sal.col);
+	  }
 	annotate_frame_source_end ();
       }
 
@@ -3559,6 +3574,13 @@ source line."),
 				&setlist, &showlist);
   disassemble_next_line = AUTO_BOOLEAN_FALSE;
 
+  add_setshow_boolean_cmd
+    ("colinfo", class_maintenance, &colinfo,
+     _("Set printing of column."),
+     _("Show printing of column setting."),
+     _("When non-zero, column information will be printed if it exists"),
+     nullptr, show_debug_col_info, &setdebuglist, &showdebuglist);
+
   gdb::option::add_setshow_cmds_for_options
     (class_stack, &user_frame_print_options,
      frame_print_option_defs, &setprintlist, &showprintlist);
diff --git a/gdb/symmisc.c b/gdb/symmisc.c
index ff7f31f885f..8b44f05a954 100644
--- a/gdb/symmisc.c
+++ b/gdb/symmisc.c
@@ -973,9 +973,10 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
       /* Leave space for 6 digits of index and line number.  After that the
 	 tables will just not format as well.  */
       struct ui_out *uiout = current_uiout;
-      ui_out_emit_table table_emitter (uiout, 6, -1, "line-table");
+      ui_out_emit_table table_emitter (uiout, 7, -1, "line-table");
       uiout->table_header (6, ui_left, "index", _("INDEX"));
       uiout->table_header (6, ui_left, "line", _("LINE"));
+      uiout->table_header (6, ui_left, "col", _("COL"));
       uiout->table_header (18, ui_left, "rel-address", _("REL-ADDRESS"));
       uiout->table_header (18, ui_left, "unrel-address", _("UNREL-ADDRESS"));
       uiout->table_header (7, ui_left, "is-stmt", _("IS-STMT"));
@@ -993,6 +994,7 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
 	    uiout->field_signed ("line", item->line);
 	  else
 	    uiout->field_string ("line", _("END"));
+	  uiout->field_signed("col", item->column);
 	  uiout->field_core_addr ("rel-address", objfile->arch (),
 				  item->pc (objfile));
 	  uiout->field_core_addr ("unrel-address", objfile->arch (),
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 4f28667b1b3..3265756a637 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -3247,6 +3247,7 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
       val.symtab = best_symtab;
       val.line = best->line;
       val.pc = best->pc (objfile);
+      val.col = best->column;
       if (best_end && (!alt || best_end < alt->pc (objfile)))
 	val.end = best_end;
       else if (alt)
diff --git a/gdb/symtab.h b/gdb/symtab.h
index d8e3c273f85..ce6444a5a71 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1615,6 +1615,9 @@ struct linetable_entry
   /* The line number for this entry.  */
   int line;
 
+  /* The column number for this entry. 0 means it has no column data. */
+  int column;
+
   /* True if this PC is a good location to place a breakpoint for LINE.  */
   bool is_stmt : 1;
 
@@ -1651,6 +1654,10 @@ struct linetable
      `struct hack', you can shove it up your ANSI (seriously, if the
      committee tells us how to do it, we can probably go along).  */
   struct linetable_entry item[1];
+  gdb::array_view<const linetable_entry> items () const
+  {
+    return gdb::array_view<const linetable_entry>{ item, item + nitems };
+  }
 };
 
 /* How to relocate the symbols from each section in a symbol file.
@@ -1681,6 +1688,17 @@ struct symtab
     return m_linetable;
   }
 
+  std::vector<linetable_entry> linetable_entries_on_line (int line)
+  {
+    std::vector<linetable_entry> entries;
+    for (const auto &entry : m_linetable->items ())
+      {
+	if (entry.line == line)
+	  entries.push_back (entry);
+      }
+    return entries;
+  }
+
   void set_linetable (const struct linetable *linetable)
   {
     m_linetable = linetable;
@@ -2330,6 +2348,10 @@ struct symtab_and_line
      information is not available.  */
   int line = 0;
 
+  /* Column number of this particular SAL. 0 indidcates no available column
+   * information */
+  int col = 0;
+
   CORE_ADDR pc = 0;
   CORE_ADDR end = 0;
   bool explicit_pc = false;
diff --git a/gdb/testsuite/gdb.base/annota1.exp b/gdb/testsuite/gdb.base/annota1.exp
index 90c03d0d385..ae3dca13130 100644
--- a/gdb/testsuite/gdb.base/annota1.exp
+++ b/gdb/testsuite/gdb.base/annota1.exp
@@ -37,7 +37,7 @@ if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {deb
 
 clean_restart ${binfile}
 
-# The commands we test here produce many lines of output; disable "press 
+# The commands we test here produce many lines of output; disable "press
 # <return> to continue" prompts.
 gdb_test_no_output "set height 0"
 
@@ -189,6 +189,8 @@ set run_re \
 
 set run_re [join $run_re ""]
 
+send_gdb "set debug colinfo 0\n"
+
 gdb_test_multiple "run" "run until main breakpoint" {
     -re $run_re.*$gdb_prompt$ {
 	pass $gdb_test_name
diff --git a/gdb/testsuite/gdb.base/annota3.exp b/gdb/testsuite/gdb.base/annota3.exp
index 62efd1aff66..77276f95582 100644
--- a/gdb/testsuite/gdb.base/annota3.exp
+++ b/gdb/testsuite/gdb.base/annota3.exp
@@ -36,10 +36,11 @@ if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {deb
 
 clean_restart ${binfile}
 
-# The commands we test here produce many lines of output; disable "press 
+# The commands we test here produce many lines of output; disable "press
 # <return> to continue" prompts.
 gdb_test_no_output "set height 0"
 
+send_gdb "set debug colinfo 0\n"
 #
 # break in main
 #
@@ -71,7 +72,7 @@ set gdb_prompt "\r\n\032\032pre-prompt\r\n$gdb_prompt \r\n\032\032prompt\r\n"
 # annotate-prompt
 # annotate-post-prompt (in the next block)
 #
-send_gdb "set annotate 3\n" 
+send_gdb "set annotate 3\n"
 gdb_expect_list "annotation set at level 3" "\r\n$gdb_prompt$" {
     "set annotate 3"
 }
@@ -105,6 +106,7 @@ gdb_expect_list "breakpoint info" "$gdb_prompt$" [concat {
 # run to a break point will test:
 #
 #exp_internal 1
+send_gdb "set annotate 3\n"
 send_gdb "run\n"
 gdb_expect_list "run until main breakpoint" "$gdb_prompt$" [concat {
     "\r\n\032\032post-prompt\r\n"
diff --git a/gdb/testsuite/gdb.base/dlmopen.exp b/gdb/testsuite/gdb.base/dlmopen.exp
index ddb832a64f8..6483d7a900b 100644
--- a/gdb/testsuite/gdb.base/dlmopen.exp
+++ b/gdb/testsuite/gdb.base/dlmopen.exp
@@ -106,7 +106,7 @@ proc test_dlmopen_one { ndso1 ndso2 exp_glob } {
 # The actual test.  We run it twice.
 proc test_dlmopen {} {
     global srcfile basename_lib bp_main
-
+    send_gdb "set debug colinfo 0\n"
     # Note that when loading dlmopen-lib.1.so and dlmopen-lib.2.so into
     # the same namespace, dlmopen-lib-dep.so is loaded only once, so in
     # this case, the changes to gdb_dlmopen_glob inside test_dlmopen_one
diff --git a/gdb/testsuite/gdb.base/empty-host-env-vars.exp b/gdb/testsuite/gdb.base/empty-host-env-vars.exp
index 96240311c24..0d11e0953d5 100644
--- a/gdb/testsuite/gdb.base/empty-host-env-vars.exp
+++ b/gdb/testsuite/gdb.base/empty-host-env-vars.exp
@@ -20,6 +20,7 @@ set all_env_vars { HOME XDG_CACHE_HOME LOCALAPPDATA XDG_CONFIG_HOME }
 
 # Record the initial value of the index-cache directory.
 clean_restart
+
 set index_cache_directory ""
 gdb_test_multiple "show index-cache directory" "" {
     -re -wrap "The directory of the index cache is \"(.*)\"\\." {
diff --git a/gdb/testsuite/gdb.base/gcore.exp b/gdb/testsuite/gdb.base/gcore.exp
index 1e88d5334ba..befa0f9b2e6 100644
--- a/gdb/testsuite/gdb.base/gcore.exp
+++ b/gdb/testsuite/gdb.base/gcore.exp
@@ -58,6 +58,7 @@ if {!$core_supported} {
 
 # Now restart gdb and load the corefile.
 clean_restart $binfile
+send_gdb "set debug colinfo 0\n"
 
 set core_loaded [gdb_core_cmd "$corefile" "re-load generated corefile"]
 if { $core_loaded == -1 } {
diff --git a/gdb/testsuite/gdb.base/maint.exp b/gdb/testsuite/gdb.base/maint.exp
index c05d0987e7f..134291f37c4 100644
--- a/gdb/testsuite/gdb.base/maint.exp
+++ b/gdb/testsuite/gdb.base/maint.exp
@@ -371,7 +371,7 @@ gdb_test_multiple "maint info breakpoints" "maint info breakpoints" {
 
 gdb_test "maint print" \
     "List.*unambiguous\\..*" \
-    "maint print w/o args" 
+    "maint print w/o args"
 
 gdb_test "maint info" \
     "List.*unambiguous\\..*" \
@@ -383,14 +383,15 @@ gdb_test "maint" \
 
 # Test that "main info line-table" w/o a file name shows the symtab for
 # $srcfile.
+match_max -i $gdb_spawn_id 150000
 set saw_srcfile 0
 gdb_test_multiple "maint info line-table" \
     "maint info line-table w/o a file name" {
-    -re "symtab: \[^\n\r\]+${srcfile} \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
+    -re "symtab: \[^\n\r\]+${srcfile} \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+COL\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
 	set saw_srcfile 1
 	exp_continue
     }
-    -re "symtab: \[^\n\r\]+ \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
+    -re "symtab: \[^\n\r\]+ \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+COL\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
 	# Match each symtab to avoid overflowing expect's buffer.
 	exp_continue
     }
@@ -398,7 +399,7 @@ gdb_test_multiple "maint info line-table" \
 	# For symtabs with no linetable.
 	exp_continue
     }
-    -re "^$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\[^\r\n\]*\r\n" {
+    -re "^$decimal\[ \t\]+$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\[^\r\n\]*\r\n" {
 	# Line table entries can be long too:
 	#
 	#  INDEX    LINE ADDRESS
@@ -429,6 +430,8 @@ gdb_test_multiple "maint info line-table" \
     }
 }
 
+match_max -i $gdb_spawn_id [match_max -d]
+
 gdb_test "maint info line-table ${srcfile}" \
     "symtab: \[^\n\r\]+${srcfile}.*INDEX.*LINE.*ADDRESS.*" \
     "maint info line-table with filename of current symtab"
diff --git a/gdb/testsuite/gdb.base/next-expression.c b/gdb/testsuite/gdb.base/next-expression.c
new file mode 100644
index 00000000000..bf5f19ae626
--- /dev/null
+++ b/gdb/testsuite/gdb.base/next-expression.c
@@ -0,0 +1,113 @@
+/* Copyright 2023 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+struct FooBarBaz
+{
+  int a, b, c;
+};
+
+struct Builder
+{
+  using Finalizer = int (*) (int);
+  Builder () {}
+
+  Builder &set_foo (int f)
+  {
+    foo = f;
+    return *this;
+  }
+
+  Builder &set_bar (int b)
+  {
+    bar = b;
+    return *this;
+  }
+
+  Builder &set_baz (int b)
+  {
+    baz = b;
+    return *this;
+  }
+
+  Builder &set_fin (Finalizer f)
+  {
+    fn = f;
+    return *this;
+  }
+
+  template <typename Finalizer> FooBarBaz fin (Finalizer fn)
+  {
+    const auto res = FooBarBaz{ .a = fn (foo), .b = fn (bar), .c = fn (baz) };
+    return res;
+  }
+
+  FooBarBaz fin ()
+  {
+    if (fn != nullptr)
+      return FooBarBaz{ .a = fn (foo), .b = fn (bar), .c = fn (baz) };
+    else
+      return FooBarBaz{ .a = foo, .b = bar, .c = baz };
+  }
+
+private:
+  int (*fn) (int) = nullptr;
+  int foo;
+  int bar;
+  int baz;
+};
+
+static int v = 0;
+
+// next v
+static int
+n()
+{
+  return ++v;
+}
+
+static bool is_n_even() {
+  return (v & 1) == 0;
+}
+
+int
+main (int argc, const char **argv)
+{
+  const int AAA = 10, BBB = 20, CCC = 30; /* COMMA_STATEMENTS */
+  Builder b;
+  // `step-expression` between all, from foo to into fin lambda (lambda is repeated 3 times)
+  const auto se_1 = b.set_foo(AAA).set_bar(BBB).set_baz(CCC).fin([] (auto v) { return v * 2; }); /* SE-1 */
+  // clang emits col loc for n(), gcc does not yet, hopefully they fix this bug.
+  // gcc also emits faulty col locs, but usable see issue
+  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902
+  const auto se_2 = b.set_foo(n()).set_bar(n()).set_baz(n()).fin([] (auto v) { return v * 3; }); /* SE-2 */
+  // `next-expression` set_fin lambda is not called immediately, should not step into
+  const auto se_3 = b.set_fin([](auto v) { return v * 3; }).fin(); /* SE-3 */
+  for (int i = 0; i < 10; i++)
+    {
+      i++;
+    }
+  // `next-expression` should step over and land on 107
+  if(is_n_even() && !is_n_even()) { /* TWO_COLS_VALID */
+    const int FOO = 10; const int BAR = 20; const int BAZ = 30;
+  }
+  const int FOO = 10; const int BAR = 20; const int BAZ = 30; /* STATEMENTS_LINE */
+
+  // do `next-expression` - step between expressions, but not into subroutines
+  const auto ne_1 = b.set_foo(AAA).set_bar(BBB).set_baz(CCC).fin([] (auto v) { return v * 2; }); /* NE-1 */
+  const auto ne_2 = b.set_foo(n()).set_bar(n()).set_baz(n()).fin([] (auto v) { return v * 3; }); /* NE-2 */
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/next-expression.exp b/gdb/testsuite/gdb.base/next-expression.exp
new file mode 100644
index 00000000000..8eb0a7d24e3
--- /dev/null
+++ b/gdb/testsuite/gdb.base/next-expression.exp
@@ -0,0 +1,267 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# The intent of this test is to verify that `next-expression` command
+# behaves as expected. Note that in the future this test might
+# start failing as columns are hard coded in the test; and GCC has
+# bugs in how it emits column meta data, see
+# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902
+# however, the column meta data is still useful even if not DWARF compliant
+
+set saved_gdbflags $GDBFLAGS
+
+# turn on column meta data, which is off by default during testing
+set GDBFLAGS [concat $GDBFLAGS " -ex \"set debug colinfo 1\""]
+
+set is_running 0
+
+set GCC_ISSUE_URL "https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902"
+
+standard_testfile
+set srcfile next-expression.c
+
+set TEST_ID 1
+
+# gcc = 0, clang = 1
+set compiler 0
+if {[test_compiler_info {gcc*} {c++}]} {
+    set compiler 0
+} elseif {[test_compiler_info {clang*} {c++}]} {
+    set compiler 1
+} else {
+    set compiler 100
+}
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+  return -1
+}
+
+proc test_command { cmd count line columns } {
+  global srcfile
+  global GCC_ISSUE_URL
+  global TEST_ID
+  global compiler
+  set idx $count
+  set list_len [ llength $columns ]
+  for { set i [expr $count - 1] } { $i < $list_len } { incr i $count } {
+    set col [ lindex $columns $i ]
+    send_gdb "$cmd $count\n"
+    # handle testsuite flakeyness
+    sleep 0.01
+    if [ gdb_test "frame" \
+	     ".*$srcfile:$line:$col.*" \
+	     "verify that we $cmd $count to column $col on $line (#$TEST_ID)"] {
+		if { $compiler == 0 } {
+		  puts "Test failed - GCC might have fixed bug \
+			$GCC_ISSUE_URL"
+		} else {
+		  puts "Landed at unexpected column"
+		}
+	     }
+    incr TEST_ID
+  }
+}
+
+proc test_next_expression { count line columns } {
+  test_command "next-expression" $count $line $columns
+}
+
+proc test_step_expression { count line columns } {
+  test_command "step-expression" $count $line $columns
+}
+
+proc goto_line_test_expression_command { count is_step line columns } {
+  global srcfile
+  global GCC_ISSUE_URL
+  global is_running
+  global TEST_ID
+  send_gdb "tbreak $line\n"
+  set firstcolumn [lindex $columns 0]
+  set test_num 1
+  if { $is_running == 0 } {
+    send_gdb "run\n"
+    # handle testsuite flakeyness
+    sleep 0.01
+    gdb_test "frame" \
+	   ".*$srcfile:$line:$firstcolumn.*" \
+	   "verify that we continued to column $firstcolumn on $line (#$TEST_ID)"
+    incr TEST_ID
+    set is_running 1
+  } else {
+    send_gdb "continue\n"
+    # handle testsuite flakeyness
+    sleep 0.01
+    gdb_test "frame" \
+	     ".*$srcfile:$line:$firstcolumn.*" \
+	     "verify that we continued to column $firstcolumn on $line (#$TEST_ID)"
+    incr TEST_ID
+  }
+  # "pop" the first element
+  set columns [lreplace $columns 0 0]
+  if { $is_step == 0 } {
+    test_next_expression $count $line $columns
+  } else {
+    test_step_expression $count $line $columns
+  }
+}
+
+proc goto_line_test_step { count line columns } {
+  goto_line_test_expression_command $count 1 $line $columns
+}
+
+proc goto_line_test_next { count line columns } {
+  goto_line_test_expression_command $count 0 $line $columns
+}
+
+# SE-FOO represents tests & columns using `step-expression`
+# NE-FOO represents tests & columns using `next-expression`
+
+# GCC emits correct col meta data (clang does as well)
+set comma_stmts_cols { 13 23 33 }
+set comma_stmts [gdb_get_line_number "COMMA_STATEMENTS"]
+goto_line_test_step 1 $comma_stmts $comma_stmts_cols
+
+set se1 [ gdb_get_line_number "SE-1" ]
+# gcc emit faulty (non DWARF-compliant) cols, but usable
+set se1_cols_gcc { 30 43 56 65 66 91 94 66 91 94 66 91 94 }
+# correct columns in order of execution, left here for documentation purposes
+set se1_cols_clang { 23 36 49 62 87 89 80 87 89 80 87 89 80}
+
+# the columns stepped to using next-expression
+set ne1 [ gdb_get_line_number "NE-1" ]
+set ne1_cols_gcc { 30 43 56 65 }
+set ne1_cols_clang { 23 36 49 62 }
+
+set se2 [ gdb_get_line_number "SE-2" ]
+# gcc emit faulty (non DWARF-compliant) cols, but usable
+set se2_cols_gcc { 30 43 56 65 66 91 94 }
+set se2_cols_clang { 31 23 44 36 57 49 62 89 80 }
+
+set ne2 [ gdb_get_line_number "NE-2" ]
+# gcc emit faulty (non DWARF-compliant) cols, but usable
+set ne2_cols_gcc { 30 43 56 65 }
+set ne2_cols_clang { 31 23 44 36 57 49 62 }
+
+set se3 [ gdb_get_line_number "SE-3" ]
+# gcc emit faulty (non DWARF-compliant) cols, but usable
+set se3_cols_gcc { 30 64 }
+# correct columns in order of execution, left here for documentation purposes
+set se3_cols_clang { 31 23 61 }
+
+
+
+
+set two_cols_valid [ gdb_get_line_number "TWO_COLS_VALID" ]
+# gcc emit faulty (non DWARF-compliant) cols, but usable
+set two_cols_gcc { 15 18 }
+# correct columns in order of execution, left here for documentation purposes
+set two_cols_clang { 6 18 }
+
+# test step-expression commands
+if { $compiler == 0 } {
+    goto_line_test_step 1 $se1 $se1_cols_gcc
+    goto_line_test_step 1 $se2 $se2_cols_gcc
+    goto_line_test_step 1 $se3 $se3_cols_gcc
+    goto_line_test_step 1 $two_cols_valid $two_cols_gcc
+} elseif { $compiler == 1 } {
+    goto_line_test_step 1 $se1 $se1_cols_clang
+    goto_line_test_step 1 $se2 $se2_cols_clang
+    goto_line_test_step 1 $se3 $se3_cols_clang
+    goto_line_test_step 1 $two_cols_valid $two_cols_clang
+}
+
+set stmt_line [ gdb_get_line_number "STATEMENTS_LINE" ]
+goto_line_test_step 1 $stmt_line { 13 33 53 }
+
+
+if { $compiler == 0 } {
+  goto_line_test_next 1 $ne1 $ne1_cols_gcc
+  goto_line_test_next 1 $ne2 $ne2_cols_gcc
+} elseif { $compiler == 1 } {
+  goto_line_test_next 1 $ne1 $ne1_cols_clang
+  goto_line_test_next 1 $ne2 $ne2_cols_clang
+}
+
+clean_restart ${testfile}
+set is_running 0
+send_gdb "set debug colinfo 1\n"
+
+# test step-expression commands with `3` as count
+if { $compiler == 0 } {
+    goto_line_test_step 3 $se1 $se1_cols_gcc
+    goto_line_test_step 3 $se2 $se2_cols_gcc
+    goto_line_test_step 3 $se3 $se3_cols_gcc
+    goto_line_test_step 3 $two_cols_valid $two_cols_gcc
+} elseif { $compiler == 1 } {
+    goto_line_test_step 3 $se1 $se1_cols_clang
+    goto_line_test_step 3 $se2 $se2_cols_clang
+    goto_line_test_step 3 $se3 $se3_cols_clang
+    goto_line_test_step 3 $two_cols_valid $two_cols_clang
+}
+
+if { $compiler == 0 } {
+  goto_line_test_next 4 $ne1 $ne1_cols_gcc
+  goto_line_test_next 4 $ne2 $ne2_cols_gcc
+} elseif { $compiler == 1 } {
+  goto_line_test_next 4 $ne1 $ne1_cols_clang
+  goto_line_test_next 4 $ne2 $ne2_cols_clang
+}
+
+# Test that caching works as expected
+# see comments on infcmd.c in function oldest_frame_at_src_and_line
+clean_restart ${testfile}
+set is_running 0
+send_gdb "set debug colinfo 1\n"
+
+send_gdb "tbreak fin\n"
+send_gdb "run\n"
+gdb_test "frame" \
+	 ".*Builder::fin.*$srcfile:54:.*" \
+	 "verify that landed in Builder::fin"
+send_gdb "step\n"
+# we're now inside lambda
+gdb_test "frame" \
+	 ".*$srcfile:$se1:.*" \
+	 "verify that we're inside lambda at $srcfile:$se1"
+# if cache'ing has worked properly, we should land at src.cpp:L+N
+# (which happens to be se-2), we should _not_ land back inside Builder::fin.
+# The `next` or `step` commands will do that, as they are instruction-level
+# commands. expression commands are meant to be source-level navigation.
+send_gdb "next-expression\n"
+gdb_test "frame" \
+	 ".*$srcfile:$se2:.*" \
+	 "verify that we did not step into calling frame, \
+	 but to next logical source line instead"
+
+# Repeat above test, but instead of a final `next-expression` use a `next`
+# to verify that we _do_ land in Builder::fin
+clean_restart ${testfile}
+set is_running 0
+send_gdb "set debug colinfo 1\n"
+if { $compiler == 0} {
+  goto_line_test_step 1 $se1 { 30 43 56 65 66 91 }
+  # we're inside lambda - `next` should get us to Builder::fin, which is 1 frame up
+  send_gdb "next\n"
+  gdb_test "frame" \
+	   ".*Builder::fin.*" \
+	   "verify difference between source & instruction level commands"
+} elseif { $compiler == 1 } {
+  goto_line_test_step 1 $se1 { 23 36 49 62 87 89 }
+  # we're inside lambda - `next` should get us to Builder::fin, which is 1 frame up
+  send_gdb "next\n"
+  gdb_test "frame" \
+	   ".*Builder::fin.*" \
+	   "verify difference between source & instruction level commands"
+}
diff --git a/gdb/testsuite/gdb.cp/annota3.exp b/gdb/testsuite/gdb.cp/annota3.exp
index 7b85c84cd97..2eb9faae6ea 100644
--- a/gdb/testsuite/gdb.cp/annota3.exp
+++ b/gdb/testsuite/gdb.cp/annota3.exp
@@ -65,7 +65,7 @@ send_gdb "set annotate 3\n"
 gdb_expect_list "annotation set at level 3" "\r\n$gdb_prompt$" {
     "set annotate 3"
 }
-
+send_gdb "set debug colinfo 0\n"
 send_gdb "run\n"
 gdb_expect_list "first run until main breakpoint" "$gdb_prompt$" {
     "\r\n\032\032post-prompt\r\n"
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp b/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp
index d2c28a87923..20c0bc90235 100644
--- a/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp
+++ b/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp
@@ -85,10 +85,10 @@ if ![runto_main] {
 
 set test "END with address 1 eliminated"
 gdb_test_multiple "maint info line-table $srcfile$" $test {
-    -re -wrap "END *0x0*1 *$hex *Y *\r\n.*" {
+    -re -wrap "END.* *0x0*1 *$hex *Y *\r\n.*" {
 	fail $gdb_test_name
     }
-    -re -wrap "END *$hex *$hex *Y *\r\n" {
+    -re -wrap "END.**$hex *$hex *Y *\r\n" {
 	pass $gdb_test_name
     }
 }
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp b/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp
index ee274ee128c..68be2b80fe7 100644
--- a/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp
+++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp
@@ -145,7 +145,7 @@ set prev -1
 set seq_count 0
 gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
     "count END markers in line table" {
-	-re "^$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
+	-re "^$decimal\[ \t\]+$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
 	    if { $prev != -1 } {
 		gdb_assert "$prev == 1" \
 		    "prev of normal entry at $seq_count is end marker"
@@ -154,7 +154,7 @@ gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
 	    incr seq_count
 	    exp_continue
 	}
-	-re "^$decimal\[ \t\]+END\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
+	-re "^$decimal\[ \t\]+END\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
 	    if { $prev != -1 } {
 		gdb_assert "$prev == 0" \
 		    "prev of end marker at $seq_count is normal entry"
@@ -174,7 +174,7 @@ gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
 	-re ".*linetable: \\(\\(struct linetable \\*\\) 0x0\\):\r\nNo line table.\r\n" {
 	    exp_continue
 	}
-	-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]PROLOGUE-END *\r\n" {
+	-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+COL\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]PROLOGUE-END *\r\n" {
 	    exp_continue
 	}
     }
diff --git a/gdb/testsuite/gdb.mi/mi-async.exp b/gdb/testsuite/gdb.mi/mi-async.exp
index 4bc82d5f55d..2aff0654bb7 100644
--- a/gdb/testsuite/gdb.mi/mi-async.exp
+++ b/gdb/testsuite/gdb.mi/mi-async.exp
@@ -31,7 +31,7 @@ require !use_gdb_stub
 
 # The plan is for async mode to become the default but toggle for now.
 set saved_gdbflags $GDBFLAGS
-set GDBFLAGS [concat $GDBFLAGS " -ex \"set mi-async on\""]
+set GDBFLAGS [concat $GDBFLAGS " -ex \"set mi-async on\"" " -ex \"set debug colinfo 0\""]
 
 load_lib mi-support.exp
 
diff --git a/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp b/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp
index 23f4c50f63f..603708fa6fd 100644
--- a/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp
+++ b/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp
@@ -41,7 +41,7 @@ proc do_test { non_stop cond_bp_target } {
     global linenum
 
     set saved_gdbflags $GDBFLAGS
-    set GDBFLAGS [concat $GDBFLAGS " -ex \"set non-stop $non_stop\""]
+    set GDBFLAGS [concat $GDBFLAGS " -ex \"set non-stop $non_stop\"" " -ex \"set debug colinfo 0\""]
     clean_restart $binfile
     set GDBFLAGS $saved_gdbflags
 
diff --git a/gdb/testsuite/lib/gdb-utils.exp b/gdb/testsuite/lib/gdb-utils.exp
index a010e14fc04..14e89716fd6 100644
--- a/gdb/testsuite/lib/gdb-utils.exp
+++ b/gdb/testsuite/lib/gdb-utils.exp
@@ -26,6 +26,8 @@ proc gdb_init_commands {} {
     if [target_info exists gdb_init_commands] {
 	set commands [concat $commands [target_info gdb_init_commands]]
     }
+    # Turn off column info; irrelevant for almost all tests.
+    lappend commands "set debug colinfo 0"
     return $commands
 }
 
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 133d914aff8..d45b389024b 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -9028,7 +9028,7 @@ proc is_stmt_addresses { file } {
     set is_stmt [list]
 
     gdb_test_multiple "maint info line-table $file" "" {
-	-re "\r\n$decimal\[ \t\]+$decimal\[ \t\]+($hex)\[ \t\]+$hex\[ \t\]+Y\[^\r\n\]*" {
+	-re "\r\n$decimal\[ \t\]+$decimal\[ \t\]+$decimal\[ \t\]+($hex)\[ \t\]+$hex\[ \t\]+Y\[^\r\n\]*" {
 	    lappend is_stmt $expect_out(1,string)
 	    exp_continue
 	}
-- 
2.40.1


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] [gdb/infcmd]: Add next-expression command
  2023-05-24 17:52 [PATCH v3] [gdb/infcmd]: Add next-expression command Simon Farre
@ 2023-05-26  9:53 ` Eli Zaretskii
  2023-08-15 16:02 ` Guinevere Larsen
  1 sibling, 0 replies; 3+ messages in thread
From: Eli Zaretskii @ 2023-05-26  9:53 UTC (permalink / raw)
  To: Simon Farre; +Cc: gdb-patches

> Cc: Simon Farre <simon.farre.cx@gmail.com>
> Date: Wed, 24 May 2023 19:52:46 +0200
> From: Simon Farre via Gdb-patches <gdb-patches@sourceware.org>
> 
> ---
>  gdb/NEWS                                      |   7 +
>  gdb/buildsym-legacy.c                         |   4 +-
>  gdb/buildsym-legacy.h                         |   2 +-
>  gdb/buildsym.c                                |   3 +-
>  gdb/buildsym.h                                |   4 +-
>  gdb/coffread.c                                |   4 +-
>  gdb/dbxread.c                                 |   6 +-
>  gdb/doc/gdb.texinfo                           |  33 +-
>  gdb/doc/python.texi                           |   6 +
>  gdb/dwarf2/read.c                             |  32 +-
>  gdb/gdbthread.h                               |   3 +
>  gdb/infcmd.c                                  | 327 ++++++++++++++++--
>  gdb/infrun.c                                  |   6 +-
>  gdb/linespec.c                                |   4 +-
>  gdb/linespec.h                                |   2 +-
>  gdb/mdebugread.c                              |   2 +-
>  gdb/python/py-symtab.c                        |  29 +-
>  gdb/stack.c                                   |  22 ++
>  gdb/symmisc.c                                 |   4 +-
>  gdb/symtab.c                                  |   1 +
>  gdb/symtab.h                                  |  22 ++
>  gdb/testsuite/gdb.base/annota1.exp            |   4 +-
>  gdb/testsuite/gdb.base/annota3.exp            |   6 +-
>  gdb/testsuite/gdb.base/dlmopen.exp            |   2 +-
>  .../gdb.base/empty-host-env-vars.exp          |   1 +
>  gdb/testsuite/gdb.base/gcore.exp              |   1 +
>  gdb/testsuite/gdb.base/maint.exp              |  11 +-
>  gdb/testsuite/gdb.base/next-expression.c      | 113 ++++++
>  gdb/testsuite/gdb.base/next-expression.exp    | 267 ++++++++++++++
>  gdb/testsuite/gdb.cp/annota3.exp              |   2 +-
>  .../dw2-out-of-range-end-of-seq.exp           |   4 +-
>  gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp  |   6 +-
>  gdb/testsuite/gdb.mi/mi-async.exp             |   2 +-
>  .../process-dies-while-handling-bp.exp        |   2 +-
>  gdb/testsuite/lib/gdb-utils.exp               |   2 +
>  gdb/testsuite/lib/gdb.exp                     |   2 +-
>  36 files changed, 862 insertions(+), 86 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/next-expression.c
>  create mode 100644 gdb/testsuite/gdb.base/next-expression.exp

Thanks, the documentation parts of this are OK.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] [gdb/infcmd]: Add next-expression command
  2023-05-24 17:52 [PATCH v3] [gdb/infcmd]: Add next-expression command Simon Farre
  2023-05-26  9:53 ` Eli Zaretskii
@ 2023-08-15 16:02 ` Guinevere Larsen
  1 sibling, 0 replies; 3+ messages in thread
From: Guinevere Larsen @ 2023-08-15 16:02 UTC (permalink / raw)
  To: Simon Farre, gdb-patches

On 24/05/2023 19:52, Simon Farre via Gdb-patches wrote:
> v3.
> Fixed documentation issues requested by Eli. Though further discussion
> is required to iron out the details wrt to documentation, I think the
> feature is complete at this point and needs code review. It will also be usable from the
> DAP-perspective, later on.
>
> Created a new stepping FSM, that extends from the normal
> FSM (for n/ni + s/si) instead of polluting the original. Because of
> this, all infrun code is untouched and as such, uses previously battle
> tested stepping-code, more or less. The majority of this feature is
> reflected in infcmd.c
>
> Added two versions of the command, the `next-expression` and
> the `step-expression`. The distinction is this:
>
> step-expression will step to positions __
>
>    __foo(__get_param()).__bar([](auto v) { __dosome(v); });
>
> thus stepping inside the lambda (a sub-frame/younger frame).
>
> Where as `next-expression` does not:
>    __foo(__get_param()).__bar([](auto v) { dosome(v); });
>
> This reflects the skip/do not skip subroutine behavior of the other
> commands.
>
> If there's no more expressions or statements to step to on the current
> line, both commands will either default to next - or in the case of
> standing inside the sub-frame of the lambda (which is a younger frame
> than the "full source line") both commands will step to the "next
> logical source line" of the top-most frame at the line foo().bar...
>
> Thus a next-expression at __
>    foo().bar([](auto v) { __dosome(v); });
>
> will not step into bar, which is the function that calls the lambda, but
> will step until the line after foo().bar... Why? Because if you want to
> step out of the lambda into bar() `next` and `step` provides these
> instruction-level functionalities already.
>
> This makes a clear distinction between "source level commands" and
> "instruction level commands", (although arguably, "next" sort of
> straddles the line) where the prior *requires* debug
> information of various kinds. I've tested gcc-9/12.3 and clang-9/15 and both
> compilers emit column meta data - though GCC is buggy and non-compliant with
> the DWARF spec - see issued filed at
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902
>
> Clang-9 up to current, emits DWARF-compliant debug info, as does the
> Rust compiler.
>
> Added debug command `set debug colinfo` which turns off column
> information printed from print_frame, to be able to handle tests breaking.
> There's just too many that relies on "src at line" to update them one by one.
>
> Added `maint info line-table` to output column information. Updated tests that
> for this command to also parse the column line.
>
> Added the `next-expression.exp` test under gdb.base which tests
> `next-expression` and `step-expression` in various permutations.
>
> Test will vomit an error, giving some pointers to GDB community
> what can be done to make test passing & what has been done to GCC,
> if GCC ever fixes the buggy or non-compliant DWARF emission. See the
> issue referenced above.

Hi! Thanks for working on this. I'm not too familiar with infrun, but I 
did spot a couple of style issues and many test changes that I think 
should be ironed out before this is ready to go in. They are all inlined.

-- 
Cheers,
Guinevere Larsen
She/Her/Hers

> v2.
>
> Column information is printed with the `frame` command, but
> work also needs to be put in for the TUI to display what column
> we're at.
>
> MI reports column when being stopped, since it re-uses the frame
> print code.
>
> v1.
> This commit adds the source-level "next-expression" command. It utilizes
> column metadata emitted by the compiler (if any). Column metadata
> has been defined in the DWARF spec since 2.0, I am currently unaware
> of how other debug information types work with columns and as such
> this patch only introduces support for binaries that has DWARF emitted.
>
> For the other debug info types, column metadata is currently set to 0, to signal that
> "we don't have column metadata"; this seems to be default behavior for line information
> in other places as well so I continued that tradition.
>
> This command will also map to the DAP request "nextRequest"
> with the parameter "stmt"; however, after discussion on IRC,
> I was informed that statement is not the correct term here
> so I named the command to "next-expression"; though it can
> step across statements on the same line.
>
> The column information is also exposed via the Python object
> Symtab_and_line. Documentation has been added for this,
> as well as the command in gdb.texinfo.
>
> Examples
>
> Example 1:
> Say we have
>    foo().bar().baz() and we're currently at _foo();
>
> next-expression 2 will land us on _baz()
>
> NOTE ON DEFAULT BEHAVIOR:
> If the user types next-expression 10 in the above example
> the command will iterate over linetable_entries and find that
> there is not 10 columns on this line and fall back to the "next command"
> I figured this was a sane default because if a user wants to "next-expression"
> they are attempting to reach at some point _on the same source line_; however
> I am absolutely willing to change this if anyone has any objections to this default.
> ---
>   gdb/NEWS                                      |   7 +
>   gdb/buildsym-legacy.c                         |   4 +-
>   gdb/buildsym-legacy.h                         |   2 +-
>   gdb/buildsym.c                                |   3 +-
>   gdb/buildsym.h                                |   4 +-
>   gdb/coffread.c                                |   4 +-
>   gdb/dbxread.c                                 |   6 +-
>   gdb/doc/gdb.texinfo                           |  33 +-
>   gdb/doc/python.texi                           |   6 +
>   gdb/dwarf2/read.c                             |  32 +-
>   gdb/gdbthread.h                               |   3 +
>   gdb/infcmd.c                                  | 327 ++++++++++++++++--
>   gdb/infrun.c                                  |   6 +-
>   gdb/linespec.c                                |   4 +-
>   gdb/linespec.h                                |   2 +-
>   gdb/mdebugread.c                              |   2 +-
>   gdb/python/py-symtab.c                        |  29 +-
>   gdb/stack.c                                   |  22 ++
>   gdb/symmisc.c                                 |   4 +-
>   gdb/symtab.c                                  |   1 +
>   gdb/symtab.h                                  |  22 ++
>   gdb/testsuite/gdb.base/annota1.exp            |   4 +-
>   gdb/testsuite/gdb.base/annota3.exp            |   6 +-
>   gdb/testsuite/gdb.base/dlmopen.exp            |   2 +-
>   .../gdb.base/empty-host-env-vars.exp          |   1 +
>   gdb/testsuite/gdb.base/gcore.exp              |   1 +
>   gdb/testsuite/gdb.base/maint.exp              |  11 +-
>   gdb/testsuite/gdb.base/next-expression.c      | 113 ++++++
>   gdb/testsuite/gdb.base/next-expression.exp    | 267 ++++++++++++++
>   gdb/testsuite/gdb.cp/annota3.exp              |   2 +-
>   .../dw2-out-of-range-end-of-seq.exp           |   4 +-
>   gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp  |   6 +-
>   gdb/testsuite/gdb.mi/mi-async.exp             |   2 +-
>   .../process-dies-while-handling-bp.exp        |   2 +-
>   gdb/testsuite/lib/gdb-utils.exp               |   2 +
>   gdb/testsuite/lib/gdb.exp                     |   2 +-
>   36 files changed, 862 insertions(+), 86 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.base/next-expression.c
>   create mode 100644 gdb/testsuite/gdb.base/next-expression.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index b82114d80b0..1fd35ded0e2 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,13 @@
>   
>   *** Changes since GDB 13
>   
> +* New commmand "next-expression" ("ne") that steps by expression or statement
> +  on a single source line if debug information is available.
> +
> +* New column attribute of Python type Symtab_and_line to reflect the added
> +  column field on "linetable_entry".  Describes what column on the source line
> +  that is being executed.  Columns are byte-level coordinates on a source line.
> +
>   * The AArch64 'org.gnu.gdb.aarch64.pauth' Pointer Authentication feature string
>     has been deprecated in favor of the 'org.gnu.gdb.aarch64.pauth_v2' feature
>     string.
> diff --git a/gdb/buildsym-legacy.c b/gdb/buildsym-legacy.c
> index 131d24fe9b5..5036fd2b48d 100644
> --- a/gdb/buildsym-legacy.c
> +++ b/gdb/buildsym-legacy.c
> @@ -205,12 +205,12 @@ finish_block (struct symbol *symbol, struct pending_block *old_blocks,
>   }
>   
>   void
> -record_line (struct subfile *subfile, int line, unrelocated_addr pc)
> +record_line (struct subfile *subfile, int line, int col, unrelocated_addr pc)
>   {
>     gdb_assert (buildsym_compunit != nullptr);
>     /* Assume every line entry is a statement start, that is a good place to
>        put a breakpoint for that line number.  */
> -  buildsym_compunit->record_line (subfile, line, pc, LEF_IS_STMT);
> +  buildsym_compunit->record_line (subfile, line, col, pc, LEF_IS_STMT);
>   }
>   
>   /* Start a new compunit_symtab for a new source file in OBJFILE.  Called, for
> diff --git a/gdb/buildsym-legacy.h b/gdb/buildsym-legacy.h
> index 664d6320e54..be44d0f5783 100644
> --- a/gdb/buildsym-legacy.h
> +++ b/gdb/buildsym-legacy.h
> @@ -76,7 +76,7 @@ extern struct context_stack *push_context (int desc, CORE_ADDR valu);
>   
>   extern struct context_stack pop_context ();
>   
> -extern void record_line (struct subfile *subfile, int line,
> +extern void record_line (struct subfile *subfile, int line, int col,
>   			 unrelocated_addr pc);
>   
>   extern struct compunit_symtab *start_compunit_symtab (struct objfile *objfile,
> diff --git a/gdb/buildsym.c b/gdb/buildsym.c
> index d12ad2187ab..6b3859ea45e 100644
> --- a/gdb/buildsym.c
> +++ b/gdb/buildsym.c
> @@ -626,7 +626,7 @@ buildsym_compunit::pop_subfile ()
>      line vector for SUBFILE.  */
>   
>   void
> -buildsym_compunit::record_line (struct subfile *subfile, int line,
> +buildsym_compunit::record_line (struct subfile *subfile, int line, int col,
>   				unrelocated_addr pc, linetable_entry_flags flags)
>   {
>     m_have_line_numbers = true;
> @@ -666,6 +666,7 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
>   
>     subfile->line_vector_entries.emplace_back ();
>     linetable_entry &e = subfile->line_vector_entries.back ();
> +  e.column = col;
>     e.line = line;
>     e.is_stmt = (flags & LEF_IS_STMT) != 0;
>     e.set_raw_pc (pc);
> diff --git a/gdb/buildsym.h b/gdb/buildsym.h
> index 98dc8f02874..d3328c630d8 100644
> --- a/gdb/buildsym.h
> +++ b/gdb/buildsym.h
> @@ -239,8 +239,8 @@ struct buildsym_compunit
>   
>     const char *pop_subfile ();
>   
> -  void record_line (struct subfile *subfile, int line, unrelocated_addr pc,
> -		    linetable_entry_flags flags);
> +  void record_line (struct subfile *subfile, int line, int col,
> +  		    unrelocated_addr pc, linetable_entry_flags flags);
>   
>     struct compunit_symtab *get_compunit_symtab ()
>     {
> diff --git a/gdb/coffread.c b/gdb/coffread.c
> index ff4d4ae5313..dfb75b12799 100644
> --- a/gdb/coffread.c
> +++ b/gdb/coffread.c
> @@ -1133,7 +1133,7 @@ coff_symtab_read (minimal_symbol_reader &reader,
>   		 other statement-line-number.  */
>   	      if (fcn_last_line == 1)
>   		record_line
> -		  (get_current_subfile (), fcn_first_line,
> +		  (get_current_subfile (), fcn_first_line, 0,
>   		   unrelocated_addr (gdbarch_addr_bits_remove (gdbarch,
>   							       fcn_first_line_addr)));
>   	      else
> @@ -1462,7 +1462,7 @@ enter_linenos (file_ptr file_offset, int first_line,
>   	{
>   	  CORE_ADDR addr = lptr.l_addr.l_paddr;
>   	  record_line (get_current_subfile (),
> -		       first_line + L_LNNO32 (&lptr),
> +		       first_line + L_LNNO32 (&lptr), 0,
>   		       unrelocated_addr (gdbarch_addr_bits_remove (gdbarch,
>   								   addr)));
>   	}
> diff --git a/gdb/dbxread.c b/gdb/dbxread.c
> index 73371edd841..310ee0eef4d 100644
> --- a/gdb/dbxread.c
> +++ b/gdb/dbxread.c
> @@ -2476,7 +2476,7 @@ process_one_symbol (int type, int desc, CORE_ADDR valu, const char *name,
>   	      CORE_ADDR addr = last_function_start + valu;
>   
>   	      record_line
> -		(get_current_subfile (), 0,
> +		(get_current_subfile (), 0, 0,
>   		 unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, addr)
>   				   - objfile->text_section_offset ()));
>   	    }
> @@ -2686,14 +2686,14 @@ process_one_symbol (int type, int desc, CORE_ADDR valu, const char *name,
>   			   last_function_start : valu;
>   
>   	  record_line
> -	    (get_current_subfile (), desc,
> +	    (get_current_subfile (), desc, 0,
>   	     unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, addr)
>   			       - objfile->text_section_offset ()));
>   	  sline_found_in_function = 1;
>   	}
>         else
>   	record_line
> -	  (get_current_subfile (), desc,
> +	  (get_current_subfile (), desc, 0,
>   	   unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, valu)
>   			     - objfile->text_section_offset ()));
>         break;
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 531147f6e6b..67884ce0a55 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -6359,6 +6359,37 @@ The @code{next} command only stops at the first instruction of a
>   source line.  This prevents multiple stops that could otherwise occur in
>   @code{switch} statements, @code{for} loops, etc.
>   
> +@kindex step-expression
> +@kindex se @r{(@code{step-expression})}
> +@item step-expression @r{[}@var{count}@r{]}
> +Step to the next expression or statement on the current source line
> +in the current (innermost) stack frame.  This is a source-level command
> +and as such requires that debug information was emitted by the compiler.  If
> +no such debug information could be found this command defaults to
> +@code{next}.  Column meta data, describing column position of statements,
> +expressions or logical breakpoint locations on a source line has been part
> +of the DWARF standard for over 30 years, since DWARF2.  GCC and clang both
> +emit this information.  Tested versions shown to emit DWARF column meta data
> +are GCC-9 and Clang-9, though only Clang is fully compliant with the DWARF
> +specification at the moment as GCC emits faulty DWARF debug information.
> +
> +Columns represent byte-level positions on a source code line.
> +
> +An argument @var{count} is a repeat count, as for @code{next}.
> +If the current source line doesn't have enough expressions to satisfy
> +@var{count}, this command will do the same as @code{next}.
> +
> +@kindex next-expression
> +@kindex ne @r{(@code{next-expression})}
> +@item next-expression @r{[}@var{count}@r{]}
> +This is similar to @code{step-expression} but does not step into
> +sub-expressions on the same source line, but steps over them.  Examples
> +of sub-expressions might be a callback, like lambdas or closures.
> +
> +An argument @var{count} is a repeat count, as for @code{next}.
> +If the current source line doesn't have enough expressions to satisfy
> +@var{count}, this command will do the same as @code{next}.
> +
>   @kindex set step-mode
>   @item set step-mode
>   @cindex functions without line info, and stepping
> @@ -31313,7 +31344,7 @@ An -exec-until or similar CLI command was accomplished.
>   @item watchpoint-scope
>   A watchpoint has gone out of scope.
>   @item end-stepping-range
> -An -exec-next, -exec-next-instruction, -exec-step, -exec-step-instruction or
> +An -exec-next, -exec-next-instruction, -exec-step, -exec-step-instruction or
>   similar CLI command was accomplished.
>   @item exited-signalled
>   The inferior exited because of a signal.
> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
> index 5d714ee1ca3..980e6a97c60 100644
> --- a/gdb/doc/python.texi
> +++ b/gdb/doc/python.texi
> @@ -5899,6 +5899,12 @@ Indicates the current line number for this object.  This
>   attribute is not writable.
>   @end defvar
>   
> +@defvar Symtab_and_line.column
> +Indicates the current column number for this object.  This
> +attribute is not writeable.  A source-line column is its byte position
> +on that line.
> +@end defvar
> +
>   A @code{gdb.Symtab_and_line} object has the following methods:
>   
>   @defun Symtab_and_line.is_valid ()
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 4828409222c..23e18c376b2 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -18137,6 +18137,11 @@ class lnp_state_machine
>       m_flags |= LEF_PROLOGUE_END;
>     }
>   
> +  void handle_set_column (unsigned int col)
> +  {
> +    m_column = col;
> +  }
> +
>   private:
>     /* Advance the line by LINE_DELTA.  */
>     void advance_line (int line_delta)
> @@ -18161,6 +18166,7 @@ class lnp_state_machine
>     /* The line table index of the current file.  */
>     file_name_index m_file = 1;
>     unsigned int m_line = 1;
> +  unsigned int m_column = 0;
>   
>     /* These are initialized in the constructor.  */
>   
> @@ -18223,6 +18229,7 @@ lnp_state_machine::handle_special_opcode (unsigned char op_code)
>     advance_line (line_delta);
>     record_line (false);
>     m_discriminator = 0;
> +  m_column = 0;
>     m_flags &= ~LEF_PROLOGUE_END;
>   }
>   
> @@ -18311,9 +18318,8 @@ dwarf_record_line_p (struct dwarf2_cu *cu,
>   
>   static void
>   dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
> -		     unsigned int line, CORE_ADDR address,
> -		     linetable_entry_flags flags,
> -		     struct dwarf2_cu *cu)
> +		     unsigned int line, unsigned int col, CORE_ADDR address,
> +		     linetable_entry_flags flags, struct dwarf2_cu *cu)
>   {
>     unrelocated_addr addr
>       = unrelocated_addr (gdbarch_addr_bits_remove (gdbarch, address));
> @@ -18327,7 +18333,7 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
>       }
>   
>     if (cu != nullptr)
> -    cu->get_builder ()->record_line (subfile, line, addr, flags);
> +    cu->get_builder ()->record_line (subfile, line, (int) col, addr, flags);
>   }
>   
>   /* Subroutine of dwarf_decode_lines_1 to simplify it.
> @@ -18350,7 +18356,7 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
>   		  paddress (gdbarch, address));
>       }
>   
> -  dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
> +  dwarf_record_line_1 (gdbarch, subfile, 0, 0, address, LEF_IS_STMT, cu);
>   }
>   
>   void
> @@ -18418,10 +18424,10 @@ lnp_state_machine::record_line (bool end_sequence)
>   				   m_last_subfile))
>   	    {
>   	      buildsym_compunit *builder = m_cu->get_builder ();
> -	      dwarf_record_line_1 (m_gdbarch,
> -				   builder->get_current_subfile (),
> -				   m_line, m_address, lte_flags,
> -				   m_currently_recording_lines ? m_cu : nullptr);
> +	      dwarf_record_line_1 (m_gdbarch, builder->get_current_subfile (),
> +				   m_line, m_column, m_address, lte_flags,
> +				   m_currently_recording_lines ? m_cu
> +							       : nullptr);
>   	    }
>   	  m_last_subfile = m_cu->get_builder ()->get_current_subfile ();
>   	  m_last_line = m_line;
> @@ -18642,8 +18648,12 @@ dwarf_decode_lines_1 (struct line_header *lh, struct dwarf2_cu *cu,
>   	      }
>   	      break;
>   	    case DW_LNS_set_column:
> -	      (void) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> -	      line_ptr += bytes_read;
> +	      {
> +		const auto col
> +		  = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> +		state_machine.handle_set_column (col);
> +		line_ptr += bytes_read;
> +	      }
>   	      break;
>   	    case DW_LNS_negate_stmt:
>   	      state_machine.handle_negate_stmt ();
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 7135515bf45..736b178bd3d 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -171,6 +171,8 @@ struct thread_control_state
>        command.  This is used to decide whether "set scheduler-locking
>        step" behaves like "on" or "off".  */
>     int stepping_command = 0;
> +
> +  bool step_expression = false;
>   };
>   
>   /* Inferior thread specific part of `struct infcall_suspend_state'.  */
> @@ -471,6 +473,7 @@ class thread_info : public refcounted_object,
>     }
>   
>     int current_line = 0;
> +  int current_column = 0;
>     struct symtab *current_symtab = NULL;
>   
>     /* Internal stepping state.  */
> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> index b12b58db9cb..649671030a5 100644
> --- a/gdb/infcmd.c
> +++ b/gdb/infcmd.c
> @@ -19,6 +19,7 @@
>   
>   #include "defs.h"
>   #include "arch-utils.h"
> +#include "gdbsupport/gdb_assert.h"
>   #include "symtab.h"
>   #include "gdbtypes.h"
>   #include "frame.h"
> @@ -61,7 +62,11 @@
>   
>   static void until_next_command (int);
>   
> -static void step_1 (int, int, const char *);
> +static void
> +step_1 (int, int, bool, const char *);
> +
> +struct step_command_fsm;
> +static int prepare_one_step (thread_info *, step_command_fsm *sm);
>   
>   #define ERROR_NO_INFERIOR \
>      if (!target_has_execution ()) error (_("The program is not being run."));
> @@ -231,8 +236,7 @@ strip_bg_char (const char *args, int *bg_char_p)
>   void
>   post_create_inferior (int from_tty)
>   {
> -
> -  /* Be sure we own the terminal in case write operations are performed.  */
> +  /* Be sure we own the terminal in case write operations are performed.  */
>     target_terminal::ours_for_output ();
>   
>     infrun_debug_show_threads ("threads in the newly created inferior",
> @@ -765,7 +769,7 @@ set_step_frame (thread_info *tp)
>   static void
>   step_command (const char *count_string, int from_tty)
>   {
> -  step_1 (0, 0, count_string);
> +  step_1 (0, 0, false, count_string);
>   }
>   
>   /* Likewise, but skip over subroutine calls as if single instructions.  */
> @@ -773,7 +777,7 @@ step_command (const char *count_string, int from_tty)
>   static void
>   next_command (const char *count_string, int from_tty)
>   {
> -  step_1 (1, 0, count_string);
> +  step_1 (1, 0, false, count_string);
>   }
>   
>   /* Likewise, but step only one instruction.  */
> @@ -781,13 +785,13 @@ next_command (const char *count_string, int from_tty)
>   static void
>   stepi_command (const char *count_string, int from_tty)
>   {
> -  step_1 (0, 1, count_string);
> +  step_1 (0, 1, false, count_string);
>   }
>   
>   static void
>   nexti_command (const char *count_string, int from_tty)
>   {
> -  step_1 (1, 1, count_string);
> +  step_1 (1, 1, false, count_string);
>   }
>   
>   /* Data for the FSM that manages the step/next/stepi/nexti
> @@ -804,44 +808,288 @@ struct step_command_fsm : public thread_fsm
>     /* If true, this is a stepi/nexti, otherwise a step/step.  */
>     int single_inst;
>   
> -  explicit step_command_fsm (struct interp *cmd_interp)
> -    : thread_fsm (cmd_interp)
> +  step_command_fsm (interp *cmd_interp, int count, int skip_subroutine,
> +		    int single_inst, thread_info *thread)
> +    : thread_fsm (cmd_interp), count (count),
> +      skip_subroutines (skip_subroutine), single_inst (single_inst)
>     {
> +    const auto frame = get_current_frame ();
> +    if (!single_inst || skip_subroutines)
> +      set_longjmp_breakpoint (thread, get_frame_id (frame));
> +
> +    thread->control.stepping_command = 1;
>     }
>   
>     void clean_up (struct thread_info *thread) override;
>     bool should_stop (struct thread_info *thread) override;
>     enum async_reply_reason do_async_reply_reason () override;
> +
> +  virtual bool is_step_expression ()
> +  {
> +    return false;
> +  }
>   };
>   
> -/* Prepare for a step/next/etc. command.  Any target resource
> -   allocated here is undone in the FSM's clean_up method.  */
> +/* When a user says `next-expression` or `step-expression` we don't want to
> + * collect symtabs and linetable_entries over and over which for a large
Minor nit, but comment lines not part of the /* token shouldn't start 
with an asterisk.
> + * project might actual come at a pretty substantial cost. If a user enters
Two spaces after period
> + * this command the likelihood that she or he wants to enter it again (and
> + * soon) is high. as such, we cache that using next_expression_cmd_cache. */
s/as/As, and 2 spaces after period too
> +class next_expression_cmd_cache
> +{
> +public:
> +  next_expression_cmd_cache () : line_number (-1), file_name (), cache () {}
> +  gdb::optional<std::vector<linetable_entry> *>
> +  get_cached (const symtab_and_line &sal, frame_info_ptr frame);
> +  int line () const
> +  {
> +    return line_number;
> +  }
> +  frame_id frame () const
> +  {
> +    return starting_frame_id;
> +  }
>   
> -static void
> -step_command_fsm_prepare (struct step_command_fsm *sm,
> -			  int skip_subroutines, int single_inst,
> -			  int count, struct thread_info *thread)
> +private:
> +  frame_id starting_frame_id;
> +  int line_number;
> +  bool is_cached (const std::string &file_name, int line_number) const;
> +  void invalidate_cache ();
> +  std::string file_name;
> +  std::vector<linetable_entry> cache;
> +};
> +
> +static frame_id
> +oldest_frame_at_src_and_line (const char *filename, int line,
> +			      frame_info_ptr begin)
>   {
> -  sm->skip_subroutines = skip_subroutines;
> -  sm->single_inst = single_inst;
> -  sm->count = count;
> +  auto same = begin;
Not a huge issue, but I think the general GDB standard is to avoid using 
auto, unless it would be very unwieldy. I think this (and the next line) 
could be written as frame_info_ptr instead.
> +  auto prev = get_prev_frame (begin);
> +  while (prev != nullptr)
> +    {
> +      const auto sal = find_frame_sal (prev);
> +      if (sal.symtab->filename == filename && sal.line == line)
> +	same = prev;
> +      prev = get_prev_frame (prev);
> +    }
> +  return get_frame_id (same);
> +}
> +
> +gdb::optional<std::vector<linetable_entry> *>
> +next_expression_cmd_cache::get_cached (const symtab_and_line &sal,
> +				       frame_info_ptr frame)
> +{
> +  if (sal.symtab == nullptr)
> +    {
> +      invalidate_cache ();
> +      return {};
> +    }
> +
> +  if (is_cached (sal.symtab->filename, sal.line))
> +    return { &cache };
> +  else
> +    {
> +      starting_frame_id
> +	= oldest_frame_at_src_and_line (sal.symtab->filename, sal.line, frame);
> +      const auto sals
> +	= symtabs_from_filename (sal.symtab->filename, sal.pspace);
> +      std::unordered_map<int, linetable_entry> ltes;
> +
> +      for (const auto &s : sals)
> +	{
> +	  const auto items = s->linetable ()->items ();
> +	  for (const auto &entry : items)
> +	    if (entry.line == sal.line && entry.column != 0)
> +	      ltes[entry.column] = entry;
> +	}
> +
> +      if (ltes.size () <= 1)
> +	{
> +	  invalidate_cache ();
> +	  return {};
> +	}
> +
> +      cache.clear ();
> +      cache.reserve (ltes.size ());
> +      for (const auto kvp : ltes)
> +	cache.push_back (kvp.second);
> +      line_number = sal.line;
> +      file_name = sal.symtab->filename;
> +      return { &cache };
> +    }
> +}
>   
> -  /* Leave the si command alone.  */
> -  if (!sm->single_inst || sm->skip_subroutines)
> -    set_longjmp_breakpoint (thread, get_frame_id (get_current_frame ()));
> +void
> +next_expression_cmd_cache::invalidate_cache ()
> +{
> +  file_name = "";
> +  line_number = -1;
> +  cache.clear ();
> +}
>   
> -  thread->control.stepping_command = 1;
> +bool
> +next_expression_cmd_cache::is_cached (const std::string &file_name,
> +				      int line_number) const
> +{
> +  return this->file_name == file_name && line_number == this->line_number;
>   }
>   
> -static int prepare_one_step (thread_info *, struct step_command_fsm *sm);
> +struct step_expr_fsm : public step_command_fsm
> +{
> +  step_expr_fsm (interp *cmd_interp, int count, int skip_subroutine,
> +		 int single_inst, thread_info *thread,
> +		 std::vector<breakpoint_up> &&bps)
> +    : step_command_fsm (cmd_interp, count, skip_subroutine, single_inst,
> +			thread),
> +      bps (std::move (bps)), is_step (single_inst)
> +  {
> +  }
> +
> +  static std::unique_ptr<step_command_fsm> default_to_next (interp *interp,
> +							    thread_info *thr);
> +
> +  bool should_stop (struct thread_info *thread) override;
> +  void clean_up (thread_info *) override;
> +
> +  bool is_step_expression () override
> +  {
> +    return true;
> +  }
> +
> +  static next_expression_cmd_cache *cache;
> +  std::vector<breakpoint_up> bps;
> +  bool is_step;
> +};
> +
> +next_expression_cmd_cache* step_expr_fsm::cache = new next_expression_cmd_cache{};
> +
> +/* static */
> +std::unique_ptr<step_command_fsm>
> +step_expr_fsm::default_to_next (interp *interp, thread_info *ti)
> +{
> +  return std::make_unique<step_command_fsm> (interp, 1, 1, 0, ti);
> +}
> +
> +void
> +step_expr_fsm::clean_up (thread_info *tp)
> +{
> +  step_command_fsm::clean_up (tp);
> +  bps.clear ();
> +}
> +
> +bool
> +step_expr_fsm::should_stop (thread_info *tp)
> +{
> +  auto stopped_at_bp
> +    = std::find_if (bps.begin (), bps.end (), [tp] (auto &bp) {
> +	return bpstat_find_breakpoint (tp->control.stop_bpstat, bp.get ())
> +	       != nullptr;
> +      });
> +
> +  if (stopped_at_bp != std::end (bps))
> +    {
> +      const auto frame = frame_find_by_id (cache->frame ());
> +      if (not is_step)
> +	{
> +	  const auto current_frame = get_current_frame ();
> +	  auto fid = get_frame_id (current_frame);
> +	  bps.erase (stopped_at_bp);
> +	  if (fid != cache->frame ())
> +	    return prepare_one_step (tp, this);
> +	}
> +      if (--count > 0)
> +	return prepare_one_step (tp, this);
> +      else
> +	{
> +	  set_finished ();
> +	  return true;
> +	}
> +    }
> +
> +  if (tp->control.stop_step)
> +    {
> +      const auto frame = frame_find_by_id (cache->frame ());
> +
> +      if (frame == nullptr)
> +	{
> +	  set_finished ();
> +	  return true;
> +	}
> +
> +      const auto sal = find_frame_sal (frame);
> +      if (sal.line != cache->line ())
> +	{
> +	  set_finished ();
> +	  return true;
> +	}
> +      return prepare_one_step (tp, this);
> +    }
> +  return true;
> +}
> +
> +static std::unique_ptr<step_command_fsm>
> +create_step_fsm (int count, int single_ins, int skip_subroutine,
> +		 bool step_expression, thread_info *thread)
> +{
> +  const auto interp = command_interp ();
> +
> +  /* n/ni, s/si fsm created here */
> +  if (!step_expression)
> +    return std::make_unique<step_command_fsm> (interp, count, skip_subroutine,
> +					       single_ins, thread);
> +  else
> +    {
> +      const auto frame = get_current_frame ();
> +      const auto sal = find_frame_sal (frame);
> +      std::vector<breakpoint_up> bps;
> +      const auto entries = step_expr_fsm::cache->get_cached (sal, frame);
> +      if (entries.has_value ())
> +	{
> +	  const std::vector<linetable_entry> &ltes = **entries;
> +	  if (!single_ins && ltes.size () < count)
> +	    return step_expr_fsm::default_to_next (interp, thread);
> +
> +	  for (const auto &entry : ltes)
> +	    {
> +	      if (!single_ins && entry.column == sal.col)
> +		continue;
> +	      const auto column_rel_addr
> +		= entry.pc (sal.pspace->symfile_object_file);
> +	      const auto frame_gdbarch = get_frame_arch (frame);
> +	      auto bp_loc = set_momentary_breakpoint_at_pc (
I think the open parenthesis should be together with the first 
parameter. If it fits, the frame_gdbarch should be moved up, I think.
> +		frame_gdbarch, column_rel_addr, bp_single_step);
> +	      bps.push_back (std::move (bp_loc));
> +	    }
> +
> +	  if (!bps.empty ())
> +	    return std::make_unique<step_expr_fsm> (
> +	      interp, count, true, single_ins, thread, std::move (bps));
> +	}
> +      return step_expr_fsm::default_to_next (interp, thread);
> +    }
> +  gdb_assert (false && "This should never happen");
This should be gdb_assert_not_reached, since that macro also tells the 
compiler that a certain part of the function is unreachable, if I 
understand it correctly.
> +  return nullptr;
> +}
>   
>   static void
> -step_1 (int skip_subroutines, int single_inst, const char *count_string)
> +next_expression (const char *count_str, int from_tty)
> +{
> +  step_1 (true, false, true, count_str);
step_1 was defined using int for the first 2 parameters, and used as 
such before in the patch... This should be
step_1 (1, 0, true, count_str);
> +}
> +
> +static void
> +step_expression (const char *count_str, int from_tty)
> +{
> +  step_1 (true, true, true, count_str);
same as above
> +}
> +
> +static void
> +step_1 (int skip_subroutines, int single_inst, bool step_expression,
> +	const char *count_string)
>   {
>     int count;
>     int async_exec;
>     struct thread_info *thr;
> -  struct step_command_fsm *step_sm;
>   
>     ERROR_NO_INFERIOR;
>     ensure_not_tfind_mode ();
> @@ -861,22 +1109,20 @@ step_1 (int skip_subroutines, int single_inst, const char *count_string)
>     /* Setup the execution command state machine to handle all the COUNT
>        steps.  */
>     thr = inferior_thread ();
> -  step_sm = new step_command_fsm (command_interp ());
> -  thr->set_thread_fsm (std::unique_ptr<thread_fsm> (step_sm));
> +  auto step_sm = create_step_fsm (count, single_inst, skip_subroutines,
> +				  step_expression, thr);
Once again, using auto here is not very helpful to read the code. I 
think keeping the declaration line separate and manually declaring the 
unique_ptr is the best way to go.
> +  auto fsm_ptr = step_sm.get ();
We shouldn't keep a reference to the raw pointer that is controlled by a 
unique_ptr variable. It could go out of scope unexpectedly on future 
revisions of the code. best to go to the rest of the function and 
manually use step_sm.get()
>   
> -  step_command_fsm_prepare (step_sm, skip_subroutines,
> -			    single_inst, count, thr);
> +  thr->set_thread_fsm (std::move (step_sm));
>   
>     /* Do only one step for now, before returning control to the event
>        loop.  Let the continuation figure out how many other steps we
>        need to do, and handle them one at the time, through
>        step_once.  */
> -  if (!prepare_one_step (thr, step_sm))
> +  if (!prepare_one_step (thr, fsm_ptr))
>       proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT);
>     else
>       {
> -      /* Stepped into an inline frame.  Pretend that we've
> -	 stopped.  */
Why did you remove this comment?
>         thr->thread_fsm ()->clean_up (thr);
>         bool proceeded = normal_stop ();
>         if (!proceeded)
> @@ -1033,6 +1279,7 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
>         if (sm->skip_subroutines)
>   	tp->control.step_over_calls = STEP_OVER_ALL;
>   
> +      tp->control.step_expression = sm->is_step_expression();
>         return 0;
>       }
>   
> @@ -3311,6 +3558,22 @@ Argument N means step N times (or till program stops for another \
>   reason)."));
>     add_com_alias ("s", step_cmd, class_run, 1);
>   
> +  cmd_list_element *nexpr_cmd
> +    = add_com ("next-expression", class_run, next_expression, _ ("\
> +Step program by expressions, proceeding through subroutine calls.\n\
> +Usage: next-expression [N]\n\
> +This is a source-level command and if no debug information was emitted by\n\
> +the compiler this command will default to behaving like 'next'"));
> +  add_com_alias ("ne", nexpr_cmd, class_run, 1);
> +
> +  cmd_list_element *stexpr_cmd
> +    = add_com ("step-expression", class_run, step_expression, _ ("\
> +Step program by expressions, stepping into subroutines if they are in-line.\n\
> +Usage: step-expression [N]\n\
> +This is a source-level command and if no debug information was emitted by\n\
> +the compiler this command will default to behaving like 'next'"));
> +  add_com_alias ("se", stexpr_cmd, class_run, 1);
> +
>     cmd_list_element *until_cmd
>       = add_com ("until", class_run, until_command, _("\
>   Execute until past the current line or past a LOCATION.\n\
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index efe2c00c489..32000b633c7 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -2887,6 +2887,7 @@ clear_proceed_status_thread (struct thread_info *tp)
>     tp->control.proceed_to_finish = 0;
>   
>     tp->control.stepping_command = 0;
> +  tp->control.step_expression = false;
>   
>     /* Discard any remaining commands or status from previous stop.  */
>     bpstat_clear (&tp->control.stop_bpstat);
> @@ -4493,6 +4494,7 @@ set_step_info (thread_info *tp, frame_info_ptr frame,
>   
>     tp->current_symtab = sal.symtab;
>     tp->current_line = sal.line;
> +  tp->current_column = sal.col;
>   
>     infrun_debug_printf
>       ("symtab = %s, line = %d, step_frame_id = %s, step_stack_frame_id = %s",
> @@ -7564,7 +7566,9 @@ process_event_stop_test (struct execution_control_state *ecs)
>     bool refresh_step_info = true;
>     if ((ecs->event_thread->stop_pc () == stop_pc_sal.pc)
>         && (ecs->event_thread->current_line != stop_pc_sal.line
> -	  || ecs->event_thread->current_symtab != stop_pc_sal.symtab))
> +	  || ecs->event_thread->current_symtab != stop_pc_sal.symtab
> +	  || (ecs->event_thread->control.step_expression
> +	      && ecs->event_thread->current_column != stop_pc_sal.col)))
>       {
>         /* We are at a different line.  */
>   
> diff --git a/gdb/linespec.c b/gdb/linespec.c
> index 7d969f37fbf..6abb885a4e5 100644
> --- a/gdb/linespec.c
> +++ b/gdb/linespec.c
> @@ -362,8 +362,6 @@ static std::vector<symtab_and_line> decode_objc (struct linespec_state *self,
>   						 linespec *ls,
>   						 const char *arg);
>   
> -static std::vector<symtab *> symtabs_from_filename
> -  (const char *, struct program_space *pspace);
>   
>   static std::vector<block_symbol> find_label_symbols
>     (struct linespec_state *self,
> @@ -3720,7 +3718,7 @@ collect_symtabs_from_filename (const char *file,
>   /* Return all the symtabs associated to the FILENAME.  If SEARCH_PSPACE is
>      not NULL, the search is restricted to just that program space.  */
>   
> -static std::vector<symtab *>
> +std::vector<symtab *>
>   symtabs_from_filename (const char *filename,
>   		       struct program_space *search_pspace)
>   {
> diff --git a/gdb/linespec.h b/gdb/linespec.h
> index d5e7334fe2d..bab4b93bf56 100644
> --- a/gdb/linespec.h
> +++ b/gdb/linespec.h
> @@ -22,7 +22,7 @@ struct symtab;
>   #include "location.h"
>   
>   /* Flags to pass to decode_line_1 and decode_line_full.  */
> -
> +std::vector<symtab *> symtabs_from_filename (const char *filename, struct program_space *search_pspace);
You seem to have placed the function declaration between the enum 
explanation comment and the enum itself. Also, the function explanation 
comment should be moved here, and linespec.c should have "see 
linespec.h" above the function.
>   enum decode_line_flags
>     {
>       /* Set this flag if you want the resulting SALs to describe the
> diff --git a/gdb/mdebugread.c b/gdb/mdebugread.c
> index 697ce0b5b1a..aaf4878de25 100644
> --- a/gdb/mdebugread.c
> +++ b/gdb/mdebugread.c
> @@ -4012,7 +4012,7 @@ mdebug_expand_psymtab (legacy_psymtab *pst, struct objfile *objfile)
>   		{
>   		  /* Handle encoded stab line number.  */
>   		  record_line
> -		    (get_current_subfile (), sh.index,
> +		    (get_current_subfile (), sh.index, 0,
>   		     unrelocated_addr (gdbarch_addr_bits_remove (gdbarch,
>   								 valu)));
>   		}
> diff --git a/gdb/python/py-symtab.c b/gdb/python/py-symtab.c
> index 26aa8b2fb04..89a86cc136b 100644
> --- a/gdb/python/py-symtab.c
> +++ b/gdb/python/py-symtab.c
> @@ -286,8 +286,12 @@ salpy_str (PyObject *self)
>         filename = symtab_to_filename_for_display (symtab);
>       }
>   
> -  return PyUnicode_FromFormat ("symbol and line for %s, line %d", filename,
> -			       sal->line);
> +  if (sal->col > 0)
> +    return PyUnicode_FromFormat ("symbol and line:col for %s, line %d:%d",
> +				 filename, sal->line, sal->col);
> +  else
> +    return PyUnicode_FromFormat ("symbol and line for %s, line %d", filename,
> +				 sal->line);
>   }
>   
>   static void
> @@ -343,6 +347,16 @@ salpy_get_line (PyObject *self, void *closure)
>     return gdb_py_object_from_longest (sal->line).release ();
>   }
>   
> +static PyObject *
> +salpy_get_col (PyObject *self, void *closure)
> +{
> +  struct symtab_and_line *sal = NULL;
> +
> +  SALPY_REQUIRE_VALID (self, sal);
> +
> +  return gdb_py_object_from_longest (sal->col).release ();
> +}
> +
>   static PyObject *
>   salpy_get_symtab (PyObject *self, void *closure)
>   {
> @@ -597,11 +611,12 @@ PyTypeObject symtab_object_type = {
>   static gdb_PyGetSetDef sal_object_getset[] = {
>     { "symtab", salpy_get_symtab, NULL, "Symtab object.", NULL },
>     { "pc", salpy_get_pc, NULL, "Return the symtab_and_line's pc.", NULL },
> -  { "last", salpy_get_last, NULL,
> -    "Return the symtab_and_line's last address.", NULL },
> -  { "line", salpy_get_line, NULL,
> -    "Return the symtab_and_line's line.", NULL },
> -  {NULL}  /* Sentinel */
> +  { "last", salpy_get_last, NULL, "Return the symtab_and_line's last address.",
> +    NULL },
> +  { "line", salpy_get_line, NULL, "Return the symtab_and_line's line.", NULL },
> +  { "column", salpy_get_col, NULL, "Return the symtab_and_line's column.",
> +    NULL },
> +  { NULL } /* Sentinel */
>   };
>   
>   static PyMethodDef sal_object_methods[] = {
> diff --git a/gdb/stack.c b/gdb/stack.c
> index b1b25aa1c7e..bba89b02ec6 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -57,6 +57,14 @@
>   #include "cli/cli-style.h"
>   #include "gdbsupport/buildargv.h"
>   
> +bool colinfo = true;
> +static void
> +show_debug_col_info (struct ui_file *file, int from_tty,
> +		   struct cmd_list_element *c, const char *value)
> +{
> +  gdb_printf (file, _("Printing column position is %s.\n"), value);
> +}
> +
>   /* The possible choices of "set print frame-arguments", and the value
>      of this setting.  */
>   
> @@ -1418,6 +1426,13 @@ print_frame (const frame_print_options &fp_opts,
>   	uiout->text (":");
>   	annotate_frame_source_line ();
>   	uiout->field_signed ("line", sal.line);
> +
> +	/* Only print column if we have column meta data. */
> +	if (colinfo && sal.col > 0)
> +	  {
> +	    uiout->text (":");
> +	    uiout->field_signed ("column", sal.col);
> +	  }
>   	annotate_frame_source_end ();
>         }
>   
> @@ -3559,6 +3574,13 @@ source line."),
>   				&setlist, &showlist);
>     disassemble_next_line = AUTO_BOOLEAN_FALSE;
>   
> +  add_setshow_boolean_cmd
> +    ("colinfo", class_maintenance, &colinfo,
> +     _("Set printing of column."),
> +     _("Show printing of column setting."),
> +     _("When non-zero, column information will be printed if it exists"),
> +     nullptr, show_debug_col_info, &setdebuglist, &showdebuglist);
> +
>     gdb::option::add_setshow_cmds_for_options
>       (class_stack, &user_frame_print_options,
>        frame_print_option_defs, &setprintlist, &showprintlist);
> diff --git a/gdb/symmisc.c b/gdb/symmisc.c
> index ff7f31f885f..8b44f05a954 100644
> --- a/gdb/symmisc.c
> +++ b/gdb/symmisc.c
> @@ -973,9 +973,10 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
>         /* Leave space for 6 digits of index and line number.  After that the
>   	 tables will just not format as well.  */
>         struct ui_out *uiout = current_uiout;
> -      ui_out_emit_table table_emitter (uiout, 6, -1, "line-table");
> +      ui_out_emit_table table_emitter (uiout, 7, -1, "line-table");
>         uiout->table_header (6, ui_left, "index", _("INDEX"));
>         uiout->table_header (6, ui_left, "line", _("LINE"));
> +      uiout->table_header (6, ui_left, "col", _("COL"));
>         uiout->table_header (18, ui_left, "rel-address", _("REL-ADDRESS"));
>         uiout->table_header (18, ui_left, "unrel-address", _("UNREL-ADDRESS"));
>         uiout->table_header (7, ui_left, "is-stmt", _("IS-STMT"));
> @@ -993,6 +994,7 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
>   	    uiout->field_signed ("line", item->line);
>   	  else
>   	    uiout->field_string ("line", _("END"));
> +	  uiout->field_signed("col", item->column);
>   	  uiout->field_core_addr ("rel-address", objfile->arch (),
>   				  item->pc (objfile));
>   	  uiout->field_core_addr ("unrel-address", objfile->arch (),
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index 4f28667b1b3..3265756a637 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -3247,6 +3247,7 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
>         val.symtab = best_symtab;
>         val.line = best->line;
>         val.pc = best->pc (objfile);
> +      val.col = best->column;
>         if (best_end && (!alt || best_end < alt->pc (objfile)))
>   	val.end = best_end;
>         else if (alt)
> diff --git a/gdb/symtab.h b/gdb/symtab.h
> index d8e3c273f85..ce6444a5a71 100644
> --- a/gdb/symtab.h
> +++ b/gdb/symtab.h
> @@ -1615,6 +1615,9 @@ struct linetable_entry
>     /* The line number for this entry.  */
>     int line;
>   
> +  /* The column number for this entry. 0 means it has no column data. */
> +  int column;
> +
>     /* True if this PC is a good location to place a breakpoint for LINE.  */
>     bool is_stmt : 1;
>   
> @@ -1651,6 +1654,10 @@ struct linetable
>        `struct hack', you can shove it up your ANSI (seriously, if the
>        committee tells us how to do it, we can probably go along).  */
>     struct linetable_entry item[1];
> +  gdb::array_view<const linetable_entry> items () const
This should also have an explained comment
> +  {
> +    return gdb::array_view<const linetable_entry>{ item, item + nitems };
> +  }
>   };
>   
>   /* How to relocate the symbols from each section in a symbol file.
> @@ -1681,6 +1688,17 @@ struct symtab
>       return m_linetable;
>     }
>   
> +  std::vector<linetable_entry> linetable_entries_on_line (int line)
ditto
> +  {
> +    std::vector<linetable_entry> entries;
> +    for (const auto &entry : m_linetable->items ())
> +      {
> +	if (entry.line == line)
> +	  entries.push_back (entry);
> +      }
> +    return entries;
> +  }
> +
>     void set_linetable (const struct linetable *linetable)
>     {
>       m_linetable = linetable;
> @@ -2330,6 +2348,10 @@ struct symtab_and_line
>        information is not available.  */
>     int line = 0;
>   
> +  /* Column number of this particular SAL. 0 indidcates no available column
> +   * information */
> +  int col = 0;
> +
>     CORE_ADDR pc = 0;
>     CORE_ADDR end = 0;
>     bool explicit_pc = false;
> diff --git a/gdb/testsuite/gdb.base/annota1.exp b/gdb/testsuite/gdb.base/annota1.exp
> index 90c03d0d385..ae3dca13130 100644
> --- a/gdb/testsuite/gdb.base/annota1.exp
> +++ b/gdb/testsuite/gdb.base/annota1.exp
> @@ -37,7 +37,7 @@ if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {deb
>   
>   clean_restart ${binfile}
>   
> -# The commands we test here produce many lines of output; disable "press
> +# The commands we test here produce many lines of output; disable "press
>   # <return> to continue" prompts.
>   gdb_test_no_output "set height 0"
>   
> @@ -189,6 +189,8 @@ set run_re \
>   
>   set run_re [join $run_re ""]
>   
> +send_gdb "set debug colinfo 0\n"

It's better to use "gdb_test_no_output" so you can properly handle if 
GDB crashes for some reason.

This is valid for all send_gdb uses here

> +
>   gdb_test_multiple "run" "run until main breakpoint" {
>       -re $run_re.*$gdb_prompt$ {
>   	pass $gdb_test_name
> diff --git a/gdb/testsuite/gdb.base/annota3.exp b/gdb/testsuite/gdb.base/annota3.exp
> index 62efd1aff66..77276f95582 100644
> --- a/gdb/testsuite/gdb.base/annota3.exp
> +++ b/gdb/testsuite/gdb.base/annota3.exp
> @@ -36,10 +36,11 @@ if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {deb
>   
>   clean_restart ${binfile}
>   
> -# The commands we test here produce many lines of output; disable "press
> +# The commands we test here produce many lines of output; disable "press
>   # <return> to continue" prompts.
>   gdb_test_no_output "set height 0"
>   
> +send_gdb "set debug colinfo 0\n"
>   #
>   # break in main
>   #
> @@ -71,7 +72,7 @@ set gdb_prompt "\r\n\032\032pre-prompt\r\n$gdb_prompt \r\n\032\032prompt\r\n"
>   # annotate-prompt
>   # annotate-post-prompt (in the next block)
>   #
> -send_gdb "set annotate 3\n"
> +send_gdb "set annotate 3\n"
>   gdb_expect_list "annotation set at level 3" "\r\n$gdb_prompt$" {
>       "set annotate 3"
>   }
> @@ -105,6 +106,7 @@ gdb_expect_list "breakpoint info" "$gdb_prompt$" [concat {
>   # run to a break point will test:
>   #
>   #exp_internal 1
> +send_gdb "set annotate 3\n"
>   send_gdb "run\n"
>   gdb_expect_list "run until main breakpoint" "$gdb_prompt$" [concat {
>       "\r\n\032\032post-prompt\r\n"
> diff --git a/gdb/testsuite/gdb.base/dlmopen.exp b/gdb/testsuite/gdb.base/dlmopen.exp
> index ddb832a64f8..6483d7a900b 100644
> --- a/gdb/testsuite/gdb.base/dlmopen.exp
> +++ b/gdb/testsuite/gdb.base/dlmopen.exp
> @@ -106,7 +106,7 @@ proc test_dlmopen_one { ndso1 ndso2 exp_glob } {
>   # The actual test.  We run it twice.
>   proc test_dlmopen {} {
>       global srcfile basename_lib bp_main
> -
> +    send_gdb "set debug colinfo 0\n"
>       # Note that when loading dlmopen-lib.1.so and dlmopen-lib.2.so into
>       # the same namespace, dlmopen-lib-dep.so is loaded only once, so in
>       # this case, the changes to gdb_dlmopen_glob inside test_dlmopen_one
> diff --git a/gdb/testsuite/gdb.base/empty-host-env-vars.exp b/gdb/testsuite/gdb.base/empty-host-env-vars.exp
> index 96240311c24..0d11e0953d5 100644
> --- a/gdb/testsuite/gdb.base/empty-host-env-vars.exp
> +++ b/gdb/testsuite/gdb.base/empty-host-env-vars.exp
> @@ -20,6 +20,7 @@ set all_env_vars { HOME XDG_CACHE_HOME LOCALAPPDATA XDG_CONFIG_HOME }
>   
>   # Record the initial value of the index-cache directory.
>   clean_restart
> +
Spurious change? This is the only change on this file
>   set index_cache_directory ""
>   gdb_test_multiple "show index-cache directory" "" {
>       -re -wrap "The directory of the index cache is \"(.*)\"\\." {
> diff --git a/gdb/testsuite/gdb.base/gcore.exp b/gdb/testsuite/gdb.base/gcore.exp
> index 1e88d5334ba..befa0f9b2e6 100644
> --- a/gdb/testsuite/gdb.base/gcore.exp
> +++ b/gdb/testsuite/gdb.base/gcore.exp
> @@ -58,6 +58,7 @@ if {!$core_supported} {
>   
>   # Now restart gdb and load the corefile.
>   clean_restart $binfile
> +send_gdb "set debug colinfo 0\n"
>   
>   set core_loaded [gdb_core_cmd "$corefile" "re-load generated corefile"]
>   if { $core_loaded == -1 } {
> diff --git a/gdb/testsuite/gdb.base/maint.exp b/gdb/testsuite/gdb.base/maint.exp
> index c05d0987e7f..134291f37c4 100644
> --- a/gdb/testsuite/gdb.base/maint.exp
> +++ b/gdb/testsuite/gdb.base/maint.exp
> @@ -371,7 +371,7 @@ gdb_test_multiple "maint info breakpoints" "maint info breakpoints" {
>   
>   gdb_test "maint print" \
>       "List.*unambiguous\\..*" \
> -    "maint print w/o args"
> +    "maint print w/o args"
>   
>   gdb_test "maint info" \
>       "List.*unambiguous\\..*" \
> @@ -383,14 +383,15 @@ gdb_test "maint" \
>   
>   # Test that "main info line-table" w/o a file name shows the symtab for
>   # $srcfile.
> +match_max -i $gdb_spawn_id 150000
>   set saw_srcfile 0
>   gdb_test_multiple "maint info line-table" \
>       "maint info line-table w/o a file name" {
> -    -re "symtab: \[^\n\r\]+${srcfile} \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
> +    -re "symtab: \[^\n\r\]+${srcfile} \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+COL\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
>   	set saw_srcfile 1
>   	exp_continue
>       }
> -    -re "symtab: \[^\n\r\]+ \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
> +    -re "symtab: \[^\n\r\]+ \\(\\(struct symtab \\*\\) $hex\\)\r\nlinetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+COL\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[^\r\n\]*" {
>   	# Match each symtab to avoid overflowing expect's buffer.
>   	exp_continue
>       }
> @@ -398,7 +399,7 @@ gdb_test_multiple "maint info line-table" \
>   	# For symtabs with no linetable.
>   	exp_continue
>       }
> -    -re "^$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\[^\r\n\]*\r\n" {
> +    -re "^$decimal\[ \t\]+$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\[^\r\n\]*\r\n" {
>   	# Line table entries can be long too:
>   	#
>   	#  INDEX    LINE ADDRESS
> @@ -429,6 +430,8 @@ gdb_test_multiple "maint info line-table" \
>       }
>   }
>   
> +match_max -i $gdb_spawn_id [match_max -d]
> +
>   gdb_test "maint info line-table ${srcfile}" \
>       "symtab: \[^\n\r\]+${srcfile}.*INDEX.*LINE.*ADDRESS.*" \
>       "maint info line-table with filename of current symtab"
> diff --git a/gdb/testsuite/gdb.base/next-expression.c b/gdb/testsuite/gdb.base/next-expression.c
> new file mode 100644
> index 00000000000..bf5f19ae626
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/next-expression.c
> @@ -0,0 +1,113 @@
> +/* Copyright 2023 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +struct FooBarBaz
> +{
> +  int a, b, c;
> +};
> +
> +struct Builder
> +{
> +  using Finalizer = int (*) (int);
> +  Builder () {}
> +
> +  Builder &set_foo (int f)
> +  {
> +    foo = f;
> +    return *this;
> +  }
> +
> +  Builder &set_bar (int b)
> +  {
> +    bar = b;
> +    return *this;
> +  }
> +
> +  Builder &set_baz (int b)
> +  {
> +    baz = b;
> +    return *this;
> +  }
> +
> +  Builder &set_fin (Finalizer f)
> +  {
> +    fn = f;
> +    return *this;
> +  }
> +
> +  template <typename Finalizer> FooBarBaz fin (Finalizer fn)
> +  {
> +    const auto res = FooBarBaz{ .a = fn (foo), .b = fn (bar), .c = fn (baz) };
> +    return res;
> +  }
> +
> +  FooBarBaz fin ()
> +  {
> +    if (fn != nullptr)
> +      return FooBarBaz{ .a = fn (foo), .b = fn (bar), .c = fn (baz) };
> +    else
> +      return FooBarBaz{ .a = foo, .b = bar, .c = baz };
> +  }
> +
> +private:
> +  int (*fn) (int) = nullptr;
> +  int foo;
> +  int bar;
> +  int baz;
> +};
> +
> +static int v = 0;
> +
> +// next v

Test cases still need to follow the GDB coding standard, so comments 
should use /* */ instead of //

This is also valid for function declarations separating the name and the 
parenthesis, as used below.

> +static int
> +n()
> +{
> +  return ++v;
> +}
> +
> +static bool is_n_even() {
> +  return (v & 1) == 0;
> +}
> +
> +int
> +main (int argc, const char **argv)
> +{
> +  const int AAA = 10, BBB = 20, CCC = 30; /* COMMA_STATEMENTS */
> +  Builder b;
> +  // `step-expression` between all, from foo to into fin lambda (lambda is repeated 3 times)
> +  const auto se_1 = b.set_foo(AAA).set_bar(BBB).set_baz(CCC).fin([] (auto v) { return v * 2; }); /* SE-1 */
> +  // clang emits col loc for n(), gcc does not yet, hopefully they fix this bug.
> +  // gcc also emits faulty col locs, but usable see issue
> +  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902
> +  const auto se_2 = b.set_foo(n()).set_bar(n()).set_baz(n()).fin([] (auto v) { return v * 3; }); /* SE-2 */
> +  // `next-expression` set_fin lambda is not called immediately, should not step into
> +  const auto se_3 = b.set_fin([](auto v) { return v * 3; }).fin(); /* SE-3 */
> +  for (int i = 0; i < 10; i++)
> +    {
> +      i++;
> +    }
> +  // `next-expression` should step over and land on 107
> +  if(is_n_even() && !is_n_even()) { /* TWO_COLS_VALID */
> +    const int FOO = 10; const int BAR = 20; const int BAZ = 30;
> +  }
> +  const int FOO = 10; const int BAR = 20; const int BAZ = 30; /* STATEMENTS_LINE */
> +
> +  // do `next-expression` - step between expressions, but not into subroutines
> +  const auto ne_1 = b.set_foo(AAA).set_bar(BBB).set_baz(CCC).fin([] (auto v) { return v * 2; }); /* NE-1 */
> +  const auto ne_2 = b.set_foo(n()).set_bar(n()).set_baz(n()).fin([] (auto v) { return v * 3; }); /* NE-2 */
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/next-expression.exp b/gdb/testsuite/gdb.base/next-expression.exp
> new file mode 100644
> index 00000000000..8eb0a7d24e3
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/next-expression.exp
> @@ -0,0 +1,267 @@
> +# Copyright 2023 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# The intent of this test is to verify that `next-expression` command
> +# behaves as expected. Note that in the future this test might
> +# start failing as columns are hard coded in the test; and GCC has
> +# bugs in how it emits column meta data, see
> +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902
> +# however, the column meta data is still useful even if not DWARF compliant
> +
> +set saved_gdbflags $GDBFLAGS
> +
> +# turn on column meta data, which is off by default during testing
> +set GDBFLAGS [concat $GDBFLAGS " -ex \"set debug colinfo 1\""]
> +
> +set is_running 0
> +
> +set GCC_ISSUE_URL "https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109902"
> +
> +standard_testfile
> +set srcfile next-expression.c
> +
> +set TEST_ID 1
> +
> +# gcc = 0, clang = 1
> +set compiler 0
> +if {[test_compiler_info {gcc*} {c++}]} {
> +    set compiler 0
> +} elseif {[test_compiler_info {clang*} {c++}]} {
> +    set compiler 1
> +} else {
> +    set compiler 100
> +}
> +
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
> +  return -1
> +}
> +
> +proc test_command { cmd count line columns } {
> +  global srcfile
> +  global GCC_ISSUE_URL
> +  global TEST_ID
> +  global compiler
> +  set idx $count
> +  set list_len [ llength $columns ]
> +  for { set i [expr $count - 1] } { $i < $list_len } { incr i $count } {
> +    set col [ lindex $columns $i ]
> +    send_gdb "$cmd $count\n"
> +    # handle testsuite flakeyness
> +    sleep 0.01
I think it would be better to have a:

gdb_test "$cmd $count" ".*"

since this already deals with the flakeyness and GDB having issues. In 
fact, when printing the line where the inferior stops, GDB already 
prints the column we're stopped at, so why not check the column here 
instead of/along with testing the "frame" command?

In fact, there are a few places where you use send_gdb where there are 
commands designed to handle those specific situations and warn us if GDB 
does something unexpected. I've pointed out each first new command I found.

> +    if [ gdb_test "frame" \
> +	     ".*$srcfile:$line:$col.*" \
> +	     "verify that we $cmd $count to column $col on $line (#$TEST_ID)"] {
> +		if { $compiler == 0 } {
> +		  puts "Test failed - GCC might have fixed bug \
> +			$GCC_ISSUE_URL"
> +		} else {
> +		  puts "Landed at unexpected column"
> +		}

Instead of having TEST_ID at the end, you can set the prefix as the TEST_ID.

Also, instead of having an if and using "puts", you should use 
setup_xfail before the call to gdb_test, so the test wont add new 
failures to the testsuite. If someone wants to investigate the xfails, 
they'll see the comments in the test case.

> +	     }
> +    incr TEST_ID
> +  }
> +}
> +
> +proc test_next_expression { count line columns } {
> +  test_command "next-expression" $count $line $columns
> +}
> +
> +proc test_step_expression { count line columns } {
> +  test_command "step-expression" $count $line $columns
> +}
> +
> +proc goto_line_test_expression_command { count is_step line columns } {
> +  global srcfile
> +  global GCC_ISSUE_URL
> +  global is_running
> +  global TEST_ID
> +  send_gdb "tbreak $line\n"
this should use gdb_breakpoint "$line" temporary
> +  set firstcolumn [lindex $columns 0]
> +  set test_num 1
> +  if { $is_running == 0 } {
> +    send_gdb "run\n"
> +    # handle testsuite flakeyness
> +    sleep 0.01
this should be handled by runto
> +    gdb_test "frame" \
> +	   ".*$srcfile:$line:$firstcolumn.*" \
> +	   "verify that we continued to column $firstcolumn on $line (#$TEST_ID)"
> +    incr TEST_ID
> +    set is_running 1
> +  } else {
> +    send_gdb "continue\n"
> +    # handle testsuite flakeyness
> +    sleep 0.01
this by gdb_continue_to_breakpoint (I think you might need to specify 
its temporary? not sure)
> +    gdb_test "frame" \
> +	     ".*$srcfile:$line:$firstcolumn.*" \
> +	     "verify that we continued to column $firstcolumn on $line (#$TEST_ID)"
> +    incr TEST_ID
> +  }
> +  # "pop" the first element
> +  set columns [lreplace $columns 0 0]
> +  if { $is_step == 0 } {
> +    test_next_expression $count $line $columns
> +  } else {
> +    test_step_expression $count $line $columns
> +  }
> +}
> +
> +proc goto_line_test_step { count line columns } {
> +  goto_line_test_expression_command $count 1 $line $columns
> +}
> +
> +proc goto_line_test_next { count line columns } {
> +  goto_line_test_expression_command $count 0 $line $columns
> +}
> +
> +# SE-FOO represents tests & columns using `step-expression`
> +# NE-FOO represents tests & columns using `next-expression`
> +
> +# GCC emits correct col meta data (clang does as well)
> +set comma_stmts_cols { 13 23 33 }
> +set comma_stmts [gdb_get_line_number "COMMA_STATEMENTS"]
> +goto_line_test_step 1 $comma_stmts $comma_stmts_cols
> +
> +set se1 [ gdb_get_line_number "SE-1" ]
> +# gcc emit faulty (non DWARF-compliant) cols, but usable
> +set se1_cols_gcc { 30 43 56 65 66 91 94 66 91 94 66 91 94 }
> +# correct columns in order of execution, left here for documentation purposes
> +set se1_cols_clang { 23 36 49 62 87 89 80 87 89 80 87 89 80}
> +
> +# the columns stepped to using next-expression
> +set ne1 [ gdb_get_line_number "NE-1" ]
> +set ne1_cols_gcc { 30 43 56 65 }
> +set ne1_cols_clang { 23 36 49 62 }
> +
> +set se2 [ gdb_get_line_number "SE-2" ]
> +# gcc emit faulty (non DWARF-compliant) cols, but usable
> +set se2_cols_gcc { 30 43 56 65 66 91 94 }
> +set se2_cols_clang { 31 23 44 36 57 49 62 89 80 }
> +
> +set ne2 [ gdb_get_line_number "NE-2" ]
> +# gcc emit faulty (non DWARF-compliant) cols, but usable
> +set ne2_cols_gcc { 30 43 56 65 }
> +set ne2_cols_clang { 31 23 44 36 57 49 62 }
> +
> +set se3 [ gdb_get_line_number "SE-3" ]
> +# gcc emit faulty (non DWARF-compliant) cols, but usable
> +set se3_cols_gcc { 30 64 }
> +# correct columns in order of execution, left here for documentation purposes
> +set se3_cols_clang { 31 23 61 }
> +
> +
> +
> +
> +set two_cols_valid [ gdb_get_line_number "TWO_COLS_VALID" ]
> +# gcc emit faulty (non DWARF-compliant) cols, but usable
> +set two_cols_gcc { 15 18 }
> +# correct columns in order of execution, left here for documentation purposes
> +set two_cols_clang { 6 18 }
> +
> +# test step-expression commands
> +if { $compiler == 0 } {
> +    goto_line_test_step 1 $se1 $se1_cols_gcc
> +    goto_line_test_step 1 $se2 $se2_cols_gcc
> +    goto_line_test_step 1 $se3 $se3_cols_gcc
> +    goto_line_test_step 1 $two_cols_valid $two_cols_gcc
> +} elseif { $compiler == 1 } {
> +    goto_line_test_step 1 $se1 $se1_cols_clang
> +    goto_line_test_step 1 $se2 $se2_cols_clang
> +    goto_line_test_step 1 $se3 $se3_cols_clang
> +    goto_line_test_step 1 $two_cols_valid $two_cols_clang
> +}
> +
> +set stmt_line [ gdb_get_line_number "STATEMENTS_LINE" ]
> +goto_line_test_step 1 $stmt_line { 13 33 53 }
> +
> +
> +if { $compiler == 0 } {
> +  goto_line_test_next 1 $ne1 $ne1_cols_gcc
> +  goto_line_test_next 1 $ne2 $ne2_cols_gcc
> +} elseif { $compiler == 1 } {
> +  goto_line_test_next 1 $ne1 $ne1_cols_clang
> +  goto_line_test_next 1 $ne2 $ne2_cols_clang
> +}
> +
> +clean_restart ${testfile}
> +set is_running 0
> +send_gdb "set debug colinfo 1\n"
> +
> +# test step-expression commands with `3` as count
> +if { $compiler == 0 } {
> +    goto_line_test_step 3 $se1 $se1_cols_gcc
> +    goto_line_test_step 3 $se2 $se2_cols_gcc
> +    goto_line_test_step 3 $se3 $se3_cols_gcc
> +    goto_line_test_step 3 $two_cols_valid $two_cols_gcc
> +} elseif { $compiler == 1 } {
> +    goto_line_test_step 3 $se1 $se1_cols_clang
> +    goto_line_test_step 3 $se2 $se2_cols_clang
> +    goto_line_test_step 3 $se3 $se3_cols_clang
> +    goto_line_test_step 3 $two_cols_valid $two_cols_clang
> +}
> +
> +if { $compiler == 0 } {
> +  goto_line_test_next 4 $ne1 $ne1_cols_gcc
> +  goto_line_test_next 4 $ne2 $ne2_cols_gcc
> +} elseif { $compiler == 1 } {
> +  goto_line_test_next 4 $ne1 $ne1_cols_clang
> +  goto_line_test_next 4 $ne2 $ne2_cols_clang
> +}
> +
> +# Test that caching works as expected
> +# see comments on infcmd.c in function oldest_frame_at_src_and_line
> +clean_restart ${testfile}
> +set is_running 0
> +send_gdb "set debug colinfo 1\n"
> +
> +send_gdb "tbreak fin\n"
> +send_gdb "run\n"
> +gdb_test "frame" \
> +	 ".*Builder::fin.*$srcfile:54:.*" \
> +	 "verify that landed in Builder::fin"
> +send_gdb "step\n"
> +# we're now inside lambda
> +gdb_test "frame" \
> +	 ".*$srcfile:$se1:.*" \
> +	 "verify that we're inside lambda at $srcfile:$se1"
> +# if cache'ing has worked properly, we should land at src.cpp:L+N
> +# (which happens to be se-2), we should _not_ land back inside Builder::fin.
> +# The `next` or `step` commands will do that, as they are instruction-level
> +# commands. expression commands are meant to be source-level navigation.
> +send_gdb "next-expression\n"
> +gdb_test "frame" \
> +	 ".*$srcfile:$se2:.*" \
> +	 "verify that we did not step into calling frame, \
> +	 but to next logical source line instead"
> +
> +# Repeat above test, but instead of a final `next-expression` use a `next`
> +# to verify that we _do_ land in Builder::fin
> +clean_restart ${testfile}
> +set is_running 0
> +send_gdb "set debug colinfo 1\n"
> +if { $compiler == 0} {
> +  goto_line_test_step 1 $se1 { 30 43 56 65 66 91 }
> +  # we're inside lambda - `next` should get us to Builder::fin, which is 1 frame up
> +  send_gdb "next\n"
> +  gdb_test "frame" \
> +	   ".*Builder::fin.*" \
> +	   "verify difference between source & instruction level commands"
> +} elseif { $compiler == 1 } {
> +  goto_line_test_step 1 $se1 { 23 36 49 62 87 89 }
> +  # we're inside lambda - `next` should get us to Builder::fin, which is 1 frame up
> +  send_gdb "next\n"
> +  gdb_test "frame" \
> +	   ".*Builder::fin.*" \
> +	   "verify difference between source & instruction level commands"
> +}
> diff --git a/gdb/testsuite/gdb.cp/annota3.exp b/gdb/testsuite/gdb.cp/annota3.exp
> index 7b85c84cd97..2eb9faae6ea 100644
> --- a/gdb/testsuite/gdb.cp/annota3.exp
> +++ b/gdb/testsuite/gdb.cp/annota3.exp
> @@ -65,7 +65,7 @@ send_gdb "set annotate 3\n"
>   gdb_expect_list "annotation set at level 3" "\r\n$gdb_prompt$" {
>       "set annotate 3"
>   }
> -
> +send_gdb "set debug colinfo 0\n"
>   send_gdb "run\n"
>   gdb_expect_list "first run until main breakpoint" "$gdb_prompt$" {
>       "\r\n\032\032post-prompt\r\n"
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp b/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp
> index d2c28a87923..20c0bc90235 100644
> --- a/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-out-of-range-end-of-seq.exp
> @@ -85,10 +85,10 @@ if ![runto_main] {
>   
>   set test "END with address 1 eliminated"
>   gdb_test_multiple "maint info line-table $srcfile$" $test {
> -    -re -wrap "END *0x0*1 *$hex *Y *\r\n.*" {
> +    -re -wrap "END.* *0x0*1 *$hex *Y *\r\n.*" {
>   	fail $gdb_test_name
>       }
> -    -re -wrap "END *$hex *$hex *Y *\r\n" {
> +    -re -wrap "END.**$hex *$hex *Y *\r\n" {
>   	pass $gdb_test_name
>       }
>   }
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp b/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp
> index ee274ee128c..68be2b80fe7 100644
> --- a/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-base.exp
> @@ -145,7 +145,7 @@ set prev -1
>   set seq_count 0
>   gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
>       "count END markers in line table" {
> -	-re "^$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
> +	-re "^$decimal\[ \t\]+$decimal\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
>   	    if { $prev != -1 } {
>   		gdb_assert "$prev == 1" \
>   		    "prev of normal entry at $seq_count is end marker"
> @@ -154,7 +154,7 @@ gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
>   	    incr seq_count
>   	    exp_continue
>   	}
> -	-re "^$decimal\[ \t\]+END\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
> +	-re "^$decimal\[ \t\]+END\[ \t\]+$decimal\[ \t\]+$hex\[ \t\]+$hex\(\[ \t\]+Y\)? *\r\n" {
>   	    if { $prev != -1 } {
>   		gdb_assert "$prev == 0" \
>   		    "prev of end marker at $seq_count is normal entry"
> @@ -174,7 +174,7 @@ gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
>   	-re ".*linetable: \\(\\(struct linetable \\*\\) 0x0\\):\r\nNo line table.\r\n" {
>   	    exp_continue
>   	}
> -	-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]PROLOGUE-END *\r\n" {
> +	-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+COL\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]PROLOGUE-END *\r\n" {
>   	    exp_continue
>   	}
>       }
> diff --git a/gdb/testsuite/gdb.mi/mi-async.exp b/gdb/testsuite/gdb.mi/mi-async.exp
> index 4bc82d5f55d..2aff0654bb7 100644
> --- a/gdb/testsuite/gdb.mi/mi-async.exp
> +++ b/gdb/testsuite/gdb.mi/mi-async.exp
> @@ -31,7 +31,7 @@ require !use_gdb_stub
>   
>   # The plan is for async mode to become the default but toggle for now.
>   set saved_gdbflags $GDBFLAGS
> -set GDBFLAGS [concat $GDBFLAGS " -ex \"set mi-async on\""]
> +set GDBFLAGS [concat $GDBFLAGS " -ex \"set mi-async on\"" " -ex \"set debug colinfo 0\""]
>   
>   load_lib mi-support.exp
>   
> diff --git a/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp b/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp
> index 23f4c50f63f..603708fa6fd 100644
> --- a/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp
> +++ b/gdb/testsuite/gdb.threads/process-dies-while-handling-bp.exp
> @@ -41,7 +41,7 @@ proc do_test { non_stop cond_bp_target } {
>       global linenum
>   
>       set saved_gdbflags $GDBFLAGS
> -    set GDBFLAGS [concat $GDBFLAGS " -ex \"set non-stop $non_stop\""]
> +    set GDBFLAGS [concat $GDBFLAGS " -ex \"set non-stop $non_stop\"" " -ex \"set debug colinfo 0\""]
>       clean_restart $binfile
>       set GDBFLAGS $saved_gdbflags
>   
> diff --git a/gdb/testsuite/lib/gdb-utils.exp b/gdb/testsuite/lib/gdb-utils.exp
> index a010e14fc04..14e89716fd6 100644
> --- a/gdb/testsuite/lib/gdb-utils.exp
> +++ b/gdb/testsuite/lib/gdb-utils.exp
> @@ -26,6 +26,8 @@ proc gdb_init_commands {} {
>       if [target_info exists gdb_init_commands] {
>   	set commands [concat $commands [target_info gdb_init_commands]]
>       }
> +    # Turn off column info; irrelevant for almost all tests.
> +    lappend commands "set debug colinfo 0"
>       return $commands
>   }
>   
> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> index 133d914aff8..d45b389024b 100644
> --- a/gdb/testsuite/lib/gdb.exp
> +++ b/gdb/testsuite/lib/gdb.exp
> @@ -9028,7 +9028,7 @@ proc is_stmt_addresses { file } {
>       set is_stmt [list]
>   
>       gdb_test_multiple "maint info line-table $file" "" {
> -	-re "\r\n$decimal\[ \t\]+$decimal\[ \t\]+($hex)\[ \t\]+$hex\[ \t\]+Y\[^\r\n\]*" {
> +	-re "\r\n$decimal\[ \t\]+$decimal\[ \t\]+$decimal\[ \t\]+($hex)\[ \t\]+$hex\[ \t\]+Y\[^\r\n\]*" {
>   	    lappend is_stmt $expect_out(1,string)
>   	    exp_continue
>   	}


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2023-08-15 16:02 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-05-24 17:52 [PATCH v3] [gdb/infcmd]: Add next-expression command Simon Farre
2023-05-26  9:53 ` Eli Zaretskii
2023-08-15 16:02 ` Guinevere Larsen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).