ACPI是现代计算机系统中BIOS与操作系统间的桥梁。ACPI不仅定义了很多电源和休眠有关的标准,它还定义了如何通过FADT、XSDT、RSDT以及ASL语言来实现与操作系统的交互。
有些系统问题,比如无法进入睡眠状态,无法唤醒(自动重启,或死机)等,是与ACPI直接有关的。但如何定位出错的具体原因一直是一个比较困难的课题。很多时候不得不使用排除及试探等间接方法来解决,效率很低。本文介绍一种直接跟踪ASL源代码的直接方法。
1, 将被调试机器上的ACPI.SYS换成Checked Build (可以在保护模式下覆盖原来的文件),并建立两台机器组成的内核调试环境(详见WinDbg帮助文档),或者使用虚拟机模拟两台机器(参阅本站的http://advdbg.org/blogs/advdbg_system/articles/130.aspx) 。
2, 在调试机上按Ctrl+Break将被调试机中断到调试器。
3, 尝试使用!amli debugger命令启动AMLI调试器。第一次运行该命令可能失败。典型的出错信息如下:
AMLI_DBGERR: failed to get debugger flag address
此错误的根本原因是本地的AMLI调试模块没有找到合适的调试符号。原因有两个:
1) 被调试机没有使用调试版本的ACPI驱动(ACPI.DLL);
2) WinDbg还没有加载ACPI调试符号文件。对于此种情况,可尝试使用.reload命令,通常就可以解决问题了。
重新运行!amli debugger,如果没有任何显示,那么本地的AMLI调试模块就合被调试机的ACPI解释器成功握手了(通信)。当ACPI解释器再得到执行时,它就会立刻break到调试机的调试器中。因此,需要执行g命令让被调试机跑起来,才可能真正中断到AMLI的脚本调试器。
本地的AMLI调试扩展模块名称如下:
Windows NT 4.0 |
Not available |
Windows 2000 |
acpikd.dll |
Windows XP and later |
kdexts.dll |
4, 可以运行一些AMLI命令来检查它是否工作正常。比如!amli dns可以打印出ACPI空间中的所有对象。
!amli dns
ACPI Name Space: \ (ffffffff81b3c024)
Unknown(\___)
| Unknown(_GPE)
| | Method(_L03:Flags=0x0,CodeBuff=ffffffff81b3c841,Len=31)
| | Method(_L04:Flags=0x0,CodeBuff=ffffffff81b3c8c1,Len=31)
| | Method(_L05:Flags=0x0,CodeBuff=ffffffff81b3c941,Len=31)
| | Method(_L0B:Flags=0x0,CodeBuff=ffffffff81b3c9c1,Len=31)
| | Method(_L0C:Flags=0x0,CodeBuff=ffffffff81b3ca41,Len=31)
| | Method(_L0D:Flags=0x0,CodeBuff=ffffffff81b3cac1,Len=31)
| | Method(_L0E:Flags=0x0,CodeBuff=ffffffff81b3cb41,Len=31)
| | Method(_L1D:Flags=0x0,CodeBuff=ffffffff81b3cbc1,Len=32)
| Unknown(_PR_)
| | Processor(CPU0:Processor ID=0x1,PBlk=0x410,PBlkLen=6)
| | Processor(CPU1:Processor ID=0x2,PBlk=0x410,PBlkLen=6)
| Unknown(_SB_)
| | Device(SLPB)
| | | Integer(_HID:Value=0x000000000e0cd041[235720769])
| | | Method(_PRW:Flags=0x0,CodeBuff=ffffffff81b3cccd,Len=8)
| | Device(PCI0)
| | | Integer(_HID:Value=0x00000000030ad041[51040321])
| | | Integer(_ADR:Value=0x0000000000000000[0])
| | | Method(_INI:Flags=0x0,CodeBuff=ffffffff81b3d6dd,Len=16)
| | | Buffer(PBRS:Ptr=ffffffff81b3d71c,Len=136){
0x88,0x0d,0x00,0x02,0x0c,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00
0x00,0x01,0x47,0x01,0xf8,0x0c,0xf8,0x0c,0x01,0x08,0x88,0x0d,0x00,0x01
0x0c,0x03,0x00,0x00,0x00,0x00,0xf7,0x0c,0x00,0x00,0xf8,0x0c,0x88,0x0d
……(省略很多行)
也可以通过命令行参数,显示ACPI空间树中从某个设备开始的子树。比如一下命令显示的是电池设备。
kd> !amli dns /s \_sb_.pci0.bat0
ACPI Name Space: \_SB_.PCI0.BAT0 (ffffffff81b290f4)
Device(BAT0)
| Integer(_HID:Value=0x000000000a0cd041[168611905])
| Method(_STA:Flags=0x0,CodeBuff=ffffffff81b291d1,Len=32)
| Method(_BIF:Flags=0x0,CodeBuff=ffffffff81b29255,Len=274)
| Method(_BST:Flags=0x0,CodeBuff=ffffffff81b293c9,Len=262)
| | Package(PBST:NumElements=4){
| | | Integer(:Value=0x0000000000000001[1])
| | | Integer(:Value=0x0000000000000a6e[2670])
| | | Integer(:Value=0x0000000000000000[0])
| | | Integer(:Value=0x0000000000002ee0[12000])
| | }
| Method(_PCL:Flags=0x0,CodeBuff=ffffffff81b29531,Len=5)
5, 发出g命令,让被调试机恢复运行状态。因为ACPI的代码的使用频率很高,所以被调试机的ACPI解释器通常很快就会得到执行,并中断进调试器。其状态如图1所示。可见,命令行提示符已经由原来的kd>变为Input> (AMLI(? For help))。在此状态下,只可以运行AMLI调试器的命令。而且不需要再使用!amli前缀。
图1,中断到AMLI调试器
6, AMLI调试器也提高了一系列断点命令以实现跟踪功能。比如可以通过以下命令在电池设备的_STA方法入口处设置断点。
AMLI(? for help)-> bp \_sb.pci0.bat0._sta
bp \_sb.pci0.bat0._sta
成功设置的断点,AMLI调试器仅仅会回显该断点命令。如果设置失败,还会附加有错误信息,例如:
AMLI(? for help)-> bp \_sb.pci0.bat0.sta
bp \_sb.pci0.bat0.sta
AMLI_DBGERR: object not found or object is not a method - \_SB.PCI0.BAT0.STA
7, 设置断点后,可发出g命令让被调试机恢复运行。
8, 当断点被触发时,被调试机会被中断到调试器。
AMLI(? for help)-> g
g
Hit Breakpoint 0.
AMLI(? for help)->
可以通过ln命令判断本次断点最近的方法名:
AMLI(? for help)-> ln
ln
81b291d1: \_SB.PCI0.BAT0._STA
通过r命令可以显示出当前的上下文。包括线程、堆栈、ASL变量值等信息。
AMLI(? for help)-> r
r
Context=81adf000*, Queue=00000000, ResList=00000000
ThreadID=81b4c020, Flags=00000030
StackTop=81ae0f0c, UsedStackSize=244 bytes, FreeStackSize=7708 bytes
LocalHeap=81adf0bc, CurrentHeap=81adf0bc, UsedHeapSize=52 bytes
Object=\_SB.PCI0.BAT0._STA, Scope=\_SB.PCI0.BAT0._STA, ObjectOwner=81adf0e0, SyncLevel=0
AsyncCallBack=f99e6e98, CallBackData=e32de8b0, CallBackContext=f9e9a760
MethodObject=\_SB.PCI0.BAT0._STA
81ae0f5c: Local0=Unknown()
81ae0f70: Local1=Unknown()
81ae0f84: Local2=Unknown()
81ae0f98: Local3=Unknown()
81ae0fac: Local4=Unknown()
81ae0fc0: Local5=Unknown()
81ae0fd4: Local6=Unknown()
81ae0fe8: Local7=Unknown()
81adf040: RetObj=Unknown()
Next AML Pointer: 81b291d1 [\_SB.PCI0.BAT0._STA]
81b291d1: Store(^^SBUS.SRDW(0x16, 0xa), Local0)
81b291e2: If(LEqual(Local0, 0xffff))
81b291e9: {
81b291e9: | Return(0xf)
81b291ec: }
Press to continue and 'q' to quit?
81b291ec: Else
81b291ee: {
81b291ee: | Return(0x1f)
81b291f1: }
通过u命令可以将当前的AML代码反汇编成ASL。
9, 可以使用p或t命令跟踪ASL代码。
AMLI(? for help)-> p
p
Store(^^SBUS.SRDW(0x16,0xa)
81b4bc69: {
81b4bc69: If(LNot(LEqual(Acquire(MSMB,0xfffe)=0x0,Zero)=0xffffffff)=0x0)
AMLI(? for help)-> p
p
81b4bc7a: If(STRT()
81b4bf55: {
81b4bf55: Store(0x3e8,Local0)=0x3e8
AMLI(? for help)->
10, 执行set traceon 命令,可以使能ACPI解释器的踪迹功能,打印出所有ACPI代码的执行情况。
81b4bc90: Store(0xbf,HSTS)=0xbf
81b4bc97: Store(Or(Arg0=0x16,One,)=0x17,TXSA)=0x17
81b4bca0: Store(Arg1=0xa,HCOM)=0xa
81b4bca6: Store(0x4c,HCON)=0x4c
81b4bcad: If(COMP()
81b29035: {
81b29035: Store(0xfa0,Local0)=0xfa0
81b2903a: While(Local0=0xfa0)
81b2903d: {
81b2903d: Store(HSTS=0x42,Local1)=0x42
81b29043: If(And(Local1=0x42,0x1e,)=0x2)
81b2904a: {
81b2904a: If(And(Local1=0x42,0x2,)=0x2)
81b29051: {
81b29051: Return(One)
……