blob: 7bbcde2ad4d6d76dcc55cfca6098ca5a6ccaf1a2 [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# A helper script for programs loaded by the Bare Metal loader.
#
import gdb
import os
import re
import subprocess
import traceback
import time
# The text section in the objdump result looks for something like:
#
# Idx Name Size VMA LMA File off Algn
# 8 .text 0006319b 0000bc20 0000bc20 0000bc20 2**4
# CONTENTS, ALLOC, LOAD, READONLY, CODE
_TEXT_SECTION_PATTERN = re.compile(r'\.text\s+(?:\w+\s+){3}(\w+)')
def _get_text_section_file_offset(path):
"""Returns the offset of the text section in the file."""
objdump_result = subprocess.check_output(['objdump', '-h', path])
match = _TEXT_SECTION_PATTERN.search(objdump_result.decode())
if not match:
return None
return int(match.group(1), 16)
class LoadHandlerBreakpoint(gdb.Breakpoint):
def __init__(self, main_binary, library_path, breakpoint_spec,
name_expr, addr_expr):
super(LoadHandlerBreakpoint, self).__init__(breakpoint_spec)
self._main_binary = main_binary
self._library_path = library_path
self._name_expr = name_expr
self._addr_expr = addr_expr
def _get_binary_path_from_link_map(self):
name = gdb.execute('p %s' % self._name_expr, to_string=True)
# This will be like: $5 = 0x357bc "libc.so"
matched = re.search(r'^.*"(.*)"', name, re.M)
if not matched:
print('Failed to retrieve the name of the shared object: "%s"' % name)
return None
path = matched.group(1)
# Check if this is the main binary before the check for
# "lib" to handle tests which start from lib such as libndk_test
# properly.
if path == os.path.basename(self._main_binary) or path == 'main.nexe':
path = self._main_binary
else:
# Some files are in a subdirectory. So search files in the _library_path.
for dirpath, _, filenames in os.walk(self._library_path):
if path in filenames:
path = os.path.join(dirpath, path)
break
if not os.path.exists(path):
# TODO(crbug.com/354290): In theory, we should be able to
# extract the APK and tell GDB the path to the NDK shared
# object.
print('%s does not exist! Maybe NDK in APK?' % path)
return None
return path
def _get_text_section_address_from_link_map(self, path):
base_addr_line = gdb.execute('p %s' % self._addr_expr, to_string=True)
# This will be like: $3 = 4148191232
matched = re.search(r'^.* = (\d+)', base_addr_line, re.M)
if not matched:
print('Failed to retrieve the address of the shared object: "%s"' %
base_addr_line)
return None
base_addr = int(matched.group(1))
file_off = _get_text_section_file_offset(path)
if file_off is None:
print('Unexpected objdump output for %s' % path)
return None
return file_off + base_addr
def stop(self):
"""Called when _NOTIFY_GDB_OF_LOAD_FUNC_NAME function is executed."""
try:
path = self._get_binary_path_from_link_map()
if not path:
return False
text_addr = self._get_text_section_address_from_link_map(path)
if text_addr is None:
print('Type \'c\' or \'continue\' to keep debugging')
# Return True to stop the execution.
return True
gdb.execute('add-symbol-file %s 0x%x' % (path, text_addr))
return False
except:
print(traceback.format_exc())
return True
def _get_program_loaded_address(path):
path_suffix = '/' + os.path.basename(path)
while True:
mapping = gdb.execute('info proc mapping', to_string=True)
for line in mapping.splitlines():
# Here is the list of columns:
# 1) Start address.
# 2) End address.
# 3) Size.
# 4) Offset.
# 5) Pathname.
# For example:
# 0xf5627000 0xf5650000 0x29000 0x0 /ssd/arc/out/.../runnable-ld.so
column_list = line.split()
if len(column_list) == 5 and column_list[4].endswith(path_suffix):
return int(column_list[0], 16)
print('Failed to find the loaded address of ' + path +
', retrying...')
time.sleep(0.1)
def init(arc_nexe, library_path, runnable_ld_path, lock_file,
remote_address=None, ssh_options=None):
"""Initializes GDB plugin for nacl_helper in Bare Metal mode.
If remote_address is specified, we control the _LOCK_FILE using this
address. This should be specified only for Chrome OS.
"""
if arc_nexe.endswith('_i686.nexe'):
# With x86 ABI, we use the stack to pass arguments:
# $esp+0: return address
# $esp+4: first argument (name)
# $esp+8: second argument (base)
name_expr = '((char**)($esp+4))[0]'
addr_expr = '((unsigned int*)($esp+8))[0]'
elif arc_nexe.endswith('_arm.nexe'):
# With ARM ABI, we use R0 and R1 to pass the first and second
# arguments, respectively.
name_expr = '(char*)$r0'
addr_expr = '(unsigned int)$r1'
else:
raise Exception('Unsupported architecture')
program_address = (_get_program_loaded_address(runnable_ld_path) +
_get_text_section_file_offset(runnable_ld_path))
gdb.execute('add-symbol-file %s 0x%x' % (runnable_ld_path, program_address))
LoadHandlerBreakpoint(arc_nexe, library_path,
'__bare_metal_notify_gdb_of_load',
name_expr, addr_expr)
# Everything gets ready, so unlock the program.
if remote_address:
command = ['ssh', 'root@%s' % remote_address]
if ssh_options:
command.extend(ssh_options)
command.extend(['rm', lock_file])
subprocess.check_call(command)
else:
os.unlink(lock_file)
def init_for_unittest(bare_metal_loader, test_binary, library_path):
# TODO(crbug.com/376666): nonsfi_loader does not support IRT
# interfaces for GDB. Use bare_metal_gdb.lock even for unittests and
# stop using bare_metal::bare_metal_irt_notify_gdb_of_load.
LoadHandlerBreakpoint(
test_binary, library_path,
'bare_metal::bare_metal_irt_notify_gdb_of_load',
'lm->l_name', 'lm->l_addr')
# TODO(crbug.com/310118): It seems only very recent GDB has
# remove-symbol-file. Create a hook for unload events once we switch
# to recent GDB.