'thread' and 'frame' attributes containing gdb.Inferior,
gdb.InferiorThread, and gdb.Frame objects respectively.
+ ** New method gdb.Symtab.source_lines(start, end, unstyled). This
+ returns a tuple of strings, each string represents a single
+ source line for the file associated with the symtab. The 'start'
+ and 'end' are line numbers, and allow for only some lines to be
+ returned, these default to the first and last lines in the file
+ respectively. If 'unstyled' is true then the returned lines will
+ have no styling applied, otherwise, styling will be applied if
+ the appropriate user setting is enabled, and GDB knows how to
+ style this source file.
+
* Guile API
** Procedures 'memory-port-read-buffer-size',
@xref{Line Tables In Python}.
@end defun
+@defun Symtab.source_lines (first = @code{1}, last = @code{0}, unstyled = @code{False})
+Read source lines from the file associated with this Symtab, this will
+be the filename pointed to by @code{Symtab.fullname}. The source
+lines are returned as a tuple of strings, each string represents a
+single source line.
+
+The @var{first} and @var{last} arguments can be used to only return
+some lines from the file, @var{first} will be the first line returned,
+and @var{last} will be the last line returned, as such @var{first}
+must be less than, or equal to, @var{last}.
+
+The default for @var{first} is @code{1}, which is the first line of
+the file. The default for @var{last} is @code{0}, which is a special
+value meaning the last line of the file.
+
+When @var{unstyled} is @code{True} then the strings returned will have
+no style information included. When @var{unstyled} is @code{False},
+the default, then the returned strings will include style information
+if styling is enabled (@pxref{Output Styling}), and @value{GDBN} knows
+how to style the source file. Styling information will take the form of
+ANSI terminal escape sequences embedded within the strings.
+
+If the source file cannot be read, for example, if the source file is
+missing, then this method returns @code{None}.
+@end defun
+
@node Line Tables In Python
@subsubsection Manipulating line tables using Python
#include "python-internal.h"
#include "objfiles.h"
#include "block.h"
+#include "source-cache.h"
+#include "cli/cli-style.h"
struct symtab_object : public PyObject
{
return symtab_to_linetable_object (self).release ();
}
+/* Implement gdb.Symtab.source_lines(). Return a tuple of strings, each
+ string representing a source line. Return None if the source could not
+ be read (e.g. if the source file is missing). */
+
+static PyObject *
+stpy_source_lines (PyObject *self, PyObject *args, PyObject *kw)
+{
+ struct symtab *symtab = nullptr;
+
+ STPY_REQUIRE_VALID (self, symtab);
+
+ static const char *keywords[] = { "first", "last", "unstyled", nullptr };
+ int first = 1, last = 0, unstyled_p = 0;
+
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "|iip", keywords,
+ &first, &last, &unstyled_p))
+ return nullptr;
+
+ gdb_assert (unstyled_p == 0 || unstyled_p == 1);
+
+ std::optional<int> last_lineno = last_symtab_line (symtab);
+ if (!last_lineno.has_value ())
+ Py_RETURN_NONE;
+
+
+ if (first < 1)
+ {
+ PyErr_Format (PyExc_ValueError,
+ _("Invalid value %d for first line number, "
+ "minimum value is 1."),
+ first);
+ return nullptr;
+ }
+ else if (first > last_lineno.value ())
+ {
+ PyErr_Format (PyExc_ValueError,
+ _("Line number %d out of range, file has %d lines."),
+ first, last_lineno.value ());
+ return nullptr;
+ }
+
+ if (last < 0)
+ {
+ PyErr_Format (PyExc_ValueError,
+ _("Invalid value %d for last line number."),
+ last);
+ return nullptr;
+ }
+ else if (last == 0 || last > last_lineno.value ())
+ last = last_lineno.value ();
+
+ if (first > last)
+ {
+ PyErr_Format (PyExc_ValueError,
+ _("First line %d is after the last line %d."),
+ first, last);
+ return nullptr;
+ }
+
+ try
+ {
+ std::string lines;
+
+ {
+ bool required_styling = (unstyled_p ? false : source_styling);
+ scoped_restore restore_styling
+ = make_scoped_restore (&source_styling, required_styling);
+
+ if (!g_source_cache.get_source_lines (symtab, first, last, &lines))
+ Py_RETURN_NONE;
+ }
+
+ gdbpy_ref<> list (PyList_New (0));
+ if (list == nullptr)
+ return nullptr;
+
+ for (std::string::size_type pos = 0; pos != lines.size (); )
+ {
+ std::string::size_type len;
+ std::string::size_type new_pos = lines.find ('\n', pos);
+ if (new_pos == std::string::npos)
+ len = lines.size () - pos;
+ else
+ {
+ new_pos++;
+ len = new_pos - pos;
+ }
+
+ std::string_view view (lines.c_str () + pos, len);
+ gdbpy_ref<> str = host_string_to_python_string (view);
+ if (str == nullptr)
+ return nullptr;
+
+ if (PyList_Append (list.get (), str.get ()) == -1)
+ return nullptr;
+
+ pos = new_pos;
+ }
+
+ return PyList_AsTuple (list.get ());
+ }
+ catch (const gdb_exception &except)
+ {
+ return gdbpy_handle_gdb_exception (nullptr, except);
+ }
+}
+
static PyObject *
salpy_str (PyObject *self)
{
{ "linetable", stpy_get_linetable, METH_NOARGS,
"linetable () -> gdb.LineTable.\n\
Return the LineTable associated with this symbol table" },
+ { "source_lines", (PyCFunction) stpy_source_lines,
+ METH_VARARGS | METH_KEYWORDS,
+ "source_lines(Int,Int,Bool)->None|[String].\n\
+Return a list of source lines, or None if source could not\n\
+be read." },
{NULL} /* Sentinel */
};
return unicode_to_encoded_string (str.get (), host_charset ());
}
-/* Convert a host string to a python string. */
+/* See python/python-internal.h. */
gdbpy_ref<>
-host_string_to_python_string (const char *str)
+host_string_to_python_string (std::string_view str)
{
- return gdbpy_ref<> (PyUnicode_Decode (str, strlen (str), host_charset (),
- NULL));
+ return gdbpy_ref<> (PyUnicode_Decode (str.data (), str.size (),
+ host_charset (), nullptr));
}
/* Return true if OBJ is a Python string or unicode object, false
gdb::unique_xmalloc_ptr<char> python_string_to_target_string (PyObject *obj);
gdbpy_ref<> python_string_to_target_python_string (PyObject *obj);
gdb::unique_xmalloc_ptr<char> python_string_to_host_string (PyObject *obj);
-gdbpy_ref<> host_string_to_python_string (const char *str);
+
+/* Convert a host string STR to a python string. */
+
+extern gdbpy_ref<> host_string_to_python_string (std::string_view str);
+
int gdbpy_is_string (PyObject *obj);
gdb::unique_xmalloc_ptr<char> gdbpy_obj_to_string (PyObject *obj);
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 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/>. */
+
+int /* Location A. */
+func (int arg)
+{
+ int i = 2;
+ i = i * arg;
+ return arg;
+}
+
+struct simple_struct
+{
+ int a;
+ int b;
+}; /* Location B. */
+
+int
+main (void)
+{
+ int result;
+ struct simple_struct ss = { 10, 11 };
+
+ result = func (ss.a);
+ result -= ss.b;
+
+ return result;
+} /* Location C. */
--- /dev/null
+# Copyright (C) 2026 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/>.
+
+# Test the gdb.Symtab.source_lines method.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+if {[build_executable "failed to build" $testfile $srcfile]} {
+ return
+}
+
+# Start with a fresh GDB, but enable color support.
+with_ansi_styling_terminal {
+ clean_restart $testfile
+}
+
+if {![runto_main]} {
+ return
+}
+
+set lineno_a [gdb_get_line_number "Location A"]
+set lineno_b [gdb_get_line_number "Location B"]
+set lineno_c [gdb_get_line_number "Location C"]
+
+# Run CMD, either 'list' or a Python replacement for that command,
+# passing in ARGS. Collect all of the command output and return it.
+# Requires that the command output not be empty (otherwise this will
+# emit a FAIL).
+proc list_lines { cmd args testname } {
+ if { $args ne "" } {
+ set args " $args"
+ }
+
+ set content ""
+ gdb_test_multiple "$cmd$args" $testname {
+ -re "$cmd$args\r\n" {
+ exp_continue
+ }
+
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $content ne "" } $gdb_test_name
+ }
+
+ -re "(^\[^\r\n\]*\r\n)" {
+ set content $content$expect_out(1,string)
+ exp_continue
+ }
+ }
+
+ return $content
+}
+
+# Run 'list' and compare the output to the output of 'py-list' or
+# 'py-list-unstyled', which are commands implemented in this tests
+# Python script file.
+#
+# CLI_ARGS is a string passed to 'list', and PY_ARGS is a string
+# passed to the two 'py-list*' commands. Passing the empty string to
+# the py-list* commands is a valid possibility, which is why the
+# default here is "NONE". If PY_ARGS is the default value then
+# CLI_ARGS is used for both.
+proc compare_list_cmds { cli_args { py_args "NONE" } } {
+ if { $py_args eq "NONE" } {
+ set py_args $cli_args
+ }
+
+ with_test_prefix "styling on" {
+ set content_cli_styled [list_lines list $cli_args \
+ "get lines from builtin list command"]
+
+ set content_py_styled [list_lines py-list $py_args \
+ "get lines from python list command"]
+
+ gdb_assert { $content_cli_styled == $content_py_styled } \
+ "cli and py commands gave same output"
+ }
+
+ gdb_test_no_output -nopass "set style enabled off"
+
+ with_test_prefix "styling off" {
+ set content_cli_unstyled [list_lines list $cli_args \
+ "get lines from builtin list command"]
+
+ set content_py_unstyled [list_lines py-list $py_args \
+ "get lines from python list command"]
+
+ gdb_assert { $content_cli_unstyled == $content_py_unstyled } \
+ "cli and py commands gave same output"
+ }
+
+ gdb_test_no_output -nopass "set style enabled on"
+
+ with_test_prefix "force unstyled" {
+ set content_py_unstyled [list_lines py-list-unstyled $py_args \
+ "get lines from python list command"]
+
+ gdb_assert { $content_cli_unstyled == $content_py_unstyled } \
+ "cli and py commands gave same output"
+ }
+}
+
+gdb_test_no_output -nopass "source $pyfile"
+
+foreach {first last} [list $lineno_a $lineno_b \
+ $lineno_a $lineno_c \
+ 1 $lineno_b \
+ 1 999] {
+
+ with_test_prefix "$first..$last" {
+ compare_list_cmds $first,$last
+ }
+}
+
+# This invokes the Python py-list command with no arguments. This
+# then exercises the Symtab.source_lines method with no arguments
+# being passed.
+compare_list_cmds 1,999 ""
+
+# Now some error checking.
+foreach_with_prefix cmd { py-list py-list-unstyled } {
+ gdb_test "$cmd -1,5" \
+ "Error occurred in Python: Invalid value -1 for first line number, minimum value is 1\\."
+ gdb_test "$cmd 10,5" \
+ "Error occurred in Python: First line 10 is after the last line 5\\."
+ gdb_test "$cmd 999,10" \
+ "Error occurred in Python: Line number 999 out of range, file has $lineno_c lines\\."
+ gdb_test "$cmd 10,-10" \
+ "Error occurred in Python: Invalid value -10 for last line number\\."
+}
+
+# Now test the case where the source file is missing. To achieve this
+# we first need to compile a new executable using a copy of the source
+# file.
+set srcfile2 [standard_output_file $testfile-copy.c]
+file copy ${srcdir}/${subdir}/${srcfile} $srcfile2
+
+set testfile2 ${testfile}-missing-src
+if {[build_executable "failed to build" $testfile2 $srcfile2]} {
+ return
+}
+
+file delete $srcfile2
+
+# Start with a fresh GDB, but enable color support.
+with_ansi_styling_terminal {
+ clean_restart $testfile2
+}
+
+if {![runto_main]} {
+ return
+}
+
+gdb_test_no_output -nopass "source $pyfile"
+
+foreach_with_prefix cmd { py-list py-list-unstyled } {
+ gdb_test "$cmd 5,10" \
+ "^Source file [string_to_regexp $srcfile2] not found\\."
+}
--- /dev/null
+# Copyright (C) 2026 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/>.
+
+import gdb
+
+
+class PythonListCommand(gdb.Command):
+ def __init__(self):
+ gdb.Command.__init__(self, "py-list", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ argv = gdb.string_to_argv(args)
+ assert len(argv) == 0 or len(argv) == 1
+
+ pc = gdb.parse_and_eval("$pc")
+ sal = gdb.current_progspace().find_pc_line(pc)
+ assert sal.symtab is not None
+
+ if len(argv) == 0:
+ source_lines = sal.symtab.source_lines()
+ start = 1
+ else:
+ parts = argv[0].split(",")
+ source_lines = sal.symtab.source_lines(
+ first=int(parts[0]), last=int(parts[1])
+ )
+ start = int(parts[0])
+ if source_lines is None:
+ print("Source file {} not found.".format(sal.symtab.fullname()))
+ else:
+ lns = gdb.Style("line-number")
+ for i, l in enumerate(source_lines, start=start):
+ print("%s\t%s" % (lns.apply(str(i)), l), end="")
+
+
+class PythonListUnstyledCommand(gdb.Command):
+ def __init__(self):
+ gdb.Command.__init__(self, "py-list-unstyled", gdb.COMMAND_USER)
+
+ def invoke(self, args, from_tty):
+ argv = gdb.string_to_argv(args)
+ assert len(argv) == 0 or len(argv) == 1
+
+ pc = gdb.parse_and_eval("$pc")
+ sal = gdb.current_progspace().find_pc_line(pc)
+ assert sal.symtab is not None
+
+ if len(argv) == 0:
+ source_lines = sal.symtab.source_lines(unstyled=True)
+ start = 1
+ else:
+ parts = argv[0].split(",")
+ source_lines = sal.symtab.source_lines(
+ first=int(parts[0]), last=int(parts[1]), unstyled=True
+ )
+ start = int(parts[0])
+ if source_lines is None:
+ print("Source file {} not found.".format(sal.symtab.fullname()))
+ else:
+ for i, l in enumerate(source_lines, start=start):
+ print("%d\t%s" % (i, l), end="")
+
+
+PythonListCommand()
+PythonListUnstyledCommand()