gdbclient: support various PT_INTERP values

gdb looks for an executable's dynamic linker using the PT_INTERP setting
from the executable. That value can be various things:
 - /system/bin/linker[64]
 - /system/bin/linker_asan[64]
 - /system/bin/bootstrap/linker[64]

Currently, only the bootstrap linker is available in the sysroot/symbols
directory. The ordinary and ASAN linkers are symlinks on the target and
are missing from the sysroot (aka symbols) directory.

Use the executable's PT_INTERP value to find the symbolized linker binary
and add it to the solib search path. If necessary, copy or pull a linker
binary.

Test: gdbclient.py -r ls
  "info sharedlib" shows $OUT/symbols/apex/com.android.runtime.debug/bin/linker64

Test: gdbclient.py -r /data/nativetest64/bionic-unit-tests/bionic-unit-tests
  "info sharedlib" shows $OUT/symbols/system/bin/bootstrap/linker64

Test: m asan_test
  gdbclient.py -r /data/nativetest64/asan_test/asan_test
  "info sharedlib" shows /tmp/gdbclient-linker-HunVs9/linker_asan64

Bug: http://b/134183407
Change-Id: I7f79943dcd9ec762d1aaf21178bb6ab3eff40617
This commit is contained in:
Ryan Prichard
2019-06-04 16:35:02 -07:00
parent 2122a3581a
commit 5d1c3cb157
2 changed files with 98 additions and 19 deletions

View File

@@ -20,14 +20,19 @@ import argparse
import json
import logging
import os
import posixpath
import re
import shutil
import subprocess
import sys
import tempfile
import textwrap
# Shared functions across gdbclient.py and ndk-gdb.py.
import gdbrunner
g_temp_dirs = []
def get_gdbserver_path(root, arch):
path = "{}/prebuilts/misc/gdbserver/android-{}/gdbserver{}"
if arch.endswith("64"):
@@ -101,14 +106,62 @@ def get_remote_pid(device, process_name):
return pids[0]
def ensure_linker(device, sysroot, is64bit):
local_path = os.path.join(sysroot, "system", "bin", "linker")
remote_path = "/system/bin/linker"
if is64bit:
local_path += "64"
remote_path += "64"
if not os.path.exists(local_path):
device.pull(remote_path, local_path)
def make_temp_dir(prefix):
global g_temp_dirs
result = tempfile.mkdtemp(prefix='gdbclient-linker-')
g_temp_dirs.append(result)
return result
def ensure_linker(device, sysroot, interp):
"""Ensure that the device's linker exists on the host.
PT_INTERP is usually /system/bin/linker[64], but on the device, that file is
a symlink to /apex/com.android.runtime/bin/linker[64]. The symbolized linker
binary on the host is located in ${sysroot}/apex, not in ${sysroot}/system,
so add the ${sysroot}/apex path to the solib search path.
PT_INTERP will be /system/bin/bootstrap/linker[64] for executables using the
non-APEX/bootstrap linker. No search path modification is needed.
For a tapas build, only an unbundled app is built, and there is no linker in
${sysroot} at all, so copy the linker from the device.
Returns:
A directory to add to the soinfo search path or None if no directory
needs to be added.
"""
# Static executables have no interpreter.
if interp is None:
return None
# gdb will search for the linker using the PT_INTERP path. First try to find
# it in the sysroot.
local_path = os.path.join(sysroot, interp.lstrip("/"))
if os.path.exists(local_path):
return None
# If the linker on the device is a symlink, search for the symlink's target
# in the sysroot directory.
interp_real, _ = device.shell(["realpath", interp])
interp_real = interp_real.strip()
local_path = os.path.join(sysroot, interp_real.lstrip("/"))
if os.path.exists(local_path):
if posixpath.basename(interp) == posixpath.basename(interp_real):
# Add the interpreter's directory to the search path.
return os.path.dirname(local_path)
else:
# If PT_INTERP is linker_asan[64], but the sysroot file is
# linker[64], then copy the local file to the name gdb expects.
result = make_temp_dir('gdbclient-linker-')
shutil.copy(local_path, os.path.join(result, posixpath.basename(interp)))
return result
# Pull the system linker.
result = make_temp_dir('gdbclient-linker-')
device.pull(interp, os.path.join(result, posixpath.basename(interp)))
return result
def handle_switches(args, sysroot):
@@ -247,7 +300,7 @@ end
return gdb_commands
def generate_setup_script(gdbpath, sysroot, binary_file, is64bit, port, debugger, connect_timeout=5):
def generate_setup_script(gdbpath, sysroot, linker_search_dir, binary_file, is64bit, port, debugger, connect_timeout=5):
# Generate a setup script.
# TODO: Detect the zygote and run 'art-on' automatically.
root = os.environ["ANDROID_BUILD_TOP"]
@@ -259,6 +312,8 @@ def generate_setup_script(gdbpath, sysroot, binary_file, is64bit, port, debugger
vendor_paths = ["", "hw", "egl"]
solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths]
solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths]
if linker_search_dir is not None:
solib_search_path += [linker_search_dir]
dalvik_gdb_script = os.path.join(root, "development", "scripts", "gdb", "dalvik.gdb")
if not os.path.exists(dalvik_gdb_script):
@@ -275,7 +330,7 @@ def generate_setup_script(gdbpath, sysroot, binary_file, is64bit, port, debugger
raise Exception("Unknown debugger type " + debugger)
def main():
def do_main():
required_env = ["ANDROID_BUILD_TOP",
"ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
for env in required_env:
@@ -303,11 +358,21 @@ def main():
binary_file, pid, run_cmd = handle_switches(args, sysroot)
with binary_file:
if sys.platform.startswith("linux"):
platform_name = "linux-x86"
elif sys.platform.startswith("darwin"):
platform_name = "darwin-x86"
else:
sys.exit("Unknown platform: {}".format(sys.platform))
arch = gdbrunner.get_binary_arch(binary_file)
is64bit = arch.endswith("64")
# Make sure we have the linker
ensure_linker(device, sysroot, is64bit)
llvm_readobj_path = os.path.join(root, "prebuilts", "clang", "host", platform_name,
"llvm-binutils-stable", "llvm-readobj")
interp = gdbrunner.get_binary_interp(binary_file.name, llvm_readobj_path)
linker_search_dir = ensure_linker(device, sysroot, interp)
tracer_pid = get_tracer_pid(device, pid)
if tracer_pid == 0:
@@ -327,19 +392,12 @@ def main():
gdbrunner.forward_gdbserver_port(device, local=args.port,
remote="tcp:{}".format(args.port))
# Find where gdb is
if sys.platform.startswith("linux"):
platform_name = "linux-x86"
elif sys.platform.startswith("darwin"):
platform_name = "darwin-x86"
else:
sys.exit("Unknown platform: {}".format(sys.platform))
gdb_path = os.path.join(root, "prebuilts", "gdb", platform_name, "bin",
"gdb")
# Generate a gdb script.
setup_commands = generate_setup_script(gdbpath=gdb_path,
sysroot=sysroot,
linker_search_dir=linker_search_dir,
binary_file=binary_file,
is64bit=is64bit,
port=args.port,
@@ -368,5 +426,15 @@ def main():
print("")
raw_input("Press enter to shutdown gdbserver")
def main():
try:
do_main()
finally:
global g_temp_dirs
for temp_dir in g_temp_dirs:
shutil.rmtree(temp_dir)
if __name__ == "__main__":
main()