blob: 0e76a30ea4d37acbe0398a9515f4a18bb6ecfb5d [file] [log] [blame]
# Copyright 2016 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Initialize gdb for debugging Fuchsia code."""
import gdb
import glob
import os
# Extract the GDB version number so scripts can easily examine it.
# We export four variables:
# GDB_MAJOR_VERSION, GDB_MINOR_VERSION, GDB_PATCH_VERSION, GDB_GOOGLE_VERSION.
# The first three are standard, e.g. 7.10.1.
# GDB_GOOGLE_VERSION is the N in "-gfN" Fuchsia releases.
# A value of zero means this isn't a Google Fuchsia gdb.
_GDB_DASH_VERSION = gdb.VERSION.split("-")
_GDB_DOT_VERSION = _GDB_DASH_VERSION[0].split(".")
GDB_MAJOR_VERSION = int(_GDB_DOT_VERSION[0])
GDB_MINOR_VERSION = int(_GDB_DOT_VERSION[1])
if len(_GDB_DOT_VERSION) >= 3:
GDB_PATCH_VERSION = int(_GDB_DOT_VERSION[2])
else:
GDB_PATCH_VERSION = 0
GDB_GOOGLE_VERSION = 0
if len(_GDB_DASH_VERSION) >= 2:
if _GDB_DASH_VERSION[1].startswith("gf"):
try:
GDB_GOOGLE_VERSION = int(_GDB_DASH_VERSION[1][2:])
except ValueError:
pass
# The top level zircon build directory
_TOP_ZIRCON_BUILD_DIR = "out/build-zircon"
# Prefix of zircon build directories within _TOP_ZIRCON_BUILD_DIR.
_ZIRCON_BUILD_SUBDIR_PREFIX = "build"
# True if fuchsia support has been initialized.
_INITIALIZED_FUCHSIA_SUPPORT = False
# The prefix for fuchsia commands.
_FUCHSIA_COMMAND_PREFIX = "fuchsia"
class _FuchsiaPrefix(gdb.Command):
"""Prefix command for Fuchsia-specific commands."""
def __init__(self):
super(_FuchsiaPrefix, self).__init__(
_FUCHSIA_COMMAND_PREFIX, gdb.COMMAND_USER, prefix=True)
class _FuchsiaSetPrefix(gdb.Command):
"""Prefix "set" command for Fuchsia parameters."""
def __init__(self):
super(_FuchsiaSetPrefix, self).__init__(
"set %s" % (_FUCHSIA_COMMAND_PREFIX), gdb.COMMAND_USER,
prefix=True)
class _FuchsiaShowPrefix(gdb.Command):
"""Prefix "show" command for Fuchsia parameters."""
def __init__(self):
super(_FuchsiaShowPrefix, self).__init__(
"show %s" % (_FUCHSIA_COMMAND_PREFIX), gdb.COMMAND_USER,
prefix=True)
def invoke(self, from_tty):
# TODO(dje): Show all the parameters, a la cmd_show_list.
pass
class _FuchsiaVerbosity(gdb.Parameter):
"""Verbosity for Fuchsia gdb support.
There are four levels of verbosity:
0 = off
1 = minimal
2 = what a typical user might want to see
3 = everything, intended for maintainers only
"""
# Note: While not every verbosity level is exercised today, these levels
# are convention in Google's internal gdb.
set_doc = "Set level of Fuchsia verbosity."
show_doc = "Show level of Fuchsia verbosity."
def __init__(self):
super(_FuchsiaVerbosity, self).__init__(
"%s verbosity" % (_FUCHSIA_COMMAND_PREFIX),
gdb.COMMAND_FILES, gdb.PARAM_ZINTEGER)
# Default to basic informational messages to help users know
# what's going on.
self.value = 1
def get_show_string(self, pvalue):
return "Fuchsia verbosity is " + pvalue + "."
def get_set_string(self):
# Ugh. There doesn't seem to be a way to implement a gdb parameter in
# Python that will be silent when the user changes the value.
return "Fuchsia verbosity been set to %d." % (self.value)
def _IsFuchsiaFile(objfile):
"""Return True if objfile is a Fuchsia file."""
# TODO(dje): Not sure how to effectively achieve this.
# Assume we're always debugging a Fuchsia program for now.
# If the user wants to debug native programs, s/he can use native gdb.
return True
def _ClearObjfilesHandler(event):
"""Reset debug information tracking when all objfiles are unloaded."""
event.progspace.seen_exec = False
def _FindSysroot(arch):
"""Return the path to the sysroot for arch."""
if arch == "x64":
suffix = "-x64"
elif arch == "arm64":
suffix = "-arm64"
else:
assert(False)
print ("TRYING: %s/%s*%s" % (
_TOP_ZIRCON_BUILD_DIR, _ZIRCON_BUILD_SUBDIR_PREFIX, suffix))
for filename in glob.iglob("%s/%s*%s" % (
_TOP_ZIRCON_BUILD_DIR, _ZIRCON_BUILD_SUBDIR_PREFIX, suffix)):
return "%s/sysroot" % (filename)
return None
def _NewObjfileHandler(event):
"""Handle new objfiles being loaded."""
# TODO(dje): Use this hook to automagically fetch debug info.
verbosity = gdb.parameter(
"%s verbosity" % (_FUCHSIA_COMMAND_PREFIX))
if verbosity >= 3:
print "Hi, I'm the new_objfile event handler."
objfile = event.new_objfile
progspace = objfile.progspace
# Assume the first objfile we see is the main executable.
# There's nothing else we can do at this point.
seen_exec = hasattr(progspace, "seen_exec") and progspace.seen_exec
# Early exit if nothing to do.
# We don't handle multiple arches so we KISS.
if seen_exec:
if verbosity >= 3:
print "Already seen exec, ignoring: %s" % (basename)
return
progspace.seen_exec = True
filename = objfile.username
basename = os.path.basename(filename)
if objfile.owner is not None:
if verbosity >= 3:
print "Separate debug file, ignoring: %s" % (basename)
return
# If we're debugging a native executable, unset the solib search path.
if not _IsFuchsiaFile(objfile):
if verbosity >= 3:
print "Debugging non-Fuchsia file: %s" % (basename)
print "Note: Unsetting solib-search-path."
gdb.execute("set solib-search-path")
return
# The sysroot to use is dependent on the architecture of the program.
# This is needed to find ld.so debug info.
# TODO(dje): IWBN to not need ld.so debug info.
# TODO(dje): IWBN if objfiles exposed their arch field.
arch_string = gdb.execute("show arch", to_string=True)
if arch_string.find("arm64") >= 0:
# Alas there are different directories for different arm64 builds
# (qemu, rpi3, etc.). Pick something, hopefully this can go away soon.
sysroot_dir = _FindSysroot("arm64")
elif arch_string.find("x64") >= 0:
sysroot_dir = _FindSysroot("x64")
else:
print "WARNING: unsupported architecture\n%s" % (arch_string)
return
# TODO(dje): We can't use sysroot to find ld.so.1 because it doesn't
# have a path on Fuchsia. Plus files in Fuchsia are intended to be
# "ephemeral" by nature. So we punt on setting sysroot for now, even
# though IWBN if we could use it.
if sysroot_dir:
solib_search_path = "%s/debug" % (sysroot_dir)
print "Note: Setting solib-search-path to %s" % (solib_search_path)
gdb.execute("set solib-search-path %s" % (solib_search_path))
else:
print "WARNING: could not find sysroot directory"
def _InitializeFuchsiaObjfileTracking():
# We *need* solib-search-path set so that we can find debug info for
# ld.so.1. Otherwise it's game over for a usable debug session:
# We won't be able to set a breakpoint at the dynamic linker breakpoint
# and we won't be able to relocate the program (all Fuchsia executables
# are PIE). However, we don't necessarily know which architecture we're
# debugging yet so we don't know which directory to set the search path
# to. To solve this we hook into the "new objfile" event.
# This event can also let us automagically fetch debug info for files
# as they're loaded (TODO(dje)).
gdb.events.clear_objfiles.connect(_ClearObjfilesHandler)
gdb.events.new_objfile.connect(_NewObjfileHandler)
class _SetFuchsiaDefaults(gdb.Command):
"""Set GDB parameters to values useful for Fuchsia code.
Usage: set-fuchsia-defaults
These changes are made:
set non-stop on
set target-async on
set remotetimeout 10
set sysroot # (set to empty path)
Fuchsia gdbserver currently supports non-stop only (and even that support
is preliminary so heads up).
"""
def __init__(self):
super(_SetFuchsiaDefaults, self).__init__(
"%s set-defaults" % (_FUCHSIA_COMMAND_PREFIX),
gdb.COMMAND_DATA)
# The name and parameters of this function are defined by GDB.
# pylint: disable=invalid-name
# pylint: disable=unused-argument
def invoke(self, arg, from_tty):
"""GDB calls this to perform the command."""
# We don't need to tell the user about everything we do.
# But it's helpful to give a heads up for things s/he may trip over.
print "Note: Enabling non-stop, target-async."
gdb.execute("set non-stop on")
gdb.execute("set target-async on")
gdb.execute("set remotetimeout 10")
# The default is "target:" which will cause gdb to fetch every dso,
# which is ok sometimes, but for right now it's a nuisance.
print "Note: Unsetting sysroot."
gdb.execute("set sysroot")
def _InstallFuchsiaCommands():
# We don't do anything with the result, we just need to call
# the constructor.
_FuchsiaPrefix()
_FuchsiaSetPrefix()
_FuchsiaShowPrefix()
_FuchsiaVerbosity()
_SetFuchsiaDefaults()
def initialize():
"""Set up GDB for debugging Fuchsia code.
This function is invoked via gdb's "system.gdbinit"
when it detects it is being started in a fuchsia tree.
It is ok to call this function multiple times, but only the first
is effective.
Returns:
Nothing.
"""
global _INITIALIZED_FUCHSIA_SUPPORT
if _INITIALIZED_FUCHSIA_SUPPORT:
print "Fuchsia support already loaded."
return
_INITIALIZED_FUCHSIA_SUPPORT = True
_InstallFuchsiaCommands()
_InitializeFuchsiaObjfileTracking()
print "Setting fuchsia defaults. 'help fuchsia set-defaults' for details."
gdb.execute("fuchsia set-defaults")