0x0 前言
逆它纯粹就是无聊,后面想想以后中断虚拟化应该能用到,由于工作项目时间很紧,勉强断断续续逆向了一部分加自己做些实验来验证,目前先将这部分分享出来,带和我一样的新手入个门,建议先看下面推荐的三本书里的APIC章节,文章下面关于基础知识的介绍,来自网络各个大佬的笔墨,小弟就搬抄过来,省去自己写的麻烦事,建议先看书,文章纯粹是辅助(王者荣耀里的孙膑,顶多给你加加速),以下的步骤就是APIC的初始化流程,对了,AMD和INTEL内部实现会有部分区别,我的是INTEL的,AMD就将就看看吧,区别不大。
参考书籍:《Intel手册》、《x86/x64体系探索及编程》、《一个64位操作系统的设计与实现》
0x1 APIC介绍(简单介绍)
APIC (Advanced Programmable Interrupt Controller)是90年代Intel为了应对将来的多核趋势提出的一整套中断处理方案,用于取代老旧的8259A PIC。这套方案适用于多核(Multi-Processor)机器,每个CPU拥有一个Local APIC,整个机器拥有一个或多个IOAPIC,设备的中断信号先经由IOAPIC汇总,再分发给一个或多个CPU的Local APIC。为了配合APIC,还推出了MPSpec (Multiprocessor Specification),为BIOS向OS提供中断配置信息的方式提供了规范。
自90年代以来,PCI总线发展出了MSI (Message Signalled Interrupt),目前的机器中是以MSI为主要的中断机制,IOAPIC作为辅助,但CPU处仍使用Local APIC接收和处理中断。当时提出的MPSpec经过演化目前已成为了ACPI规范的一部分,BIOS可以通过ACPI表向OS报告中断配置情况(e.g. IOAPIC的引脚连接到哪个设备)。
初代奔腾(Pentium)上初次引入Local APIC时,它是外置的Intel 82489DX芯片。在一些奔腾型号以及P6 family(即从奔腾Pro到奔腾3)上则将其改为了内置,但功能保持不变。自奔腾4及至强(Xeon)开始取消了APIC Bus以及一部分相关设置,于是改称xAPIC,目前Intel CPU的默认模式就是xAPIC。后来又增加了x2APIC模式作为xAPIC的扩展。
0x2 ACPI表
通过上面【BIOS可以通过ACPI表向OS报告中断配置情况】这段话,我们知道windows内核是通过ACPI表知道中断硬件的配置情况。
没办法,简单介绍一下啥是ACPI,简单来说:就是固件(BIOS/UEFI)向系统传递硬件架构信息的机制。
那传递哪些硬件架构信息了?
MADT/"APIC":每个处理器的Local APIC信息(包括ID以及NMI等信息)以及系统的I/O APIC信息(包括与8259A相比的板载基础设备的中断重定向信息)
"HPET":系统的HPET信息
"MCFG":系统的PCIe的MCFG信息
"DSDT"、"SSDT":其它信息,包括板载基础设备的配置信息(包括内存、I/O、中断等信息),PCI/PCIe设备在8259A和APIC状态下的中断路由信息,以及 ACPI的一些特定的电源配置信息等
好了,正主来了,MADT(多APIC描述表),它描述了APIC的工作原理,windows通过以下API来得到,内部实现并不复杂,就是通过关键字去查找你需要的是那个硬件配置信息,有兴趣可以自行逆向研究,当然整个ACPI机制还是较为复杂的,有兴趣可以带小弟一起学习。
pMapic=HalSocGetAcpiTable('CIPA')
不同的版本,有不同的结构体,APICTABLES内部不同,为了方便,我们通过RW来查看
内部各个结构字段的意义,还请各位看官自己动动手BAIDU/GOOGLE吧!
0x3 APIC注册函数
前面的基础介绍里面,介绍了APIC->xAPIC->x2APIC的由来,知道从Apic总线变成了SystemBus,当然变化的不只这一点,CPU的个数变了,更重要的是访问方式也改变了,LAPIC的寄存器是通过MMIO访问的(即xAPIC模式),后来添加的x2APIC模式则通过MSR来访问其寄存器(可以向前兼容),windows需要根据你是哪个模式,采用哪种方式访问,所以用了下面这个API。
status = HalpApicSetupRegisterAccess();
主要实现了
如果是xAPIC:
HalpApicRead=HalpApic1ReadRegister
HalpApicWrite=HalpApic1WriteRegister
HalpApicWriteCommand=HalpApic1WriteIcr
HalpApicWaitForCommand=HalpApic1WaitForIcr
HalpApicEndOfInterrupt=HalpApic1EndOfInterrupt
如果是x2APIC
HalpApicRead=HalpApicX2ReadRegister
HalpApicWrite=HalpApicX2WriteRegister
HalpApicWriteCommand=HalpApicX2WriteCommand
HalpApicWaitForCommand=HalAcquireDisplayOwnership
HalpApicEndOfInterrupt==HalpApicX2EndOfInterrupt
0x4 APIC注册Io单元
实际上就是生成一个中断控制器对象,然后放进HalpRegisteredInterruptControllers链表里,(不好理解?你就理解类似EPROCESS的创建,后续只要调用APIC的函数,就需要这个对象)
HalpApicRegisterIoUnit(__int64 IoapicPhyAddr, int IoApicId, int GsiBase)
至于这个GsiBase是啥,百度吧,简单介绍一下就是:GSI是ACPI引入的概念,它为系统中每个中断源指定一个唯一的中断号,如果你只是想简单的在windows上了解APIC的工作机制,这个可以不用去深究,如果你想了解windows上APIC的管理,这个你得去深究了,windows把LocalApi和IoApic的引脚弄了一套中断线(INTERRUPT_LINE)的概念,一个Interrupt Line可以管理对应1/多个引脚,对中断控制器进行抽象 ,类似于对象管理器(object/object_header等等),Apic / Gic / Bcm2836ic / pic就是实例,实现很复杂,目前我没有逆完,就不在这里误人子弟了。
0x5 LocalApic的初始化(HalpInterruptInitializeLocalUnit)
1.调用HalpApicInitializeLocalUnit(_IO_APIC_DATA *ApicData, __int64 CpuNumber, int SvrVector, int LvtCmciVector, unsigned int LvtErrorVector, unsigned int *pApicId)
好了,我要从书上截图了,没错,就是开始初始化LVT表:
里面的各个参数,各位看官看书吧,我再这么解释,不如书上的一目了然。
那内部干了啥了:
LocalApic的物理地址映射一个虚拟地址。
HalpLocalApic= HalMapIoSpace(HalpLocalApicPhysical, 0x1000, 0);
我截图吧,实在懒得码字了,累,注释都写明白,这个CMCI的设置很复杂,这里是简单的初始化了一下,Intel白皮书第15章,可有整整一个章节介绍,哈里路亚,这里各位看官结合书来理解吧,应该没什么难度。
APIC可以硬件禁用/启用,禁用再启用后相当于断电重启。此外还可以软件禁用/启用(这里就是伪中断寄存器干的活)。需要注意的是,APIC在通电后默认是处于软件禁用的状态的。
注意:这里面的Vector对应的中断函数,在HalpInitializeInterrupts会提前注册,后续我们实验的时候会模拟自己创建这种中断流程,并且LocalApic的初始化并不会有很大的纠葛,所以省略,但可以截图上来。
2.调用HalpApicSetLogicalId(_IO_APIC_DATA *ApicData, _INTERRUPT_TARGET *InterruptTarget) 设置LocalAPIC寄存器里逻辑APIC ID值(逻辑目标寄存器LDR 和 目标格式寄存器DFR)。
备注:逻辑APIC ID值和LocalApic ID值不是一个东西哦。
LocalAPIC ID寄存器:当物理平台上电后,硬件设备会为系统总线上的每一个LocalAPIC分配唯一的初始APIC ID值,并将其保存在APIC ID寄存器内,你会发现在上面的HalpApicInitializeLocalUnit这个API里,并没有设置APIC ID的值,只有得到,这个地址在LocalApic寄存器地址映射表+0x20的偏移。
逻辑APIC ID,这玩意就有点复杂了,懒得废话,直接截图。
来看看windows是怎么设置的(我的CPU总共核没有超过8个,也就在逻辑平坦模式混混,哪位超过的,可以分享一下自己的,应该会进入HalpApicConvertId,进行ID转换,这个你们自己去验证咯)
这里直观感受一下二者的不同,+0xD0是逻辑APIC ID,+0x20是APIC ID,
备注:每个核会创建一个_INTERRUPT_TAGRET CPU核目标模式的结构,放进HalpInterruptTargets数组里。
0x6 IoApic的初始化(HalpApicInitializeIoUnit)
IoApic的初始化就相当简单了,调用HalpApicInitializeIoUnit(_IO_APIC_DATA *IoApicData)
好了,我又要从书上截图了,没错,就是开始初始化RET重定向表:
这个就没有难度了,直接给两个结构,自己去倒腾一下
0x7 结语
初始化先写到这里吧,肚子饿了,后面会另开一个帖子,写实验,windows居然写了一个bug,和Intel手册的处理居然写反了,不过那里无关轻重,不会产生特别大的影响,后续的代码注释会写的比较详细,上个图,吃饭去了,拜拜了各位。