关于WMI ACPI,建议先通读:1.<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>了解WMI ACPI能提供什么后,再参考MSDN 2.
本文应该是<解密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.ASL
Device(DWMI)
{
// PNP0C14 is PNP ID assigned to WMI mapper
Name(_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, //GUID
0x30, 0x30, //'00' Object ID
0x01, //Instance Num
0x01, // 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所描述的Class的实现。如果读者反复阅读和对比MOF/ASL文件,会觉得他们很像动态链接库:MOF文件如同Class头文件,负责约定和导出调用接口。ASL如同DLL文件负责DLL函数的实现。至于_WDG对象,则充当了DLL的EAT(函数地址表)的中间人角色(其实还依赖于_WDG对象中嵌入式MOF文件,后面会提到),当然EAT表中可能会有若干表项组成,_WDG对象也是如此。_WDG中单个表项具有如下数据结构:
typedef struct _Mapper
{
GUID guid; // GUID that names data block
union
{
CHAR ObjectId[2]; // 2-character ACPI ID (Data Blocks and Methods)
struct
{
UCHAR NotificationValue; // Byte value passed by event handler control method
UCHAR Reserved[1];
} NotifyId;
}
USHORT InstanceCount; // Number of separate instances of data block
USHORT Flags; // Flags
}Mapper;
当所有表项结合在一起,形成一个巨大的Mapper[N]数组,这个数组最终构成_WDG对象。而Mof资源文件的编写者(无疑是OEM厂商了),会为_WDG对象中每个Mapper[i]制作Class。每个Class都有Mapper[i].guid对应。
WMI的调用者(想象成DLL的调用者),根据MOF资源文件(想象成Class头文件)描述的WMI接口,以ClassName的形式去调用ASL code的(想象成DLL文件)时,ACPI.sys会到ACPI命名空间中先查找_WDG对象。
找到_WDG对象后,遍历其Mapper[N]数组,并定位到Mapper[i].Flags==0的数组项,该数组项指向一块buffer,buffer中包含编译后的MOF文件,MSDN称之为嵌入式MOF资源。
ACPI.sys从嵌入式Mof资源中查找匹配的ClassName(因为上层使用Class调用WMI)。大家不妨回忆前面Mof资源描述文件中含有guid和ClassName
获得ClassName后,进一步可以获得接口名的guid,然后回到_WDG的Mapper[N]数组中,根据Mapper[i].guid查找与调用者GUID相匹配的数组项。
匹配到GUID后,ACPI.sys会取出Mapper[i].ObjectID成员,成员中包含xx值,ACPI.sys将WQ/WS和xx拼接,形成WQxx或者WSxx,然后去ACPI命名空间调用相应的WQxx或者WSxx方法。
<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>作为一个demo程序,_WDG对象的Mapper[N]数组只有一个数组项,没有体现上述搜索过程。所以,我换一个N>1的例子,嗯,那就用从<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>提取出来的ThinkPad的_WDG对象:
Device (WMI2)
{
Name (_HID, EisaId ("PNP0C14") /* Windows Management Instrumentation Device */) // _HID: Hardware ID
Name (_UID, 0x02) // _UID: Unique ID
Name (_WDG, Buffer (0x64)
{
//==============Mapper[0]==============
0xF1, 0x24, 0xB4, 0xFC, 0x5A, 0x07, 0x0E, 0x4E,
0xBF, 0xC4, 0x62, 0xF3, 0xE7, 0x17, 0x71, 0xFA,
0x41, 0x37,
0x01,
0x01,
//==============Mapper[1]==============
0xE3, 0x5E, 0xBE, 0xE2, 0xDA, 0x42, 0xDB, 0x49,
0x83, 0x78, 0x1F, 0x52, 0x47, 0x38, 0x82, 0x02,
0x41, 0x38,
0x01,
0x02,
//==============Mapper[2]==============
0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45,
0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF,
0x41, 0x39,
0x0A,
0x05,
//==============Mapper[3]==============
0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E,
0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1,
0x41, 0x41,
0x01,
0x06,
//==============Mapper[4]==============
0x21, 0x12, 0x90, 0x05, 0x66, 0xD5, 0xD1, 0x11,
0xB2, 0xF0, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0x10,
0x42, 0x42,
0x01,
0x00
})
这是ThinkPad T460部分_WDG对象,其中有5个数组项,最后是一个特殊数组项,暂不讨论。其他几个数组项对应的WMI接口如下:
On Error Resume Next
Set 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 enumSet
a.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 enumSet
a.WriteLine(" InstanceName=" & instance.InstanceName)
next 'instance
Rem Lenovo_PlatformSetting - Platform Setting
Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting")
a.WriteLine("Lenovo_PlatformSetting")
for each instance in enumSet
a.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 enumSet
a.WriteLine(" InstanceName=" & instance.InstanceName)
next 'instance
a.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")
]
}
借由Wmi Code generate生成的ACPI WMI接口测试脚本中有不少InstanceOf\instance语句:
Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting")
a.WriteLine("Lenovo_PlatformSetting")
for each instance in enumSet
a.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。
如果把enumSet理解为某个Class的对象,那么vbs脚本中的instance就是Mof资源文件中描述的Class的各个成员变量。需要注意的是:阅读vbs测试脚本时,我们发现它是以循环遍历的方式访问类对象的各个成员的,所以,我们应该认为类对象中成员变量的类型一致,因此可以构成一个数组。另外,特殊情况下,如果数组中只有一项,那么数组将简化为普通变量。为了与_WDG对象中的Mapper[N]数组区分,我们用Item数组表示Class中类型一致的成员变量。Item数组的长度(即instance的数量或者说for循环的次数)在_WDG对象的Mapper[i].instanceCount中有指定:
//再次搬出这个结构!
typedef struct _Mapper
{
GUID guid; // GUID that names data block
union
{
CHAR ObjectId[2]; // 2-character ACPI ID (Data Blocks and Methods)
struct
{
UCHAR NotificationValue; // Byte value passed by event handler control method
UCHAR Reserved[1];
} NotifyId;
}
USHORT InstanceCount; // <----就是它指定instance的数量
USHORT Flags; // Flags
}Mapper;
以ThinkPad T460P为例:
Class:Lenovo_PlatformSetting.InstanceCount=0x0A;
Class:Lenovo_SetPlarformSeting.InstanceCount=0x01;
这些都可以在_WDG对象Mapper[i].InstanceCount中找到线索:
Device (WMI2)
{
Name (_HID, EisaId ("PNP0C14") /* Windows Management Instrumentation Device */) // _HID: Hardware ID
Name (_UID, 0x02) // _UID: Unique ID
Name (_WDG, Buffer (0x64)
{
...
//==============Mapper[2]==============
0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45,
0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF,
0x41, 0x39,
0x0A, <----- Lenovo_PlatformSetting.InstanceCount
0x05,
//==============Mapper[3]==============
0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E,
0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1,
0x41, 0x41,
0x01,
0x06, <----- Lenovo_PlarformSeting.InstanceCount=0x01
虽然,Item数组的长度随着Mapper[i].InstanceCount的值可以固定下来,但是Item数组的数组项类型却没有指定。简单的可能是UINT类型的Item数组,复杂的可能是Buffer或者Package类型的Item(再次注意,我这里用的是Item数组)。仍以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,从ACPI命名空间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理所当然可以认为是这样的结构体类型的Item数组,数组项共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对象Mapper[N]数组中都有一个(唯一的一个)特殊的数组项,它具有特殊的Flags值,其值为0x00。这个特殊的Mapper[i]数组项只有一个作用:提供buffer,存储编译后的Mof资源文件,因此这个表项本身并不会出现在Mof资源描述文件中。但它是构成调用ACPI WMI接口过程中重要的一步,没有它,无法实现Class名到ACPI Method的转换。