diff --git a/tensorflow/lite/experimental/ios/BUILD.apple b/tensorflow/lite/experimental/ios/BUILD.apple index aa41b9e2d62..1a85b604f9b 100644 --- a/tensorflow/lite/experimental/ios/BUILD.apple +++ b/tensorflow/lite/experimental/ios/BUILD.apple @@ -1,7 +1,7 @@ # TensorFlow Lite for iOS load("@bazel_skylib//rules:build_test.bzl", "build_test") -load("//tensorflow/lite/experimental/ios:ios.bzl", "TFL_MINIMUM_OS_VERSION") +load("//tensorflow/lite/experimental/ios:ios.bzl", "TFL_MINIMUM_OS_VERSION", "tflite_ios_static_framework") load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework") package( @@ -11,8 +11,15 @@ package( licenses = ["notice"], # Apache 2.0 ) +sh_binary( + name = "hide_symbols_with_whitelist", + srcs = [ + "hide_symbols_with_whitelist.sh", + ], +) + # bazel build -c opt --config=ios_fat //tensorflow/lite/experimental/ios:TensorFlowLiteC_framework -ios_static_framework( +tflite_ios_static_framework( name = "TensorFlowLiteC_framework", hdrs = [ "//tensorflow/lite/c:c_api.h", @@ -20,6 +27,7 @@ ios_static_framework( ], bundle_name = "TensorFlowLiteC", minimum_os_version = TFL_MINIMUM_OS_VERSION, + whitelist_symbols_file = ":whitelist_TensorFlowLiteC.txt", deps = [ ":tensorflow_lite_c", ], @@ -60,16 +68,14 @@ genrule( # TensorFlowLiteC framework above in a composable way. # # bazel build -c opt --config=ios_fat //tensorflow/lite/experimental/ios:TensorFlowLiteCCoreMl_framework -ios_static_framework( +tflite_ios_static_framework( name = "TensorFlowLiteCCoreML_framework", hdrs = [ ":coreml_delegate.h", ], - avoid_deps = [ - ":tensorflow_lite_c", - ], bundle_name = "TensorFlowLiteCCoreML", minimum_os_version = TFL_MINIMUM_OS_VERSION, + whitelist_symbols_file = ":whitelist_TensorFlowLiteCCoreML.txt", deps = [ "//tensorflow/lite/experimental/delegates/coreml:coreml_delegate", ], @@ -81,16 +87,14 @@ ios_static_framework( # TensorFlowLiteC framework above in a composable way. # # bazel build -c opt --config=ios_fat //tensorflow/lite/experimental/ios:TensorFlowLiteCMetal_framework -ios_static_framework( +tflite_ios_static_framework( name = "TensorFlowLiteCMetal_framework", hdrs = [ "//tensorflow/lite/delegates/gpu:metal_delegate.h", ], - avoid_deps = [ - ":tensorflow_lite_c", - ], bundle_name = "TensorFlowLiteCMetal", minimum_os_version = TFL_MINIMUM_OS_VERSION, + whitelist_symbols_file = ":whitelist_TensorFlowLiteCMetal.txt", deps = [ "//tensorflow/lite/delegates/gpu:metal_delegate", ], diff --git a/tensorflow/lite/experimental/ios/hide_symbols_with_whitelist.sh b/tensorflow/lite/experimental/ios/hide_symbols_with_whitelist.sh new file mode 100755 index 00000000000..2fa0fc53c33 --- /dev/null +++ b/tensorflow/lite/experimental/ios/hide_symbols_with_whitelist.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +# +# A script to merge Mach-O object files into a single object file and hide +# their internal symbols. Only whitelisted symbols will be visible in the +# symbol table after this script. + +# To run this script, you must set several variables: +# INPUT_FRAMEWORK: a zip file containing the iOS static framework. +# BUNDLE_NAME: the pod/bundle name of the iOS static framework. +# WHITELIST_FILE_PATH: contains the whitelisted symbols. +# OUTPUT: the output zip file. + +# Halt on any error or any unknown variable. +set -ue + +LD_DEBUGGABLE_FLAGS="-x" +# Uncomment the below to get debuggable output. This can only be done for one +# library at a time. +# LD_DEBUGGABLE_FLAGS="-d" + +# Exits if C++ symbols are found in the whitelist list. +if grep -q "^__Z" "${WHITELIST_FILE_PATH}" +then + echo "ERROR: Failed in symbol hiding. This rule does not permit hiding of" \ + "C++ symbols due to possible serious problems mixing symbol hiding," \ + "shared libraries and the C++ runtime." \ + "More info can be found in go/ios-symbols-hiding." \ + "Please recheck the whitelist list and remove C++ symbols:" + echo "$(grep "^__Z" "${WHITELIST_FILE_PATH}")" + exit 1 # terminate and indicate error +fi +# Unzips the framework zip file into a temp workspace. +framework=$(mktemp -t framework -d) +unzip "${INPUT_FRAMEWORK}" -d "${framework}"/ + +# Executable file in the framework. +executable_file="${BUNDLE_NAME}.framework/${BUNDLE_NAME}" + +# Extracts architectures from the framework binary. +archs_str=$(xcrun lipo -info "${framework}/${executable_file}" | +sed -En -e 's/^(Non-|Architectures in the )fat file: .+( is architecture| are): (.*)$/\3/p') + +IFS=' ' read -r -a archs <<< "${archs_str}" + +merge_cmd=(xcrun lipo) + +# Merges object files and hide symbols for each architecture. +for arch in "${archs[@]}" +do + archdir=$(mktemp -t "${arch}" -d) + arch_file="${archdir}/${arch}" + + # Handles the binary differently if they are fat or thin. + if [[ "${#archs[@]}" -gt 1 ]]; then + xcrun lipo "${framework}/${executable_file}" -thin "${arch}" -output "${arch_file}" + else + mv "${framework}/${executable_file}" "${arch_file}" + fi + if [[ "$arch" == "armv7" ]]; then + # Check that there are no thread local variables in the input, as they get broken. + # See b/124533863. + thread_locals=$(xcrun nm -m -g "${arch_file}" | awk '/__DATA,__thread_vars/ { print $5 }' | c++filt) + if [[ -n "${thread_locals}" ]]; then + echo + echo "WARNING: This symbol hiding script breaks thread local variables on 32-bit arm, you had:" + echo "${thread_locals}" + echo + echo "Your build will crash if these variables are actually used at runtime." + echo + fi + fi + xcrun ar -x "${arch_file}" + mv *.o "${archdir}"/ + + objects_file_list=$(mktemp) + # Hides the symbols except the whitelisted ones. + find "${archdir}" -name "*.o" >> "${objects_file_list}" + + # Checks whether bitcode is enabled in the framework. + all_objects_have_bitcode=true + for object_file in $(cat "$objects_file_list"); do + if otool -arch "${arch}" -l "${object_file}" | grep -q __LLVM; then + : # Do nothing + else + echo "The ${arch} in ${object_file} is NOT bitcode-enabled." + all_objects_have_bitcode=false + break + fi + done + if [[ "$all_objects_have_bitcode" = "true" ]]; then + echo "The ${arch} in ${executable_file} is fully bitcode-enabled." + xcrun ld -r -bitcode_bundle -exported_symbols_list \ + "${WHITELIST_FILE_PATH}" \ + $LD_DEBUGGABLE_FLAGS \ + -filelist "${objects_file_list}" -o "${arch_file}_processed.o" + else + echo "The ${arch} in ${executable_file} is NOT fully bitcode-enabled." + xcrun ld -r -exported_symbols_list \ + "${WHITELIST_FILE_PATH}" \ + $LD_DEBUGGABLE_FLAGS \ + -filelist "${objects_file_list}" -o "${arch_file}_processed.o" + fi + + output_object="${framework}/${arch}" + + mv "${arch_file}_processed.o" "${output_object}" + rm -rf "${archdir}" + rm "${objects_file_list}" + merge_cmd+=(-arch "${arch}" "${output_object}") +done + +# Repackages the processed object files. +unzip "${INPUT_FRAMEWORK}" +merge_cmd+=(-create -output "${BUNDLE_NAME}") +"${merge_cmd[@]}" + +chmod +x "${BUNDLE_NAME}" +rm "${executable_file}" +mv "${BUNDLE_NAME}" "${executable_file}" +( TZ=UTC find "${BUNDLE_NAME}.framework/" -exec touch -h -t 198001010000 {} \+ ) +zip --compression-method store --symlinks --recurse-paths --quiet "${OUTPUT}" "${BUNDLE_NAME}.framework/" diff --git a/tensorflow/lite/experimental/ios/ios.bzl b/tensorflow/lite/experimental/ios/ios.bzl index 976c6b09a97..3181b587e72 100644 --- a/tensorflow/lite/experimental/ios/ios.bzl +++ b/tensorflow/lite/experimental/ios/ios.bzl @@ -1,5 +1,8 @@ """TensorFlow Lite Build Configurations for iOS""" +# Placeholder for Google-internal load statements. +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework") + TFL_MINIMUM_OS_VERSION = "9.0" # Default tags for filtering iOS targets. Targets are restricted to Apple platforms. @@ -13,3 +16,58 @@ TFL_DISABLED_SANITIZER_TAGS = [ "nomsan", "notsan", ] + +# iOS static framework with symbol whitelist. Exported C++ symbbols might cause +# symbol collision with other libraries. List of symbols to whitelist can be +# generated by running `nm -m -g FRAMEWORK_LIBRARY | grep _TfLite` for framework +# built with `ios_static_framework` rule. +def tflite_ios_static_framework( + name, + bundle_name, + whitelist_symbols_file, + exclude_resources = True, + **kwargs): + """TFLite variant of ios_static_framework with symbol hiding. + + Args: + name: The name of the target. + bundle_name: The name to give to the framework bundle, without the + ".framework" extension. If omitted, the target's name will be used. + whitelist_symbols_file: a file including a list of whitelisted symbols, + one symbol per line. + exclude_resources: Indicates whether resources should be excluded from the + bundle. This can be used to avoid unnecessarily bundling resources if + the static framework is being distributed in a different fashion, such + as a Cocoapod. + **kwargs: Pass-through arguments. + """ + + preprocessed_name = "Preprocessed_" + name + ios_static_framework( + name = preprocessed_name, + bundle_name = bundle_name, + exclude_resources = exclude_resources, + **kwargs + ) + + framework_target = ":{}.zip".format(preprocessed_name) + + srcs = [ + framework_target, + whitelist_symbols_file, + ] + cmd = ("INPUT_FRAMEWORK=\"$(location " + framework_target + ")\" " + + "BUNDLE_NAME=\"" + bundle_name + "\" " + + "WHITELIST_FILE_PATH=\"$(location " + whitelist_symbols_file + ")\" " + + "OUTPUT=\"$(OUTS)\" " + + "\"$(location //tensorflow/lite/experimental/ios:hide_symbols_with_whitelist)\"") + + native.genrule( + name = name, + srcs = srcs, + outs = [name + ".zip"], + cmd = cmd, + tools = [ + "//tensorflow/lite/experimental/ios:hide_symbols_with_whitelist", + ], + ) diff --git a/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteC.txt b/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteC.txt new file mode 100644 index 00000000000..e8ae288ea8f --- /dev/null +++ b/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteC.txt @@ -0,0 +1 @@ +_TfLite* diff --git a/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteCCoreML.txt b/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteCCoreML.txt new file mode 100644 index 00000000000..817b4a7f2ec --- /dev/null +++ b/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteCCoreML.txt @@ -0,0 +1,2 @@ +_TfLiteCoreMlDelegateCreate +_TfLiteCoreMlDelegateDelete diff --git a/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteCMetal.txt b/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteCMetal.txt new file mode 100644 index 00000000000..b66b059eef0 --- /dev/null +++ b/tensorflow/lite/experimental/ios/whitelist_TensorFlowLiteCMetal.txt @@ -0,0 +1,2 @@ +_TFLGpuDelegateCreate +_TFLGpuDelegateDelete