From: Tom de Vries <tdevries@suse.de>
To: gdb-patches@sourceware.org
Subject: [PATCH v4 09/11] [gdb] Handle sections when disassembling
Date: Wed, 7 Oct 2026 11:40:55 +0200 [thread overview]
Message-ID: <20261007094057.1811886-10-tdevries@suse.de> (raw)
In-Reply-To: <20261007094057.1811886-1-tdevries@suse.de>
Consider the following session:
...
$ gdb -q -batch a.out -ex start -ex "disassemble _init,0x0000000000401030"
...
Dump of assembler code from 0x401000 to 0x401030:
...
0x000000000040101a <_init+26>: ret
0x000000000040101b: add %al,(%rax)
0x000000000040101d: add %al,(%rax)
0x000000000040101f: add %bh,%bh
0x0000000000401021: xor $0x2fca,%eax
0x0000000000401026: jmp *0x2fcc(%rip)
0x000000000040102c: nopl 0x0(%rax)
End of assembler dump.
...
If disassemble with objdump instead, we get:
...
Disassembly of section .init:
0000000000401000 <_init>:
...
40101a: c3 ret
Disassembly of section .plt:
0000000000401020 <puts@plt-0x10>:
401020: ff 35 ca 2f 00 00 push 0x2fca(%rip)
401026: ff 25 cc 2f 00 00 jmp *0x2fcc(%rip)
40102c: 0f 1f 40 00 nopl 0x0(%rax)
...
Note the xor vs push.
So gdb:
- first disassembles the .init section
- then disassembles the section hole in between .init and .plt
- interprets the first byte of .plt as the last byte of an instruction
starting in the section hole
- continues disassembling at .plt+1 instead of .plt+0
Fix this in gdb_disassembly by making it section-aware, and not disassembling
across a section change, getting us instead:
...
0x000000000040101a <_init+26>: ret
0x000000000040101b: add %al,(%rax)
0x000000000040101d: add %al,(%rax)
0x000000000040101f: add %bh,%bh
0x0000000000401020: push 0x2fca(%rip) # 0x403ff0
0x0000000000401026: jmp *0x2fcc(%rip) # 0x403ff8
0x000000000040102c: nopl 0x0(%rax)
...
Likewise in tui_disassemble.
Also add a test-case for the fix in gdb_disassembly:
gdb.base/disasm-section-sync.exp.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34617
---
gdb/disasm.c | 50 +++++++++-
gdb/testsuite/gdb.base/disasm-section-sync.S | 47 +++++++++
.../gdb.base/disasm-section-sync.exp | 28 ++++++
gdb/tui/tui-disasm.c | 99 ++++++++++++++-----
4 files changed, 195 insertions(+), 29 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/disasm-section-sync.S
create mode 100644 gdb/testsuite/gdb.base/disasm-section-sync.exp
diff --git a/gdb/disasm.c b/gdb/disasm.c
index ec922fbd323..23d0c880777 100644
--- a/gdb/disasm.c
+++ b/gdb/disasm.c
@@ -34,6 +34,7 @@
#include "cli/cli-style.h"
#include "objfiles.h"
#include "inferior.h"
+#include "addrmap.h"
/* Disassemble functions.
FIXME: We should get rid of all the duplicate code in gdb that does
@@ -1145,12 +1146,12 @@ gdb_disassembler::print_insn (CORE_ADDR memaddr,
return length;
}
-/* See disasm.h. */
+/* Helper function for gdb_disassembly. */
-int
-gdb_disassembly (struct gdbarch *gdbarch, struct ui_out *uiout,
- gdb_disassembly_flags flags, int how_many,
- CORE_ADDR low, CORE_ADDR high)
+static int
+gdb_disassembly_1 (struct gdbarch *gdbarch, struct ui_out *uiout,
+ gdb_disassembly_flags flags, int how_many,
+ CORE_ADDR low, CORE_ADDR high)
{
struct symtab *symtab;
int nlines = -1;
@@ -1182,6 +1183,45 @@ gdb_disassembly (struct gdbarch *gdbarch, struct ui_out *uiout,
return num_displayed;
}
+/* See disasm.h. */
+
+int
+gdb_disassembly (struct gdbarch *gdbarch, struct ui_out *uiout,
+ gdb_disassembly_flags flags, int how_many,
+ CORE_ADDR low, CORE_ADDR high)
+{
+ std::unique_ptr<addrmap_mutable> map = section_addrmap ();
+ bool update_how_many = how_many != -1;
+ int num_displayed = 0;
+ while (low < high)
+ {
+ CORE_ADDR tmp_high = high;
+
+ CORE_ADDR range_high;
+ map->find (low, nullptr, &range_high);
+
+ /* Don't disassemble past a section change. */
+ if (range_high != (CORE_ADDR)-1)
+ tmp_high = std::min (tmp_high, range_high + 1);
+
+ int res
+ = gdb_disassembly_1 (gdbarch, uiout, flags, how_many, low,
+ tmp_high);
+ num_displayed += res;
+
+ if (update_how_many)
+ {
+ how_many -= res;
+ if (how_many <= 0)
+ break;
+ }
+
+ low = tmp_high;
+ }
+
+ return num_displayed;
+}
+
/* Print the instruction at address MEMADDR in debugged memory,
on STREAM. Returns the length of the instruction, in bytes,
and, if requested, the number of branch delay slot instructions. */
diff --git a/gdb/testsuite/gdb.base/disasm-section-sync.S b/gdb/testsuite/gdb.base/disasm-section-sync.S
new file mode 100644
index 00000000000..06949c4b444
--- /dev/null
+++ b/gdb/testsuite/gdb.base/disasm-section-sync.S
@@ -0,0 +1,47 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ 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/>. */
+
+ .section .text_1,"ax",@progbits
+ .globl foo_1
+ .type foo_1, @function
+foo_1:
+ endbr64
+ sub $0x8,%rsp
+ mov 0x2fd1(%rip),%rax
+ test %rax,%rax
+ je 401016
+ call *%rax
+ add $0x8,%rsp
+ ret
+
+ # Get instruction decoding out of sync.
+ .byte 0
+
+ .section .text_2,"ax",@progbits
+ .globl foo_2
+ .type foo_2, @function
+foo_2:
+ push 0x2fca(%rip)
+ jmp *0x2fcc(%rip)
+ nopl 0x0(%rax)
+
+ .globl foo_3
+ .type foo_3, @function
+foo_3:
+ nop
+
+ .section .note.GNU-stack,"",@progbits
diff --git a/gdb/testsuite/gdb.base/disasm-section-sync.exp b/gdb/testsuite/gdb.base/disasm-section-sync.exp
new file mode 100644
index 00000000000..2f8b01cae52
--- /dev/null
+++ b/gdb/testsuite/gdb.base/disasm-section-sync.exp
@@ -0,0 +1,28 @@
+# 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/>.
+
+# Check that disassembling stops and starts at section boundaries.
+
+require is_x86_64_m64_target
+
+standard_testfile .S main.c
+
+if { [prepare_for_testing "failed to prepare" $testfile \
+ [list $srcfile $srcfile2] {nodebug}] } {
+ return
+}
+
+# Regression test for PR34617.
+gdb_test "disassemble foo_1,foo_3" "push.*"
diff --git a/gdb/tui/tui-disasm.c b/gdb/tui/tui-disasm.c
index c242614be45..ba11b8e8188 100644
--- a/gdb/tui/tui-disasm.c
+++ b/gdb/tui/tui-disasm.c
@@ -38,6 +38,7 @@
#include "tui/tui-location.h"
#include "gdbsupport/selftest.h"
#include "inferior.h"
+#include "addrmap.h"
struct tui_asm_line
{
@@ -80,23 +81,13 @@ len_without_escapes (const std::string &str)
return len;
}
-/* Function to disassemble up to COUNT instructions starting from address
- PC into the ASM_LINES vector (which will be emptied of any previous
- contents). Return the address after the last disassembled instruction.
- When ADDR_SIZE is non-null then place the maximum size of an address and
- label into the value pointed to by ADDR_SIZE, and set the addr_size
- field on each item in ASM_LINES, otherwise the addr_size fields within
- ASM_LINES are undefined.
+/* Helper function for tui_disassemble. */
- It is worth noting that ASM_LINES might not have COUNT entries when this
- function returns. If the disassembly is truncated for some other
- reason, for example, we hit invalid memory, then ASM_LINES can have
- fewer entries than requested. */
static CORE_ADDR
-tui_disassemble (struct gdbarch *gdbarch,
- std::vector<tui_asm_line> &asm_lines,
- CORE_ADDR pc, int count,
- size_t *addr_size = nullptr)
+tui_disassemble_1 (struct gdbarch *gdbarch,
+ std::vector<tui_asm_line> &asm_lines, CORE_ADDR pc,
+ std::optional<CORE_ADDR> high_pc, int count,
+ size_t *addr_size)
{
bool term_out = disassembler_styling && gdb_stdout->can_emit_style_escape ();
string_file gdb_dis_out (term_out);
@@ -104,20 +95,29 @@ tui_disassemble (struct gdbarch *gdbarch,
? (decltype (stream))&null_stream
: (decltype (stream))&gdb_dis_out);
- /* Must start with an empty list. */
- asm_lines.clear ();
-
/* Now construct each line. */
for (int i = 0; i < count; ++i)
{
tui_asm_line tal;
+ if (high_pc.has_value () && pc >= *high_pc)
+ break;
+
/* Save the instruction address. */
tal.addr = pc;
try
{
- pc += gdb_print_insn (gdbarch, pc, stream, NULL);
+ int len = gdb_print_insn (gdbarch, pc, stream, NULL);
+ CORE_ADDR next_pc = pc + len;
+ if (high_pc.has_value () && next_pc > *high_pc)
+ {
+ /* Instruction spans high_pc. */
+ gdb_dis_out = "(bad)";
+ next_pc = *high_pc;
+ }
+
+ pc = next_pc;
}
catch (const gdb_exception_error &except)
{
@@ -153,6 +153,53 @@ tui_disassemble (struct gdbarch *gdbarch,
return pc;
}
+/* Function to disassemble up to COUNT instructions starting from address
+ PC into the ASM_LINES vector (which will be emptied of any previous
+ contents). Return the address after the last disassembled instruction.
+ When ADDR_SIZE is non-null then place the maximum size of an address and
+ label into the value pointed to by ADDR_SIZE, and set the addr_size
+ field on each item in ASM_LINES, otherwise the addr_size fields within
+ ASM_LINES are undefined.
+
+ MAP contains a map from adressess to sections and corresponding ranges.
+
+ It is worth noting that ASM_LINES might not have COUNT entries when this
+ function returns. If the disassembly is truncated for some other
+ reason, for example, we hit invalid memory, then ASM_LINES can have
+ fewer entries than requested. */
+
+static CORE_ADDR
+tui_disassemble (struct gdbarch *gdbarch,
+ std::vector<tui_asm_line> &asm_lines,
+ addrmap_mutable &map, CORE_ADDR pc, int count,
+ size_t *addr_size = nullptr)
+{
+ /* Must start with an empty list. */
+ asm_lines.clear ();
+
+ while (count > 0)
+ {
+ CORE_ADDR range_high;
+ map.find (pc, nullptr, &range_high);
+
+ /* Don't disassemble past a section change. */
+ std::optional<CORE_ADDR> high_pc;
+ if (range_high != (CORE_ADDR)-1)
+ high_pc = range_high + 1;
+
+ int prev_nr_lines = asm_lines.size ();
+ pc = tui_disassemble_1 (gdbarch, asm_lines, pc, high_pc, count,
+ addr_size);
+ int nr_lines = asm_lines.size () - prev_nr_lines;
+ if (nr_lines == 0)
+ break;
+
+ count -= nr_lines;
+ }
+
+ return pc;
+}
+
/* Look backward from ADDR for an address from which we can start
disassembling, this needs to be something we can be reasonably
confident will fall on an instruction boundary. We use msymbol
@@ -197,13 +244,14 @@ tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
return pc;
std::vector<tui_asm_line> asm_lines;
+ std::unique_ptr<addrmap_mutable> map = section_addrmap ();
if (from > 0)
{
/* Always disassemble 1 extra instruction here, then if the last
instruction fails to disassemble we will take the address of the
previous instruction that did disassemble as the result. */
- tui_disassemble (gdbarch, asm_lines, pc, max_lines + 1);
+ tui_disassemble (gdbarch, asm_lines, *map, pc, max_lines + 1);
if (asm_lines.empty ())
return pc;
return asm_lines.back ().addr;
@@ -249,7 +297,8 @@ tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
break;
/* Disassemble forward. */
- next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines);
+ next_addr
+ = tui_disassemble (gdbarch, asm_lines, *map, new_low, max_lines);
if (asm_lines.empty ())
{
new_low = prev_low;
@@ -280,7 +329,8 @@ tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
/* Take the best possible match we have. */
new_low = *possible_new_low;
- next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines);
+ next_addr
+ = tui_disassemble (gdbarch, asm_lines, *map, new_low, max_lines);
}
/* The following walk forward assumes that ASM_LINES contains exactly
@@ -303,7 +353,7 @@ tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
CORE_ADDR old_next_addr = next_addr;
std::vector<tui_asm_line> single_asm_line;
next_addr = tui_disassemble (gdbarch, single_asm_line,
- next_addr, 1);
+ *map, next_addr, 1);
/* If there are some problems while disassembling exit. */
if (next_addr <= old_next_addr)
return pc;
@@ -347,8 +397,9 @@ tui_disasm_window::set_contents (struct gdbarch *arch,
/* Get temporary table that will hold all strings (addr & insn). */
std::vector<tui_asm_line> asm_lines;
+ std::unique_ptr<addrmap_mutable> map = section_addrmap ();
size_t addr_size = 0;
- tui_disassemble (m_gdbarch, asm_lines, pc, max_lines, &addr_size);
+ tui_disassemble (m_gdbarch, asm_lines, *map, pc, max_lines, &addr_size);
/* Align instructions to the same column. */
insn_pos = (1 + (addr_size / tab_len)) * tab_len;
--
2.51.0
next prev parent reply other threads:[~2026-10-07 9:44 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-07 9:40 [PATCH v4 00/11] [gdb/tui] Some section hole handling fixes Tom de Vries
2026-10-07 9:40 ` [PATCH v4 01/11] [gdb/tui] Use early exit in tui_find_disassembly_address Tom de Vries
2026-10-07 9:40 ` [PATCH v4 02/11] [gdb/tui] Make low_prev loop-local " Tom de Vries
2026-10-07 9:40 ` [PATCH v4 03/11] [gdb/tui] Disassemble less often " Tom de Vries
2026-10-07 9:40 ` [PATCH v4 04/11] [gdb/tui] Fix buglet " Tom de Vries
2026-10-07 9:40 ` [PATCH v4 05/11] [gdb] Factor out update_section_map Tom de Vries
2026-10-07 9:40 ` [PATCH v4 06/11] [gdb] Add section_addrmap Tom de Vries
2026-10-07 9:40 ` [PATCH v4 07/11] [gdb] Extend addrmap::find with low/high results Tom de Vries
2026-10-07 9:40 ` [PATCH v4 08/11] [gdb] Make gdb_disassembly return number of insns Tom de Vries
2026-10-07 9:40 ` Tom de Vries [this message]
2026-10-07 9:40 ` [PATCH v4 10/11] [gdb] Don't stop disassembly at section hole Tom de Vries
2026-10-07 9:40 ` [PATCH v4 11/11] [RFC, gdb/tui] Don't scroll into non-code sections Tom de Vries
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=20261007094057.1811886-10-tdevries@suse.de \
--to=tdevries@suse.de \
--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