]>
Commit | Line | Data |
---|---|---|
1d506c26 | 1 | # Copyright 2022-2024 Free Software Foundation, Inc. |
de7d7cb5 TT |
2 | |
3 | # This program is free software; you can redistribute it and/or modify | |
4 | # it under the terms of the GNU General Public License as published by | |
5 | # the Free Software Foundation; either version 3 of the License, or | |
6 | # (at your option) any later version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, | |
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | # GNU General Public License for more details. | |
12 | # | |
13 | # You should have received a copy of the GNU General Public License | |
14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | ||
16 | import gdb | |
19201489 TT |
17 | import itertools |
18 | ||
19 | from gdb.frames import frame_iterator | |
de7d7cb5 TT |
20 | |
21 | from .startup import in_gdb_thread | |
22 | ||
23 | ||
85c72d70 TT |
24 | # A list of all the frames we've reported. A frame's index in the |
25 | # list is its ID. We don't use a hash here because frames are not | |
26 | # hashable. | |
27 | _all_frames = [] | |
de7d7cb5 TT |
28 | |
29 | ||
19201489 TT |
30 | # Map from a global thread ID to a memoizing frame iterator. |
31 | _iter_map = {} | |
32 | ||
33 | ||
de7d7cb5 TT |
34 | # Clear all the frame IDs. |
35 | @in_gdb_thread | |
36 | def _clear_frame_ids(evt): | |
85c72d70 TT |
37 | global _all_frames |
38 | _all_frames = [] | |
19201489 TT |
39 | global _iter_map |
40 | _iter_map = {} | |
de7d7cb5 TT |
41 | |
42 | ||
43 | # Clear the frame ID map whenever the inferior runs. | |
44 | gdb.events.cont.connect(_clear_frame_ids) | |
45 | ||
46 | ||
de7d7cb5 TT |
47 | @in_gdb_thread |
48 | def frame_for_id(id): | |
49 | """Given a frame identifier ID, return the corresponding frame.""" | |
85c72d70 TT |
50 | global _all_frames |
51 | return _all_frames[id] | |
5b86f108 TT |
52 | |
53 | ||
54 | @in_gdb_thread | |
55 | def select_frame(id): | |
56 | """Given a frame identifier ID, select the corresponding frame.""" | |
57 | frame = frame_for_id(id) | |
58 | frame.inferior_frame().select() | |
19201489 TT |
59 | |
60 | ||
61 | # A simple memoizing iterator. Note that this is not very robust. | |
62 | # For example, you can't start two copies of the iterator and then | |
63 | # alternate fetching items from each. Instead, it implements just | |
64 | # what is needed for the current callers. | |
65 | class _MemoizingIterator: | |
66 | def __init__(self, iterator): | |
67 | self.iterator = iterator | |
68 | self.seen = [] | |
69 | ||
70 | def __iter__(self): | |
71 | # First the memoized items. | |
72 | for item in self.seen: | |
73 | yield item | |
74 | # Now memoize new items. | |
75 | for item in self.iterator: | |
76 | self.seen.append(item) | |
77 | yield item | |
78 | ||
79 | ||
80 | # A generator that fetches frames and pairs them with a frame ID. It | |
81 | # yields tuples of the form (ID, ELIDED, FRAME), where ID is the | |
82 | # generated ID, ELIDED is a boolean indicating if the frame should be | |
83 | # elided, and FRAME is the frame itself. This approach lets us | |
84 | # memoize the result and assign consistent IDs, independent of how | |
85 | # "includeAll" is set in the request. | |
86 | @in_gdb_thread | |
87 | def _frame_id_generator(): | |
88 | try: | |
89 | base_iterator = frame_iterator(gdb.newest_frame(), 0, -1) | |
90 | except gdb.error: | |
91 | base_iterator = () | |
92 | ||
93 | # Helper function to assign an ID to a frame. | |
94 | def get_id(frame): | |
95 | global _all_frames | |
96 | num = len(_all_frames) | |
97 | _all_frames.append(frame) | |
98 | return num | |
99 | ||
100 | def yield_frames(iterator, for_elided): | |
101 | for frame in iterator: | |
102 | # Unfortunately the frame filter docs don't describe | |
103 | # whether the elided frames conceptually come before or | |
104 | # after the eliding frame. Here we choose after. | |
105 | yield (get_id(frame), for_elided, frame) | |
106 | ||
107 | elided = frame.elided() | |
108 | if elided is not None: | |
109 | yield from yield_frames(frame.elided(), True) | |
110 | ||
111 | yield from yield_frames(base_iterator, False) | |
112 | ||
113 | ||
114 | # Return the memoizing frame iterator for the selected thread. | |
115 | @in_gdb_thread | |
116 | def _get_frame_iterator(): | |
117 | thread_id = gdb.selected_thread().global_num | |
118 | global _iter_map | |
119 | if thread_id not in _iter_map: | |
120 | _iter_map[thread_id] = _MemoizingIterator(_frame_id_generator()) | |
121 | return _iter_map[thread_id] | |
122 | ||
123 | ||
124 | # A helper function that creates an iterable that returns (ID, FRAME) | |
125 | # pairs. It uses the memoizing frame iterator, but also handles the | |
126 | # "includeAll" member of StackFrameFormat. | |
127 | @in_gdb_thread | |
128 | def dap_frame_generator(frame_low, levels, include_all): | |
129 | """A generator that yields identifiers and frames. | |
130 | ||
131 | Each element is a pair of the form (ID, FRAME). | |
132 | ID is the internally-assigned frame ID. | |
133 | FRAME is a FrameDecorator of some kind. | |
134 | ||
135 | Arguments are as to the stackTrace request.""" | |
136 | ||
137 | base_iterator = _get_frame_iterator() | |
138 | ||
139 | if not include_all: | |
140 | base_iterator = itertools.filterfalse(lambda item: item[1], base_iterator) | |
141 | ||
142 | if levels == 0: | |
143 | # Zero means all remaining frames. | |
144 | frame_high = None | |
145 | else: | |
146 | frame_high = frame_low + levels | |
147 | base_iterator = itertools.islice(base_iterator, frame_low, frame_high) | |
148 | ||
149 | for ident, _, frame in base_iterator: | |
150 | yield (ident, frame) |