From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>
Subject: [PATCH] gdb/python: allow Architecture.disassemble to give styled output
Date: Fri, 20 Mar 2026 15:31:11 +0000 [thread overview]
Message-ID: <2412723aff2f39ed778743b957d2ab8da51335e1.1774020654.git.aburgess@redhat.com> (raw)
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.
---
gdb/NEWS | 5 +
gdb/doc/python.texi | 7 +-
gdb/python/py-arch.c | 11 +-
.../gdb.python/py-arch-disasm-style.c | 52 ++++++++
.../gdb.python/py-arch-disasm-style.exp | 119 ++++++++++++++++++
5 files changed, 189 insertions(+), 5 deletions(-)
create mode 100644 gdb/testsuite/gdb.python/py-arch-disasm-style.c
create mode 100644 gdb/testsuite/gdb.python/py-arch-disasm-style.exp
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..e9ca0d67792 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -7632,7 +7632,7 @@ Architectures In Python
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 @@ Architectures In Python
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 @var{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 @var{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"
base-commit: e5425f2687d66034a8d3fe94264cf99b42c1cb1a
--
2.25.4
next reply other threads:[~2026-03-20 15:31 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-20 15:31 Andrew Burgess [this message]
2026-03-20 15:48 ` Eli Zaretskii
2026-03-23 10:17 ` Andrew Burgess
2026-03-20 17:54 ` Tom Tromey
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=2412723aff2f39ed778743b957d2ab8da51335e1.1774020654.git.aburgess@redhat.com \
--to=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox