aboutsummaryrefslogtreecommitdiff
path: root/gdb/python/lib/gdb/dap/scopes.py
blob: d0e9115c6fa217771e68427783bcfb954f2ac8ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# Copyright 2022-2024 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import gdb

from .frames import frame_for_id
from .globalvars import get_global_scope
from .server import request
from .sources import make_source
from .startup import in_gdb_thread
from .varref import BaseReference

# Map DAP frame IDs to scopes.  This ensures that scopes are re-used.
frame_to_scope = {}


# If the most recent stop was due to a 'finish', and the return value
# could be computed, then this holds that value.  Otherwise it holds
# None.
_last_return_value = None


# When the inferior is re-started, we erase all scope references.  See
# the section "Lifetime of Objects References" in the spec.
@in_gdb_thread
def clear_scopes(event):
    global frame_to_scope
    frame_to_scope = {}
    global _last_return_value
    _last_return_value = None


gdb.events.cont.connect(clear_scopes)


@in_gdb_thread
def set_finish_value(val):
    """Set the current 'finish' value on a stop."""
    global _last_return_value
    _last_return_value = val


# A helper function to compute the value of a symbol.  SYM is either a
# gdb.Symbol, or an object implementing the SymValueWrapper interface.
# FRAME is a frame wrapper, as produced by a frame filter.  Returns a
# tuple of the form (NAME, VALUE), where NAME is the symbol's name and
# VALUE is a gdb.Value.
@in_gdb_thread
def symbol_value(sym, frame):
    inf_frame = frame.inferior_frame()
    # Make sure to select the frame first.  Ideally this would not
    # be needed, but this is a way to set the current language
    # properly so that language-dependent APIs will work.
    inf_frame.select()
    name = str(sym.symbol())
    val = sym.value()
    if val is None:
        # No synthetic value, so must read the symbol value
        # ourselves.
        val = sym.symbol().value(inf_frame)
    elif not isinstance(val, gdb.Value):
        val = gdb.Value(val)
    return (name, val)


class _ScopeReference(BaseReference):
    def __init__(self, name, hint, frame, var_list):
        super().__init__(name)
        self.hint = hint
        self.frame = frame
        self.inf_frame = frame.inferior_frame()
        self.func = frame.function()
        self.line = frame.line()
        # VAR_LIST might be any kind of iterator, but it's convenient
        # here if it is just a collection.
        self.var_list = tuple(var_list)

    def to_object(self):
        result = super().to_object()
        result["presentationHint"] = self.hint
        # How would we know?
        result["expensive"] = False
        result["namedVariables"] = self.child_count()
        if self.line is not None:
            result["line"] = self.line
        filename = self.frame.filename()
        if filename is not None:
            result["source"] = make_source(filename)
        return result

    def has_children(self):
        return True

    def child_count(self):
        return len(self.var_list)

    @in_gdb_thread
    def fetch_one_child(self, idx):
        return symbol_value(self.var_list[idx], self.frame)


# A _ScopeReference that prepends the most recent return value.  Note
# that this object is only created if such a value actually exists.
class _FinishScopeReference(_ScopeReference):
    def __init__(self, *args):
        super().__init__(*args)

    def child_count(self):
        return super().child_count() + 1

    def fetch_one_child(self, idx):
        if idx == 0:
            global _last_return_value
            return ("(return)", _last_return_value)
        return super().fetch_one_child(idx - 1)


class _RegisterReference(_ScopeReference):
    def __init__(self, name, frame):
        super().__init__(
            name, "registers", frame, frame.inferior_frame().architecture().registers()
        )

    @in_gdb_thread
    def fetch_one_child(self, idx):
        return (
            self.var_list[idx].name,
            self.inf_frame.read_register(self.var_list[idx]),
        )


@request("scopes")
def scopes(*, frameId: int, **extra):
    global _last_return_value
    global frame_to_scope
    if frameId in frame_to_scope:
        scopes = frame_to_scope[frameId]
    else:
        frame = frame_for_id(frameId)
        scopes = []
        # Make sure to handle the None case as well as the empty
        # iterator case.
        args = tuple(frame.frame_args() or ())
        if args:
            scopes.append(_ScopeReference("Arguments", "arguments", frame, args))
        has_return_value = frameId == 0 and _last_return_value is not None
        # Make sure to handle the None case as well as the empty
        # iterator case.
        locs = tuple(frame.frame_locals() or ())
        if has_return_value:
            scopes.append(_FinishScopeReference("Locals", "locals", frame, locs))
        elif locs:
            scopes.append(_ScopeReference("Locals", "locals", frame, locs))
        scopes.append(_RegisterReference("Registers", frame))
        frame_to_scope[frameId] = scopes
        global_scope = get_global_scope(frame)
        if global_scope is not None:
            scopes.append(global_scope)
    return {"scopes": [x.to_object() for x in scopes]}