Add tool to namespace Objective-C classes at link time
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 <martin.smith@theqtcompany.com> Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com> Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@theqtcompany.com>
This commit is contained in:
parent
50489f2e41
commit
f24536f8a5
216
mkspecs/features/data/mac/objc_namespace.sh
Executable file
216
mkspecs/features/data/mac/objc_namespace.sh
Executable file
@ -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
|
||||
|
50
mkspecs/features/mac/unsupported/objc_namespace.prf
Normal file
50
mkspecs/features/mac/unsupported/objc_namespace.prf
Normal file
@ -0,0 +1,50 @@
|
||||
#
|
||||
# W A R N I N G
|
||||
# -------------
|
||||
#
|
||||
# This file is not part of the Qt API. It exists purely as an
|
||||
# implementation detail. It may change from version to version
|
||||
# without notice, or even be removed.
|
||||
#
|
||||
# We mean it.
|
||||
#
|
||||
|
||||
# The Objective-C runtime will complain when loading a binary that
|
||||
# introduces as class name that already exists in the global namespace.
|
||||
# This may happen when linking Qt statically into a plugin, and then
|
||||
# loading more than two plugins into the same host, both using Qt.
|
||||
#
|
||||
# We work around this by doing a bit of post-processing on the final
|
||||
# binary, adding new suffixed class name entries to the __objc_classname
|
||||
# section of the __TEXT segment, and then patching the class_ro_t
|
||||
# entries to point to the newly added class names.
|
||||
#
|
||||
# By linking the binary between these two steps we avoid having to
|
||||
# manually remap all the offsets in the Mach-O binary due to the
|
||||
# added class names, instead relying on the linker to do this
|
||||
# for us by linking in an assembly file with the added names.
|
||||
|
||||
objc_namespace_script = $$clean_path($$PWD/../../data/mac/objc_namespace.sh)
|
||||
|
||||
isEmpty(QMAKE_OBJC_NAMESPACE_SUFFIX) {
|
||||
QMAKE_OBJC_NAMESPACE_SUFFIX = $$TARGET
|
||||
!isEmpty(QMAKE_TARGET_BUNDLE_PREFIX): \
|
||||
QMAKE_OBJC_NAMESPACE_SUFFIX = $${QMAKE_TARGET_BUNDLE_PREFIX}.$${QMAKE_OBJC_NAMESPACE_SUFFIX}
|
||||
}
|
||||
|
||||
QMAKE_LFLAGS += \
|
||||
-Wobjc_namespace,--target=$$shell_quote($$TARGET) \
|
||||
-Wobjc_namespace,--suffix=$$shell_quote($$QMAKE_OBJC_NAMESPACE_SUFFIX) \
|
||||
-Wobjc_namespace,--original_ld=$$shell_quote($$QMAKE_LINK)
|
||||
|
||||
!isEmpty(QMAKE_OBJC_NAMESPACE_EXCLUDE): \
|
||||
QMAKE_LFLAGS += -Wobjc_namespace,--exclude_list=$$shell_quote($$QMAKE_OBJC_NAMESPACE_EXCLUDE)
|
||||
!isEmpty(QMAKE_OBJC_NAMESPACE_EXCLUDE_REGEX) {
|
||||
equals(MAKEFILE_GENERATOR, UNIX): \
|
||||
QMAKE_OBJC_NAMESPACE_EXCLUDE_REGEX ~= s/\\$/\$\$/
|
||||
QMAKE_LFLAGS += -Wobjc_namespace,--exclude_regex=$$shell_quote($$QMAKE_OBJC_NAMESPACE_EXCLUDE_REGEX)
|
||||
}
|
||||
|
||||
slient: QMAKE_LFLAGS += -Wobjc_namespace,--silent=1
|
||||
|
||||
QMAKE_LINK = $$objc_namespace_script
|
Loading…
Reference in New Issue
Block a user