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