ACPI是现代计算机系统中BIOS与操作系统间的桥梁。ACPI不仅定义了很多电源和休眠有关的标准,它还定义了如何通过FADT、XSDT、RSDT以及ASL语言来实现与操作系统的交互。

有些系统问题,比如无法进入睡眠状态,无法唤醒(自动重启,或死机)等,是与ACPI直接有关的。但如何定位出错的具体原因一直是一个比较困难的课题。很多时候不得不使用排除及试探等间接方法来解决,效率很低。本文介绍一种直接跟踪ASL源代码的直接方法。

1,   建立两台机器组成的内核调试环境(详见WinDbg帮助文档)。

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)