Skip to content

Instantly share code, notes, and snippets.

@luser
Created February 6, 2019 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luser/330f27023a20137f87b9fbd0c680bd6d to your computer and use it in GitHub Desktop.
Save luser/330f27023a20137f87b9fbd0c680bd6d to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
import lldb
import gzip
import io
import itertools
import os
import shutil
import tarfile
try:
from urllib.request import urlopen
from urllib.parse import urljoin, quote
except ImportError:
from urllib2 import urlopen
from urllib import quote
from urlparse import urljoin
SYMBOL_SERVER_URL = 'https://symbols.mozilla.org/'
cache_dir = os.path.join(os.environ['HOME'], 'Library', 'SymbolCache', 'dsyms')
uuid_dir = os.path.join(cache_dir, 'uuids')
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 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 == '/':
return False
if os.path.isfile(os.path.join(filename, 'application.ini')):
return True
def try_fetch_symbols(filename, build_id, destination, is_mac):
if is_mac:
debug_file = os.path.join(destination, filename + '.dSYM', 'Contents',
'Resources', 'DWARF', filename)
else:
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
if is_mac:
path = os.path.join(filename, build_id + '0',
filename + '.dSYM.tar.bz2')
else:
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('Fetching symbols from {0}'.format(url))
if is_mac:
with tarfile.open(fileobj=io.BytesIO(u.read()), mode='r:bz2') as t:
t.extractall(destination)
return debug_file
else:
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 Exception as e:
print 'Error: %s' % e
return None
def fetch_symbols(debugger, command, exe_ctx, result, internal_dict):
'''Attempt to fetch symbols from Mozilla's symbol server'''
target = exe_ctx.target
is_mac = target.triple.endswith('-apple-macosx')
for m in target.module_iter():
path = m.file.fullpath
if m.file == m.GetSymbolFileSpec() and is_moz_binary(path):
uuid = m.uuid.hex
debug_file = try_fetch_symbols(os.path.basename(path), uuid,
cache_dir, is_mac)
if debug_file is not None:
print 'Downloaded: %s' % debug_file
lldb.debugger.HandleCommand('target symbols add ' + debug_file)
def __lldb_init_module(debugger, internal_dict):
print 'Loaded lldb-symbols. target: %s' % debugger.GetSelectedTarget()
try:
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
except OSError:
pass
lldb.debugger.HandleCommand('command script add -f lldb_symbols.fetch_symbols fetch-symbols')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment