最早了解到fishhook是看了下面两篇文章之后,顿时让我觉得这是一个非常好的东西。总共210行代码,收获了1500+个star,神作啊。

  1. iOS Lazy Binding,使用fishhook拦截NSSetUncaughtExceptionHandler函数解决NSUncaughtExceptionHandler被修改的问题。
  2. 聊聊苹果的Bug - iOS 10 nano_free Crash,通过fishhook替换malloc相关的函数尝试解决crash。

OC的runtime非常强大,可以玩很多黑魔法。而操作系统也会提供一些基础设施,比如Solaris/Mac DTrace和Linux systemtap,通过编写脚本分析各种系统调用的情况。相比之下,C语言没有runtime,也玩不出什么花来。不过fishhook提供了一种很好的方式,让我们得以做一些有意义的工作。花了点时间研究一下fishhook的源代码,也能增加一下对Mach-O执行格式的理解。

_dyld_register_func_for_add_image

fishhook首次替换利用了_dyld_register_func_for_add_image在动态库加载完成之后,做一次符号地址的替换,简化了替换逻辑。当然fishhook也支持在App启动之后遍历所有的动态库做符号替换。

/** The following functions allow you to install callbacks which will be called   * by dyld whenever an image is loaded or unloaded.  During a call to _dyld_register_func_for_add_image()* the callback func is called for every existing image.  Later, it is called as each new image* is loaded and bound (but initializers not yet run).  The callback registered with* _dyld_register_func_for_remove_image() is called after any terminators in an image are run* and before the image is un-memory-mapped.*/
extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))    __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);

我们App目前在用_dyld_register_func_for_add_image将所有要加载的动态库都在控制台打印出来了。

Added: 0x101afe000 (0x8000) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/libBacktraceRecording.dylib <0B27D656-0E24-3133-BAA2-3B3EC2409C04> time: 1488353483592Added: 0x101b0c000 (0x8000) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.2.sdk/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib <31AD1037-C99E-3141-AAD1-CF345BA42E72> time: 1488353483592

Mach-O

Mach-O的格式如下所示。每个segment里面包含了很多section。__DATA segment的section尤其多,__la_symbol_ptr是其中一个。

fishhook的原理是遍历__DATA segment里面__nl_symbol_ptr__la_symbol_ptr两个section里面的符号,通过Indirect Symbol Table、Symbol Table、String Table的配合,找到自己要替换的函数,然后做替换。整个过程如下所示。

MachOView

MachOView(附件里面有)看Mach-O格式比较直观。

otools && nm

通过otools和nm也可以对可执行程序做很多分析。

$ otool -l /Users/henshao/Library/Developer/Xcode/DerivedData/FishHookTest-elsztizpkmqzuedzxtjbslvzndzj/Build/Products/Debug-iphonesimulator/FishHookTest.app/FishHookTest
/Users/henshao/Library/Developer/Xcode/DerivedData/FishHookTest-elsztizpkmqzuedzxtjbslvzndzj/Build/Products/Debug-iphonesimulator/FishHookTest.app/FishHookTest:
Mach headermagic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags0xfeedfacf 16777223          3  0x00           2    20       2760 0x00200085
Load command 0cmd LC_SEGMENT_64cmdsize 72segname __PAGEZEROvmaddr 0x0000000000000000vmsize 0x0000000100000000fileoff 0filesize 0maxprot 0x00000000initprot 0x00000000nsects 0flags 0x0
Load command 1cmd LC_SEGMENT_64cmdsize 792segname __TEXTvmaddr 0x0000000100000000vmsize 0x0000000000004000fileoff 0filesize 16384maxprot 0x00000007initprot 0x00000005nsects 9flags 0x0
Sectionsectname __textsegname __TEXTaddr 0x00000001000018c0size 0x0000000000000f7aoffset 6336align 2^4 (16)reloff 0nreloc 0flags 0x80000400reserved1 0reserved2 0
Sectionsectname __stubssegname __TEXTaddr 0x000000010000283asize 0x0000000000000096offset 10298align 2^1 (2)reloff 0nreloc 0flags 0x80000408reserved1 0 (index into indirect symbol table)reserved2 6 (size of stubs)
Sectionsectname __stub_helpersegname __TEXTaddr 0x00000001000028d0size 0x000000000000010aoffset 10448align 2^2 (4)reloff 0nreloc 0flags 0x80000400reserved1 0reserved2 0
Sectionsectname __objc_methnamesegname __TEXTaddr 0x00000001000029dasize 0x0000000000000a13offset 10714align 2^0 (1)reloff 0nreloc 0flags 0x00000002reserved1 0reserved2 0
Sectionsectname __objc_classnamesegname __TEXTaddr 0x00000001000033edsize 0x000000000000003coffset 13293align 2^0 (1)reloff 0nreloc 0flags 0x00000002reserved1 0reserved2 0
Sectionsectname __objc_methtypesegname __TEXTaddr 0x0000000100003429size 0x000000000000082aoffset 13353align 2^0 (1)reloff 0nreloc 0flags 0x00000002reserved1 0reserved2 0
Sectionsectname __cstringsegname __TEXTaddr 0x0000000100003c53size 0x00000000000001cboffset 15443align 2^0 (1)reloff 0nreloc 0flags 0x00000002reserved1 0reserved2 0
Sectionsectname __entitlementssegname __TEXTaddr 0x0000000100003e1esize 0x0000000000000190offset 15902align 2^0 (1)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __unwind_infosegname __TEXTaddr 0x0000000100003fb0size 0x0000000000000050offset 16304align 2^2 (4)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Load command 2cmd LC_SEGMENT_64cmdsize 1192segname __DATAvmaddr 0x0000000100004000vmsize 0x0000000000001000fileoff 16384filesize 4096maxprot 0x00000007initprot 0x00000003nsects 14flags 0x0
Sectionsectname __nl_symbol_ptrsegname __DATAaddr 0x0000000100004000size 0x0000000000000010offset 16384align 2^3 (8)reloff 0nreloc 0flags 0x00000006reserved1 25 (index into indirect symbol table)reserved2 0
Sectionsectname __gotsegname __DATAaddr 0x0000000100004010size 0x0000000000000008offset 16400align 2^3 (8)reloff 0nreloc 0flags 0x00000006reserved1 27 (index into indirect symbol table)reserved2 0
Sectionsectname __la_symbol_ptrsegname __DATAaddr 0x0000000100004018size 0x00000000000000c8 //0xc8 = 200,200/8=25。offset 16408align 2^3 (8)reloff 0nreloc 0flags 0x00000007reserved1 28 (index into indirect symbol table)reserved2 0
Sectionsectname __objc_classlistsegname __DATAaddr 0x00000001000040e0size 0x0000000000000010offset 16608align 2^3 (8)reloff 0nreloc 0flags 0x10000000reserved1 0reserved2 0
Sectionsectname __objc_protolistsegname __DATAaddr 0x00000001000040f0size 0x0000000000000010offset 16624align 2^3 (8)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __objc_imageinfosegname __DATAaddr 0x0000000100004100size 0x0000000000000008offset 16640align 2^2 (4)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __objc_constsegname __DATAaddr 0x0000000100004108size 0x0000000000000be0offset 16648align 2^3 (8)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __objc_selrefssegname __DATAaddr 0x0000000100004ce8size 0x0000000000000018offset 19688align 2^3 (8)reloff 0nreloc 0flags 0x10000005reserved1 0reserved2 0
Sectionsectname __objc_classrefssegname __DATAaddr 0x0000000100004d00size 0x0000000000000008offset 19712align 2^3 (8)reloff 0nreloc 0flags 0x10000000reserved1 0reserved2 0
Sectionsectname __objc_superrefssegname __DATAaddr 0x0000000100004d08size 0x0000000000000008offset 19720align 2^3 (8)reloff 0nreloc 0flags 0x10000000reserved1 0reserved2 0
Sectionsectname __objc_ivarsegname __DATAaddr 0x0000000100004d10size 0x0000000000000008offset 19728align 2^3 (8)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __objc_datasegname __DATAaddr 0x0000000100004d18size 0x00000000000000a0offset 19736align 2^3 (8)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __datasegname __DATAaddr 0x0000000100004db8size 0x00000000000000c0offset 19896align 2^3 (8)reloff 0nreloc 0flags 0x00000000reserved1 0reserved2 0
Sectionsectname __bsssegname __DATAaddr 0x0000000100004e78size 0x0000000000000018offset 0align 2^3 (8)reloff 0nreloc 0flags 0x00000001reserved1 0reserved2 0
Load command 3cmd LC_SEGMENT_64cmdsize 72segname __LINKEDITvmaddr 0x0000000100005000vmsize 0x0000000000005000fileoff 20480filesize 17040maxprot 0x00000007initprot 0x00000001nsects 0flags 0x0
Load command 4cmd LC_DYLD_INFO_ONLYcmdsize 48rebase_off 20480rebase_size 200bind_off 20680bind_size 296weak_bind_off 0weak_bind_size 0lazy_bind_off 20976lazy_bind_size 568export_off 21544export_size 200
Load command 5cmd LC_SYMTABcmdsize 24symoff 21776nsyms 162stroff 24580strsize 3152
Load command 6cmd LC_DYSYMTABcmdsize 80ilocalsym 0nlocalsym 121iextdefsym 121nextdefsym 8iundefsym 129nundefsym 33tocoff 0ntoc 0modtaboff 0nmodtab 0extrefsymoff 0nextrefsyms 0indirectsymoff 24368nindirectsyms 53extreloff 0nextrel 0locreloff 0nlocrel 0
Load command 7cmd LC_LOAD_DYLINKERcmdsize 32name /usr/lib/dyld (offset 12)
Load command 8cmd LC_UUIDcmdsize 24uuid 0F497FE2-E79F-3376-BCFE-3E51F2AB6112
Load command 9cmd LC_VERSION_MIN_IPHONEOScmdsize 16version 10.2sdk 10.2
Load command 10cmd LC_SOURCE_VERSIONcmdsize 16version 0.0
Load command 11cmd LC_MAINcmdsize 24entryoff 9920stacksize 0
Load command 12cmd LC_LOAD_DYLIBcmdsize 88name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24)time stamp 2 Thu Jan  1 08:00:02 1970current version 1349.13.0
compatibility version 300.0.0
Load command 13cmd LC_LOAD_DYLIBcmdsize 56name /usr/lib/libobjc.A.dylib (offset 24)time stamp 2 Thu Jan  1 08:00:02 1970current version 228.0.0
compatibility version 1.0.0
Load command 14cmd LC_LOAD_DYLIBcmdsize 56name /usr/lib/libSystem.dylib (offset 24)time stamp 2 Thu Jan  1 08:00:02 1970current version 1238.0.0
compatibility version 1.0.0
Load command 15cmd LC_LOAD_DYLIBcmdsize 80name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24)time stamp 2 Thu Jan  1 08:00:02 1970current version 3600.6.21
compatibility version 1.0.0
Load command 16cmd LC_RPATHcmdsize 40path @executable_path/Frameworks (offset 12)
Load command 17cmd LC_FUNCTION_STARTScmdsize 16dataoff 21744datasize 32
Load command 18cmd LC_DATA_IN_CODEcmdsize 16dataoff 21776datasize 0
Load command 19cmd LC_CODE_SIGNATUREcmdsize 16dataoff 27744datasize 9776#这里面_OBJC_CLASS_xxx都是外部符号。还有些依赖的函数不在__la_symbol_ptr里面,比如___stack_chk_guard和dyld_stub_binder。
$ nm -um /Users/henshao/Library/Developer/Xcode/DerivedData/FishHookTest-elsztizpkmqzuedzxtjbslvzndzj/Build/Products/Debug-iphonesimulator/FishHookTest.app/FishHookTest (undefined) external _NSStringFromClass (from Foundation)(undefined) external _OBJC_CLASS_$_UIResponder (from UIKit)(undefined) external _OBJC_CLASS_$_UIViewController (from UIKit)(undefined) external _OBJC_METACLASS_$_NSObject (from libobjc)(undefined) external _OBJC_METACLASS_$_UIResponder (from UIKit)(undefined) external _OBJC_METACLASS_$_UIViewController (from UIKit)(undefined) external _UIApplicationMain (from UIKit)(undefined) external ___memcpy_chk (from libSystem)(undefined) external ___stack_chk_fail (from libSystem)(undefined) external ___stack_chk_guard (from libSystem)(undefined) external __dyld_get_image_header (from libSystem)(undefined) external __dyld_get_image_vmaddr_slide (from libSystem)(undefined) external __dyld_image_count (from libSystem)(undefined) external __dyld_register_func_for_add_image (from libSystem)(undefined) external __objc_empty_cache (from libobjc)(undefined) external _close (from libSystem)(undefined) external _dladdr (from libSystem)(undefined) external _free (from libSystem)(undefined) external _malloc (from libSystem)(undefined) external _memset (from libSystem)(undefined) external _objc_autoreleasePoolPop (from libobjc)(undefined) external _objc_autoreleasePoolPush (from libobjc)(undefined) external _objc_msgSend (from libobjc)(undefined) external _objc_msgSendSuper2 (from libobjc)(undefined) external _objc_release (from libobjc)(undefined) external _objc_retainAutoreleasedReturnValue (from libobjc)(undefined) external _objc_storeStrong (from libobjc)(undefined) external _open (from libSystem)(undefined) external _printf (from libSystem)(undefined) external _read (from libSystem)(undefined) external _strcmp (from libSystem)(undefined) external _strnlen (from libSystem)(undefined) external dyld_stub_binder (from libSystem)

一点点注释

#import "fishhook.h"
#import <stdio.h>#import <dlfcn.h>
#import <stdlib.h>
#import <string.h>
#import <sys/types.h>
#import <mach-o/dyld.h>
#import <mach-o/loader.h>
#import <mach-o/nlist.h>#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST  "__DATA_CONST"
#endifstruct rebindings_entry {struct rebinding *rebindings;size_t rebindings_nel;struct rebindings_entry *next;
};static struct rebindings_entry *_rebindings_head;static int prepend_rebindings(struct rebindings_entry **rebindings_head,struct rebinding rebindings[],size_t nel) {struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry));if (!new_entry) {return -1;}new_entry->rebindings = malloc(sizeof(struct rebinding) * nel);if (!new_entry->rebindings) {free(new_entry);return -1;}memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);new_entry->rebindings_nel = nel;new_entry->next = *rebindings_head;*rebindings_head = new_entry;return 0;
}static void perform_rebinding_with_section(struct rebindings_entry *rebindings,section_t *section,intptr_t slide,nlist_t *symtab,char *strtab,uint32_t *indirect_symtab) {printf("size: %llu\n", section->size);uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;//slide+section->addr 就是符号地址的数组void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);//遍历section里面的每一个符号for (uint i = 0; i < section->size / sizeof(void *); i++) {//找到符号在Indrect Symbol Table表中的值uint32_t symtab_index = indirect_symbol_indices[i];if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {continue;}//接着去symbol table里面找到符号的值,进一步获取到符号在String Table的名字。uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;char *symbol_name = strtab + strtab_offset;if (strnlen(symbol_name, 2) < 2) {continue;}printf("symbol_name: %s\n", symbol_name);struct rebindings_entry *cur = rebindings;while (cur) {for (uint j = 0; j < cur->rebindings_nel; j++) {if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {if (cur->rebindings[j].replaced != NULL &&indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {//把函数原来的地址保存起来*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];}//将新函数的地址设置上indirect_symbol_bindings[i] = cur->rebindings[j].replacement;goto symbol_loop;}}cur = cur->next;}symbol_loop:;}
}static void rebind_symbols_for_image(struct rebindings_entry *rebindings,const struct mach_header *header,intptr_t slide) {Dl_info info;if (dladdr(header, &info) == 0) {return;}segment_command_t *cur_seg_cmd;segment_command_t *linkedit_segment = NULL;struct symtab_command* symtab_cmd = NULL;struct dysymtab_command* dysymtab_cmd = NULL;//遍历Mach-O的segment非常有意思,要通过Load Command来遍历。//从下面的代码来看,SEG_LINKEDIT这个segment非常重要,Indirect Symbol Table、Symbol Table、String Table的地址都要基于它获取。uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {cur_seg_cmd = (segment_command_t *)cur;if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {linkedit_segment = cur_seg_cmd;}} else if (cur_seg_cmd->cmd == LC_SYMTAB) {symtab_cmd = (struct symtab_command*)cur_seg_cmd;} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;}}if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||!dysymtab_cmd->nindirectsyms) {return;}// Find base symbol/string table addressesuintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;//linkedit_base+symtab_cmd->symoff是Symbol Table的位置nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);//linkedit_base+symtab_cmd->stroff是String Table的位置char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);// Get indirect symbol table (array of uint32_t indices into symbol table)uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);cur = (uintptr_t)header + sizeof(mach_header_t);for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {cur_seg_cmd = (segment_command_t *)cur;if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {//找到DATA和DATA_CONST segmentif (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {continue;}for (uint j = 0; j < cur_seg_cmd->nsects; j++) {//找到__nl_symbol_ptr和__la_symbol_ptr这两个sectionsection_t *sect =(section_t *)(cur + sizeof(segment_command_t)) + j;if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);}if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);}}}}
}//完成动态库的binding之后,会回调这个函数。其中slide跟ALSR(Address space layout randomization)有关系,是一个随机的加载地址。
static void _rebind_symbols_for_image(const struct mach_header *header,intptr_t slide) {Dl_info image_info;int result = dladdr(header, &image_info);if (result == 0) {printf("Could not print info for mach_header: %p\n\n", header);return;}printf("added dylib: %s\n", image_info.dli_fname);rebind_symbols_for_image(_rebindings_head, header, slide);
}int rebind_symbols_image(void *header,intptr_t slide,struct rebinding rebindings[],size_t rebindings_nel) {struct rebindings_entry *rebindings_head = NULL;int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);rebind_symbols_for_image(rebindings_head, header, slide);free(rebindings_head);return retval;
}int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);if (retval < 0) {return retval;}// If this was the first call, register callback for image additions (which is also invoked for// existing images, otherwise, just run on existing imagesif (!_rebindings_head->next) {//启动的时候做函数替换_dyld_register_func_for_add_image(_rebind_symbols_for_image);} else {uint32_t c = _dyld_image_count();for (uint32_t i = 0; i < c; i++) {//启动之后也可以做函数替换,非常强大。_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));}}return retval;
}

通过日志可以看出,主程序和所有动态库的open和close都被替换掉了。

dylib: /Users/henshao/Library/Developer/CoreSimulator/Devices/BFC573F8-E181-4C9F-92CC-87F5E06F5B3C/data/Containers/Bundle/Application/C1226A56-043D-4029-B034-2B8D85C1D3CE/FishHookTest.app/FishHookTestsectname: __nl_symbol_ptr, segname: __DATA
size: 16
symbol_name: dyld_stub_bindersectname: __got, segname: __DATA
size: 8
symbol_name: ___stack_chk_guardsectname: __la_symbol_ptr, segname: __DATA
size: 200
symbol_name: _NSStringFromClass
symbol_name: _objc_autoreleasePoolPop
symbol_name: _objc_autoreleasePoolPush
symbol_name: _objc_msgSend
symbol_name: _objc_msgSendSuper2
symbol_name: _objc_release
symbol_name: _objc_retainAutoreleasedReturnValue
symbol_name: _objc_storeStrong
symbol_name: ___memcpy_chk
symbol_name: ___stack_chk_fail
symbol_name: __dyld_get_image_header
symbol_name: __dyld_get_image_vmaddr_slide
symbol_name: __dyld_image_count
symbol_name: __dyld_register_func_for_add_image
symbol_name: _close
symbol_name: _dladdr
symbol_name: _free
symbol_name: _malloc
symbol_name: _memset
symbol_name: _open
symbol_name: _printf
symbol_name: _read
symbol_name: _strcmp
symbol_name: _strnlen
symbol_name: _UIApplicationMain

参考文章

  1. fishhook 源码分析
  2. Mach-O的动态链接相关知识
  3. 趣探 Mach-O:文件格式分析
  4. 趣探 Mach-O:加载过程
  5. 通过 GDB 调试理解 GOT/PLT
  6. DTrace
  7. Mac及Linux动态库延迟加载对比

fishhook源码分析相关推荐

  1. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  2. SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...

  3. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  4. SpringBoot-web开发(一): 静态资源的导入(源码分析)

    目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...

  5. Yolov3Yolov4网络结构与源码分析

    Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...

  6. ViewGroup的Touch事件分发(源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...

  7. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

  8. MyBatis原理分析之四:一次SQL查询的源码分析

    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: Java代码   String res ...

  9. [转]slf4j + log4j原理实现及源码分析

    slf4j + log4j原理实现及源码分析 转载于:https://www.cnblogs.com/jasonzeng888/p/6051080.html

最新文章

  1. oracle网站注入,oracle注入,utl_http方法
  2. mysql 触发器 赋值_MYSQL的触发器中 变量赋值
  3. POJ2985 The k-th Largest Group(平衡树查询第K大)
  4. 实现一个对象验证库系列 -- 1) 接口介绍以及总体思路概述 (请大神批评)
  5. jmeter--入参为json类型
  6. 调用摄像头_摄像头 | 浏览器调用摄像头并实现截图保存的效果
  7. 使用 C# 9 的records作为强类型ID - 初次使用
  8. c++如何防止一个类被其他类继承?
  9. html为何转换为json,将HTML元素的“样式”属性转换为JSON
  10. java xml dom4j 解析_Java使用DOM4J解析XML
  11. 卓越领导者的智慧(精华版)
  12. MFCk开发IM-msvcrt.lib 和 libcmtd.lib 冲突的解决方案
  13. Linux的Cache Memory(缓存内存)机制
  14. oracle asm 加盘,ASM添加磁盘最佳实践
  15. 机电传动与控制【2】
  16. LeCun列传:曾因AI理论遭人唾弃,如今终成一代大神
  17. 腾讯微信客服电话号码是多少呢/腾讯微信人工服务热线
  18. 如何把uniapp的vue小程序项目跑起来
  19. Nginx代理浏览器接口调用本地的后端接口
  20. 选择勤哲Excel服务器做企业管理系统ERP的经验之谈

热门文章

  1. Linux编程(7)_gdb
  2. 用Kubeadm安装K8s后,kube-flannel-ds一直CrashLoopBackOff
  3. Kotlin协程的迷惑
  4. java接口自动化(五) - 企业级代码管理工具Git的配置
  5. mac python2.7升级到3.7_Mac 升级 Python2.7 到 Python3.5
  6. 成绩查询数据结构c语言,学生成绩管理系统(数据结构c语言版源代码)09169.pdf
  7. 给网站加上“新年快乐”灯笼源码
  8. [过年菜谱之]红烧甲鱼
  9. [Ext JS 4] 实战之升级系列一[Ext jS 3--Ext JS 4]
  10. [译] Dojo 工具包参考手册