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