├── README.md └── gdbx.py /README.md: -------------------------------------------------------------------------------- 1 | gdb-scripts 2 | =========== 3 | 4 | * [English guide](http://www.cinsk.org/wiki/En:_Debugging_with_GDB:_gdbx.py) 5 | * [Korean guide](http://www.cinsk.org/wiki/Debugging_with_GDB:_gdbx.py) 6 | 7 | -------------------------------------------------------------------------------- /gdbx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Some useful GDB command in python 4 | # Copyright (C) 2010 Seong-Kook Shin 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | import sys 21 | import locale 22 | import gdb 23 | import subprocess 24 | import tempfile 25 | import re 26 | 27 | HEXDUMP_PATH="/usr/bin/hexdump" 28 | ICONV_PATH="/usr/bin/iconv" 29 | XMLLINT_PATH="/usr/bin/xmllint" 30 | 31 | DEBUG=False 32 | 33 | DEBUG_FD=sys.stderr 34 | def error(message): 35 | sys.stderr.write("error: %s\n" % message) 36 | 37 | def debug(message): 38 | if DEBUG: 39 | DEBUG_FD.write("debug: %s\n" % message) 40 | DEBUG_FD.flush() 41 | 42 | def set_debug_file(pathname): 43 | global DEBUG_FD 44 | DEBUG_FD = open(pathname, "w") 45 | 46 | def cmd_dump(filename, args, format="binary", type="value"): 47 | cmd = "dump %s %s %s %s" % (format, type, filename, args) 48 | debug("cmd_dump: executing '%s'..." % cmd ) 49 | try: 50 | gdb.execute(cmd) 51 | except RuntimeError as e: 52 | error("%s" % e) 53 | #raise 54 | 55 | def set_default_encoding(encoding = None): 56 | """set_default_encoding(encoding) - set the default character encoding 57 | 58 | Set the default character encoding of the run-time. If 'encoding' is not 59 | provided, the current locale's character encoding is used by default. 60 | 61 | If current locale's encoding is not set, and 'encoding' is not provided, 62 | the system encoding will not be changed. 63 | 64 | On invalid encoding name, it returns False. Otherwise returns True.""" 65 | 66 | defenc = sys.getdefaultencoding().upper() 67 | debug("system default encoding: %s" % defenc) 68 | if encoding == None: 69 | encoding = locale.getlocale()[1] 70 | debug("locale encoding (LC_CTYPE): %s" % encoding) 71 | if encoding: 72 | encoding = encoding.upper() 73 | 74 | if encoding != None and defenc != encoding and \ 75 | defenc.find(encoding) < 0 and encoding.find(defenc) < 0: 76 | # If the new encoding 'encoding' is different from the default 77 | # encoding 'defenc', 78 | reload(sys) 79 | try: 80 | sys.setdefaultencoding(encoding) 81 | debug("Default encoding is changed to: %s" % encoding) 82 | except LookupError as e: 83 | error("unrecoginized encoding, '%s'" % encoding) 84 | return False 85 | else: 86 | debug("Default encoding is %s" % defenc) 87 | 88 | 89 | class GdbDumpParent(gdb.Command): 90 | def __init__(self, name, completer = -1, prefix = False): 91 | gdb.Command.__init__(self, name, gdb.COMMAND_DATA, completer, prefix) 92 | 93 | def parse_arguments(self, args): 94 | """parse the command arguments into two group; gdb dump 95 | arguments and execute arguments. 96 | 97 | GDB dump arguments is a string containing all arguments that will be 98 | passed to the GDB 'dump' command. 99 | 100 | Execute arguments is a string containing command line arguments that 101 | will be used for the executing the shell command. The exact command 102 | line arguments are built via self.commandline().""" 103 | return (args, "") 104 | 105 | def commandline(self, filename, args): 106 | """commandline(filename, args) -- build the shell command line. 107 | 108 | 'filename' is the pathname of the file that contains the data, 'args' 109 | is a string contains the arguments that is passed from user input. 110 | 111 | The return value should be in either a string value or a list contains 112 | one or more string values.""" 113 | 114 | return [] 115 | 116 | def execute(self, filename, args): 117 | cmdline = self.commandline(filename, args) 118 | if type(cmdline) != list: 119 | use_shell = True 120 | else: 121 | use_shell = False 122 | debug("type of cmdline: %s" % type(cmdline)) 123 | debug("excuting %s (shell=%s)" % (cmdline, use_shell)) 124 | p = subprocess.Popen(cmdline, 125 | shell=use_shell, 126 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 127 | (out, err) = p.communicate() 128 | status = p.wait() 129 | 130 | sys.stdout.write(out) 131 | if err != "": 132 | sys.stdout.write(err) 133 | debug("exit status: %d" % status) 134 | return True 135 | 136 | def dump(self, filename, args): 137 | debug("Never reached here!!!") 138 | pass 139 | 140 | def on_execute_error(self): 141 | pass 142 | 143 | def invoke(self, args, from_tty): 144 | with tempfile.NamedTemporaryFile(prefix="gdb-") as tmp: 145 | #tmp = tempfile.NamedTemporaryFile(prefix="gdb-") 146 | #if True: 147 | try: 148 | (dump_args, exec_args) = self.parse_arguments(args) 149 | debug("dump_args: |%s|" % dump_args) 150 | debug("exec_args: |%s|" % exec_args) 151 | self.dump(tmp.name, dump_args) 152 | if not self.execute(tmp.name, exec_args): 153 | self.on_execute_error() 154 | except RuntimeError as e: 155 | print e 156 | except: 157 | sys.stderr.write("error: an exception occurred during excution") 158 | raise 159 | 160 | class GdbDumpValueParent(GdbDumpParent): 161 | def __init__(self, name, completer = -1, prefix = False): 162 | GdbDumpParent.__init__(self, name, completer, prefix) 163 | 164 | def dump(self, filename, args): 165 | cmd_dump(filename, args, format="binary", type="value") 166 | 167 | class GdbDumpMemoryParent(GdbDumpParent): 168 | def __init__(self, name, completer = -1, prefix = False): 169 | GdbDumpParent.__init__(self, name, completer, prefix) 170 | 171 | def dump(self, filename, args): 172 | cmd_dump(filename, args, format="binary", type="memory") 173 | 174 | 175 | class HexdumpCommand(gdb.Command): 176 | """Dump the given data using hexdump(1)""" 177 | def __init__(self): 178 | gdb.Command.__init__(self, "hexdump", gdb.COMMAND_DATA, -1, True) 179 | 180 | class HexdumpImpl(object): 181 | def parse_argument(self, args): 182 | idx = args.find("##") 183 | if idx < 0: 184 | debug("hexdump impl parse args: |%s|, |%s|" % (args, "")) 185 | return (args, "") 186 | else: 187 | debug("hexdump impl parse args: |%s|, |%s|" % (args[:idx].strip(), args[idx+2:].strip())) 188 | return (args[:idx].strip(), args[idx+2:].strip()) 189 | 190 | def commandline(self, filename, args): 191 | if args == "": 192 | return [HEXDUMP_PATH, "-C", filename] 193 | else: 194 | return "%s %s %s" % (HEXDUMP_PATH, args, filename) 195 | 196 | def complete(self, text, word): 197 | if text.find("##") < 0: 198 | return gdb.COMPLETE_SYMBOL 199 | else: 200 | return gdb.COMPELTE_NONE 201 | 202 | class HexdumpValueCommand(GdbDumpValueParent): 203 | """Dump the value EXPR using hexdump(1) 204 | 205 | usage: hexdump value EXPR [## OPTION...] 206 | 207 | Dump the value, EXPR using hexdump(1). If no OPTION is provided, '-C' 208 | is assumed (canonnical hex+ASCII display). If provided, OPTION is 209 | passed to hexdump(1). Note that you need '##' to separate EXPR from 210 | OPTION. 211 | 212 | For example, to dump the value, 'buffer' using hexdump(1) '-C': 213 | 214 | (gdb) hexdump value buffer 215 | 216 | To dump the value, 'buffer' in one-byte octal display: 217 | 218 | (gdb) hexdump value buffer ## -b 219 | """ 220 | def __init__(self): 221 | GdbDumpValueParent.__init__(self, "hexdump value", -1) 222 | self.impl = HexdumpImpl() 223 | 224 | def parse_arguments(self, args): 225 | return self.impl.parse_argument(args) 226 | 227 | def commandline(self, filename, args): 228 | return self.impl.commandline(filename, args) 229 | 230 | def complete(self, text, word): 231 | return self.impl.complete(text, word) 232 | 233 | class HexdumpMemoryCommand(GdbDumpMemoryParent): 234 | """Dump the memory using hexdump(1) 235 | 236 | usage: hexdump memory START_ADDR END_ADDR [## OPTION...] 237 | 238 | Dump the memory from START_ADDR to END_ADDR using hexdump(1). If no 239 | OPTION is provided, '-C' is assumed (canonnical hex+ASCII display). 240 | If provided, OPTION is passed to hexdump(1). Note that you need '##' 241 | to separate EXPR from OPTION. 242 | 243 | For example, to dump the memory from 'buffer' (100 bytes) using 244 | hexdump(1) '-C': 245 | 246 | (gdb) hexdump memory buffer ((char*)buffer+100) 247 | 248 | To dump the value, 'buffer' in one-byte octal display: 249 | 250 | (gdb) hexdump memory buffer ((char*)buffer+100) ## -b 251 | """ 252 | def __init__(self): 253 | GdbDumpMemoryParent.__init__(self, "hexdump memory", -1) 254 | self.impl = HexdumpImpl() 255 | 256 | def parse_arguments(self, args): 257 | return self.impl.parse_argument(args) 258 | 259 | def commandline(self, filename, args): 260 | return self.impl.commandline(filename, args) 261 | 262 | def complete(self, text, word): 263 | return self.impl.complete(text, word) 264 | 265 | HexdumpCommand() 266 | HexdumpValueCommand() 267 | HexdumpMemoryCommand() 268 | 269 | 270 | class IconvCommand(gdb.Command): 271 | """Check the character encoding of the data""" 272 | def __init__(self): 273 | gdb.Command.__init__(self, "iconv", gdb.COMMAND_DATA, -1, True) 274 | 275 | class IconvEncodings(object): 276 | encodings = None 277 | replaces = "./:-()" 278 | 279 | @staticmethod 280 | def supported_encodings(): 281 | ret = dict() 282 | try: 283 | p = subprocess.Popen([ICONV_PATH, "-l"], stdout=subprocess.PIPE) 284 | outbuf = p.communicate()[0] 285 | enclist = outbuf.split("\n") 286 | for enc in enclist: 287 | # 'enc' is something like "ANSI_X3.110-1983//" 288 | enc = enc.rstrip("/") 289 | alias = enc.lower() 290 | 291 | for repl in IconvEncodings.replaces: 292 | alias = alias.replace(repl, "_") 293 | 294 | ret[alias] = enc 295 | except: 296 | print "error: cannot get supported encoding list" 297 | raise 298 | 299 | return ret 300 | 301 | def __init__(self): 302 | if IconvEncodings.encodings == None: 303 | IconvEncodings.encodings = IconvEncodings.supported_encodings() 304 | reload(sys) 305 | 306 | def name(self, alias): 307 | """Return the actual encoding name if exists, otherwise None""" 308 | alias = alias.lstrip("#") 309 | if IconvEncodings.encodings.has_key(alias): 310 | return IconvEncodings.encodings[alias] 311 | return None 312 | 313 | def complete(self, text, word): 314 | """Callback for auto completion, used in gdb.Command.complete()""" 315 | ret = list() 316 | 317 | debug("Encodings.complete(): text(%s) word(%s)" % (text, word)) 318 | for a in IconvEncodings.encodings.iterkeys(): 319 | #debug(" enc(%s)" % a) 320 | if a.find(word) == 0: 321 | ret.append(a) 322 | return ret 323 | 324 | class IconvEncodingCommand(gdb.Command): 325 | """Set/get the current character encoding 326 | 327 | usage: iconv encoding [ENCODING] 328 | 329 | If ENCODING is not provided, this command shows the current system encoding. 330 | If provided, this command set the current system encoding to ENCODING. 331 | 332 | Once set, 'iconv value' and 'iconv memory' will try to convert the 333 | given data into ENCODING. 334 | 335 | Note that this changes the internal 'system default encoding' in 336 | Python runtime.""" 337 | 338 | def __init__(self): 339 | gdb.Command.__init__(self, "iconv encoding", gdb.COMMAND_DATA, -1) 340 | self.encodings = IconvEncodings() 341 | 342 | def invoke(self, arg, from_tty): 343 | arg = arg.strip() 344 | debug("arg: '%s'" % arg) 345 | if arg == "": 346 | print sys.getdefaultencoding() 347 | else: 348 | enc = self.encodings.name(arg) 349 | if enc != None: 350 | try: 351 | sys.setdefaultencoding(enc) 352 | except LookupError as e: 353 | error("encoding %s is not supported by Python" % enc) 354 | else: 355 | error("invalid encoding alias, %s." % arg) 356 | def complete(self, text, word): 357 | debug("iconv encoding complete: text(%s) word(%s)" % (text, word)) 358 | return self.encodings.complete(text, word) 359 | 360 | class IconvImpl(object): 361 | def __init__(self): 362 | self.re_encoding = re.compile(r"([ ]*(#[a-z0-9_]+))+") 363 | self.encodings = IconvEncodings() 364 | 365 | def partition(self, args): 366 | m = self.re_encoding.search(args) 367 | if m == None: 368 | return (args, "") 369 | else: 370 | idx = m.start() 371 | return (args[:idx], args[idx:]) 372 | 373 | def format_error(self, errbuf): 374 | """Format iconv(1)-related error message 375 | 376 | Currently, capture the only first line, removing iconv pathname""" 377 | (msg, dummy1, dummy2) = errbuf.partition("\n") 378 | idx = msg.find("iconv: ") 379 | if idx >= 0: 380 | return msg[idx:] 381 | 382 | def execute_iconv(self, filename, args): 383 | debug("execute_iconv('%s', '%s')" % (filename, args)) 384 | encodings = list() 385 | for e in args.split(): 386 | realname = self.encodings.name(e) 387 | if realname != None: 388 | debug(" target encoding: %s" % self.encodings.name(e)) 389 | encodings.append(realname) 390 | else: 391 | error("unknown encoding alias %s, ignored" % e) 392 | 393 | target = sys.getdefaultencoding() 394 | 395 | width = max(map(len, encodings)) 396 | 397 | sys.stdout.write("Target encoding is %s:\n" % target) 398 | 399 | for enc in encodings: 400 | cmdline = [ICONV_PATH, "-t", target, "-f", enc, filename] 401 | debug("cmdline: %s" % cmdline) 402 | p = subprocess.Popen(cmdline, 403 | stdout=subprocess.PIPE, 404 | stderr=subprocess.PIPE) 405 | (out, err) = p.communicate() 406 | status = p.wait() 407 | 408 | try: 409 | sys.stdout.write("%*s: " % (width, enc)) 410 | sys.stdout.write("|%s|\n" % out) 411 | except TypeError as e: 412 | sys.stdout.write("\n") 413 | error("TypeError: %s" % e) 414 | error("Try to change the default encoding") 415 | if err != "": 416 | sys.stdout.write("\t%s\n" % self.format_error(err)) 417 | return True 418 | 419 | def complete_any(self, text, word): 420 | try: 421 | prevchar = text[-(len(word) + 1)] 422 | except: 423 | prevchar = "" 424 | debug("complete: text(%s), word(%s), prev(%s)" % \ 425 | (text, word, prevchar)) 426 | if prevchar == "#": 427 | debug("complete for encoding...") 428 | return self.encodings.complete(text, word) 429 | else: 430 | debug("complete for symbol...") 431 | return gdb.COMPLETE_SYMBOL 432 | 433 | class IconvValueCommand(GdbDumpValueParent, IconvImpl): 434 | """Check the encoding of the value EXPR. 435 | 436 | usage: iconv memory EXPR ENCODING [ENCODING]... 437 | 438 | Send a value EXPR to iconv(1) command to check the encoding of the 439 | contents. ENCODING is the source encoding of the memory region. The 440 | target encoding is controlled via 'iconv encoding' command. 441 | 442 | If more than one ENCODING provided, iconv(1) is called several times 443 | for each ENCODING. 444 | 445 | ENCODING is an alias name that has a form '#name', where 'name' is 446 | a encoding name except these: 447 | 448 | 1. all capital letters are in lower-cases 449 | 2. all non-alpha-numeric characters are substituted to the 450 | underline character('_'). 451 | 452 | For example, to use the character encoding 'ISO_8859-10:1992', the 453 | ENCODING should be 'iso_8859_10_1992'. Note that this command will 454 | support auto-completion for the ENCODING. 455 | 456 | For example, if you know want to make sure that the value 'buffer' 457 | contains Korean character, but you don't know the exact encoding, you 458 | may try following: 459 | 460 | (gdb) iconv memory buffer buffer+100 #euc_kr #cp949 #utf-8 461 | 462 | Then it will try three times for the encoding 'EUC-KR', 'CP949', and 463 | 'UTF-8'.""" 464 | # iconv value EXPR #ENCODING... 465 | 466 | def __init__(self): 467 | GdbDumpValueParent.__init__(self, "iconv value", -1) 468 | IconvImpl.__init__(self) 469 | 470 | def complete(self, text, word): 471 | return self.complete_any(text, word) 472 | 473 | def execute(self, filename, args): 474 | return self.execute_iconv(filename, args) 475 | 476 | def parse_arguments(self, args): 477 | return self.partition(args) 478 | 479 | class IconvMemoryCommand(GdbDumpMemoryParent, IconvImpl): 480 | """Check the encoding of the memory. 481 | 482 | usage: iconv memory START_ADDR END_ADDR ENCODING [ENCODING]... 483 | 484 | Send a memory region to iconv(1) command to check the encoding of the 485 | contents. ENCODING is the source encoding of the memory region. The 486 | target encoding is controlled via 'iconv encoding' command. 487 | 488 | If more than one ENCODING provided, iconv(1) is called several times 489 | for each ENCODING. 490 | 491 | ENCODING is an alias name that has a form '#name', where 'name' is 492 | a encoding name except these: 493 | 494 | 1. all capital letters are in lower-cases 495 | 2. all non-alpha-numeric characters are substituted to the 496 | underline character('_'). 497 | 498 | For example, to use the character encoding 'ISO_8859-10:1992', the 499 | ENCODING should be 'iso_8859_10_1992'. Note that this command will 500 | support auto-completion for the ENCODING. 501 | 502 | For example, if you know want to make sure that the memory from 503 | 'buffer' to 'buffer+100' contains Korean character, but you don't know 504 | the exact encoding, you may try following: 505 | 506 | (gdb) iconv memory buffer buffer+100 #euc_kr #cp949 #utf-8 507 | 508 | Then it will try three times for the encoding 'EUC-KR', 'CP949', and 509 | 'UTF-8'.""" 510 | # iconv value EXPR #ENCODING... 511 | 512 | def __init__(self): 513 | GdbDumpMemoryParent.__init__(self, "iconv memory", -1) 514 | IconvImpl.__init__(self) 515 | 516 | def complete(self, text, word): 517 | return self.complete_any(text, word) 518 | 519 | def execute(self, filename, args): 520 | return self.execute_iconv(filename, args) 521 | 522 | def parse_arguments(self, args): 523 | r = self.partition(args) 524 | debug("parse_argument: partition: %s" % repr(r)) 525 | return r 526 | 527 | class XmllintCommand(gdb.Command): 528 | """Check the XML using xmllint(1)""" 529 | def __init__(self): 530 | gdb.Command.__init__(self, "xmllint", gdb.COMMAND_DATA, -1, True) 531 | 532 | class XmllintImpl(object): 533 | def complete(self, text, word): 534 | if text.find("##") < 0: 535 | return gdb.COMPLETE_SYMBOL 536 | else: 537 | return gdb.COMPLETE_NONE 538 | 539 | def partition(self, args): 540 | idx = args.rfind("##") 541 | if idx < 0: 542 | return (args, "") 543 | else: 544 | return (args[:idx].strip(), args[idx+2:].strip()) 545 | 546 | def commandline(self, filename, args): 547 | # args is something like '--format --schema http://asdfadf --debug' 548 | return "%s %s %s" % (XMLLINT_PATH, args, filename) 549 | 550 | class XmllintValueCommand(GdbDumpValueParent): 551 | """Check the value of an expression as a full XML document 552 | 553 | Usage: xmllint value EXPR [## ARGUMENTS...] 554 | 555 | Send the value of EXPR to xmllint(1) to check the validity. 556 | 557 | ARGUMENTS is the additional arguments that will be passed to 558 | xmllint(1). If you use ARGUMENTS, make sure to separate them from 559 | EXPR using '##'. 560 | 561 | To validate 'xml_buffer', just use: 562 | 563 | (gdb) xmllint value xml_buffer 564 | 565 | To validate 'xml_buffer' and to indent for human-readability: 566 | 567 | (gdb) xmllint value xml_buffer ## --format 568 | """ 569 | def __init__(self): 570 | # xmllint value EXPR ## OPTIONS... 571 | 572 | GdbDumpValueParent.__init__(self, "xmllint value", -1) 573 | self.impl = XmllintImpl() 574 | 575 | def complete(self, text, word): 576 | debug("xmllint value complete: text(%s) word(%s)" % (text, word)) 577 | return self.impl.complete(text, word) 578 | 579 | def parse_arguments(self, args): 580 | return self.impl.partition(args) 581 | 582 | def commandline(self, filename, args): 583 | return self.impl.commandline(filename, args) 584 | 585 | class XmllintMemoryCommand(GdbDumpMemoryParent): 586 | """Check the contents of memory as a full XML document 587 | 588 | Usage: xmllint memory START_ADDR END_ADDR [## ARGUMENTS...] 589 | 590 | Send the memory from the address START_ADDR to the address END_ADDR to 591 | xmllint(1) to check the validity. 592 | 593 | ARGUMENTS is the additional arguments that will be passed to 594 | xmllint(1). If you use ARGUMENTS, make sure to separate them from 595 | EXPR using '##'. 596 | 597 | To validate a memory from 'xml_buffer' to plus 100 units: 598 | 599 | (gdb) xmllint memory xml_buffer xml_buffer+100 600 | 601 | If 'xml_buffer' is not an array of character, you may need to cast it: 602 | 603 | (gdb) xmllint memory xml_buffer ((char*)xml_buffer)+100 604 | 605 | To validate and to indent for human-readability: 606 | 607 | (gdb) xmllint value xml_buffer xml_buffer+100 ## --format 608 | """ 609 | def __init__(self): 610 | # xmllint value EXPR ## OPTIONS... 611 | 612 | GdbDumpMemoryParent.__init__(self, "xmllint memory", -1) 613 | self.impl = XmllintImpl() 614 | 615 | def complete(self, text, word): 616 | debug("xmllint memory complete: text(%s) word(%s)" % (text, word)) 617 | return self.impl.complete(text, word) 618 | 619 | def parse_arguments(self, args): 620 | return self.impl.partition(args) 621 | 622 | def commandline(self, filename, args): 623 | return self.impl.commandline(filename, args) 624 | 625 | 626 | set_default_encoding() 627 | 628 | IconvCommand() 629 | IconvEncodingCommand() 630 | IconvValueCommand() 631 | IconvMemoryCommand() 632 | 633 | XmllintCommand() 634 | XmllintValueCommand() 635 | XmllintMemoryCommand() 636 | 637 | #set_debug_file("/dev/pts/13") 638 | --------------------------------------------------------------------------------