Step to UEFI (216)InstallProtocolInterface 的简单研究

InstallProcotolInterface() 是用来安装 Protocol 的函数,之前有提及【参考1】。这次研究和实验了一下它的具体代码。

在\MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c有定义 BootService 的函数:

//
// DXE Core Module Variables
//
EFI_BOOT_SERVICES mBootServices = {
  {
    EFI_BOOT_SERVICES_SIGNATURE,                                                          // Signature
    EFI_BOOT_SERVICES_REVISION,                                                           // Revision
    sizeof (EFI_BOOT_SERVICES),                                                           // HeaderSize
    0,                                                                                    // CRC32
    0                                                                                     // Reserved
  },
………………………………………
  (EFI_CHECK_EVENT)                             CoreCheckEvent,                           // CheckEvent
  (EFI_INSTALL_PROTOCOL_INTERFACE)              CoreInstallProtocolInterface,             // InstallProtocolInterface
  (EFI_REINSTALL_PROTOCOL_INTERFACE)            CoreReinstallProtocolInterface,           // ReinstallProtocolInterface
………………………………………

具体实现在 \MdeModulePkg\Core\Dxe\Hand\Handle.c 文件中:

/**
  Wrapper function to CoreInstallProtocolInterfaceNotify.  This is the public API which
  Calls the private one which contains a BOOLEAN parameter for notifications

  @param  UserHandle             The handle to install the protocol handler on,
                                 or NULL if a new handle is to be allocated
  @param  Protocol               The protocol to add to the handle
  @param  InterfaceType          Indicates whether Interface is supplied in
                                 native form.
  @param  Interface              The interface for the protocol being added

  @return Status code

**/
EFI_STATUS
EFIAPI
CoreInstallProtocolInterface (
  IN OUT EFI_HANDLE     *UserHandle,
  IN EFI_GUID           *Protocol,
  IN EFI_INTERFACE_TYPE InterfaceType,
  IN VOID               *Interface
  )
{
  return CoreInstallProtocolInterfaceNotify (
            UserHandle,
            Protocol,
            InterfaceType,
            Interface,
            TRUE
            );
}

上述会跳转到同一个文件中的下面这个函数:

EFI_STATUS
CoreInstallProtocolInterfaceNotify (
  IN OUT EFI_HANDLE     *UserHandle,
  IN EFI_GUID           *Protocol,
  IN EFI_INTERFACE_TYPE InterfaceType,
  IN VOID               *Interface,
  IN BOOLEAN            Notify
  )
{
  PROTOCOL_INTERFACE  *Prot;
  PROTOCOL_ENTRY      *ProtEntry;
  IHANDLE             *Handle;
  EFI_STATUS          Status;
  VOID                *ExistingInterface;

  //
  // returns EFI_INVALID_PARAMETER if InterfaceType is invalid.
  // Also added check for invalid UserHandle and Protocol pointers.
  //
  if (UserHandle == NULL || Protocol == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (InterfaceType != EFI_NATIVE_INTERFACE) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Print debug message
  //
  DEBUG((DEBUG_INFO, "LABZ InstallProtocolInterface: %g %p\n", Protocol, Interface));

  Status = EFI_OUT_OF_RESOURCES;
  Prot = NULL;
  Handle = NULL;

比如,按照上述修改之后,运行下面的命令 build -a X64 -p OvmfPkg\OvmfPkgX64.dsc   即可生成 QEMU 的 BIOS:OVMF.fd,在 \Build\OvmfX64\DEBUG_VS2015x86\FV\ 。

将它copy 到 QEMU 的目录后,运行 qemu-system-x86_64.exe -m 256 -bios ovmf.fd -debugcon file:debug.log -global isa-debugcon.iobase=0x402 -net none 即可抓到串口 log。例如:

Memory Allocation 0x00000003 0xFEA4000 - 0xFEC8FFF
Memory Allocation 0x00000003 0xFEA4000 - 0xFEC8FFF
Memory Allocation 0x00000004 0xFE84000 - 0xFEA3FFF
Memory Allocation 0x00000004 0xFC00000 - 0xFDFFFFF
Memory Allocation 0x00000007 0xFE00000 - 0xFE83FFF
Memory Allocation 0x00000004 0xBF36000 - 0xBF55FFF
FV Hob            0x900000 - 0x12FFFFF
LABZ InstallProtocolInterface: D8117CFE-94A6-11D4-9A3A-0090273FC14D FEC3E30
LABZ InstallProtocolInterface: 8F644FA9-E850-4DB1-9CE2-0B44698E8DA4 FA11EB0
LABZ InstallProtocolInterface: 09576E91-6D3F-11D2-8E39-00A0C969723B FA11C18
LABZ InstallProtocolInterface: 220E73B6-6BDB-4413-8405-B974B108619A FA10BB0
LABZ InstallProtocolInterface: EE4E5898-3914-4259-9D6E-DC7BD79403CF FEC4B10

参考:

1. http://www.lab-z.com/stu100/

C# EXE 自动检验功能

最近比较恼火,因为测试机中毒了,除了在 Idle 状态下 CPU 占用率不断升高还有就是在测试 MS 的时候会遇到稀奇古怪的问题。比我更惨的是有同事收到了 IT 的警告邮件…….

因此,我有一个问题:能否设计出一个自我检查的程序,保证没有被篡改过?研究一番之后就开始动手编写。从原理上说,使用 WinPE头部没有用到的位置写入校验值,然后在代码中加入校验的内容,每次执行时先校验即可得知是否篡改。

因此,有2个程序,第一个是给被校验EXE 添加校验值的程序,另外一个是被校验的程序。

1.给 EXE 添加校验值的程序,选择在 DOS MZ 头的e_res2 位置添加 MD5值:

// DOS MZ头,大小为64个字节
typedef struct _IMAGE_DOS_HEADER {     
    WORD   e_magic;                     // EXE标志,“MZ”(有用,解析时作为是否是PE文件的第一个标志)
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // 非常重要,操作系统通过它找到NT头,NT头相对于文件的偏移地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 【参考1】

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace ConsoleApplication1
{
    class Program
    {
        public const int WINPEHEADERSize = 0x40;
        public const int RVSOffset = 0x28;
        private static byte[] GetMD5(byte[] message)
        {
            MD5 hashString = new MD5CryptoServiceProvider();
            return hashString.ComputeHash(message);
        }

        static void Main(string[] args)
        {
            byte[] TotalFile;
            byte[] FileNoHeader;
            byte[] MD5;

            if (args.Length != 1) {
                Console.WriteLine("Please input a file name");
                Console.ReadKey();
                Environment.Exit(0);
            }

            TotalFile = File.ReadAllBytes(args[0]);
            FileNoHeader = new byte[TotalFile.Length - WINPEHEADERSize];

            Buffer.BlockCopy(TotalFile, WINPEHEADERSize, FileNoHeader, 0, FileNoHeader.Length);

            MD5 = GetMD5(FileNoHeader);

            for (int i = 0; i < MD5.Length; i++)
            {
                TotalFile[i + RVSOffset] = MD5[i];
            }
            File.WriteAllBytes("E"+args[0], TotalFile);
            File.WriteAllBytes("Ex" + args[0], FileNoHeader); 
            Console.WriteLine("Complete. New file:"+ " E" + args[0]);
            Console.ReadKey();
        }
    }
}

程序接收一个文件名作为参数,然后将这个文件内容读取到TotalFile[] 中,去掉DOS 文件头后复制到FileNoHeader[] 中,之后计算这个数组的 MD5值,再将MD5放在前面提到的  e_res2 偏移处,将新生成的 EXE 写入  “E”+原文件名 的文件中。

2.带有自校验功能的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace ConsoleApplication3
{
    class Program
    {
        public const int WINPEHEADERSize = 0x40;
        public const int RVSOffset = 0x28;
        private static byte[] GetMD5(byte[] message)
        {
            MD5 hashString = new MD5CryptoServiceProvider();
            return hashString.ComputeHash(message);
        }

        static void Main(string[] args)
        {
            byte[] TotalFile;
            byte[] FileNoHeader;
            byte[] MD5;

            TotalFile = File.ReadAllBytes(System.Reflection.Assembly.GetExecutingAssembly().Location);
            FileNoHeader = new byte[TotalFile.Length - WINPEHEADERSize];

            Buffer.BlockCopy(TotalFile, WINPEHEADERSize, FileNoHeader, 0, FileNoHeader.Length);

            MD5 = GetMD5(FileNoHeader);

            int i = 0;
            while (i < MD5.Length) {
                if (MD5[i]!= TotalFile[RVSOffset+i]) {
                    Console.WriteLine("File is modified!");
                    Environment.Exit(0);
                }
                i++;
            }
            Console.WriteLine("File is OK!");
        }
    }
}

大部分和前面的程序类似,不同在于计算FileNoHeader的 MD5之后有一个比较过程,如果不一样就提示之后退出。

下面是一个测试的过程。首先用工具给 TestOK 添加签名,加入校验头的文件是 eTestOK;直接运行 TestOK, 因为头部并没有 MD5校验值,所以显示校验失败;运行 eTestOK 可以通过验证;之后用一个十六进制工具修改任意一个 BIT 都会有报错。

测试修改之前和修改之后

参考:

1. https://blog.csdn.net/weixin_30878501/article/details/99825394 WinPE基础知识之头部

Arduino 科学记数法库

科学记数法是一种记数的方法。把一个数表示成a与10的n次幂相乘的形式(1≤|a|<10,a不为分数形式,n为整数),这种记数法叫做科学记数法。 例如:19971400000000=1.99714×10^13。计算器或电脑表达10的幂是一般是用E或e,也就是1.99714E13=19971400000000。[参考1]

https://github.com/RobTillaart/Arduino/tree/master/libraries/MathHelpers 提供了一个 Arduino 科学计数法的库,能够将一个浮点数转化为科学计数法的表示。

测试代码:

//
//    FILE: mathHelperTest.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.0.1
// PURPOSE:
//
// HISTORY:

#include "MathHelpers.h"

uint32_t start;
uint32_t stop;

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);
  Serial.print("MATHHELPERS_VERSION: ");
  Serial.println(MATHHELPERS_VERSION);

  test1();
  test2();
  test3();
  test4();
  test5();

  test10();
  test11();
  test12();

}

void loop()
{}


void test1()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  float f = 1;
  for (int i = 0; i < 40; i++)
  {
    f *= 10;
    Serial.println(sci(f, 6));
  }
  Serial.println();

  f = 1;
  for (int i = 0; i < 50; i++)
  {
    f /= 10;
    Serial.println(sci(f, 6));
  }
  Serial.println();

  f = -1;
  for (int i = 0; i < 40; i++)
  {
    f *= 10;
    Serial.println(sci(f, 6));
  }
  Serial.println();

  f = -1;
  for (int i = 0; i < 50; i++)
  {
    f /= 10;
    Serial.println(sci(f, 6));
  }
  Serial.println();
  Serial.println();
}

void test2()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  float f = 1;
  for (int i = 0; i < 40; i++)
  {
    f *= (PI * PI);
    Serial.println(sci(f, 6));
  }
  Serial.println();
  f = 1;
  for (int i = 0; i < 50; i++)
  {
    f /= (PI * PI);
    Serial.println(sci(f, 6));
  }
  Serial.println();
  f = -1;
  for (int i = 0; i < 40; i++)
  {
    f *= (PI * PI);
    Serial.println(sci(f, 6));
  }
  Serial.println();
  f = -1;
  for (int i = 0; i < 50; i++)
  {
    f /= (PI * PI);
    Serial.println(sci(f, 6));
  }
  Serial.println();
  Serial.println();
}

void test3()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  float f = PI;
  for (int i = 0; i < 8; i++)
  {
    Serial.println(sci(f, i));
  }
  Serial.println();
}

void test4()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  float f = PI;
  for (int i = 0; i < 8; i++)
  {
    sci(Serial, f, i);
    Serial.println();
  }
  Serial.println();
}


void test5()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  float f = 1.0 / 0.0;
  Serial.println(sci(f, 6));
  f = 0.0 / 0.0;
  Serial.println(sci(f, 6));
  f = -1.0 / 0.0;
  Serial.println(sci(f, 6));
  // TODO find a -inf

  Serial.println();
}


void test10()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  Serial.println("HH:MM:SS");
  for (int i = 0; i < 7; i++)
  {
    uint32_t now = micros();
    Serial.println(seconds2clock(now));
  }
  Serial.println();
}

void test11()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  Serial.println("HH:MM:SS.nnn");
  for (int i = 0; i < 7; i++)
  {
    uint32_t now = micros();
    Serial.println(millis2clock(now));
  }
  Serial.println();
}

void test12()
{
  Serial.println();
  Serial.println(__FUNCTION__);

  Serial.println("time\tweeks\tdays\thours\tminutes");
  for (int i = 0; i < 7; i++)
  {
    uint32_t now = micros();
    Serial.print(now);
    Serial.print('\t');
    Serial.print(weeks(now), 3);
    Serial.print('\t');
    Serial.print(days(now), 3);
    Serial.print('\t');
    Serial.print(hours(now), 2);
    Serial.print('\t');
    Serial.println(minutes(now), 2);
  }
  Serial.println();
}


// END OF FILE
Arduino 科学计数法

库下载

参考:
1.https://baike.baidu.com/item/%E7%A7%91%E5%AD%A6%E8%AE%B0%E6%95%B0%E6%B3%95?fromtitle=%E7%A7%91%E5%AD%A6%E8%AE%A1%E6%95%B0%E6%B3%95&fromid=756685 科学记数法

M.2 NMVE PICE 转接卡

最近在做Modern Standby 的实验,测试结果显示CPU进入了PC10,但是一直被硬盘 Block。板子上使用的是通过 PCIE-M.2 转接板转接后插在 PCIE Slot 上的 NVME SSD 。于是,我觉得问题出在转接板上。

PCIE M.2转接卡

对于M.2 上的ModernStandby来说,NVME 上的CLKREQ 拉高通知 PCH/SOC 停止发送 PCIE Clock。如果出现问题通常都是这个Pin导致的。于是,怀疑转接卡是否将 PCIE 上的CLKREQ Pin正确转接到了 M.2 接口上。

CLKREQ 在 PCIE金手指上的位置 【参考1】

接下来找到 M.2 的引脚定义【参考2】:

M.2 引脚定义
M.2 CLKREQ Pin和实物对应(特别注意,为了方便对应右侧的照片是旋转之后再左右镜像的)

进一步追查,这个Pin连接了一个LED,然后到了GND 上(万用表测试)。

CLKREQ 在转接板上的位置

为了验证上面的正确性,再挑选一个PICIE 金手指上的 REFCLKp pin(A侧13,参考本文第一幅),使用万用表测量,找到M.2上面的引脚如下:

这个说明参考的M.2的引脚资料是正确的。

进一步结论:转接卡没有将 PCIE 上的 CLKREQ 转接给M.2。

最终,通过飞线的方式,将 M.2 的CLKREQ 和PCIE Slot 上的 CLKREQ 相连,在 RVP (CML-V)上测试无问题,运行 ModerStandby 的 Stress Test,一晚跑50次顺利通过。这个从侧面也印证了:M.2 上的 PCIE 和 PCIE Slot 没有本质差别。很多时候如果你发现供应商提供的某些 M.2 接口的设备无法在你的设计上影响 MS 功能,不妨转接在 RVP 上验证之。

参考:

1. https://baike.baidu.com/item/pcie

2. http://read.pudn.com/downloads794/doc/project/3133918/PCIe_M.2_Electromechanical_Spec_Rev1.0_Final_11012013_RS_Clean.pdf  

DSLogic逻辑分析仪试用

最近入手了 DSLogic 逻辑分析仪,买的是 DSLogicPlus 个人版,这个是个人使用的顶配版本。选择这个品牌的原因是一个网友的推荐,还有从搜索到的资料来看这款最开始是在KickStarter上众筹的产品,总共筹集到了11万美元。想必质量有保证。

梦源实验室 DSLogic

几个版本比较如下,可以看到这个版本对于个人来说完全够用了:

DSLogic 版本比对

开箱照:

DSLogic Plus 开箱照

首先用它抓取PS2 鼠标协议,于是找了一个PS2鼠标和一个PS2转USB Dongle,这一套是能够在我的电脑上正常工作的:

刚用的时候还是能够正常抓到一些数据的:

逻辑分析仪抓取 PS2 鼠标数据

但是很快就跑偏了,下面 Channel 0 的数据是错误的,应该的大部分时间为高,部分时间为低,但是不知道为什么出现大部分时间为低,少数时候为高的情况。这种情况下数据的解析更是“太监开会—无稽之谈”。

为了搞清楚问题,我还将两个通道接到同样的引脚上,测试结果更是让人大吃一惊。

联系售后邮箱直接让我邮寄回去检测。之后我还直接给他们技术人员打电话。对方也表示这个事情比较奇怪,让我更换不同的线,实验之后也没有效果。

经历了4天的研究,我发现最好的解决办法就是:退货。趁着7天无理由退货赶紧。调试调试设备这个并不是明智的选择。所以,建议如果有购买逻辑分析仪设备需求的朋友,最好拿到手后立即实验,否则即便最简单的协议也可能存在解析问题。

WDTF 安装器

测试 MS 功能, WDTF 是必须的,但是每次通过 WDK 安装会比较麻烦【参考1】,因此制作了一个 WDTF 安装器,运行之后界面如下:

WDTF 安装器

上部显示当前 Windows 版本,中间按钮选择你要安装的,点击之后即可自动完成安装。

需要注意的是:这个版本带有自校验功能,如果你发现无法运行,那么最好进行杀毒避免因为病毒干扰测试。

参考:

1.http://www.lab-z.com/wdtfin/ WDTF的安装

C# Console 获取当前程序名称

方法来自 https://stackoverflow.com/questions/7881148/how-do-i-get-the-exe-name-of-a-c-sharp-console-application

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(System.Reflection.Assembly.GetExecutingAssembly().Location);
            Console.ReadKey();
        }
    }
}

获得当前 Application 的路径

推荐 Toshiba 的一款 NVME

最近在 CML-V 的 RVP 上测试 MS Stress,开始使用 Samsung 的一款,后来发现总会有 fail 的情况发生(大约是 2/5 )。后来换了 Toshiba 的KXG6AZNVT02 这款(PCIE NVME M.2接口),首先感觉是速度明显变快(这个应该和容量有关系,这款是 1T 容量的);轻松跑了一晚上(30s idle, 15 min sleep ),50次都 Pass。

如果你在测试Stress 的时候遇到 fail 的情况不妨找这款进行实验,或者说多换几个NVME硬盘试试看。

20200811 补充, 使用这个硬盘在 CML-S RVP 上关闭各种设备只保留 NVME 之后测试得到的结果:

示波器直接测量 SLPS0 信号,测试水平方向一格 20s

需要说明的是:

1.SLPS0 是低有效,意思是睡下之后应该是低,醒来的时候变高。上图测试点是在LED 上,所以是反的,即高为睡,低为醒来;

2.最好的状态下,180S SLPS0 会自动唤醒一次,这个是正常现象。

3.3V Arduino Proi Micro 的使用和维修

市面上最常见的 Arduino Pro Micro 是5V的,除此之外还有一种是 3.3V的,他们之间的区别除了电压之外还有主频不同,5V版本是16MHz,3.3V的是8Mhz。

前一段我入手了一个3.3V版本的,主要目标是给 USB Host Mini 使用。拿到手之后错误的使用了 Leonardo进行上传,马上板子就变砖了。所以,这里特别强调必须使用LilyPad USB 编译上传!

一定要选择这个

讲完了使用下面讲如何恢复,找出了 USBTinyISP(极客工坊出品的)。

USBTinyISP接口定义

接线顺序:

USBTinyISPPro Micro 3.3V
1.MISOD14.MISO
2.VCCRAW(特别注意不是VCC)
3.SCKD15.SCLK
4.MOSID16.MOSI
5.RESETRST
6.GNDGND

之后使用 Arduino 自带刷Bootloader的功能最稳妥(板子要选好 LilyPad)

Arduino 烧写 Bootloader

写入之后还会读取校验,之后板子就恢复正常了。

参考:

1.https://www.sparkfun.com/products/12640 这里可以找到电路图