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:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user