关于WMI ACPI,建议先通读:1.<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>了解WMI ACPI能提供什么后,再参考MSDN 2.<Windows Instrumentation: WMI and ACPI>,另外,3.<文件系统驱动编程基础篇之4——Wmi管理规范 mof文件>也可以作为1.的补充。需要特别说明的,1.中留了demo程序,一般读者没有环境测试(也不建议用真实机器测试,万一不开机损失挺大的),这时倒可以参考我的文章<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>,从现有BIOS中提取MOF和WMI接口用于学习。
本文应该是<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>一文的原理补充和总结,主要补充一下MOF资源文件和ACPI中各个WMI接口的关系。要厘清他们的关系,得先用<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>文中使用的代码片:
Mof描述文件:
// Author: bini.Yi易祝兵 http://www.ufoit.com 2008-09-24
// File: demowmi.mof
//{39142400-C6A3-40fa-BADB-8A2652834100}
//IMPLEMENT_OLECREATE
//0x39142400, 0xc6a3, 0x40fa, 0xba, 0xdb, 0x8a, 0x26, 0x52, 0x83, 0x41, 0x00);[WMI,
Dynamic,
Provider("WmiProv"),
Locale("MS\\0x409"),
Description("Events"),
guid("{39142400-C6A3-40fa-BADB-8A2652834100}")
]
class DemoWMIData
{[key, read]string InstanceName;[read] boolean Active;[WmiDataId(1),read, write,Description("description")] uint32 Data;
};
ASL(WMI)文件:
// Author: bini.Yi易祝兵 2008-09-24
// File: demowmi.ASLDevice(DWMI)
{// PNP0C14 is PNP ID assigned to WMI mapperName(_HID, EISAID("PNP0C14"))Name(_UID, 0x0)Name(_WDG, Buffer(){// {39142400-C6A3-40fa-BADB-8A2652834100}0x00, 0x24, 0x14, 0x39, 0xA3, 0xC6, 0xFA, 0x40, 0xBA, 0xDB, 0x8A, 0x26, 0x52, 0x83, 0x41, 0x00, //GUID0x30, 0x30, //'00' Object ID0x01, //Instance Num0x01, // 00 = Demo})Name(DD00, 0)Method(WQ00, 1){DBGS("Demo Wmi Get Function:")DW2H(DD00)Return(DD00)}Method(WS00, 2){DBGS("Demo Wmi Set Function:")DW2H(ARG1)Store(ARG1, DD00)}
}
模型:
MOF最终是导出给用户调用的接口,WMI ACPI中的ASL code部分则是MOF接口的实现。如果读者反复阅读和对比MOF/ASL文件,会觉得他们很像动态链接库:MOF如同Class头文件,负责约定和导出调用接口。ASL如同DLL文件负责DLL函数的实现。至于_WDG对象,则充当了DLL的EAT(函数地址表)的中间人角色,当然EAT表中可能会有若干表项组成,_WDG对象也是如此。_WDG中单个表项具有如下数据结构:
typedef struct
{GUID guid; // GUID that names data blockunion{CHAR ObjectId[2]; // 2-character ACPI ID (Data Blocks and Methods)struct {UCHAR NotificationValue; // Byte value passed by event handler control methodUCHAR Reserved[1];} NotifyId;}USHORT InstanceCount; // Number of separate instances of data blockUSHORT Flags; // Flags
};
当所有表项结合在一起,形成一个巨大的数组,最终构成_WDG对象。而Mof编写者(无疑OEM厂商了),会从_WDG对象中取出每个表项,根据其GUID生成一个Class。
调用过程(纯个人臆测):
WMI的调用者(想象成DLL的调用者),根据MOF(想象成Class头文件)的接口去调用ASL(想象成DLL文件)实现时,ACPI会去_WDG对象的数组中查找与MOF的GUID相匹配的表项。匹配到GUID后,ACPI会取出Object ID,然后调用相应的WQxx或者WSxx方法。<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>作为一个demo程序,_WDG对象下只有一组表项,没有体现上述搜索过程。所以,我换一个表项多的例子,嗯,那就用从<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>提取出来的ThinkPad的_WDG对象:
Device (WMI2){Name (_HID, EisaId ("PNP0C14") /* Windows Management Instrumentation Device */) // _HID: Hardware IDName (_UID, 0x02) // _UID: Unique IDName (_WDG, Buffer (0x64){/* 0000 */ 0xF1, 0x24, 0xB4, 0xFC, 0x5A, 0x07, 0x0E, 0x4E, // .$..Z..N/* 0008 */ 0xBF, 0xC4, 0x62, 0xF3, 0xE7, 0x17, 0x71, 0xFA, // ..b...q./* 0010 */ 0x41, 0x37,0x01,0x01,0xE3, 0x5E, 0xBE, 0xE2, 0xDA, 0x42, 0xDB, 0x49, 0x83, 0x78, 0x1F, 0x52, 0x47, 0x38, 0x82, 0x02,0x41, 0x38,0x01,0x02,/* 0028 */ 0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45, // ..0t..HE/* 0030 */ 0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF, // .....5../* 0038 */ 0x41, 0x39,0x0A,0x05,0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E,0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1,0x41, 0x41,0x01,0x06, // .J..AA../* 0050 */ 0x21, 0x12, 0x90, 0x05, 0x66, 0xD5, 0xD1, 0x11, // !...f.../* 0058 */ 0xB2, 0xF0, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0x10, // ......)./* 0060 */ 0x42, 0x42,0x01,0x00 // BB..})
这是ThinkPad T460部分_WDG对象,其中有5个表项,最后是一个特殊表项,暂不讨论。这几个表项对应的WMI接口如下:
On Error Resume NextSet fso = CreateObject("Scripting.FileSystemObject")
Set a = fso.CreateTextFile("lenvon.log", True)
Set Service = GetObject("winmgmts:{impersonationLevel=impersonate}!root/wmi")
Rem Lenovo_PreloadLanguage - Preload Language
Set enumSet = Service.InstancesOf ("Lenovo_PreloadLanguage")
a.WriteLine("Lenovo_PreloadLanguage")
for each instance in enumSeta.WriteLine(" InstanceName=" & instance.InstanceName)a.WriteLine(" instance.CurrentSetting=" & instance.CurrentSetting)
next 'instance
Rem Lenovo_SetPreloadLanguage - Set Preload Language
Set enumSet = Service.InstancesOf ("Lenovo_SetPreloadLanguage")
a.WriteLine("Lenovo_SetPreloadLanguage")
for each instance in enumSeta.WriteLine(" InstanceName=" & instance.InstanceName)
next 'instanceRem Lenovo_PlatformSetting - Platform Setting
Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting")
a.WriteLine("Lenovo_PlatformSetting")
for each instance in enumSeta.WriteLine(" InstanceName=" & instance.InstanceName)a.WriteLine(" instance.CurrentSetting=" & instance.CurrentSetting)
next 'instance
Rem Lenovo_SetPlatformSetting - Set Platform Setting
Set enumSet = Service.InstancesOf ("Lenovo_SetPlatformSetting")
a.WriteLine("Lenovo_SetPlatformSetting")
for each instance in enumSeta.WriteLine(" InstanceName=" & instance.InstanceName)
next 'instancea.Close
Wscript.Echo "lenvon Test Completed, see lenvon.log for details"
由于ThinkPad用的是嵌入式MOF,我无法获得原始的Mof文件(如同demowmi.mof),但是运行这段vbs脚本,可以推测出mof可能的内容:
//entry 1
[WMI,
Dynamic,
Provider("WmiProv"),
Locale("MS\\0x409"),
Description("Platform Setting"),
guid("{0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45,0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF}")
]
class Lenovo_PlatformSetting
{[key, read]string InstanceName;[read] boolean Active;[WmiDataId(0x0A),read, write,Description("platfrom setting")] String CurrentSetting;
};//entry 2
[WMI,
Dynamic,
Provider("WmiProv"),
Locale("MS\\0x409"),
Description("Platform Setting"),
guid("{0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E,0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1}")
]
class Lenovo_SetPlatformSetting
{[key, read]string InstanceName;[read] boolean Active;[WmiMethodId,Description("Set platfrom setting")]
}//entry 3
[WMI,
Dynamic,
Provider("WmiProv"),
Locale("MS\\0x409"),
Description("Platform Setting"),
guid("{0xF1, 0x24, 0xB4, 0xFC, 0x5A, 0x07, 0x0E, 0x4E,0xBF, 0xC4, 0x62, 0xF3, 0xE7, 0x17, 0x71, 0xFA}")
]
class Lenovo_PreloadLanguage
{[key, read]string InstanceName;[read] boolean Active;[WmiDataId(0x01),read, write,Description("Preload Langugage")] String CurrentSetting;
}//entry 4
[WMI,
Dynamic,
Provider("WmiProv"),
Locale("MS\\0x409"),
Description("Platform Setting"),
guid("{}")
]
class Lenovo_SetPreloadLanguage
{[key, read]string InstanceName;[read] boolean Active;[WmiMethodId,Description("Set Set Preload Language")]
}
从中看出,Mof文件中含有Class和Guid,并且Class和Guid一一对应。Class以类似C++头文件的形式提供给WMI调用者;ACPI WMI映射器使用Class对应得Guid到_WDG对象表项中查找匹配的Guid(这个过程是本人结合MSDN得出的个人猜测)。
VBS中的InstanceOf:
借由Wmi Code generate生成的ACPI WMI接口测试脚本中有不少InstanceOf\instance语句:
Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting")
a.WriteLine("Lenovo_PlatformSetting")
for each instance in enumSeta.WriteLine(" InstanceName=" & instance.InstanceName)a.WriteLine(" instance.CurrentSetting=" & instance.CurrentSetting)
next 'instance
(以下为个人理解:)前面说过Mof为_WDG对象的每个表项生成一个Class。套用面向对象思维,必须创建对象(实例化Class)才能访问其成员,所以vbs脚本中
Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting")
应该就是创建Mof文件中Lenovo_PlatformSetting类对象enumSet。
VBS中"for each instance in enumSet":
如果把enumSet理解为某个Class的对象,那么instance就是类对象中的各个成员变量。需要注意的是:阅读vbs测试脚本时,我们发现它是以循环遍历的方式访问ACPI WMI对象的各个成员的,所以,我们应该认为类对象中成员变量的类型一致,因此构成一个数组。另外,特殊情况下,如果数组中只有一项,那么数组将简化为普通变量。数组的长度(即instance的数量或者说for循环的次数)在_WDG对象的表项中有指定:
//再次搬出这个结构!
typedef struct _Mapper
{GUID guid; // GUID that names data blockunion{CHAR ObjectId[2]; // 2-character ACPI ID (Data Blocks and Methods)struct {UCHAR NotificationValue; // Byte value passed by event handler control methodUCHAR Reserved[1];} NotifyId;}USHORT InstanceCount; // <----就是它指定instance的数量USHORT Flags; // Flags
}Mapper;
以ThinkPad T460P为例:
Class:Lenovo_PlatformSetting.InstanceCount=0x0A;
Class:Lenovo_SetPlarformSeting.InstanceCount=0x01;
这些都可以在_WDG对象中找到线索:
Name(_WDG,Buffer(0x64){
...
0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45, // ..0t..HE
0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF, // .....5..
0x41, 0x39,
0x0A, <----- Lenovo_PlatformSetting.InstanceCount
0x05,0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E,
0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1,
0x41, 0x41,
0x01, <----- Lenovo_PlarformSeting.InstanceCount=0x01
0x06,
...
})
虽然,类对象中数组(即前面说的成员变量)的长度随着InstanceCount的值可以固定下来,但是数组项的类型却没有指定。简单的可能是UINT类型数组,复杂的可能是Buffer或者Package类型数组(再次注意,我这里用的是数组)。仍以ThinkPad T460P Lenovo_PlarformSeting类为例,Lenovo_PlarformSeting类的接口函数为Lenovo_PlarformSeting,在WMI ACPI中的实现为:Method (WQA9, 1, NotSerialized)
Method (WQA9, 1, NotSerialized)
{Acquire (\_SB.WMI1.MWMI, 0xFFFF)If ((\WMIS (0x09, Arg0) != 0x00)){Release (\_SB.WMI1.MWMI)Return ("")}Local0 = DerefOf (ITEM [\WITM])Local1 = DerefOf (Local0 [0x00])Local2 = DerefOf (Local0 [0x01])Concatenate (Local2, ",", Local6)Local3 = DerefOf (VSEL [Local1])Concatenate (Local6, DerefOf (Local3 [\WSEL]), Local7)Release (\_SB.WMI1.MWMI)Return (Local7)
}
Method WQA9根据唯一的参数Arg0,从ITEM对象中获取数值。至于ITEM对象,它长这样:
Name (ITEM, Package (0x06)
{Package (0x02){0x00, "InhibitEnteringThinkPadSetup"}, Package (0x02){0x00, "MTMSerialConcatenation"}, Package (0x02){0x00, "SwapProductName"}, Package (0x02){0x00, "ComputraceMsgDisable"}, Package (0x02){0x00, "CpuDebugEnable"}, Package (0x02){0x00, "PasswordAfterBootDeviceList"}
})
额,这个结构有点复杂,看不懂!我们慢慢梳理以下:
1.根据ACPI语法,Package可以认为结构体,也可以认为是数组。把最内层的Package (0x2)当作一个只有2个成员变量的结构体:
typedef struct _SettingEntry
{UINT32 SettingVal;char* SettingName;
}SettingEntry;
外层Package理所当然的可以认为是这样的结构体数组,数组项共6项(虽然_WDG指定该数组项应该共有10项,但实际只有6项,而且访问超出部分也没发生异常!):
SettingEntry T460P[] = {{0x00,"InhibitEnteringThinkPadSetup"},{0x00,"MTMSerialConcatenation"},{0x00,"SwapProductName"},{0x00,"ComputraceMsgDisable"},{0x00,"CpuDebugEnable"},{0x00,"PasswordAfterBootDeviceList"}};
回到我们的标题:VBS中"for each instance in enumSet",vbs每轮循环,其实就是访问T460P[n]。对于有些OEM厂商会进行复杂的访问,比如仅仅访问T460P[n].SettingVal或者T460P[n].SettingName。这时就会在ASL code中看到大量的DerefOf语句。
_WDG中特殊的Flag:
每个_WDG对象的表项中都有一个(唯一的一个)特殊的表项,它具有特殊的Flag,其值为0x00。这个表项只有一个作用:提供buffer存储编译后的Mof,因此这个表项本身并不会出现在Mof描述文件中。但它是构成调用ACPI WMI接口过程中重要的一步----提供具体的映射:OS进入到_WDG对象后,查找Mapper.Flag==0的项,找到后将它作为Mof(Mof的作用,请移步前面调用过程章节),进行ACPI Method跳转。