From 5d1c3cb157c28d451bf1c074849ac4ad201a1de0 Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Tue, 4 Jun 2019 16:35:02 -0700 Subject: [PATCH] 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 --- python-packages/gdbrunner/__init__.py | 11 +++ scripts/gdbclient.py | 106 +++++++++++++++++++++----- 2 files changed, 98 insertions(+), 19 deletions(-) diff --git a/python-packages/gdbrunner/__init__.py b/python-packages/gdbrunner/__init__.py index 17b98337e..dbdcfed9e 100644 --- a/python-packages/gdbrunner/__init__.py +++ b/python-packages/gdbrunner/__init__.py @@ -20,6 +20,7 @@ import adb import argparse import atexit import os +import re import subprocess import sys import tempfile @@ -318,6 +319,16 @@ def get_binary_arch(binary_file): 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): """Start gdb in the background and block until it finishes. diff --git a/scripts/gdbclient.py b/scripts/gdbclient.py index a96aaa885..e3a050330 100755 --- a/scripts/gdbclient.py +++ b/scripts/gdbclient.py @@ -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()