创建IOKit驱动工程

Info.plist中添加OSBundleLibraries 查看内核扩展依赖:
http://mirror.informatimago.com/next/developer.apple.com/documentation/Darwin/Conceptual/KEXTConcept/KEXTConceptDependencies/kext_dependencies.html#//apple_ref/doc/uid/20002370-BABFGHCJ

怎么列举kernel depend?确定kernel depend的版本?

编辑IOKitPersonalities定义了驱动支持的硬件设备信息。没有设置个信息的话,IOKit就不知道什么时候,如何加载这个驱动。Screen Shot 2020-05-13 at 9.59.29 PM.png

Screen Shot 2020-06-03 at 2.59.01 PM.png

IOResources 是特殊的nub,可以作为没有硬件的驱动(既是虚拟驱动)的provider

这个字典包含一下基本Key

  • IOClass – Names the C++ class that will be instantiated by I/O Kit if a driver matches a nub.
  • IOProviderClass – Defines a driver’s provider class type. Usually, it’s the nub controlling the port that the device connects to.
  • IOMatchCategory – Allows multiple drivers to match the same provider class. A driver should include this property if it matches IOResources or is on a port with multiple devices attached to it.
  • IOResourceMatch – Specifies a dependency to a specific resource. If the specified resource is unavailable, a driver won’t be loaded into the kernel.
  • IOProbeScore – Specifies the initial match score for a particular driver. The purpose of the probe score will be covered in the following sections.
  • IOKitDebug – Indicates specific I/O Kit events that should be logged in the kernel log (/var/log/kernel.log).
  • IOUserClient – Defines the class type of the driver’s user interface.

IOKitTest.hpp

  1. #include <IOKit/IOService.h>
  2. #ifndef IOKitTest_hpp
  3. #define IOKitTest_hpp
  4. class com_apple_driver_IOKitTest : public IOService {
  5. //一个宏定义,会自动生成该类的构造方法、析构方法和运行时
  6. OSDeclareDefaultStructors(com_apple_driver_IOKitTest);
  7. public:
  8. // 该方法与cocoa中init方法和C++中构造函数类似
  9. virtual bool init(OSDictionary *dict = 0) APPLE_KEXT_OVERRIDE;
  10. // 该方法与cocoa中dealloc方法和c++中析构函数类似
  11. virtual void free(void) APPLE_KEXT_OVERRIDE;
  12. // 进行驱动匹配时调用
  13. virtual IOService *probe(IOService *provider, SInt32 *score) APPLE_KEXT_OVERRIDE;
  14. // 进行加载时调用
  15. virtual bool start(IOService *provider) APPLE_KEXT_OVERRIDE;
  16. // 进行卸载时调用
  17. virtual void stop(IOService *provider) APPLE_KEXT_OVERRIDE;
  18. };
  19. #endif /* IOKitTest_hpp */

IOTestKit.cpp

  1. bool com_apple_driver_IOKitTest::init(OSDictionary *dict)
  2. {
  3. bool result = super::init(dict);
  4. IOLog("IOKitTest : did init !! \n"); // IOlog() 生成log日志, 存在在system.log里
  5. /** 遍历OSDictionary */
  6. OSCollectionIterator *iter = OSCollectionIterator::withCollection(dict);
  7. if (iter)
  8. {
  9. OSObject *object = NULL;
  10. while ((object = iter->getNextObject()))
  11. {
  12. OSSymbol *key = OSDynamicCast(OSSymbol, object);
  13. IOLog("iRedTest : key:%s ",key->getCStringNoCopy());
  14. OSString *value = OSDynamicCast(OSString, dict->getObject(key));
  15. if (value != NULL)
  16. {
  17. IOLog("iRedTest : value:%s\n",value->getCStringNoCopy());
  18. }
  19. }
  20. }
  21. return result;
  22. }

搭建调试环境

目标:远程调试机器内核和内核扩展代码,跟踪内核崩溃问题。

步骤一,需要找到一个可以被调试的内核,内核可以被调试需要满足两个条件。

  1. 苹果开发者下载中心中能找到这个相应内核版本的KDK工具。
  2. 苹果开源网站能找到这个内核的开源代码。

苹果虽然提供了旧版macOS系统的下载链接(10.15, 10.14, 10.13, 10.1210.11),很可惜我运气不太好,下载后发现并不能满足调试要求。同事推荐了Mac Downloader这个工具就比较好用了,下载列表里,每个系统版本号后面都带有build号,再去苹果开发者下载中心看看有没有对应build的的KDK,如果有的话,把KDK下载下来后安装好,你就可以在/Library/Developer/KDKs/目录下找到它了。KDK中包含了kernel和各种kext,接下来要试试看能否找到这个KDK说对应的源代码了。挑一个你准备调试的kext模块的.dSYM,用命令查看对应的代码版本信息,这里我关系IOGraphicsFamily这个kext,输入命令
xcrun dwarfdump --all /Library/Developer/KDKs/path/to/IOGraphicsFamily.kext.dSYM
可以发现使用的代码版本是IOGraphics-569.3,运气不错,苹果开源网站能找到开源代码,那么目标系统锁定了
10.15.2(19C39d),开始下载系统吧。

步骤二,你需要设置目标机器,也就是被调试的机器。我选择使用vmware Fusion安装虚拟机的方式来。

  1. 启动虚拟机按Cmd+R进入recovery模式。
  2. 关闭SIPcsrutil disalbe
  3. 更改boot-args, sudo nvram boot-args=”debug=0x145 kext-dev-mode=1 kcsuffix=development pmuflags=1 -v”

debug选项解释

  • 0x01 - Stop at boot time and wait for the debugger to attach
  • 0x02 - Send kernel debugging output to the console
  • 0x04 - Drop into debugger on a nonmaskable interrupt
  • 0x08 - Send kernel debugging information to a serial port
  • 0x10 - Make ddb the default debugger
  • 0x20 - Output diagnostics information to the system log
  • 0x40 - Allow the debugger to ARP and route
  • 0x80 - Support old versions of gdb on newer systems
  • 0x100 - Disable the graphical panic dialog screen

pmuflags=1, 关闭电源芯片的看门狗定时器

  1. 将文件系统设置为可写,sudo mount -uw /
  2. 利用KDK中的kernel.development代替kernel, sudo cp /Library/Developer/path/to/kernel.development /System/Library/Kernel
  3. 清理kext缓存,貌似不是必须的,sudo kextcache -invalidate
  4. 重启机器。

步骤三:实现源码级别调试

  1. 重启机器后,电脑会卡住等待调试器远程连接,输入kdp-remote 172.16.248.136连接调试器。
  2. 利用命令source info查看当前代码位置,然后将代码位置重新映射到你本机代码位置。

    1. settings set target.source-map /BuildRoot/Library/Caches/com.apple.xbs
  3. c继续运行,进入系统,随时可以运行sudo dtrace -w -n "BEGIN { breakpoint(); }"进入调试。

  4. 加载你的内核扩展或者你关心的任何系统内核模块 ```shell showallkexts -s Ali #找到你关心的内核模块加载地址

添加模块所在主机的实际位置

addkext -F /Users/zhaorui/AWS/AliProxyFramebuffer/build/Debug/AliVEDFrameBuffer.kext/Contents/MacOS/AliVEDFrameBuffer 0xffffff7f9591d000

添加dSYM

add-dsym /Users/zhaorui/AWS/AliProxyFramebuffer/build/Debug/AliVEDFrameBuffer.kext.dSYM

添加断点

b EWProxyFrameBufferClient.cpp:258

执行操作触发断点进入调试器, gui 进入图形调试器

gui

  1. 调试过程中往往还需要关注实时日志,可以使用下面命令
  2. ```shell
  3. log stream --predicate "process == 'kernel'" | grep AliVEDFrameBuffer
  4. log show --predicate 'process == "kernel"' --last 1h | grep AliVEDFrameBuffer

内核崩溃排查(日志符号化)

Screen Shot 2020-05-31 at 5.59.12 PM.png
这里只介绍分析我们自己的驱动AliVEDFrameBuffer的崩溃堆栈,内核崩溃堆栈分析方法一样,只要能找到对应版本号的KDK,利用其中的dSYM就能符号化。

  1. xcrun atos -o /Users/zhaorui/Downloads/virtualDisplayDriver+Demo/AliVEDFrameBuffer.kext/Contents/MacOS/AliVEDFrameBuffer -l 0xffffff7f8f51d000 0xffffff7f8f51f8ec

正常结果返回:
com_alibaba_AliVEDFrameBuffer_client::CheckFramebufferState() (in AliVEDFrameBuffer) (EWProxyFrameBufferClient.cpp:279)

查看驱动是否被授权

  1. sudo /usr/bin/sqlite3 /var/db/SystemPolicyConfiguration/KextPolicy
  2. sqlite> SELECT team_id, bundle_id, allowed, developer_name, flags FROM kext_policy;

参考

https://www.jianshu.com/p/232022a08ba4