xref: /xnu-12377.61.12/tools/lldbmacros/core/standard.py (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8)
1import getopt
2import os
3import string
4import sys
5import re
6
7from lldb import SBValue, SBCommandReturnObject
8from core import value as cvalue
9from .configuration import config
10
11HELP_ARGUMENT_EXCEPTION_SENTINEL = "HELP"
12
13class ArgumentError(Exception):
14    """ Exception class for raising errors in command arguments. The lldb_command framework will catch this
15        class of exceptions and print suitable error message to user.
16    """
17    def __init__(self, msg="Bad arguments provided"):
18        self.error_message = msg
19    def __str__(self):
20        return str(self.error_message)
21
22
23class RedirectStdStreams(object):
24    def __init__(self, stdout=None, stderr=None):
25        self._stdout = stdout or sys.stdout
26        self._stderr = stderr or sys.stderr
27
28    def __enter__(self):
29        self.old_stdout, self.old_stderr = sys.stdout, sys.stderr
30        self.old_stdout.flush(); self.old_stderr.flush()
31        sys.stdout, sys.stderr = self._stdout, self._stderr
32
33    def __exit__(self, exc_type, exc_value, traceback):
34        self._stdout.flush(); self._stderr.flush()
35        sys.stdout = self.old_stdout
36        sys.stderr = self.old_stderr
37
38class IndentScope(object):
39    def __init__(self, O):
40        self._O = O
41
42    def __enter__(self):
43        self._O._indent += '    '
44
45    def __exit__(self, exc_type, exc_value, traceback):
46        self._O._indent = self._O._indent[:-4]
47
48class HeaderScope(object):
49    def __init__(self, O, hdr, indent = False):
50        self._O = O
51        self._header = hdr
52        self._indent = indent
53
54    def __enter__(self):
55        self._oldHeader = self._O._header
56        self._oldLastHeader = self._O._lastHeader
57        self._O._header = self._header
58        self._O._lastHeader = None
59        if self._indent:
60            self._O._indent += '    '
61
62    def __exit__(self, exc_type, exc_value, traceback):
63        self._O._header = self._oldHeader
64        self._O._lastHeader = self._oldLastHeader
65        if self._indent:
66            self._O._indent = self._O._indent[:-4]
67
68class VT(object):
69    Black        = "\033[38;5;0m"
70    DarkRed      = "\033[38;5;1m"
71    DarkGreen    = "\033[38;5;2m"
72    Brown        = "\033[38;5;3m"
73    DarkBlue     = "\033[38;5;4m"
74    DarkMagenta  = "\033[38;5;5m"
75    DarkCyan     = "\033[38;5;6m"
76    Grey         = "\033[38;5;7m"
77
78    DarkGrey     = "\033[38;5;8m"
79    Red          = "\033[38;5;9m"
80    Green        = "\033[38;5;10m"
81    Yellow       = "\033[38;5;11m"
82    Blue         = "\033[38;5;12m"
83    Magenta      = "\033[38;5;13m"
84    Cyan         = "\033[38;5;14m"
85    White        = "\033[38;5;15m"
86
87    Default      = "\033[39m"
88
89    Bold         = "\033[1m"
90    EndBold      = "\033[22m"
91
92    Oblique      = "\033[3m"
93    EndOblique   = "\033[23m"
94
95    Underline    = "\033[4m"
96    EndUnderline = "\033[24m"
97
98    Reset        = "\033[0m"
99
100class NOVT(object):
101    def __getattribute__(self, *args):
102        return ""
103
104class SBValueFormatter(string.Formatter):
105    """
106    Formatter that treats SBValues specially
107
108    It adds the following magical syntax for fields:
109
110    - {$value->path.to[10].field} will follow a given expression path,
111      and compute the resulting SBValue. This works with cvalues too.
112
113    - {&value->path.to[10].field} will return the load address
114      of the specified value path. This works with cvalue too.
115
116
117    The format spec can now take a multi-char conversion,
118    {field|<multi-char-conversion>!conv:spec},
119    where <multi-char-conversion> is one of:
120
121    - `c_str` which will attempt to read the value as a C string using
122      xGetValueAsCString()
123
124    - `human_size` will convert sizes into a human readable representation.
125
126    - a conversion registered with the SBValueFormatter.converter
127      decorator,
128
129    - a `key.method` specification where the key is one of the positional
130      or named arguments to the format.
131
132
133    When the value of a given field is an SBValue (because &/$ was used,
134    or the field was already an SBValue -- but not a cvalue), in the absence
135    of a explicit conversion, the SBValue will be converted to a scalar
136    using xGetValueAsScalar()
137    """
138
139    _KEY_RE = re.compile(r"[.-\[]")
140
141    _CONVERTERS = {}
142
143    @classmethod
144    def converter(cls, name, raw=False):
145        def register(fn):
146            cls._CONVERTERS[name] = (fn, raw)
147
148        return register
149
150    def format(self, format_string, *args, **kwargs):
151        return self.vformat(self, format_string, args, kwargs)
152
153    def _raise_switch_manual_to_automatic(self):
154        raise ValueError('cannot switch from manual field '
155                         'specification to automatic field '
156                         'numbering')
157
158    def vformat(self, format_string, args, kwargs):
159        result    = []
160        auto_idx  = 0
161
162        #
163        # Python 2.7 doesn't support empty field names in Formatter,
164        # so we need to implement vformat. Because we avoid certain
165        # features such as "unused fields accounting" we actually
166        # are faster than the core library Formatter this way which
167        # adds up quickly for our macros, so it's worth keeping
168        # this implementation even on Python 3.
169        #
170        for text, field_name, format_spec, conv in \
171                self.parse(format_string):
172
173            if text:
174                result.append(text)
175
176            if field_name is None:
177                continue
178
179            field_name, _, transform = field_name.partition('|')
180
181            if field_name == '':
182                #
183                # Handle auto-numbering like python 3
184                #
185                if auto_idx is None:
186                    self._raise_switch_manual_to_automatic()
187                field_name = str(auto_idx)
188                auto_idx  += 1
189
190            elif field_name.isdigit():
191                #
192                # numeric key
193                #
194                if auto_idx:
195                    self._raise_switch_manual_to_automatic()
196                auto_idx = None
197
198            try:
199                if field_name[0] in '&$':
200                    #
201                    # Our magic sigils
202                    #
203                    obj, auto_idx = self.get_value_field(
204                        field_name, args, kwargs, auto_idx)
205
206                else:
207                    #
208                    # Fallback typical case
209                    #
210                    obj, _ = self.get_field(field_name, args, kwargs)
211            except:
212                if config['debug']: raise
213                result.extend((
214                    VT.Red,
215                    "<FAIL {}>".format(field_name),
216                    VT.Reset
217                ))
218                continue
219
220            # do any conv on the resulting object
221            try:
222                obj = self.convert_field(obj, conv, transform, args, kwargs)
223            except:
224                if config['debug']: raise
225                result.extend((
226                    VT.Red,
227                    "<CONV {}>".format(field_name),
228                    VT.Reset
229                ))
230                continue
231
232            result.append(self.format_field(obj, format_spec))
233
234        return ''.join(result)
235
236    def get_value_field(self, name, args, kwargs, auto_idx):
237        match = self._KEY_RE.search(name)
238        index = match.start() if match else len(name)
239        key   = name[1:index]
240        path  = name[index:]
241
242        if key == '':
243            raise ValueError("Key part of '{}' can't be empty".format(name))
244
245        if key.isdigit():
246            key = int(key)
247            if auto_idx:
248                self._raise_switch_manual_to_automatic()
249            auto_idx = None
250
251        obj = self.get_value(key, args, kwargs)
252        if isinstance(obj, cvalue):
253            obj = obj.GetSBValue()
254
255        if name[0] == '&':
256            if len(path):
257                return obj.xGetLoadAddressByPath(path), auto_idx
258            return obj.GetLoadAddress(), auto_idx
259
260        if len(path):
261            obj = obj.GetValueForExpressionPath(path)
262        return obj, auto_idx
263
264    def convert_field(self, obj, conv, transform='', args=None, kwargs=None):
265        is_sbval = isinstance(obj, SBValue)
266
267        if transform != '':
268            fn, raw = self._CONVERTERS.get(transform, (None, False))
269            if not raw and is_sbval:
270                obj = obj.xGetValueAsScalar()
271
272            if fn:
273                obj = fn(obj)
274            else:
275                objname, _, method = transform.partition('.')
276                field, _ = self.get_field(objname, args, kwargs)
277                obj = getattr(field, method)(obj)
278
279            is_sbval = False
280
281        if conv is None:
282            return obj.xGetValueAsScalar() if is_sbval else obj
283
284        return super(SBValueFormatter, self).convert_field(obj, conv)
285
286@SBValueFormatter.converter("c_str", raw=True)
287def __sbval_to_cstr(v):
288    return v.xGetValueAsCString() if isinstance(v, SBValue) else str(v)
289
290@SBValueFormatter.converter("human_size")
291def __human_size(v):
292    n = v.xGetValueAsCString() if isinstance(v, SBValue) else int(v)
293    order = ((n//10) | 1).bit_length() // 10
294    return "{:.1f}{}".format(n / (1024 ** order), "BKMGTPE"[order])
295
296
297_xnu_core_default_formatter = SBValueFormatter()
298
299def xnu_format(fmt, *args, **kwargs):
300    """ Conveniency function to call SBValueFormatter().format """
301    return _xnu_core_default_formatter.vformat(fmt, args, kwargs)
302
303def xnu_vformat(fmt, args, kwargs):
304    """ Conveniency function to call SBValueFormatter().vformat """
305    return _xnu_core_default_formatter.vformat(fmt, args, kwargs)
306
307
308class CommandOutput(object):
309    """
310    An output handler for all commands. Use Output.print to direct all output of macro via the handler.
311    These arguments are passed after a "--". eg
312    (lldb) zprint -- -o /tmp/zprint.out.txt
313
314    Currently this provide capabilities
315    -h show help
316    -o path/to/filename
317       The output of this command execution will be saved to file. Parser information or errors will
318       not be sent to file though. eg /tmp/output.txt
319    -s filter_string
320       the "filter_string" param is parsed to python regex expression and each line of output
321       will be printed/saved only if it matches the expression.
322       The command header will not be filtered in any case.
323    -p <plugin_name>
324       Send the output of the command to plugin.
325    -v ...
326       Up verbosity
327    -c <always|never|auto>
328       configure color
329    """
330    def __init__(self, cmd_name: str, CommandResult: SBCommandReturnObject=None, fhandle=None):
331        """ Create a new instance to handle command output.
332        params:
333                CommandResult : SBCommandReturnObject result param from lldb's command invocation.
334        """
335        self.fname=None
336        self.fhandle=fhandle
337        self.FILTER=False
338        self.pluginRequired = False
339        self.pluginName = None
340        self.cmd_name = cmd_name
341        self.resultObj = CommandResult
342        self.verbose_level = 0
343        self.target_cmd_args = []
344        self.target_cmd_options = {}
345        self._indent = ''
346        self._buffer = ''
347
348        self._header = None
349        self._lastHeader = None
350        self._line = 0
351
352        self.color = None
353        self.isatty = os.isatty(sys.__stdout__.fileno())
354        self.VT = VT if self._doColor() else NOVT()
355
356
357    def _doColor(self):
358        if self.color is True:
359            return True;
360        return self.color is None and self.isatty
361
362    def _needsHeader(self):
363        if self._header is None:
364            return False
365        if self._lastHeader is None:
366            return True
367        if not self.isatty:
368            return False
369        return self._line - self._lastHeader > 40
370
371    def indent(self):
372        return IndentScope(self)
373
374    def table(self, header, indent = False):
375        return HeaderScope(self, header, indent)
376
377    def format(self, s, *args, **kwargs):
378        kwargs['VT'] = self.VT
379        return xnu_vformat(s, args, kwargs)
380
381    def error(self, s, *args, **kwargs):
382        print(self.format("{cmd.cmd_name}: {VT.Red}"+s+"{VT.Default}", cmd=self, *args, **kwargs))
383
384    def write(self, s):
385        """ Handler for all commands output. By default just print to stdout """
386
387        o = self.fhandle or self.resultObj
388
389        for l in (self._buffer + s).splitlines(True):
390            if l[-1] != '\n':
391                self._buffer = l
392                return
393
394            if self.FILTER:
395                if not self.reg.search(l):
396                    continue
397                l = self.reg.sub(self.VT.Underline + r"\g<0>" + self.VT.EndUnderline, l);
398
399            if len(l) == 1:
400                o.write(l)
401                self._line += 1
402                continue
403
404            if len(l) > 1 and self._needsHeader():
405                for h in self._header.splitlines():
406                    o.write(self.format("{}{VT.Bold}{:s}{VT.EndBold}\n", self._indent, h))
407                self._lastHeader = self._line
408
409            o.write(self._indent + l)
410            self._line += 1
411
412        self._buffer = ''
413
414    def flush(self):
415        if self.fhandle != None:
416            self.fhandle.flush()
417
418    def __del__(self):
419        """ closes any open files. report on any errors """
420        if self.fhandle != None and self.fname != None:
421            self.fhandle.close()
422
423    def setOptions(self, cmdargs, cmdoptions =''):
424        """ parse the arguments passed to the command
425            param :
426                cmdargs => [] of <str> (typically args.split())
427                cmdoptions : str - string of command level options.
428                             These should be CAPITAL LETTER options only.
429        """
430        opts=()
431        args = cmdargs
432        cmdoptions = cmdoptions.upper()
433        try:
434            opts,args = getopt.gnu_getopt(args,'hvo:s:p:c:'+ cmdoptions,[])
435            self.target_cmd_args = args
436        except getopt.GetoptError as err:
437            raise ArgumentError(str(err))
438        #continue with processing
439        for o,a in opts :
440            if o == "-h":
441                # This is misuse of exception but 'self' has no info on doc string.
442                # The caller may handle exception and display appropriate info
443                raise ArgumentError(HELP_ARGUMENT_EXCEPTION_SENTINEL)
444            if o == "-o" and len(a) > 0:
445                self.fname=os.path.normpath(os.path.expanduser(a.strip()))
446                self.fhandle=open(self.fname,"w")
447                print("saving results in file ",str(a))
448                self.fhandle.write("(lldb)%s %s \n" % (self.cmd_name, " ".join(cmdargs)))
449                self.isatty = os.isatty(self.fhandle.fileno())
450            elif o == "-s" and len(a) > 0:
451                self.reg = re.compile(a.strip(),re.MULTILINE|re.DOTALL)
452                self.FILTER=True
453                print("showing results for regex:",a.strip())
454            elif o == "-p" and len(a) > 0:
455                self.pluginRequired = True
456                self.pluginName = a.strip()
457                #print "passing output to " + a.strip()
458            elif o == "-v":
459                self.verbose_level += 1
460            elif o == "-c":
461                if a in ["always", '1']:
462                    self.color = True
463                elif a in ["never", '0']:
464                    self.color = False
465                else:
466                    self.color = None
467                self.VT = VT if self._doColor() else NOVT()
468            else:
469                o = o.strip()
470                self.target_cmd_options[o] = a
471
472
473