dyld: 神秘的 __dso_handle

chaoguo1234發表於2024-11-19

iOS動態連結器dyld中有一個神秘的變數__dso_handle:

// dyld/dyldMain.cpp
static const MachOAnalyzer* getDyldMH()
{
#if __LP64__
    // 宣告 __dso_handle
    extern const MachOAnalyzer __dso_handle;
    return &__dso_handle;
#else
    ...
#endif // __LP64__
}

這個函式內部宣告瞭一個變數__dso_handle,其型別是struct MachOAnalyzer

檢視struct MachOAnalyzer的定義,它繼承自struct mach_header:

image

struct mach_header正是XNU核心裡面,定義的Mach-O檔案頭:

// EXTENERL_HEADERS/mach-o/loader.h
struct mach_header {
	uint32_t	magic;		/* mach magic number identifier */
	cpu_type_t	cputype;	/* cpu specifier */
	cpu_subtype_t	cpusubtype;	/* machine specifier */
	uint32_t	filetype;	/* type of file */
	uint32_t	ncmds;		/* number of load commands */
	uint32_t	sizeofcmds;	/* the size of all the load commands */
	uint32_t	flags;		/* flags */
};

從上面函式getDyldMH的名字來看,它返回dyld這個Mach-O檔案的檔案頭,而這確實也符合變數__dso_handle的型別定義。

但是奇怪的事情發生了,搜遍整個dyld原始碼庫,都無法找到變數__dso_handle的定義。所有能搜到的地方,都只是對這個變數__dso_handle的宣告。

眾所周知,動態聯結器dyld本身是靜態連結的。

也就是說,動態聯結器dyld本身是不依賴任何其他動態庫的。

因此,這個變數__dso_handle不可能定義在其他動態庫。

既然這樣,動態連結器dyld本身是如何靜態連結透過的呢?

答案只可能是靜態連結器ld在連結過程中做了手腳。

檢視靜態連結器ld的原始碼,也就是llvm的原始碼,可以找到如下程式碼:

// lld/MachO/SyntheticSections.cpp
void macho::createSyntheticSymbols() {
  // addHeaderSymbol 的 lamba 表示式
  auto addHeaderSymbol = [](const char *name) {
    symtab->addSynthetic(name, in.header->isec, /*value=*/0,
                         /*isPrivateExtern=*/true, /*includeInSymtab=*/false,
                         /*referencedDynamically=*/false);
  };

  ...

  // The Itanium C++ ABI requires dylibs to pass a pointer to __cxa_atexit
  // which does e.g. cleanup of static global variables. The ABI document
  // says that the pointer can point to any address in one of the dylib's
  // segments, but in practice ld64 seems to set it to point to the header,
  // so that's what's implemented here.
  addHeaderSymbol("___dso_handle");
}

上面程式碼定義了一個addHeaderSymbollamda表示式,然後使用它新增了一個符號,這個符號正是__dso_handle

呼叫addHeaderSymbol上方的註釋使用chatGPT翻譯過來如下:

Itanium C++ ABI 要求動態庫傳遞一個指向 __cxa_atexit 的指標,該函式負責例如靜態全域性變數的清理。ABI 文件指出,指標可以指向動態庫的某個段中的任意地址,但實際上,ld64(蘋果的連結器)似乎將其設定為指向頭部,所以這裡實現了這種做法。

註釋中提到的Itanium C++ ABI最初是為英特爾和惠普聯合開發的Itanium處理器架構設計的。

但其影響已經超過了最初設計的架構範圍,並被廣泛用於其他架構,比如x86x86-64上的多種編譯器,包括GCCClang

而且,註釋中還提到,__dso_handle在蘋果的實現裡,是指向了Mach-O的頭部。

至此,謎底解開~。

相關文章