From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-il1-x12d.google.com (mail-il1-x12d.google.com [IPv6:2607:f8b0:4864:20::12d]) by sourceware.org (Postfix) with ESMTPS id 5248E385AFA8 for ; Tue, 25 Jul 2023 17:11:16 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5248E385AFA8 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=adacore.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=adacore.com Received: by mail-il1-x12d.google.com with SMTP id e9e14a558f8ab-345f1e0abf9so25631115ab.3 for ; Tue, 25 Jul 2023 10:11:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=adacore.com; s=google; t=1690305075; x=1690909875; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=2dV0SMGpBQkceb5veE8wB2fC6x5SRca1JwsgXMCvWi8=; b=c99GzwCbf2O9waaNGr2d0AMpCED7sRJjzxK8es+fP4HGcNNs7B1EziEHH/jwcAgH7K bCpeMRfgaGW0d9cNDt0kX70s7A4t3r0Krb52I2HwJfFQcySKcYYqKkzlSLYQKrfzfJd7 7+umEc+q8wBwto7vuvvlGkg8lu80C0HEkbYGDDFjYY5n3n9JSrTOyY6mTZ5Lyvaqxxqe GAGJWciPEBicofL345MBrSVNY1RiKE2b4rhWxUQzqv0nRU8Luk0lPvDVm3oAxHh2g3KZ UU1c6MB/htdXXiBWaKPWh7I3vwDH1OiRmq9D83pBvdNHispplgJj5zgwAJ+VIXK+ndZZ K7RQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1690305075; x=1690909875; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=2dV0SMGpBQkceb5veE8wB2fC6x5SRca1JwsgXMCvWi8=; b=c4taK8b6EXx0kaMz8111IFY+UOremspBMOmelVqOo8OfSWs1rVs0ilXXYJjOCRhQw2 Pzee5UqbQRQQrXWQ+hoX3nm5WOGHNP60mWP34eJ2O9YE5HkV4xLnfoIEcPbhdBBbm9et N8UW2QJu51wBL+W7h+LSi4SHm+bNqNcAah7Kp275QZfxvJ96Z7RyFZbd0vkhbkxpCHY/ XvSK3IAhLsEEaFLteKBsnnP5mCC8RXB8qF9qLFt045JnVJ17d8ytDnw6Eywwe0PVZSLE D6JsCN+UwrPN2rtpQ4Nz4T2sI8pDDPPMBlwl4cgiKu4mB1g+e+n+bysyJy/+PG/Yi3+Q Fphg== X-Gm-Message-State: ABy/qLZuKBkuGnfv4RGzwYMGmkI7YMRcV7WQzZtww81LsTuv0lyVesY8 uKqw32ucSdtxQEmbzOXXKZBqtGn2oaUzfW4VufyhVQ== X-Google-Smtp-Source: APBJJlHCicOSYrqk0YO8/RdMKiLiuL+vzkvafj0ttNcxx28Vol+u12BsnYtGdwmYhPGYbcBnjjErBg== X-Received: by 2002:a05:6e02:ed1:b0:347:7399:e9c1 with SMTP id i17-20020a056e020ed100b003477399e9c1mr2813119ilk.14.1690305075306; Tue, 25 Jul 2023 10:11:15 -0700 (PDT) Received: from localhost.localdomain (75-166-135-140.hlrn.qwest.net. [75.166.135.140]) by smtp.gmail.com with ESMTPSA id h9-20020a02c4c9000000b0042b2e309f97sm3647220jaj.177.2023.07.25.10.11.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 25 Jul 2023 10:11:14 -0700 (PDT) From: Tom Tromey To: gdb-patches@sourceware.org Cc: Tom Tromey Subject: [PATCH] Implement ValueFormat for DAP Date: Tue, 25 Jul 2023 11:11:05 -0600 Message-Id: <20230725171105.1300205-1-tromey@adacore.com> X-Mailer: git-send-email 2.40.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-11.6 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: This patch implements ValueFormat for DAP. Currently this only means supporting "hex". Note that StackFrameFormat is defined to have many more options, but none are currently recognized. It isn't entirely clear how these should be handled. I'll file a new gdb bug for this, and perhaps an upstream DAP bug as well. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30469 --- gdb/python/lib/gdb/dap/bt.py | 102 +++++++++++++++-------------- gdb/python/lib/gdb/dap/evaluate.py | 79 ++++++++++++---------- gdb/python/lib/gdb/dap/varref.py | 18 +++++ gdb/testsuite/gdb.dap/hover.exp | 5 ++ 4 files changed, 123 insertions(+), 81 deletions(-) diff --git a/gdb/python/lib/gdb/dap/bt.py b/gdb/python/lib/gdb/dap/bt.py index 975c88f8208..4d1d89a81c3 100644 --- a/gdb/python/lib/gdb/dap/bt.py +++ b/gdb/python/lib/gdb/dap/bt.py @@ -22,60 +22,66 @@ from .modules import module_id from .server import request, capability from .startup import send_gdb_with_response, in_gdb_thread from .state import set_thread +from .varref import apply_format # Helper function to compute a stack trace. @in_gdb_thread -def _backtrace(thread_id, levels, startFrame): - set_thread(thread_id) - frames = [] - if levels == 0: - # Zero means all remaining frames. - high = -1 - else: - # frame_iterator uses an inclusive range, so subtract one. - high = startFrame + levels - 1 - try: - frame_iter = frame_iterator(gdb.newest_frame(), startFrame, high) - except gdb.error: - frame_iter = () - for current_frame in frame_iter: - pc = current_frame.address() - newframe = { - "id": frame_id(current_frame), - "name": current_frame.function(), - # This must always be supplied, but we will set it - # correctly later if that is possible. - "line": 0, - # GDB doesn't support columns. - "column": 0, - "instructionPointerReference": hex(pc), - } - objfile = gdb.current_progspace().objfile_for_address(pc) - if objfile is not None: - newframe["moduleId"] = module_id(objfile) - line = current_frame.line() - if line is not None: - newframe["line"] = line - filename = current_frame.filename() - if filename is not None: - newframe["source"] = { - "name": os.path.basename(filename), - "path": filename, - # We probably don't need this but it doesn't hurt - # to be explicit. - "sourceReference": 0, +def _backtrace(thread_id, levels, startFrame, value_format): + with apply_format(value_format): + set_thread(thread_id) + frames = [] + if levels == 0: + # Zero means all remaining frames. + high = -1 + else: + # frame_iterator uses an inclusive range, so subtract one. + high = startFrame + levels - 1 + try: + frame_iter = frame_iterator(gdb.newest_frame(), startFrame, high) + except gdb.error: + frame_iter = () + for current_frame in frame_iter: + pc = current_frame.address() + newframe = { + "id": frame_id(current_frame), + "name": current_frame.function(), + # This must always be supplied, but we will set it + # correctly later if that is possible. + "line": 0, + # GDB doesn't support columns. + "column": 0, + "instructionPointerReference": hex(pc), } - frames.append(newframe) - # Note that we do not calculate totalFrames here. Its absence - # tells the client that it may simply ask for frames until a - # response yields fewer frames than requested. - return { - "stackFrames": frames, - } + objfile = gdb.current_progspace().objfile_for_address(pc) + if objfile is not None: + newframe["moduleId"] = module_id(objfile) + line = current_frame.line() + if line is not None: + newframe["line"] = line + filename = current_frame.filename() + if filename is not None: + newframe["source"] = { + "name": os.path.basename(filename), + "path": filename, + # We probably don't need this but it doesn't hurt + # to be explicit. + "sourceReference": 0, + } + frames.append(newframe) + # Note that we do not calculate totalFrames here. Its absence + # tells the client that it may simply ask for frames until a + # response yields fewer frames than requested. + return { + "stackFrames": frames, + } @request("stackTrace") @capability("supportsDelayedStackTraceLoading") -def stacktrace(*, levels: int = 0, startFrame: int = 0, threadId: int, **extra): - return send_gdb_with_response(lambda: _backtrace(threadId, levels, startFrame)) +def stacktrace( + *, levels: int = 0, startFrame: int = 0, threadId: int, format=None, **extra +): + return send_gdb_with_response( + lambda: _backtrace(threadId, levels, startFrame, format) + ) diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py index 63e80331b24..6c920577f32 100644 --- a/gdb/python/lib/gdb/dap/evaluate.py +++ b/gdb/python/lib/gdb/dap/evaluate.py @@ -21,7 +21,7 @@ from typing import Optional from .frames import select_frame from .server import capability, request, client_bool_capability from .startup import send_gdb_with_response, in_gdb_thread -from .varref import find_variable, VariableReference +from .varref import find_variable, VariableReference, apply_format class EvaluateResult(VariableReference): @@ -31,24 +31,26 @@ class EvaluateResult(VariableReference): # Helper function to evaluate an expression in a certain frame. @in_gdb_thread -def _evaluate(expr, frame_id): - global_context = True - if frame_id is not None: - select_frame(frame_id) - global_context = False - val = gdb.parse_and_eval(expr, global_context=global_context) - ref = EvaluateResult(val) - return ref.to_object() +def _evaluate(expr, frame_id, value_format): + with apply_format(value_format): + global_context = True + if frame_id is not None: + select_frame(frame_id) + global_context = False + val = gdb.parse_and_eval(expr, global_context=global_context) + ref = EvaluateResult(val) + return ref.to_object() # Like _evaluate but ensure that the expression cannot cause side # effects. @in_gdb_thread -def _eval_for_hover(expr, frame_id): - with gdb.with_parameter("may-write-registers", "off"): - with gdb.with_parameter("may-write-memory", "off"): - with gdb.with_parameter("may-call-functions", "off"): - return _evaluate(expr, frame_id) +def _eval_for_hover(expr, frame_id, value_format): + with apply_format(value_format): + with gdb.with_parameter("may-write-registers", "off"): + with gdb.with_parameter("may-write-memory", "off"): + with gdb.with_parameter("may-call-functions", "off"): + return _evaluate(expr, frame_id) class _SetResult(VariableReference): @@ -65,15 +67,16 @@ class _SetResult(VariableReference): # Helper function to perform an assignment. @in_gdb_thread -def _set_expression(expression, value, frame_id): - global_context = True - if frame_id is not None: - select_frame(frame_id) - global_context = False - lhs = gdb.parse_and_eval(expression, global_context=global_context) - rhs = gdb.parse_and_eval(value, global_context=global_context) - lhs.assign(rhs) - return _SetResult(lhs).to_object() +def _set_expression(expression, value, frame_id, value_format): + with apply_format(value_format): + global_context = True + if frame_id is not None: + select_frame(frame_id) + global_context = False + lhs = gdb.parse_and_eval(expression, global_context=global_context) + rhs = gdb.parse_and_eval(value, global_context=global_context) + lhs.assign(rhs) + return _SetResult(lhs).to_object() # Helper function to evaluate a gdb command in a certain frame. @@ -90,42 +93,50 @@ def _repl(command, frame_id): @request("evaluate") @capability("supportsEvaluateForHovers") +@capability("supportsValueFormattingOptions") def eval_request( *, expression: str, frameId: Optional[int] = None, context: str = "variables", + format=None, **args, ): if context in ("watch", "variables"): # These seem to be expression-like. - return send_gdb_with_response(lambda: _evaluate(expression, frameId)) + return send_gdb_with_response(lambda: _evaluate(expression, frameId, format)) elif context == "hover": - return send_gdb_with_response(lambda: _eval_for_hover(expression, frameId)) + return send_gdb_with_response( + lambda: _eval_for_hover(expression, frameId, format) + ) elif context == "repl": + # Ignore the format for repl evaluation. return send_gdb_with_response(lambda: _repl(expression, frameId)) else: raise Exception('unknown evaluate context "' + context + '"') @in_gdb_thread -def _variables(ref, start, count): - var = find_variable(ref) - children = var.fetch_children(start, count) - return [x.to_object() for x in children] +def _variables(ref, start, count, value_format): + with apply_format(value_format): + var = find_variable(ref) + children = var.fetch_children(start, count) + return [x.to_object() for x in children] @request("variables") # Note that we ignore the 'filter' field. That seems to be # specific to javascript. -def variables(*, variablesReference: int, start: int = 0, count: int = 0, **args): +def variables( + *, variablesReference: int, start: int = 0, count: int = 0, format=None, **args +): # This behavior was clarified here: # https://github.com/microsoft/debug-adapter-protocol/pull/394 if not client_bool_capability("supportsVariablePaging"): start = 0 count = 0 result = send_gdb_with_response( - lambda: _variables(variablesReference, start, count) + lambda: _variables(variablesReference, start, count, format) ) return {"variables": result} @@ -133,6 +144,8 @@ def variables(*, variablesReference: int, start: int = 0, count: int = 0, **args @capability("supportsSetExpression") @request("setExpression") def set_expression( - *, expression: str, value: str, frameId: Optional[int] = None, **args + *, expression: str, value: str, frameId: Optional[int] = None, format=None, **args ): - return send_gdb_with_response(lambda: _set_expression(expression, value, frameId)) + return send_gdb_with_response( + lambda: _set_expression(expression, value, frameId, format) + ) diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py index 213151fd3d3..9465198d01c 100644 --- a/gdb/python/lib/gdb/dap/varref.py +++ b/gdb/python/lib/gdb/dap/varref.py @@ -17,6 +17,7 @@ import gdb from .startup import in_gdb_thread from .server import client_bool_capability from abc import abstractmethod +from contextlib import contextmanager # A list of all the variable references created during this pause. @@ -34,6 +35,23 @@ def clear_vars(event): gdb.events.cont.connect(clear_vars) +# A null context manager. Python supplies one, starting in 3.7. +@contextmanager +def _null(**ignore): + yield + + +@in_gdb_thread +def apply_format(value_format): + """Temporarily apply the DAP ValueFormat. + + This returns a new context manager that applies the given DAP + ValueFormat object globally, then restores gdb's state when finished.""" + if value_format is not None and "hex" in value_format and value_format["hex"]: + return gdb.with_parameter("output-radix", 16) + return _null() + + class BaseReference: """Represent a variable or a scope. diff --git a/gdb/testsuite/gdb.dap/hover.exp b/gdb/testsuite/gdb.dap/hover.exp index 4caf105b347..1600984981c 100644 --- a/gdb/testsuite/gdb.dap/hover.exp +++ b/gdb/testsuite/gdb.dap/hover.exp @@ -48,6 +48,11 @@ set obj [dap_check_request_and_response "evaluate global" \ dap_match_values "global value in function" [lindex $obj 0] \ "body result" 23 +set obj [dap_check_request_and_response "evaluate global as hex" \ + evaluate {o expression [s global_variable] format [o hex [l true]]}] +dap_match_values "global value in function as hex" [lindex $obj 0] \ + "body result" 0x17 + set obj [dap_request_and_response \ evaluate {o context [s hover] expression [s increment()]}] gdb_assert {[dict get [lindex $obj 0] success] == "false"} \ -- 2.40.1