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
diff --git a/python-packages/gdbrunner/__init__.py b/python-packages/gdbrunner/__init__.py
index 17b9833..dbdcfed 100644
--- a/python-packages/gdbrunner/__init__.py
+++ b/python-packages/gdbrunner/__init__.py
@@ -20,6 +20,7 @@
import argparse
import atexit
import os
+import re
import subprocess
import sys
import tempfile
@@ -318,6 +319,16 @@
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 a96aaa8..e3a0503 100755
--- a/scripts/gdbclient.py
+++ b/scripts/gdbclient.py
@@ -20,14 +20,19 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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()