WinDBG 查看 ACPI Table 的操作

最近根据【参考1】研究了一下通过 WinDBG 查看 ACPI Table 的方法,需要特别注意的是,必须按照顺序执行才能取得需要查看的信息。

1.运行!rsdt命令,感觉是让 WinDBG 进行查找的动作

||0:6: kd> !rsdt
Sorry: Unable to get ACPI!AcpiInformation.
Searching for RSDP.*************************************************************************
***                                                                   ***
***                                                                   ***
***    Either you specified an unqualified symbol, or your debugger   ***
***    doesn't have full symbol information.  Unqualified symbol      ***
***    resolution is turned off by default. Please either specify a   ***
***    fully qualified symbol module!symbolname, or enable resolution ***
***    of unqualified symbols by typing ".symopt- 100". Note that     ***
***    enabling unqualified symbol resolution with network symbol     ***
***    server shares in the symbol path may cause the debugger to     ***
***    appear to hang for long periods of time when an incorrect      ***
***    symbol name is typed or the network symbol server is down.     ***
***                                                                   ***
***    For some commands to work properly, your symbol path           ***
***    must point to .pdb files that have full type information.      ***
***                                                                   ***
***    Certain .pdb files (such as the public OS symbols) do not      ***
***    contain the required information.  Contact the group that      ***
***    provided you with these symbols if you need this command to    ***
***    work.                                                          ***
***                                                                   ***
***    Type referenced: hal!_RSDT_32                                  ***
***                                                                   ***
*************************************************************************
.........................................0x0effe0: Read 0x000020 of 0x000024 bytes
Could not locate the RSDT pointer

2. !acpicache 命令,显示当前系统 Cache 的ACPI Table

||0:6: kd> !acpicache
Dumping cached ACPI tables...
  XSDT @(fffff7e200004018) Rev: 0x1 Len: 0x000104 TableID: CFL-ULT
  DBGP @(fffff7e200005018) Rev: 0x1 Len: 0x000034 TableID: CFL-ULT
  MCFG @(fffff7e200006018) Rev: 0x1 Len: 0x00003c TableID: CFL-ULT
  FACP @(fffff7e200050018) Rev: 0x6 Len: 0x000114 TableID: CFL-ULT
  APIC @(fffff7e200051018) Rev: 0x3 Len: 0x00012c TableID: CFL-ULT
  BOOT @(fffff7e200054018) Rev: 0x1 Len: 0x000028 TableID: TIANO   
  DMAR @(fffff7e200055018) Rev: 0x1 Len: 0x0000a8 TableID: CFL     
  HPET @(fffff7e20007e018) Rev: 0x1 Len: 0x000038 TableID: CFL-ULT
  FPDT @(ffffe3020d0fe9e8) Rev: 0x1 Len: 0x000044 TableID: CFL-ULT
  DSDT @(ffffe3020d400018) Rev: 0x2 Len: 0x045806 TableID: CFL-ULT
  SSDT @(ffffe3020d0a7018) Rev: 0x2 Len: 0x001b1c TableID: CpuSsdt
  SSDT @(ffffe3020f31f018) Rev: 0x2 Len: 0x0031c6 TableID: SaSsdt 
  SSDT @(ffffe3020f322208) Rev: 0x2 Len: 0x00045a TableID: Tpm2Tabl
  SSDT @(ffffe3020f322698) Rev: 0x2 Len: 0x000046 TableID: MeSsdt 
  TPM2 @(ffffe3020f322708) Rev: 0x3 Len: 0x000034 TableID: CFL     
  UEFI @(ffffe3020f322768) Rev: 0x1 Len: 0x000042 TableID: CFL-ULT
  SSDM @(ffffe3020f3227d8) Rev: 0x1 Len: 0x000055 TableID: 
  SSDT @(ffffe3020f322858) Rev: 0x2 Len: 0x000538 TableID: PerfTune
  ECDT @(ffffe3020f322db8) Rev: 0x1 Len: 0x000069 TableID: CFL-ULT
  SSDT @(ffffe3020d05e018) Rev: 0x2 Len: 0x002f5c TableID: CnlU_Rvp
  SSDT @(ffffe3020e7fa978) Rev: 0x2 Len: 0x000fae TableID: Ther_Rvp
  SSDT @(ffffe3020d05b018) Rev: 0x2 Len: 0x0029c5 TableID: xh_whld4
  SSDT @(ffffe3020d0a5018) Rev: 0x2 Len: 0x001b67 TableID: UsbCTabl
  LPIT @(ffffe3020d0a6ba8) Rev: 0x1 Len: 0x00005c TableID: CFL-ULT
  WSMT @(ffffe3020d0a6c38) Rev: 0x1 Len: 0x000028 TableID: CFL-ULT
  SSDT @(ffffe3020d058018) Rev: 0x2 Len: 0x0027de TableID: PtidDevc
  SSDT @(ffffe3020d0a3018) Rev: 0x2 Len: 0x00149f TableID: TbtTypeC
  DBG2 @(ffffe3020d0a4548) Rev: 000 Len: 0x000054 TableID: CFL-ULT
  NHLT @(ffffe3020d0a1018) Rev: 000 Len: 0x001783 TableID: CFL     
  BGRT @(ffffe3020d0a2908) Rev: 0x1 Len: 0x000038 TableID: CFL-ULT

3. !fadt 命令,显示 FADT 的信息

||0:6: kd> !fadt 
FADT -- fffff8036da7aee0
FADT revision is 6, which is not understood by this debugger
HEADER - fffff8036da7aee0
  Signature:               FACP
  Length:                  0x00000114
  Revision:                0x06
  Checksum:                0xa1
  OEMID:                   INTEL 
  OEMTableID:              CFL-ULT
  OEMRevision:             0x20170001
  CreatorID:               INTL
  CreatorRev:              0x20160422
FADT - BODY - fffff8036da7af04
  FACS:                    0x8ca12000
  DSDT:                    0x8cbb6000
  Int Model:               Dual PIC
  SCI Vector:              0x009
  SMI Port:                0x000000b2
  ACPI On Value:           0x0f0
  ACPI Off Value:          0x0f1
  SMI CMD For S4 State:    0x0f2
  PM1A Event Block:        0x00001800
  PM1B Event Block:        0x00000000
  PM1 Event Length:        0x004
  PM1A Control Block:      0x00001804
  PM1B Control Block:      0x00000000
  PM1 Control Length:      0x002
  PM2 Control Block:       0x00001850
  PM2 Control Length:      0x001
  PM Timer Block:          0x00001808
  PM Timer Length:         0x004
  GP0 Block:               0x00001860
  GP0 Length:              0x020
  GP1 Block:               0x00000000
  GP1 Length:              0x00000000
  GP1 Base:                0x00000010
  C2 Latency:              0x00065
  C3 Latency:              0x003e9
  Memory Flush Size:       0x00000
  Memory Flush Stride:     0x00000
  Duty Cycle Index:        0x001
  Duty Cycle Index Width:  0x003
  Day Alarm Index:         0x00d
  Month Alarm Index:       0x000
  Century byte (CMOS):     0x032
  Boot Architecture:       0x0001
    The machine does not contain a legacy i8042
  Flags:                   0x0020c4b5
    WRITEBACKINVALIDATE_WORKS .................. SET
    WRITEBACKINVALIDATE_DOESNT_INVALIDATE ...... CLEAR
    SYSTEM_SUPPORTS_C1 ......................... SET
    P_LVL2_UP_ONLY ............................. CLEAR
    PWR_BUTTON_GENERIC ......................... SET
    SLEEP_BUTTON_GENERIC ....................... SET
    RTC_WAKE_GENERIC ........................... CLEAR
    RTC_WAKE_FROM_S4 ........................... SET
    TMR_VAL_EXT ................................ CLEAR
    DCK_CAP .................................... CLEAR
    RESET_CAP .................................. SET
      RESET_VALUE: 6
      RESET_REG: System I/O - 0000000000000cf9
    SEALED_CASE_CAP ............................ CLEAR
    HEADLESS_CAP ............................... CLEAR
    CPU_SW_SLP_CAP ............................. CLEAR
    PCI_EXP_WAK ................................ SET
    USE_PLATFORM_CLOCK ......................... SET
    RTC_WAKE_VALID_FROM_S4 ..................... CLEAR
    REMOTE_POWER_ON_CAPABLE .................... CLEAR
    FADT_FORCE_CLUSTERED_APIC_MODE ............. CLEAR
    FADT_FORCE_APIC_PHYSICAL_DESTINATION_MODE .. CLEAR
    ACPI_HARDWARE_NOT_PRESENT .................. CLEAR
    AOAC_CAPABLE_PLATFORM ...................... SET

上述实验平台为 WHL HDK,有兴趣的朋友可以试试。

参考:

1. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/other-acpi-debugging-extensions 

WinDBG访问硬件

前面对于 RU.EFI 的研究告诉我们对于 X86 来说,访问硬件信息需要的基本操作有:

1. PCI 信息的访问

2. 访问IO Port 直接访问

3. IO Port Index/Data 方式的访问

4. Memory 的访问

5. Memory Index/Data 方式的访问

6. MSR 的访问

掌握了上述的访问方法就可以访问到 X86 上的全部空间和寄存器。下面逐项介绍在 WinDBG 中的访问方法。因为大多数情况下,WinDBG 是用于调试 Windows 软件本身而不是硬件,因此很多操作都是来自个人总结如果有错误或者遗漏,恳请及时指出。

1.PCI的访问

a. !pcitree 命令可以用来查看当前系统中的PCI总线和设备信息【参考1】

实例如下

0: kd> !pcitree
SYMSRV:  BYINDEX: 0x5
         C:\ProgramData\Dbg\sym
         pci.pdb
         96732E11A7284081C982C9A015D949A81
SYMSRV:  UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb - path not found
SYMSRV:  UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pd_ - path not found
SYMSRV:  UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\file.ptr - path not found
SYMSRV:  RESULT: 0x80070003
SYMSRV:  BYINDEX: 0x6
         C:\ProgramData\Dbg\sym*https://msdl.microsoft.com/download/symbols
         pci.pdb
         96732E11A7284081C982C9A015D949A81
SYMSRV:  UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb - path not found
SYMSRV:  UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pd_ - path not found
SYMSRV:  UNC: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\file.ptr - path not found
SYMSRV:  HTTPGET: /download/symbols/pci.pdb/96732E11A7284081C982C9A015D949A81/pci.pdb
SYMSRV:  HttpQueryInfo: 801900c8 - HTTP_STATUS_OK
SYMSRV:  pci.pdb from https://msdl.microsoft.com/download/symbols:copied      
SYMSRV:  PATH: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb
SYMSRV:  RESULT: 0x00000000
DBGHELP: C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb cached to C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb
DBGHELP: pci - public symbols  
        C:\ProgramData\Dbg\sym\pci.pdb\96732E11A7284081C982C9A015D949A81\pci.pdb
Bus 0x0 (FDO Ext ffffb106414d5a20)
  (d=0,  f=0) 80863e34 devext 0xffffb1063bfef1b0 devstack 0xffffb1063bfef060 0600 Bridge/HOST to PCI
  (d=2,  f=0) 80863ea0 devext 0xffffb1063bfee1b0 devstack 0xffffb1063bfee060 0300 Display Controller/VGA
  (d=8,  f=0) 80861911 devext 0xffffb1063bfed1b0 devstack 0xffffb1063bfed060 0880 Base System Device/'Other' base system device
  (d=12, f=0) 80869df9 devext 0xffffb1063bfec1b0 devstack 0xffffb1063bfec060 1180 Unknown Base Class/Unknown Sub Class
  (d=13, f=0) 80869dfc devext 0xffffb1063bfeb1b0 devstack 0xffffb1063bfeb060 0700 Simple Serial Communications Controller/Serial Port
  (d=14, f=0) 80869ded devext 0xffffb1063bfea1b0 devstack 0xffffb1063bfea060 0c03 Serial Bus Controller/USB
  (d=14, f=2) 80869def devext 0xffffb1063bfe91b0 devstack 0xffffb1063bfe9060 0500 Memory Controller/RAM
  (d=15, f=0) 80869de8 devext 0xffffb1063b4751b0 devstack 0xffffb1063b475060 0c80 Serial Bus Controller/Unknown Sub Class
  (d=16, f=0) 80869de0 devext 0xffffb1063b4671b0 devstack 0xffffb1063b467060 0780 Simple Serial Communications Controller/'Other'
  (d=1d, f=0) 80869db0 devext 0xffffb1063b4691b0 devstack 0xffffb1063b469060 0604 Bridge/PCI to PCI
  Bus 0x1 (FDO Ext ffffb106414e1a20)
    (d=0,  f=0) 144da802 devext 0xffffb106415d81b0 devstack 0xffffb106415d8060 0108 Mass Storage Controller/Unknown Sub Class
  (d=1f, f=0) 80869d84 devext 0xffffb1063b47b740 devstack 0xffffb1063b47b5f0 0601 Bridge/PCI to ISA
  (d=1f, f=3) 80869dc8 devext 0xffffb106414d01b0 devstack 0xffffb106414d0060 0401 Multimedia Device/Audio
  (d=1f, f=4) 80869da3 devext 0xffffb106415c21b0 devstack 0xffffb106415c2060 0c05 Serial Bus Controller/Unknown Sub Class
  (d=1f, f=5) 80869da4 devext 0xffffb106415c91b0 devstack 0xffffb106415c9060 0c80 Serial Bus Controller/Unknown Sub Class
Total PCI Root busses processed = 1
Total PCI Segments processed = 1
如果运行时出现类似下面的提示,需要使用.reload pci.sys 加载一下对应的 symbol
0: kd> !pcitree
Error retrieving address of PciFdoExtensionListHead

b. !pci 命令,参数比较多,这里直接给出常用的命令【参考2】

!pci 2 ff  //列出当前系统中全部 PCI 设备

0: kd> !pci 2 ff  
PCI Segment 0 Bus 0
00:0  8086:3e34.0b  Cmd[0006:.mb...]  Sts[0090:c....]  Intel Host Bridge  SubID:8086:7270
02:0  8086:3ea0.00  Cmd[0400:......]  Sts[0010:c....]  Intel VGA Compatible Controller  SubID:8086:2212
08:0  8086:1911.00  Cmd[0000:......]  Sts[0010:c....]  Intel Other System Peripheral  SubID:8086:7270
12:0  8086:9df9.30  Cmd[0002:.m....]  Sts[0010:c....]  Intel Other Signal Processing Controller  SubID:8086:7270
13:0  8086:9dfc.30  Cmd[0000:......]  Sts[0010:c....]  Intel Serial Controller  SubID:8086:7270
14:0  8086:9ded.30  Cmd[0406:.mb...]  Sts[0290:c....]  Intel Class:c:3:30  SubID:8086:7270
14:2  8086:9def.30  Cmd[0006:.mb...]  Sts[0010:c....]  Intel RAM Controller
15:0  8086:9de8.30  Cmd[0400:......]  Sts[0010:c....]  Intel Class:c:80:0  SubID:8086:7270
16:0  8086:9de0.30  Cmd[0406:.mb...]  Sts[0010:c....]  Intel Other Communications Controller  SubID:8086:7270
1d:0  8086:9db0.f0  Cmd[0407:imb...]  Sts[0010:c....]  Intel PCI-PCI Bridge 0->0x1-0x1
1f:0  8086:9d84.30  Cmd[0407:imb...]  Sts[0000:.....]  Intel ISA Bridge  SubID:8086:7270
1f:3  8086:9dc8.30  Cmd[0406:.mb...]  Sts[0010:c....]  Intel Audio Device  SubID:8086:7270
1f:4  8086:9da3.30  Cmd[0000:......]  Sts[0280:.....]  Intel SMBus Controller  SubID:8086:7270
1f:5  8086:9da4.30  Cmd[0402:.m....]  Sts[0000:.....]  Intel Class:c:80:0  SubID:8086:7270
PCI Segment 0 Bus 0x1
00:0  144d:a802.01  Cmd[0406:.mb...]  Sts[0010:c....]  Class:1:8:2  SubID:144d:a801

!pci f 0 2 0 0 0x200 //查看Bus 0, Device 2,function 0 上从 0 到 0x200的寄存器

0: kd> !pci f 0 2 0 0 0x200
PCI Segment 0 Bus 0
02:0  8086:3ea0.00  Cmd[0400:......]  Sts[0010:c....]  Intel VGA Compatible Controller  SubID:8086:2212
      cf8:80001000  IntPin:1  IntLine:0  Rom:0  cis:0  cap:40
      MEM[0]:cf000004  MPF[2]:d000000c  IO[4]:ffc1       
      00000000:  3ea08086 00100400 03000000 00000000
      00000010:  cf000004 00000000 d000000c 00000000
      00000020:  0000ffc1 00000000 00000000 22128086
      00000030:  00000000 00000040 00000000 00000100
      00000040:  010c7009 7a6160b1 9615808c 00000000
      00000050:  000001c1 00008031 00000000 8e000001
      00000060:  00010000 00000000 00000000 00000000
      00000070:  0092ac10 10008000 00000000 00000000
      00000080:  00000000 00000000 00000000 00000000
      00000090:  00000000 00000000 00000000 00000000
      000000a0:  00000000 00000000 00000000 0000d005
      000000b0:  fee00358 00000000 00000000 00000000
      000000c0:  00000000 00000000 00000000 00000000
      000000d0:  00220001 00000003 00000000 00000000
      000000e0:  00000000 00000000 00008000 00000000
      000000f0:  00000000 00000000 00000000 8cb33018
      00000100:  2001001b 00001400 00000000 00000000
      00000110:  00000000 00000000 00000000 00000000
      00000120:  00000000 00000000 00000000 00000000
      00000130:  00000000 00000000 00000000 00000000
      00000140:  00000000 00000000 00000000 00000000
      00000150:  00000000 00000000 00000000 00000000
      00000160:  00000000 00000000 00000000 00000000
      00000170:  00000000 00000000 00000000 00000000
      00000180:  00000000 00000000 00000000 00000000
      00000190:  00000000 00000000 00000000 00000000
      000001a0:  00000000 00000000 00000000 00000000
      000001b0:  00000000 00000000 00000000 00000000
      000001c0:  00000000 00000000 00000000 00000000
      000001d0:  00000000 00000000 00000000 00000000
      000001e0:  00000000 00000000 00000000 00000000
      000001f0:  00000000 00000000 00000000 00000000
      00000200:  0001000f

2. IO Port 的访问

IO port to read and write【参考3】

Function Command Description / mnemonic
Read IO port ib, iw, id Input from port (byte, word, double word)
Write IO port ob, ow, od Output to port (byte, word, double word)

实例如下 :访问70/71 上面的 CMOS,这个位置是当前的Second 所以过了一段时间会有变化

0: kd> !ob 70 0
0: kd> !ib 71
00000071: 000000000059
0: kd> !ob 70 0
0: kd> !ib 71
00000071: 000000000004

3. Memory 的访问

对于我们来说关注点通常只是物理内存,可以通过 !d* 命令来访问到【参考1】

实例如下 :!db 0xFFFFFF00 l 0x100   //按照Byte访问0xFFFFFF00,长度为 0x100 字节

目标机上同时使用 RW Everything 查看,可以看到结果相同:

4. MSR 的访问

可以通过 rdmsr 和  wrmsr 来实现,例如:

0: kd> rdmsr 0x2ff
msr[2ff] = 00000000`00000c06

此外,CPUID也可以被看作是一种MSR 可以用 CPUID 指令进行访问【参考4】:

0: kd> !cpuid
CP  F/M/S  Manufacturer     MHz
 0  6,142,11 GenuineIntel    1992
 1  6,142,11 GenuineIntel    1992
 2  6,142,11 GenuineIntel    1992
 3  6,142,11 GenuineIntel    1992
 4  6,142,11 GenuineIntel    1992
 5  6,142,11 GenuineIntel    1992
 6  6,142,11 GenuineIntel    1992
 7  6,142,11 GenuineIntel    1992

不过看起来能够提供的信息还是比较有限的。

参考:

  1. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-pcitree
  2. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-pci
  3. https://www.cnblogs.com/jiangxueqiao/p/7418195.html 
  4. https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-cpuid

Step to UEFI (209)subst 命令

Subst 命令是一个很老的 DOS命令,通过它可以将一个目录映射为一个盘符。

例如,我本机当前只有一个盘符c:

通过 subst d: 201903, 即可将 buildbs\201903 这个目录映射为 D:

再次查看会多出一个 d: 盘符

对于我们BIOS编译来说,有时候会发生BUILD Debug版本的时候 Code 爆掉了,超过容量大小的限制。其中很大原因是目录过长。代码中很多DEBUG宏使用了 __FILE__ 定义,在编译中会展开为当前文件的完整路径,凭空要多出十几个字节。累积下来数量可观。为此,可以将你的编路径映射为一个盘符然后进入盘符编译。这样编译过程中路径是 D: 这样可以减少尺寸:

使用完毕后,可以使用 subst 盘符 /d 来取消映射。

台湾宝工(Pro’sKit)真的是一家玩具公司了

前一段时间参加创客嘉年华,惊奇的发现他们在这个活动上有一个摊位,更惊奇的是他们的摊位主打竟然是玩具。然后和他们聊了两句发现他们这些年转型向玩具市场进军。

转过天,我修理东西,拿出了他们家生产的螺丝刀,发现竟然生锈了。

基本上每一个刀头都没有幸免…….
这套工具用的很少
外包装还是完好的

除了这一套螺丝刀,我还有一个游标卡尺也是他们家的

检查发现也是生锈的

工具一直存放在家里,看起他们他们用的材质是很有问题的。如果非要加一个条件的话那就是:宝工产品不适合南方地区使用(作为东北人,过了山海关都算是南方)。

Step to UEFI (208)谁改动了我的文件头?

前面介绍了如何使用汇编语言直接编写UEFI Application,这里我偶然发现一个问题:用汇编生成的EFI 文件中,DOS 头部看起来多了一些东西。比如十六进制查看之前的 NasmUEFI.EFI 文件:

对比 Hello.EFI:

可以看到有些内容被清空为0x00. 首先怀疑的是编译过程中有工具来完成这个动作。于是,重新编译 Hello.EFI 观察到在末期有下面的操作:

Generating code
Finished generating code
        "GenFw" -e UEFI_APPLICATION -o c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\Hello\Hello\DEBUG\Hello.efi c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\Hello\Hello\DEBUG\Hello.dll

这个操作输入的是hello.dll 输出为 hello.efi,观察发现DLL 中还存在一些字符串,到了EFI 中会消失。接下来在窗口中直接运行 GenFw 来从 DLL生成EFI文件,最终确认是这个工具来完成移除多余的字符工作的,准确的说只是用0xFF覆盖,并没有引起文件长度的变化。

接下来查看 GenFW 的代码, \BaseTools\Source\C\GenFw\GenFw.c 其中下面就是我们需要找到的位置:

//
  // Zero all unused fields of the DOS header
  //
  if (DosHdr != NULL) {
    memcpy (&BackupDosHdr, DosHdr, sizeof (EFI_IMAGE_DOS_HEADER));
    memset (DosHdr, 0, sizeof (EFI_IMAGE_DOS_HEADER));
    DosHdr->e_magic  = BackupDosHdr.e_magic;
    DosHdr->e_lfanew = BackupDosHdr.e_lfanew;
    for (Index = sizeof (EFI_IMAGE_DOS_HEADER); Index < (UINT32 ) DosHdr->e_lfanew; Index++) {
      FileBuffer[Index] = (UINT8) DosHdr->e_cp;
    }
  }

特别提醒:目前提供的编译辅助工具都是 X86 版本的,因此需要在 x86 Native Tools Command Prompt 窗口下编译,如果使用X64会出现编译报错。

本文只是为了简单试验,并没有考虑一些特殊情况。针对上面的代码进行修改,插入一句话即可:

strcpy(&FileBuffer[sizeof (EFI_IMAGE_DOS_HEADER)],"www.lab-z.com");

这样,再次使用 GenFw 来进行DLL到EFI的转换,可以看到 EFI 文件中DOS Header后面被插入了我们期望的字符串。

通过上述的方法可以在 EFI 文件中自动加入自定义的字符串,通过这样的方法可以制作一些特殊的 EFI 文件,比如只能在特别BIOS上运行的 EFI文件。

Step to UEFI (207)汇编语言编写UEFI Application

使用汇编语言来编写UEFI Application 完全没有问题,理论上编写 DXE Driver 也没有问题。最近抽空研究了一下这个问题。

从网上的资料来说,可以使用 FASM来完成这个工作,在http://x86asm.net/articles/uefi-programming-first-steps/ 可以看到一篇名为 “UEFI Programming – First Steps”的文章。但是经过我的实验使用 FASM 最出来的EFI 在文件头部上(PE Header)就存在很大的问题,比如: Image Size 给出来的不正确,还有 BaseImage 给出的不正确。有可能是我没有使用正确的参数导致的,但是看起来研究会很麻烦,并且最终能否成功严重存疑于是放弃了。

接下来研究如何使用 Nasm 来实现编写一个 UEFI Application。在 https://hackerpulp.com/os/os-development-windows-1-building-uefi-applications-nasm/https://github.com/BrianOtto/nasm-uefi 找到了一个例子,经过修改可以正常工作。

1.原文是编写一个 BootLoader,所以最后完成显示之后Hang 住即可,这里我们需要正常返回,最终ASM 代码如下:

; Copyright 2018-2019 Brian Otto @ https://hackerpulp.com
; 
; Permission to use, copy, modify, and/or distribute this software for any 
; purpose with or without fee is hereby granted, provided that the above 
; copyright notice and this permission notice appear in all copies.
; 
; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
; AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 
; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
; PERFORMANCE OF THIS SOFTWARE.

; generate 64-bit code
bits 64

; contains the code that will run
section .text

; allows the linker to see this symbol
global _start

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001729
struc EFI_TABLE_HEADER
    .Signature    RESQ 1
    .Revision     RESD 1
    .HeaderSize   RESD 1
    .CRC32        RESD 1
    .Reserved     RESD 1
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
struc EFI_SYSTEM_TABLE
    .Hdr                  RESB EFI_TABLE_HEADER_size
    .FirmwareVendor       RESQ 1
    .FirmwareRevision     RESD 1
    .ConsoleInHandle      RESQ 1
    .ConIn                RESQ 1
    .ConsoleOutHandle     RESQ 1
    .ConOut               RESQ 1
    .StandardErrorHandle  RESQ 1
    .StdErr               RESQ 1
    .RuntimeServices      RESQ 1
    .BootServices         RESQ 1
    .NumberOfTableEntries RESQ 1
    .ConfigurationTable   RESQ 1
endstruc

; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1016807
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    .Reset             RESQ 1
    .OutputString      RESQ 1
    .TestString	       RESQ 1
    .QueryMode	       RESQ 1
    .SetMode	       RESQ 1
    .SetAttribute      RESQ 1
    .ClearScreen       RESQ 1
    .SetCursorPosition RESQ 1
    .EnableCursor      RESQ 1
    .Mode              RESQ 1
endstruc

_start:

    push rax    ;ConOut requires a push here. I don't know why

    ; reserve space for 4 arguments
    sub rsp, 4 * 8

    ; rdx points to the EFI_SYSTEM_TABLE structure
    ; which is the 2nd argument passed to us by the UEFI firmware
    ; adding 64 causes rcx to point to EFI_SYSTEM_TABLE.ConOut
    mov rcx, [rdx + 64]

    ; load the address of our string into rdx
    lea rdx, [rel strHello]

    ; EFI_SYSTEM_TABLE.ConOut points to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    ; call OutputString on the value in rdx
    call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
    
    add rsp, 4 * 8
    pop rax     
    ret

codesize equ $ - $$

; contains nothing - but it is required by UEFI
section .reloc

; contains the data that will be displayed
section .data
    ; this must be a Unicode string
    strHello db __utf16__ `Hello World !\n\r\0`

datasize equ $ - $$

上面代码很简单,就是根据传入的参数调用 EFI_SYSTEM_TABLE.ConOut 来完成字符串显示。特别的,根据之前的经验要在调用ConOut的时候多向堆栈压入一个8字节内容,否则模拟器会崩溃。

2.使用 Nasm(推荐使用 NASM 2.14 win64 或者更高版本)编译生成 OBJ 文件:

nasm -f win64 nasmuefi.asm

3.使用 Link 来生成 EFI 文件。原文使用的参数不全,导致编译出来的EFI 文件无法运行。经过研究可以使用下面的参数:

link /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64  /DLL /ENTRY:_start  /SUBSYSTEM:EFI_APPLICATION /SAFESEH:NO /BASE:0 /DRIVER NasmUEFI.obj

(可以加入/DEBUG 生成带有 PDB文件名信息的EFI 文件)

        这样我们得到了 NasmUEFI.EFI 文件,大小是 768 bytes.

可以在NT32 模拟器上运行,同样的我在实体机上验证过也可以正常运行。

可以看到,汇编语言可以用来编写UEFI Application,相比C语言来说复杂的多。

完整的代码下载:

Step to UEFI (206)EFI 文件研究(3):显示自定义字符串

目标:对于一个已经存在,但是没有 Source Code 的 EFI Application ,通过一种方法来插入代码实现在运行时显示自定义字符串。

前面关于 EFI 文件的研究提到对于一个 EFI Application 来说,运行后会把全部内容加载到内存中。如果我们能在EFI中找到足够大的 “缝隙” ,可以将代码插其中然后通过修改EntryPoint 处加入跳转到缝隙处我们的代码执行之后再跳转会继续执行。

经过观察,我发现“MZ”标志后有一段可以使用的“缝隙”,这次试验就在这里加入代码完成。

首先遇到的问题是:我们需要插入什么样的代码来完成显示。众所周知,UEFI 下需要使用ConOut的OutputString来实现显示。当然无法手工直接编写汇编语句,于是在 \MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c文件中,直接插入要显示的代码:

EFI_STATUS
EFIAPI
_ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
   SystemTable->ConOut->OutputString(SystemTable->ConOut,L"www.lab-z.com\n\r");
   
  EFI_STATUS                 Status;

  
  if (_gUefiDriverRevision != 0) {
    //
    // Make sure that the EFI/UEFI spec revision of the platform is >= EFI/UEFI spec revision of the application.
    //
    if (SystemTable->Hdr.Revision < _gUefiDriverRevision) {
      return EFI_INCOMPATIBLE_VERSION;
    }
  }
…….省略……

我们通过在编译过程中插入 /FAsc /Od 指令的方法强制生成汇编代码。查看上面代码生成的内容如下:

$LN25:
  00000	48 89 5c 24 08	 mov	 QWORD PTR [rsp+8], rbx
  00005	57		 push	 rdi
  00006	48 83 ec 20	 sub	 rsp, 32			; 00000020H
; 46   :   
; 47   :     SystemTable->ConOut->OutputString(SystemTable->ConOut,L"www.lab-z.com\n\r");

  0000a	48 8b 42 40	 mov	 rax, QWORD PTR [rdx+64]
  0000e	48 8b da	 mov	 rbx, rdx
  00011	48 8b f9	 mov	 rdi, rcx
  00014	48 8d 15 00 00
	00 00		 lea	 rdx, OFFSET FLAT:??_C@_1CA@KGEHCEOJ@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AN?$AA?$AA@
  0001b	48 8b c8	 mov	 rcx, rax
  0001e	ff 50 08	 call	 QWORD PTR [rax+8]
; File c:\buildbs\201903\mdepkg\library\uefibootservicestablelib\uefibootservicestablelib.c

最开始处标记的代码编译器生成的,不对应任何C语句。其中的 lea rdx,Offset 部分没有在编译阶段生成,所以为 00。从上面可以看出来,UEFI 调用Application 之后,ImageHandle 作为第一个参数放在RCX中,RDX中存放的第二个参数是指向SystemTable的指针。

接下来,我们修改Application 的 EntryPoint 为跳转,即从 0x2C0跳转到0x002。为了简单起见,使用NASM设计代码如下:

[BITS 64]
org 2C0h
jmp 0x2

Nasm 编译后再反编译,获得机器码如下:

C:\NASM>ndisasm -b 64 inst
00000000  E93DFDFFFF        jmp qword 0xfffffffffffffd42

接下来我们需要恢复现场,去掉前面ApplicationEntryPoint.c 插入的代码后再重新编译检查EFI中需要替换的代码。同样查看生成的ApplicationEntryPoint.cod文件:

_ModuleEntryPoint PROC					; COMDAT
; File c:\buildbs\201903\mdepkg\library\uefibootservicestablelib\uefibootservicestablelib.c

; 62   :   gBS = SystemTable->BootServices;

  00000	48 8b 42 60	 mov	 rax, QWORD PTR [rdx+96]
  00004	48 89 05 00 00
	00 00		 mov	 QWORD PTR gBS, rax
; File c:\buildbs\201903\mdepkg\library\uefiruntimeservicestablelib\uefiruntimeservicestablelib.c

; 46   :   gRT = SystemTable->RuntimeServices;

  0000b	48 8b 42 58	 mov	 rax, QWORD PTR [rdx+88]
  0000f	48 89 05 00 00
	00 00		 mov	 QWORD PTR gRT, rax
; File c:\buildbs\201903\mdepkg\library\uefibootservicestablelib\uefibootservicestablelib.c

; 50   :   gImageHandle = ImageHandle;

  00016	48 89 0d 00 00
	00 00		 mov	 QWORD PTR gImageHandle, rcx

就是说,我们需要将原来在 0x2C0处的值替换成跳转的代码 E9 7D FD FF FF 。原来处于0x2C0处的机器可以从COD文件中看到(特别注意,我们打开 EFI看到0x2C0处的机器是 48 8B 42 60 48 89 05 45 1B 00 00,这是OBJ文件在Link 之后的结果,和CL.EXE 直接生成的有所差别)。

之后,我们需要跳转到它的下一条语句(mov QWORD PTR gBS, rax这个的下一条)在 0x2C0+ 0xB。

有了上面的经验接下来设计位于 0x02 处的代码:

BITS 64
DEFAULT REL
nop
nop     ;还可以使用ORG 2 设定编译的起始偏移,这里使用NOP 是为了便于反编译观察
;EFI文件之前EnteryPoint处的动作我们需要重新做一次
DB 0x48,0x8b,0x42,0x60        ; mov rax, QWORD PTR [rdx+96]
DB 0x48,0x89,0x05,0x03,0x1E,0x00,0x00   ;mov QWORD PTR gBS, rax

push rdi ;为了保证OutputString()正常工作,需要多Push 8Bytes
push rcx
push rdx
sub rsp,80 ;从实践上来看,下面的函数会损坏堆栈
mov rax,[dword rdx+64] ;SystemTable->ConOut
lea rdx,[MyString]
mov rcx,rax
call [rax+8]   ;Call SystemTable->ConOut->OutputString()
add rsp,80
pop  rdx
pop  rcx
pop  rdi

jmp 0x2CB

MyString:
    db "www.lab-z.com",0

直接使用 Nasm inst.asm 即可编译,编译后再使用 ndisasm -b 64 inst反编译查看:

可以看到反编译后,前面的指令都是能够和汇编代码对应上的。接下来就是直接将上面的代码写入从 0x02开始的EFI文件中。

之后可以在 NT32模拟器上运行,同样也可以在实体机上运行:

接下来介绍解决这个问题的时候遇到的各种坑:

1.0x3C 处的0xB8,这是属于Dos Header 上的 E_lfanew ,给出了 PE Header 的起始位置,这里是不可以覆盖的,否则会产生 Load Error。如果你的代码很大,有可能会不小心覆盖掉,这样会导致EFI Image 加载错误。这也是为什么这次试验字符串放置在0x40的原因;

2.代码起始处的 push rdi , 不一定是RDI,任何8Bytes的寄存器都可以,但是如果没有这语句在调用ConOut的时候这个函数内部会发生错误;

3.实践发现在调用 SystemTable->ConOut 的时候会损坏堆栈,比如下面的调用方式(按道理2个参数的情况下是通过寄存器来进行参数传递的):

push rdx 
 Call ConOut
pop rdx 

执行后会导致 rdx 内容的损坏。因此,这里采用先做PUSH,之后  sub rsp,80 把堆栈指针移开这样的操作来避免堆栈内容的损坏。

4.修改之前的 EFI文件EntryPoint 处的 48 89 05 45 1B 00 00 (mov QWORD PTR gBS, rax),这是一个相对的操作,搬移到其他地址之后需要重新修正。比如,之前这个指令位于 0x2C4:

实际访问到的位置在 0x2C4 + 7 (这个指令的长度) + 0x1B45=0x1E10;当这个指令被移动到0x006 处执行时,需要重新计算其中的偏移: 0x1E10-7-0x006=0x1E03,所以对应机器码如下:

上述使用【参考1】提供的反编译工具。此外,还有 ConOut 中字符串偏移的计算方法与此类似,有兴趣的朋友可以手工试验。

上面的问题都可以使用之前提到的动态调试方法进行查看验证,特别是一些偏移计算上,如果不是很确定可以通过 UltraEdit 进行编辑不断试验和调整。

本文提到的Hello.EFI 原始文件和修改后的文件下载:

参考:

1. https://defuse.ca/online-x86-assembler.htm#disassembly2

ESP32 实现蓝牙键盘

手上有一块 DFRobot 出品的 FireBeelte,它的主控芯片是 ESP32 自带 WIFI 和 蓝牙,因此可以直接模拟成蓝牙键盘。

首先需要安装ESP32 BLE for Arduino这个库,在https://github.com/nkolban/ESP32_BLE_Arduino

然后安装Bluetooth LE Keyboard 这个库,在 https://github.com/T-vK/ESP32-BLE-Keyboard

之后,还需要修改\ESP32-BLE-Keyboard-master\BleKeyboard.cpp 文件,在前面插入  HIDINPUT 和HIDOUTPUT 的定义,否则编译会报错:

#if defined(CONFIG_ARDUHAL_ESP_LOG)
  #include "esp32-hal-log.h"
  #define LOG_TAG ""
#else
  #include "esp_log.h"
  static const char* LOG_TAG = "BLEDevice";
#endif

//LABZ_Debug_Start
#define HIDINPUT(size)             (0x80 | size)
#define HIDOUTPUT(size)            (0x90 | size)
//LABZ_Debug_End

// Report IDs:
#define KEYBOARD_ID 0x01
#define MEDIA_KEYS_ID 0x02

之后打开ESP32-BLE-Keyboard-master\examples\SendKeyStrokes  中的示例文件编译上传即可。

Step to UEFI (205)NT32下动态查看 Application(下)

前面提到了使用 Image Size 作为CPU Debug Break的触发条件,相比使用Image 的Size作为触发条件,使用 Image Name 作为触发条件要方便很多,每次只需要重新编译Application 然后运行之即可,因此这里研究如何实现。

首先要解决的是哪里取得Image Name。通过观察可以得知当我们运行 NT32 模拟器时,每次调用 EFI Application时候会在 Debug 窗口显示加载的Image 名称:

这个显示的功能位于 \MdeModulePkg\Core\Dxe\Image\Image.c如下函数中:

EFI_STATUS
CoreLoadPeImage (
  IN BOOLEAN                     BootPolicy,
  IN VOID                        *Pe32Handle,
  IN LOADED_IMAGE_PRIVATE_DATA   *Image,
  IN EFI_PHYSICAL_ADDRESS        DstBuffer    OPTIONAL,
  OUT EFI_PHYSICAL_ADDRESS       *EntryPoint  OPTIONAL,
  IN  UINT32                     Attribute
  ) 

具体代码如下:

    DEBUG ((DEBUG_INFO | DEBUG_LOAD,
           "Loading driver at 0x%11p EntryPoint=0x%11p ",
           (VOID *)(UINTN) Image->ImageContext.ImageAddress,
           FUNCTION_ENTRY_POINT (Image->ImageContext.EntryPoint)));

    //
    // Print Module Name by Pdb file path.
    // Windows and Unix style file path are all trimmed correctly.
    //
    if (Image->ImageContext.PdbPointer != NULL) {
      StartIndex = 0;
      for (Index = 0; Image->ImageContext.PdbPointer[Index] != 0; Index++) {
        if ((Image->ImageContext.PdbPointer[Index] == '\\') || (Image->ImageContext.PdbPointer[Index] == '/')) {
          StartIndex = Index + 1;
        }
      }
      //
      // Copy the PDB file name to our temporary string, and replace .pdb with .efi
      // The PDB file name is limited in the range of 0~255.
      // If the length is bigger than 255, trim the redudant characters to avoid overflow in array boundary.
      //
      for (Index = 0; Index < sizeof (EfiFileName) - 4; Index++) {
        EfiFileName[Index] = Image->ImageContext.PdbPointer[Index + StartIndex];
        if (EfiFileName[Index] == 0) {
          EfiFileName[Index] = '.';
        }
        if (EfiFileName[Index] == '.') {
          EfiFileName[Index + 1] = 'e';
          EfiFileName[Index + 2] = 'f';
          EfiFileName[Index + 3] = 'i';
          EfiFileName[Index + 4] = 0;
          break;
        }
      }

      if (Index == sizeof (EfiFileName) - 4) {
        EfiFileName[Index] = 0;
      }
      DEBUG ((DEBUG_INFO | DEBUG_LOAD, "%a", EfiFileName)); // &Image->ImageContext.PdbPointer[StartIndex]));
    }
    DEBUG ((DEBUG_INFO | DEBUG_LOAD, "\n"));

  DEBUG_CODE_END ();

简单的说,有些 Application包含了 PDB 信息的EFI 文件可以从Image中获得文件名称。

第一个关键位置在于 Image->ImageContext.PdbPointer,其中的ImageContext 定义如下:

   /// PeCoffLoader ImageContext
  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext;
  PE_COFF_LOADER_IMAGE_CONTEXT  结构体定义在 \MdePkg\Include\Library\PeCoffLib.h 文件中:
///
/// The context structure used while PE/COFF image is being loaded and relocated.
///
typedef struct {
…..省略…..
  ///
  /// Set by PeCoffLoaderLoadImage() to point to the PDB entry contained in the CodeView area.
  /// The PdbPointer points to the filename of the PDB file used for source-level debug of
  /// the image by a debugger.
  ///
  CHAR8                             *PdbPointer; 
…..省略…..
} PE_COFF_LOADER_IMAGE_CONTEXT;

在\MdePkg\Library\BasePeCoffGetEntryPointLib\PeCoffGetEntryPoint.c 有定义如下函数用来取得这个指针:

/**
  Returns a pointer to the PDB file name for a PE/COFF image that has been
  loaded into system memory with the PE/COFF Loader Library functions.

  Returns the PDB file name for the PE/COFF image specified by Pe32Data.  If
  the PE/COFF image specified by Pe32Data is not a valid, then NULL is
  returned.  If the PE/COFF image specified by Pe32Data does not contain a
  debug directory entry, then NULL is returned.  If the debug directory entry
  in the PE/COFF image specified by Pe32Data does not contain a PDB file name,
  then NULL is returned.
  If Pe32Data is NULL, then ASSERT().

  @param  Pe32Data   The pointer to the PE/COFF image that is loaded in system
                     memory.

  @return The PDB file name for the PE/COFF image specified by Pe32Data or NULL
          if it cannot be retrieved.

**/
VOID *
EFIAPI
PeCoffLoaderGetPdbPointer (
  IN VOID  *Pe32Data
  )

    if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      //
      // Use PE32 offset get Debug Directory Entry
      //
      NumberOfRvaAndSizes = Hdr.Pe32->OptionalHeader.NumberOfRvaAndSizes;
      DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
      DebugEntry     = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *) ((UINTN) Pe32Data + DirectoryEntry->VirtualAddress);

使用 SFF 分析,上面的位置是下面绿色框中 Data Directories[x] 中的EFI_IMAGE_DIRECTORY_ENTRY_DEBUG (该值为6),即右侧红色框中的值。可以看到在文件中的0x1AD0位置,大小为0x54。

继续使用 SFF 可以直接查看 Debug Directory 的内容:

直接查看 0x1B24 位置就可以看到信息:

PeCoffLoaderGetPdbPointer函数对应的代码上有一个扫描的动作,最终确定 PDB File Name:

  //
  // Scan the directory to find the debug entry.
  //
  for (DirCount = 0; DirCount < DirectoryEntry->Size; DirCount += sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY), DebugEntry++) {
    if (DebugEntry->Type == EFI_IMAGE_DEBUG_TYPE_CODEVIEW) {
      if (DebugEntry->SizeOfData > 0) {
        CodeViewEntryPointer = (VOID *) ((UINTN) DebugEntry->RVA + ((UINTN)Pe32Data) + (UINTN)TEImageAdjust);
        switch (* (UINT32 *) CodeViewEntryPointer) {
        case CODEVIEW_SIGNATURE_NB10:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY));
        case CODEVIEW_SIGNATURE_RSDS:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY));
        case CODEVIEW_SIGNATURE_MTOC:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY));
        default:
          break;
        }
      }
    }
  }

根据上面的代码,最终代码如下,就是根据上面的代码取出PDB文件名,然后通过比较确定加载的Image是否触发BreakPoint。

EFI_STATUS
EFIAPI
CoreStartImage (
  IN EFI_HANDLE  ImageHandle,
  OUT UINTN      *ExitDataSize,
  OUT CHAR16     **ExitData  OPTIONAL
  )
……省略……
  SetJumpFlag = SetJump (Image->JumpContext);
  //
  // The initial call to SetJump() must always return 0.
  // Subsequent calls to LongJump() cause a non-zero value to be returned by SetJump().
  //
  if (SetJumpFlag == 0) {
    RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));
    //LABZDEBUG_Start
                UINTN Index;
                UINTN StartIndex;
                CHAR8 EfiFileName[256];
            //
            // Print Module Name by Pdb file path.
            // Windows and Unix style file path are all trimmed correctly.
            //
            if (Image->ImageContext.PdbPointer != NULL) {
              StartIndex = 0;
              for (Index = 0; Image->ImageContext.PdbPointer[Index] != 0; Index++) {
                if ((Image->ImageContext.PdbPointer[Index] == '\\') || (Image->ImageContext.PdbPointer[Index] == '/')) {
                  StartIndex = Index + 1;
                }
              }
              //
              // Copy the PDB file name to our temporary string, and replace .pdb with .efi
              // The PDB file name is limited in the range of 0~255.
              // If the length is bigger than 255, trim the redudant characters to avoid overflow in array boundary.
              //
              for (Index = 0; Index < sizeof (EfiFileName) - 4; Index++) {
                EfiFileName[Index] = Image->ImageContext.PdbPointer[Index + StartIndex];
                if (EfiFileName[Index] == 0) {
                  EfiFileName[Index] = '.';
                }
                if (EfiFileName[Index] == '.') {
                  EfiFileName[Index + 1] = 'e';
                  EfiFileName[Index + 2] = 'f';
                  EfiFileName[Index + 3] = 'i';
                  EfiFileName[Index + 4] = 0;
                  break;
                }
              }

              if (Index == sizeof (EfiFileName) - 4) {
                EfiFileName[Index] = 0;
              }
              DEBUG ((DEBUG_INFO , "%a\n", EfiFileName)); 
              if (AsciiStrCmp(EfiFileName,"Hello.efi")==0) {
                   CpuBreakpoint();
              }
            }
    //LABZDEBUG_End
    //
    // Call the image's entry point
    //
    Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
……省略……

同样的方法可以用于判定当前的Image是什么文件上,简单的说就是向上,找到“MZ”头文件,然后再去查找 PDB 的信息从中确认文件名称。这样的方法对于使用 WinDBG/DCI 调试Windows 驱动同样有效。

Step to UEFI (204)NT32下动态查看 Application(上)

最近在研究 UEFI Application结构相关的内容。除了静态的分析,还需要找到一种观察和调试加载到内存后EFI文件的方法。经过比较和研究, NT32 模拟器是很好的选择。通过它能够方便的进行观察和反编译。

需要解决的第一个问题是:找到跳转到Application Entry Point 处的代码。经过研究入口位于  \MdeModulePkg\Core\Dxe\Image\Image.c  文件中下面这个函数:

EFI_STATUS
EFIAPI
CoreStartImage (
  IN EFI_HANDLE  ImageHandle,
  OUT UINTN      *ExitDataSize,
  OUT CHAR16     **ExitData  OPTIONAL
  )

前面准备妥当后,在下面的语句中跳转到Application Entry来开始执行:

    //
    // Call the image's entry point
    //
    Image->Started = TRUE;
    Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);

接下来研究如何实现如何触发,这里采用根据文件大小的方式进行触发。同样的上面这个函数中,申明了下面这个变量:

  LOADED_IMAGE_PRIVATE_DATA     *Image;

这个结构体定义在 \MdePkg\Include\Protocol\LoadedImage.h

typedef struct {
  UINTN                       Signature;
  /// Image handle
  EFI_HANDLE                  Handle;
  /// Image type
  UINTN                       Type;
  /// If entrypoint has been called
  BOOLEAN                     Started;
  /// The image's entry point
  EFI_IMAGE_ENTRY_POINT       EntryPoint;
  /// loaded image protocol
  EFI_LOADED_IMAGE_PROTOCOL   Info;
  /// Location in memory
  EFI_PHYSICAL_ADDRESS        ImageBasePage;
  /// Number of pages
  UINTN                       NumberOfPages;
  /// Original fixup data
  CHAR8                       *FixupData;
  /// Tpl of started image
  EFI_TPL                     Tpl;
  /// Status returned by started image
  EFI_STATUS                  Status;
  /// Size of ExitData from started image
  UINTN                       ExitDataSize;
  /// Pointer to exit data from started image
  VOID                        *ExitData;
  /// Pointer to pool allocation for context save/restore
  VOID                        *JumpBuffer;
  /// Pointer to buffer for context save/restore
  BASE_LIBRARY_JUMP_BUFFER    *JumpContext;
  /// Machine type from PE image
  UINT16                      Machine;
  /// EBC Protocol pointer
  EFI_EBC_PROTOCOL            *Ebc;
  /// Runtime image list
  EFI_RUNTIME_IMAGE_ENTRY     *RuntimeData;
  /// Pointer to Loaded Image Device Path Protocol
  EFI_DEVICE_PATH_PROTOCOL    *LoadedImageDevicePath;
  /// PeCoffLoader ImageContext
  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext;
  /// Status returned by LoadImage() service.
  EFI_STATUS                  LoadImageStatus;
} LOADED_IMAGE_PRIVATE_DATA;

其中的 EFI_LOADED_IMAGE_PROTOCOL 包含了当前的 Image Size 信息:

///
/// Revision defined in EFI1.1.
///
#define EFI_LOADED_IMAGE_INFORMATION_REVISION    EFI_LOADED_IMAGE_PROTOCOL_REVISION
///
/// Can be used on any image handle to obtain information about the loaded image.
///
typedef struct {
  UINT32            Revision;       ///< Defines the revision of the EFI_LOADED_IMAGE_PROTOCOL structure.
                                    ///< All future revisions will be backward compatible to the current revision.
  EFI_HANDLE        ParentHandle;   ///< Parent image's image handle. NULL if the image is loaded directly from
                                    ///< the firmware's boot manager.
  EFI_SYSTEM_TABLE  *SystemTable;   ///< the image's EFI system table pointer.

  //
  // Source location of image
  //
  EFI_HANDLE        DeviceHandle;   ///< The device handle that the EFI Image was loaded from.
  EFI_DEVICE_PATH_PROTOCOL  *FilePath;  ///< A pointer to the file path portion specific to DeviceHandle
                                        ///< that the EFI Image was loaded from.
  VOID              *Reserved;      ///< Reserved. DO NOT USE.

  //
  // Images load options
  //
  UINT32            LoadOptionsSize;///< The size in bytes of LoadOptions.
  VOID              *LoadOptions;   ///< A pointer to the image's binary load options.

  //
  // Location of where image was loaded
  //
  VOID              *ImageBase;     ///< The base address at which the image was loaded.
  UINT64            ImageSize;      ///< The size in bytes of the loaded image.
  EFI_MEMORY_TYPE   ImageCodeType;  ///< The memory type that the code sections were loaded as.
  EFI_MEMORY_TYPE   ImageDataType;  ///< The memory type that the data sections were loaded as.
  EFI_IMAGE_UNLOAD  Unload;
} EFI_LOADED_IMAGE_PROTOCOL;

我们使用代码中的 Hello.EFI 作为例子,它的大小是7712bytes。最终,代码如下:

  SetJumpFlag = SetJump (Image->JumpContext);
  //
  // The initial call to SetJump() must always return 0.
  // Subsequent calls to LongJump() cause a non-zero value to be returned by SetJump().
  //
  if (SetJumpFlag == 0) {
    RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));
    //LABZDEBUG_Start
        DEBUG ((EFI_D_INFO,"Current size [%d] bytes\n", Image->Info.ImageSize));
        if (Image->Info.ImageSize == 7712) {
                CpuBreakpoint();
        }
    //LABZDEBUG_End
    //
    // Call the image's entry point
    //
    Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);

就是说当发现加载的Image 大小是 7712 bytes 的时候自动触发一个 Breakpoint 打开 VS 进行调试:

接下来我们可以跳入Image 的领空进行查看和调试了。但是显而易见,这样的方法并不完美,如果Image 大小有变化,我们就需要重新编译运行 NT32 模拟器。后面会介绍如何使用文件名称来作为触发的判定条件,有兴趣的朋友可以尝试自己先进性研究。