-
Notifications
You must be signed in to change notification settings - Fork 5
/
colour_filter.py
197 lines (145 loc) · 5.71 KB
/
colour_filter.py
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#!/usr/bin/env gdb
# encoding: utf8
# file: colour_filter.py
from typing import Iterator, Text
from gdb import parameter as get_parameter
from gdb import Frame, frame_filters, execute
from gdb.FrameDecorator import FrameDecorator
class FrameColorizer(FrameDecorator):
"""FrameColorizer repeats all actions to get all common frame attribute and
then spices a bit output with colours. Format of output string is following.
#<depth> <address> in <function> (<frame_args>) at <filename>[:<line>]
Notes: There is not special support Frame.elided() property.
"""
def __init__(self, *args, depth=0, **kwargs):
super(FrameColorizer, self).__init__(*args, **kwargs)
self._depth = depth
self.frame = super(FrameColorizer, self).inferior_frame()
def __str__(self):
is_print_address = get_parameter('print address')
part1 = self.depth()
part2 = self.function() + ' \033[1;37m(' + self.frame_args() + ')\033[0m'
part3 = self.filename() + self.line()
if is_print_address:
part1 += ' ' + self.address() + ' in '
else:
part1 += ' '
parts = part1 + part2 + ' at ' + part3
screen_width = self.get_screen_width()
if screen_width is not None and len(parts) > screen_width:
shift_width = int(self.length(part1)) - 1
shift_width -= 3 * int(is_print_address) # compensate ' in ' part
value = part1 + part2 + '\n'
value += ' ' * shift_width + ' at ' + part3
else:
value = parts
return value
def address(self):
address = super(FrameColorizer, self).address()
return '\033[1;30m0x%016x\033[0m' % address
def depth(self):
return '\033[1;37m#%-3d\033[0m' % self._depth
def filename(self):
filename = super(FrameColorizer, self).filename()
return '\033[0;36m%s\033[0m' % filename
def frame_args(self):
try:
block = self.frame.block()
except RuntimeError:
block = None
while block is not None:
if block.function is not None:
break
block = block.superblock
if block is None:
return ''
args = []
for sym in block:
if not sym.is_argument:
continue;
val = sym.value(self.frame)
arg = '%s=%s' % (sym, val) if str(val) else str(sym)
args.append(arg)
return ', '.join(args)
def function(self):
func = super(FrameColorizer, self).function()
# GDB could somehow resolve function name by its address.
# See details here https://cygwin.com/ml/gdb/2017-12/msg00013.html
if isinstance(func, int):
# Here we have something like
# > raise + 272 in section .text of /usr/lib/libc.so.6
# XXX: gdb.find_pc_line
symbol = gdb.execute('info symbol 0x%016x' % func, False, True)
# But here we truncate layout in binary
# > raise + 272
name = symbol[:symbol.find('in section')].strip()
# Check if we in format
# > smthing + offset
parts = name.rsplit(' ', 1)
# > raise
if len(parts) == 1:
return name
try:
offset = hex(int(parts[1]))
except ValueError:
return name
return '\033[1;34m' + parts[0] + ' ' + offset + '\033[0m'
else:
return '\033[1;34m' + func + '\033[0m'
def get_screen_width(self):
"""Get screen width from GDB. Source format is following
"""
return get_parameter('width')
def line(self):
value = super(FrameColorizer, self).line()
return '\033[0;35m:%d\033[0m' % value if value else ''
@staticmethod
def length(colored_string):
"""This function calculates length of string with terminal control
sequences.
"""
start = 0
term_seq_len = 0
while True:
begin = colored_string.find('\033', start)
if begin == -1:
break
end = colored_string.find('m', begin)
if end == -1:
end = len(s)
term_seq_len += end - begin + 1
start = end
return len(colored_string) - term_seq_len
class FilterProxy:
"""FilterProxy class keep ensures that frame iterator will be comsumed
properly on the first and the sole call.
"""
def __init__(self, frames: Iterator[Frame]):
self.frames = (FrameColorizer(frame, depth=ix)
for ix, frame in enumerate(frames))
def __iter__(self):
return self
def __next__(self):
self.unroll_stack()
raise StopIteration
def unroll_stack(self):
output = (str(frame) for frame in self.frames)
print('\n'.join(output))
class ColourFilter:
def __init__(self, name='backtrace-filter', priority=0, enabled=True):
"""Frame filter with the lower priority that consumes every frame and
colouring output.
:param name: The name of the filter that GDB will display.
:param priority: The priority of the filter relative to other filters.
:param enabled: A boolean that indicates whether this filter is enabled
and should be executed.
"""
self.name = name
self.priority = priority
self.enabled = enabled
# Register this frame filter with the global frame_filters
# dictionary.
frame_filters[self.name] = self
def filter(self, iters: Iterator[Frame]) -> Iterator[Frame]:
return FilterProxy(iters)
ColourFilter() # register colour filter forcibly