Windows驱动开发学习笔记(七)—— 多核同步
前言
一、学习自
滴水编程达人
中级班课程,官网:https://bcdaren.com
二、海东老师牛逼!
基础知识
并发与同步
并发:指多个线程在同时执行
单核
:各个线程分时执行,不是真正意义上的同时多核
:在某一时刻,会同时有多个线程在执行
同步:保证在并发执行的环境中各个线程可以有序的执行
示例:
DWORD dwVal = 0; //全局变量
某线程中的代码:
dwVal++; //只有一行,安全吗
对应的汇编代码:
mov eax, [0x12345678]
add eax, 1 /*若当一个线程执行完这行代码时,发生了线程切换
*另一个线程在它的时间片中执行了这三行代码
*此时,0x12345678中存储的是4
*当再次发生线程切换,回到原线程,执行第三行代码后
*0x12345678钟存储的理应是5
*然而由于在第一次发生线程切换时,eax中存储的是4
*因此在回到原线程,执行第三行代码后,0x12345678中存储的仍然是4
*/
mov [0x12345678], eax
解决方案:LOCK指令
将
add eax, 1
改为
LOCK add eax, 1
参考:kernel32.InterlockedIncrement
分析 InterlockedIncrement
原子操作相关API
InterlockedIncrement InterlockedExchangeAdd
InterlockedDecrement InterlockedFlushSList
InterlockedExchange InterlockedPopEntrySList
InterlockedCompareExchange InterlockedPushEntrySList
...
思考:如何实现多行代码的原子操作?
关键代码A //N行代码要求原子操作
关键代码B //单独加LOCK可以吗?
关键代码C
...
内核文件
描述:在同一个操作系统中,单核模式和多核模式的内核文件(ntoskrnl.exe)会有一点小区别
多核同步
临界区
描述:一次只允许一个线程进入直到离开
示例一:错误的临界区
//实现临界区的方式就是加锁
//锁:全局变量,进去加一,出去减一
DWORD dwFlag = 0;
if( dwFlag == 0) //进入临界区
{ ↑↓
dwFlag = 1; //进入临界区
...
dwFalg = 0; //离开临界区
}
思考:以上代码哪里存在问题?
答案:当第一个线程进入if
中,但还未执行dwFlag=1
时,若发生线程切换,第二个线程仍然能够进入if
中
示例二:正确的临界区
全局变量:
Flag = 0;
进入临界区:
Lab:
mov eax,1
//多核情况下必须加lock
lock xadd [Flag],eax
cmp eax,0
jz endLab
dec [Flag]
//线程等待Sleep..
jmp Lab
endLab:
ret
离开临界区:
lock dec [Flag]
自旋锁
描述:
- 自旋锁只对多核有意义
- 自旋锁与临界区、事件、互斥体一样,都是一种同步机制, 都可以让当前线程处于等待状态,区别在于自旋锁不用切换线程
参考:KeAcquireSpinLock
AtDpcLevel
分析 KeAcquireSpinLockAtDpcLevel
思考
-
如何HOOK高并发的内核函数?
描述:在hook的时候如果使用memcpy等函数写入长跳转指令(五个字节),那么在函数内部实现中是逐个字节对内存进行修改的,若在修改的过程中有线程运行到这行指令,必然会引发错误,甚至蓝屏
答案:- 先在附近未使用的内存空间构造一个长跳转(五个字节),再在要hook的地方构造一个短跳转(一次性写入两个字节)指向长跳转位置
- 使用 cmpxchg8b 指令最多可一次性写入八个字节
-
若HOOK时需要覆盖多行指令(例如5个push指令),如何保证此时没有线程正在执行这几行指令而引发错误
答案:- 依然是短跳转
- 尽量避免在这些地方进行hook
-
使用临界区或者自旋锁是否可以实现多核HOOK?
答案:不能,因为使用锁的前提是其它线程也在使用这把锁,否则锁是没有意义的
内核重载
描述:
- 内核中的很多函数被层层HOOK,重载一份内核可以绕过这些HOOK
- 内核重载与映射PE文件的方法无异,本质上没有区别
- 内核重载就是重新加载一份内核文件(ntkrnlpa.exe)
步骤:
- 申请内存,按内存对齐展开
- 根据重定位表修复全局变量
- 修复IAT表(修复导入表的说法不准确)
- 山寨系统服务表
- 狸猫换太子(Hook KiFastCallEntry)
练习
描述:通过代码实现内核重载
答案:略(待补充)