解析 Theos Make 過程
前言
撰寫貼文 探究 Logos、Makefile 語法 時,對於 Theos Make 的過程感到十分好奇。
花了點時間研究後,撰寫本文作心得。
總結來說,高階程式語言轉換為可執行檔會經過下列步驟:
原始碼
→ 預處理器 → 編譯器 →目的碼
→ 連結器 →執行檔
。
實作過程
使用下列指令查看 Make 過程:
$ make -n
1. Make 遞迴(Making)
mkdir -p /Users/yuripeyamashita/hello/.theos
touch /Users/yuripeyamashita/hello/.theos/build_session \
abs_build_dir=.; \
if [[ "" != "" ]]; then \
printf "\e[1;31m> \e[1;3;39m%s…\e[m\n" "Making all in subprojects of tweak hello"; \
for d in ; do \
d="${d%:*}"; \
if [[ "${abs_build_dir}" = "." ]]; then \
lbuilddir="."; \
else \
lbuilddir="${abs_build_dir}/$d"; \
fi; \
if /Applications/Xcode.app/Contents/Developer/usr/bin/make \
-C $d \
-f Makefile \
--no-keep-going COLOR=1 \
--no-print-directory all \
THEOS_BUILD_DIR="$lbuilddir" \
; then\
:; \
else exit $?; \
fi; \
done; \
fi; \
printf "\e[1;31m> \e[1;3;39m%s…\e[m\n" "Making all for tweak hello"; \
/Applications/Xcode.app/Contents/Developer/usr/bin/make \
-f Makefile \
--no-keep-going COLOR=1 \
--no-print-directory \
internal-tweak-all \
_THEOS_CURRENT_TYPE="tweak" \
THEOS_CURRENT_INSTANCE="hello" \
_THEOS_CURRENT_OPERATION="all" \
THEOS_BUILD_DIR="."
> Making all for tweak hello…
mkdir -p /Users/yuripeyamashita/hello/.theos/obj/debug
mkdir -p /Users/yuripeyamashita/hello/.theos/obj/debug/;
touch /Users/yuripeyamashita/hello/.theos/obj/debug/.stamp
/Applications/Xcode.app/Contents/Developer/usr/bin/make \
-f Makefile \
--no-keep-going COLOR=1 \
--no-print-directory \
internal-library-compile \
_THEOS_CURRENT_TYPE=tweak THEOS_CURRENT_INSTANCE=hello \
_THEOS_CURRENT_OPERATION=compile \
THEOS_BUILD_DIR="." _THEOS_MAKE_PARALLEL=yes
mkdir -p /Users/yuripeyamashita/hello/.theos/obj/debug/arm64
/Applications/Xcode.app/Contents/Developer/usr/bin/make \
-f Makefile \
--no-print-directory \
--no-keep-going \
internal-tweak-compile\
_THEOS_CURRENT_TYPE="tweak" \
THEOS_CURRENT_INSTANCE="hello" \
_THEOS_CURRENT_OPERATION="compile" \
THEOS_BUILD_DIR="." \
THEOS_CURRENT_ARCH="arm64"
如上,通過遞迴呼叫,最終指定編譯目標為:internal-tweak-compile。
同時,幾個攜帶幾個重要變數的賦值:
_THEOS_CURRENT_TYPE = "tweak"
THEOS_CURRENT_INSTANCE = "hello"
_THEOS_CURRENT_OPERATION = "compile"
THEOS_CURRENT_ARCH = "arm64"
2. 預處理(Preprocessing)
Theos 使用自帶的 logos.pl 工具,將包含 Logos 語法的原始碼處理成 Objective-c 檔案。
如下,Tweak.x
被處理成了Tweak.x.m
:
(mkdir -p /Users/yuripeyamashita/hello/.theos/obj/debug/arm64/)
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 1 "Preprocessing Tweak.x");
set -o pipefail;
(/Users/yuripeyamashita/theos/bin/logos.pl \
-c warnings=error -c generator=MobileSubstrate \
Tweak.x > /Users/yuripeyamashita/hello/.theos/obj/debug/arm64/Tweak.x.m)
3. 編譯(Compiling)
Theos 使用 Xcode 中的 Clang 編譯器進行編譯。
專案名稱_CFLAGS = -fobjc-arc # 開啟 ARC
專案名稱_CFLAGS = -F./BookFramework # .framework 標頭檔尋找目錄
上述 Makefile 範例中的CFLAGS
變數於此步驟作為參數傳入編譯器。
編譯器將確保原始碼的語法正確性、函式與變數是否宣告。
對於後者,通常需要告知編譯器標頭檔的所在位置,只要語法正確,編譯器就可以編譯出目的碼。
一般來說,每個原始碼都對應於一個目的碼(Mach-O 檔案)。
如下,Tweak.x.m
被處理成了Tweak.x.59bb3dfc.o
:
(mkdir -p /Users/yuripeyamashita/hello/.theos/obj/debug/arm64/)
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 2 "Compiling Tweak.x (arm64)");
set -o pipefail;
(/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ \
-x objective-c -c \
-I"/Users/yuripeyamashita/hello" \
-I/Users/yuripeyamashita/theos/include -I/Users/yuripeyamashita/theos/vendor/include \
-I/Users/yuripeyamashita/theos/include/_fallback \
-include /Users/yuripeyamashita/theos/Prefix.pch \
-MT /Users/yuripeyamashita/hello/.theos/obj/debug/arm64/Tweak.x.59bb3dfc.o \
-MMD -MP -MF "/Users/yuripeyamashita/hello/.theos/obj/debug/arm64/Tweak.x.59bb3dfc.Td" \
-fcolor-diagnostics -DTARGET_IPHONE=1 -O0 -Wall -ggdb -Werror \
-isysroot "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk" \
-miphoneos-version-min=6.0 -fmodules -fcxx-modules \
-fmodule-name=hello -fbuild-session-file=/Users/yuripeyamashita/hello/.theos/build_session \
-fmodules-prune-after=345600 -fmodules-prune-interval=86400 -fmodules-validate-once-per-build-session \
-fobjc-arc -DDEBUG -O0 -DTHEOS_INSTANCE_NAME="\"hello\"" -fmodules -fcxx-modules -fmodule-name=hello \
-fbuild-session-file=/Users/yuripeyamashita/hello/.theos/build_session -fmodules-prune-after=345600 \
-fmodules-prune-interval=86400 -fmodules-validate-once-per-build-session -arch arm64 \
-std=c99 /Users/yuripeyamashita/hello/.theos/obj/debug/arm64/Tweak.x.m \
-o /Users/yuripeyamashita/hello/.theos/obj/debug/arm64/Tweak.x.59bb3dfc.o)
4. 連結(Linking)
Theos 使用 Xcode 中的 Clang 連結器進行連結。
專案名稱_LDFLAGS = -L./BookLib # .a 二進位檔案連結目錄
專案名稱_LIBRARIES = BookLib # .a
專案名稱_LDFLAGS = -F./BookFramework # .framework 二進位檔案連結目錄
專案名稱_FRAMEWORKS = BookFramework # .framework
專案名稱_LDFLAGS = -lsqlite3.0 # 連結 Mach-O 二進位檔案
上述 Makefile 範例中的LDFLAGS
、LIBRARIES
、FRAMEWORKS
變數於此步驟作為參數傳入連結器。
連結器會於所有的目的碼中尋找函式的實現,將目的碼加上函式庫連結為一個可執行檔。
如下,Tweak.x.59bb3dfc.o
被處理成了hello.dylib
:
(mkdir -p /Users/yuripeyamashita/hello/.theos/obj/debug/arm64)
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 3 "Linking tweak hello (arm64)");
set -o pipefail;
(/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ \
-fcolor-diagnostics \
-L/Users/yuripeyamashita/theos/lib -ggdb \
-L/Users/yuripeyamashita/theos/vendor/lib -lobjc \
-framework Foundation \
-framework CoreFoundation \
-F/Users/yuripeyamashita/theos/vendor/lib \
-framework CydiaSubstrate \
-dynamiclib \
-install_name "/Library/MobileSubstrate/DynamicLibraries/hello.dylib" \
-isysroot "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk" \
-miphoneos-version-min=6.0 -multiply_defined suppress -stdlib=libc++ -lc++ -arch arm64 -O0 \
-o "/Users/yuripeyamashita/hello/.theos/obj/debug/arm64/hello.dylib" \
/Users/yuripeyamashita/hello/.theos/obj/debug/arm64/Tweak.x.59bb3dfc.o)
5. 符號處理
Theos 使用 Xcode 中的 dsymutil 工具建立 dSYM 檔案:
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 4 "Generating debug symbols for hello");
set -o pipefail;
(/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil \
"/Users/yuripeyamashita/hello/.theos/obj/debug/arm64/hello.dylib")
dSYM 是個保存了函式與記憶體位址映射資訊的檔案。
程式在實機上崩潰時,崩潰日誌上可能只有顯示十六進位的記憶體位址。
若要進行除錯,須將記憶體位址透過 dSYM 檔案,還原成原始碼中的函式以及行號。
若DEBUG = 0
,Theos 將再使用 Xcode 中的 strip 工具進行符號剝離:
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 4 "Stripping hello (arm64)");
set -o pipefail;
(/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip \
-x "/Users/yuripeyamashita/hello/.theos/obj/arm64/hello.dylib")
參數-x
表示僅剝離非全域之符號(Non-Global Symbols)。
剝離後將無法於呼叫堆疊中看見非全域的類別、方法名稱。
6. 合併(Merging)
Theos 使用 Xcode 中的 lipo 工具將不同處理器架構的可執行檔進行合併:
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 4 "Merging tweak hello");
set -o pipefail;
(/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo \
-arch arm64 \
/Users/yuripeyamashita/hello/.theos/obj/debug/arm64/hello.dylib \
-create \
-output "/Users/yuripeyamashita/hello/.theos/obj/debug/hello.dylib.47ba6b93.unsigned")
7. 簽名(Signing)
Theos 使用 ldid 工具進行簽名:
(printf "\e[0;3%im==> \e[1;39m%s…\e[m\n" 4 "Signing hello");
set -o pipefail;
(CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate \
ldid -S "/Users/yuripeyamashita/hello/.theos/obj/debug/hello.dylib.47ba6b93.unsigned" \
&& mv "/Users/yuripeyamashita/hello/.theos/obj/debug/hello.dylib.47ba6b93.unsigned" \
"/Users/yuripeyamashita/hello/.theos/obj/debug/hello.dylib")
rm /Users/yuripeyamashita/hello/.theos/obj/debug/hello.dylib.47ba6b93.unsigned