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