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