libpng warning: iCCP: known incorrect sRGB profile

今天我在编译一个 C# Console 的代码时候忽然出现了“libpng warning: iCCP: known incorrect sRGB profile”一堆的 Warning。因为前几天编译运行的时候还没有遇到过这样的事情。研究了很久。网上的资料都是说 libpng 是一个 Png 的解码库,但是我的代码没有Png 相关的任何内容。

最终,我写了一个只有一行 Console.WriteLine() 输出的程序,运行之后出现了同样的现象我才确定不是因为我的代码导致了问题。

搜索的时候看到一篇文章提到可能和输入法相关。于是我去控制面板中根据时间排序惊奇的发现 QQ拼音输入法 排在第一位,然后想起来今天手欠,看到输入法提示选择了升级。卸载之后再次运行,世界终于清静了。

很早之前遇到过这样的事情,那次是搜狗输入法导致我的 Delphi 无法调试编译好的EXE文件,更加让人觉得崩溃。半年之后才发现是搜狗导致的,于是果断卸载之。没想到今天又遇到这样的情况。

ps: 更让人吃惊的是,现在的 QQ 输入法就是搜狗做的“
2013年09月16日,腾讯4.48亿美元注资搜狗,宣布将搜搜和QQ输入法业务与搜狗现有业务进行合并。 ”看起来他们已经整合的差不多了。

Step to UEFI (168)Setup 中替换字符串

以前设置BIOS的门槛挺高,至少对于不熟悉英文的人进入满屏英文的Setup界面很容易变成丈二和尚———摸不着头脑。但是随着时代的进步,汉化Setup界面并不复杂,在 EDK2 上已经加入了多语言支持功能。但是,某些情况下,Setup中的选项不是来自BIOS Source Code 而是第三方的UEFI ROM,遇到这样的情况,经常会出现一片中文夹杂了一行英文的情况。为了更详细的描述这样的情况,用下面的例子来进行说明。在UDK2017中,有一个 RAM Disk 功能,这个功能在之前的系列文章中有介绍过。默认情况下,NT32Pkg 没有加载这个功能,但是编译过程中会生成 RamDiskDxe.efi。实验如下:


1. 先进入 Setup ,可以看到目前是没有相关选项的

2. 进入 Shell 之后,加载 RamDiskDxe.efi

3. 再进入 Setup,可以看到已经出现了 Ram Disk 的选项

这里,实验的目标就是在不修改 RamDiskDxe.efi 的情况下,当语言选择为法语时,修改 Ram Disk 的名称。
首先研究一下 Setup 的选项,经过研究发现在\MdeModulePkg\Library\UefiHiiLib\HiiString.c中有一个HiiGetString的函数。从注释可以看出,这个函数负责返回 Hii Package中的字符串,因此,我们在其中添加代码,当发现语言设置为法语时,并且取得 Ram Disk 相关的String后,用我们定义的 String 替换掉正常的返回值从而实现“翻译”的动作。

/**
  Retrieves a string from a string package in a specific language.  If the language
  is not specified, then a string from a string package in the current platform 
  language is retrieved.  If the string can not be retrieved using the specified 
  language or the current platform language, then the string is retrieved from 
  the string package in the first language the string package supports.  The 
  returned string is allocated using AllocatePool().  The caller is responsible 
  for freeing the allocated buffer using FreePool().
  
  If HiiHandle is NULL, then ASSERT().
  If StringId is 0, then ASSET.

  @param[in]  HiiHandle  A handle that was previously registered in the HII Database.
  @param[in]  StringId   The identifier of the string to retrieved from the string 
                         package associated with HiiHandle.
  @param[in]  Language   The language of the string to retrieve.  If this parameter 
                         is NULL, then the current platform language is used.  The 
                         format of Language must follow the language format assumed 
                         the HII Database.

  @retval NULL   The string specified by StringId is not present in the string package.
  @retval Other  The string was returned.

**/
EFI_STRING
EFIAPI
HiiGetString (
  IN EFI_HII_HANDLE  HiiHandle,
  IN EFI_STRING_ID   StringId,
  IN CONST CHAR8     *Language  OPTIONAL
  )

最终代码如下:

  //
  // Return the Null-terminated Unicode string
  //
  //LABZ_Start
  if (String!=NULL) {
          DEBUG((DEBUG_INFO, "Get [%s]\n",String));
          if (StrCmp(String,L"Choisir la Langue")==0) { //这里判断一下语言是否已经选择为法语
                  DEBUG((DEBUG_INFO, "Let's speek French\n"));
                  FrenchX=TRUE; //做一个标记
                }
          //只有在切换为法语以及发现需要替换的字符串时再下手
          if ((FrenchX)&&(StrCmp(String,L"RAM Disk Configuration")==0)) {
                  DEBUG((DEBUG_INFO, "Replace item string\n"));
                  //"RAM Disk Configuration"
                  //"Jia zhuang fayu       "
                  StrCpy(String,L"Jia zhuang fayu       ");
                }      
          }
  //LABZ_End

需要特别注意的是:返回值是放在 String 中的,它是由HiiGetString()函数进行空间分配,然后由调用者负责分配。因此,我们需要在 String 中修改,而不能简单的用另外一个变量来赋值给 String,否则在运行时会出现不可预料的错误。

重新实验,加载 RamDiskDxe.efi ,再选择法语后运行结果:

在 Log 中我们可以看到下面的 Debug Message:
1.发现语言选项切换为法语

2.发现选项,替换之。

上面的实验都是在 UDK2017 NT32中进行的。完整的代码和 RamDiskDxe.efi 可以在这里下载:

额外说两句,法国是非常有名的老牌资本主义国家,譬如每个人耳熟能详的名言“知识就是力量,法国就是培根”。

不过从目前的新闻来看,这个国家正在一步步走向衰落。
纵观历史,我们所处的时代正是国运上升的时期,也许是我们的幸运吧。希望通过我们的努力能让我们的后辈有着更多的选择。

Teensy 打造USB纯数字麦克风

很多人喜欢在文章中引用名人名言来证明自己的观点,但是大多数名人名言都是有前提条件的,甚至前后段意义完全相反。比方说:打算宽恕别人的时候可以说“以德报怨”是中华民族的传统美德;打算复仇的时候就可以完整的引用“以德报怨,何以报德”。又比方说:在作文中我们用了无数次的“天才就是1%的灵感加上99%的汗水”;但是爱迪生要表达的是“天才就是1%的灵感加上99%的汗水,但那1%的灵感是最重要的,甚至比那99%的汗水都要重要”。所以,在我看来,这样的名人名言除了凑凑字数别无大用。与之相反,理科生的世界中,各种定理和名言充满了简洁和力量。比如:奈奎斯特教导我们说“要从抽样信号中无失真地恢复原信号,抽样频率应大于2倍信号最高频率。 抽样频率小于2倍频谱最高频率时,信号的频谱有混叠。 抽样频率大于2倍频谱最高频率时,信号的频谱无混叠。”

这句话可以指导我们对音频进行采样,语音信号频率范围是:300Hz~3.4kHz,因此,电话系统的 8000Hz 采样率对于传递语音信息已经足够了。

要想让电脑处理音频,先要用声能到电能的元件进行转换,再经过放大之后达到一定幅度才能进行采样(否则只有100mv不适合采样),可以看到在这个过程中会引入需要经过很多步骤而每一个步骤都会引入不确定性,因此,很长一段时间以内,音频处理都算得上是玄学。

最近我接触到了数字麦克风(MEMS micphone),和传统的相比数字麦克风输出的是数字信号,而不是通常的模拟信号。产品的基本形态有方形的,也有圆形的,形状与通常的驻极体传声器类似。基本结构为:一个换能器,用于产生代表声信号的模拟信号(通常由驻极体振膜及其背电极来完成的);和大于1的单比特∑-Δ调制器模数变换器,按照过抽样速率并以∑-Δ调制比特流的形式从模拟信号中产生数据输出信号。

数字麦克风的最大优点是抗干扰能力强,无需像传统传声器那样内置高频滤波电容、滤波器电路。数字麦克风因其固有的特点,不会受到那些来自电脑、网络、射频际磁场信号源的干扰、影响,因此在接入的时候,无需采用屏蔽线,可以有效地利用相关产品的有限空间。【参考1】

这次使用的是SPH0645LM4H (SPH0645) 数字麦克风模块

特别注意,有孔的一面是正面,需要面对音源。

硬件连接方式

Teensy 3.2     SPH0645LM4H

        GND             SEL

        D23              LRCL

         D13             DOUT

         D9                     BCLK

         GND                     GND

   3.3V               3V 

       

https://www.pjrc.com/teensy/gui/index.html?info=AudioInputI2S# 可以直接使用图形方式设定连接

可以用这样的方式直接生成需要的文件头部。最终的源代码:

#include <Audio.h>

#include <Wire.h>

#include <SPI.h>

#include <SD.h>

#include <SerialFlash.h>

 

// GUItool: begin automatically generated code

AudioInputI2S            i2s1;           //xy=223,84

AudioOutputUSB           usb1;           //xy=435,172

AudioConnection          patchCord1(i2s1, 0, usb1, 0);

// GUItool: end automatically generated code

 

void setup() {

  // put your setup code here, to run once:

   AudioMemory(80);

}

 

void loop() {

}

除了上面提到的数字麦克风的优点之外,它还有如下缺点:

1.     对于处理器有要求。比如 Teensy 3.X 能够支持这款麦克风,最主要是因为它上面有对应的处理单元。而像 Arduino Uno,并没有这样的硬件基础,因此不能直接使用;

2.     价格昂贵。本次试验用到的模块价格为 48元。单纯的数字麦克风数字麦克风模块也要十几块;

另外,现在绝大多数的笔记本都采用这种麦克风作为内置麦克风。相信未来,这种麦克风会有更大的用途。

参考:

1. https://baike.baidu.com/item/%E6%95%B0%E5%AD%97%E9%BA%A6%E5%85%8B%E9%A3%8E/6847775

使用VirtualBox 进行UEFI 网络实验

最近研究了一下 UEFI 下的网络编程。需要解决的首要问题是:如何进行相关实验。对于我们来说最便捷的莫过于使用虚拟机,《UEFI 原理与编程》推荐的是使用 NT32 环境加SnpNt32Io 来完成模拟。但是我这边实验一直不成功,可能是Winpcap 安装不正确导致的。最终经过研究比较,确定了使用 VirtualBox作为实验对象。

第一个目标是要讲实验用到的驱动传入到虚拟机中。我们创建虚拟硬盘然后将需要内容 COPY到其中。

1. 打开 Disk Management,使用 Create VHD 功能

2. 创建一个 500MB,Fixed Size (可能是出于兼容性原因,这里建议用 Fixed Size)

3. 创建之后需要分区和格式化,Windows只能格式化成 NTFS 格式,这里需要使用 DiskGenius来完成。直接创建一个500MB大小的 ESP分区。然后文件系统需要为 FAT32。保存分区后格式化。

4. 上述操作结束之后,可以在 Windows 中对刚出现的盘符进行操作,COPY进需要的文件。比如:这里放入 Intel 网卡驱动和 UDK2018的WebServer 的UEFI Applicaiton

5. 再到 Disk Management 中 Detach VHD

至此,已经完成虚拟机硬盘的创建,并且放入了必须的文件。下面就是在 VirtualBox中验证的过程。

6. 在 VirtualBox 中新建一个虚拟机,一定要 Enable EFI ,此外,要将刚才制作的 VHD 文件挂上去。

上面的设置完成之后就可以进行网络方面的测试了。有几个基本的命令:
1. ifconfig –l eth0 //查看第一个网卡的配置信息
2. ifconfig –s eth0 DHCP //设置使用 DHCP 取得IP 地址,因为使用 Bridge 的网卡设定,新创建好的虚拟机和HOST主机一样是“暴露”在网络上的,我的 HOST 主机是通过 DHCP 获得IP的,因此虚拟机同样可以从 DHCP 上获得IP

上述完成之后,运行 WebServer.efi ,运行之后这个EFI Application会监听网络 Port 80,等待 HTTP 请求。在 Host 机器中,使用 Chrome 访问虚拟机的IP 80端口,结果如下:

就是说访问到了虚拟机提供的 WebServer 。
额外的话:
1. 除了 VirtualBox , QEMU 也是一个很好的选择。同时 QEMU 能够运行编译出来的BIOS Image 相对来说更胜一筹。但是我在设置网络的时候,QEMU 一直没能实现 Bridge ,只有 NAT,可能是因为我的HOST 是 Windows 导致的。只有NAT这对于使用来说显示颇多限制,因此,最终选择 VirtualBox 来实现模拟环境。
2. DiskGenius注册版可以直接编辑VHD 文件,挂载之后可以直接对其写入文件。普通免费版没有这个功能。
3. VHD 还可以使用ImDisk 直接挂载到 Windows 上然后写入数据。官方网站 http://www.ltr-data.se/opencode.html/
4. 实验发现 VirtualBox 的UEFI BIOS无法识别 IDE 的光驱,但是 SATA 的可以。用制作 ISO 的方法直接传递EFI Application 也是一个好方法。

20210531 补充

1.虚拟机中网络设置如下

2.实验用到的编译好的 WebServer.efi 和网卡驱动下载

Step to UEFI (167)Shell 下的二维码显示

二维码是一种非常方便的对手机输入的方式,现在的日常生活中二维码几乎随处可见,无论是手机支付还是网页分享都能看到它的身影。最近研究了一下如何在生成二维码,找到【参考1】提供的一个C语言库。经过简单调试即可在 Shell 下跑起来。
实例代码如下:

#include <stdio.h>
#include <string.h>
#include "qr_encode.h"

int main(void)
{
	int side, i, j, a, ecclevel;
	uint8_t bitdata[QR_MAX_BITDATA];
	char str[2048];

	printf("ECC Level [LMQH]: ");
	if (!fgets(str, sizeof(str), stdin)) {
		return 1;
	}
	switch (str[0]) {
	case 'l':
	case 'L':
		ecclevel = QR_LEVEL_L;
		break;
	case 'm':
	case 'M':
	default:
		ecclevel = QR_LEVEL_M;
		break;
	case 'q':
	case 'Q':
		ecclevel = QR_LEVEL_Q;
		break;
	case 'h':
	case 'H':
		ecclevel = QR_LEVEL_H;
		break;
	}

	printf("Enter string: ");
	if (!fgets(str, sizeof(str), stdin)) {
		return 1;
	}
	// remove newline
	if (str[strlen(str) - 1] == '\n') {
		str[strlen(str) - 1] = 0;
	}

	side = qr_encode(ecclevel, 0, str, 0, bitdata);

	printf("side: %d\n", side);

	for (i = 0; i < side + 2; i++) printf("██");
	printf("\n");
	for (i = 0; i < side; i++) {
		printf("██");
		for (j = 0; j < side; j++) {
			a = i * side + j;
			printf((bitdata[a / 8] & (1 << (7 - a % 8))) ? "  " : "██");
		}
		printf("██");
		printf("\n");
	}
	for (i = 0; i < side + 2; i++) printf("██");
	printf("\n");

	return 0;
}

运行之后会要求你选择容错程度,容错性越高面积和复杂度会越高。然后要求你输入需要编码的字符串,之后就生成 ASCII 组成的二维码了。编译后的 EFI 可以在 NT32或者实体机上运行,下面就是在 KBL-R HDK 上运行的结果(有兴趣的朋友可以直接用手机扫描一下):

完整的代码和库下载:

参考:
1. https://github.com/trezor/trezor-qrenc

保险中的“不可抗辩条款”

最近偶然看到了“不可抗辩条款”,好奇心驱使之下对其进行了一番研究。有兴趣的朋友可以多读几遍这篇文章,有机会能够以此判断对你推销保险的人是否专业。

先说说背景:保险公司和美国大学一样,普遍实行“宽进严出”。意思是买保险的时候审核不严格,承诺也很多如同结婚之前的男人一样;等你需要用的时候就严格了,国内很常见的是“非常抱歉,您没有按照约定出现问题,所以无法赔偿”。

国外保险公司也一样,这样就导致正常人很难相信保险,也对保险公司产生了严重的负面影响。“1848年英国伦敦寿险公司出售的产品中首次应用了不可抗辩条款。即合同生效一定时期之后,保险公司就不得以投保人误告、漏告等为理由拒绝赔付。这一条款一经推出,就受到了投保人的普遍欢迎,极大地改善了该公司与消费者的关系,为公司赢得了信任。其后该条款被其他公司纷纷仿效,在寿险业得到了极大的推广。1930年,不可抗辩条款首次成为法定条款,由美国纽约州保险监督管理部门在该州保险法例中加以规定,要求所有寿险保单必须包含此条款,以约束保险人的行为,保护保单持有人的利益,防止保险公司不当得利,最终保护整个保险业的健康发展。其后不可抗辩条款通过立法的形式,成为了绝大多数发达国家寿险合同中的一条固定条款”【参考1】

简单的说就是合同生效之后,过了一定的期限,无论你找什么理由,比如之前未能如实告知,进门的时候先迈左腿等等,都必须按照合同约定进行理赔。大致就是这样的意思。

国内2009年《保险法》加入了“不可抗辩条款”(说到这里吐槽一下,我小时候抨击资本主义社会的一条就是:各种法律多如牛毛,各种条款复杂无比,因此,相关问题必须交给专业人士进行处理,充分体现了资本主义法律的虚伪。现在国内法律也有多如各种毛的趋势,这种事情是社会发展的必然结果,和制度没多少关系)。具体条款如下(中华人民共和国保险法(2015年修正)):

第十六条

订立保险合同,保险人就保险标的或者被保险人的有关情况提出询问的,投保人应当如实告知。 投保人故意或者因重大过失未履行前款规定的如实告知义务,足以影响保险人决定是否同意承保或者提高保险费率的,保险人有权解除合同。 前款规定的合同解除权,自保险人知道有解除事由之日起,超过三十日不行使而消灭。自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任。 投保人故意不履行如实告知义务的,保险人对于合同解除前发生的保险事故,不承担赔偿或者给付保险金的责任,并不退还保险费。 投保人因重大过失未履行如实告知义务,对保险事故的发生有严重影响的,保险人对于合同解除前发生的保险事故,不承担赔偿或者给付保险金的责任,但应当退还保险费。 保险人在合同订立时已经知道投保人未如实告知的情况的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任。 保险事故是指保险合同约定的保险责任范围内的事故。【参考2】

我第一遍看过之后,关注在“自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任。”但是后面又说了“投保人因重大过失未履行如实告知义务,对保险事故的发生有严重影响的,保险人对于合同解除前发生的保险事故,不承担赔偿或者给付保险金的责任,但应当退还保险费。”感觉这个操作空间就很大了。

为了正确理解,我去法律裁判文书网搜索案例,虽然我们国家并非依据案例判决,但是这仍然是最权威的解读。

搜索检索条件:全文检索:保险纠纷+不可抗辩

案例1【参考3】

被保险人要求太平洋人寿保险公司日照中心支公司支付理赔。 2010年6月25日签署合同。然后 2017年3月29日,诊断为XX完全性左束支传导阻滞心功能IV级,××症。要求理赔,公司拒绝。理由是:2010年7月12日,原告入住日照市人民医院,××。根据保险合同的约定,原告所患××病情发生在等待期,被告不应承担保险责任,并无息退还原告所缴纳的保费,同时该合同终止。2017年3月29日,原告再次因××入院,后原告向被告申请理赔,被告已经退还原告保费,原告在投保时被告已经向有关说明了保险责任等待期的内容,对等待期条款予以认可,故应驳回原告的诉讼请求。

最终法院认定:保险公司没有办法证明是同一个问题一直如此,并且保险公司一直收费,时间这么久,超过2年不可抗辩。判决保险公司需要理赔。

案例2【参考4】:

原告被保险人要求新华人寿保险股份有限公司青岛分公司支付赔偿。 2013年11月16日购买终身重大XX保险。2016年2月19日原告因患左侧卵巢粘液性囊腺癌急需医疗费而向被告递交索赔文件申请理赔,被告拒赔。被告新华人寿保险股份有限公司青岛分公司辩称,第一,原告在投保之前三年已检出左侧卵巢××,原告在此情况下向被告投保,而且是短期保险,明显属于带病投保,隐瞒事实真相,没有尽到如实告知义务,因此被告有权拒绝理赔。第二,原告所患左侧卵巢粘液性囊腺癌并没有达到涉案保险合同约定的理赔标准,被告拒绝赔付符合合同约定,应驳回原告诉讼请求。

最终法院裁定,虽然2011、2012、2013年原告体检时检查出左侧卵巢见囊性无回声,但是投保时仍然选择“否”,已经违反如实告知,但是被告2年内没有提出解除合同,因此判决保险公司需要理赔。

案例3【参考5】:

原告被保险人诉被告百年人寿保险股份有限公司内蒙古分公司赤峰中心支公司保险纠纷。被保险人2014年12月19日买的重大疾病保险。2017年11月21日被保险人因颅内胶质瘤入院治疗。申请赔偿的时候被拒绝。理由是:被保险人2012年11月6日因原告被诊断患有“星形细胞瘤”在赤峰市医院住院并进行手术治疗。保险公司以被保险人投保时未如实告知、系“保险欺诈”为由拒绝理赔并于2018年2月5日作出《理赔决定通知书》整案拒赔。自合同成立后原告已连续缴费3年。

最终法院认定:原告投保时未如实告知,原告具有主观恶意,系恶意骗保的不诚信行为,并违反保险合同法的规定,应赋予被告解除权,且两年不可抗辩期间适用的前提是保险合同成立两年后新发生的保险事故,因此,保险合同成立前已经发生保险事故不适用《保险法》第十六条的规定,故被告不应理赔。但是,因为保险公司在与被保险人签订保险合同时未尽到职责,在被保险人投保时若要求提供体检报告,就能阻止被保险人带病投保,避免此类保险纠纷的发生

最终要求保险公司支付赔偿。

这个案例让我感觉就是晕,逻辑非常混乱的感觉,也许是因为走得简易程序?这个事情还有后续【参考6】,前文的被保险人因病去世,保险公司提起上诉不希望赔偿,然后被保险人的姐姐继承财产。双方无新证据提交。最终法院认定维持原判,保险公司需要赔偿。

案例4【参考7】

这是二审的案例。一审的是被保险人起诉平安人寿襄阳中心支公司拒赔。审理结果是:拒赔有道理。

被保险人 2014年8月11日购买平安人寿襄阳中心支公司销售的人身保险,后来缴纳了三期。但是,被保险人2009年1月就开始患有霍奇金氏淋巴瘤并前后多次住院接受治疗。签订保险时没有如实告知。在合同成立的2年零7个月后,即2017年2月8日至2017年5月24日期间,上诉人先后分别在襄阳中心医院、协和医院住院治疗99天。之后申请理赔被拒绝。

案例5【参考8】

这是上诉的案子,中国太平洋人寿保险股份有限公司周口中心支公司上诉不服一审判决。

大概的案情:2015年6月17日 投保人时佳为其父亲时富根(××)在中国太平洋人寿保险股份有限公司投保,保险合同生效日为2015年6月17日。投保人时佳为时富根在中国太平洋人寿保险股份有限公司周口中心支公司已经连续两年交纳保险费(这句话没明白什么意思)。2017年5月9日,时富根检查出来肝癌。然后申请理赔。中国太平洋人寿保险股份有限公司周口中心支公司于2017年10月13日做出《理赔决定通知书》,以××时富根投保前因原发性肝癌住院,××投保为由,拒不承担给付保险金的义务。另查明,2015年6月17日时富根因病在驻马店中心医院住院8天,被诊断为原发性肝癌。

一审认定:本案双方自2015年6月17日签订保险合同成立并生效以来,已经超过两年,时富根并且连续两年交纳保费。无论时富根是否存在故意或重大过失未履行如实告知义务的情形,均不能成为拒绝给付保险金的理由,故时富根诉请判令支付××保险金150000元,于法有据,法院予以支持。

二审认定,本案的争议焦点为:在投保前时富根已身患××,保险公司应否理赔。

本案中,双方之间的保险合同自2015年6月17日签订成立并生效以来,已经超过两年,投保人连续两年缴纳保费。××时富根虽为××投保,未向保险公司尽到如实告知义务。但保险合同的解除是保险人拒绝承担保险责任的前提,保险人应当在不可抗辩期间内解除合同。根据《中华人民共和国保险法》第十六条第三款“前款规定的合同解除权,自保险人知道有解除事由之日起,超过三十日不行使而消灭。自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任”的规定,保险人解除保险合同应受不可抗辩期间的限制:一是在知道有解除事由之日起三十日内;二是自合同成立之日起二年之内,超过任何一个期间解除权即丧失。在本案所涉保险合同未被解除的情况下,对双方具有约束力,保险公司应当按照本案所涉保险合同的约定承担给付保险金的责任。

最终,法院认为,《中华人民共和国保险》第十六条第三款规定“前款规定的合同解除权,自保险人知道有解除事由之日起,超过三十日不行使而消灭。自合同成立之日起超过二年的,保险人不得解除合同;发生保险事故的,保险人应当承担赔偿或者给付保险金的责任”,该条款虽然是对保险人解除合同的权利加以限制,但并不意味着投保人可以滥用此条款来进行恶意投保并拖延理赔的不诚信行为,因此该条款所规定的两年不可抗辩期间适用的前提是保险合同成立两年后新发生的保险事故。而本案中,上诉人廖启来投保前所患疾病与其提出保险理赔所患疾病系同一疾病,属于保险合同成立前已经发生的保险事故,并非投保后经医院确诊初次发生“重大疾病”,不属于保险合同约定的保险责任,故保险公司不应赔偿。因此上诉的被保险人来此项上诉理由不能成立,本院不予支持。

案例6【参考9】

这是上诉的案件,一审被保险人败诉。

案情如下: 2011年6月,被保险人和中国人寿保险公司签订合同。2015年2月2日,被保险人因重症肌无力到医院住院治疗,并于2015年3月4日以此次住院为由向中国人寿保险公司申请理赔,保险公司发现她在2011年3月投保前因重症肌无力住院。

一审判决:被保险人带病投保的行为违背了当事人在从事民事行为中应当遵循的诚实信用原则,根据《中华人民共和国合同法》第五十四条第二款“一方以欺诈、胁迫的手段或者乘人之危,使对方在违背真实意思的情况下订立的合同,受损害方有权请求人民法院或者仲裁机构变更或撤销”的规定,中国人寿保险公司有权撤销保险合同,且中国人寿保险公司行使撤销权并未超过《中华人民共和国合同法》第五十五规定的一年期限,因此,对中国人寿保险公司撤销保险合同的请求,予以支持。

二审结论:上诉人的上诉理由不成立,本院不予支持。原审判决认定事实清楚,适用法律正确,应予维持。

看过上述案例之后,对于投保之前患病,缴纳保费超过2年,能否使用不可抗辩条款的答案仍然是无法确定。如果你是之前患病距离投保时间比较长,比如大于2年,然后再次患病距离首次投保时间也比较长,比如超过3年,拒绝理赔之后直接起诉胜率会比较大。上述案例中,起诉费用不高,普遍在1-2千,偶尔还能打折,但是整体周期会比较长。

参考:

1. https://baike.baidu.com/item/%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9%E6%9D%A1%E6%AC%BE/2491542?fr=aladdin   不可抗辩条款

2. https://duxiaofa.baidu.com/detail?searchType=statute&from=aladdin_28231&originquery=%E4%BF%9D%E9%99%A9%E6%B3%95&count=182&cid=995d3f3bcf96060c74df1af2f6fce4d8_law中华人民共和国保险法(2015年修正)

3. http://wenshu.court.gov.cn/content/content?DocID=1e96371d-dd87-42ae-8437-a86d0186878a&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  惠杨与太平人寿保险有限公司日照中心支公司保险纠纷一审民事判决书

4. http://wenshu.court.gov.cn/content/content?DocID=12fa70fd-4d7d-4ba9-8dcc-6149d33ef89c&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  迟延文与新华人寿保险股份有限公司青岛分公司保险纠纷一审民事判决书

5. http://wenshu.court.gov.cn/content/content?DocID=2bd9419c-39fd-4f74-815d-a9be00ef2eb1&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  张雪东与百年人寿保险股份有限公司内蒙古分公司赤峰中心支公司保险纠纷一审民事判决书

6. http://wenshu.court.gov.cn/content/content?DocID=3b8867a6-7ba6-452b-b2fb-a9b800e20fc2&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  百年人寿保险股份有限公司内蒙古分公司赤峰中心支公司与(原审原告张雪东姐姐)张艳秋保险纠纷二审民事判决书

7. http://wenshu.court.gov.cn/content/content?DocID=43adb4a9-d430-499e-a9d7-a95e0164635e&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9 廖启来、中国平安人寿保险股份有限公司襄阳中心支公司保险纠纷二审民事判决书

8. http://wenshu.court.gov.cn/content/content?DocID=f1622c7c-80cf-4f45-a984-a8bf011c7397&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9  中国太平洋人寿保险股份有限公司周口中心支公司、时富根保险纠纷二审民事判决书

9. http://wenshu.court.gov.cn/content/content?DocID=8725b916-016a-4cf5-a11b-f0a2aa19efc9&KeyWord=%E4%BF%9D%E9%99%A9%E7%BA%A0%E7%BA%B7%7C%E4%B8%8D%E5%8F%AF%E6%8A%97%E8%BE%A9 中国人寿保险股份有限公司青岛市分公司与于元章保险纠纷二审民事判决书

OneFileLinux

最近在 Github 上看到一个比较有意思的项目:OneFileLinux。就是将一个Linux系统打包为单独的一个EFI 文件。项目地址是:https://github.com/zhovner/OneFileLinux

我在VirtualBox 虚拟机上实验了一下项目生成的EFI文件,感觉挺有意思。

首先在https://github.com/zhovner/OneFileLinux/releases 页面下载OneFileLinux.efi。然后使用 WinISO创建一个ISO镜像,将下载的EFI文件改名之后放在 \EFI\BOOT\ 目录下。接下来再创建 VirtualBox 虚拟机。创建虚拟机的Type为linux,Version 为 Linux 2.6/3.X/4.X(64-Bit)。特别注意: System -> Motherboard 中需要选中 Enable EFI (special OSer Only)。

最后,将ISO 作为启动镜像,开机即可(时间比较长,中间有一段黑屏,我以为是死机…)。运行结果如下:

对 Linux 和 UEFI 感兴趣的朋友可以仔细研究一下这个项目。

Step to UEFI (166)在Application 中调用包裹的 Application(下)

自由自在的在空中飞翔一直是人类梦想和追求的目标。在飞机发明之前,人类能够通过热气球氢气球的方式实现滞空飞行,1903年12月17日莱特兄弟实验成功的“飞行者一号”是完全受控、依靠自身动力、机身比空气重、持续滞空不落地的飞行器。因此,莱特兄弟也是世界公认的飞机发明者。他们能够成功的一个重要原因是他们实验的方法和之前的先驱相比,更加安全和高效。莱特兄弟于1900年建造了一个风洞,截面40.6厘米×40.6厘米,长1.8米,气流速度40~56.3千米/小时。1901年莱特兄弟又建造了风速12米/秒的风洞,为他们的飞机进行有关的实验测试。【来自百度百科】

对于我们来说,EDK2 自带的 NT32 模拟环境也是一个便于实验的风洞。在没有实体机的情况下,它提供更加简单便捷的测试方法。

前面提到了可以通过在 Application 中直接 Include 另外一个 EFI Application ,然后通过 StartImage 执行之。剩下的问题就是为什么当我们使用UnloadImage 的时候会出现 Error。

首先,要找到出现这个错误的位置。根据我们之前的经验,在\MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c 有定义LoadImage StartImage 和 UnloadImage

//
// 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_IMAGE_LOAD)      CoreLoadImage,                            // LoadImage
  (EFI_IMAGE_START)     CoreStartImage,                           // StartImage
  (EFI_IMAGE_UNLOAD)    CoreUnloadImage,                          // UnloadImage
……………………

 

当我们调用 gBS->UnloadImage 的时候,实际上是由CoreUnloadImage来完成的。对应的代码在\MdeModulePkg\Core\Dxe\Image\Image.c 中。

/**
  Unloads an image.

  @param  ImageHandle             Handle that identifies the image to be
                                  unloaded.

  @retval EFI_SUCCESS             The image has been unloaded.
  @retval EFI_UNSUPPORTED         The image has been started, and does not support
                                  unload.
  @retval EFI_INVALID_PARAMPETER  ImageHandle is not a valid image handle.

**/
EFI_STATUS
EFIAPI
CoreUnloadImage (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS                 Status;
  LOADED_IMAGE_PRIVATE_DATA  *Image;

  Image = CoreLoadedImageInfo (ImageHandle);
  if (Image == NULL ) {
    //
    // The image handle is not valid
    //
    Status = EFI_INVALID_PARAMETER;
    goto Done;
  }

 

通过前面介绍的插入 DEBUG 输出 Message 的方法,可以看到最终的错误是CoreLoadedImageInfo (ImageHandle); 调用返回的错误找到的,在同样的文件中还可以找到CoreLoadedImageInfo 的定义:

/**
  Get the image's private data from its handle.

  @param  ImageHandle             The image handle

  @return Return the image private data associated with ImageHandle.

**/
LOADED_IMAGE_PRIVATE_DATA *
CoreLoadedImageInfo (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS                 Status;
  EFI_LOADED_IMAGE_PROTOCOL  *LoadedImage;
  LOADED_IMAGE_PRIVATE_DATA  *Image;

  Status = CoreHandleProtocol (
             ImageHandle,
             &gEfiLoadedImageProtocolGuid,
             (VOID **)&LoadedImage
             );
  if (!EFI_ERROR (Status)) {
    Image = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS (LoadedImage);
  } else {
    DEBUG ((DEBUG_LOAD, "CoreLoadedImageInfo: Not an ImageHandle %p\n", ImageHandle));
    Image = NULL;
  }

  return Image;
}

 

下面的错误是NT32 模拟环境输出的 Debug 信息,Hello2.efi 的 Handle 是 4BFE318:

从上面的信息可以确定错误发生在 CoreHandleProtocol 函数的调用过程中。此时正在尝试在给定的Handle 上查找gEfiLoadedImageProtocolGuid Protocol( gEfiLoadedImageProtocolGuid = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }})。
继续查找这个 Protocol 的来源,是在 CoreLoadImageCommon 函数中安装的:

  //
  // Install the protocol interfaces for this image
  // don't fire notifications yet
  //
  Status = CoreInstallProtocolInterfaceNotify (
             &Image->Handle,
             &gEfiLoadedImageProtocolGuid,
             EFI_NATIVE_INTERFACE,
             &Image->Info,
             FALSE
             );
  if (EFI_ERROR (Status)) {
    goto Done;
  }

 

经过试验,运行 RIM.EFI 后会多次调用CoreLoadedImageInfo 函数,但是奇怪的是前面几次不会有问题,最后一次UnloadImage 的时候才会出现错误。因此,这意味着有人在整个过程中卸载了这个 Protocol。接下来尝试在 Application 中去掉了 StartImage 函数,惊奇的发现问题会消失。接下来就研究StartImage对应的CoreStartImage 函数,在 \MdeModulePkg\Core\Dxe\Image\Image.c

/**
  Transfer control to a loaded image's entry point.

  @param  ImageHandle             Handle of image to be started.
  @param  ExitDataSize            Pointer of the size to ExitData
  @param  ExitData                Pointer to a pointer to a data buffer that
                                  includes a Null-terminated string,
                                  optionally followed by additional binary data.
                                  The string is a description that the caller may
                                  use to further indicate the reason for the
                                  image's exit.

  @retval EFI_INVALID_PARAMETER   Invalid parameter
  @retval EFI_OUT_OF_RESOURCES    No enough buffer to allocate
  @retval EFI_SECURITY_VIOLATION  The current platform policy specifies that the image should not be started.
  @retval EFI_SUCCESS             Successfully transfer control to the image's
                                  entry point.

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

 

看到了其中有如下操作:

  //
  // If the image returned an error, or if the image is an application
  // unload it
  //
  if (EFI_ERROR (Image->Status) || Image->Type == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) {
    CoreUnloadAndCloseImage (Image, TRUE);
    //
    // ImageHandle may be invalid after the image is unloaded, so use NULL handle to record perf log.
    //
    Handle = NULL;
  }

 

这段代码的意思是:如果加载的代码运行有问题或者Image->Type 是EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION 就直接在 StartImage 中释放掉 Image了。而我们调用的 Hello2.efi 类型是EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ,因此这里释放掉了Handle 上面的 Protocol,当我们调用的时候确实也无法找到。

结论:我们碰到的错误是因为RIM 这个 Application中多此一举的添加了UnLoadImage的操作,去掉这个动作就正常了。

Step to UEFI (165)在Application 中调用包裹的 Application(上)

如何调用另外的 Application 我们已经研究过很多次了。这次的目标是将一个别人编译好的 EFI Application 包裹在自己编写的 Application中然后调用之。

通过这样的方式可以在一些情况下让我们在没有源代码的情况下实现一些特别的功能。

为了方便测试,我们先写一个测试的Application作为 Test Image。代码非常简单简单,没有调用 CLIB,直接在屏幕上输出 “Hello, World 2” 字样。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>

extern EFI_SYSTEM_TABLE  *gST;

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  gST->ConOut->OutputString(gST->ConOut,L"Hello, World 2\r\n");  
  
  return EFI_SUCCESS;
}

接下来用工具把这个 EFI 文件转换为 C的头文件,调用者通过 Include 这个头文件来把Test Image加载到内存中。

  

再使用 gBS->LoadImage() 加载这个 Image。在加载过程中,Test Image 的地址作为 SourceBuffer参数,当这个参数不为NULL时,LoadImage函数也会知道当前要调用的Image已经存在内存中了,也不需要在从DevicePath给出的位置读取到内存中。这步之后再使用 gBS->StartImage()即可运行之。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/BaseMemoryLib.h>
#include  <Library/DevicePathLib.h>
#include  <Hello2.efi.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
 
{
        EFI_DEVICE_PATH  *DP;
        EFI_STATUS      Status;
        EFI_HANDLE      NewHandle;
        UINTN           ExitDataSizePtr; 
        
        DP=FileDevicePath(NULL,L"fso:\\fake.efi");
        Print(L"%s\n",ConvertDevicePathToText(DP,TRUE,FALSE));
    
        //
        // Load the image with:
        // FALSE - not from boot manager and NULL, 0 being not already in memory
        //
        Status = gBS->LoadImage(
                        FALSE,
                        ImageHandle,
                        DP,
                        (VOID*)&Hello2_efi[0],
                        sizeof(Hello2_efi),
                        &NewHandle);     
        if (EFI_ERROR(Status)) {
                Print(L"Load image Error!\n");
                return 0;
        }

        //
        // now start the image, passing up exit data if the caller requested it
        //
        Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
        if (EFI_ERROR(Status)) {
                Print(L"\nError during StartImage [%X]\n",Status);
                return 0;
        }       
        
        Status = gBS->UnloadImage(NewHandle);                        
        if (EFI_ERROR(Status)) {
                Print(L"Un-Load image Error! %r\n",Status);
                return 0;
        }        
        
        return EFI_SUCCESS;
}






运行结果:

完整的代码下载:

RunInMem

可以看到,当我们运行 rim 的时候,能够正常调用 hello2.efi 输出字符。剩下的问题是:为什么当我们UnloadImage的时候会出现错误?

Step to UEFI (164)NT32 环境下的OpenFile研究

根据之前的研究,UDK中带的 NT32 模拟环境里面的很多操作都是直接和 Windows API挂钩来实现的。最近查看了一下 NT32 下面的 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 的实现。具体代码可以在\Nt32Pkg\WinNtSimpleFileSystemDxe\WinNtSimpleFileSystem.c 下面看到。

首先,在 WinNtSimpleFileSystem.c 中有下面这样的代码:

  Private->SimpleFileSystem.OpenVolume  = WinNtSimpleFileSystemOpenVolume;

 

这样,当我们调用EFI_SIMPLE_FILE_SYSTEM_PROTOCOL OpenVolume 的时候,实际工作的是WinNtSimpleFileSystemOpenVolume 的代码。此外,还有下面这样的代码,都是用Windows API 来替换 Protocol 中的操作。

  PrivateFile->EfiFile.Open         = WinNtSimpleFileSystemOpen;
  PrivateFile->EfiFile.Close        = WinNtSimpleFileSystemClose;
  PrivateFile->EfiFile.Delete       = WinNtSimpleFileSystemDelete;
  PrivateFile->EfiFile.Read         = WinNtSimpleFileSystemRead;
  PrivateFile->EfiFile.Write        = WinNtSimpleFileSystemWrite;
  PrivateFile->EfiFile.GetPosition  = WinNtSimpleFileSystemGetPosition;
  PrivateFile->EfiFile.SetPosition  = WinNtSimpleFileSystemSetPosition;
  PrivateFile->EfiFile.GetInfo      = WinNtSimpleFileSystemGetInfo;
  PrivateFile->EfiFile.SetInfo      = WinNtSimpleFileSystemSetInfo;
  PrivateFile->EfiFile.Flush        = WinNtSimpleFileSystemFlush;
  PrivateFile->IsValidFindBuf       = FALSE;

 

通过这样的赋值,当我们在NT32 模拟环境中调用打开读取等等Protocol 的函数时,实际上是用Windows 对应的API来完成实际操作的。
为了证明这一点,可以在上面的函数中插入输出 Debug 信息的代码【参考1】,比如:修改 WinNTSimpleFileSystemOpenVolume() 代码,插入Debug 信息:

/*++

Routine Description:

  Open the root directory on a volume.

Arguments:

  This  - A pointer to the volume to open.

  Root  - A pointer to storage for the returned opened file handle of the root directory.

Returns:

  EFI_SUCCESS           - The volume was opened.

  EFI_UNSUPPORTED       - The volume does not support the requested file system type.

  EFI_NO_MEDIA          - The device has no media.

  EFI_DEVICE_ERROR      - The device reported an error.

  EFI_VOLUME_CORRUPTED  - The file system structures are corrupted.

  EFI_ACCESS_DENIED     - The service denied access to the file.

  EFI_OUT_OF_RESOURCES  - The file volume could not be opened due to lack of resources.

  EFI_MEDIA_CHANGED     - The device has new media or the media is no longer supported.

--*/
// TODO:    EFI_INVALID_PARAMETER - add return value to function comment
{
  EFI_STATUS                        Status;
  WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE *Private;
  WIN_NT_EFI_FILE_PRIVATE           *PrivateFile;
  EFI_TPL                           OldTpl;
  CHAR16                            *TempFileName;
  UINTN                             Size;

  //LABZ_Start
  DEBUG ((EFI_D_INFO, "www.lab-z.com\n"));
  //LABZ_End
  
  if (This == NULL || Root == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  Private     = WIN_NT_SIMPLE_FILE_SYSTEM_PRIVATE_DATA_FROM_THIS (This);

  PrivateFile = AllocatePool (sizeof (WIN_NT_EFI_FILE_PRIVATE));
  if (PrivateFile == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  PrivateFile->FileName = AllocatePool (StrSize (Private->FilePath));
  if (PrivateFile->FileName == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  PrivateFile->FilePath = AllocatePool (StrSize (Private->FilePath));
  if (PrivateFile->FilePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  StrCpy (PrivateFile->FilePath, Private->FilePath);
  StrCpy (PrivateFile->FileName, PrivateFile->FilePath);
  PrivateFile->Signature            = WIN_NT_EFI_FILE_PRIVATE_SIGNATURE;
  PrivateFile->WinNtThunk           = Private->WinNtThunk;
  PrivateFile->SimpleFileSystem     = This;
  PrivateFile->IsRootDirectory      = TRUE;
  PrivateFile->IsDirectoryPath      = TRUE;
  PrivateFile->IsOpenedByRead       = TRUE;
  PrivateFile->EfiFile.Revision     = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
  PrivateFile->EfiFile.Open         = WinNtSimpleFileSystemOpen;
  PrivateFile->EfiFile.Close        = WinNtSimpleFileSystemClose;
  PrivateFile->EfiFile.Delete       = WinNtSimpleFileSystemDelete;
  PrivateFile->EfiFile.Read         = WinNtSimpleFileSystemRead;
  PrivateFile->EfiFile.Write        = WinNtSimpleFileSystemWrite;
  PrivateFile->EfiFile.GetPosition  = WinNtSimpleFileSystemGetPosition;
  PrivateFile->EfiFile.SetPosition  = WinNtSimpleFileSystemSetPosition;
  PrivateFile->EfiFile.GetInfo      = WinNtSimpleFileSystemGetInfo;
  PrivateFile->EfiFile.SetInfo      = WinNtSimpleFileSystemSetInfo;
  PrivateFile->EfiFile.Flush        = WinNtSimpleFileSystemFlush;
  PrivateFile->IsValidFindBuf       = FALSE;

  //
  // Set DirHandle
  //
  PrivateFile->DirHandle = PrivateFile->WinNtThunk->CreateFile (
                                                      PrivateFile->FilePath,
                                                      GENERIC_READ,
                                                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                                                      NULL,
                                                      OPEN_EXISTING,
                                                      FILE_FLAG_BACKUP_SEMANTICS,
                                                      NULL
                                                      );

  if (PrivateFile->DirHandle == INVALID_HANDLE_VALUE) {
    Status = EFI_NOT_FOUND;
    goto Done;
  }

  //
  // Find the first file under it
  //
  Size  = StrSize (PrivateFile->FilePath);
  Size += StrSize (L"\\*");
  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  Size,
                  (VOID **)&TempFileName
                  );
  if (EFI_ERROR (Status)) {
    goto Done;
  }
  StrCpy (TempFileName, PrivateFile->FilePath);
  StrCat (TempFileName, L"\\*");

  PrivateFile->LHandle = PrivateFile->WinNtThunk->FindFirstFile (TempFileName, &PrivateFile->FindBuf);
  FreePool (TempFileName);

  if (PrivateFile->LHandle == INVALID_HANDLE_VALUE) {
    PrivateFile->IsValidFindBuf = FALSE;
  } else {
    PrivateFile->IsValidFindBuf = TRUE;
  }
  *Root = &PrivateFile->EfiFile;

  Status = EFI_SUCCESS;

Done:

  if (EFI_ERROR (Status)) {
    if (PrivateFile) {
      if (PrivateFile->FileName) {
        FreePool (PrivateFile->FileName);
      }

      if (PrivateFile->FilePath) {
        FreePool (PrivateFile->FilePath);
      }

      FreePool (PrivateFile);
    }
  }

  gBS->RestoreTPL (OldTpl);

  return Status;
}

 

之后在启动的过程中就能够看到如下的 Debug 信息:

参考:
1. http://www.lab-z.com/stu130nt32/ NT32 模拟器中的 Debug Message 输出