feature: App 逆向防护

This commit is contained in:
杭城小刘
2024-07-15 20:03:01 +08:00
parent 13f7457be9
commit 83fefff66b
109 changed files with 2549 additions and 672 deletions

View File

@@ -219,12 +219,65 @@ NSLog(@"5");
Demo8
```objective-c
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
// 这里有没有具体的逻辑都不影响,本质是一个任务 block区别是空 block 和非空 block。
});
NSLog(@"4");
});
dispatch_sync(queue, ^{
NSLog(@"5");
});
// console
1
2
死锁
```
Demo9
```objective-c
dispatch_sync(dispathc_get_main_queue(), ^{
NSLog(@"主队列同步");
});
// 死锁
dispatch_sync(dispathc_get_main_queue(), ^{});
// 死锁
dispatch_sync(dispatch_get_main_queue(), nil);
// 死锁
```
### 总结
只要是同步提交任务 `dispatch_sync()` 不管是提交到串行队列还是并发队列,都是在当前线程执行。
## 一些经典 Demo
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/BlockAndQueue.png" style="zoom:30%" />
会输出什么?
打印结果电脑速度快的话会有很多次打印出5.慢的话打印出大于5的几次。
分析因为在循环内部是全局并发队列。多线程的情况下执行异步任务任务的先后顺序没办法保证。可能线程1拿到a=0然后内部加了1.线程2一开始拿到a=0但是代码还没执行到a++在线程1里面a就已经变为2因为是 __block 修饰的。所以线程2里面拿到的a变成了a然后内部a++后a就是3.其他线程执行情况类似。
NSLog 属于 IO 流,比普通运算耗时。所以当能执行 NSLog 的时候a 一定是大于等于5的。某条线程 a 大于等于5之后就立马结束 while 循环,开始执行最后的 NSLog。
所以电脑越快打印5的次数更多。电脑慢的情况下可能会存在几次输出大于5的情况。
## performSelector...withObject 研究
@@ -263,7 +316,7 @@ NSLog(@"5");
Demo1
<img src="./../assets/RunloopPerformSelector.png" style="zoom:25%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunloopPerformSelector.png" style="zoom:25%" />
QA为什么先打印1、3再打印2因为 `performSelector...withObject...afterDelay` 相当于给 RunLoop 添加了一个 TimerTimer 运行需要 RunLoop 配合。RunLoop 在被唤醒的时候会处理定时器。
@@ -271,7 +324,7 @@ Demo2:
<img src="./../assets/RunloopPerformSelectorAfterDelay.png" style="zoom:25%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunloopPerformSelectorAfterDelay.png" style="zoom:25%" />
@@ -327,7 +380,7 @@ QA为什么 showLog 里的2没有打印
Demo3:
<img src="./../assets/GCDThreadWillTerminateWhenBlockFinished.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/GCDThreadWillTerminateWhenBlockFinished.png" style="zoom:30%" />
同理GCD 虽然开启了子线程,但是 Block 结束后线程也就结束了。所以线程任务中的1秒后的任务肯定也结束了。
@@ -335,13 +388,13 @@ Demo3:
Demo4:
<img src="./../assets/NSThreadWillTerminateSoCannotUse.png" style="zoom:25%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSThreadWillTerminateSoCannotUse.png" style="zoom:25%" />
可以看到 NSThread 里的 block 执行结束后thread 结束了。后面的 performSelector 想在线程里执行任务,就会 crash。
解决办法也是在线程的 block 里面加 RunLoop让它保活
<img src="./../assets/NSThreadWillTerminateSoCanUseViaRunLoopPort.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSThreadWillTerminateSoCanUseViaRunLoopPort.png" style="zoom:30%" />
@@ -550,21 +603,21 @@ sistep instruction简写为 stepisi。当你在 Xcode 汇编面板看
第一步:当第二次调用 saveMoney 方法,开启汇编调试
<img src="./../assets/OSSpinLock-Assemble2.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLock-Assemble2.png" style="zoom:30%" />
看到可疑方法 `OSSpinLockLock`给它加断点看到第10行高亮了。lldb 模式输入 c敲回车。次数输入 si 即可进入 `OSSpinLockLock`  方法内部调试
第二步:继续输入 si敲回车
<img src="./../assets/OSSpinLock-Assemble3.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLock-Assemble3.png" style="zoom:30%" />
第三步:看到可疑方法 `_OSSpinLockLockSlow`给它加断点lldb 输入 C。此时断点到这一行了继续输入 si。
<img src="./../assets/OSSpinLock-Assemble4.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLock-Assemble4.png" style="zoom:30%" />
第四步:在 `OSSpinLockLockSlow` 方法内部调试,不断输入 si。
<img src="./../assets/OSSpinLockAssemble1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLockAssemble1.png" style="zoom:30%" />
发现不断 si 最终一直会在第6行到第19行之间执行。懂汇编的会发现这其实是一个 while 循环。便可以证明自旋锁 OSSpinLock 在等锁的时候,底层实现是执行 while 循环,忙等,“太浪费性能了”(如果使用锁资源的线程任务很简单,那自旋也是高效的,可以快速获取锁。)
@@ -643,26 +696,26 @@ int cursorr = 1;
假如对存钱过程,忘记解锁怎么办?产生死锁,如下
<img src="./../assets/Thread-deadlock-unfaillock.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Thread-deadlock-unfaillock.png" style="zoom:30%" />
添加 cursor 标记死锁是发生在 `saveMoney` 方法执行的第几次。发现是第二次。因为第一次锁没有任何使用方,所以加锁成功,当第二次加锁的时候发现锁没有释放,所以产生死锁。
这时候使用尝试加锁 API `os_unfair_lock_trylock` 即可成功如下
<img src="./../assets/Thread-deadlock-unfairTrylock.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Thread-deadlock-unfairTrylock.png" style="zoom:30%" />
#### 汇编窥探原理
同样方式看看 ,按照上述调试汇编代码的步骤,我将关键步骤截图如下
<img src="./../assets/osunfairlock-assemble1.png" style="zoom:30%" />
<img src="./../assets/osunfairlock-assemble2.png" style="zoom:30%" />
<img src="./../assets/osunfairlock-assemble3.png" style="zoom:30%" />
<img src="./../assets/osunfairlock-assemble4.png" style="zoom:30%" />
<img src="./../assets/osunfairlock-assemble5.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble2.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble3.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble4.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble5.png" style="zoom:30%" />
<img src="./../assets/osunfairlock-assemble6.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble6.png" style="zoom:30%" />
结论:可以看到 `os_unfair_lock` 在锁等待的时候,底层调用的是 `sysCall`,当这一步执行后会发现后续代码都不执行了,也就是调用系统底层能力,线程真正休眠了,而不是一个循环忙等,性能也好。
@@ -711,7 +764,7 @@ pthread_mutex_destroy(&_moneyLock);
使用如下
<img src="./../assets/PThreadMutextLock.png" style="zoom:25%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutextLock.png" style="zoom:25%" />
#### 递归锁
@@ -757,7 +810,7 @@ pthread_mutex_destroy(&_moneyLock);
改进后的效果如下
<img src="./../assets/PThreadMutextRecursiveLock.png" style="zoom:25%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutextRecursiveLock.png" style="zoom:25%" />
@@ -769,33 +822,33 @@ QA互斥递归锁可以在不同线程中加锁吗
#### 汇编窥探原理
<img src="./../assets/PThreadMutexLock1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock1.png" style="zoom:30%" />
输入 si 继续跟进可以看到还是在执行我们自己的代码LockExplore image 的 `pthread_mutex_lock` 方法
<img src="./../assets/PThreadMutexLock2.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock2.png" style="zoom:30%" />
继续输入 si 跟进
<img src="./../assets/PThreadMutexLock3.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock3.png" style="zoom:30%" />
可以看到此时调用到系统 `libsystem_pthread.dylib` 库的 `pthread_mutex_lock` 方法了。
第41行看到关键函数继续输入 si 进去看看
<img src="./../assets/PThreadMutexLock4.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock4.png" style="zoom:30%" />
可以看到内部第62行关键函数调用了 `_pthread_mutex_firstfit_lock_wait` 方法。此时继续输入 si 跟踪看看
<img src="./../assets/PThreadMutexLock5.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock5.png" style="zoom:30%" />
可以看到内部第25行调用了关键函数 `__psynch_mutexwait`,继续输入 si 看看
<img src="./../assets/PThreadMutexLock6.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock6.png" style="zoom:30%" />
可以看到内部继续调用了系统 `libsystem_pthread.dylib` 库的 `__psynch_mutexwait` 方法。继续输入 si
<img src="./../assets/PThreadMutexLock7.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock7.png" style="zoom:30%" />
可以看到内部第4行发生了系统调用 `sysCall`,执行完第四句指令,线程立马就结束了。
@@ -815,7 +868,7 @@ QA互斥递归锁可以在不同线程中加锁吗
激活所有等待该条件的线程 `pthread_cond_broadcast(&_condition)`
<img src="./../assets/PThreadConditionLock.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadConditionLock.png" style="zoom:30%" />
可以看到同时调用 remove、add 方法
@@ -917,13 +970,13 @@ NSRecursiveLock 不能在多线程下递归调用。@synchronized 可以在多
Demo
<img src="./../assets/NSLockDemo.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSLockDemo.png" style="zoom:30%" />
NSLock 死锁
<img src="./../assets/NSLockDeadLock.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSLockDeadLock.png" style="zoom:40%" />
会发生死锁后续代码无法执行App 表现就是 ANR。重复对 NSLock 进行加锁可能导致死锁问题,同时也可能引发数据竞争和性能下降等并发相关隐患
@@ -973,7 +1026,7 @@ API
Demo
<img src="./../assets/NSConditionLockDemo.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionLockDemo.png" style="zoom:30%" />
@@ -981,13 +1034,13 @@ Demo
使用 NSCondition 的时候 unlock 和 signal 的顺序可能会对结果造成影响。举个例子
<img src="./../assets/NSConditionOrder1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionOrder1.png" style="zoom:30%" />
可以看到在这种情况下,由于 NSCondition 另一个地方 waitwait 也需要释放锁,但是另一个发 signal 的地方还没释放锁。所以会等待2s。
针对这个情况,可以将 unlock 和 signal 的顺序进行调整。
<img src="./../assets/NSConditionOrder2.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionOrder2.png" style="zoom:30%" />
先解锁,然后发送 singal后续其他的业务逻辑也不影响。当然这个需要针对实际代码进行设计。
@@ -1030,7 +1083,7 @@ API 如下:
Demo
<img src="./../assets/NSConditionLockDemo1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionLockDemo1.png" style="zoom:30%" />
分析虽然通过3个线程设置了线程的先后顺序但是多线程任务执行的时候到底谁先执行是没办法控制的。但是通过 `NSConditionLock lockWhenCondition:*` 的能力,可以控制线程的执行顺序。
@@ -1046,7 +1099,7 @@ Demo
线程同步的本质就是多线程的任务是顺序执行
<img src="./../assets/SerialQueueToSolveThreadSyn.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SerialQueueToSolveThreadSyn.png" style="zoom:30%" />
@@ -1060,11 +1113,11 @@ semaphore 叫做”信号量”
信号量的初始值为1代表同时只允许1条线程访问资源保证线程同步
<img src="./../assets/DispatchSemaphoreControlThreadCount1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchSemaphoreControlThreadCount1.png" style="zoom:30%" />
可以看到打印了20个线程但是我们控制线程最大数量怎么办呢可以用信号量实现。效果如下
<img src="./../assets/DispatchSemaphoreControlThreadCount2.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchSemaphoreControlThreadCount2.png" style="zoom:30%" />
`dispatch_semaphore_wait` 函数的本质
@@ -1076,7 +1129,7 @@ semaphore 叫做”信号量”
所以如何让线程同步?设置信号量的值=1即可。保证同一时间只有一个线程任务在执行。代码如下
<img src="./../assets/SemaphoreMethodToControlThreadSync.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SemaphoreMethodToControlThreadSync.png" style="zoom:30%" />
@@ -1136,15 +1189,15 @@ dispatch_semaphore_signal(semaphore);
`@synchronized` 使用很方便,它是对 `pthread_mutex_t` 递归锁的封装。Demo 如下
<img src="./../assets/SynchronizedControlThreadSync.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SynchronizedControlThreadSync.png" style="zoom:30%" />
为了探究下实现,开启汇编调试
<img src="./../assets/synchronized-asemble1.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/synchronized-asemble1.png" style="zoom:30%" />
<img src="./../assets/synchronized-asemble2.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/synchronized-asemble2.png" style="zoom:30%" />
通过汇编可以看到 `@synchronized` 底层调用了 `objc_sync_enter` 方法,其中又调用了 `id2data` 和 `os_unfair_recursive_lock_lock_with_options` 方法。 可以查看 objc4 的源码(笔者的 objc 版本为 objc4-objc4-912.3),查找 `objc_sync_enter`
@@ -1620,7 +1673,7 @@ pthread_rwlock_init(&_lock, NULL)
Demo
<img src="./../assets/PThreadRWLockDemo.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadRWLockDemo.png" style="zoom:30%" />
@@ -1648,7 +1701,7 @@ dispatch_barrier_async(self.queue, ^{
上 Demo
<img src="./../assets/DispatchBarrierReadWriteDemo.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchBarrierReadWriteDemo.png" style="zoom:30%" />