KM的博客.

Dyld源码阅读

字数统计: 2.3k阅读时长: 12 min
2020/01/15

Dyld源码阅读

  • Version:dyld-551.4
  • Lauange:C++
  • load()调用路径:3566行
    • load()->loadPhase0()->loadPhase1()->loadPhase2()->loadPhase4()->loadPhase5()打开或检查已经存在的动态库:dyld3::findInSharedCacheImage->loadPhase5load()->loadPhase5open()->loadPhase6()->加载3种Mach-O文件

1、动态库路径

iOS越狱手机

  • 在Mac\iOS中,是使用了/usr/lib/dyld程序来加载动态库
  • UIKit路径:/system/Library/Frameworks/UIKit.framework
  • 动态库共享缓存/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64

0x00 load()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
{
// try all path permutations and check against existing loaded images
ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL);

if ( image != NULL ) {
CRSetCrashLogMessage2(NULL);
return image;
}

// try all path permutations and try open() until first success
image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions);

if ( image == NULL)
image = loadPhase2cache(path, orgPath, context, cacheIndex, &exceptions);
#endif
CRSetCrashLogMessage2(NULL);

}

0x01 loadPhase2()

0x02 loadPhase5

loadPhase5load()

loadPhase5check()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// open or check existing
static ImageLoader* loadPhase5(const char* path, const char* orgPath, const LoadContext& context, unsigned& cacheIndex, std::vector<const char*>* exceptions)
{
// check for specific dylib overrides
for (std::vector<DylibOverride>::iterator it = sDylibOverrides.begin(); it != sDylibOverrides.end(); ++it) {
if ( strcmp(it->installName, path) == 0 ) {
path = it->override;
break;
}
}

if ( exceptions != NULL )
return loadPhase5load(path, orgPath, context, cacheIndex, exceptions);
else
return loadPhase5check(path, orgPath, context);
}

0x03 findInSharedCacheImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static bool findInSharedCacheImage(const char* path, bool searchByPath, const struct stat* stat_buf, const macho_header** mh, const char** pathInCache, long* slide)
{
dyld3::SharedCacheFindDylibResults results;
if ( dyld3::findInSharedCacheImage(sSharedCacheLoadInfo, path, &results) ) {
*mh = (macho_header*)results.mhInCache;
*pathInCache = results.pathInCache;
*slide = results.slideInCache;
return true;
}
return false;
}

bool inSharedCache(const char* path)
{
return dyld3::pathIsInSharedCacheImage(sSharedCacheLoadInfo, path);
}

static int imageSorter(const void* l, const void* r)
{
const ImageLoader* left = *((ImageLoader**)l);
const ImageLoader* right= *((ImageLoader**)r);
return left->compare(right);
}
  • findInSharedCacheImage
  • inSharedCache

0x04 Clang++编译dsc_extractor.cpp

生成可执行文件dsc_extractor :

1
$: clang++ -o dsc_extractor dsc_extractor.cpp 

抽取动态共享缓存中的Mach-O

1
2
$: cd xxx/com.apple.dyld
$: ./dsc_extractor dyld_shared_cache_arm64 arm64_file

dsc_extractor.cpp内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// test program
#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>


typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path,
void (^progress)(unsigned current, unsigned total));

int main(int argc, const char* argv[])
{
if ( argc != 3 ) {
fprintf(stderr, "usage: dsc_extractor <path-to-cache-file> <path-to-device-dir>\n");
return 1;
}

//void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY);
void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY);
if ( handle == NULL ) {
fprintf(stderr, "dsc_extractor.bundle could not be loaded\n");
return 1;
}

extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress");
if ( proc == NULL ) {
fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n");
return 1;
}

int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } );
fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result);
return 0;
}

dyld加载流程

  • dyldStartup.s: call __dyld_start ->call dyldbootstrap::start -> dyldInitialization.cpp: call start()->_main()->
dyld的start()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
intptr_t slide, const struct macho_header* dyldsMachHeader,
uintptr_t* startGlue)
{
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
if ( slide != 0 ) {
rebaseDyld(dyldsMachHeader, slide);
}

// allow dyld to use mach messaging
mach_init();

// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];

// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;

// set up random value for stack canary
__guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif

// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);

return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);

}

dyld的main()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
//1、保存可执行文件header,后面可以根据header访问其他信息
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;

//2、设置上下文信息
setContext(mainExecutableMH, argc, argv, envp, apple);

//3、获取可执行文件的路径
sExecPath = _simple_getenv(apple, "executable_path");


}
0x05反汇编 Hopper Disassembler
  • [Xclient下载Hopper Disassembler](https://xclie nt.info/s/hopper-disassembler.html#versions)

2、Mach-O文件

  • Mac 内核xnu
  • 工具:MachOView
  • 来源:C/OC/Swift -> .O目标文件->Mach-O可执行文件
  • 查看Mac内核中的Mach-O: xnu-6153.11.26->EXTERNAL_HEADERS->mach-o

2.1 常见Mach-O

MH_EXECUTE-可执行文件

  • .app/xxx

MH_OBJECT-目标文件或静态库

  • 目标文件(.o)
  • 静态库文件(.a)-静态库其实就是多个.o的集合

MH_DYLIB-动态库文件

  • .dylib
  • .framework/xxx

MH_DYLIKER-动态链接编辑器

  • /usr/lib/dyld

MH_DSYM:存储着二进制文件符号信息

  • .dSYM/Contents/Resources/DWARF/XXX

11种mach-o格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14

#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
#define MH_DSYM 0xa /* companion file with only debug */
/* sections */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */

Mach-o的作用

  • The layout of the file depends on the filetype. For all but the MH_OBJECT file type the segments are padded out and aligned on a segment alignment boundary for efficient demand pageing.

  • The MH_EXECUTE, MH_FVMLIB, MH_DYLIB,MH_DYLINKER and MH_BUNDLE file types also have the headers included as part of their first segment.

  • Mach-O的布局取决于文件类型。除了MH_OBJECT以外的所有的文件类型将分段填充并在分段对齐时-对齐有效请求分页的边界。

  • MH_EXECUTE,MH_FVMLIB,MH_DYLIB,MH_DYLINKER和MH_BUNDLE这些文件也有headers作为它们第一个segment。

  • The file type MH_OBJECT is a compact format intended as output of the assembler and input (and possibly output) of the link editor (the .oformat). All sections are in one unnamed segment with no segment padding.

  • This format is used as an executable format when the file is so small the segment padding greatly increases its size.

  • 文件类型MH_OBJECT是一种紧凑格式,旨在作为汇编器和链接编辑器(.o的输入)(可能是输出格式)。所有sections都在一个未命名的segment中,没有segment的填充。

  • 当文件太小时segment填充可以大大增加了它的size。此格式用作可执行格式。

  • The file type MH_PRELOAD is an executable format intended for things that are not executed under the kernel (proms, stand alones, kernels, etc).

  • The format can be executed under the kernel but may demand paged it and not preload it before execution.

  • 文件类型MH_PRELOAD是一种可执行格式,用于非kernel内核下执行。(proms, stand alones, kernels, etc)

  • 格式可以在内核下执行,但可能需要分页而不是在执行之前预加载它。

  • A core file is in MH_CORE format and can be any in an arbritray legal Mach-O file. Constants for the filetype field of the mach_header

  • 核心文件为MH_CORE格式,可以是任意格式的Mach-O文件。mach_header的文件类型是常量

生成通用二进制文件:

Architectures.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// Architectures
//
struct x86
{
typedef Pointer32<LittleEndian> P;

};

struct x86_64
{
typedef Pointer64<LittleEndian> P;
};

struct arm
{
typedef Pointer32<LittleEndian> P;

};

struct arm64
{
typedef Pointer64<LittleEndian> P;

};

Universal Binary
  • 通用二进制文件,包含多种不同架构的二进制文件,比单个架构的文件大,也叫Fat Binary
  • 由于执行过程中,只是调用一部分代码,所以运行起来也不需要额外的内存。
Xcode生成Universal Binary

Standard architectures $(ARCHS_STANDARD)

dyld_cache_format.h

1
2
#define IPHONE_DYLD_SHARED_CACHE_DIR	"/System/Library/Caches/com.apple.dyld/"
#define DYLD_SHARED_CACHE_BASE_NAME "dyld_shared_cache_"

FileAbstraction.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template <typename _E>
class Pointer32
{
public:
typedef uint32_t uint_t;
typedef _E E;

static uint64_t getP(const uint_t& from) INLINE { return _E::get32(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set32(into, (uint32_t)value); }

// Round to a P-size boundary
template <typename T>
static T round_up(T value) { return (value+3) & ~(T)3; }
template <typename T>
static T round_down(T value) { return value & ~(T)3; }
};


template <typename _E>
class Pointer64
{
public:
typedef uint64_t uint_t;
typedef _E E;

static uint64_t getP(const uint_t& from) INLINE { return _E::get64(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set64(into, value); }

// Round to a P-size boundary
template <typename T>
static T round_up(T value) { return (value+7) & ~(T)7; }
template <typename T>
static T round_down(T value) { return value & ~(T)7; }
};

3、dyld与Mach-O的关系

  • dyld属于MH_DYLDLINKER类型的Mach-O文件
  • dyld负责加载三种类型Mach-O文件
    • 可执行文件
    • 动态库
    • Bundle

mach-o loader:only MH_BUNDLE, MH_DYLIB, and some MH_EXECUTE can be dynamically loaded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// try mach-o loader
if ( shortPage ) throw "file too short";
if ( isCompatibleMachO(firstPages, path) ) {

// only MH_BUNDLE, MH_DYLIB, and some MH_EXECUTE can be dynamically loaded
const mach_header* mh = (mach_header*)firstPages;
switch ( mh->filetype ) {
case MH_EXECUTE:
case MH_DYLIB:
case MH_BUNDLE:
break;
default:
throw "mach-o, but wrong filetype";
}

uint32_t headerAndLoadCommandsSize = sizeof(macho_header) + mh->sizeofcmds;
if ( headerAndLoadCommandsSize > MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE )
throwf("malformed mach-o: load commands size (%u) > %u", headerAndLoadCommandsSize, MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE);

if ( headerAndLoadCommandsSize > fileLength )
dyld::throwf("malformed mach-o: load commands size (%u) > mach-o file size (%llu)", headerAndLoadCommandsSize, fileLength);

if ( headerAndLoadCommandsSize > 4096 ) {
// read more pages
unsigned readAmount = headerAndLoadCommandsSize - 4096;
if ( pread(fd, &firstPages[4096], readAmount, fileOffset+4096) != readAmount )
throwf("pread of extra load commands past 4KB failed: %d", errno);
}

// instantiate an image
ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPages, headerAndLoadCommandsSize, fileOffset, fileLength, stat_buf, gLinkContext);

// validate
return checkandAddImage(image, context);
}

4、符号地址

符号地址= 基地址 - 偏移地址

获取基地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//获取基地址
uintptr_t get_load_address(void) {
const struct mach_header *exe_header = NULL;
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
const struct mach_header *header = _dyld_get_image_header(i);
if (header->filetype == MH_EXECUTE) {
exe_header = header;
break;
}
}

//返回值即为加载地址
return (uintptr_t)exe_header;
}

获取偏移地址

1
2
3
4
5
6
7
8
9
10
11
12
uintptr_t get_slide_address(void) {
uintptr_t vmaddr_slide = NULL;
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
const struct mach_header *header = _dyld_get_image_header(i);
if (header->filetype == MH_EXECUTE) {
vmaddr_slide = _dyld_get_image_vmaddr_slide(i);
break;
}
}

return (uintptr_t)vmaddr_slide;
}

dSYM文件

  • 编译时添加选项:DWARF with dSYM File,在编译打包完成之后就会生成调试符号文件(Mach-O文件)
  • 文件查找:找到.xcarchive文件→show package contents→…一直到DWARF→工程二进制文件

atos命令

有了dSYM文件,就可以使用atos命令查找到具体代码行出现奔溃信息的地方

1
2
3
atos [-o executable] [-l loadAddress] [-arch architecture] [address ...]

#-arch 选择框架arm64/arm32/x86_64

4、Crash收集

KSCrash

SentryCocoa

CATALOG
  1. 1. Dyld源码阅读
    1. 1.1. 1、动态库路径
    2. 1.2. 0x00 load()
    3. 1.3. 0x01 loadPhase2()
    4. 1.4. 0x02 loadPhase5
      1. 1.4.0.1. loadPhase5load()
      2. 1.4.0.2. loadPhase5check()
  2. 1.5. 0x03 findInSharedCacheImage
  3. 1.6. 0x04 Clang++编译dsc_extractor.cpp
    1. 1.6.0.1. 生成可执行文件dsc_extractor :
    2. 1.6.0.2. 抽取动态共享缓存中的Mach-O
    3. 1.6.0.3. dyld加载流程
      1. 1.6.0.3.1. dyld的start()方法:
      2. 1.6.0.3.2. dyld的main()方法
      3. 1.6.0.3.3. 0x05反汇编 Hopper Disassembler
  • 1.7. 2、Mach-O文件
    1. 1.7.0.1. 2.1 常见Mach-O
    2. 1.7.0.2. MH_EXECUTE-可执行文件
    3. 1.7.0.3. MH_OBJECT-目标文件或静态库
    4. 1.7.0.4. MH_DYLIB-动态库文件
    5. 1.7.0.5. MH_DYLIKER-动态链接编辑器
    6. 1.7.0.6. MH_DSYM:存储着二进制文件符号信息
    7. 1.7.0.7. 11种mach-o格式
    8. 1.7.0.8. Mach-o的作用
    9. 1.7.0.9. 生成通用二进制文件:
      1. 1.7.0.9.1. Architectures.hpp
      2. 1.7.0.9.2. Universal Binary
      3. 1.7.0.9.3. Xcode生成Universal Binary
    10. 1.7.0.10. dyld_cache_format.h
    11. 1.7.0.11. FileAbstraction.hpp
  • 1.8. 3、dyld与Mach-O的关系
  • 1.9. 4、符号地址
    1. 1.9.0.1. 获取基地址
    2. 1.9.0.2. 获取偏移地址
    3. 1.9.0.3. dSYM文件
    4. 1.9.0.4. atos命令
  • 1.10. 4、Crash收集
    1. 1.10.0.1. KSCrash
    2. 1.10.0.2. SentryCocoa
      1. 1.10.0.2.1. SentryCrashDynamicLinker