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