mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
gdb/python: introduce gdb.Symtab.source_lines method
This commit adds a new method gdb.Symtab.source_lines. This method can be used to read the lines from a symtab's source file. This is similar to GDB's internal source_cache::get_source_lines function. Currently using the Python API, if a user wants to display source lines then they need to use Symtab.fullname() to get the source file name, then open this file and parse out the lines themselves. This isn't too much effort, but the problem is that these lines will not be styled. The user could style the source content themselves, but will this be styled exactly as GDB would style it? The new Symtab.source_lines() method returns source lines with styling included (as ANSI terminal escape sequences), assuming of course, that styling is currently enabled. Of course, in some cases, a user of the Python API might want source code without styling. That's supported too, the new method has an 'unstyled' argument. If this is True then the output is forced to be unstyled. The argument is named 'unstyled' rather than 'styled' because the API call cannot force styling on. If 'set style enabled off' is in effect then making the API call will never return styled source lines. The new API call allows for a range of lines to be requested if desired. As part of this commit I've updated the host_string_to_python_string utility function to take a std::string_view. Reviewed-By: Eli Zaretskii <eliz@gnu.org> Approved-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
parent
8012cfca41
commit
030f56e0e8
8 changed files with 454 additions and 5 deletions
10
gdb/NEWS
10
gdb/NEWS
|
|
@ -207,6 +207,16 @@ qExecAndArgs
|
|||
'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',
|
||||
|
|
|
|||
|
|
@ -6817,6 +6817,32 @@ Return the line table associated with the symbol table.
|
|||
@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
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
#include "python-internal.h"
|
||||
#include "objfiles.h"
|
||||
#include "block.h"
|
||||
#include "source-cache.h"
|
||||
#include "cli/cli-style.h"
|
||||
|
||||
struct symtab_object : public PyObject
|
||||
{
|
||||
|
|
@ -223,6 +225,113 @@ stpy_get_linetable (PyObject *self, PyObject *args)
|
|||
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)
|
||||
{
|
||||
|
|
@ -467,6 +576,11 @@ Return the static block of the symbol table." },
|
|||
{ "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 */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -147,13 +147,13 @@ python_string_to_host_string (PyObject *obj)
|
|||
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
|
||||
|
|
|
|||
|
|
@ -939,7 +939,11 @@ gdb::unique_xmalloc_ptr<char> unicode_to_target_string (PyObject *unicode_str);
|
|||
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);
|
||||
|
||||
|
|
|
|||
42
gdb/testsuite/gdb.python/py-symtab-source-lines.c
Normal file
42
gdb/testsuite/gdb.python/py-symtab-source-lines.c
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* 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. */
|
||||
176
gdb/testsuite/gdb.python/py-symtab-source-lines.exp
Normal file
176
gdb/testsuite/gdb.python/py-symtab-source-lines.exp
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
# 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\\."
|
||||
}
|
||||
77
gdb/testsuite/gdb.python/py-symtab-source-lines.py
Normal file
77
gdb/testsuite/gdb.python/py-symtab-source-lines.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue