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,6 +20,7 @@ import adb
import argparse import argparse
import atexit import atexit
import os import os
import re
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@@ -318,6 +319,16 @@ def get_binary_arch(binary_file):
raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine)) raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine))
def get_binary_interp(binary_path, llvm_readobj_path):
args = [llvm_readobj_path, "--elf-output-style=GNU", "-l", binary_path]
output = subprocess.check_output(args, universal_newlines=True)
m = re.search(r"\[Requesting program interpreter: (.*?)\]\n", output)
if m is None:
return None
else:
return m.group(1)
def start_gdb(gdb_path, gdb_commands, gdb_flags=None): def start_gdb(gdb_path, gdb_commands, gdb_flags=None):
"""Start gdb in the background and block until it finishes. """Start gdb in the background and block until it finishes.

View File

@@ -20,14 +20,19 @@ import argparse
import json import json
import logging import logging
import os import os
import posixpath
import re import re
import shutil
import subprocess import subprocess
import sys import sys
import tempfile
import textwrap import textwrap
# Shared functions across gdbclient.py and ndk-gdb.py. # Shared functions across gdbclient.py and ndk-gdb.py.
import gdbrunner import gdbrunner
g_temp_dirs = []
def get_gdbserver_path(root, arch): def get_gdbserver_path(root, arch):
path = "{}/prebuilts/misc/gdbserver/android-{}/gdbserver{}" path = "{}/prebuilts/misc/gdbserver/android-{}/gdbserver{}"
if arch.endswith("64"): if arch.endswith("64"):
@@ -101,14 +106,62 @@ def get_remote_pid(device, process_name):
return pids[0] return pids[0]
def ensure_linker(device, sysroot, is64bit): def make_temp_dir(prefix):
local_path = os.path.join(sysroot, "system", "bin", "linker") global g_temp_dirs
remote_path = "/system/bin/linker" result = tempfile.mkdtemp(prefix='gdbclient-linker-')
if is64bit: g_temp_dirs.append(result)
local_path += "64" return result
remote_path += "64"
if not os.path.exists(local_path):
device.pull(remote_path, local_path) 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): def handle_switches(args, sysroot):
@@ -247,7 +300,7 @@ end
return gdb_commands 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. # Generate a setup script.
# TODO: Detect the zygote and run 'art-on' automatically. # TODO: Detect the zygote and run 'art-on' automatically.
root = os.environ["ANDROID_BUILD_TOP"] 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"] vendor_paths = ["", "hw", "egl"]
solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths] 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] 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") dalvik_gdb_script = os.path.join(root, "development", "scripts", "gdb", "dalvik.gdb")
if not os.path.exists(dalvik_gdb_script): 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) raise Exception("Unknown debugger type " + debugger)
def main(): def do_main():
required_env = ["ANDROID_BUILD_TOP", required_env = ["ANDROID_BUILD_TOP",
"ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"] "ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
for env in required_env: for env in required_env:
@@ -303,11 +358,21 @@ def main():
binary_file, pid, run_cmd = handle_switches(args, sysroot) binary_file, pid, run_cmd = handle_switches(args, sysroot)
with binary_file: 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) arch = gdbrunner.get_binary_arch(binary_file)
is64bit = arch.endswith("64") is64bit = arch.endswith("64")
# Make sure we have the linker # 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) tracer_pid = get_tracer_pid(device, pid)
if tracer_pid == 0: if tracer_pid == 0:
@@ -327,19 +392,12 @@ def main():
gdbrunner.forward_gdbserver_port(device, local=args.port, gdbrunner.forward_gdbserver_port(device, local=args.port,
remote="tcp:{}".format(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_path = os.path.join(root, "prebuilts", "gdb", platform_name, "bin",
"gdb") "gdb")
# Generate a gdb script. # Generate a gdb script.
setup_commands = generate_setup_script(gdbpath=gdb_path, setup_commands = generate_setup_script(gdbpath=gdb_path,
sysroot=sysroot, sysroot=sysroot,
linker_search_dir=linker_search_dir,
binary_file=binary_file, binary_file=binary_file,
is64bit=is64bit, is64bit=is64bit,
port=args.port, port=args.port,
@@ -368,5 +426,15 @@ def main():
print("") print("")
raw_input("Press enter to shutdown gdbserver") 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__": if __name__ == "__main__":
main() main()