blob: 2277728e0389d0a674bac885c1c635cbff771248 [file] [log] [blame]
# Copyright 2021 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
Downloads, extracts, and loads symbols for the current version of Google Chrome
running under the debugger. This only works for official, Google Chrome builds.
To use, add this to .lldbinit:
command script import /path/to/tools/lldb/
If you are attached to any Google Chrome process, type:
(lldb) getchromesyms
And the symbols will automatically be added to the debugger. Symbols by default
are stored in /tmp because they are large. If you want to change the location,
add this to .lldbinit, after the `script import`:
script lldb_chrome_symbols.SYMBOL_STORE = '/path/to/symbols'
Note that because Google Chrome is codesigned with certain security options,
you need to either have System Integrity Protection Debugging disabled to be
able to attach to the process, or force re-sign the binary to remove the
codesigning options.
import glob
import lldb
import os.path
import plistlib
import sys
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'mac'))
import download_symbols
SYMBOL_STORE = '/tmp/lldb_chrome_symbols'
def get_chrome_symbols(debugger, command, result, internal_dict):
modules = debugger.GetSelectedTarget().module_iter()
chrome_framework = [
m for m in modules
if m.GetFileSpec().GetFilename() == 'Google Chrome Framework'
if len(chrome_framework) != 1:
print('Could not find a "Google Chrome Framework" module in the ' \
'current target',
chrome_framework = chrome_framework[0]
# Extract the version information from the path.
# framework_parent_dir is:
# /Applications/Google Chrome Chrome Framework.framework/Versions/91.0.4449.6
framework_parent_dir = chrome_framework.GetFileSpec().GetDirectory()
chrome_version = os.path.basename(framework_parent_dir)
# Get the outer .app bundle path.
outer_bundle = os.path.dirname(
chrome_arch = chrome_framework.GetTriple().split('-')[0]
chrome_channel = _get_channel(outer_bundle, chrome_version)
if os.path.exists(SYMBOL_STORE):
dsym_dir = download_symbols.get_symbol_directory(
chrome_version, chrome_channel, chrome_arch, SYMBOL_STORE)
if os.path.exists(dsym_dir):
print('Adding existing symbols from {}'.format(dsym_dir),
_add_symbols(debugger, dsym_dir)
dsym_dir = download_symbols.download_chrome_symbols(chrome_version,
if dsym_dir:
_add_symbols(debugger, dsym_dir)
def _get_channel(outer_bundle, version):
"""Looks up the Chrome release channel by reading the Info.plist key. The
target's Chrome `version` will be checked against the `outer_bundle`'s."""
info_plist = os.path.join(outer_bundle, 'Contents', 'Info.plist')
# Reading the Info.plist may fail if this is a core or minidump, but
# there is no way to test in the lldb API if the target is one.
with open(info_plist, 'rb') as f:
plist = plistlib.load(f)
if plist['KSVersion'] != version:
# The on-disk bundle version does not match the target version,
# so guess the channel.
return None
kschannel = plist['KSChannelID']
if kschannel == '':
return 'stable'
channels = ('extended', 'stable', 'beta', 'dev', 'canary')
for channel in channels:
if channel in kschannel:
return channel
return None
def _add_symbols(debugger, dsym_dir):
"""Adds the dSYMs in `dsym_dir` to the `debugger`. This filters the symbols
to just the relevant modules that are loaded in the process because lldb
prints an error when adding symbols that are not used.
dsyms = glob.glob(os.path.join(dsym_dir, '*.dSYM'))
wanted_dsyms = set([
m.GetFileSpec().GetFilename() + '.dSYM'
for m in debugger.GetSelectedTarget().module_iter()
for dsym in dsyms:
if os.path.basename(dsym) in wanted_dsyms:
debugger.HandleCommand('target symbol add "{}"'.format(dsym))
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f ' \
'lldb_chrome_symbols.get_chrome_symbols getchromesyms'