From f24536f8a533a3001c3a142f9d5c4bc4036bc5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Sun, 27 Sep 2015 23:30:41 +0200 Subject: Add tool to namespace Objective-C classes at link time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The feature is enabled by CONFIG += unsupported/objc_namespace, but can be easily integrated into other build systems such as CMake or native Xcode by modifying the LD and LDFLAGS equivalent for each build system. This is a less resource-intensive alternative to using multiple Qt builds with different -qtnamespace settings. Note: The feature is not supported in any way, and should be used with care. Change-Id: Ibb8ba1159db36efd7106c117cc2210c7e2e24784 Reviewed-by: Martin Smith Reviewed-by: Morten Johan Sørvig Reviewed-by: Tor Arne Vestbø --- mkspecs/features/data/mac/objc_namespace.sh | 216 ++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100755 mkspecs/features/data/mac/objc_namespace.sh (limited to 'mkspecs/features/data') diff --git a/mkspecs/features/data/mac/objc_namespace.sh b/mkspecs/features/data/mac/objc_namespace.sh new file mode 100755 index 0000000000..0c7faf18bf --- /dev/null +++ b/mkspecs/features/data/mac/objc_namespace.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +############################################################################# +## +## Copyright (C) 2015 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is the build configuration utility of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL21$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see http://www.qt.io/terms-conditions. For further +## information use the contact form at http://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 2.1 or version 3 as published by the Free +## Software Foundation and appearing in the file LICENSE.LGPLv21 and +## LICENSE.LGPLv3 included in the packaging of this file. Please review the +## following information to ensure the GNU Lesser General Public License +## requirements will be met: https://www.gnu.org/licenses/lgpl.html and +## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +## +## As a special exception, The Qt Company gives you certain additional +## rights. These rights are described in The Qt Company LGPL Exception +## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +## +## $QT_END_LICENSE$ +## +############################################################################# + +script_argument_prefix="-Wobjc_namespace,--" + +required_arguments="target suffix original_ld" +optional_arguments="exclude_list exclude_regex slient" + +for argument in $required_arguments $optional_arguments; do + declare "$argument=" +done + +declare -i silent=0 +declare -a linker_arguments + +for i in "$@"; do + case $1 in + $script_argument_prefix*) + declare "${1#$script_argument_prefix}" + ;; + -o) + if [ -n "$2" ]; then + target="$2" + fi + linker_arguments+=("$1") + ;; + *) + linker_arguments+=("$1") + ;; + esac + shift +done + +get_entry() { + local map=$1 key=$2 + local i="${map}_map_${2}" + printf '%s' "${!i}" +} + +error() { + echo "$0: error: $*" >&2 + exit 1 +} + +for argument in $required_arguments; do + if [ -z "${!argument}" ]; then + error "missing argument --${argument}" + fi +done + +# Normalize suffix so we can use it as a bash variable +suffix=${suffix//[-. ]/_} + +link_binary() { + (PS4=; test $silent -ne 1 && set -x; $original_ld "${linker_arguments[@]}" "$@") 2>&1 || exit 1 +} + +sanitize_address() { + local address="$1" + address=${address#0x} # Remove hex prefix + address=${address: ${#address} < 8 ? 0 : -8} # Limit to 32-bit + echo "0x$address" +} + +read_binary() { + local address=$1 + local length=$2 + + dd if="$target" bs=1 iseek=$address count=$length 2>|/dev/null +} + +read_32bit_value() { + local address=$1 + read_binary $address 4 | xxd -p | dd conv=swab 2>/dev/null | rev +} + +inspect_binary() { + inspect_mode="$1" + + echo -n "🔎 Inspecting binary '$target', " + if [ ! -f "$target" ]; then + echo "target does not exist!" + exit 1 + fi + + read -a mach_header <<< "$(otool -h "$target" -v | tail -n 1)" + if [ "${mach_header[1]}" != "X86_64" ]; then + echo "binary is not 64-bit, only 64-bit binaries are supported!" + exit 1 + fi + + classnames_section="__objc_classname" + classnames=$(otool -v -s __TEXT $classnames_section "$target" | tail -n +3) + while read -a classname; do + address=$(sanitize_address ${classname[0]}) + name=${classname[1]} + + declare "address_to_classname_map_$address=$name" + declare "classname_to_address_map_$name=$address" + done <<< "$classnames" + + extra_classnames_file="$(mktemp -t ${classnames_section}_additions).S" + + if [ "$inspect_mode" == "inject_classnames" ]; then + echo "class names have not been namespaced, adding suffix '$suffix'..." + printf ".section __TEXT,$classnames_section,cstring_literals,no_dead_strip\n" > $extra_classnames_file + elif [ "$inspect_mode" == "patch_classes" ]; then + echo "found namespaced class names, updating class entries..." + fi + + classes=$(otool -o "$target" | grep class_ro_t) + while read -a class; do + address="$(sanitize_address ${class[1]})" + + class_flags="0x$(read_32bit_value $address)" + if [ -z "$class_flags" ]; then + echo " 💥 failed to read class flags for class at $address" + continue + fi + + is_metaclass=$(($class_flags & 0x1)) + + name_offset=$(($address + 24)) + classname_address="0x$(read_32bit_value $name_offset)" + if [ -z "$classname_address" ]; then + echo " 💥 failed to read class name address for class at $address" + continue + fi + + classname=$(get_entry address_to_classname $classname_address) + if [ -z "$classname" ]; then + echo " 💥 failed to resolve class name for address '$classname_address'" + continue + fi + + if [[ $exclude_list =~ $classname || $classname =~ $exclude_regex ]]; then + if [ $is_metaclass -eq 1 ]; then + class_type="meta class" + else + class_type="class" + fi + echo " 🚽 skipping excluded $class_type '$classname'" + continue + fi + + newclassname="${classname}_${suffix}" + + if [ "$inspect_mode" == "inject_classnames" ]; then + if [ $is_metaclass -eq 1 ]; then + continue + fi + + echo " 💉 injecting $classnames_section entry '$newclassname' for '$classname'" + printf ".asciz \"$newclassname\"\n" >> $extra_classnames_file + + elif [ "$inspect_mode" == "patch_classes" ]; then + newclassname_address=$(get_entry classname_to_address ${newclassname}) + if [ -z "$newclassname_address" ]; then + echo " 💥 failed to resolve class name address for class '$newclassname'" + continue + fi + + if [ $is_metaclass -eq 1 ]; then + class_type="meta" + else + class_type="class" + fi + + echo " 🔨 patching class_ro_t at $address ($class_type) from $classname_address ($classname) to $newclassname_address ($newclassname)" + echo ${newclassname_address: -8} | rev | dd conv=swab 2>/dev/null | xxd -p -r -seek $name_offset -l 4 - "$target" + fi + done <<< "$classes" +} + +echo "🔩 Linking binary using '$original_ld'..." +link_binary + +inspect_binary inject_classnames + +echo "🔩 Re-linking binary with extra __objc_classname section..." +link_binary $extra_classnames_file + +inspect_binary patch_classes + -- cgit v1.2.3