import gdb
from .server import capability, request
+from .sources import make_source
+
+
+# This tracks labels associated with a disassembly request and helps
+# with updating individual instructions.
+class _BlockTracker:
+ def __init__(self):
+ # Map from PC to symbol names. A given PC is assumed to have
+ # just one label -- DAP wouldn't let us return multiple labels
+ # anyway.
+ self.labels = {}
+ # List of blocks that have already been handled. Note that
+ # blocks aren't hashable so a set is not used.
+ self.blocks = []
+
+ # Add a gdb.Block and its superblocks, ignoring the static and
+ # global block. BLOCK can also be None, which is ignored.
+ def add_block(self, block):
+ while block is not None:
+ if block.is_static or block.is_global or block in self.blocks:
+ return
+ self.blocks.append(block)
+ if block.function is not None:
+ self.labels[block.start] = block.function.name
+ for sym in block:
+ if sym.addr_class == gdb.SYMBOL_LOC_LABEL:
+ self.labels[int(sym.value())] = sym.name
+ block = block.superblock
+
+ # Add PC to this tracker. Update RESULT as appropriate with
+ # information about the source and any label.
+ def add_pc(self, pc, result):
+ self.add_block(gdb.block_for_pc(pc))
+ if pc in self.labels:
+ result["symbol"] = self.labels[pc]
+ sal = gdb.find_pc_line(pc)
+ if sal.symtab is not None:
+ if sal.line != 0:
+ result["line"] = sal.line
+ if sal.symtab.filename is not None:
+ # The spec says this can be omitted in some
+ # situations, but it's a little simpler to just always
+ # supply it.
+ result["location"] = make_source(sal.symtab.filename)
@request("disassemble")
except gdb.error:
# Maybe there was no frame.
arch = inf.architecture()
+ tracker = _BlockTracker()
result = []
total_count = instructionOffset + instructionCount
for elt in arch.disassemble(pc, count=total_count)[instructionOffset:]:
mem = inf.read_memory(elt["addr"], elt["length"])
- result.append(
- {
- "address": hex(elt["addr"]),
- "instruction": elt["asm"],
- "instructionBytes": mem.hex(),
- }
- )
+ insn = {
+ "address": hex(elt["addr"]),
+ "instruction": elt["asm"],
+ "instructionBytes": mem.hex(),
+ }
+ tracker.add_pc(elt["addr"], insn)
+ result.append(insn)
return {
"instructions": result,
}
--- /dev/null
+/* Copyright 2024 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+__attribute__((__noinline__)) static int
+callee (int x)
+{
+ return x * 2;
+}
+
+static inline int __attribute__((__always_inline__))
+compute (int x)
+{
+ return callee (x);
+}
+
+static int
+return_value (int x)
+{
+ int accum = 0;
+
+ for (int i = 0; i < x; ++i)
+ {
+ int value = compute (i);
+ if (value < 0)
+ goto out_label;
+ }
+
+ out_label:
+
+ return accum;
+}
+
+int
+main ()
+{
+ int value = return_value (23);
+ return value > 0 ? 0 : 1;
+}
--- /dev/null
+# Copyright 2024 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 DAP disassembly.
+
+require allow_dap_tests
+
+load_lib dap-support.exp
+
+standard_testfile
+
+if {[build_executable ${testfile}.exp $testfile] == -1} {
+ return
+}
+
+if {[dap_initialize] == ""} {
+ return
+}
+
+set obj [dap_check_request_and_response "set breakpoint" \
+ setFunctionBreakpoints \
+ {o breakpoints [a [o name [s main]]]}]
+set fn_bpno [dap_get_breakpoint_number $obj]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+if {[dap_launch $testfile] == ""} {
+ return
+}
+dap_wait_for_event_and_check "inferior started" thread "body reason" started
+
+dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
+ "body reason" breakpoint \
+ "body hitBreakpointIds" $fn_bpno
+
+# Find out how many lines of disassembly we should request. This is
+# kind of lame but DAP doesn't really provide tools to do this, and
+# gdb's DAP implementation doesn't try to figure out what memory might
+# not really be part of a function.
+set obj [dap_check_request_and_response "disassemble using CLI" \
+ evaluate {o expression [s {disassemble &return_value}] \
+ context [s repl]}]
+set output [dict get [lindex $obj 0] body result]
+# The result will have literal "\" "n" sequences, turn these into
+# newlines.
+set with_nl [string map [list "\\n" "\n"] $output]
+# The value we want is the number of lines starting with an address.
+set insn_count 0
+foreach line [split $with_nl "\n"] {
+ if {[regexp "^ *0x" $line]} {
+ incr insn_count
+ }
+}
+
+set obj [dap_check_request_and_response "find function address" \
+ evaluate {o expression [s "&return_value"]}]
+set pc [dict get [lindex $obj 0] body memoryReference]
+
+set obj [dap_check_request_and_response "disassemble the function" \
+ disassemble \
+ [format {o memoryReference [s %s] instructionCount [i %d]} \
+ $pc $insn_count]]
+set response [lindex $obj 0]
+
+set seen_labels(_) _
+set insn_no 1
+foreach insn [dict get $response body instructions] {
+ with_test_prefix $insn_no {
+ gdb_assert {[dict exists $insn line]} \
+ "line in disassemble output"
+ gdb_assert {[dict exists $insn location]} \
+ "location in disassemble output"
+ if {[dict exists $insn symbol]} {
+ set seen_labels([dict get $insn symbol]) 1
+ }
+ }
+ incr insn_no
+}
+
+proc require_label {name} {
+ global seen_labels
+ if {[info exists seen_labels($name)]} {
+ pass "saw label $name"
+ } else {
+ fail "saw label $name"
+ }
+}
+
+require_label return_value
+require_label compute
+require_label out_label
+
+dap_shutdown