Skip to content

Add gdb-repr directive for tests/debuginfo - #160377

Draft
Walnut356 wants to merge 2 commits into
rust-lang:mainfrom
Walnut356:gdb_di_repr
Draft

Add gdb-repr directive for tests/debuginfo#160377
Walnut356 wants to merge 2 commits into
rust-lang:mainfrom
Walnut356:gdb_di_repr

Conversation

@Walnut356

Copy link
Copy Markdown
Contributor

Part of #148483 and followup to #158298. Applies (approximately) identical logic to GDB.

I still need to test things on windows-gnu (and locally try a few more tests to see if there's any glaring issues). There's also a few bike-sheddy things.

The main way this differs from the LLDB implementation is that GDB doesn't use lldb_batchmode/runner.py to orchestrate the commands. Instead, I have compiletest import from_gdb.py which registers a custom repr CLI command. GDB's batch processing works as normal, but dispatches to our logic automatically whenever it encounters repr <var_name>.

An additional repr-finalize CLI command is also registered to implement the checks that ensure we encountered all expected types/vars, and verifies that there were no errors before saving blessed data.

r? @Kobzol, @jieyouxu

@rustbot

rustbot commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Kobzol is not on the review rotation at the moment.
They may take a while to respond.

@rustbot rustbot added A-compiletest Area: The compiletest test runner A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 2, 2026
Comment thread src/etc/lldb_batchmode/check_gdb.py Outdated
Comment on lines +35 to +76
class ReprCommand(gdb.Command):
def __init__(self):
super().__init__("repr", gdb.COMMAND_OBSCURE)

def invoke(self, argument: str, from_tty: bool):
print(f"(gdb) repr {argument}")

global REPR_COMMAND_RUN
REPR_COMMAND_RUN = True
try:
if check(argument) == Result.Mismatch:
global REPR_ERROR
REPR_ERROR = True
except Exception as e:
import sys
import traceback

traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)
gdb.execute("exit 1")


ReprCommand()


class ReprFinalize(gdb.Command):
def __init__(self):
super().__init__("repr_finalize", gdb.COMMAND_OBSCURE)

def invoke(self, argument: str, from_tty: bool):
if not REPR_COMMAND_RUN:
return

if not tested_all_variables() or not tested_all_types():
gdb.execute("exit 1")

if BLESS and not REPR_ERROR:
gdb_version = gdb.execute("show version", to_string=True).splitlines()[0]
metadata = BlessMetadata(sys.version, gdb_version)
INPUT_DATA.save_blessing(metadata)


ReprFinalize()

@Walnut356 Walnut356 Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's where we create and register the custom CLI commands.

View changes since the review

Comment on lines +519 to +579
TYPES_TESTED: dict[str, Result] = {}
"""Since types are unique and unchanging, we only need to test each type once. This also helps
ensure we have tested all types in `INPUT_DATA`
"""


VARS_TESTED: list[dict[str, Result]] = []
"""Used to help ensure all expected variables were tested. Each element of the list corresponds to a
breakpoint, and contains a set of all of the variable names tested for that breakpoint."""


def tested_all_types() -> bool:
"""Returns true if all types in INPUT_DATA were tested this run."""

expected_types = set(INPUT_DATA.types)
untested_types = expected_types.difference(TYPES_TESTED.keys())

if len(untested_types) != 0:
print(
f"{ANSI_RED}[repr error]{ANSI_END} The following types were expected, but were not \
tested:\n {untested_types}"
)

return len(untested_types) == 0


def tested_all_variables() -> bool:
expected_vars = [set(vars) for vars in INPUT_DATA.breakpoints]
untested_vars = [
expected.difference(tested.keys())
for expected, tested in zip(expected_vars, VARS_TESTED)
]

tested_not_expected = [
set(tested.keys()).difference(expected)
for expected, tested in zip(expected_vars, VARS_TESTED)
]

result = True

for i, v in enumerate(untested_vars):
if len(v) == 0:
continue

result = False
print(
f"{ANSI_RED}[repr error]{ANSI_END} The following variables were expected at \
breakpoint#{i}, but were not tested:\n {v}"
)

for i, v in enumerate(tested_not_expected):
if len(v) == 0:
continue

result = False
print(
f"{ANSI_RED}[repr error]{ANSI_END} The following variables were tested, but do not \
exist in the input data at breakpoint#{i}:\n {v}"
)

return result

@Walnut356 Walnut356 Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were just moved from check_lldb.py.

I might be able to factor more of the checking logic out and share it between lldb and gdb. There are less differences for their API than I was expecting. It would probably require passing a few wrappers to the check function that abstract away the debugger-specific accesses, but we'll see.

View changes since the review

gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
gdb.args(debugger_opts)
.env("PYTHONPATH", pythonpath)
.env("BATCHMODE_DEBUGGER", "gdb")

@Walnut356 Walnut356 Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bike-shedding I mentioned more or less has to do with these env vars and the lldb_batchmode package. With GDB support, we'll probably want to re-name lldb_batchmode (and probably rename runner.py to lldb_batchmode.py), but I'm not super happy with any of the names that came to mind. Maybe just batchmode?

View changes since the review

@rust-log-analyzer

This comment has been minimized.

@Walnut356

Copy link
Copy Markdown
Contributor Author

Oh yeah, i need to gate it so it doesn't check for the file path until a repr command has been run

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-compiletest Area: The compiletest test runner A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants