Commit 9e10a67a authored by Matthew Cary's avatar Matthew Cary Committed by Commit Bot

[Android] Dexlayout with obfuscated profiles

Extends the dexlayout build path with profile obfuscation. This
hooks build/android/gyp/dex.py into the recent convert_dex_profile.py,
and makes the necessary changes to GN to pass the proguard mapping
to the dex pass.

Bug: 875276
Change-Id: Id595b17fdba69c7e2e2dcd252c467954a558c56f
Reviewed-on: https://chromium-review.googlesource.com/c/1366315
Commit-Queue: Matthew Cary <mattcary@chromium.org>
Reviewed-by: default avataragrieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615863}
parent e36b86e5
...@@ -477,6 +477,26 @@ def ProcessProfile(input_profile, proguard_mapping): ...@@ -477,6 +477,26 @@ def ProcessProfile(input_profile, proguard_mapping):
return profile return profile
def ObfuscateProfile(nonobfuscated_profile, dex_file, proguard_mapping,
dexdump_path, output_filename):
"""Helper method for obfuscating a profile.
Args:
nonobfuscated_profile: a profile with nonobfuscated symbols.
dex_file: path to the dex file matching the mapping.
proguard_mapping: a mapping from nonobfuscated to obfuscated symbols used
in the dex file.
dexdump_path: path to the dexdump utility.
output_filename: output filename in which to write the obfuscated profile.
"""
dexinfo = ProcessDex(_RunDexDump(dexdump_path, dex_file))
_, reverse_mapping = ProcessProguardMapping(
_ReadFile(proguard_mapping), dexinfo)
obfuscated_profile = ProcessProfile(
_ReadFile(nonobfuscated_profile), reverse_mapping)
obfuscated_profile.WriteToFile(output_filename)
def main(args): def main(args):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
......
...@@ -2,12 +2,31 @@ ...@@ -2,12 +2,31 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import unittest """Tests for convert_dex_profile.
Can be run from build/android/:
$ cd build/android
$ python convert_dex_profile_tests.py
"""
import os
import sys
import tempfile import tempfile
import unittest
import convert_dex_profile as cp import convert_dex_profile as cp
sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'gyp'))
from util import build_utils
cp.logging.disable(cp.logging.CRITICAL) cp.logging.disable(cp.logging.CRITICAL)
# There are two obfuscations used in the tests below, each with the same
# unobfuscated profile. The first, corresponding to DEX_DUMP, PROGUARD_MAPPING,
# and OBFUSCATED_PROFILE, has an ambiguous method a() which is mapped to both
# getInstance and initialize. The second, corresponding to DEX_DUMP_2,
# PROGUARD_MAPPING_2 and OBFUSCATED_PROFILE_2, removes the ambiguity.
DEX_DUMP = """ DEX_DUMP = """
Class descriptor : 'La;' Class descriptor : 'La;'
...@@ -76,6 +95,67 @@ PLa;->b()La; ...@@ -76,6 +95,67 @@ PLa;->b()La;
SLa;->a(Ljava/lang/Object;)I SLa;->a(Ljava/lang/Object;)I
HPLa;->a(Ljava/lang/String;)I""" HPLa;->a(Ljava/lang/String;)I"""
DEX_DUMP_2 = """
Class descriptor : 'La;'
Direct methods -
#0 : (in La;)
name : '<clinit>'
type : '(Ljava/lang/String;)V'
code -
catches : 1
0x000f - 0x001e
<any> -> 0x0093
positions :
0x0001 line=310
0x0057 line=313
locals :
#1 : (in La;)
name : '<init>'
type : '()V'
positions :
locals :
Virtual methods -
#0 : (in La;)
name : 'a'
type : '(Ljava/lang/String;)I'
positions :
0x0000 line=2
0x0003 line=3
0x001b line=8
locals :
0x0000 - 0x0021 reg=3 this La;
#1 : (in La;)
name : 'c'
type : '(Ljava/lang/Object;)I'
positions :
0x0000 line=8
0x0003 line=9
locals :
0x0000 - 0x0021 reg=3 this La;
#2 : (in La;)
name : 'b'
type : '()La;'
positions :
0x0000 line=1
locals :
"""
# pylint: disable=line-too-long
PROGUARD_MAPPING_2 = \
"""org.chromium.Original -> a:
org.chromium.Original sDisplayAndroidManager -> e
org.chromium.Original another() -> b
void initialize() -> c
org.chromium.Original getInstance():203 -> a
4:4:void inlined():237:237 -> a"""
OBFUSCATED_PROFILE_2 = \
"""La;
PLa;->b()La;
HPSLa;->a()La;
HPLa;->c()V"""
UNOBFUSCATED_PROFILE = \ UNOBFUSCATED_PROFILE = \
"""Lorg/chromium/Original; """Lorg/chromium/Original;
PLorg/chromium/Original;->another()Lorg/chromium/Original; PLorg/chromium/Original;->another()Lorg/chromium/Original;
...@@ -169,6 +249,28 @@ class GenerateProfileTests(unittest.TestCase): ...@@ -169,6 +249,28 @@ class GenerateProfileTests(unittest.TestCase):
for a, b in zip(sorted(f), sorted(UNOBFUSCATED_PROFILE.splitlines())): for a, b in zip(sorted(f), sorted(UNOBFUSCATED_PROFILE.splitlines())):
self.assertEquals(a.strip(), b.strip()) self.assertEquals(a.strip(), b.strip())
def testObfuscateProfile(self):
with build_utils.TempDir() as temp_dir:
# The dex dump is used as the dexfile, by passing /bin/cat as the dexdump
# program.
dex_path = os.path.join(temp_dir, 'dexdump')
with open(dex_path, 'w') as dex_file:
dex_file.write(DEX_DUMP_2)
mapping_path = os.path.join(temp_dir, 'mapping')
with open(mapping_path, 'w') as mapping_file:
mapping_file.write(PROGUARD_MAPPING_2)
unobfuscated_path = os.path.join(temp_dir, 'unobfuscated')
with open(unobfuscated_path, 'w') as unobfuscated_file:
unobfuscated_file.write(UNOBFUSCATED_PROFILE)
obfuscated_path = os.path.join(temp_dir, 'obfuscated')
cp.ObfuscateProfile(unobfuscated_path, dex_path, mapping_path, '/bin/cat',
obfuscated_path)
with open(obfuscated_path) as obfuscated_file:
obfuscated_profile = sorted(obfuscated_file.readlines())
for a, b in zip(
sorted(OBFUSCATED_PROFILE_2.splitlines()), obfuscated_profile):
self.assertEquals(a.strip(), b.strip())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -16,6 +16,10 @@ import zipfile ...@@ -16,6 +16,10 @@ import zipfile
from util import build_utils from util import build_utils
sys.path.insert(1, os.path.join(os.path.dirname(__file__), os.path.pardir))
import convert_dex_profile
def _CheckFilePathEndsWithJar(parser, file_path): def _CheckFilePathEndsWithJar(parser, file_path):
if not file_path.endswith(".jar"): if not file_path.endswith(".jar"):
...@@ -76,7 +80,12 @@ def _ParseArgs(args): ...@@ -76,7 +80,12 @@ def _ParseArgs(args):
help=('Path to ART dexlayout binary. There should be a ' help=('Path to ART dexlayout binary. There should be a '
'lib/ directory at the same path containing shared ' 'lib/ directory at the same path containing shared '
'libraries (shared with dexlayout).')) 'libraries (shared with dexlayout).'))
parser.add_option('--dexdump-path', help='Path to dexdump binary.')
parser.add_option(
'--proguard-mapping-path',
help=('Path to proguard map from obfuscated symbols in the jar to '
'unobfuscated symbols present in the code. If not '
'present, the jar is assumed not to be obfuscated.'))
options, paths = parser.parse_args(args) options, paths = parser.parse_args(args)
...@@ -84,8 +93,12 @@ def _ParseArgs(args): ...@@ -84,8 +93,12 @@ def _ParseArgs(args):
build_utils.CheckOptions(options, parser, required=required_options) build_utils.CheckOptions(options, parser, required=required_options)
if options.dexlayout_profile: if options.dexlayout_profile:
build_utils.CheckOptions(options, parser, required=('profman_path', build_utils.CheckOptions(
'dexlayout_path')) options,
parser,
required=('profman_path', 'dexlayout_path', 'dexdump_path'))
elif options.proguard_mapping_path is not None:
raise Exception('Unexpected proguard mapping without dexlayout')
if options.multidex_configuration_path: if options.multidex_configuration_path:
with open(options.multidex_configuration_path) as multidex_config_file: with open(options.multidex_configuration_path) as multidex_config_file:
...@@ -274,6 +287,16 @@ def _ZipMultidex(file_dir, dex_files): ...@@ -274,6 +287,16 @@ def _ZipMultidex(file_dir, dex_files):
return zip_name return zip_name
def _ZipSingleDex(dex_file, zip_name):
"""Zip up a single dex file.
Args:
dex_file: A dexfile whose name is ignored.
zip_name: The output file in which to write the zip.
"""
build_utils.DoZip([('classes.dex', dex_file)], zip_name)
def main(args): def main(args):
options, paths = _ParseArgs(args) options, paths = _ParseArgs(args)
if ((options.proguard_enabled == 'true' if ((options.proguard_enabled == 'true'
...@@ -306,6 +329,9 @@ def main(args): ...@@ -306,6 +329,9 @@ def main(args):
is_dex = options.dex_path.endswith('.dex') is_dex = options.dex_path.endswith('.dex')
is_jar = options.dex_path.endswith('.jar') is_jar = options.dex_path.endswith('.jar')
with build_utils.TempDir() as tmp_dir:
tmp_dex_dir = os.path.join(tmp_dir, 'tmp_dex_dir')
os.mkdir(tmp_dex_dir)
if is_jar and _NoClassFiles(paths): if is_jar and _NoClassFiles(paths):
# Handle case where no classfiles are specified in inputs # Handle case where no classfiles are specified in inputs
# by creating an empty JAR # by creating an empty JAR
...@@ -315,29 +341,44 @@ def main(args): ...@@ -315,29 +341,44 @@ def main(args):
# .dex files can't specify a name for D8. Instead, we output them to a # .dex files can't specify a name for D8. Instead, we output them to a
# temp directory then move them after the command has finished running # temp directory then move them after the command has finished running
# (see _MoveTempDexFile). For other files, tmp_dex_dir is None. # (see _MoveTempDexFile). For other files, tmp_dex_dir is None.
with build_utils.TempDir() as tmp_dex_dir:
_RunD8(dex_cmd, paths, tmp_dex_dir) _RunD8(dex_cmd, paths, tmp_dex_dir)
tmp_dex_output = os.path.join(tmp_dir, 'tmp_dex_output')
if is_dex: if is_dex:
_MoveTempDexFile(tmp_dex_dir, options.dex_path) _MoveTempDexFile(tmp_dex_dir, tmp_dex_output)
else: else:
# d8 supports outputting to a .zip, but does not have deterministic file # d8 supports outputting to a .zip, but does not have deterministic file
# ordering: https://issuetracker.google.com/issues/119945929 # ordering: https://issuetracker.google.com/issues/119945929
build_utils.ZipDir(options.dex_path, tmp_dex_dir) build_utils.ZipDir(tmp_dex_output, tmp_dex_dir)
if options.dexlayout_profile: if options.dexlayout_profile:
with build_utils.TempDir() as temp_dir: if options.proguard_mapping_path is not None:
binary_profile = _CreateBinaryProfile(options.dexlayout_profile, matching_profile = os.path.join(tmp_dir, 'obfuscated_profile')
options.dex_path, convert_dex_profile.ObfuscateProfile(
options.profman_path, temp_dir) options.dexlayout_profile, tmp_dex_output,
output_files = _LayoutDex(binary_profile, options.dex_path, options.proguard_mapping_path, options.dexdump_path,
options.dexlayout_path, temp_dir) matching_profile)
else:
logging.warning('No obfuscation for %s', options.dexlayout_profile)
matching_profile = options.dexlayout_profile
binary_profile = _CreateBinaryProfile(matching_profile, tmp_dex_output,
options.profman_path, tmp_dir)
output_files = _LayoutDex(binary_profile, tmp_dex_output,
options.dexlayout_path, tmp_dir)
target = None target = None
if len(output_files) > 1: if len(output_files) > 1:
target = _ZipMultidex(temp_dir, output_files) target = _ZipMultidex(tmp_dir, output_files)
else:
output = output_files[0]
if not zipfile.is_zipfile(output):
target = os.path.join(tmp_dir, 'dex_classes.zip')
_ZipSingleDex(output, target)
else: else:
target = output_files[0] target = output
shutil.move(os.path.join(temp_dir, target), options.dex_path) shutil.move(os.path.join(tmp_dir, target), tmp_dex_output)
# The dex file is complete and can be moved out of tmp_dir.
shutil.move(tmp_dex_output, options.dex_path)
build_utils.WriteDepfile( build_utils.WriteDepfile(
options.depfile, options.dex_path, input_paths, add_pydeps=False) options.depfile, options.dex_path, input_paths, add_pydeps=False)
......
# Generated by running: # Generated by running:
# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dex.pydeps build/android/gyp/dex.py # build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dex.pydeps build/android/gyp/dex.py
../../gn_helpers.py ../../gn_helpers.py
../convert_dex_profile.py
dex.py dex.py
util/__init__.py util/__init__.py
util/build_utils.py util/build_utils.py
......
...@@ -43,6 +43,7 @@ _java_target_blacklist = [ "*:*_unpack_aar" ] ...@@ -43,6 +43,7 @@ _java_target_blacklist = [ "*:*_unpack_aar" ]
_default_proguard_jar_path = "//third_party/proguard/lib/proguard.jar" _default_proguard_jar_path = "//third_party/proguard/lib/proguard.jar"
_r8_path = "//third_party/r8/lib/r8.jar" _r8_path = "//third_party/r8/lib/r8.jar"
_dexdump_path = "$android_sdk_build_tools/dexdump"
_dexlayout_path = "//third_party/android_build_tools/art/dexlayout" _dexlayout_path = "//third_party/android_build_tools/art/dexlayout"
_profman_path = "//third_party/android_build_tools/art/profman" _profman_path = "//third_party/android_build_tools/art/profman"
_art_lib_file_names = [ _art_lib_file_names = [
...@@ -1288,13 +1289,23 @@ if (enable_java_templates) { ...@@ -1288,13 +1289,23 @@ if (enable_java_templates) {
rebase_path(_dexlayout_path, root_build_dir), rebase_path(_dexlayout_path, root_build_dir),
"--profman-path", "--profman-path",
rebase_path(_profman_path, root_build_dir), rebase_path(_profman_path, root_build_dir),
"--dexdump-path",
rebase_path(_dexdump_path, root_build_dir),
] ]
inputs += [ inputs += [
_dexlayout_path, _dexlayout_path,
_profman_path, _profman_path,
_dexdump_path,
invoker.dexlayout_profile, invoker.dexlayout_profile,
] ]
inputs += _default_art_libs inputs += _default_art_libs
if (_proguard_enabled) {
args += [
"--proguard-mapping-path",
rebase_path(invoker.proguard_mapping_path, root_build_dir),
]
inputs += [ invoker.proguard_mapping_path ]
}
} }
if (!is_java_debug) { if (!is_java_debug) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment