前言

撰寫貼文 探究 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 範例中的LDFLAGSLIBRARIESFRAMEWORKS變數於此步驟作為參數傳入連結器。

連結器會於所有的目的碼中尋找函式的實現,將目的碼加上函式庫連結為一個可執行檔。

如下,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