docs: fishhook 底层原理

This commit is contained in:
杭城小刘
2020-08-10 20:16:18 +08:00
parent 4c6ccac464
commit 9fd9df9337
21 changed files with 10583 additions and 1168 deletions

View File

@@ -522,7 +522,7 @@ LinkMap 文件分为3部分Object File、Section、Symbols。
Objective-C 中的方法都会通过 **objc_msgSend** 来调用,而 objc_msgSend 在 Mach-O 文件里是通过 **_objc_selrefs** 这个 **section** 来获取 selector 这个参数的。
所以_objc_selrefs 里的方法一定是被调用了的。**_objc_classrefs** 里是被调用过的类, **objc_superrefs** 是调用过 super 的类(继承关系)。通过 _objc_classrefs 和 _objc_superrefs,我们就可以找出使用过的类和子类。
里是被调用过的类, **objc_superrefs** 是调用过 super 的类(继承关系)。通过 _objc_classrefs 和 _objc_superrefs,我们就可以找出使用过的类和子类。
那么Mach-O 文件中的 _objc_selrefs、_objc_classrefs、_objc_superrefs 如何查看呢?

View File

@@ -5,4 +5,5 @@
学过 React.js 之后你再去学习 React Native 会很简单,一些核心的东西理解之后会很简单。比如 React 中的单向数据流、虚拟 Dom、diff 算法、数据变动的批量更新机制、diff 之后的 UI 渲染。
样式布局方面增加了 flexbox这样子布局在移动端会非常方便非常简单
样式布局方面增加了 flexbox这样子布局在移动端会非常方便非常简单

View File

@@ -1,2 +1,3 @@
# 带你打造一套 APM 监控系统
内容脱敏中...

File diff suppressed because it is too large Load Diff

View File

@@ -171,7 +171,13 @@ CI、CD 在稍微有点规模的公司内部都会内建一套自己的系统。
旧版本 cocoapods 中:
- SDK、App 主工程都可以使用 `#import <NativeQS/NativeQS.h>`、`#import "NativeQS.h"`、`#import <NativeQS.h>`
2. 部分 SDK 的使用了未在 podspec 文件中声明的依赖,在新版本 cocoapods 下会报错(某些 SDK 由于历史原因造成新版本丢失依赖描述)
- 涉及到的 SDKCMRCTToast
- 改造点:
问题基本定位是在于, App 主工程引用的 SdkBbs2 SDK 依赖了 SdkBbs2 版本(该版本的依赖描述为 `CMRCTToast (~> 0.1)` )
@@ -190,6 +196,7 @@ CI、CD 在稍微有点规模的公司内部都会内建一套自己的系统。
s.dependency 'CMDevice', '~> 0.1'
```
所以, 将 `CMRCTToast.podspec` 中的依赖修改掉。需要兼容不同 RN SDK 的版本。
3. 部分 pod 的 hook 脚本会失败。
- 涉及到的 SDK
- 改造点:

View File

@@ -0,0 +1,3 @@
# 打造一个通用、可配置、多句柄的数据上报 SDK
内容脱敏中...

View File

@@ -355,7 +355,6 @@ HTTPStubsProtocol 继承自 NSURLProtocol可以在 HTTP 请求发送之前对
[参考资料](https://github.com/draveness/analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20MockiOS.md)

100
Chapter1 - iOS/1.85.md Normal file
View File

@@ -0,0 +1,100 @@
# 统跳
定义:
使用统一的 URL 来描述特定的业务功能,并通过路由映射到任意资源目标,并基于此可实现 rewrite、埋点、监控、灰度、A/B 等功能
iOS非统跳接口调用检测设计方案
工作原理
由于ObjectC语言自身的限制获取编译时的调用关系信息相对较为困难。因此没有一个简便的方法获取到哪些接口调用了SDK中的方法。
ObjectC语言继承自C语言的头文件引用的机制可以作为检测的点。即如果App中调用了SDK中的某个方法那么一定会直接或者间接引用了SDK中的某个头文件。通过检测引用头文件引导iOS开发删除对应SDK的头文件那么应该可以暴露出App中直接调用SDK接口的位置。从而达到辅助检测非统跳接口的目的。
配置文件
配置文件主要用于在检测过程中,提供工程的目录结构相关信息。在生成过程中,提供生成结果文件需要的额外信息。
```YAML
category: login_register_sdk
plist: Example/LoginRegisterSDK/LoginRegisterSDK-Info.plist
workspace: Example/LoginRegisterSDK.xcworkspace
xcodeproj: Example/LoginRegisterSDK.xcodeproj
scheme: LoginRegisterSDK-Example
source_directory: LoginRegisterSDK/
pods_directory: Example/Pods
ignore:
- LoginRegisterSDK/**/*SDK.m
```
调用流程
```objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ...
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[TNTRouter sharedRouter] manuallyFetchRoutingTableWithCompletion:^(BOOL succeeded) {
}];
});
return YES;
}
```
```objective-c
- (void)manuallyFetchRoutingTableWithCompletion:(TNTManualFetchRoutingTableCompletion)completion {
NSDictionary *params = [self getRouterRequestParams];
[TMTrinityMGetTask manuallyFetchWithKey:TMNeutronRouterRewriteKey
params:params
moduleCallback:^(NSString * _Nullable result, NSError * _Nullable error) {
if (error) return ;
if (result.length == 0) return;
// encryp & json
NSDictionary *routesAndWrites = [self routesAndRewriteForRemoteEncryptedString:result];
// routes is existed
if (routesAndWrites) {
// process routes
[self loadRoutesAndWritesFromRemote:routesAndWrites];
// save decrpy data string
[self saveRemoteRouteString:result];
if(completion) completion(true);
}
} callback:nil];
}
```
# 参考资料
- [iOS performSelector传递两个以上参数](https://www.jianshu.com/p/4118793c88df)
-
之前看代码理解设计太慢了,不清楚设计背景,所以想请教你一下
1. 使用统一的 URL 来描述特定的业务功能,并通过路由映射到任意资源目标,并基于此可实现 rewrite、埋点、监控、灰度、A/B 等功能
如何理解rewrite、埋点、监控、灰度、A/B 等功能。
用统跳 url 后,调用某 SDK ,区分 typeh5、RN、Nativenative 则 runtimeperformSelector
H5、RN 没看到处理逻辑?
2. 通跳的设计背景是什么?解决业务上什么问题?举个例子理解下工作流程
- RoutingTableGenerator.rb 扫描工程 TNT_TARGET写入 Targets.json 的目的是什么?
- DefaultRoutingTableFetcher.rb 调用接口,写入文件
- ProjectConfigurator.rb 调用上述2个脚本写入工程 script
3. TrinityParams 和 TrinityConfigurator 2个 SDK 背景、作用分别是什么?

7
Chapter1 - iOS/1.86.md Normal file
View File

@@ -0,0 +1,7 @@
# GCD 源码探究
可以设计实现一个线程池涉及几个角色、任务队列、调度者如何调度、线程池的有哪些策略。iOS GCD 的线程池策略可以类比 Java 中的4个线程池策略明白不同语言设计的共同之处
- https://juejin.im/post/6855807995570618375

157
Chapter1 - iOS/1.87.md Normal file
View File

@@ -0,0 +1,157 @@
# Objective-C 底层
1. Objective-C 中对象、类主要是基于 C/C++ 中的结构体实现的。
方法一:
可以用 clang 验证。`clang -rewrite-objc main.m -o main.cpp`
转到指定平台代码。`xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp`
```
struct NSObject_IMPL {
Class isa;
};
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
```
所以 Class 是指向结构体的指针,所以在 64 位系统中占据8个字节32位系统占据4个字节。
NSObject 就是结构体占据8个字节。
![image-20200726130625217](/Users/lbp/Library/Application Support/typora-user-images/image-20200726130625217.png)
![image-20200726125926892](/Users/lbp/Library/Application Support/typora-user-images/image-20200726125926892.png)
发现 `class_getInstanceSize` 和 `malloc_size` 结果不一致。
```c
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
```
`class_getInstanceSize` 返回的是类的实例对象的成员变量的大小(四舍五入等于指针指向对象的大小,所以不精确)
```c
extern size_t malloc_size(const void *ptr);
/* Returns size of given ptr */
```
`malloc_size` 返回的就是指针指向的对象大小.
![image-20200726130920234](/Users/lbp/Library/Application Support/typora-user-images/image-20200726130920234.png)
结论:
- 当某个类继承自 NSObject 的时候如果没有其他属性则这个类占据16个字节。`class_getInstanceSize` 占据 8 `malloc_size` 占据 16
- 当某个类继承自 NSObject 的时候如果有其他属性则这个类占据16个字节。`class_getInstanceSize` 占据 16 `malloc_size` 占据 16
方法二: 从源代码角度出发验证(从上大下)
```c++
// NSObject.mm
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
// objc-class-old.mm
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
if (fastpath(!zone)) {
obj = class_createInstance(cls, 0);
} else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls);
return obj;
}
// objc-class-old.mm
/***********************************************************************
* _class_createInstance. Allocate an instance of the specified
* class with the specified number of bytes for indexed variables, in
* the default zone, using _class_createInstanceFromZone.
**********************************************************************/
static id _class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone (cls, extraBytes, nil);
}
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
// objc-runtime-new.h
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// objc-runtime-new.h
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
```
`CF requires all objects be at least 16 bytes.` 系统为 NSObject 对象分配了至少16个字节大小的空间但是它使用了 8 个字节大小的空间用来存放 ivars64位系统
2. 某个类继承自 NSObject 的情况,内存如何分配

40
Chapter1 - iOS/1.88.md Normal file
View File

@@ -0,0 +1,40 @@
# fishhook 原理
1. image: 代码编译后的可执行文件,被加载到内存中,就叫做镜像文件。
2. MachO 结构:
3. iOS 代码在编译时没有办法确定方法的实现地址。**动态库共享缓存**里面有动态库。NSLog 属于 Foundation 框架,每个手机内部中的地址不一定。
![image-20200810182447587](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/image-20200810182447587.png)
4. DYLD 动态链接器负责将可执行文件加载到内存中。App 启动后DYLD 将 Foundation、UIKit 等加载进动态库共享缓存中,但是加载到的位置不确定。
5. 可执行文件头有 **Load Commands**:加载命令。告诉 DYLD 依赖了什么。
6. 从 NSLog 找到代码实现经历过2种方式。早期重定向。现在PIC 技术(位置代码独立)。
7. 可执行文件:
- 代码段:可读可执行
- 数据段:可读可写
8. 写的业务代码里面假如某一行调用了 `NSLog`,那么在编译阶段,使用 NSLog 只是 IDE 提供了功能,让你可以看到声明而已。编译后的可执行文件,还是不知道 NSLog 的具体函数地址,它指向了一个表(类似一个应用程序的外部函数名,函数真正实现地址),去这个表里面找地址,这个表叫做**符号表**。
当 DYLD 加载当前可执行文件的时候,才将这个表每个编号对应的函数地址去填上去,这个动作叫做**符号绑定**。
当真正去调用 NSLog 函数的时候才去这个符号表中去寻找函数地址,去调用实现。
| 编号(符号) | 地址 | |
| ------------ | -------- | ---- |
| NSLog | 0xaabbcc | |
| ... | ... | |
![image-20200810190329801](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/image-20200810190329801.png)
9. fishhook 做的事情就是将系统的符号表,将符号表中的特定符号对应的地址,修改为自定义的函数地址。起到了 hook 作用。也就是说外部的 c 函数,在 iOS 中的调用属于**动态调用**。
https://www.bilibili.com/video/BV1UZ4y1u7Ba?from=search&seid=14997461811427810898

View File

@@ -87,4 +87,8 @@
* [81、__asm__ 重命名符号](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.81.md)
* [82、runtime](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.82.md)
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
* [84、WKWebView 技巧集合](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.84.md)
* [84、WKWebView 技巧集合](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.84.md)
* [85、统跳技术](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.85.md)
* [86、GCD 源码探究](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.86.md)
* [87、Objective-C 底层探究](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.87.md)
* [88、fishhook 原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.88.md)