From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-lf1-x12e.google.com (mail-lf1-x12e.google.com [IPv6:2a00:1450:4864:20::12e]) by sourceware.org (Postfix) with ESMTPS id 542983858D28 for ; Mon, 19 Jun 2023 11:47:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 542983858D28 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-lf1-x12e.google.com with SMTP id 2adb3069b0e04-4f63ab1ac4aso4155838e87.0 for ; Mon, 19 Jun 2023 04:47:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1687175240; x=1689767240; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=9bPWQQoSbRKSJoaN9QPJR+aynTQPOloNS6jF1zKkekM=; b=eShgAu8itVJ/1RRTrv+PdGfQHucGTHXBk3qvSBHH2BaFWOAoIcOFE5VUr/OoOlCesU IKe+lJMfPKI/0ETIBuuuTUYUdJg7PdOSbqSGxXwLiGLa3ARE3emKl0nWUffy/GKtj7G4 ItU+PjM6Cid+R69P5cpp8psorVDlk1Ntsen3XFlOLemnA3gApjAftp7wuw4AXu41btsB +y20O6f89KG3gofnhMBBGGER2pPMJDp0HCK6XtMYnIvhDBGmjvUbaCF+qfWYjKRMw8RS mrn3/cZkhWa6L98vqZYA3rs1R+0F4cUHoYBSU6iTBFO1X4VNv6i3Jv87SX548PpKuOZn TKBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1687175240; x=1689767240; 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=9bPWQQoSbRKSJoaN9QPJR+aynTQPOloNS6jF1zKkekM=; b=kiOtkk2ysFqfuEYA/kOZ0j95oRZ3U86eKDEaCy4dDEhUzvWJpI6uCPGezX4QdEEkMx ij6fnvgwQYC5fY2+6U2/Qm+6fIMf07k8YfwV1/TV+BCVpRJYCQJAQvJl+iNbnTRoD4To /WQZLBuTa95GD+xjNCFUY/P3mxcumdzlLCAbOlQsY+TeekMirGvIvZW3yPYstQereZOl 8Gn7arfNayAIhyP6ER7mJgNzJDjZ7Un9PzHl1JBMoJaBWDw/t1PSJtD7xLnB8FV6S5Nl OYOMw9ie6rDUYkLKUgyJIgQCyZsGvGD0ScAj0elhG9utQzj50CDwl0b5EjNGr0cAt9I0 qrgw== X-Gm-Message-State: AC+VfDwoaK7XikjHgrIO7/+F86NnCBUjHuP9jXsZXV2io9j0Asj27iBD kpHggpWLlyEEbpiikUeYD7bL/wCnL1I= X-Google-Smtp-Source: ACHHUZ5Iy2N4rnv8QHfqpKrkVLgQcJqRRl0zKtyPL3HZsPRPnnHH5GdvjI6uTt4GlvnXhK6LB9EFfQ== X-Received: by 2002:a05:6512:110a:b0:4f6:2b25:194e with SMTP id l10-20020a056512110a00b004f62b25194emr4589526lfg.58.1687175238245; Mon, 19 Jun 2023 04:47:18 -0700 (PDT) Received: from fedora.. (78-73-77-63-no2450.tbcn.telia.com. [78.73.77.63]) by smtp.gmail.com with ESMTPSA id a6-20020a19f806000000b004f8703fbd80sm459402lff.57.2023.06.19.04.47.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Jun 2023 04:47:17 -0700 (PDT) From: Simon Farre To: gdb-patches@sourceware.org Cc: tromey@adacore.com, Simon Farre Subject: [PATCH v3] gdb/dap - dataBreakpointInfo & setDataBreakpoints Date: Mon, 19 Jun 2023 13:47:07 +0200 Message-Id: <20230619114707.70902-1-simon.farre.cx@gmail.com> X-Mailer: git-send-email 2.40.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-11.4 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,GIT_PATCH_0,KAM_SHORT,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: v3. Updated outdated documentation. Made watchpoints be set using addresses by resolving them from the gdb.Value, as Tom pointed out in v2, this means that the watchpoints are the equivalent of `watch -l foo`. Added tests for data breakpoint, both for a simple struct and vars[1] which is a std::vector. To be able to test this, I had to throw in the gcc pretty printers file, as I had no other way (it seemed to me) to be able to source them from the test. Also made the watchpoint creation behave/fit in to current design. frameId is more or less ignored in it's entirety for now as it doesn't make much sense from a GDB perspective. I suppose one way to actually make it make sense, would be to customize a gdb.Breakpoint (i.e. deriving from it), that behaves both as a watchpoint *and* as a finish breakpoint; as soon as the finish breakpoint hits, remove the watchpoint. Ran `black` on all .py files. Removed prior versions from commit msg, due to overhaul. --- gdb/python/lib/gdb/dap/breakpoint.py | 124 +- gdb/python/lib/gdb/dap/events.py | 6 +- gdb/python/lib/gdb/dap/varref.py | 30 +- gdb/testsuite/gdb.dap/data-breakpoint.cc | 46 + gdb/testsuite/gdb.dap/data-breakpoint.exp | 215 ++ .../gdb.dap/stdlib-pretty-printers.py | 2351 +++++++++++++++++ 6 files changed, 2764 insertions(+), 8 deletions(-) create mode 100644 gdb/testsuite/gdb.dap/data-breakpoint.cc create mode 100644 gdb/testsuite/gdb.dap/data-breakpoint.exp create mode 100644 gdb/testsuite/gdb.dap/stdlib-pretty-printers.py diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py index 20e65aa0e61..bb1134a6c52 100644 --- a/gdb/python/lib/gdb/dap/breakpoint.py +++ b/gdb/python/lib/gdb/dap/breakpoint.py @@ -20,7 +20,8 @@ import os from typing import Optional, Sequence from .server import request, capability -from .startup import send_gdb_with_response, in_gdb_thread +from .startup import send_gdb_with_response, in_gdb_thread, log +from .varref import find_variable # Map from the breakpoint "kind" (like "function") to a second map, of @@ -34,7 +35,7 @@ breakpoint_map = {} @in_gdb_thread def breakpoint_descriptor(bp): "Return the Breakpoint object descriptor given a gdb Breakpoint." - if bp.locations: + if bp.location is not None and len(bp.locations) > 0: # Just choose the first location, because DAP doesn't allow # multiple locations. See # https://github.com/microsoft/debug-adapter-protocol/issues/13 @@ -56,6 +57,11 @@ def breakpoint_descriptor(bp): if path is not None: result["source"]["path"] = path return result + elif bp.expression is not None: + return { + "id": bp.number, + "verified": True, + } else: return { "id": bp.number, @@ -248,3 +254,117 @@ def set_exception_breakpoints( return { "breakpoints": result, } + + +def _info_not_available(desc): + return {"dataId": None, "description": f"{desc}", "accessTypes": None} + + +@in_gdb_thread +def _databreakpoint_info( + name: str, variablesReference: Optional[int] = None, frameId: Optional[int] = None +): + + if variablesReference is not None: + variable = None + # Resolve variable expression until find_variable throws + try: + variable = find_variable(variablesReference) + except Exception as vr_not_found: + return _info_not_available(vr_not_found) + + gdb_value = None + try: + gdb_value = variable.try_find_member(name) + except gdb.error as value_not_found: + return _info_not_available(value_not_found) + + if gdb_value.address is None: + return _info_not_available("Could not take address of value") + + dataId = hex(int(gdb_value.address)) + + return { + "dataId": dataId, + "description": f"{name} @ {dataId}", + "accessTypes": ["read", "write", "readWrite"], + } + elif variablesReference is None: + try: + v = gdb.parse_and_eval(name) + if v.address is None: + return _info_not_available("Could not take address of expression") + dataId = hex(int(v.address)) + return { + "dataId": dataId, + "description": f"{v.type} @ {dataId}", + "accessTypes": ["read", "write", "readWrite"], + } + except Exception as parse_ex: + return _info_not_available(f"Can not find {name} ({parse_ex})") + + return _info_not_available(f"Can not find {name}") + + +@capability("supportsDataBreakpoints") +@request("dataBreakpointInfo") +def data_breakpoint_info( + *, + name: str, + variablesReference: Optional[int] = None, + frameId: Optional[int] = None, + **args, +): + result = send_gdb_with_response( + lambda: _databreakpoint_info(name, variablesReference, frameId) + ) + return result + + +def _sanitize_wp_input(breakpoints: Sequence) -> Sequence: + """Sanitize input so that it plays well with current design.""" + res = [] + for wp in breakpoints: + obj = {} + obj["condition"] = wp.get("condition") + obj["hitCondition"] = wp.get("hitCondition") + obj["accessType"] = wp.get("accessType") + obj["dataId"] = wp.get("dataId") + res.append(obj) + return res + + +def _wp_type(type: str): + if type == "read": + return gdb.WP_READ + if type == "write": + return gdb.WP_WRITE + if type == "readWrite": + return gdb.WP_ACCESS + raise gdb.GdbError("Erroneous watchpoint type") + + +@in_gdb_thread +def _create_watchpoints(dataId, accessType, enabled=True): + wp = gdb.Breakpoint( + spec=f"*{dataId}", type=gdb.BP_WATCHPOINT, wp_class=_wp_type(accessType) + ) + wp.enabled = enabled + return wp + + +@in_gdb_thread +def _set_data_breakpoints(breakpoints: Sequence): + return _set_breakpoints_callback("watchpoints", breakpoints, _create_watchpoints) + + +@request("setDataBreakpoints") +@capability("supportsDataBreakpoints") +@capability("supportsHitConditionalBreakpoints") +@capability("supportsConditionalBreakpoints") +def set_databreakpoints(*, breakpoints: Sequence = (), **args): + wps = _sanitize_wp_input(breakpoints=breakpoints) + result = send_gdb_with_response(lambda: _set_data_breakpoints(wps)) + return { + "breakpoints": result, + } diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py index d9ae603dfa4..8fbf1f09891 100644 --- a/gdb/python/lib/gdb/dap/events.py +++ b/gdb/python/lib/gdb/dap/events.py @@ -101,6 +101,7 @@ class StopKinds(enum.Enum): # The values here are chosen to follow the DAP spec. STEP = "step" BREAKPOINT = "breakpoint" + DATABREAKPOINT = "data breakpoint" PAUSE = "pause" EXCEPTION = "exception" @@ -144,7 +145,10 @@ def _on_stop(event): } if isinstance(event, gdb.BreakpointEvent): # Ignore the expected stop, we hit a breakpoint instead. - _expected_stop = StopKinds.BREAKPOINT + if event.breakpoints[0].expression: + _expected_stop = StopKinds.DATABREAKPOINT + else: + _expected_stop = StopKinds.BREAKPOINT obj["hitBreakpointIds"] = [x.number for x in event.breakpoints] elif _expected_stop is None: # FIXME what is even correct here diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py index 23f18d647c3..5a49384a6b6 100644 --- a/gdb/python/lib/gdb/dap/varref.py +++ b/gdb/python/lib/gdb/dap/varref.py @@ -22,7 +22,6 @@ from abc import abstractmethod # A list of all the variable references created during this pause. all_variables = [] - # When the inferior is re-started, we erase all variable references. # See the section "Lifetime of Objects References" in the spec. @in_gdb_thread @@ -101,7 +100,7 @@ class BaseReference: for idx in range(start, start + count): if self.children[idx] is None: (name, value) = self.fetch_one_child(idx) - self.children[idx] = VariableReference(name, value) + self.children[idx] = VariableReference(name=name, value=value) result.append(self.children[idx]) return result @@ -109,13 +108,15 @@ class BaseReference: class VariableReference(BaseReference): """Concrete subclass of BaseReference that handles gdb.Value.""" - def __init__(self, name, value, result_name="value"): + def __init__(self, name, value: gdb.Value, result_name="value"): """Initializer. NAME is the name of this reference, see superclass. VALUE is a gdb.Value that holds the value. RESULT_NAME can be used to change how the simple string result - is emitted in the result dictionary.""" + is emitted in the result dictionary. + P_REF parent variableReference. Is set to -1 if parent is not + of VariableReference type""" super().__init__(name) self.value = value self.printer = gdb.printing.make_visualizer(value) @@ -128,6 +129,22 @@ class VariableReference(BaseReference): else: self.count = -1 + def try_find_member(self, name: str) -> gdb.Value: + type = self.value.type + # Non-pretty printed values more common. Search type definition first + for field in type.fields(): + if field.name == name: + return self.value[field] + + if not hasattr(self.printer, "children"): + raise gdb.error("Pretty printer has no children") + + for (child_name, value) in self.printer.children(): + if child_name == name: + return value + + raise gdb.error(f"Child {name} could not be found in {self.name}") + def cache_children(self): if self.child_cache is None: # This discards all laziness. This could be improved @@ -152,7 +169,10 @@ class VariableReference(BaseReference): def to_object(self): result = super().to_object() - result[self.result_name] = self.printer.to_string() + + result[self.result_name] = ( + f"{self.value.type}" if self.ref is not 0 else f"{self.value}" + ) num_children = self.child_count() if num_children is not None: if ( diff --git a/gdb/testsuite/gdb.dap/data-breakpoint.cc b/gdb/testsuite/gdb.dap/data-breakpoint.cc new file mode 100644 index 00000000000..7b82e013d6d --- /dev/null +++ b/gdb/testsuite/gdb.dap/data-breakpoint.cc @@ -0,0 +1,46 @@ +/* Copyright 2023 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 . */ + + +#include + +struct Var { + int val; + struct Member { + int val; + } member; +}; + +static Var global{.val = 10, .member = Var::Member{ .val = 20 }}; + +int +main (int argc, const char** argv) +{ + std::vector vars{}; + vars.reserve(10); // Make sure no re-alloc + vars.emplace_back(Var{.val = 1, .member = Var::Member{ .val = 2 }}); + vars.emplace_back(Var{.val = 3, .member = Var::Member{ .val = 4 }}); + vars.emplace_back(Var{.val = 5, .member = Var::Member{ .val = 6 }}); + + Var var{.val = 7, .member = Var::Member{ .val = 8 }}; + int new_val = 30; + int member_new_val = 40; /* PRETTY_PRINT_WP */ + auto copy = vars[1]; /* AFTER_VARS_PP_READ */ + var.val = new_val; + var.member.val = member_new_val; + return 0; /* AFTER_SIMPLE_WRITE */ +} diff --git a/gdb/testsuite/gdb.dap/data-breakpoint.exp b/gdb/testsuite/gdb.dap/data-breakpoint.exp new file mode 100644 index 00000000000..1fac0962937 --- /dev/null +++ b/gdb/testsuite/gdb.dap/data-breakpoint.exp @@ -0,0 +1,215 @@ +# Copyright 2022-2023 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 . + +# Basic DAP test. + +require allow_dap_tests + +load_lib dap-support.exp + +set stdlib_pp stdlib-pretty-printers.py +set remote_python_file [gdb_remote_download host ${srcdir}/${subdir}/${stdlib_pp}] +set sourcepp "source ${remote_python_file}" + +standard_testfile .cc +set exefile $testfile + +if {[build_executable "failed to prepare" $exefile $srcfile {debug c++}]} { + return +} + +if {[dap_launch $testfile] == ""} { + return +} + +proc get_stack_frame_id { level } { + set args {o threadId [i 1] } + set bt [lindex [dap_request_and_response stackTrace $args ] 0] + set frame_id [dict get [lindex [dict get $bt body stackFrames] $level] id] + return $frame_id +} + +proc get_line_of_frame { level } { + set bt [ dap_request_and_response stackTrace {o threadId [i 1]}] + set bt [lindex $bt 0] + set stop [dict get [lindex [dict get $bt body stackFrames] $level] line] + return $stop +} + +proc get_variables_of { vr } { + set args [format {o variablesReference [i %d] } $vr] + set refs [dap_request_and_response variables $args ] + set refs [lindex $refs 0] + + return [dict get $refs body variables] +} + +proc get_variable_reference_for { frame_id name_ } { + set variables [ get_variables_of $frame_id ] + set result {} + foreach var $variables { + set var_name [dict get $var name] + if { $var_name == $name_ } { + set result [dict get $var variablesReference] + } + } + return $result +} + +proc get_local_scope { frame_id } { + set scopes_res [dap_request_and_response scopes {o frameId [i $frame_id]} ] + set scopes [dict get [lindex $scopes_res 0] body scopes] + lassign $scopes args locals reg_scope + set local [dict get $locals variablesReference] + return $local +} + +set line [gdb_get_line_number "PRETTY_PRINT_WP"] +set obj [dap_check_request_and_response "set breakpoint by line number" \ + setBreakpoints \ + [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \ + [list s $srcfile] $line]] +set line_bpno [dap_get_breakpoint_number $obj] + +dap_check_request_and_response "start inferior" configurationDone +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" $line_bpno + + +set frame_id [ get_stack_frame_id 0 ] +dap_check_request_and_response "source pretty printers" evaluate \ + {o context [s "repl"] frameId [i $frame_id] expression [s $sourcepp]} +set register_pp_cmd "py register_libstdcxx_printers(gdb.current_objfile())" +dap_check_request_and_response "register pretty printers" evaluate {o context [s "repl"] frameId [i $frame_id] expression [s $register_pp_cmd]} + +set local [ get_local_scope $frame_id ] + +set pretty_printed_var_ref [ get_variable_reference_for $local "vars" ] + +# Do nothing with this, we need GDB to retrive the children of "vars" +set vars_members [ get_variables_of $pretty_printed_var_ref ] +# get dataBreakpointInfo for vars[1] +set dbi_vars_idx_1 \ + [lindex [dap_check_request_and_response \ + "dataBreakpointInfo request var ref + name" \ + dataBreakpointInfo \ + {o variablesReference [i $pretty_printed_var_ref] name [s "\[1\]"] }] 0] + +set dataId [ dict get $dbi_vars_idx_1 body dataId ] + +set wp_response [lindex [dap_check_request_and_response "set read watchpoint" \ + setDataBreakpoints \ + {o breakpoints \ + [a [o dataId [s $dataId] accessType [s "read" ] ] ] \ + }] \ + 0 ] + +set after_read [gdb_get_line_number "AFTER_VARS_PP_READ"] + +dap_check_request_and_response "continue until watchpoint triggers" continue \ + {o threadId [i 1]} + +dap_wait_for_event_and_check "stopped at read watchpoint" stopped \ + "body reason" "data breakpoint" \ + "body hitBreakpointIds" 2 + + +set stopped_line [ get_line_of_frame 0 ] + +gdb_assert { $stopped_line == $after_read } \ + "Watchpoint stopped at line $stopped_line (should stop at $after_read)" + +# Set watchpoint for simple data type Var +set frame_id [ get_stack_frame_id 0 ] + +set scopes [dap_check_request_and_response "get scopes" scopes \ + [format {o frameId [i %d]} $frame_id]] +set scopes [dict get [lindex $scopes 0] body scopes] + +lassign $scopes args scope reg_scope +set locals [dict get $scope variablesReference] + +set var [ get_variable_reference_for $locals "var" ] + +set var_members [ get_variables_of $var ] + +set var_member_var_ref [ get_variable_reference_for $var "member" ] + +set dbi_var_member_val [lindex [dap_check_request_and_response "varRef name" \ + dataBreakpointInfo {o variablesReference \ + [i $var_member_var_ref] name [s "val"] } \ + ] 0 ] + +set dataId [ dict get $dbi_var_member_val body dataId ] + +# Same as above but without variablesReference and the full resolve symbol path +set dbi_var_member_val [lindex [dap_check_request_and_response "name only" \ + dataBreakpointInfo {o name [s "var.member.val"] }] 0] + +set dataId [ dict get $dbi_var_member_val body dataId ] + +set wp_response [lindex [dap_check_request_and_response "set write watchpoint" \ + setDataBreakpoints \ + {o breakpoints \ + [a [o dataId [s $dataId] accessType [s "write" ] ] ] \ + }] \ + 0 ] + +# dataBreakpointInfo for global.member.val => dataId == None +set dbi_global_member_val \ + [lindex [dap_check_request_and_response \ + "dataBreakpointInfo dataId == None" \ + dataBreakpointInfo \ + {o frameId [i $frame_id ] name [s "global.member.val"] } \ + ] \ + 0 ] + +set null_dataId [ dict get $dbi_global_member_val body dataId ] +gdb_assert { $null_dataId == "null" } "with frameid should return null" + +# dataBreakpointInfo for global.member.val without varRef|frameId +set dbi_global_member_val \ + [lindex [dap_check_request_and_response \ + "dataBreakpointInfo request" \ + dataBreakpointInfo \ + {o name [s "global.member.val"] } \ + ] \ + 0] + +set dataId [ dict get $dbi_global_member_val body dataId ] +gdb_assert { $dataId != "null" } "Should find globals" + +set after_write [gdb_get_line_number "AFTER_SIMPLE_WRITE"] + +dap_check_request_and_response "continue until simple watchpoint triggers" continue \ + {o threadId [i 1]} + +dap_wait_for_event_and_check "stopped at write watchpoint" stopped \ + "body reason" "data breakpoint" \ + "body hitBreakpointIds" 3 + +set bt [lindex [dap_check_request_and_response "stopped at line" stackTrace \ + {o threadId [i 1]}] \ + 0] + +set stopped_line [dict get [lindex [dict get $bt body stackFrames] 0] line] + +gdb_assert { $stopped_line == $after_write } \ + "Watchpoint stopped at line $stopped_line" + +dap_shutdown diff --git a/gdb/testsuite/gdb.dap/stdlib-pretty-printers.py b/gdb/testsuite/gdb.dap/stdlib-pretty-printers.py new file mode 100644 index 00000000000..bb06a4959ee --- /dev/null +++ b/gdb/testsuite/gdb.dap/stdlib-pretty-printers.py @@ -0,0 +1,2351 @@ +# Pretty-printers for libstdc++. + +# Copyright (C) 2008-2022 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 . + +import gdb +import itertools +import re +import sys, os, errno + +### Python 2 + Python 3 compatibility code + +# Resources about compatibility: +# +# * : Documentation of the "six" module + +# FIXME: The handling of e.g. std::basic_string (at least on char) +# probably needs updating to work with Python 3's new string rules. +# +# In particular, Python 3 has a separate type (called byte) for +# bytestrings, and a special b"" syntax for the byte literals; the old +# str() type has been redefined to always store Unicode text. +# +# We probably can't do much about this until this GDB PR is addressed: +# + +if sys.version_info[0] > 2: + ### Python 3 stuff + Iterator = object + # Python 3 folds these into the normal functions. + imap = map + izip = zip + # Also, int subsumes long + long = int +else: + ### Python 2 stuff + class Iterator: + """Compatibility mixin for iterators + + Instead of writing next() methods for iterators, write + __next__() methods and use this mixin to make them work in + Python 2 as well as Python 3. + + Idea stolen from the "six" documentation: + + """ + + def next(self): + return self.__next__() + + # In Python 2, we still need these from itertools + from itertools import imap, izip + +# Try to use the new-style pretty-printing if available. +_use_gdb_pp = True +try: + import gdb.printing +except ImportError: + _use_gdb_pp = False + +# Try to install type-printers. +_use_type_printing = False +try: + import gdb.types + if hasattr(gdb.types, 'TypePrinter'): + _use_type_printing = True +except ImportError: + pass + +# Starting with the type ORIG, search for the member type NAME. This +# handles searching upward through superclasses. This is needed to +# work around http://sourceware.org/bugzilla/show_bug.cgi?id=13615. +def find_type(orig, name): + typ = orig.strip_typedefs() + while True: + # Use Type.tag to ignore cv-qualifiers. PR 67440. + search = '%s::%s' % (typ.tag, name) + try: + return gdb.lookup_type(search) + except RuntimeError: + pass + # The type was not found, so try the superclass. We only need + # to check the first superclass, so we don't bother with + # anything fancier here. + fields = typ.fields() + if len(fields) and fields[0].is_base_class: + typ = fields[0].type + else: + raise ValueError("Cannot find type %s::%s" % (str(orig), name)) + +_versioned_namespace = '__8::' + +def lookup_templ_spec(templ, *args): + """ + Lookup template specialization templ + """ + t = '{}<{}>'.format(templ, ', '.join([str(a) for a in args])) + try: + return gdb.lookup_type(t) + except gdb.error as e: + # Type not found, try again in versioned namespace. + global _versioned_namespace + if _versioned_namespace and _versioned_namespace not in templ: + t = t.replace('::', '::' + _versioned_namespace, 1) + try: + return gdb.lookup_type(t) + except gdb.error: + # If that also fails, rethrow the original exception + pass + raise e + +# Use this to find container node types instead of find_type, +# see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91997 for details. +def lookup_node_type(nodename, containertype): + """ + Lookup specialization of template NODENAME corresponding to CONTAINERTYPE. + e.g. if NODENAME is '_List_node' and CONTAINERTYPE is std::list + then return the type std::_List_node. + Returns None if not found. + """ + # If nodename is unqualified, assume it's in namespace std. + if '::' not in nodename: + nodename = 'std::' + nodename + try: + valtype = find_type(containertype, 'value_type') + except: + valtype = containertype.template_argument(0) + valtype = valtype.strip_typedefs() + try: + return lookup_templ_spec(nodename, valtype) + except gdb.error as e: + # For debug mode containers the node is in std::__cxx1998. + if is_member_of_namespace(nodename, 'std'): + if is_member_of_namespace(containertype, 'std::__cxx1998', + 'std::__debug', '__gnu_debug'): + nodename = nodename.replace('::', '::__cxx1998::', 1) + try: + return lookup_templ_spec(nodename, valtype) + except gdb.error: + pass + return None + +def is_member_of_namespace(typ, *namespaces): + """ + Test whether a type is a member of one of the specified namespaces. + The type can be specified as a string or a gdb.Type object. + """ + if type(typ) is gdb.Type: + typ = str(typ) + typ = strip_versioned_namespace(typ) + for namespace in namespaces: + if typ.startswith(namespace + '::'): + return True + return False + +def is_specialization_of(x, template_name): + "Test if a type is a given template instantiation." + global _versioned_namespace + if type(x) is gdb.Type: + x = x.tag + if _versioned_namespace: + return re.match('^std::(%s)?%s<.*>$' % (_versioned_namespace, template_name), x) is not None + return re.match('^std::%s<.*>$' % template_name, x) is not None + +def strip_versioned_namespace(typename): + global _versioned_namespace + if _versioned_namespace: + return typename.replace(_versioned_namespace, '') + return typename + +def strip_inline_namespaces(type_str): + "Remove known inline namespaces from the canonical name of a type." + type_str = strip_versioned_namespace(type_str) + type_str = type_str.replace('std::__cxx11::', 'std::') + expt_ns = 'std::experimental::' + for lfts_ns in ('fundamentals_v1', 'fundamentals_v2'): + type_str = type_str.replace(expt_ns+lfts_ns+'::', expt_ns) + fs_ns = expt_ns + 'filesystem::' + type_str = type_str.replace(fs_ns+'v1::', fs_ns) + return type_str + +def get_template_arg_list(type_obj): + "Return a type's template arguments as a list" + n = 0 + template_args = [] + while True: + try: + template_args.append(type_obj.template_argument(n)) + except: + return template_args + n += 1 + +class SmartPtrIterator(Iterator): + "An iterator for smart pointer types with a single 'child' value" + + def __init__(self, val): + self.val = val + + def __iter__(self): + return self + + def __next__(self): + if self.val is None: + raise StopIteration + self.val, val = None, self.val + return ('get()', val) + +class SharedPointerPrinter: + "Print a shared_ptr, weak_ptr, atomic, or atomic" + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + self.pointer = val['_M_ptr'] + + def children (self): + return SmartPtrIterator(self.pointer) + + # Return the _Sp_counted_base<>* that holds the refcounts. + def _get_refcounts (self): + if self.typename == 'std::atomic': + # A tagged pointer is stored as uintptr_t. + ptr_val = self.val['_M_refcount']['_M_val']['_M_i'] + ptr_val = ptr_val - (ptr_val % 2) # clear lock bit + ptr_type = find_type(self.val['_M_refcount'].type, 'pointer') + return ptr_val.cast(ptr_type) + return self.val['_M_refcount']['_M_pi'] + + def to_string (self): + state = 'empty' + refcounts = self._get_refcounts() + targ = self.val.type.template_argument(0) + targ = strip_versioned_namespace(str(targ)) + + if refcounts != 0: + usecount = refcounts['_M_use_count'] + weakcount = refcounts['_M_weak_count'] + if usecount == 0: + state = 'expired, weak count %d' % weakcount + else: + state = 'use count %d, weak count %d' % (usecount, weakcount - 1) + return '%s<%s> (%s)' % (self.typename, targ, state) + +def _tuple_impl_get(val): + "Return the tuple element stored in a _Tuple_impl base class." + bases = val.type.fields() + if not bases[-1].is_base_class: + raise ValueError("Unsupported implementation for std::tuple: %s" % str(val.type)) + # Get the _Head_base base class: + head_base = val.cast(bases[-1].type) + fields = head_base.type.fields() + if len(fields) == 0: + raise ValueError("Unsupported implementation for std::tuple: %s" % str(val.type)) + if fields[0].name == '_M_head_impl': + # The tuple element is the _Head_base::_M_head_impl data member. + return head_base['_M_head_impl'] + elif fields[0].is_base_class: + # The tuple element is an empty base class of _Head_base. + # Cast to that empty base class. + return head_base.cast(fields[0].type) + else: + raise ValueError("Unsupported implementation for std::tuple: %s" % str(val.type)) + +def tuple_get(n, val): + "Return the result of std::get(val) on a std::tuple" + tuple_size = len(get_template_arg_list(val.type)) + if n > tuple_size: + raise ValueError("Out of range index for std::get on std::tuple") + # Get the first _Tuple_impl<0, T...> base class: + node = val.cast(val.type.fields()[0].type) + while n > 0: + # Descend through the base classes until the Nth one. + node = node.cast(node.type.fields()[0].type) + n -= 1 + return _tuple_impl_get(node) + +def unique_ptr_get(val): + "Return the result of val.get() on a std::unique_ptr" + # std::unique_ptr contains a std::tuple, + # either as a direct data member _M_t (the old implementation) + # or within a data member of type __uniq_ptr_data. + impl_type = val.type.fields()[0].type.strip_typedefs() + # Check for new implementations first: + if is_specialization_of(impl_type, '__uniq_ptr_data') \ + or is_specialization_of(impl_type, '__uniq_ptr_impl'): + tuple_member = val['_M_t']['_M_t'] + elif is_specialization_of(impl_type, 'tuple'): + tuple_member = val['_M_t'] + else: + raise ValueError("Unsupported implementation for unique_ptr: %s" % str(impl_type)) + return tuple_get(0, tuple_member) + +class UniquePointerPrinter: + "Print a unique_ptr" + + def __init__ (self, typename, val): + self.val = val + + def children (self): + return SmartPtrIterator(unique_ptr_get(self.val)) + + def to_string (self): + return ('std::unique_ptr<%s>' % (str(self.val.type.template_argument(0)))) + +def get_value_from_aligned_membuf(buf, valtype): + """Returns the value held in a __gnu_cxx::__aligned_membuf.""" + return buf['_M_storage'].address.cast(valtype.pointer()).dereference() + +def get_value_from_list_node(node): + """Returns the value held in an _List_node<_Val>""" + try: + member = node.type.fields()[1].name + if member == '_M_data': + # C++03 implementation, node contains the value as a member + return node['_M_data'] + elif member == '_M_storage': + # C++11 implementation, node stores value in __aligned_membuf + valtype = node.type.template_argument(0) + return get_value_from_aligned_membuf(node['_M_storage'], valtype) + except: + pass + raise ValueError("Unsupported implementation for %s" % str(node.type)) + +class StdListPrinter: + "Print a std::list" + + class _iterator(Iterator): + def __init__(self, nodetype, head): + self.nodetype = nodetype + self.base = head['_M_next'] + self.head = head.address + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.base == self.head: + raise StopIteration + elt = self.base.cast(self.nodetype).dereference() + self.base = elt['_M_next'] + count = self.count + self.count = self.count + 1 + val = get_value_from_list_node(elt) + return ('[%d]' % count, val) + + def __init__(self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + + def children(self): + nodetype = lookup_node_type('_List_node', self.val.type).pointer() + return self._iterator(nodetype, self.val['_M_impl']['_M_node']) + + def to_string(self): + headnode = self.val['_M_impl']['_M_node'] + if headnode['_M_next'] == headnode.address: + return 'empty %s' % (self.typename) + return '%s' % (self.typename) + +class NodeIteratorPrinter: + def __init__(self, typename, val, contname, nodename): + self.val = val + self.typename = typename + self.contname = contname + self.nodetype = lookup_node_type(nodename, val.type) + + def to_string(self): + if not self.val['_M_node']: + return 'non-dereferenceable iterator for std::%s' % (self.contname) + node = self.val['_M_node'].cast(self.nodetype.pointer()).dereference() + return str(get_value_from_list_node(node)) + +class StdListIteratorPrinter(NodeIteratorPrinter): + "Print std::list::iterator" + + def __init__(self, typename, val): + NodeIteratorPrinter.__init__(self, typename, val, 'list', '_List_node') + +class StdFwdListIteratorPrinter(NodeIteratorPrinter): + "Print std::forward_list::iterator" + + def __init__(self, typename, val): + NodeIteratorPrinter.__init__(self, typename, val, 'forward_list', + '_Fwd_list_node') + +class StdSlistPrinter: + "Print a __gnu_cxx::slist" + + class _iterator(Iterator): + def __init__(self, nodetype, head): + self.nodetype = nodetype + self.base = head['_M_head']['_M_next'] + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.base == 0: + raise StopIteration + elt = self.base.cast(self.nodetype).dereference() + self.base = elt['_M_next'] + count = self.count + self.count = self.count + 1 + return ('[%d]' % count, elt['_M_data']) + + def __init__(self, typename, val): + self.val = val + + def children(self): + nodetype = lookup_node_type('__gnu_cxx::_Slist_node', self.val.type) + return self._iterator(nodetype.pointer(), self.val) + + def to_string(self): + if self.val['_M_head']['_M_next'] == 0: + return 'empty __gnu_cxx::slist' + return '__gnu_cxx::slist' + +class StdSlistIteratorPrinter: + "Print __gnu_cxx::slist::iterator" + + def __init__(self, typename, val): + self.val = val + + def to_string(self): + if not self.val['_M_node']: + return 'non-dereferenceable iterator for __gnu_cxx::slist' + nodetype = lookup_node_type('__gnu_cxx::_Slist_node', self.val.type).pointer() + return str(self.val['_M_node'].cast(nodetype).dereference()['_M_data']) + +class StdVectorPrinter: + "Print a std::vector" + + class _iterator(Iterator): + def __init__ (self, start, finish, bitvec): + self.bitvec = bitvec + if bitvec: + self.item = start['_M_p'] + self.so = 0 + self.finish = finish['_M_p'] + self.fo = finish['_M_offset'] + itype = self.item.dereference().type + self.isize = 8 * itype.sizeof + else: + self.item = start + self.finish = finish + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + count = self.count + self.count = self.count + 1 + if self.bitvec: + if self.item == self.finish and self.so >= self.fo: + raise StopIteration + elt = bool(self.item.dereference() & (1 << self.so)) + self.so = self.so + 1 + if self.so >= self.isize: + self.item = self.item + 1 + self.so = 0 + return ('[%d]' % count, elt) + else: + if self.item == self.finish: + raise StopIteration + elt = self.item.dereference() + self.item = self.item + 1 + return ('[%d]' % count, elt) + + def __init__(self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + self.is_bool = val.type.template_argument(0).code == gdb.TYPE_CODE_BOOL + + def children(self): + return self._iterator(self.val['_M_impl']['_M_start'], + self.val['_M_impl']['_M_finish'], + self.is_bool) + + def to_string(self): + start = self.val['_M_impl']['_M_start'] + finish = self.val['_M_impl']['_M_finish'] + end = self.val['_M_impl']['_M_end_of_storage'] + if self.is_bool: + start = self.val['_M_impl']['_M_start']['_M_p'] + finish = self.val['_M_impl']['_M_finish']['_M_p'] + fo = self.val['_M_impl']['_M_finish']['_M_offset'] + itype = start.dereference().type + bl = 8 * itype.sizeof + length = bl * (finish - start) + fo + capacity = bl * (end - start) + return ('%s of length %d, capacity %d' + % (self.typename, int (length), int (capacity))) + else: + return ('%s of length %d, capacity %d' + % (self.typename, int (finish - start), int (end - start))) + + def display_hint(self): + return 'array' + +class StdVectorIteratorPrinter: + "Print std::vector::iterator" + + def __init__(self, typename, val): + self.val = val + + def to_string(self): + if not self.val['_M_current']: + return 'non-dereferenceable iterator for std::vector' + return str(self.val['_M_current'].dereference()) + +class StdBitIteratorPrinter: + "Print std::vector's _Bit_iterator and _Bit_const_iterator" + + def __init__(self, typename, val): + self.val = val + + def to_string(self): + if not self.val['_M_p']: + return 'non-dereferenceable iterator for std::vector' + return bool(self.val['_M_p'].dereference() & (1 << self.val['_M_offset'])) + +class StdBitReferencePrinter: + "Print std::vector::reference" + + def __init__(self, typename, val): + self.val = val + + def to_string(self): + if not self.val['_M_p']: + return 'invalid std::vector::reference' + return bool(self.val['_M_p'].dereference() & (self.val['_M_mask'])) + +class StdTuplePrinter: + "Print a std::tuple" + + class _iterator(Iterator): + @staticmethod + def _is_nonempty_tuple (nodes): + if len (nodes) == 2: + if is_specialization_of (nodes[1].type, '__tuple_base'): + return True + elif len (nodes) == 1: + return True + elif len (nodes) == 0: + return False + raise ValueError("Top of tuple tree does not consist of a single node.") + + def __init__ (self, head): + self.head = head + + # Set the base class as the initial head of the + # tuple. + nodes = self.head.type.fields () + if self._is_nonempty_tuple (nodes): + # Set the actual head to the first pair. + self.head = self.head.cast (nodes[0].type) + self.count = 0 + + def __iter__ (self): + return self + + def __next__ (self): + # Check for further recursions in the inheritance tree. + # For a GCC 5+ tuple self.head is None after visiting all nodes: + if not self.head: + raise StopIteration + nodes = self.head.type.fields () + # For a GCC 4.x tuple there is a final node with no fields: + if len (nodes) == 0: + raise StopIteration + # Check that this iteration has an expected structure. + if len (nodes) > 2: + raise ValueError("Cannot parse more than 2 nodes in a tuple tree.") + + if len (nodes) == 1: + # This is the last node of a GCC 5+ std::tuple. + impl = self.head.cast (nodes[0].type) + self.head = None + else: + # Either a node before the last node, or the last node of + # a GCC 4.x tuple (which has an empty parent). + + # - Left node is the next recursion parent. + # - Right node is the actual class contained in the tuple. + + # Process right node. + impl = self.head.cast (nodes[1].type) + + # Process left node and set it as head. + self.head = self.head.cast (nodes[0].type) + + self.count = self.count + 1 + + # Finally, check the implementation. If it is + # wrapped in _M_head_impl return that, otherwise return + # the value "as is". + fields = impl.type.fields () + if len (fields) < 1 or fields[0].name != "_M_head_impl": + return ('[%d]' % self.count, impl) + else: + return ('[%d]' % self.count, impl['_M_head_impl']) + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val; + + def children (self): + return self._iterator (self.val) + + def to_string (self): + if len (self.val.type.fields ()) == 0: + return 'empty %s' % (self.typename) + return '%s containing' % (self.typename) + +class StdStackOrQueuePrinter: + "Print a std::stack or std::queue" + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.visualizer = gdb.default_visualizer(val['c']) + + def children (self): + return self.visualizer.children() + + def to_string (self): + return '%s wrapping: %s' % (self.typename, + self.visualizer.to_string()) + + def display_hint (self): + if hasattr (self.visualizer, 'display_hint'): + return self.visualizer.display_hint () + return None + +class RbtreeIterator(Iterator): + """ + Turn an RB-tree-based container (std::map, std::set etc.) into + a Python iterable object. + """ + + def __init__(self, rbtree): + self.size = rbtree['_M_t']['_M_impl']['_M_node_count'] + self.node = rbtree['_M_t']['_M_impl']['_M_header']['_M_left'] + self.count = 0 + + def __iter__(self): + return self + + def __len__(self): + return int (self.size) + + def __next__(self): + if self.count == self.size: + raise StopIteration + result = self.node + self.count = self.count + 1 + if self.count < self.size: + # Compute the next node. + node = self.node + if node.dereference()['_M_right']: + node = node.dereference()['_M_right'] + while node.dereference()['_M_left']: + node = node.dereference()['_M_left'] + else: + parent = node.dereference()['_M_parent'] + while node == parent.dereference()['_M_right']: + node = parent + parent = parent.dereference()['_M_parent'] + if node.dereference()['_M_right'] != parent: + node = parent + self.node = node + return result + +def get_value_from_Rb_tree_node(node): + """Returns the value held in an _Rb_tree_node<_Val>""" + try: + member = node.type.fields()[1].name + if member == '_M_value_field': + # C++03 implementation, node contains the value as a member + return node['_M_value_field'] + elif member == '_M_storage': + # C++11 implementation, node stores value in __aligned_membuf + valtype = node.type.template_argument(0) + return get_value_from_aligned_membuf(node['_M_storage'], valtype) + except: + pass + raise ValueError("Unsupported implementation for %s" % str(node.type)) + +# This is a pretty printer for std::_Rb_tree_iterator (which is +# std::map::iterator), and has nothing to do with the RbtreeIterator +# class above. +class StdRbtreeIteratorPrinter: + "Print std::map::iterator, std::set::iterator, etc." + + def __init__ (self, typename, val): + self.val = val + nodetype = lookup_node_type('_Rb_tree_node', self.val.type) + self.link_type = nodetype.pointer() + + def to_string (self): + if not self.val['_M_node']: + return 'non-dereferenceable iterator for associative container' + node = self.val['_M_node'].cast(self.link_type).dereference() + return str(get_value_from_Rb_tree_node(node)) + +class StdDebugIteratorPrinter: + "Print a debug enabled version of an iterator" + + def __init__ (self, typename, val): + self.val = val + + # Just strip away the encapsulating __gnu_debug::_Safe_iterator + # and return the wrapped iterator value. + def to_string (self): + base_type = gdb.lookup_type('__gnu_debug::_Safe_iterator_base') + itype = self.val.type.template_argument(0) + safe_seq = self.val.cast(base_type)['_M_sequence'] + if not safe_seq: + return str(self.val.cast(itype)) + if self.val['_M_version'] != safe_seq['_M_version']: + return "invalid iterator" + return str(self.val.cast(itype)) + +def num_elements(num): + """Return either "1 element" or "N elements" depending on the argument.""" + return '1 element' if num == 1 else '%d elements' % num + +class StdMapPrinter: + "Print a std::map or std::multimap" + + # Turn an RbtreeIterator into a pretty-print iterator. + class _iter(Iterator): + def __init__(self, rbiter, type): + self.rbiter = rbiter + self.count = 0 + self.type = type + + def __iter__(self): + return self + + def __next__(self): + if self.count % 2 == 0: + n = next(self.rbiter) + n = n.cast(self.type).dereference() + n = get_value_from_Rb_tree_node(n) + self.pair = n + item = n['first'] + else: + item = self.pair['second'] + result = ('[%d]' % self.count, item) + self.count = self.count + 1 + return result + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + + def to_string (self): + return '%s with %s' % (self.typename, + num_elements(len(RbtreeIterator (self.val)))) + + def children (self): + node = lookup_node_type('_Rb_tree_node', self.val.type).pointer() + return self._iter (RbtreeIterator (self.val), node) + + def display_hint (self): + return 'map' + +class StdSetPrinter: + "Print a std::set or std::multiset" + + # Turn an RbtreeIterator into a pretty-print iterator. + class _iter(Iterator): + def __init__(self, rbiter, type): + self.rbiter = rbiter + self.count = 0 + self.type = type + + def __iter__(self): + return self + + def __next__(self): + item = next(self.rbiter) + item = item.cast(self.type).dereference() + item = get_value_from_Rb_tree_node(item) + # FIXME: this is weird ... what to do? + # Maybe a 'set' display hint? + result = ('[%d]' % self.count, item) + self.count = self.count + 1 + return result + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + + def to_string (self): + return '%s with %s' % (self.typename, + num_elements(len(RbtreeIterator (self.val)))) + + def children (self): + node = lookup_node_type('_Rb_tree_node', self.val.type).pointer() + return self._iter (RbtreeIterator (self.val), node) + +class StdBitsetPrinter: + "Print a std::bitset" + + def __init__(self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + + def to_string (self): + # If template_argument handled values, we could print the + # size. Or we could use a regexp on the type. + return '%s' % (self.typename) + + def children (self): + try: + # An empty bitset may not have any members which will + # result in an exception being thrown. + words = self.val['_M_w'] + except: + return [] + + wtype = words.type + + # The _M_w member can be either an unsigned long, or an + # array. This depends on the template specialization used. + # If it is a single long, convert to a single element list. + if wtype.code == gdb.TYPE_CODE_ARRAY: + tsize = wtype.target ().sizeof + else: + words = [words] + tsize = wtype.sizeof + + nwords = wtype.sizeof / tsize + result = [] + byte = 0 + while byte < nwords: + w = words[byte] + bit = 0 + while w != 0: + if (w & 1) != 0: + # Another spot where we could use 'set'? + result.append(('[%d]' % (byte * tsize * 8 + bit), 1)) + bit = bit + 1 + w = w >> 1 + byte = byte + 1 + return result + +class StdDequePrinter: + "Print a std::deque" + + class _iter(Iterator): + def __init__(self, node, start, end, last, buffer_size): + self.node = node + self.p = start + self.end = end + self.last = last + self.buffer_size = buffer_size + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.p == self.last: + raise StopIteration + + result = ('[%d]' % self.count, self.p.dereference()) + self.count = self.count + 1 + + # Advance the 'cur' pointer. + self.p = self.p + 1 + if self.p == self.end: + # If we got to the end of this bucket, move to the + # next bucket. + self.node = self.node + 1 + self.p = self.node[0] + self.end = self.p + self.buffer_size + + return result + + def __init__(self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + self.elttype = val.type.template_argument(0) + size = self.elttype.sizeof + if size < 512: + self.buffer_size = int (512 / size) + else: + self.buffer_size = 1 + + def to_string(self): + start = self.val['_M_impl']['_M_start'] + end = self.val['_M_impl']['_M_finish'] + + delta_n = end['_M_node'] - start['_M_node'] - 1 + delta_s = start['_M_last'] - start['_M_cur'] + delta_e = end['_M_cur'] - end['_M_first'] + + size = self.buffer_size * delta_n + delta_s + delta_e + + return '%s with %s' % (self.typename, num_elements(long(size))) + + def children(self): + start = self.val['_M_impl']['_M_start'] + end = self.val['_M_impl']['_M_finish'] + return self._iter(start['_M_node'], start['_M_cur'], start['_M_last'], + end['_M_cur'], self.buffer_size) + + def display_hint (self): + return 'array' + +class StdDequeIteratorPrinter: + "Print std::deque::iterator" + + def __init__(self, typename, val): + self.val = val + + def to_string(self): + if not self.val['_M_cur']: + return 'non-dereferenceable iterator for std::deque' + return str(self.val['_M_cur'].dereference()) + +class StdStringPrinter: + "Print a std::basic_string of some kind" + + def __init__(self, typename, val): + self.val = val + self.new_string = typename.find("::__cxx11::basic_string") != -1 + + def to_string(self): + # Make sure &string works, too. + type = self.val.type + if type.code == gdb.TYPE_CODE_REF: + type = type.target () + + # Calculate the length of the string so that to_string returns + # the string according to length, not according to first null + # encountered. + ptr = self.val ['_M_dataplus']['_M_p'] + if self.new_string: + length = self.val['_M_string_length'] + # https://sourceware.org/bugzilla/show_bug.cgi?id=17728 + ptr = ptr.cast(ptr.type.strip_typedefs()) + else: + realtype = type.unqualified ().strip_typedefs () + reptype = gdb.lookup_type (str (realtype) + '::_Rep').pointer () + header = ptr.cast(reptype) - 1 + length = header.dereference ()['_M_length'] + if hasattr(ptr, "lazy_string"): + return ptr.lazy_string (length = length) + return ptr.string (length = length) + + def display_hint (self): + return 'string' + +class Tr1HashtableIterator(Iterator): + def __init__ (self, hashtable): + self.buckets = hashtable['_M_buckets'] + self.bucket = 0 + self.bucket_count = hashtable['_M_bucket_count'] + self.node_type = find_type(hashtable.type, '_Node').pointer() + self.node = 0 + while self.bucket != self.bucket_count: + self.node = self.buckets[self.bucket] + if self.node: + break + self.bucket = self.bucket + 1 + + def __iter__ (self): + return self + + def __next__ (self): + if self.node == 0: + raise StopIteration + node = self.node.cast(self.node_type) + result = node.dereference()['_M_v'] + self.node = node.dereference()['_M_next']; + if self.node == 0: + self.bucket = self.bucket + 1 + while self.bucket != self.bucket_count: + self.node = self.buckets[self.bucket] + if self.node: + break + self.bucket = self.bucket + 1 + return result + +class StdHashtableIterator(Iterator): + def __init__(self, hashtable): + self.node = hashtable['_M_before_begin']['_M_nxt'] + valtype = hashtable.type.template_argument(1) + cached = hashtable.type.template_argument(9).template_argument(0) + node_type = lookup_templ_spec('std::__detail::_Hash_node', str(valtype), + 'true' if cached else 'false') + self.node_type = node_type.pointer() + + def __iter__(self): + return self + + def __next__(self): + if self.node == 0: + raise StopIteration + elt = self.node.cast(self.node_type).dereference() + self.node = elt['_M_nxt'] + valptr = elt['_M_storage'].address + valptr = valptr.cast(elt.type.template_argument(0).pointer()) + return valptr.dereference() + +class Tr1UnorderedSetPrinter: + "Print a std::unordered_set or tr1::unordered_set" + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + + def hashtable (self): + if self.typename.startswith('std::tr1'): + return self.val + return self.val['_M_h'] + + def to_string (self): + count = self.hashtable()['_M_element_count'] + return '%s with %s' % (self.typename, num_elements(count)) + + @staticmethod + def format_count (i): + return '[%d]' % i + + def children (self): + counter = imap (self.format_count, itertools.count()) + if self.typename.startswith('std::tr1'): + return izip (counter, Tr1HashtableIterator (self.hashtable())) + return izip (counter, StdHashtableIterator (self.hashtable())) + +class Tr1UnorderedMapPrinter: + "Print a std::unordered_map or tr1::unordered_map" + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + + def hashtable (self): + if self.typename.startswith('std::tr1'): + return self.val + return self.val['_M_h'] + + def to_string (self): + count = self.hashtable()['_M_element_count'] + return '%s with %s' % (self.typename, num_elements(count)) + + @staticmethod + def flatten (list): + for elt in list: + for i in elt: + yield i + + @staticmethod + def format_one (elt): + return (elt['first'], elt['second']) + + @staticmethod + def format_count (i): + return '[%d]' % i + + def children (self): + counter = imap (self.format_count, itertools.count()) + # Map over the hash table and flatten the result. + if self.typename.startswith('std::tr1'): + data = self.flatten (imap (self.format_one, Tr1HashtableIterator (self.hashtable()))) + # Zip the two iterators together. + return izip (counter, data) + data = self.flatten (imap (self.format_one, StdHashtableIterator (self.hashtable()))) + # Zip the two iterators together. + return izip (counter, data) + + def display_hint (self): + return 'map' + +class StdForwardListPrinter: + "Print a std::forward_list" + + class _iterator(Iterator): + def __init__(self, nodetype, head): + self.nodetype = nodetype + self.base = head['_M_next'] + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.base == 0: + raise StopIteration + elt = self.base.cast(self.nodetype).dereference() + self.base = elt['_M_next'] + count = self.count + self.count = self.count + 1 + valptr = elt['_M_storage'].address + valptr = valptr.cast(elt.type.template_argument(0).pointer()) + return ('[%d]' % count, valptr.dereference()) + + def __init__(self, typename, val): + self.val = val + self.typename = strip_versioned_namespace(typename) + + def children(self): + nodetype = lookup_node_type('_Fwd_list_node', self.val.type).pointer() + return self._iterator(nodetype, self.val['_M_impl']['_M_head']) + + def to_string(self): + if self.val['_M_impl']['_M_head']['_M_next'] == 0: + return 'empty %s' % self.typename + return '%s' % self.typename + +class SingleObjContainerPrinter(object): + "Base class for printers of containers of single objects" + + def __init__ (self, val, viz, hint = None): + self.contained_value = val + self.visualizer = viz + self.hint = hint + + def _recognize(self, type): + """Return TYPE as a string after applying type printers""" + global _use_type_printing + if not _use_type_printing: + return str(type) + return gdb.types.apply_type_recognizers(gdb.types.get_type_recognizers(), + type) or str(type) + + class _contained(Iterator): + def __init__ (self, val): + self.val = val + + def __iter__ (self): + return self + + def __next__(self): + if self.val is None: + raise StopIteration + retval = self.val + self.val = None + return ('[contained value]', retval) + + def children (self): + if self.contained_value is None: + return self._contained (None) + if hasattr (self.visualizer, 'children'): + return self.visualizer.children () + return self._contained (self.contained_value) + + def display_hint (self): + # if contained value is a map we want to display in the same way + if hasattr (self.visualizer, 'children') and hasattr (self.visualizer, 'display_hint'): + return self.visualizer.display_hint () + return self.hint + +def function_pointer_to_name(f): + "Find the name of the function referred to by the gdb.Value f, " + " which should contain a function pointer from the program." + + # Turn the function pointer into an actual address. + # This is needed to unpack ppc64 function descriptors. + f = f.dereference().address + + if sys.version_info[0] == 2: + # Older versions of GDB need to use long for Python 2, + # because int(f) on 64-bit big-endian values raises a + # gdb.error saying "Cannot convert value to int." + f = long(f) + else: + f = int(f) + + try: + # If the function can't be found older versions of GDB raise a + # RuntimeError saying "Cannot locate object file for block." + return gdb.block_for_pc(f).function.name + except: + return None + +class StdExpAnyPrinter(SingleObjContainerPrinter): + "Print a std::any or std::experimental::any" + + def __init__ (self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.typename = re.sub('^std::experimental::fundamentals_v\d::', 'std::experimental::', self.typename, 1) + self.val = val + self.contained_type = None + contained_value = None + visualizer = None + mgr = self.val['_M_manager'] + if mgr != 0: + func = function_pointer_to_name(mgr) + if not func: + raise ValueError("Invalid function pointer in %s" % (self.typename)) + rx = r"""({0}::_Manager_\w+<.*>)::_S_manage\((enum )?{0}::_Op, (const {0}|{0} const) ?\*, (union )?{0}::_Arg ?\*\)""".format(typename) + m = re.match(rx, func) + if not m: + raise ValueError("Unknown manager function in %s" % self.typename) + + mgrname = m.group(1) + # FIXME need to expand 'std::string' so that gdb.lookup_type works + if 'std::string' in mgrname: + mgrname = re.sub("std::string(?!\w)", str(gdb.lookup_type('std::string').strip_typedefs()), m.group(1)) + + mgrtype = gdb.lookup_type(mgrname) + self.contained_type = mgrtype.template_argument(0) + valptr = None + if '::_Manager_internal' in mgrname: + valptr = self.val['_M_storage']['_M_buffer'].address + elif '::_Manager_external' in mgrname: + valptr = self.val['_M_storage']['_M_ptr'] + else: + raise ValueError("Unknown manager function in %s" % self.typename) + contained_value = valptr.cast(self.contained_type.pointer()).dereference() + visualizer = gdb.default_visualizer(contained_value) + super(StdExpAnyPrinter, self).__init__ (contained_value, visualizer) + + def to_string (self): + if self.contained_type is None: + return '%s [no contained value]' % self.typename + desc = "%s containing " % self.typename + if hasattr (self.visualizer, 'children'): + return desc + self.visualizer.to_string () + valtype = self._recognize (self.contained_type) + return desc + strip_versioned_namespace(str(valtype)) + +class StdExpOptionalPrinter(SingleObjContainerPrinter): + "Print a std::optional or std::experimental::optional" + + def __init__ (self, typename, val): + valtype = self._recognize (val.type.template_argument(0)) + typename = strip_versioned_namespace(typename) + self.typename = re.sub('^std::(experimental::|)(fundamentals_v\d::|)(.*)', r'std::\1\3<%s>' % valtype, typename, 1) + payload = val['_M_payload'] + if self.typename.startswith('std::experimental'): + engaged = val['_M_engaged'] + contained_value = payload + else: + engaged = payload['_M_engaged'] + contained_value = payload['_M_payload'] + try: + # Since GCC 9 + contained_value = contained_value['_M_value'] + except: + pass + visualizer = gdb.default_visualizer (contained_value) + if not engaged: + contained_value = None + super (StdExpOptionalPrinter, self).__init__ (contained_value, visualizer) + + def to_string (self): + if self.contained_value is None: + return "%s [no contained value]" % self.typename + if hasattr (self.visualizer, 'children'): + return "%s containing %s" % (self.typename, + self.visualizer.to_string()) + return self.typename + +class StdVariantPrinter(SingleObjContainerPrinter): + "Print a std::variant" + + def __init__(self, typename, val): + alternatives = get_template_arg_list(val.type) + self.typename = strip_versioned_namespace(typename) + self.typename = "%s<%s>" % (self.typename, ', '.join([self._recognize(alt) for alt in alternatives])) + self.index = val['_M_index'] + if self.index >= len(alternatives): + self.contained_type = None + contained_value = None + visualizer = None + else: + self.contained_type = alternatives[int(self.index)] + addr = val['_M_u']['_M_first']['_M_storage'].address + contained_value = addr.cast(self.contained_type.pointer()).dereference() + visualizer = gdb.default_visualizer(contained_value) + super (StdVariantPrinter, self).__init__(contained_value, visualizer, 'array') + + def to_string(self): + if self.contained_value is None: + return "%s [no contained value]" % self.typename + if hasattr(self.visualizer, 'children'): + return "%s [index %d] containing %s" % (self.typename, self.index, + self.visualizer.to_string()) + return "%s [index %d]" % (self.typename, self.index) + +class StdNodeHandlePrinter(SingleObjContainerPrinter): + "Print a container node handle" + + def __init__(self, typename, val): + self.value_type = val.type.template_argument(1) + nodetype = val.type.template_argument(2).template_argument(0) + self.is_rb_tree_node = is_specialization_of(nodetype.name, '_Rb_tree_node') + self.is_map_node = val.type.template_argument(0) != self.value_type + nodeptr = val['_M_ptr'] + if nodeptr: + if self.is_rb_tree_node: + contained_value = get_value_from_Rb_tree_node(nodeptr.dereference()) + else: + contained_value = get_value_from_aligned_membuf(nodeptr['_M_storage'], + self.value_type) + visualizer = gdb.default_visualizer(contained_value) + else: + contained_value = None + visualizer = None + optalloc = val['_M_alloc'] + self.alloc = optalloc['_M_payload'] if optalloc['_M_engaged'] else None + super(StdNodeHandlePrinter, self).__init__(contained_value, visualizer, + 'array') + + def to_string(self): + desc = 'node handle for ' + if not self.is_rb_tree_node: + desc += 'unordered ' + if self.is_map_node: + desc += 'map'; + else: + desc += 'set'; + + if self.contained_value: + desc += ' with element' + if hasattr(self.visualizer, 'children'): + return "%s = %s" % (desc, self.visualizer.to_string()) + return desc + else: + return 'empty %s' % desc + +class StdExpStringViewPrinter: + "Print a std::basic_string_view or std::experimental::basic_string_view" + + def __init__ (self, typename, val): + self.val = val + + def to_string (self): + ptr = self.val['_M_str'] + len = self.val['_M_len'] + if hasattr (ptr, "lazy_string"): + return ptr.lazy_string (length = len) + return ptr.string (length = len) + + def display_hint (self): + return 'string' + +class StdExpPathPrinter: + "Print a std::experimental::filesystem::path" + + def __init__ (self, typename, val): + self.val = val + self.typename = typename + start = self.val['_M_cmpts']['_M_impl']['_M_start'] + finish = self.val['_M_cmpts']['_M_impl']['_M_finish'] + self.num_cmpts = int (finish - start) + + def _path_type(self): + t = str(self.val['_M_type']) + if t[-9:] == '_Root_dir': + return "root-directory" + if t[-10:] == '_Root_name': + return "root-name" + return None + + def to_string (self): + path = "%s" % self.val ['_M_pathname'] + if self.num_cmpts == 0: + t = self._path_type() + if t: + path = '%s [%s]' % (path, t) + return "experimental::filesystem::path %s" % path + + class _iterator(Iterator): + def __init__(self, cmpts, pathtype): + self.pathtype = pathtype + self.item = cmpts['_M_impl']['_M_start'] + self.finish = cmpts['_M_impl']['_M_finish'] + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.item == self.finish: + raise StopIteration + item = self.item.dereference() + count = self.count + self.count = self.count + 1 + self.item = self.item + 1 + path = item['_M_pathname'] + t = StdExpPathPrinter(self.pathtype, item)._path_type() + if not t: + t = count + return ('[%s]' % t, path) + + def children(self): + return self._iterator(self.val['_M_cmpts'], self.typename) + +class StdPathPrinter: + "Print a std::filesystem::path" + + def __init__ (self, typename, val): + self.val = val + self.typename = typename + impl = unique_ptr_get(self.val['_M_cmpts']['_M_impl']) + self.type = impl.cast(gdb.lookup_type('uintptr_t')) & 3 + if self.type == 0: + self.impl = impl + else: + self.impl = None + + def _path_type(self): + t = str(self.type.cast(gdb.lookup_type(self.typename + '::_Type'))) + if t[-9:] == '_Root_dir': + return "root-directory" + if t[-10:] == '_Root_name': + return "root-name" + return None + + def to_string (self): + path = "%s" % self.val ['_M_pathname'] + if self.type != 0: + t = self._path_type() + if t: + path = '%s [%s]' % (path, t) + return "filesystem::path %s" % path + + class _iterator(Iterator): + def __init__(self, impl, pathtype): + self.pathtype = pathtype + if impl: + # We can't access _Impl::_M_size because _Impl is incomplete + # so cast to int* to access the _M_size member at offset zero, + int_type = gdb.lookup_type('int') + cmpt_type = gdb.lookup_type(pathtype+'::_Cmpt') + char_type = gdb.lookup_type('char') + impl = impl.cast(int_type.pointer()) + size = impl.dereference() + #self.capacity = (impl + 1).dereference() + if hasattr(gdb.Type, 'alignof'): + sizeof_Impl = max(2 * int_type.sizeof, cmpt_type.alignof) + else: + sizeof_Impl = 2 * int_type.sizeof + begin = impl.cast(char_type.pointer()) + sizeof_Impl + self.item = begin.cast(cmpt_type.pointer()) + self.finish = self.item + size + self.count = 0 + else: + self.item = None + self.finish = None + + def __iter__(self): + return self + + def __next__(self): + if self.item == self.finish: + raise StopIteration + item = self.item.dereference() + count = self.count + self.count = self.count + 1 + self.item = self.item + 1 + path = item['_M_pathname'] + t = StdPathPrinter(self.pathtype, item)._path_type() + if not t: + t = count + return ('[%s]' % t, path) + + def children(self): + return self._iterator(self.impl, self.typename) + + +class StdPairPrinter: + "Print a std::pair object, with 'first' and 'second' as children" + + def __init__(self, typename, val): + self.val = val + + class _iter(Iterator): + "An iterator for std::pair types. Returns 'first' then 'second'." + + def __init__(self, val): + self.val = val + self.which = 'first' + + def __iter__(self): + return self + + def __next__(self): + if self.which is None: + raise StopIteration + which = self.which + if which == 'first': + self.which = 'second' + else: + self.which = None + return (which, self.val[which]) + + def children(self): + return self._iter(self.val) + + def to_string(self): + return None + +class StdCmpCatPrinter: + "Print a comparison category object" + + def __init__ (self, typename, val): + self.typename = typename[typename.rfind(':')+1:] + self.val = val['_M_value'] + + def to_string (self): + if self.typename == 'strong_ordering' and self.val == 0: + name = 'equal' + else: + names = {2:'unordered', -1:'less', 0:'equivalent', 1:'greater'} + name = names[int(self.val)] + return 'std::{}::{}'.format(self.typename, name) + +class StdErrorCodePrinter: + "Print a std::error_code or std::error_condition" + + _system_is_posix = None # Whether std::system_category() use errno values. + + def __init__ (self, typename, val): + self.val = val + self.typename = strip_versioned_namespace(typename) + # Do this only once ... + if StdErrorCodePrinter._system_is_posix is None: + try: + import posix + StdErrorCodePrinter._system_is_posix = True + except ImportError: + StdErrorCodePrinter._system_is_posix = False + + @staticmethod + def _find_errc_enum(name): + typ = gdb.lookup_type(name) + if typ is not None and typ.code == gdb.TYPE_CODE_ENUM: + return typ + return None + + @classmethod + def _find_standard_errc_enum(cls, name): + for ns in ['', _versioned_namespace]: + try: + qname = 'std::{}{}'.format(ns, name) + return cls._find_errc_enum(qname) + except RuntimeError: + pass + + @classmethod + def _match_net_ts_category(cls, cat): + net_cats = ['stream', 'socket', 'ip::resolver'] + for c in net_cats: + func = c + '_category()' + for ns in ['', _versioned_namespace]: + ns = 'std::{}experimental::net::v1'.format(ns) + sym = gdb.lookup_symbol('{}::{}::__c'.format(ns, func))[0] + if sym is not None: + if cat == sym.value().address: + name = 'net::' + func + enum = cls._find_errc_enum('{}::{}_errc'.format(ns, c)) + return (name, enum) + return (None, None) + + @classmethod + def _category_info(cls, cat): + "Return details of a std::error_category" + + name = None + enum = None + is_errno = False + + # Try these first, or we get "warning: RTTI symbol not found" when + # using cat.dynamic_type on the local class types for Net TS categories. + func, enum = cls._match_net_ts_category(cat) + if func is not None: + return (None, func, enum, is_errno) + + # This might give a warning for a program-defined category defined as + # a local class, but there doesn't seem to be any way to avoid that. + typ = cat.dynamic_type.target() + # Shortcuts for the known categories defined by libstdc++. + if typ.tag.endswith('::generic_error_category'): + name = 'generic' + is_errno = True + if typ.tag.endswith('::system_error_category'): + name = 'system' + is_errno = cls._system_is_posix + if typ.tag.endswith('::future_error_category'): + name = 'future' + enum = cls._find_standard_errc_enum('future_errc') + if typ.tag.endswith('::io_error_category'): + name = 'io' + enum = cls._find_standard_errc_enum('io_errc') + + if name is None: + try: + # Want to call std::error_category::name() override, but it's + # unsafe: https://sourceware.org/bugzilla/show_bug.cgi?id=28856 + # gdb.set_convenience_variable('__cat', cat) + # return '"%s"' % gdb.parse_and_eval('$__cat->name()').string() + pass + except: + pass + return (name, typ.tag, enum, is_errno) + + @staticmethod + def _unqualified_name(name): + "Strip any nested-name-specifier from NAME to give an unqualified name" + return name.split('::')[-1] + + def to_string (self): + value = self.val['_M_value'] + cat = self.val['_M_cat'] + name, alt_name, enum, is_errno = self._category_info(cat) + if value == 0: + default_cats = { 'error_code' : 'system', + 'error_condition' : 'generic' } + if name == default_cats[self._unqualified_name(self.typename)]: + return self.typename + ' = { }' # default-constructed value + + strval = str(value) + if is_errno and value != 0: + try: + strval = errno.errorcode[int(value)] + except: + pass + elif enum is not None: + strval = self._unqualified_name(str(value.cast(enum))) + + if name is not None: + name = '"%s"' % name + else: + name = alt_name + return '%s = {%s: %s}' % (self.typename, name, strval) + + +class StdRegexStatePrinter: + "Print a state node in the NFA for a std::regex" + + def __init__ (self, typename, val): + self.val = val + self.typename = typename + + def to_string (self): + opcode = str(self.val['_M_opcode']) + if opcode: + opcode = opcode[25:] + next_id = self.val['_M_next'] + + variants = {'repeat':'alt', 'alternative':'alt', + 'subexpr_begin':'subexpr', 'subexpr_end':'subexpr', + 'line_begin_assertion':None, 'line_end_assertion':None, + 'word_boundary':'neg', 'subexpr_lookahead':'neg', + 'backref':'backref_index', + 'match':None, 'accept':None, + 'dummy':None, 'unknown':None + } + v = variants[opcode] + + s = "opcode={}, next={}".format(opcode, next_id) + if v is not None and self.val['_M_' + v] is not None: + s = "{}, {}={}".format(s, v, self.val['_M_' + v]) + return "{%s}" % (s) + +class StdSpanPrinter: + "Print a std::span" + + class iterator(Iterator): + def __init__(self, begin, size): + self.count = 0 + self.begin = begin + self.size = size + + def __iter__ (self): + return self + + def __next__ (self): + if self.count == self.size: + raise StopIteration + + count = self.count + self.count = self.count + 1 + return '[%d]' % count, (self.begin + count).dereference() + + def __init__(self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + if val.type.template_argument(1) == gdb.parse_and_eval('static_cast(-1)'): + self.size = val['_M_extent']['_M_extent_value'] + else: + self.size = val.type.template_argument(1) + + def to_string(self): + return '%s of length %d' % (self.typename, self.size) + + def children(self): + return self.iterator(self.val['_M_ptr'], self.size) + + def display_hint(self): + return 'array' + +class StdInitializerListPrinter: + "Print a std::initializer_list" + + def __init__(self, typename, val): + self.typename = typename + self.val = val + self.size = val['_M_len'] + + def to_string(self): + return '%s of length %d' % (self.typename, self.size) + + def children(self): + return StdSpanPrinter.iterator(self.val['_M_array'], self.size) + + def display_hint(self): + return 'array' + +class StdAtomicPrinter: + "Print a std:atomic" + + def __init__(self, typename, val): + self.typename = strip_versioned_namespace(typename) + self.val = val + self.shptr_printer = None + self.value_type = self.val.type.template_argument(0) + if self.value_type.tag is not None: + typ = strip_versioned_namespace(self.value_type.tag) + if typ.startswith('std::shared_ptr<') or typ.startswith('std::weak_ptr<'): + impl = val['_M_impl'] + self.shptr_printer = SharedPointerPrinter(typename, impl) + self.children = self._shptr_children + + def _shptr_children(self): + return SmartPtrIterator(self.shptr_printer.pointer) + + def to_string(self): + if self.shptr_printer is not None: + return self.shptr_printer.to_string() + + if self.value_type.code == gdb.TYPE_CODE_INT: + val = self.val['_M_i'] + elif self.value_type.code == gdb.TYPE_CODE_FLT: + val = self.val['_M_fp'] + elif self.value_type.code == gdb.TYPE_CODE_PTR: + val = self.val['_M_b']['_M_p'] + elif self.value_type.code == gdb.TYPE_CODE_BOOL: + val = self.val['_M_base']['_M_i'] + else: + val = self.val['_M_i'] + return '%s<%s> = { %s }' % (self.typename, str(self.value_type), val) + +# A "regular expression" printer which conforms to the +# "SubPrettyPrinter" protocol from gdb.printing. +class RxPrinter(object): + def __init__(self, name, function): + super(RxPrinter, self).__init__() + self.name = name + self.function = function + self.enabled = True + + def invoke(self, value): + if not self.enabled: + return None + + if value.type.code == gdb.TYPE_CODE_REF: + if hasattr(gdb.Value,"referenced_value"): + value = value.referenced_value() + + return self.function(self.name, value) + +# A pretty-printer that conforms to the "PrettyPrinter" protocol from +# gdb.printing. It can also be used directly as an old-style printer. +class Printer(object): + def __init__(self, name): + super(Printer, self).__init__() + self.name = name + self.subprinters = [] + self.lookup = {} + self.enabled = True + self.compiled_rx = re.compile('^([a-zA-Z0-9_:]+)(<.*>)?$') + + def add(self, name, function): + # A small sanity check. + # FIXME + if not self.compiled_rx.match(name): + raise ValueError('libstdc++ programming error: "%s" does not match' % name) + printer = RxPrinter(name, function) + self.subprinters.append(printer) + self.lookup[name] = printer + + # Add a name using _GLIBCXX_BEGIN_NAMESPACE_VERSION. + def add_version(self, base, name, function): + self.add(base + name, function) + if _versioned_namespace and not '__cxx11' in base: + vbase = re.sub('^(std|__gnu_cxx)::', r'\g<0>%s' % _versioned_namespace, base) + self.add(vbase + name, function) + + # Add a name using _GLIBCXX_BEGIN_NAMESPACE_CONTAINER. + def add_container(self, base, name, function): + self.add_version(base, name, function) + self.add_version(base + '__cxx1998::', name, function) + + @staticmethod + def get_basic_type(type): + # If it points to a reference, get the reference. + if type.code == gdb.TYPE_CODE_REF: + type = type.target () + + # Get the unqualified type, stripped of typedefs. + type = type.unqualified ().strip_typedefs () + + return type.tag + + def __call__(self, val): + typename = self.get_basic_type(val.type) + if not typename: + return None + + # All the types we match are template types, so we can use a + # dictionary. + match = self.compiled_rx.match(typename) + if not match: + return None + + basename = match.group(1) + + if val.type.code == gdb.TYPE_CODE_REF: + if hasattr(gdb.Value,"referenced_value"): + val = val.referenced_value() + + if basename in self.lookup: + return self.lookup[basename].invoke(val) + + # Cannot find a pretty printer. Return None. + return None + +libstdcxx_printer = None + +class TemplateTypePrinter(object): + r""" + A type printer for class templates with default template arguments. + + Recognizes specializations of class templates and prints them without + any template arguments that use a default template argument. + Type printers are recursively applied to the template arguments. + + e.g. replace "std::vector >" with "std::vector". + """ + + def __init__(self, name, defargs): + self.name = name + self.defargs = defargs + self.enabled = True + + class _recognizer(object): + "The recognizer class for TemplateTypePrinter." + + def __init__(self, name, defargs): + self.name = name + self.defargs = defargs + # self.type_obj = None + + def recognize(self, type_obj): + """ + If type_obj is a specialization of self.name that uses all the + default template arguments for the class template, then return + a string representation of the type without default arguments. + Otherwise, return None. + """ + + if type_obj.tag is None: + return None + + if not type_obj.tag.startswith(self.name): + return None + + template_args = get_template_arg_list(type_obj) + displayed_args = [] + require_defaulted = False + for n in range(len(template_args)): + # The actual template argument in the type: + targ = template_args[n] + # The default template argument for the class template: + defarg = self.defargs.get(n) + if defarg is not None: + # Substitute other template arguments into the default: + defarg = defarg.format(*template_args) + # Fail to recognize the type (by returning None) + # unless the actual argument is the same as the default. + try: + if targ != gdb.lookup_type(defarg): + return None + except gdb.error: + # Type lookup failed, just use string comparison: + if targ.tag != defarg: + return None + # All subsequent args must have defaults: + require_defaulted = True + elif require_defaulted: + return None + else: + # Recursively apply recognizers to the template argument + # and add it to the arguments that will be displayed: + displayed_args.append(self._recognize_subtype(targ)) + + # This assumes no class templates in the nested-name-specifier: + template_name = type_obj.tag[0:type_obj.tag.find('<')] + template_name = strip_inline_namespaces(template_name) + + return template_name + '<' + ', '.join(displayed_args) + '>' + + def _recognize_subtype(self, type_obj): + """Convert a gdb.Type to a string by applying recognizers, + or if that fails then simply converting to a string.""" + + if type_obj.code == gdb.TYPE_CODE_PTR: + return self._recognize_subtype(type_obj.target()) + '*' + if type_obj.code == gdb.TYPE_CODE_ARRAY: + type_str = self._recognize_subtype(type_obj.target()) + if str(type_obj.strip_typedefs()).endswith('[]'): + return type_str + '[]' # array of unknown bound + return "%s[%d]" % (type_str, type_obj.range()[1] + 1) + if type_obj.code == gdb.TYPE_CODE_REF: + return self._recognize_subtype(type_obj.target()) + '&' + if hasattr(gdb, 'TYPE_CODE_RVALUE_REF'): + if type_obj.code == gdb.TYPE_CODE_RVALUE_REF: + return self._recognize_subtype(type_obj.target()) + '&&' + + type_str = gdb.types.apply_type_recognizers( + gdb.types.get_type_recognizers(), type_obj) + if type_str: + return type_str + return str(type_obj) + + def instantiate(self): + "Return a recognizer object for this type printer." + return self._recognizer(self.name, self.defargs) + +def add_one_template_type_printer(obj, name, defargs): + r""" + Add a type printer for a class template with default template arguments. + + Args: + name (str): The template-name of the class template. + defargs (dict int:string) The default template arguments. + + Types in defargs can refer to the Nth template-argument using {N} + (with zero-based indices). + + e.g. 'unordered_map' has these defargs: + { 2: 'std::hash<{0}>', + 3: 'std::equal_to<{0}>', + 4: 'std::allocator >' } + + """ + printer = TemplateTypePrinter('std::'+name, defargs) + gdb.types.register_type_printer(obj, printer) + + # Add type printer for same type in debug namespace: + printer = TemplateTypePrinter('std::__debug::'+name, defargs) + gdb.types.register_type_printer(obj, printer) + + if _versioned_namespace and not '__cxx11' in name: + # Add second type printer for same type in versioned namespace: + ns = 'std::' + _versioned_namespace + # PR 86112 Cannot use dict comprehension here: + defargs = dict((n, d.replace('std::', ns)) for (n,d) in defargs.items()) + printer = TemplateTypePrinter(ns+name, defargs) + gdb.types.register_type_printer(obj, printer) + +class FilteringTypePrinter(object): + r""" + A type printer that uses typedef names for common template specializations. + + Args: + template (str): The class template to recognize. + name (str): The typedef-name that will be used instead. + targ1 (str): The first template argument. + If arg1 is provided (not None), match only template specializations + with this type as the first template argument, + e.g. if template='basic_string is the same type as + std::istream then print it as std::istream. + """ + + def __init__(self, template, name, targ1): + self.template = template + self.name = name + self.targ1 = targ1 + self.enabled = True + + class _recognizer(object): + "The recognizer class for FilteringTypePrinter." + + def __init__(self, template, name, targ1): + self.template = template + self.name = name + self.targ1 = targ1 + self.type_obj = None + + def recognize(self, type_obj): + """ + If type_obj starts with self.template and is the same type as + self.name then return self.name, otherwise None. + """ + if type_obj.tag is None: + return None + + if self.type_obj is None: + if self.targ1 is not None: + if not type_obj.tag.startswith('{}<{}'.format(self.template, self.targ1)): + # Filter didn't match. + return None + elif not type_obj.tag.startswith(self.template): + # Filter didn't match. + return None + + try: + self.type_obj = gdb.lookup_type(self.name).strip_typedefs() + except: + pass + + if self.type_obj is None: + return None + + if gdb.types.get_basic_type(self.type_obj) == gdb.types.get_basic_type(type_obj): + return strip_inline_namespaces(self.name) + + # Workaround ambiguous typedefs matching both std:: and std::__cxx11:: symbols. + if self.template.split('::')[-1] == 'basic_string': + if self.type_obj.tag.replace('__cxx11::', '') == type_obj.tag.replace('__cxx11::', ''): + return strip_inline_namespaces(self.name) + + return None + + def instantiate(self): + "Return a recognizer object for this type printer." + return self._recognizer(self.template, self.name, self.targ1) + +def add_one_type_printer(obj, template, name, targ1 = None): + printer = FilteringTypePrinter('std::' + template, 'std::' + name, targ1) + gdb.types.register_type_printer(obj, printer) + if _versioned_namespace and not '__cxx11' in template: + ns = 'std::' + _versioned_namespace + printer = FilteringTypePrinter(ns + template, ns + name, targ1) + gdb.types.register_type_printer(obj, printer) + +def register_type_printers(obj): + global _use_type_printing + + if not _use_type_printing: + return + + # Add type printers for typedefs std::string, std::wstring etc. + for ch in (('', 'char'), + ('w', 'wchar_t'), + ('u8', 'char8_t'), + ('u16', 'char16_t'), + ('u32', 'char32_t')): + add_one_type_printer(obj, 'basic_string', ch[0] + 'string', ch[1]) + add_one_type_printer(obj, '__cxx11::basic_string', ch[0] + 'string', ch[1]) + # Typedefs for __cxx11::basic_string used to be in namespace __cxx11: + add_one_type_printer(obj, '__cxx11::basic_string', + '__cxx11::' + ch[0] + 'string', ch[1]) + add_one_type_printer(obj, 'basic_string_view', ch[0] + 'string_view', ch[1]) + + # Add type printers for typedefs std::istream, std::wistream etc. + for ch in (('', 'char'), ('w', 'wchar_t')): + for x in ('ios', 'streambuf', 'istream', 'ostream', 'iostream', + 'filebuf', 'ifstream', 'ofstream', 'fstream'): + add_one_type_printer(obj, 'basic_' + x, ch[0] + x, ch[1]) + for x in ('stringbuf', 'istringstream', 'ostringstream', + 'stringstream'): + add_one_type_printer(obj, 'basic_' + x, ch[0] + x, ch[1]) + # types are in __cxx11 namespace, but typedefs aren't: + add_one_type_printer(obj, '__cxx11::basic_' + x, ch[0] + x, ch[1]) + + # Add type printers for typedefs regex, wregex, cmatch, wcmatch etc. + for abi in ('', '__cxx11::'): + for ch in (('', 'char'), ('w', 'wchar_t')): + add_one_type_printer(obj, abi + 'basic_regex', abi + ch[0] + 'regex', ch[1]) + for ch in ('c', 's', 'wc', 'ws'): + add_one_type_printer(obj, abi + 'match_results', abi + ch + 'match') + for x in ('sub_match', 'regex_iterator', 'regex_token_iterator'): + add_one_type_printer(obj, abi + x, abi + ch + x) + + # Note that we can't have a printer for std::wstreampos, because + # it is the same type as std::streampos. + add_one_type_printer(obj, 'fpos', 'streampos') + + # Add type printers for typedefs. + for dur in ('nanoseconds', 'microseconds', 'milliseconds', + 'seconds', 'minutes', 'hours'): + add_one_type_printer(obj, 'duration', dur) + + # Add type printers for typedefs. + add_one_type_printer(obj, 'linear_congruential_engine', 'minstd_rand0') + add_one_type_printer(obj, 'linear_congruential_engine', 'minstd_rand') + add_one_type_printer(obj, 'mersenne_twister_engine', 'mt19937') + add_one_type_printer(obj, 'mersenne_twister_engine', 'mt19937_64') + add_one_type_printer(obj, 'subtract_with_carry_engine', 'ranlux24_base') + add_one_type_printer(obj, 'subtract_with_carry_engine', 'ranlux48_base') + add_one_type_printer(obj, 'discard_block_engine', 'ranlux24') + add_one_type_printer(obj, 'discard_block_engine', 'ranlux48') + add_one_type_printer(obj, 'shuffle_order_engine', 'knuth_b') + + # Add type printers for experimental::basic_string_view typedefs. + ns = 'experimental::fundamentals_v1::' + for ch in (('', 'char'), + ('w', 'wchar_t'), + ('u8', 'char8_t'), + ('u16', 'char16_t'), + ('u32', 'char32_t')): + add_one_type_printer(obj, ns + 'basic_string_view', + ns + ch[0] + 'string_view', ch[1]) + + # Do not show defaulted template arguments in class templates. + add_one_template_type_printer(obj, 'unique_ptr', + { 1: 'std::default_delete<{0}>' }) + add_one_template_type_printer(obj, 'deque', { 1: 'std::allocator<{0}>'}) + add_one_template_type_printer(obj, 'forward_list', { 1: 'std::allocator<{0}>'}) + add_one_template_type_printer(obj, 'list', { 1: 'std::allocator<{0}>'}) + add_one_template_type_printer(obj, '__cxx11::list', { 1: 'std::allocator<{0}>'}) + add_one_template_type_printer(obj, 'vector', { 1: 'std::allocator<{0}>'}) + add_one_template_type_printer(obj, 'map', + { 2: 'std::less<{0}>', + 3: 'std::allocator>' }) + add_one_template_type_printer(obj, 'multimap', + { 2: 'std::less<{0}>', + 3: 'std::allocator>' }) + add_one_template_type_printer(obj, 'set', + { 1: 'std::less<{0}>', 2: 'std::allocator<{0}>' }) + add_one_template_type_printer(obj, 'multiset', + { 1: 'std::less<{0}>', 2: 'std::allocator<{0}>' }) + add_one_template_type_printer(obj, 'unordered_map', + { 2: 'std::hash<{0}>', + 3: 'std::equal_to<{0}>', + 4: 'std::allocator>'}) + add_one_template_type_printer(obj, 'unordered_multimap', + { 2: 'std::hash<{0}>', + 3: 'std::equal_to<{0}>', + 4: 'std::allocator>'}) + add_one_template_type_printer(obj, 'unordered_set', + { 1: 'std::hash<{0}>', + 2: 'std::equal_to<{0}>', + 3: 'std::allocator<{0}>'}) + add_one_template_type_printer(obj, 'unordered_multiset', + { 1: 'std::hash<{0}>', + 2: 'std::equal_to<{0}>', + 3: 'std::allocator<{0}>'}) + +def register_libstdcxx_printers (obj): + "Register libstdc++ pretty-printers with objfile Obj." + + global _use_gdb_pp + global libstdcxx_printer + + if _use_gdb_pp: + gdb.printing.register_pretty_printer(obj, libstdcxx_printer) + else: + if obj is None: + obj = gdb + obj.pretty_printers.append(libstdcxx_printer) + + register_type_printers(obj) + +def build_libstdcxx_dictionary (): + global libstdcxx_printer + + libstdcxx_printer = Printer("libstdc++-v6") + + # libstdc++ objects requiring pretty-printing. + # In order from: + # http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/a01847.html + libstdcxx_printer.add_version('std::', 'basic_string', StdStringPrinter) + libstdcxx_printer.add_version('std::__cxx11::', 'basic_string', StdStringPrinter) + libstdcxx_printer.add_container('std::', 'bitset', StdBitsetPrinter) + libstdcxx_printer.add_container('std::', 'deque', StdDequePrinter) + libstdcxx_printer.add_container('std::', 'list', StdListPrinter) + libstdcxx_printer.add_container('std::__cxx11::', 'list', StdListPrinter) + libstdcxx_printer.add_container('std::', 'map', StdMapPrinter) + libstdcxx_printer.add_container('std::', 'multimap', StdMapPrinter) + libstdcxx_printer.add_container('std::', 'multiset', StdSetPrinter) + libstdcxx_printer.add_version('std::', 'pair', StdPairPrinter) + libstdcxx_printer.add_version('std::', 'priority_queue', + StdStackOrQueuePrinter) + libstdcxx_printer.add_version('std::', 'queue', StdStackOrQueuePrinter) + libstdcxx_printer.add_version('std::', 'tuple', StdTuplePrinter) + libstdcxx_printer.add_container('std::', 'set', StdSetPrinter) + libstdcxx_printer.add_version('std::', 'stack', StdStackOrQueuePrinter) + libstdcxx_printer.add_version('std::', 'unique_ptr', UniquePointerPrinter) + libstdcxx_printer.add_container('std::', 'vector', StdVectorPrinter) + # vector + + if hasattr(gdb.Value, 'dynamic_type'): + libstdcxx_printer.add_version('std::', 'error_code', + StdErrorCodePrinter) + libstdcxx_printer.add_version('std::', 'error_condition', + StdErrorCodePrinter) + + # Printer registrations for classes compiled with -D_GLIBCXX_DEBUG. + libstdcxx_printer.add('std::__debug::bitset', StdBitsetPrinter) + libstdcxx_printer.add('std::__debug::deque', StdDequePrinter) + libstdcxx_printer.add('std::__debug::list', StdListPrinter) + libstdcxx_printer.add('std::__debug::map', StdMapPrinter) + libstdcxx_printer.add('std::__debug::multimap', StdMapPrinter) + libstdcxx_printer.add('std::__debug::multiset', StdSetPrinter) + libstdcxx_printer.add('std::__debug::set', StdSetPrinter) + libstdcxx_printer.add('std::__debug::vector', StdVectorPrinter) + + # These are the TR1 and C++11 printers. + # For array - the default GDB pretty-printer seems reasonable. + libstdcxx_printer.add_version('std::', 'shared_ptr', SharedPointerPrinter) + libstdcxx_printer.add_version('std::', 'weak_ptr', SharedPointerPrinter) + libstdcxx_printer.add_container('std::', 'unordered_map', + Tr1UnorderedMapPrinter) + libstdcxx_printer.add_container('std::', 'unordered_set', + Tr1UnorderedSetPrinter) + libstdcxx_printer.add_container('std::', 'unordered_multimap', + Tr1UnorderedMapPrinter) + libstdcxx_printer.add_container('std::', 'unordered_multiset', + Tr1UnorderedSetPrinter) + libstdcxx_printer.add_container('std::', 'forward_list', + StdForwardListPrinter) + + libstdcxx_printer.add_version('std::tr1::', 'shared_ptr', SharedPointerPrinter) + libstdcxx_printer.add_version('std::tr1::', 'weak_ptr', SharedPointerPrinter) + libstdcxx_printer.add_version('std::tr1::', 'unordered_map', + Tr1UnorderedMapPrinter) + libstdcxx_printer.add_version('std::tr1::', 'unordered_set', + Tr1UnorderedSetPrinter) + libstdcxx_printer.add_version('std::tr1::', 'unordered_multimap', + Tr1UnorderedMapPrinter) + libstdcxx_printer.add_version('std::tr1::', 'unordered_multiset', + Tr1UnorderedSetPrinter) + + libstdcxx_printer.add_version('std::', 'initializer_list', + StdInitializerListPrinter) + libstdcxx_printer.add_version('std::', 'atomic', StdAtomicPrinter) + + # std::regex components + libstdcxx_printer.add_version('std::__detail::', '_State', + StdRegexStatePrinter) + + # These are the C++11 printer registrations for -D_GLIBCXX_DEBUG cases. + # The tr1 namespace containers do not have any debug equivalents, + # so do not register printers for them. + libstdcxx_printer.add('std::__debug::unordered_map', + Tr1UnorderedMapPrinter) + libstdcxx_printer.add('std::__debug::unordered_set', + Tr1UnorderedSetPrinter) + libstdcxx_printer.add('std::__debug::unordered_multimap', + Tr1UnorderedMapPrinter) + libstdcxx_printer.add('std::__debug::unordered_multiset', + Tr1UnorderedSetPrinter) + libstdcxx_printer.add('std::__debug::forward_list', + StdForwardListPrinter) + + # Library Fundamentals TS components + libstdcxx_printer.add_version('std::experimental::fundamentals_v1::', + 'any', StdExpAnyPrinter) + libstdcxx_printer.add_version('std::experimental::fundamentals_v1::', + 'optional', StdExpOptionalPrinter) + libstdcxx_printer.add_version('std::experimental::fundamentals_v1::', + 'basic_string_view', StdExpStringViewPrinter) + # Filesystem TS components + libstdcxx_printer.add_version('std::experimental::filesystem::v1::', + 'path', StdExpPathPrinter) + libstdcxx_printer.add_version('std::experimental::filesystem::v1::__cxx11::', + 'path', StdExpPathPrinter) + libstdcxx_printer.add_version('std::filesystem::', + 'path', StdPathPrinter) + libstdcxx_printer.add_version('std::filesystem::__cxx11::', + 'path', StdPathPrinter) + + # C++17 components + libstdcxx_printer.add_version('std::', + 'any', StdExpAnyPrinter) + libstdcxx_printer.add_version('std::', + 'optional', StdExpOptionalPrinter) + libstdcxx_printer.add_version('std::', + 'basic_string_view', StdExpStringViewPrinter) + libstdcxx_printer.add_version('std::', + 'variant', StdVariantPrinter) + libstdcxx_printer.add_version('std::', + '_Node_handle', StdNodeHandlePrinter) + + # C++20 components + libstdcxx_printer.add_version('std::', 'partial_ordering', StdCmpCatPrinter) + libstdcxx_printer.add_version('std::', 'weak_ordering', StdCmpCatPrinter) + libstdcxx_printer.add_version('std::', 'strong_ordering', StdCmpCatPrinter) + libstdcxx_printer.add_version('std::', 'span', StdSpanPrinter) + + # Extensions. + libstdcxx_printer.add_version('__gnu_cxx::', 'slist', StdSlistPrinter) + + if True: + # These shouldn't be necessary, if GDB "print *i" worked. + # But it often doesn't, so here they are. + libstdcxx_printer.add_container('std::', '_List_iterator', + StdListIteratorPrinter) + libstdcxx_printer.add_container('std::', '_List_const_iterator', + StdListIteratorPrinter) + libstdcxx_printer.add_version('std::', '_Rb_tree_iterator', + StdRbtreeIteratorPrinter) + libstdcxx_printer.add_version('std::', '_Rb_tree_const_iterator', + StdRbtreeIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Deque_iterator', + StdDequeIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Deque_const_iterator', + StdDequeIteratorPrinter) + libstdcxx_printer.add_version('__gnu_cxx::', '__normal_iterator', + StdVectorIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Bit_iterator', + StdBitIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Bit_const_iterator', + StdBitIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Bit_reference', + StdBitReferencePrinter) + libstdcxx_printer.add_version('__gnu_cxx::', '_Slist_iterator', + StdSlistIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Fwd_list_iterator', + StdFwdListIteratorPrinter) + libstdcxx_printer.add_container('std::', '_Fwd_list_const_iterator', + StdFwdListIteratorPrinter) + + # Debug (compiled with -D_GLIBCXX_DEBUG) printer + # registrations. + libstdcxx_printer.add('__gnu_debug::_Safe_iterator', + StdDebugIteratorPrinter) + +build_libstdcxx_dictionary () -- 2.40.1