EFI Protocol VS C++

本文介绍了EFI中的Protocol概念及其如何实现面向对象的三大特性:封装、继承和多态。通过具体的例子展示了Protocol如何在二进制级别实现复用,并讨论了与C++相比的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

EFI Protocol VS C++

1.    Introduction

 

ProtocolEFI引出的新概念,翻翻EDK就会发现与protocol相关的code散落在EFI的各个角落,大到host bus driver,小到Hello Word App都和protocol 脱不了关系,因此毫不夸张的说protocol应该是EFI的精髓所在,DXE阶段模块之间的通信都经由protocol完成(我觉得PEI阶段的ppi应该和protocol一个概念,只不过是限于PEI的特殊性而稍有差异而已).EFI Driver基本上就是使用一堆protocol的实现的一个可执行的文件.一个protocol就是一堆函数指针和数据成员的集合,官方称呼为protocol interface structure;这个protocol必须要定义一个GUID作为它的唯一标识,DXE driver使用Boot Service table中的类似InstallProtocolInterface等函数将porotocol安装到device handle上这就叫做produce protocol, InstallProtocolInterface成功的话该protocol就是published,后续其它driver使用该protocol就只要用Boot Service table中的LocateProtocol等相关函数从handle database中索引出该protocol就可以了.protocolDXE阶段模块之间的通信提供了非常有效的手段.

 

2.    Protocol Implement OO

 

EFI是一个使用C实现的OOFramework,它的OO特性主要经由protocl实现.EFI可以说

是一个优秀的Framework, 对它的扩展可以优雅的实现.使用过C++,JAVA这类面向对象语言的朋友都会知道OO有三大基本特性:1.Encapsulation,2.Inheritance,3.Polymorphism. 那么既然EFI是使用protocol实现的OO,那么protocol是如何实现这三大特性的呢?

1.       Encapsulation的实现其实比较简单,它所表达的信息隐藏的概念,这个部分在protocol中可以方便的实现,protocol通常是函数指针和属性的集合,我们导出函数的接口,而将数据属性放在函数接口内部处理.如下述EDK中的code所示:

typedef struct _EFI_CPU_ARCH_PROTOCOL {

  EFI_CPU_FLUSH_DATA_CACHE            FlushDataCache;

  EFI_CPU_ENABLE_INTERRUPT            EnableInterrupt;

  EFI_CPU_DISABLE_INTERRUPT           DisableInterrupt;

  EFI_CPU_GET_INTERRUPT_STATE         GetInterruptState;

  EFI_CPU_INIT                        Init;

  EFI_CPU_REGISTER_INTERRUPT_HANDLER 

RegisterInterruptHandler;

  EFI_CPU_GET_TIMER_VALUE             GetTimerValue;

  EFI_CPU_SET_MEMORY_ATTRIBUTES       SetMemoryAttributes;

  UINT32                              NumberOfTimers;

  UINT32                              DmaBufferAlignment;

} EFI_CPU_ARCH_PROTOCOL;

NumberOfTimers, DmaBufferAlignment就是数据属性了.

 

2.     Inheritance子类继承了父类的所有的方法和属性,C的级别上表示就是内

存的叠加.同时它还代表的是IS-A的概念.如下面的sample所示B_PROTOCOL

继承自A_PROTOCOL.

typedef struct _ A_PROTOCOL {

UINT32                           A;

 

}A_PROTOCOL;

typedef struct _ B_PROTOCOL {

A_ PROTOCOL                                    AP;

UINT32                              B;

}B_PROTOCOL;

它们的类图如下图1所示,内存布局如下图2所示:

 

 

3. Polymorphism是面向对象的精髓所在了,如果没有了多态,则只能称之为基于对象了.多态是指基于相同的接口实现的不同的class(EFI之中应该是指实现protocoldriver),具有不同的行为.如两个EFI Driver A&B 它们都实现了ComponentName protocol,A在它的GetDriverName interface回复的Driver Name”A”Driver B则回复的是”B”,所以同样的接口具有不同的行为.protocol相当于一个共同的接口,因此我们可以以统一的方式去管理和配置这些实现了该protocol的这些Driver.shell app dirver.efi它就是通过枚举Handle database找到挂在这些device handle上的ComponentName protocol,然后call它们的GetDriverName interface获悉driver的名字信息.

 

3.    Who Win(Protocol vs C++)?

 

看上去貌似用protocol实现OO特性还是比较费事的,那么为什么不直接使C++实现呢?我不是很清楚L!呵呵,可是我可以猜的到的几种可能的原因如下:

1. C++无法做到二进制级别上的复用,C++是一个非常复杂的语言,它有非常多的特性,C++标准委员会制定出了编译器厂商所需要实现的一堆特性,比如构造函数,析构函数,继承,重载,多态等等一堆的规则,但是标准委员会并没有规定编译器厂商如何实现这些特性于是问题就来了,我们使用A厂商编译器build driver daB厂商编译器build同一只driver da它们的内存布局就不相同,于是二进制级别上的互操作就不可能了,设备厂商都要把source code拿出来放在一个编译器上build OK,不同的编译器实现不同的一个地方就体现在实现多态时,vptr存放的位置上,有一些编译器会把该指针放在对象内存布局的头部,有些厂商则喜欢放在尾部,这个差异非常大;如果存在多重继承那么就可能出现多个vptr那么vptr摆放的顺序又会有不同等等不胜其扰的问题。而protocol使用标准的c实现,它就没有二进制文件不一致的问题。

 

2C++实现多态,虚继承以及name mangling等会带来空间以及编译时间上的开销。C++中的多态是通过在基类的函数声明中加上virtual实现的,这一个关键字里面大有文章,我们使用一个class VI演示该过程:

class VI

{

      int a;

      int b;

      void test0(void) virtual;

};

没有virtual这个关键字的时候它的内存布局如下图3所示,加上之后就如图4所示:

 

编译器通过增加一个vptr虚函数指针,和vtbl虚函数表实现多态的功能,一旦子类改写了test0那么子类就会修改掉vtbl中的test0的地址。如此一来开销就来了J,一旦涉及到虚继承多重继承,那个开销就更大了。相比较来看protocol的实现就完全没有这个问题。

3C++使用字符命名对象会有命名冲突的可能而protocol使用128GUID是不可能出现命名冲突的。当project大到一定程度就会感受到变量命名冲突的讨厌了,你定义的变量可能在别的你不知道的模块里已经被用过了,然后编译器报上一大堆另人恐慌的错误出来。当然C++也有解决命名冲突的方法,那就是namespace可是这样又会造成性能上的影响。

所以宗上所述,protocol应该较C++稍有优势一点哦J,它完整的实现了OO的所有特性,只是感官上有些差异,如intelspec所说的那样Look/fell very different.

 

That’s all!

 

Enjoy it!

 

Peter

 

<think>我们正在解决EFIRNG协议不可用的问题。根据引用[2],我们知道在Linux内核的EFIstub中,当尝试获取随机数时,如果EFI_RNG_PROTOCOL不可用,就会打印"EFI_RNG_PROTOCOLunavailable"的消息。引用[2]中的代码展示了如何通过EFI_RNG_PROTOCOL获取随机数,但当前环境缺少这个协议。解决方案思路:1.确保固件(UEFI)支持EFI_RNG_PROTOCOL。如果不支持,可能需要更新固件。2.如果固件不支持,可以考虑在启动加载器(bootloader)中提供随机数源,或者在内核中绕过对EFI_RNG_PROTOCOL的依赖。3.引用[2]中的代码片段提供了一个思路:我们可以修改内核的EFIstub代码,在没有EFI_RNG_PROTOCOL时使用一个固定的值(例如0x12345678)替代,但这会降低安全性(因为每次启动的随机种子相同,导致KASLR的熵不足)。因此,这不是一个安全的解决方案,仅用于调试。更安全的替代方案:-在bootloader(如GRUB)中提供随机数,然后通过bootloader传递给内核。-使用其他可用的随机源(如RDRAND指令)来生成随机数。具体步骤:方案1:更新固件检查并更新UEFI固件到最新版本,以支持EFI_RNG_PROTOCOL。方案2:使用bootloader传递随机种子1.在bootloader中,使用其自身的随机数生成能力(例如,GRUB可以从硬件或时间等收集熵)生成一个随机种子。2.将这个种子通过EFI配置表或设备树(DTB)传递给内核。方案3:修改内核代码(不安全,仅用于测试)如引用[2]所示,我们可以修改`efi_get_random_bytes`函数,在没有EFI_RNG_PROTOCOL时使用一个伪随机值(但注意,固定值会导致安全风险)。修改方法如下:在`drivers/firmware/efi/libstub/random.c`中,找到`efi_get_random_bytes`函数,在`status=efi_call_early(locate_protocol,...);`之后,如果返回的状态不是`EFI_SUCCESS`,则使用其他方式生成随机数(例如,使用固定值,或者使用RDRAND指令生成)。但注意,使用RDRAND需要确保CPU支持,并且在内核stub中可以使用。示例修改(不安全,仅用于测试):```cefi_status_tefi_get_random_bytes(efi_system_table_t*sys_table_arg,unsignedlongsize,u8*out){efi_guid_trng_proto=EFI_RNG_PROTOCOL_GUID;efi_status_tstatus;structefi_rng_protocol*rng;status=efi_call_early(locate_protocol,&rng_proto,NULL,(void**)&rng);if(status==EFI_SUCCESS)returnrng->get_rng(rng,NULL,size,out);//如果找不到EFI_RNG_PROTOCOL,使用固定值(仅用于测试!)for(unsignedlongi=0;i<size;i++)out[i]=0x78;//简单填充一个固定值,但注意这会导致安全风险returnEFI_SUCCESS;}```但是,上述修改会破坏KASLR的安全性,因此不推荐在生产环境中使用。更安全的修改:尝试使用RDRAND指令(如果可用)我们可以检查CPU是否支持RDRAND,然后使用它来生成随机数。在EFI环境中,我们可能无法使用CPUID指令(因为EFI环境可能没有设置IDT等),所以需要谨慎。另一种方法是使用EFI的CPU体系结构协议来获取CPU特性,但这样比较复杂。方案4:在QEMU虚拟环境中,可以传递一个虚拟的RNG设备如果问题发生在QEMU虚拟机中,我们可以为虚拟机添加一个虚拟的随机数生成设备。例如,使用`virtio-rng`设备。在QEMU启动参数中添加:```-devicevirtio-rng-pci```这样,虚拟机的UEFI固件(如edk2)就可以通过virtio-rng设备提供EFI_RNG_PROTOCOL。总结:根据环境选择解决方案:-如果是物理机,尝试更新固件。-如果是虚拟机(如QEMU),添加virtio-rng设备。-在无法解决协议缺失的情况下,考虑通过bootloader传递随机种子(如GRUB的kaslr-seed功能)。-作为调试手段,可以修改内核代码(但生产环境不推荐)。注意:引用[3]提到了编译edk2固件(QEMU_EFI.fd),如果你在QEMU中使用自定义的edk2固件,确保编译时包含了RNG协议的支持。在edk2中,RNG协议的支持通常需要包含相应的驱动模块(如VirtioRngDxe)。因此,对于QEMU环境,我们重新编译edk2固件并包含VirtioRngDxe驱动,同时确保QEMU命令行添加了virtio-rng设备,这样EFI_RNG_PROTOCOL就可用。步骤:1.编译edk2时,确保在平台描述文件(.dsc)中包含了VirtioRngDxe驱动。2.使用编译出的QEMU_EFI.fd文件。3.启动QEMU时添加`-devicevirtio-rng-pci`。这样,内核在启动时就可以通过EFI_RNG_PROTOCOL获取随机数了。如果用户的问题是在QEMU环境中遇到的,那么方案4是最合适的。相关问题:§§相关问题§§1.如何更新UEFI固件以支持EFI_RNG_PROTOCOL?2.如何在GRUB中为Linux内核提供随机种子?3.如何修改Linux内核的EFIstub代码以使用RDRAND指令生成随机数?4.如何在edk2(UEFI固件)中启用RNG协议支持?</think>### EFI_RNG_PROTOCOL不可用的解决方案 `EFI_RNG_PROTOCOL unavailable` 错误表明系统固件未提供随机数生成协议(EFI_RNG_PROTOCOL),这会影响内核启动时的随机熵获取(如KASLR安全特性)。以下是分步解决方案: --- ### 1. **检查并更新固件** - 确认当前固件版本: ```bash sudo dmidecode -t bios ``` - 访问主板制造商官网下载最新固件更新 - **原因**:旧版固件可能未实现EFI_RNG_PROTOCOL[^2] --- ### 2. **虚拟机环境专用方案(QEMU/KVM)** #### (1) 添加虚拟RNG设备 在QEMU启动命令中加入: ```bash -object rng-random,filename=/dev/urandom,id=rng0 \ -device virtio-rng-pci,rng=rng0 ``` #### (2) 重新编译EDK2固件 ```bash # 启用RNG支持 cd edk2 build -a AARCH64 -p ArmVirtPkg/ArmVirtQemuKernel.dsc -t GCC5 ``` 确认生成文件包含RNG驱动: `Build/ArmVirtQemuKernel-AARCH64/DEBUG_GCC5/FV/QEMU_EFI.fd`[^3] --- ### 3. **内核层解决方案** #### (1) 强制使用备选熵源(仅调试) 修改内核代码: `drivers/firmware/efi/libstub/random.c` ```c efi_status_t efi_get_random_bytes(...) { // 添加固定值(示例) *out = 0x12345678; return EFI_SUCCESS; // 强制返回成功[^2] } ``` > **警告**:此方法破坏KASLR安全性,仅限测试环境! #### (2) 启用CONFIG_RANDOM_TRUST_BOOTLOADER 在内核配置中: ```bash make menuconfig ``` 路径: `Boot options -> Trust bootloader-provided RNG entropy` --- ### 4. **Bootloader层解决方案(GRUB)** 在GRUB配置中传递固定熵值: ```bash # /etc/default/grub GRUB_CMDLINE_LINUX="kaslr-seed=0x12345678" update-grub ``` --- ### 验证修复 启动后检查内核日志: ```bash dmesg | grep -i "rng" # 期望输出:EFI stub: Using RNG protocol for KASLR ``` ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值