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