[go: nahoru, domu]

Add a script to extract and add unwind table to apk

The unwind tables are stripped from release builds. This CL adds a
script to extract the unwind table sections from unstripped binary and
adds it as an asset file in the apk.
This script is first used in the test apk, when add_unwind_tables_in_apk
is specified. Will be used later added to chrome apk.

BUG=819888

Change-Id: I68d2ea843ed0c7a7a3ee60c48f683e01809ff217
Reviewed-on: https://chromium-review.googlesource.com/954421
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Reviewed-by: agrieve <agrieve@chromium.org>
Commit-Queue: Siddhartha S <ssid@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542699}
diff --git a/build/android/gyp/extract_unwind_tables.py b/build/android/gyp/extract_unwind_tables.py
new file mode 100755
index 0000000..21025a05
--- /dev/null
+++ b/build/android/gyp/extract_unwind_tables.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+# Copyright 2018 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.
+
+"""Extracts the unwind tables in ARM EHABI format.
+
+The ARM EHABI format requires 2 sections for unwinding, ARM.exidx and ARM.extab.
+This script copies these sections from unstripped binary into an output file.
+
+Usage:
+  extract_unwind_tables.py --input_path [root path to unstripped chrome.so]
+      --output_path [output path]
+
+"""
+
+import argparse
+import struct
+import subprocess
+import sys
+
+
+def _GetArmSectionsFromObjdump(input_path):
+  """Parses the objdump of the binary and returns the relevant sections."""
+  objdump = subprocess.check_output(['objdump', '-h', input_path]).splitlines()
+  sections = {}
+  for line in objdump:
+    if '.ARM' not in line:
+      continue
+    parts = line.split()
+    section = {}
+    section['size'] = int(parts[2], 16)
+    section['offset'] = int(parts[5], 16)
+    sections[parts[1]] = section
+  return sections
+
+
+def _Write4Bytes(output_file, val):
+  """Writes a 32 bit unsigned integer to the given output file."""
+  output_file.write(struct.pack('<L', val));
+
+
+def _AddSectionToOutput(input_path, output_file, section):
+  """Copies the specified section from input file to output."""
+  _Write4Bytes(output_file, section['size'])
+  _Write4Bytes(output_file, section['offset'])
+
+  with open(input_path, 'rb') as f:
+    f.seek(section['offset'])
+    data = f.read(section['size'])
+    output_file.write(data)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--input_path', required=True,
+      help='The input path of the unstripped binary')
+  parser.add_argument(
+      '--output_path', required=True,
+      help='The path of the output file')
+  args = parser.parse_args()
+
+  sections = _GetArmSectionsFromObjdump(args.input_path)
+  exidx = sections.get('.ARM.exidx')
+  extab = sections.get('.ARM.extab')
+  if not exidx or not extab:
+    raise Exception('No arm32 exception section found.')
+  with open(args.output_path, 'wb') as outputFile:
+    _AddSectionToOutput(args.input_path, outputFile, exidx)
+    _AddSectionToOutput(args.input_path, outputFile, extab)
+
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/build/config/android/extract_unwind_tables.gni b/build/config/android/extract_unwind_tables.gni
new file mode 100644
index 0000000..5377c1d
--- /dev/null
+++ b/build/config/android/extract_unwind_tables.gni
@@ -0,0 +1,40 @@
+# Copyright 2018 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.
+
+import("//build/config/android/rules.gni")
+
+template("unwind_table_asset") {
+  _unwind_action = "${target_name}__extract"
+  _asset_path = "${target_gen_dir}/${target_name}/unwind_cfi"
+  action(_unwind_action) {
+    if (defined(invoker.testonly)) {
+      testonly = invoker.testonly
+    }
+    script = "//build/android/gyp/extract_unwind_tables.py"
+    outputs = [
+      _asset_path,
+    ]
+    args = [
+      "--input_path",
+      rebase_path(
+          "$root_out_dir/lib.unstripped/$shlib_prefix${invoker.library_target}$shlib_extension",
+          root_build_dir),
+      "--output_path",
+      rebase_path(_asset_path, root_build_dir),
+    ]
+    deps = [
+      ":${invoker.library_target}",
+    ]
+  }
+  android_assets(target_name) {
+    testonly = invoker.testonly
+    sources = [
+      _asset_path,
+    ]
+    disable_compression = true
+    deps = [
+      ":$_unwind_action",
+    ]
+  }
+}
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index c06c164..c9b987b 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2830,7 +2830,6 @@
 
     android_apk(target_name) {
       data_deps = []
-      deps = []
       forward_variables_from(invoker, "*")
       testonly = true
       create_apk_script = false
diff --git a/testing/test.gni b/testing/test.gni
index f9f8c91..251b003fa 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -10,6 +10,7 @@
   import("//build/config/android/config.gni")
   import("//build/config/android/rules.gni")
   import("//build/config/sanitizers/sanitizers.gni")
+  import("//build/config/android/extract_unwind_tables.gni")
 }
 
 if (is_fuchsia) {
@@ -100,6 +101,18 @@
         "write_asset_list",
         "use_native_activity",
       ]
+
+      # Adds the unwind tables from unstripped binary as an asset file in the
+      # apk, if |add_unwind_tables_in_apk| is specified by the test.
+      if (defined(invoker.add_unwind_tables_in_apk) &&
+          invoker.add_unwind_tables_in_apk) {
+        _unwind_table_asset_name = "${target_name}_unwind_assets"
+        unwind_table_asset(_unwind_table_asset_name) {
+          testonly = true
+          library_target = _library_target
+        }
+      }
+
       shared_library(_library_target) {
         # Configs will always be defined since we set_defaults in BUILDCONFIG.gn.
         configs = []  # Prevent list overwriting warning.
@@ -129,6 +142,10 @@
         # Add the Java classes so that each target does not have to do it.
         deps += [ "//base/test:test_support_java" ]
 
+        if (defined(_unwind_table_asset_name)) {
+          deps += [ ":${_unwind_table_asset_name}" ]
+        }
+
         # TODO(agrieve): Remove this data_dep once bots don't build the _apk
         #     target (post-GYP).
         # It's a bit backwards for the apk to depend on the runner script, since