介绍一个 USB 分析软件 Usblyzer

之前我的很多USB分析都是直接使用USB逻辑分析仪完成的,优点是:可靠性高,全部信息都可以抓取到;缺点是价格昂贵(20万人民币,个人基本上不可能购买。国产的我看到过8千的,但是貌似只有USB Full Speed),架设配置复杂。在Windows上还有一些纯软件的抓包分析工具,比如:BusHound【参考1】,可以抓取USB、SCSI & ATAPI、IDE & SATA、FireWire、1394等等总线的底层数据。从原理上说,是通过安装一个过滤驱动来实现的。

usb1

最近发现了一款专门用来抓取 USB 数据的工具软件:USBlyzer。因为他是专门设计用来抓取USB数据的,所以和 Bus Hound 相比,对USB数据能够进行详细的解析。
这个软件可以免费试用30天,对于一般的用户来说已经足够。

main

软件界面如下:

usb2

使用非常简单,首先在 Device Tree 上选中需要监视的USB设备

usb3

然后使用 Start Capture 即可开始抓包,使用Stop Capture 停止抓包后,可以在Capture List 中看到抓到的包。

usb4

下面是一个实际操作的例子,比如,我要分析微软 InteilMouse optical1.1A 这款鼠标的数据

usb5

首先选中设备:

usb6

在 USB Properties可以看到设备属性,对于标准支持的属性都有详细分析。因为鼠标是标准的HID协议,所以在协议分析中除了常见的描述符还能看到HID的描述符。

usb7

usb8

点击 Start Capture开始抓包,移动鼠标会有很多数据,使用 Stop Capture 后停止

usb9

点击抓到的数据,可以在 Data Analysis 中直接看到解析后的数据,这个解析是根据 HID描述符进行的。

usba

从可以看到通过这个软件可以方便进行鼠标数据的分析,并且能够看到驱动级别的消息,对于编写驱动会有莫大的帮助。

参考:
1. http://perisoft.net/bushound/index.htm
2. http://www.usblyzer.com

Step to UEFI (140)ProtocolsPerHandle 的例子

Boot Services 提供了 ProtocolsPerHandle 函数,用它可以取得一个 Handle上面的 Protocols。这里编写程序用来验证一下。
函数原型在 UEFI Specification 中可以看到:

pph

编写程序,取得当前 Application 上面的 Protocol:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        EFI_GUID     **ProtocolBuffer;
        UINTN        ProtocolCount;
        EFI_STATUS   Status;
        UINTN        i;
        
        Status = gBS->ProtocolsPerHandle(ImageHandle, &ProtocolBuffer, &ProtocolCount);
        if (EFI_ERROR (Status)) {
                //
                // TheHandle is not valid, so do not add to handle list
                //
                Print(L"Can't get Protcols on ImageHandle\n");
                return 0;
        }

        Print(L"Protocol list\n");        
        for (i=0;i<ProtocolCount;i++)   {
                Print(L"%g \n",ProtocolBuffer[i]);
        }
        FreePool (ProtocolBuffer);
        

        return 0;
}

 

运行结果如下(NT32虚拟机中运行结果):

pph2

在 DEC 文件中查找这些 GUID对应的 Protocol,如下:

752F3136-4E16-4FDC-A22A-E5F46812F4CA gEfiShellParametersProtocolGuid
BC62157E-3E33-4FEC-9920-2D3B36D750DF gEfiLoadedImageDevicePathProtocolGuid
5B1B31A1-9562-11D2-8E3F-00A0C969723B gEfiLoadedImageProtocolGuid

源代码和EFI下载:
sd

=============================================================
2018年2月24日 krishnaLee(sssky307)做了一番深入的研究,他发现一个可以将 UUID 转化为对应名称的简单方法。例程如下:

#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle

#include <Protocol/LoadedImage.h>      //EFI_LOADED_IMAGE_PROTOCOL
#include <Protocol/DevicePath.h>       //EFI_DEVICE_PATH_PROTOCOL

#include <Library/DevicePathLib.h>     //link
#include <Library/ShellLib.h>
#include <Library/ShellCommandLib.h> 
#include <Library/HandleParsingLib.h> 

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable)
{

  EFI_GUID **ProtocolGuidArray;
  UINTN ArrayCount;
  CHAR16 *str;

  Print(L"1\n");
  EFI_STATUS status = gBS->ProtocolsPerHandle(gImageHandle,&ProtocolGuidArray,&ArrayCount);

  if (status == EFI_SUCCESS)
  {
    for (UINTN i = 0; i < ArrayCount; i++)
    {
      str = GetStringNameFromGuid(ProtocolGuidArray[i], 0);
      Print(L"%s,%g\n", str,ProtocolGuidArray[i]);
      if (str)
      {
        gBS->FreePool(str);
        str=0;
      }
    }

    if (ProtocolGuidArray)
    {
      gBS->FreePool(ProtocolGuidArray);
      ProtocolGuidArray=0;
    }
  }

  EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
    //open the Loaded image protocol,which is binded on the imageHandle,to get the device handle.
    status = gBS->OpenProtocol(
      gImageHandle,
      &gEfiLoadedImageProtocolGuid,
      (VOID **)&LoadedImage,
      gImageHandle,
      NULL,
      EFI_OPEN_PROTOCOL_GET_PROTOCOL);

  if(status == EFI_SUCCESS)
  {
    Print(L"2\n");
   status = gBS->ProtocolsPerHandle(LoadedImage->DeviceHandle,&ProtocolGuidArray,&ArrayCount);
    
      if (status == EFI_SUCCESS)
      {
        for (UINTN i = 0; i < ArrayCount; i++)
        {
          str = GetStringNameFromGuid(ProtocolGuidArray[i], 0);
          Print(L"%s,%g\n", str,ProtocolGuidArray[i]);
          if (str)
          {
            gBS->FreePool(str);
            str=0;
          }
        }
    
        if (ProtocolGuidArray)
        {
          gBS->FreePool(ProtocolGuidArray);
          ProtocolGuidArray=0;
        }
      }

      gBS->CloseProtocol(
        gImageHandle,
        &gEfiLoadedImageProtocolGuid,
        gImageHandle,
        NULL);
  }


  return EFI_SUCCESS;
}

 

在 NT32 下运行的结果如下:

dpe

完整的代码下载:

mytesthandle

特别提醒,如果在 AppPkg 中编译,需要在AppPkg.dsc的 [LibraryClasses] 中加入下面的语句:

HandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
ShellCommandLib|ShellPkg/Library/UefiShellCommandLib/UefiShellCommandLib.inf

再次感谢 krishnaLee(sssky307)

Shell MSR 工具

最近需要在 Shell 下读取 MSR 设置,然后发现没有顺手的工具,于是写了一个。
最主要的工作是AsmReadMsr64 和AsmWriteMsr64 函数来完成的,所以整体上并不复杂,代码如下:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>

extern EFI_BOOT_SERVICES         *gBS;

BOOLEAN	WriteOpt=FALSE;
BOOLEAN	ReadOpt=FALSE;

//Copied from UDK2017\UefiCpuPkg\Include\Register\Msr\SandyBridgeMsr.h
/**
  Package. PP0 Balance Policy (R/W) See Section 14.9.4, "PP0/PP1 RAPL
  Domains.".

  @param  ECX  MSR_SANDY_BRIDGE_PP0_POLICY (0x0000063A)
  @param  EAX  Lower 32-bits of MSR value.
  @param  EDX  Upper 32-bits of MSR value.

  <b>Example usage</b>
  @code
  UINT64  Msr;

  Msr = AsmReadMsr64 (MSR_SANDY_BRIDGE_PP0_POLICY);
  AsmWriteMsr64 (MSR_SANDY_BRIDGE_PP0_POLICY, Msr);
  @endcode
  @note MSR_SANDY_BRIDGE_PP0_POLICY is defined as MSR_PP0_POLICY in SDM.
**/
#define MSR_SANDY_BRIDGE_PP0_POLICY              0x0000063A

void PrintUsage() {
  Print(L"******************************************\n");
  Print(L"*  MSR Read & Write utility for KBL      *\n");
  Print(L"*               Powered by www.lab-z.com *\n");
  Print(L"*               11/14/2017 Version 0.5   *\n");
  Print(L"*  Usage:                                *\n");
  Print(L"*  Ex1:read MSR                          *\n");
  Print(L"*       msrtest -r 63A                   *\n");
  Print(L"*  Ex2:write MSR                         *\n");
  Print(L"*       msrtest -w 63A 11                *\n");
  Print(L"******************************************\n");
}

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        UINT64          MsrValue;
        UINT32          MsrAddr;
        
        if (Argc<2) {
                PrintUsage();
                return 0;
        }
        
        if ((StrStr(Argv[1],L"-w")!=NULL) ||
           (StrStr(Argv[1],L"-W")!=NULL)) {
                WriteOpt=TRUE;
                if (Argc!=4) {
                        PrintUsage();
                        return 0;
                }
                MsrAddr=StrHexToUintn(Argv[2]) & 0xFFFFFFFF;
                MsrValue=StrHexToUint64(Argv[3]);
                AsmWriteMsr64(MsrAddr,MsrValue);   
                Print(L"Write MSR[%X] as %lX\n",MsrAddr,MsrValue);
        }
        
        if ((StrStr(Argv[1],L"-r")!=NULL) ||
            (StrStr(Argv[1],L"-R")!=NULL)) {
                ReadOpt=TRUE;
                if (Argc!=3) {
                        PrintUsage();
                        return 0;
                }
                MsrAddr=StrHexToUintn(Argv[2]) & 0xFFFFFFFF;                
                MsrValue=AsmReadMsr64(MsrAddr);   
                Print(L"MSR[%X]=%lX\n",MsrAddr,MsrValue);
        }
        
  return 0;
}

 

在KBL-R HDK 上运行结果如下:

msr

完整代码下载:

MSRTestSRC

编译好的 X64 EFI 代码:

msrtest

Step to UEFI (139)关于Image DevicePath 的小实验

最近做了一个关于 Image 的 Device Path 的实验,比较简单,分享一下。
首先是根据当前 Application 的 ImageHandle 取得对应的 Handle。使用 HandleProtcol 直接打开加载在Application 上面的 EFI_Loaded_Image_Protocol。在这个 Protocol上有 FilePath,就是当前 Image 的 Device Path Protocol ,具体分析可以在【参考1】看到。
接下来,要把取得的 FilePath转化为人类易读的字符串。使用 Device_Path_To_Text_Protocol,具体用法可以在【参考2】找到。
最后,我们看一下这个 Device Path 的Raw Data,先是输出 Type、SubType和Length。从结果中看到属于 File Path Media Path,对照 Specification进一步分析,就是说在 Length 后面跟着一个CHAT16 字符串。
dp2
完整代码如下:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/DevicePath.h> //EFI_DEVICE_PATH_PROTOCOL
#include <Protocol/DevicePathToText.h> //EFI_DEVICE_PATH_TO_TEXT_PROTOCOL

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        EFI_STATUS               Status;
        EFI_LOADED_IMAGE_PROTOCOL *ImageInfo = NULL;
        EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *Device2TextProtocol;
        CHAR16  *TextDevicePath;
        UINTN           Index;
        CHAR8  *p;
        
        Status = gBS -> HandleProtocol(
                        ImageHandle,
                        &gEfiLoadedImageProtocolGuid,
                        &ImageInfo);
        if (EFI_ERROR(Status)) {
                Print(L"Error!\n");
        }

        //get a converter.
        Status = gBS->LocateProtocol(&gEfiDevicePathToTextProtocolGuid,
                                     NULL,
                                     (VOID**)&Device2TextProtocol
                                    );

        //convert device path to text.
        if (!EFI_ERROR (Status))
        {
                TextDevicePath= Device2TextProtocol->ConvertDevicePathToText(ImageInfo->FilePath, TRUE, FALSE);
                Print(L"%s\n",TextDevicePath);
                gBS->FreePool(TextDevicePath);
        }
        
        Print(L"Type:[%d] SubType:[%d] Length:[%d] \n",
                    ImageInfo->FilePath->Type,            
                    ImageInfo->FilePath->SubType,
                    ImageInfo->FilePath->Length[0]+
                    (ImageInfo->FilePath->Length[1]<<8));
        
        Print(L"%x\n",ImageInfo->FilePath);  
        p=(CHAR8 *)ImageInfo->FilePath;
        p=p+4;
        Print(L"%s\n",p);          
        for (Index=0;Index<ImageInfo->FilePath->Length[0]+(ImageInfo->FilePath->Length[1]<<8)-4;Index++) {
              Print(L"%2X ",*p);  
              p++;
        }
        Print(L"\n");  

        return 0;
}

 

运行结果:

dp1

结果中第一行是Device_Path_To_Text_Protocol转化的结果;第二行是这个DevciePath 的基本信息;第三行是DevicePath的内存地址,实验表明当运行完 Application后,这里面的内容会被释放掉,无法使用MEM之类的指令查看到;第四行是直接输出字符串的结果,可以看到和前面的使用 Protocol 的结果一致;最后一行直接输出DevicePath 的内容,就是 “\idp.EFI”的 Unicode值。
完整的 源代码和 X64 EFI 下载:
ImageDevicePath

参考:
1. http://www.lab-z.com/efiloadedimageprotocol/ EFILOADEDIMAGEPROTOCOL的使用
2. http://www.lab-z.com/dpdemo/解析 DevicePath 的例子

背诵,唐诗 和 MicroBit

对于小朋友是否要背诵唐诗是有争论的,一方认为有助于提升品味,不至于长大了只会夸赞“牛13,很牛13”;另外一方也有人认为因为孩子太小无法理解内容,只是机械的记忆并没有多少帮助。譬如说,长大了之后《咏鹅》只记得“鹅鹅鹅,曲项用刀割,拔毛加瓢水,点火盖上锅”。不过以我的经验来看,无论用什么内容来训练,对于小朋友的语言和记忆力很有帮助。并且,诗歌也算得上汉族人抒发感情独特的方法。譬如,有一次,送我岳父去火车站回老家。作为一个酱菜行业的手工业资本家,他对毛爷爷有着在我看起来莫名其妙但是无比深厚的感情。到火车站,还有一段时间才检票,为了抒发一下我的心情,我吟诗一段毛爷爷的诗歌“春风杨柳万千条,六亿神州尽舜尧。 红雨随心翻作浪,青山着意化为桥”。看着他点头称赞的样子更加坚定我让孩子背唐诗的信心。
最近拿到了YWROBOT提供的 MicroBit,这是一款BBC针对青少年编程推出的设备,配合微软提供的工具【参考1】可以方便的进行图形画的编程。为了实现训练孩子的目的,我用这个板子做一套手机遥控的盒子,背出之后可以自动弹开,孩子能够吃到里面放置的食物。
image001

首先介绍一下硬件设备:

1. 电控锁,工作电压12V,通电就打开,力道挺大,能把锁扣一下弹走,这种也是广泛使用在快递柜上的。
image002

2.除了 Micro Bit本身,我还买了一个扩展板,因为 MicroBit本身没有排针,不容易焊接。有了扩展版,插上后可以用杜邦线连接。
image003

3. 继电器,5V,这是很常见的低电压控制高电压设备的元件
image004

4. 三极管。我第一次直接使用这种元件。起因是偶然间我发现 MicroBit的供电能力太弱了,无法驱动继电器。具体现象是,接上之后继电器的PWR可以亮,但是控制无反应。我随后还实验了Mosfet的模块,现象相同,始悟应该是MicroBit驱动能力不足导致的。去查了一下扩流的方法,最简单的莫过于选择三极管。
最终电路如下:
image005

工作流程:手机通过蓝牙和MicroBit连接,运行BittyBlue 软件。关于这个软件和蓝牙的使用,可以在【参考2】找到。代码非常简单,使用了 Bluetooth LED 的服务,当我们设置 (0,0)的LED从0 到1的时候,就触发将 Pin16拉高1秒,驱动一个继电器,继电器将接通锁的电源,锁就会自动打开
image006

外壳使用亚克力制作,使用六面螺母搭配 M3螺丝固定
image007

组装后的外观如下:
image008

参考:
1. https://makecode.microbit.org/#
2. http://www.arduino.cn/thread-74162-1-1.html Micro:Bit 的蓝牙实验

Step to UEFI (138) Shell 下 BootOrder 的研究

对于普通用户来说,启动顺序显而易见,因此是重要的功能,比如:希望使用U盘安装系统,就必须能从U盘上引导起来。

在 Legacy 的时代,关于启动部分只有一份BBS specification可供参考,这个规范看起来非常生涩,缺点是理解不同,好处也是理解不同。很多年前,我遇到过一个客户报告的问题。他们使用IDE光驱,如果启动 失败并不会直接跳转到下一个启动设备而是显示 Boot Failure。板子上的南桥是 Intel最新推出的 ICH6, 刚好这一代南桥取消了IDE接口,为了兼容板子上使用了一颗 SATA转IDE 的JMicron 的芯片,启动的时候BIOS首先会 Load他们的Option ROM来完成初始化之类的工作。
问题确切,一时也找不到原因,我拿着BBS Spec翻来翻去,最终看懂了上面提到:报告不同种类设备(BEV 或者BCV)的启动 fail 有 Int 18 和 Int 19 的差别。于是,做了一个大胆的猜测:光驱启动失败之后,他们使用了某一种错误的报告错误方式,以至于我们在期待的点上没有办法“捕捉”到。接下来用十六进制工具,将 JMicron Option ROM 中的 INT 18 (CD 18)全都修改为 INT 19 (CD 19)。在重新编译BIOS的时间里顺便祈祷一番,然后烧录,实验成功故障消失。有了这样的结果,按照BBS 解读,再就去和 JMicron 理论了,最终他们重新给我们出了一个ROM问题解决。

时至今日,BIOS 已进化为 UEFI,启动引导有了统一的标准,使用上方便很多。理论部分可以在 UEFI Specification中的第三章 “3 Boot Manager”中看到,有兴趣的朋友可以仔细研读。

本文介绍如何在 Shell 下面查看启动顺序之类的设定,并且给出了一个实现查看的Application,资料和代码来自【参考1】 ,在此特别鸣谢。

先介绍一下原理,其实我是先看【参考2】的代码才理解的,如果下面讲述有误,也请读者朋友直接查看后面的代码,能否正常工作才是检验一切猜测的真理标准。

1. 启动设备的顺序设定
UEFI 下面的启动顺序是存储在 variable 中,放在 NVRAM 中的。使用 gRT->GetVariable ,参数为 “BootOrder”, GUID 如下即可取得设定的顺序
#define EFI_GLOBAL_VARIABLE
{0x8BE4DF61,0x93CA,0x11d2,0xAA,0x0D,0x00,0xE0,0x98,0x03,0x2B,0x8C}
取得的结果是一串数字(起初我以为有结构体,但是查找之后发现就是2个BYTE一组组成的)。
例如:实体机上,在 Shell 下 dmpstore 的结果如下:
Variable NV+RT+BS ‘8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootOrder’ DataSize = 0x0C
00000000: 04 00 00 00 01 00 02 00-03 00 05 00 *…………*
意思是 UEFI 会首先尝试0004设备, 然后是 0000设备,然后是 0001设备一直到最后,其中的 0004设备指的就是 BOOT0004

2.BOOTXXXX设备
在variable 中,还有 Boot0004这样的定义,例如:

Variable NV+RT+BS ‘8BE4DF61-93CA-11D2-AA0D-00E098032B8C:Boot0002’ DataSize = 0x4A
00000000: 09 01 00 00 2C 00 45 00-6E 00 74 00 65 00 72 00 *….,.E.n.t.e.r.*
00000010: 20 00 53 00 65 00 74 00-75 00 70 00 00 00 04 07 * .S.e.t.u.p…..*
00000020: 14 00 67 D5 81 A8 B0 6C-EE 4E 84 35 2E 72 D3 3E *..g….l.N.5.r.>*
00000030: 45 B5 04 06 14 00 21 AA-2C 46 14 76 03 45 83 6E *E…..!.,F.v.E.n*
00000040: 8A B6 F4 66 23 31 7F FF-04 00 *…f#1….*

对应内容是一个结构体:
so
so2

Attributes中有一个项目是 LOAD_OPTION_ACTIVE,用来标记这个设备是否可以启动,如果设置起来,系统会尝试从它上面进行引导。如果你不想让这个设备启动,可以直接清楚该标志。这样无需增减项目就可以实现控制。
比较有趣的是其中的 Description,给出了一个人类易读的说明,比如上面的Boot0004 设备说明即为”Enter Setup”

3.当前启动设备

“BootCurrent”变量,可以用来指出当前的引导设备。比如:当前我直接启动到了 Shell 下,BootCurrent 记录的启动设备就是 Boot0000,即Internal EDK Shell
Variable NV+RT+BS ‘8BE4DF61-93CA-11D2-AA0D-00E098032B8C:Boot0000’ DataSize = 0x58
00000000: 01 00 00 00 2C 00 49 00-6E 00 74 00 65 00 72 00 *….,.I.n.t.e.r.*
00000010: 6E 00 61 00 6C 00 20 00-45 00 44 00 4B 00 20 00 *n.a.l. .E.D.K. .*
00000020: 53 00 68 00 65 00 6C 00-6C 00 00 00 04 07 14 00 *S.h.e.l.l…….*
00000030: 22 F3 2C B9 FA 8A A4 4A-B9 46 00 5D F1 D6 97 78 *”.,….J.F.]…x*
00000040: 04 06 14 00 B7 D6 7A C5-15 05 A8 40 9D 21 55 16 *……z….@.!U.*
00000050: 52 85 4E 37 7F FF 04 00- *R.N7….*
下面是来自【参考2】的Shell Application,可以显示和设定当前的启动顺序:

运行结果:
so3

X64 UEFI Application 下载  bttest

源代码下载:bootmgr

最后顺便说一下,JMicron 后来推出了新的Sata转 IDE 芯片,无需Option ROM 了,也就没有BIOS什么事情了。

参考:
1. http://kurtqiao.github.io/uefi/2015/01/13/uefi-boot-manager.html
2. https://github.com/kurtqiao/MyPkg/tree/master/Application/bootmgr

顺丰历险记

我对顺丰的印象很好,很多重要的东西都是用他们来发送。2017年末,给妈妈买了一个手机,12月30日发出,突发奇想,用微信中的顺丰公众号下单,使用蜂巢快递箱发了一次(快递箱是按照大小收费的,小的20中的40大的要60,保价2500收费13元,总共是53,微信支付了),第二天,想查查到哪里了,结果惊奇的发现微信公众号竟然查不到。29日的快递能看到,但是30日是空白。

这个就很奇怪了,急忙给顺丰打电话,接通之后对方就问快递号,我是不知道的,因为从始至终没有收到过,只有一个寄件码,对方说查不到,因为我是用蜂巢发送的,让我找蜂巢;

我打给蜂巢的客服,对方也是先问快递号,我说没有,只有寄件码,对方又问我手机号,一番查询之后又让我联系顺丰,这下我有点着急了,按照这样扯皮,保价也没有用啊;蜂巢随后又打来电话,特别说明了一下:“没有,真没有”。

v2-4a4db82058fd27da225013994c8cd39a_hd

再次打给顺丰,顺丰接通之后对方就问快递号,我是不知道的,因为从始至终没有收到过,只有一个寄件码,对方说查不到。又查询我的手机号码依然没有,刹那间我有了记忆欺骗了我的感觉,但是微信钱包中的支付记录告诉我,一切都是真实的。

v2-b15a8bb6e8f64aded1a38d9dcf9b999f_hd

顺丰客服又给我本地分部的电话,我打过去半天才有人接听。对方还是问了一遍快递号,得知我没有快递号之后,对方直接拒绝了。

没办法又打顺丰客服,接下来的客服又详细问了我收件人的信息,然后告诉我找到了,前面无法找到的原因是:手机号码给我搞错了。这个听起来很奇怪,因为我是微信下单的,除非我选错了发件人,否则不可能出现填写错误手机号的情况。当然,客服也只能给出含糊的解释。终于,我拿到了快递单号,对方也答应给我更改。

高高兴兴以为万事大吉之后又在网上查询了一下那个单号,惊奇的发现,空号。再次拨打顺丰客服,接电话的是一个男的,告知单号之后,他回答,此单正在修改手机号码,需要和发件人核实。剩下的问题就是上面的号码是错的,不知道他怎么核实。我咨询了一下为什么网上查不到单号,他问我“你是用百度查询的吧?”想象中小哥都是下面的表情了:

v2-1b80fa1589ec6472fba3aacdb5bcd573_hd

听到这句话,我简直惊呆了,急忙说是。他回答,现在百度查不到了。要查询,请用微信或者顺丰官网,欢迎来电…….

v2-33e8cfed77f5e3f3e4563eb9da7d9a58_hd

终于,放下电话,我再次到顺丰官网查询,结果却是“快件被退回”,上海到上海。我之前有同事发快递到上海华山路,结果包裹跑到华山旅游了一圈,但是这是要让我的包裹上海郊区游的节奏吗?

v2-2cf4784f45ea10f52dedd750958e3e3c_hd

于是,又打了一次客服,这次对方给我解释,这个状态不是给客人看的,等等等就是相安无事万事大吉的意思。只是除了这个消息还有一个坏消息:顺丰可以修改收件人姓名,发件人姓名还有收件人地址手机号,唯独不能修改发件人手机号。意思是:我还是没有办法直接在微信中跟踪这个包裹。

就这样吧……

v2-581203645f87b37c08d0a8ec98ca82dc_hd

结论:即使发顺丰,也不要使用快递柜。多叫上门,和人类接触很有好处。

v2-24353868722a03863a47fb306e6e5313_hd

v2-7174add2aa9dcbb9c37f3e598755a3e8_hd

TTS 真人发音 SYN6288 模块

通常合成语音技术被称作 TTS (Text To Speech),这样的功能在报站,排队叫号等等场合有着广泛的应用。最近我入手了一个SYN6288的TTS模块,价格50元送喇叭。

image001

具体的说明可以在芯片手册中找到,对于我们来说,最直接的用法就是播放语音。推荐刚拿到模块的时候使用USB串口直接对模块发送下面的数据可以测试是否正常。
image002

从上面也能看出,命令的构成。需要特别注意的是命令参数指定了文本的编码方式,对于 Windows来说默认通常都是 Unicode。另外就是最后用于校验的值,这是对每一个 byte 异或运算获得的。计算方式是: buffer[0]+ buffer[1]+…+ buffer[n]。

我们还需要了解的是 Arduino 中的汉字使用的是UTF8编码 (因为 IDE 是 Unicode),例如:用下面的代码输出“宇音天下”。

char buffer[]="宇音天下";

void setup() {

  Serial1.begin(9600);

  Serial.begin(9600);

}



void loop() {



  for (int i =0;i<sizeof(buffer);i++) {

      Serial.print(buffer[i]&0xFF,HEX);

      Serial.print(' ');

    }



    Serial.println(' ');



    delay(15000);

}

 

结果如下:

E5 AE 87 E9 9F B3 E5 A4 A9 E4 B8 8B 0

其中“宇”UTF8编码:E5AE87,“音” UTF8编码:E99FB3….. 最后还有一个表示结尾的0【参考1】。

然后这个地方就让人晕掉了,为什么资料中给出来的 Unicode 是2Bytes一个汉字而上面给出来的是3个bytes?

终于我在【参考2】找到了答案,原来 Unicode规定了编码方式,但是没有规定如何存储,比如:高位在前还是在后,具体要存储多长。所以具体落地实现有 UTF8 UTF16 等等。对于我们这个情况,Arduino Java 使用的是UTF8,但是模块需要你以Unicode的编号通知它。接下来的问题就是,我有UTF8,如何转化为 Unicode?

先说Unicode转UTF8, 有下面这样的表格

Unicode符号范围     |        UTF-8编码方式

(十六进制)        |              (二进制)

———————-+———————————————

0000 0000-0000 007F | 0xxxxxxx

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

还是以汉字严为例,演示如何实现 UTF-8 编码。

严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 – 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。

上面的资料依然来自【参考2】。

然后我们要尝试将E5AE87 转换为 Unicode:

首先,E5AE87 写成二进制就是下面这样
image003

根据上面的表格,切分一下(我们已经知道一个汉字是3 Bytes,所以直接使用第三行)
image004

就是说上面红色框中的对我们来说是多余的,去掉之后我们只剩下下面的数值
image005

这就是 5B87, 网页上查到的也是这样:
image006

接下来的问题就是:为什么上面给出来的0x5B87 而资料给出来的是0x8BED,对于这个问题我还真没有找到答案,0x8BED是“语”的 Unicode。只能猜测一下,要么是写手册的不小心犯错,毕竟发出来声音没人听的出来这两个字的差别;要么就是故意设计成这样。

因此,简单起见,我们可以直接计算要输出的字符,将Arduino 定义的字符串每三个bytes一组,掐头重新拼接成2个bytes。‘

//要输出的字符串

char buffer[]="宇音天下";

//实际汉字长度

#define BSIZE (sizeof(buffer)/3)

//存放转化后的汉字 Unicode值

char character[BSIZE * 2];

&nbsp;

void setup() {

Serial1.begin(9600);

Serial.begin(9600);

delay(3000);

}

&nbsp;

void loop() {

//首先输出一次Arduino 原始字符串 UTF8 的值

for (int i =0;i&lt;BSIZE*3;i++) {

Serial.print(buffer[i]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println(' ');

&nbsp;

//将 UTF8 转化为 Unicode

for (int i =0;i&lt;BSIZE;i=i+1) {

character[i*2]=((buffer[i*3]&amp;0xF)&lt;&lt;4)+((buffer[i*3+1]&gt;&gt;2)&amp;0xF);

character[i*2+1]=((buffer[i*3+1]&amp;0x3)&lt;&lt;6)+(buffer[i*3+2]&amp;0x3F);

Serial.print(character[i*2]&amp;0xFF,HEX);

Serial.print(' ');

Serial.print(character[i*2+1]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println(' ');

delay(15000);

}

 

实验结果就是:
image007

接下来在上面代码的基础上继续修改,集成直接串口发送

//要输出的字符串

char buffer[]="宇音天下";

//实际汉字长度

#define BSIZE (sizeof(buffer)/3)

//存放转化后的汉字 Unicode值

char character[BSIZE * 2];

&nbsp;

//根据字符串计算计算出来的送到串口的值

char output[BSIZE*2+6];

&nbsp;

void setup() {

Serial1.begin(9600);

Serial.begin(9600);

delay(5000);

}

&nbsp;

void loop() {

//首先输出一次Arduino 原始字符串 UTF8 的值

for (int i =0;i&lt;BSIZE*3;i++) {

Serial.print(buffer[i]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println(' ');

//将 UTF8 转化为 Unicode

for (int i =0;i&lt;BSIZE;i=i+1) {

character[i*2]=((buffer[i*3]&amp;0xF)&lt;&lt;4)+((buffer[i*3+1]&gt;&gt;2)&amp;0xF);

character[i*2+1]=((buffer[i*3+1]&amp;0x3)&lt;&lt;6)+(buffer[i*3+2]&amp;0x3F);

Serial.print(character[i*2]&amp;0xFF,HEX);

Serial.print(' ');

Serial.print(character[i*2+1]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println("");

&nbsp;

output[0]=0xFD;

output[1]=(BSIZE*2+3)&gt;&gt;8;

output[2]=((BSIZE*2+3)&amp;0xFF);

output[3]=0x01;

output[4]=0x03;

//把字符串定义搬过去

for (int i=0;i&lt;BSIZE*2;i++) {

output[i+5]=character[i];

}

//计算一个校验和

output[BSIZE*2+5]=output[0];

for (int i=1;i&lt;BSIZE*2+5;i++) {

output[BSIZE*2+5]=output[BSIZE*2+5] ^ output[i];

}

&nbsp;

for (int i =0;i&lt;BSIZE*2+6;i++) {

Serial.print(output[i]&amp;0xFF,HEX);

Serial.print(' ');

Serial1.write(output[i]);

}

&nbsp;

Serial.println(' ');

&nbsp;

delay(15000);

}

 

运行结果
image008

工作的视频可以在知乎专栏中看到 https://zhuanlan.zhihu.com/p/32856362

从上面可以看到,Arduino中是有机会将指定定义的中文字符串转换后发送出去的。但是更多时候,我们直接定义每一个字符对应的Unicode即可,虽然不是很直观,但是在编码上会省很多力气。

特别提一下:模块上面有耳机插孔,我插入了一个没有声音,并且芯片迅速发热,怀疑是兼容性上的问题。如果有朋友需要用耳机或者功放,需要特别注意一下(我怀疑是耳机插头什么地方导致短路)。

参考:

  1. http://www.qqxiuzi.cn/bianma/Unicode-UTF.php Unicode和UTF编码转换
  2. http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

20200625 补充

用 FirBeetle (ESP32) 测试,模块使用 3.3v 供电,使用第二个串口的 TX (IO10,D6)对模块发送数据。 具体代码如下:

//要输出的字符串
char buffer[]="七点五十一分";
//实际汉字长度
#define BSIZE (sizeof(buffer)/3)
//存放转化后的汉字 Unicode值
char character[BSIZE * 2];
#include &lt;HardwareSerial.h>

HardwareSerial Serial1(1);

//根据字符串计算计算出来的送到串口的值
char output[BSIZE*2+6];

void setup() {
  Serial1.begin(9600);
      delay(5000);
}

void loop() {
  //首先输出一次Arduino 原始字符串 UTF8 的值
  for (int i =0;i&lt;BSIZE*3;i++) {
     Serial.print(buffer[i]&amp;0xFF,HEX);
      Serial.print(' ');
  }
  Serial.println(' ');
  //将 UTF8 转化为 Unicode
    for (int i =0;i&lt;BSIZE;i=i+1) {
      character[i*2]=((buffer[i*3]&amp;0xF)&lt;&lt;4)+((buffer[i*3+1]>>2)&amp;0xF);
      character[i*2+1]=((buffer[i*3+1]&amp;0x3)&lt;&lt;6)+(buffer[i*3+2]&amp;0x3F);
      Serial.print(character[i*2]&amp;0xFF,HEX);
      Serial.print(' ');
      Serial.print(character[i*2+1]&amp;0xFF,HEX);
      Serial.print(' ');      
    } 
  Serial.println(""); 

  output[0]=0xFD;
  output[1]=(BSIZE*2+3)>>8;
  output[2]=((BSIZE*2+3)&amp;0xFF);
  output[3]=0x01;
  output[4]=0x03;
  //把字符串定义搬过去
  for (int i=0;i&lt;BSIZE*2;i++) {
      output[i+5]=character[i];
    } 
  //计算一个校验和  
  output[BSIZE*2+5]=output[0];
  for (int i=1;i&lt;BSIZE*2+5;i++) {
      output[BSIZE*2+5]=output[BSIZE*2+5] ^ output[i];
    }   
  
  for (int i =0;i&lt;BSIZE*2+6;i++) {
      Serial.print(output[i]&amp;0xFF,HEX);
      Serial.print(' ');
      Serial1.write(output[i]);
    }     
  
Serial.println(' ');

    delay(3000);
}

Step to UEFI (137) 通过 BGRT 取得当前系统的 LOGO

对于BIOS来说,用户能够看到的是非常重要的事情。开机Logo就是这样。自从 Win8.1开始,Windows在进入桌面的时候可以显示用户自定义的Logo,而不是Microsoft自定义的图片,这是很有意思的事情。
最近看了一篇介绍的文章,恍然大悟,原来是BIOS解压自己的Logo在内存中,然后通过ACPI Table将这个Logo传递给Windows,于是开机Logo比以前显示的时间更长更持久。
具体的Table就是 Boot Graphics Resource Table。在 ACPI 6.1的5.2.22有专门的介绍。

bgrt1

bgrt11

根据上面的原理,我们可以编写一个UEFI Application,将内存存放的 Logo Dump出来。具体操作:

1. 找到RSDP,找到 XSDT

bgrt3

2. 在XSDT中检查每一个Entry,根据 Signature 找到BGRT
3. 解析 BGRT ,得到Logo 图像的地址,这个 Logo一定是 BMP格式的
4. 根据BMP格式能够解析出文件大小,直接Memory Dump即可得到结果

完整的代码:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include "acpi.h"
#include "acpi61.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_RUNTIME_SERVICES      *gRT;
extern EFI_SYSTEM_TABLE          *gST;

#pragma pack(push, 1)
/** BGRT structure */
typedef struct {
        EFI_ACPI_DESCRIPTION_HEADER header;
        UINT16 version;
        UINT8 status;
        UINT8 image_type;
        UINT64 image_address;
        UINT32 image_offset_x;
        UINT32 image_offset_y;
} ACPI_BGRT;

/** Bitmap file header */
typedef struct {
        UINT8 magic_BM[2];
        UINT32 file_size;
        UINT8 unused_0x06[4];
        UINT32 pixel_data_offset;
        UINT32 dib_header_size;
        UINT32 width;
        UINT32 height;
        UINT16 planes;
        UINT16 bpp;
} BMP;
#pragma pack(pop)

EFI_GUID        gEfiAcpi20TableGuid =   { 0x8868E871, 0xE4F1, 0x11D3, 
                        { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
                        
EFI_STATUS 
SaveToFile(
        IN UINT8 *FileData, 
        IN UINTN FileDataLength)
{
    EFI_STATUS          Status;
    EFI_FILE_PROTOCOL   *FileHandle;
    UINTN               BufferSize;
    EFI_FILE_PROTOCOL   *Root;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;

    Status = gBS->LocateProtocol(
                &gEfiSimpleFileSystemProtocolGuid, 
                NULL,
                (VOID **)&SimpleFileSystem);
                
    if (EFI_ERROR(Status)) {
            Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
            return Status;
    }

    Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"OpenVolume error \r\n");
        return Status;
    }
    Status = Root->Open(
                Root, 
                &FileHandle, 
                L"BIOSLogo.bmp",
                EFI_FILE_MODE_READ |
                EFI_FILE_MODE_WRITE | 
                EFI_FILE_MODE_CREATE, 
                0);
    if (EFI_ERROR(Status)){
        Print(L"Error Open NULL  [%r]\n",Status);
        return Status;
    }
    
    BufferSize = FileDataLength;
    Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
    FileHandle->Close(FileHandle);
    
    return Status;
}
                        
/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        EFI_STATUS      Status;
        EFI_ACPI_DESCRIPTION_HEADER                     *XSDT;
        EFI_ACPI_6_1_ROOT_SYSTEM_DESCRIPTION_POINTER    *RSDP;
        UINT8           *p;
        UINTN           Index;
        UINT64          *Entry;
        ACPI_BGRT       *pBGRT;
        BMP             *pBMP;
        
        //1. Find RSDP
        Status=EfiGetSystemConfigurationTable(&gEfiAcpi20TableGuid,(VOID**)&RSDP);
        if(EFI_ERROR(Status)) {
                Print(L"Can't find Acpi Table\n");
                return 0;
        }
        
        //2. Find XSDT
        Print(L"RSDP address [%X]\n",RSDP);
        Print(L"XSDT address [%X]\n",RSDP->XsdtAddress);
        XSDT=(EFI_ACPI_DESCRIPTION_HEADER*)RSDP->XsdtAddress;
        Print(L"XSDT information\n");
        p=(UINT8*)XSDT;
        
        //Show some DSDT information
        Print(L" Signature [%c%c%c%c]\n",*p,*(p+1),*(p+2),*(p+3));
        
        //3.Find entries
        Entry=(UINT64*)&XSDT[1];
        Print(L" Entry 0 @[0x%x]\n",Entry);
        for (Index=0;Index<(XSDT->Length-sizeof(EFI_ACPI_DESCRIPTION_HEADER))/8;Index++) {
           //Print(L" Entry [0x%x]",Index);
           p=(UINT8*)(*Entry);
           //You can show every signature here
           //Print(L" [%x][%c%c%c%c]\n",*Entry,*p,*(p+1),*(p+2),*(p+3));
           if ((*p=='B')&&(*(p+1)=='G')&&(*(p+2)=='R')&&(*(p+3)=='T')) {
                   pBGRT=(ACPI_BGRT*)p;
                   Print(L"  Found BGRT @[0x%X]\n",*Entry);
                   Print(L"  Image address @[0x%X]\n",pBGRT->image_address);
                   //Get BMP address
                   pBMP=(BMP*)(pBGRT->image_address);
                   Print(L"     [0x%X]\n",pBMP);
                   Print(L"     Image size  [0x%X]\n",pBMP->file_size);
                   Print(L"     Data offset [0x%X]\n",pBMP->pixel_data_offset);
                   Print(L"     Header size [0x%X]\n",pBMP->dib_header_size);
                   Print(L"           Width [0x%X]\n",pBMP->width);
                   Print(L"           Height[0x%X]\n",pBMP->height);
                   Print(L"           Planes[0x%X]\n",pBMP->planes);
                   Print(L"           BPP   [0x%X]\n",pBMP->bpp);                   
                    
                   SaveToFile((UINT8*)pBMP,pBMP->file_size);
                   Print(L"BIOS logo has been saved to 'BIOSLogo.bmp'\n");
           }
           Entry++;
        }
        return 0;
}

 

运行结果,测试平台为 Intel Kabylake-R HDK,使用的是 Byo BIOS

bgrt4

(不知道为啥,他家的 Shell分辨率很高, 字体极小,看起来简直要瞎)
取得的 BIOSLogo.bmp 结果如下
bgrt5

完整的代码下载:

FindBGRT

X64 Application下载:

fdefi

特别鸣谢sssky307在之前的文章中给出了EfiGetSystemConfigurationTable函数使得代码能够能够大幅度化简。

最新版本的 RU

本文提供的下载来自:https://firmwaresecurity.com/tag/ru-efi/

d8ec4-20171204183355-bmp

作者Blog 在: http://ruexe.blogspot.tw/ (需要翻墙)
Release 在 https://github.com/JamesAmiTw/ru-uefi

下载 5.20.0328

提起来这个工具的原因是有朋友给我留言说 RU 有查看 ACPI Table 的功能,虽然我几乎天天都在使用但是无印象,于是特地去找了验证一下,真的没有。不过这个版本和之前的相比增加了下面的功能,有需要的朋友可以直接使用,附件中有三个版本:32位、64位、DOS。

1.查看 UEFI Variable
2.AHCI MMIO
3.USB MMIO
4.Mass storage 设备的编辑
5.截图

最后特别感谢作者,来自 AMI 的 James Wang
======================================================================================================
2018年1月12日 来自微信的朋友“耳溫”,在公众号上留言,表示 ALT+F6可以实现 ACPI Table的读取,在此特别感谢指导

UEFI4BIOS_20180110_213822