[binutils-gdb] gdb/python: allow Architecture.disassemble to give styled output
Andrew Burgess via Gdb-cvs <[email protected]>
| Newsgroups | gmane.comp.gdb.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=285072c3d5612f8eeaf3d621346ab4a2586c7bf0 commit 285072c3d5612f8eeaf3d621346ab4a2586c7bf0 Author: Andrew Burgess <[email protected]> Date: Fri Mar 20 15:05:16 2026 +0000 gdb/python: allow Architecture.disassemble to give styled output Extend the Architecture.disassemble API to allow the user to request styled disassembler output via a new styling argument. A user can now write: insn = arch.disassemble(address, styling = True) The instruction strings returned within INSN will contain ANSI escape sequences so long as 'set style enabled on' is in effect. This means that the user's personal settings (disabling styling) will override a GDB extension that requests styled disassembler output. I think this makes sense. The default for the styling argument is False, this maintains the current unstyled output as default. Reviewed-By: Eli Zaretskii <[email protected]> Approved-By: Tom Tromey <[email protected]> Diff: --- gdb/NEWS | 5 + gdb/doc/python.texi | 7 +- gdb/python/py-arch.c | 11 +- gdb/testsuite/gdb.python/py-arch-disasm-style.c | 52 ++++++++++ gdb/testsuite/gdb.python/py-arch-disasm-style.exp | 119 ++++++++++++++++++++++ 5 files changed, 189 insertions(+), 5 deletions(-) diff --git a/gdb/NEWS b/gdb/NEWS index e46a5108272..da74c248c54 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -229,6 +229,11 @@ qExecAndArgs the appropriate user setting is enabled, and GDB knows how to style this source file. + ** The Architecture.disassemble method accepts a new 'styling' + argument, which defaults to False. When set to True the 'asm' + strings in the disassembler output can contain ANSI escape + sequences to indicate styling. + * Guile API ** Procedures 'memory-port-read-buffer-size', diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 2df3b7c0423..d7718818def 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -7632,7 +7632,7 @@ A @code{gdb.Architecture} class has the following methods: Return the name (string value) of the architecture. @end defun -@defun Architecture.disassemble (start_pc @r{[}, end_pc @r{[}, count@r{]]}) +@defun Architecture.disassemble (start_pc @r{[}, end_pc @r{[}, count@r{]]} @w{@r{[}, styling = @code{False}@r{]}}) Return a list of disassembled instructions starting from the memory address @var{start_pc}. The optional arguments @var{end_pc} and @var{count} determine the number of instructions in the returned list. @@ -7661,6 +7661,11 @@ the instruction with assembly language mnemonics. The assembly language flavor used is the same as that specified by the current CLI variable @code{disassembly-flavor}. @xref{Machine Code}. +When the optional argument @var{styling} is @code{True} the @samp{asm} +string can contain ANSI terminal escape sequences if styling is +enabled (@pxref{Output Styling}). When @var{styling} is @code{False}, +which is the default, then no styling will be applied to @samp{asm}. + @item length The value corresponding to this key is the length (integer value) of the instruction in bytes. diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c index f40d7da1763..4031f925b04 100644 --- a/gdb/python/py-arch.c +++ b/gdb/python/py-arch.c @@ -124,18 +124,21 @@ archpy_name (PyObject *self, PyObject *args) static PyObject * archpy_disassemble (PyObject *self, PyObject *args, PyObject *kw) { - static const char *keywords[] = { "start_pc", "end_pc", "count", NULL }; + static const char *keywords[] = { + "start_pc", "end_pc", "count", "styling", nullptr + }; CORE_ADDR start = 0, end = 0; CORE_ADDR pc; long count = 0, i; PyObject *start_obj = nullptr, *end_obj = nullptr, *count_obj = nullptr; struct gdbarch *gdbarch = NULL; + int styling_p = 0; ARCHPY_REQUIRE_VALID (self, gdbarch); - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O|OO", + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O|OOp", keywords, &start_obj, &end_obj, - &count_obj)) + &count_obj, &styling_p)) return NULL; if (get_addr_from_python (start_obj, &start) < 0) @@ -190,7 +193,7 @@ archpy_disassemble (PyObject *self, PyObject *args, PyObject *kw) if (PyList_Append (result_list.get (), insn_dict.get ())) return NULL; /* PyList_Append Sets the exception. */ - string_file stb; + string_file stb (styling_p); try { diff --git a/gdb/testsuite/gdb.python/py-arch-disasm-style.c b/gdb/testsuite/gdb.python/py-arch-disasm-style.c new file mode 100644 index 00000000000..ec2ae965552 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-arch-disasm-style.c @@ -0,0 +1,52 @@ +/* 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/>. */ + +volatile int global_var = 0; + +/* This only exists so we can call it from main. */ + +void +worker_func (void) +{ + global_var = global_var + 2; + global_var = global_var + 2; + global_var = global_var + 2; + global_var = global_var + 2; +} + +/* It's all nonsense in this function. We just want 'main' to be more than + 10 instructions long. */ + +int +main (void) +{ + int i; + + global_var = global_var + 2; + + for (int i = 0; i < 10; ++i) + global_var = global_var + i; + + worker_func (); + + global_var = global_var + 2; + global_var = global_var + 2; + global_var = global_var + 2; + global_var = global_var + 2; + + return global_var; +} diff --git a/gdb/testsuite/gdb.python/py-arch-disasm-style.exp b/gdb/testsuite/gdb.python/py-arch-disasm-style.exp new file mode 100644 index 00000000000..2c38a62b80e --- /dev/null +++ b/gdb/testsuite/gdb.python/py-arch-disasm-style.exp @@ -0,0 +1,119 @@ +# 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/>. + +# Test styling for the Architecture.disassemble method. + +load_lib gdb-python.exp +require allow_python_tests + +standard_testfile + +if { [build_executable "failed to build" ${testfile} ${srcfile}] } { + return +} + +with_ansi_styling_terminal { + clean_restart $testfile +} + +if {![runto_main]} { + return +} + +# A new command 'py-disasm' which takes an address and disassembles 10 +# instructions starting from that address. The disassembly will +# include styling. +gdb_test_multiline "python disasm command" \ + "python" "" \ + "class py_disasm_cmd(gdb.Command):" "" \ + " def __init__(self):" "" \ + " super().__init__(\"py-disasm\", gdb.COMMAND_OBSCURE)" "" \ + " def invoke(self, args, from_tty):" "" \ + " argv = gdb.string_to_argv(args)" "" \ + " inf = gdb.selected_inferior ()" "" \ + " arch = inf.architecture ()" "" \ + " addr = gdb.parse_and_eval(argv\[0\])" "" \ + " insn = arch.disassemble(addr.address, count = 10, styling = True)" "" \ + " for i in insn:" "" \ + " formatted_addr = gdb.format_address(i\['addr'\])" "" \ + " print(\"0x%x: \t %s\" % (i\['addr'\], i\['asm'\]))" "" \ + "py_disasm_cmd()" "" \ + "end" "" + +# Run CMD which will disassemble 10 instructions. Capture those +# instructions into a list where each list item is itself a list of +# the form [ADDRESS, STRING], where STRING is the disassembled +# instruction. +# +# Returns the list of disassembled instructions, which should be 10 +# elements long if this worked. +# +# This proc emits a pass/fail on whether 10 instructions were captured +# using TESTNAME. +proc disassemble_and_gather_insn { cmd testname } { + set insn {} + gdb_test_multiple $cmd $testname { + -re "^[string_to_regexp $cmd]\r\n" { + exp_continue + } + + -re "^\[^:\r\n]*($::hex)\[^:\r\n\]*:\\s+(\[^\r\n\]+)\r\n" { + set addr $expect_out(1,string) + set asm $expect_out(2,string) + lappend insn [list $addr $asm] + exp_continue + } + + -re "^$::gdb_prompt $" { + gdb_assert {[llength $insn] == 10} $gdb_test_name + } + } + + return $insn +} + +# Capture the instructions, with styling, using GDB's CLI +# disassembler. +set expected_insn [disassemble_and_gather_insn "x/10i *main" \ + "disassemble some instructions"] + +# Now disassemble using our Python disassemble command. Capture these +# instructions. +set py_insn [disassemble_and_gather_insn "py-disasm *main" \ + "disassemble instructions via python"] + +# Check we got the same disassembled output, including styling, in +# both cases. +gdb_assert { $expected_insn eq $py_insn } \ + "instructions, with styling, are the same" + +# Disable styling. +gdb_test_no_output "set style enabled off" + +# Run the Python disassembled again. Now that styling is disabled +# there should be no escape sequences in the disassembler output. +set py_insn [disassemble_and_gather_insn "py-disasm *main" \ + "disassemble instructions via python, no styling"] + +# Check for escape sequences. There should be none. +set found_escape false +foreach insn $py_insn { + set asm [lindex $insn 1] + if {[string first "\033" $asm] != -1} { + set found_escape true + } +} +gdb_assert { !$found_escape } \ + "no escape sequences in unstyled disassembler output"