symbols.py (3942B)
1 # Any copyright is dedicated to the Public Domain. 2 # http://creativecommons.org/publicdomain/zero/1.0/ 3 # 4 # A GDB Python script to fetch debug symbols from the Mozilla symbol server. 5 # 6 # To use, run `source /path/to/symbols.py` in GDB 7.9 or newer, or 7 # put that in your ~/.gdbinit. 8 9 10 import gzip 11 import io 12 import itertools 13 import os 14 import shutil 15 16 try: 17 from urllib.error import HTTPError, URLError 18 from urllib.parse import quote, urljoin 19 from urllib.request import urlopen 20 except ImportError: 21 from urllib import quote 22 23 from urllib2 import urlopen 24 from urlparse import urljoin 25 26 # /try covers regular symbols as well, see https://github.com/mstange/reliost/pull/8 27 SYMBOL_SERVER_URL = "https://symbols.mozilla.org/try/" 28 29 debug_dir = os.path.join(os.environ["HOME"], ".cache", "gdb") 30 cache_dir = os.path.join(debug_dir, ".build-id") 31 32 33 def munge_build_id(build_id): 34 """ 35 Breakpad stuffs the build id into a GUID struct so the bytes are 36 flipped from the standard presentation. 37 """ 38 b = list(map("".join, list(zip(*[iter(build_id.upper())] * 2)))) 39 return ( 40 "".join( 41 itertools.chain( 42 reversed(b[:4]), reversed(b[4:6]), reversed(b[6:8]), b[8:16] 43 ) 44 ) 45 + "0" 46 ) 47 48 49 def try_fetch_symbols(filename, build_id, destination): 50 debug_file = os.path.join(destination, build_id[:2], build_id[2:] + ".debug") 51 if os.path.exists(debug_file): 52 return debug_file 53 try: 54 d = os.path.dirname(debug_file) 55 if not os.path.isdir(d): 56 os.makedirs(d) 57 except OSError: 58 pass 59 path = os.path.join(filename, munge_build_id(build_id), filename + ".dbg.gz") 60 url = urljoin(SYMBOL_SERVER_URL, quote(path)) 61 try: 62 print(f"Trying to fetch symbols from {url}") 63 u = urlopen(url) 64 if u.getcode() != 200: 65 return None 66 with open(debug_file, "wb") as f, gzip.GzipFile( 67 fileobj=io.BytesIO(u.read()), mode="r" 68 ) as z: 69 shutil.copyfileobj(z, f) 70 print(f"Fetched symbols from {url}") 71 return debug_file 72 except (URLError, HTTPError): 73 None 74 75 76 def is_moz_binary(filename): 77 """ 78 Try to determine if a file lives in a Firefox install dir, to save 79 HTTP requests for things that aren't going to work. 80 """ 81 # The linux-gate VDSO doesn't have a real filename. 82 if not os.path.isfile(filename): 83 return False 84 while True: 85 filename = os.path.dirname(filename) 86 if filename == "/" or not filename: 87 return False 88 if os.path.isfile(os.path.join(filename, "application.ini")): 89 return True 90 91 92 def fetch_symbols_for(objfile): 93 build_id = objfile.build_id if hasattr(objfile, "build_id") else None 94 if getattr(objfile, "owner", None) is not None or any( 95 o.owner == objfile for o in gdb.objfiles() 96 ): 97 # This is either a separate debug file or this file already 98 # has symbols in a separate debug file. 99 return 100 if build_id and is_moz_binary(objfile.filename): 101 debug_file = try_fetch_symbols( 102 os.path.basename(objfile.filename), build_id, cache_dir 103 ) 104 if debug_file: 105 objfile.add_separate_debug_file(debug_file) 106 107 108 def new_objfile(event): 109 fetch_symbols_for(event.new_objfile) 110 111 112 def fetch_symbols(): 113 """ 114 Try to fetch symbols for all loaded modules. 115 """ 116 for objfile in gdb.objfiles(): 117 fetch_symbols_for(objfile) 118 119 120 # Create our debug cache dir. 121 try: 122 if not os.path.isdir(cache_dir): 123 os.makedirs(cache_dir) 124 except OSError: 125 pass 126 127 # Set it as a debug-file-directory. 128 try: 129 dirs = gdb.parameter("debug-file-directory").split(":") 130 except gdb.error: 131 dirs = [] 132 if debug_dir not in dirs: 133 dirs.append(debug_dir) 134 gdb.execute("set debug-file-directory {}".format(":".join(dirs))) 135 136 gdb.events.new_objfile.connect(new_objfile)