惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
H
Hacker News: Front Page
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
Cisco Blogs
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
S
Schneier on Security
The Hacker News
The Hacker News
Cyberwarzone
Cyberwarzone
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Tailwind CSS Blog
S
Secure Thoughts
N
Netflix TechBlog - Medium
T
The Exploit Database - CXSecurity.com
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Help Net Security
Help Net Security
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
L
LangChain Blog
Martin Fowler
Martin Fowler
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
www.infosecurity-magazine.com
www.infosecurity-magazine.com
有赞技术团队
有赞技术团队
P
Privacy International News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Recent Announcements
Recent Announcements
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Register - Security
The Register - Security
云风的 BLOG
云风的 BLOG
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
WordPress大学
WordPress大学
Recorded Future
Recorded Future
The Last Watchdog
The Last Watchdog
G
Google Developers Blog
T
Threatpost
小众软件
小众软件
S
Securelist
Recent Commits to openclaw:main
Recent Commits to openclaw:main
O
OpenAI News

博客园 - yooooooo

ftraceoption irq-info MPAM BE/ FE 的区别及功能 IPI与CPU唤醒机制 【ARM CoreLink 系列 5 -- CI-700 控制器介绍 】 ARM CoreLink 系列 4.3 -- NI-700 Component and interface identifiers ARM CCI-500 与 NI0700 的关系. ARM NIC-400 与 NI-700 的区别 claude code命令使用 【ARM Trace32(劳特巴赫) 使用介绍 2.2 -- TRACE32 进阶命令之 DIAG 弹框命令】 trace32 .cmm脚本和.t32文件的区别 【ARM Trace32(劳特巴赫) 使用介绍 2.1 -- TRACE32 Practice 脚本 cmm 脚本学习】 【ARM Trace32(劳特巴赫) 使用介绍 1.1 - Veloce 环境中使用trace32 连接 Cortex-M33】 PCIe 总线的 ASPM 和 链路状态机制总结 fw_devlink 功能 I3C协议详解 UART 协议规范 BPF 调度器 sched_ext 实现机制、调度流程及样例 Android Camera性能分析 录像Buffer Path详解 【UEFI基础】Protocol介绍 sched feature TTWU_QUEUE 【ARM CoreLink 系列 4.2 -- NI-700 Function units 详细介绍】 edk2构建编译流程 UEFI:FDF文件及FD、FV、FFS EDK II PCD的概念、类型、使用 PELT算法浅析 load_balance函数代码详解 UEFI Boot Manager Linux misfit task Linux 内核中sched_prio_to_weight转换关系 CFS任务放置代码详解 CFS任务的负载均衡(load balance)
【UEFI实战】在库中使用全局变量
yooooooo · 2025-11-30 · via 博客园 - yooooooo

说明

本文涉及的代码都可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.中找到。

一个不怎么好的测试代码

有两个驱动,NullDxeDriverOne.inf和NullDxeDriverTwo.inf,它们做的事情只有一件,就是调用一个库函数:

EFI_STATUS
EFIAPI
NullDxeDriverOneEntry (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS         Status;
  Status             = EFI_SUCCESS;
  
  DEBUG ((EFI_D_ERROR, "[beni]NullDxeDriverOneEntry Start.\n"));
  PrintGlobalVar ();
  DEBUG ((EFI_D_ERROR, "[beni]NullDxeDriverOneEntry End.\n"));
  
  return Status;
}

下面是PrintGlobalVar()函数的实现:

/**
  Print the address of the global variables.
  
  @param   NA
  
  @retval EFI_SUCCESS     Executed successfully.
  @retval Others          Error happened.
**/
EFI_STATUS
EFIAPI
PrintGlobalVar (
  VOID
  )
{
  if (NULL == gBuffer) {
    gBuffer = AllocatePool (128);
  }
  DEBUG ((EFI_D_ERROR, "[beni]gBuffer addr: 0x%p.\n", gBuffer));
  DEBUG ((EFI_D_ERROR, "[beni]&Data addr: 0x%p.\n", &Data));
 
  return EFI_SUCCESS;
}

这里的gBuffer和Data是两个全局的变量。

这里的本意是,我们从其它设备上获取到一部分数据,存放在gBuffer对应的缓冲区去,之后就不需要在每次调用都去访问设备。

但是实际的情况如下:

[beni]NullDxeDriverOneEntry Start.
[beni]gBuffer addr: 0x7191218.
[beni]&Data addr: 0x7922390.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7191340
Loading driver at 0x0000791D000 EntryPoint=0x0000791D380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7191698
[beni]NullDxeDriverTwoEntry Start.
[beni]gBuffer addr: 0x7191798.
[beni]&Data addr: 0x791F390.
[beni]NullDxeDriverTwoEntry End.

可以看到实际上还是有两份gBuffer(Data是一个整型,它也有两份),也就是说还是需要访问两次设备。

这是因为不同的模块是分开编译的,实际上都是独立的存放了库函数,也就分开存放了这些全局变量。

如果这个库函数被多次的调用,那么多少会影响到启动时间。

PCD数据

对应普通数据,比如说前面提到的Data整型,可以保存成PCD数据。

比如在dec里面声明一个PCD,如下所示:

[PcdsDynamic]
  gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0xFFFFFFFF|UINT32|0x40000001

注意这里的类型是Dynamic的,这样就可以在启动过程中设置。

比如在之前的Lib中加入如下的代码:

EFI_STATUS
EFIAPI
GlobalDataTestLibConstructor (
  IN  EFI_HANDLE               ImageHandle,
  IN  EFI_SYSTEM_TABLE         *SystemTable
  )
{
  DEBUG ((EFI_D_ERROR, "[beni]CustomizedDisplayLibConstructor.\n"));
  if (0xFFFFFFFF == PcdGet32 (PcdOemVersion)) {
    DEBUG ((EFI_D_ERROR, "[beni]PcdSet.\n"));
    PcdSet32 (PcdOemVersion, 0x00000001);
  }
  return EFI_SUCCESS;
}

这个构造器在每个驱动第一次调用该库中的函数的时候都会调用。

之后在BIOS运行的时候上面的两个驱动的打印如下:

[beni]CustomizedDisplayLibConstructor.
[beni]PcdSet.
[beni]NullDxeDriverOneEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]gBuffer addr: 0x7191218.
[beni]Version: 0x1.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7191340
Loading driver at 0x0000791D000 EntryPoint=0x0000791D380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7191698
[beni]CustomizedDisplayLibConstructor.
[beni]NullDxeDriverTwoEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]gBuffer addr: 0x7191798.
[beni]Version: 0x1.
[beni]NullDxeDriverTwoEntry End.

在第一个驱动运行的时候,PcdOemVersion的值被修改成了1,第二个驱动运行的时候,PcdOemVersion的值已经被修改了,就不会再进入了,然后第二个驱动也能打印PcdOemVersion的值为1。也就是说PcdOemVersion的值再全局都能够正常使用了。

变量

对于上文提到的一般的数据类型,比如UINT32之类的,可以通过PCD来保存。

但是对于一段数据区域,就不太方便,这个时候可以使用变量。

UEFI里面的Runtime Service里面提供了变量的操作函数GetVariable()和SetVariable()。

注意它不能用来PEI阶段,因为只有在DXE阶段相关的驱动安装之后(也并不是一开始就能用,PEI阶段有一种叫做HOB的东西有类似的作用,这里先不讲)才能使用变量操作。

还是以上文的代码为例,为了保存gBuffer的数据并能够在BIOS启动的过程中都可以使用(其实也并没有都可以),这里新建了一个模块,用来初始化gBuffer对应的数据:(新模块名为GlobalDataInstall.inf)

EFI_STATUS
EFIAPI
GlobalDataInstallEntry (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS         Status;
  DEBUG ((EFI_D_ERROR, "[beni]GlobalDataInstallEntry start.\n"));
  Status = VariableMethod ();
  DEBUG ((EFI_D_ERROR, "[beni]GlobalDataInstallEntry end.\n"));
  return Status;
}

其中VariableMethod()的实现如下:

EFI_STATUS
EFIAPI
VariableMethod (
  VOID
  )
{
  EFI_STATUS    Status;
  VOID          *Buffer;
  
  Buffer = AllocateZeroPool (128);
  if (NULL == Buffer) {
    DEBUG ((EFI_D_ERROR, "[beni]AllocatePool failed.\n"));
    return EFI_OUT_OF_RESOURCES;
  }
  *((UINT32 *)Buffer) = OEM_DATA_MAGIC;
  
  Status = gRT->SetVariable (
                  OEM_DATA_NAME,
                  &gEfiOemGlobalDataGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  128,
                  Buffer
                  );
 
  FreePool (Buffer);
  
  return Status;
}

其实就是一个简单的调用SetVariable()的过程。

这里因为是测试,所以没有设计到Buffer里面的具体的数据,只是申请了一个128字节的数据区,然后放了个魔术字在最前面。

当SetVariable()之后,系统会保存一份Buffer数据,原来的可以释放掉。

上述的操作其实也不需要再一个新的驱动里面,也可以放在库函数的构造函数中,这种方式已经在前面PCD数据的时候使用过,所以这里采用了新的方式。

之后在库函数中添加如下的代码来获取全局的变量:

EFI_STATUS
EFIAPI
PrintGlobalVar (
  VOID
  )
{
  EFI_STATUS         Status;
  VOID               *Buffer;
  UINTN              Size;
  
  Size   = 0;
  Buffer = NULL;
  Status = gRT->GetVariable (
                  OEM_DATA_NAME,
                  &gEfiOemGlobalDataGuid,
                  NULL,
                  &Size,
                  Buffer
                  );
  if (EFI_ERROR (Status)) {
    if (EFI_BUFFER_TOO_SMALL == Status) {
      DEBUG ((EFI_D_ERROR, "[beni]Size : %d.\n", Size));
      Buffer = AllocatePool (Size);
      if (NULL == Buffer) {
        DEBUG ((EFI_D_ERROR, "[beni]AllocatePool failed.\n"));
        return EFI_OUT_OF_RESOURCES;
      }
      Status = gRT->GetVariable (
                      OEM_DATA_NAME,
                      &gEfiOemGlobalDataGuid,
                      NULL,
                      &Size,
                      Buffer
                      );
      if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "[beni]GetVariable failed 0. - %r\n", Status));
        return Status;
      }
    } else {
      DEBUG ((EFI_D_ERROR, "[beni]GetVariable failed 1. - %r\n", Status));
      return Status;
    }
  }
  
  if (OEM_DATA_MAGIC == *((UINT32 *)Buffer)) {
    DEBUG ((EFI_D_ERROR, "[beni]I got the data.\n"));
  }
  
  DEBUG ((EFI_D_ERROR, "[beni]Version: 0x%x.\n", PcdGet32(PcdOemVersion)));
  
  if (NULL != Buffer) {
    FreePool (Buffer);
    Buffer = NULL;
  }
  
  return EFI_SUCCESS;
}

同样在NullDxeDriverOne.inf和NullDxeDriverTwo.inf两个模块当中调用上述的库函数,结果如下:

[beni]CustomizedDisplayLibConstructor.
[beni]NullDxeDriverOneEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]Size : 128.
[beni]I got the data.
[beni]Version: 0x1.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 718C2C0
Loading driver at 0x0000791A000 EntryPoint=0x0000791A380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 718CC98
[beni]CustomizedDisplayLibConstructor.
[beni]NullDxeDriverTwoEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]Size : 128.
[beni]I got the data.
[beni]Version: 0x1.
[beni]NullDxeDriverTwoEntry End.

可以看到能够正常获取到数据。

这种方式可以使用到一段数据中,当然也能用在普通的数据中。

不过有个问题,可以在上面的代码中看到,需要在库函数中返回的申请内存和释放内存,当多次调用这个库函数的时候,还是会浪费一些时间(当然对于普通数据没有这种烦恼,但是普通数据显然用PCD更方便)。

其它

上述两种是最常见的方式,理论上还应该有其它的方法。

比如将数据放在Protocol中,然后安装这个Protocol,在其它地方获取这个Protocol并解析其中的数据。