blob: 695ef1b19a709a84bd36e30b0ee8d46ac0d61fbb [file] [edit]
# GDB Python script to handle Cargo `<exe>.trim-paths.jsonl` files from the trim-paths feature
# - https://github.com/rust-lang/cargo/issues/12137
# - https://github.com/rust-lang/rust/issues/111540
import json
import os
import gdb
import sys
# https://doc.guix.gnu.org/gdb/16.3/en/html_node/Source-Path.html#index-set-substitute_002dpath
def _process_v1_trim_paths(lines, trim_paths_path):
for idx, line in enumerate(lines[2:], start=3):
try:
entry = json.loads(line)
if "from" in entry and "to" in entry:
cmd = f'set substitute-path "{entry["from"]}" "{entry["to"]}"'
gdb.execute(cmd)
except json.JSONDecodeError:
print(
f"(rust-gdb) warning: invalid JSON on line {idx} of {trim_paths_path}",
file=sys.stderr,
)
def _load_trim_paths(filepath):
trim_paths_path = f"{filepath}.trim-paths.jsonl"
# FIXME: It might be worth looking into the debuginfod fetch content if the local file
# doesn't exists (maybe with `debuginfod-find debuginfo`).
if not os.path.isfile(trim_paths_path):
return
try:
# Load all the lines of the trim-paths file
with open(trim_paths_path, "r", encoding="utf-8") as f:
lines = [line.strip() for line in f]
# Abort if we have less than 3 lines as that means that we cannot have any
# substitutions (header + metadata is already 2 lines)
if not lines or len(lines) < 3:
return
# Try loading the header line, which contains the version (v) field
try:
header = json.loads(lines[0])
ver = header["v"]
except json.JSONDecodeError:
print(
f"(rust-gdb) warning: header line 1 of {trim_paths_path} is not valid JSON",
file=sys.stderr,
)
return
# We only handle version 1
if ver == 1:
_process_v1_trim_paths(lines, trim_paths_path)
else:
print(
f"(rust-gdb) warning: unsupported trim-paths version {ver}: {trim_paths_path}",
file=sys.stderr,
)
except Exception as e:
print(
f"(rust-gdb) warning: failed to process trim-paths mappings: {e} of {trim_paths_path}",
file=sys.stderr,
)
def _on_objfile(objfile):
filepath = objfile.filename
if not filepath or not objfile.is_valid() or not os.path.isfile(filepath):
return
_load_trim_paths(filepath)
def _on_progspace(progspace):
filepath = progspace.filename
if not filepath or not os.path.isfile(filepath):
return
_load_trim_paths(filepath)
def _on_new_objfile(event):
_on_objfile(event.new_objfile)
def _on_executable_changed(event):
_on_progspace(event.progspace)
# Setup the events for new objfile (so) and new executable
# https://doc.guix.gnu.org/gdb/16.3/en/html_node/Events-In-Python.html
#
# FIXME: should we handle clear/free events?
gdb.events.new_objfile.connect(_on_new_objfile)
gdb.events.executable_changed.connect(_on_executable_changed)
# Load the trim-paths files for the already loaded objfiles
for objfile in gdb.objfiles():
_on_objfile(objfile)