import mock
import os
import re
import shutil
import subprocess
import sys
import tempfile
import textwrap
import unittest
import zipfile
sys.path.insert(
1,
os.path.join(
os.path.dirname(__file__), '..', '..', '..', '..', 'build', 'android'))
import devil_chromium
from pylib import constants
sys.path.insert(1,
constants.host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH)
import stack
sys.path.insert(
1, os.path.join(constants.DIR_SOURCE_ROOT, 'build'))
import zip_helpers
class FakeSymbolizer:
def __init__(self, directory):
self._lib_directory = directory
def GetSymbolInformation(self, library, address):
basename = os.path.basename(library)
local_file = os.path.join(self._lib_directory, basename)
if not os.path.exists(local_file):
return [('<UNKNOWN>', library)]
lib_size = os.stat(local_file).st_size
if address >= lib_size:
return [('??', '??:0:0')]
namespace = basename.split('.')[0].replace('lib', '', 1)
if 'android_clang_' in library:
namespace += '32'
method_name = '{}::Func_{:X}'.format(namespace, address)
return [(method_name, '{}.cc:1:1'.format(namespace))]
@staticmethod
def IsValidTarget(path):
return True
class StackDecodeTest(unittest.TestCase):
def setUp(self):
self._num_libraries = 0
self._temp_dir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self._temp_dir)
def _MakeElf(self, library):
lib_dir = os.path.dirname(library)
if not os.path.exists(lib_dir):
os.makedirs(lib_dir)
data = '\x7fELF' + ' ' * (0xE00 - self._num_libraries)
self._num_libraries += 1
with open(library, 'wb') as f:
f.write(data.encode('utf-8'))
def _MakeApk(self, apk, libs, apk_dir, out_dir, crazy):
apk_file = os.path.join(apk_dir, apk)
with zipfile.ZipFile(apk_file, 'w') as archive:
for lib in libs:
path, name = os.path.split(lib)
library_file = os.path.join(out_dir, path, 'lib.unstripped', name)
self._MakeElf(library_file)
name_in_apk = 'crazy.' + lib if crazy else lib
zip_helpers.add_to_zip_hermetic(
archive,
name_in_apk,
src_path=library_file,
alignment=0x1000)
def _StripLines(self, text):
if isinstance(text, str):
lines = text.splitlines()
else:
assert isinstance(text, list)
lines = text
lines = [line.strip() for line in lines]
return [line for line in lines if line]
def _RunCase(self, logcat, expected, apks, crazy=False):
temp = self._temp_dir
out_dir = os.path.join(temp, 'out', 'Debug')
os.makedirs(out_dir)
apk_dir = os.path.join(out_dir, 'apks')
os.makedirs(apk_dir)
input_file = os.path.join(temp, 'input.txt')
output_file = os.path.join(temp, 'output.txt')
for name, libs in apks.items():
self._MakeApk(name, libs, apk_dir, out_dir, crazy)
symbolizer = FakeSymbolizer(os.path.join(out_dir, 'lib.unstripped'))
with open(input_file, 'w') as f:
input_lines = self._StripLines(logcat)
f.write('\n'.join(input_lines))
stack_script_args = [
'--output-directory',
out_dir,
'--apks-directory',
apk_dir,
input_file,
]
with open(output_file, 'w') as f:
old_stdout = sys.stdout
sys.stdout = f
try:
stack.main(stack_script_args, test_symbolizer=symbolizer)
except Exception:
pass
sys.stdout.flush()
sys.stdout = old_stdout
with open(output_file, 'r') as f:
lines = f.readlines()
delimiter = [l for l in lines if 'RELADDR' in l]
if delimiter:
index = lines.index(delimiter[-1])
output_lines = lines[index + 1:]
output_lines = self._StripLines(output_lines)
else:
output_lines = []
expected_lines = self._StripLines(expected)
expected_tokens = [line.split() for line in expected_lines]
actual_tokens = [line.split() for line in output_lines]
self.assertEqual(len(expected_tokens), len(actual_tokens))
for i in range(len(expected_tokens)):
self.assertEqual(expected_tokens[i], actual_tokens[i])
@mock.patch('stack_core._BuildIdFromElf')
def test_BasicDecoding(self, patch_build_id):
patch_build_id.side_effect = ['1', '1', '2', '2']
apks = {
'chrome.apk': ['libchrome.so', 'libfoo.so'],
}
input_trace = textwrap.dedent('''
DEBUG : #01 pc 00000174 /path==/base.apk (offset 0x00001000)
DEBUG : #02 pc 00000274 /path==/base.apk (offset 0x00002000)
DEBUG : #03 pc 00000374 /path==/lib/arm/libchrome.so
''')
expected_decode = textwrap.dedent('''
00000174 chrome::Func_174 chrome.cc:1:1
00000274 foo::Func_274 foo.cc:1:1
00000374 chrome::Func_374 chrome.cc:1:1
''')
self._RunCase(input_trace, expected_decode, apks)
@mock.patch('stack_core._BuildIdFromElf')
def test_OutOfRangeAddresses(self, patch_build_id):
patch_build_id.side_effect = ['1', '1']
apks = {
'chrome.apk': ['libchrome.so'],
}
input_trace = textwrap.dedent('''
DEBUG : #01 pc 00777777 /path==/base.apk (offset 0x00001000)
DEBUG : #02 pc 00000374 /path==/base.apk (offset 0x00003000)
''')
expected_decode = textwrap.dedent('''
00777777 ?? ??:0:0
00000374 offset 0x00003000 /path==/base.apk
''')
self._RunCase(input_trace, expected_decode, apks)
def test_SystemLibraries(self):
apks = {
'chrome.apk': [],
}
input_trace = textwrap.dedent('''
DEBUG : #01 pc 00000474 /system/lib/libart.so (art_function+40)
DEBUG : #02 pc 00000474 /system/lib/libart.so
''')
expected_decode = textwrap.dedent('''
00000474 art_function+40 /system/lib/libart.so
00000474 <UNKNOWN> /system/lib/libart.so
''')
self._RunCase(input_trace, expected_decode, apks)
@mock.patch('stack_core._BuildIdFromElf')
def test_MultiArchPrimaryAbi(self, patch_build_id):
patch_build_id.side_effect = ['1', '1']
apks = {
'monochrome.apk': [
'libmonochrome.so', 'android_clang_arm/libmonochrome.so'
],
}
input_trace = textwrap.dedent('''
DEBUG : #01 pc 00000174 /path==/base.apk (offset 0x00001000)
''')
expected_decode = textwrap.dedent('''
00000174 monochrome::Func_174 monochrome.cc:1:1
''')
self._RunCase(input_trace, expected_decode, apks)
@mock.patch('stack_core._BuildIdFromElf')
def test_MultiArchSecondary(self, patch_build_id):
patch_build_id.side_effect = ['1', '2', '1']
apks = {
'monochrome.apk': [
'libmonochrome.so', 'android_clang_arm/libmonochrome.so'
],
}
input_trace = textwrap.dedent('''
DEBUG : #02 pc 00000274 /path==/base.apk (offset 0x00002000)
''')
expected_decode = textwrap.dedent('''
00000274 monochrome32::Func_274 monochrome32.cc:1:1
''')
self._RunCase(input_trace, expected_decode, apks)
@mock.patch('stack_core._BuildIdFromElf')
def test_CrazyUncompressedLibraries(self, patch_build_id):
patch_build_id.side_effect = ['1', '1']
apks = {
'chrome.apk': ['libchrome.so'],
}
input_trace = textwrap.dedent('''
DEBUG : #01 pc 00000174 /path==/base.apk (offset 0x00001000)
''')
expected_decode = textwrap.dedent('''
00000174 chrome::Func_174 chrome.cc:1:1
''')
self._RunCase(input_trace, expected_decode, apks, crazy=True)
def test_AndroidQ(self):
apks = {
'chrome.apk': ['libchrome.so'],
}
input_trace = textwrap.dedent('''
DEBUG : #01 pc 00000174 /path==/base.apk!libchrome.so (offset 0x00001000)
''')
expected_decode = textwrap.dedent('''
00000174 chrome::Func_174 chrome.cc:1:1
''')
self._RunCase(input_trace, expected_decode, apks)
if __name__ == '__main__':
unittest.main()