2026年2月更新,Step to UEFI 文章索引:
推荐一个 PDF 处理工具
很多时候,我们需要对 PDF 文件进行一些简单的处理,诸如:拆分合并加水印等等。通常情况下,在线的 PDF 工具已经足够应付。但是如果考虑到隐私保密和速度的问题,本地工具会更合适一些。
最近我有一个将扫描后的 PDF 合并的需求,最后使用了PDFtk 这个工具,网站是 https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/。
有兴趣的朋友可以试试。

BCDEDIT 创建一个 WinDBG 选项
本文介绍通过 BCDEDIT 创建一个 Windows 启动选项,这个选项中打开 WinDBG 功能。
根据当前已有启动项拷贝出来一个名为 “WinDBG”的启动项, 这个命令运行之后会生成一个 GUID,需要记下来
bcdedit /copy {current} /d "windbg"
设置启动超时 45秒
bcdedit /timeout 45
设置打开 Debug 功能
bcdedit /debug {前面的 GUID} on
设置使用 USB Debug
bcedit /dbgsettings usb targetname:labz
指定 XHCI
bcdedit /set "{dbgssettings}" busparams 0.20.0

这样,每次启动的时候都会出现一个菜单,选择第一项会正常启动Windows,选择第二项即可启动能够通过 WinDBG 调试的 Windows

TIPS:一种C语言奇怪的数组引用方式
最近看到了一种奇怪的写法,例如下面的代码中:Print(L”%d “,i[segM]); 这个语句,实际上是Print(L”%d “,segM[i]) 的意思。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
UINT8 segM[]={
0xAA,0xBB,0xCC,0xDD,0xEE,0xFF
};
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
;
for (UINT8 i=0;i<6;i++) {
Print(L"%d ",i[segM]);
}
return(0);
}
解释说数组访问操作符 [] 是对称的,即:
a[b] == b[a]
当然了,我个人是非常不建议你在代码中使用上面的方法的。
好用的C语言分析工具 CTags
CTags 是一款开源的 C语言分析工具,项目地址是:
https://github.com/universal-ctags/ctags
使用这个工具可以分析C语言,例如:
ctags C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c
可以得到下面的结果;
!_TAG_EXTRA_DESCRIPTION anonymous /Include tags for non-named objects like lambda/
!_TAG_EXTRA_DESCRIPTION fileScope /Include tags of file scope/
!_TAG_EXTRA_DESCRIPTION pseudo /Include pseudo tags/
!_TAG_EXTRA_DESCRIPTION subparser /Include tags generated by subparsers/
!_TAG_FIELD_DESCRIPTION epoch /the last modified time of the input file (only for F\/file kind tag)/
!_TAG_FIELD_DESCRIPTION file /File-restricted scoping/
!_TAG_FIELD_DESCRIPTION input /input file/
!_TAG_FIELD_DESCRIPTION name /tag name/
!_TAG_FIELD_DESCRIPTION pattern /pattern/
!_TAG_FIELD_DESCRIPTION typeref /Type and name of a variable or typedef/
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_KIND_DESCRIPTION!C d,macro /macro definitions/
!_TAG_KIND_DESCRIPTION!C e,enumerator /enumerators (values inside an enumeration)/
!_TAG_KIND_DESCRIPTION!C f,function /function definitions/
!_TAG_KIND_DESCRIPTION!C g,enum /enumeration names/
!_TAG_KIND_DESCRIPTION!C h,header /included header files/
!_TAG_KIND_DESCRIPTION!C m,member /struct, and union members/
!_TAG_KIND_DESCRIPTION!C s,struct /structure names/
!_TAG_KIND_DESCRIPTION!C t,typedef /typedefs/
!_TAG_KIND_DESCRIPTION!C u,union /union names/
!_TAG_KIND_DESCRIPTION!C v,variable /variable definitions/
!_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/
!_TAG_OUTPUT_FILESEP slash /slash or backslash/
!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
!_TAG_OUTPUT_VERSION 1.1 /current.age/
!_TAG_PARSER_VERSION!C 2.2 /current.age/
!_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/
!_TAG_PROC_CWD C:/Users/yanbwang/Downloads/ctags/ //
!_TAG_PROGRAM_AUTHOR Universal Ctags Team //
!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
!_TAG_PROGRAM_URL https://ctags.io/ /official site/
!_TAG_PROGRAM_VERSION 6.2.0 /p6.2.20260125.0/
!_TAG_ROLE_DESCRIPTION!C!function foreigncall /called in foreign languages/
!_TAG_ROLE_DESCRIPTION!C!function foreigndecl /declared in foreign languages/
!_TAG_ROLE_DESCRIPTION!C!header local /local header/
!_TAG_ROLE_DESCRIPTION!C!header system /system header/
!_TAG_ROLE_DESCRIPTION!C!macro undef /undefined/
!_TAG_ROLE_DESCRIPTION!C!struct foreigndecl /declared in foreign languages/
AddressWidthInitialization C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^AddressWidthInitialization ($/;" f typeref:typename:VOID
GetFirstNonAddress C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetFirstNonAddress ($/;" f typeref:typename:STATIC UINT64
GetPeiMemoryCap C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetPeiMemoryCap ($/;" f typeref:typename:STATIC UINT32
GetSystemMemorySizeAbove4gb C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetSystemMemorySizeAbove4gb ($/;" f typeref:typename:STATIC UINT64
GetSystemMemorySizeBelow4gb C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetSystemMemorySizeBelow4gb ($/;" f typeref:typename:UINT32
InitializeRamRegions C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^InitializeRamRegions ($/;" f typeref:typename:VOID
PublishPeiMemory C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^PublishPeiMemory ($/;" f typeref:typename:EFI_STATUS
Q35TsegMbytesInitialization C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^Q35TsegMbytesInitialization ($/;" f typeref:typename:VOID
QemuInitializeRam C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^QemuInitializeRam ($/;" f typeref:typename:STATIC VOID
mPhysMemAddressWidth C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^UINT8 mPhysMemAddressWidth;$/;" v typeref:typename:UINT8
mQ35SmramAtDefaultSmbase C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^BOOLEAN mQ35SmramAtDefaultSmbase = FALSE;$/;" v typeref:typename:BOOLEAN
mQ35TsegMbytes C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^STATIC UINT16 mQ35TsegMbytes;$/;" v typeref:typename:STATIC UINT16
mS3AcpiReservedMemoryBase C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^STATIC UINT32 mS3AcpiReservedMemoryBase;$/;" v typeref:typename:STATIC UINT32
mS3AcpiReservedMemorySize C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^STATIC UINT32 mS3AcpiReservedMemorySize;$/;" v typeref:typename:STATIC UINT32
类似的,可以直接分析取得C语言中的函数名,命令是:
ctags --c-kinds=f C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c
输出结果:
AddressWidthInitialization C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^AddressWidthInitialization ($/;" f typeref:typename:VOID
GetFirstNonAddress C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetFirstNonAddress ($/;" f typeref:typename:STATIC UINT64
GetPeiMemoryCap C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetPeiMemoryCap ($/;" f typeref:typename:STATIC UINT32
GetSystemMemorySizeAbove4gb C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetSystemMemorySizeAbove4gb ($/;" f typeref:typename:STATIC UINT64
GetSystemMemorySizeBelow4gb C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^GetSystemMemorySizeBelow4gb ($/;" f typeref:typename:UINT32
InitializeRamRegions C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^InitializeRamRegions ($/;" f typeref:typename:VOID
PublishPeiMemory C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^PublishPeiMemory ($/;" f typeref:typename:EFI_STATUS
Q35TsegMbytesInitialization C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^Q35TsegMbytesInitialization ($/;" f typeref:typename:VOID
QemuInitializeRam C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c /^QemuInitializeRam ($/;" f typeref:typename:STATIC VOID
Step to UEFI (302)VC 中的机器码填充
VC 的 X86 模式下无法直接使用 _ASM 关键字定义汇编代码,最近偶然看到了VC 支持Intrinsics(内联函数),它是编译器提供的特殊函数,它们直接映射到特定的处理器指令,让你能够在 C++ 代码中使用底层硬件功能。
于是,编写代码测试:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
UINT32 cpuInfo[4];
// CPUID 指令
__cpuid(cpuInfo, 1);
//NOP 指令
__nop();
return(0);
}
使用 /FACS 查看生成的汇编代码,对应片段如下:
ShellAppMain PROC ; COMDAT
; 19 : {
$LN3:
00000 48 89 54 24 10 mov QWORD PTR [rsp+16], rdx
00005 48 89 4c 24 08 mov QWORD PTR [rsp+8], rcx
0000a 53 push rbx
0000b 48 83 ec 10 sub rsp, 16
; 20 : UINT32 cpuInfo[4];
; 21 :
; 22 : // CPUID 指令
; 23 : __cpuid(cpuInfo, 1);
0000f b8 01 00 00 00 mov eax, 1
00014 33 c9 xor ecx, ecx
00016 0f a2 cpuid
00018 4c 8d 04 24 lea r8, QWORD PTR cpuInfo$[rsp]
0001c 41 89 00 mov DWORD PTR [r8], eax
0001f 41 89 58 04 mov DWORD PTR [r8+4], ebx
00023 41 89 48 08 mov DWORD PTR [r8+8], ecx
00027 41 89 50 0c mov DWORD PTR [r8+12], edx
; 24 :
; 25 : __nop();
0002b 90 npad 1
; 26 :
; 27 : return(0);
0002c 33 c0 xor eax, eax
; 28 : }
0002e 48 83 c4 10 add rsp, 16
00032 5b pop rbx
00033 c3 ret 0
ShellAppMain ENDP
可以看到编译后生成了对应的汇编代码(也就是机器码)。特别是 _NOP() 函数,直接生成了0x90 这样的机器码。使用这样的思路可以在代码中使用 _NOP()函数进行填充,然后在编译完成后使用工具替换这部分机器码为我们自定义的代码。让我们的程序更加灵活,实现更多的功能。
记录一个ESP32 S3/P4 上奇怪的问题
最近我在使用 ESP32 P4 时遇到了一个奇怪的问题,经过简化,代码如下:
class Ch346 {
public:
Ch346() {};
uint8_t WriteData() {
for (int i = 0; i < 512; i++) {
Serial.println(i);
}
}
};
Ch346 MyCh346;
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("Start");
}
void loop() {
if (Serial.available()) {
char c = Serial.read();
if (c == '1') {
Serial.println("Input 1");
MyCh346.WriteData();
Serial.println("Input 1 Complete");
}
}
}
运行之后,打开串口输入1 然后应该得到 0-511的值,上述代码在Leonardo 板子上上没问题,但是不知道为什么在 ESP32 S3 P4上会一直输出:

===========================================================
有兴趣的朋友可以先思考一下,具体解析在下面:
CH397 UEFI 网络功能测试
CH397是 WCH 推出的USB转LAN芯片,WCH提供了对应的 UEFI驱动,用户有机会在 UEFI 环境中直接使用这个网卡。本次试验是在UEFI Shell 下启动一个 HTTP服务器,然后通过网络内另外一台机器的浏览器对这台机器进行访问。
这次我们使用一台机器作为 DHCP 服务器,能够给被测机器提供IP地址,具体的配置请看之前的文章。
第一步,Boot到UEFI 下,然后 load WCHUSBNIC.EFI 加载驱动

正确加载后,使用 ifconfig -l 列出当前系统中的网卡
第二步,运行如下命令让网卡通过 DHCP分配地址(如果你接入的网络没有DHCP,那么就需要手工指定网卡使用 IP)
Ifconfig -s eth0 dhcp
这步之后,再次运行 ifconfig -l 可以看到网卡的 MAC 还有分配到的 IP地址


第三步,我们在UEFI Shell 下运行一个 webserver

第四步,在主机端浏览器上使用 上面分配到的IP地址即可访问被测机。
具体的工作测试视频,可以在这里看到:
WCHUSBNIC.EFI 下载
TPS63802 升降压芯片模块测试
TPS63802DLAR 是 TI 推出降压/升压转换器。
• 输入电压范围:1.3V 至 5.5V – 器件启动时输入电压大于 1.8V
• 输出电压范围:1.8V 至 5.2V(可调节)
• VI ≥ 2.3V、VO = 3.3V 时,输出电流为 2A
这里使用这个芯片制作一个模块,对于外部输入的 1.8-5.5V 的电压转化为 3.3V。


焊接之后如下

接下来对模块进行测试:
1.7V输入时,模块不工作

1.8V时开始工作,输出3.3V

2.4V时输出3.3V

3.4V时输出3.3V

4.4V时输出3.3V

5.4V时输出3.3V

可见,这个芯片非常适合配合 3.7V 充电电池使用。
模块的电路图和PCB下载(立创标准版)
C# 程序:统计文本文件中未曾出现的 ASCII
一段简单的代码,找到给定的文本中哪个 ASCII 字符没有出现过
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace FilterAscii
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: AsciiCharCounter <filename>");
return;
}
string filename = args[0];
if (!File.Exists(filename))
{
Console.WriteLine($"File not found: {filename}");
return;
}
bool[] charPresence = new bool[128];
foreach (char c in File.ReadAllText(filename))
{
if (c < 128) charPresence[c] = true;
}
Console.WriteLine("Missing ASCII characters:");
for (int i = 0; i < 128; i++)
{
if (!charPresence[i])
{
Console.WriteLine($"'{(char)i}' (ASCII {i})");
}
}
}
}
}
我用这个工具分析一个Debug BIOS 串口输出结果如下,没有出现过的大部分 ASCII 都是不可见的,只有 $ 和 ^ 这两个可见的没有输出过

Arduino中ESP32 P4 中一次性设置多个 GPIO的方法
一般情况下我们可以用过 digitalWrite() 在 Arduino 中设置 GPIO 的 HIGH/LOW, 但是这种方法非常耗时。这里给出一种可以一次性设置多个 GPIO 的方法:
在 C:\Users\USERNAME\AppData\Local\Arduino15\packages\esp32\tools\esp32-arduino-libs\idf-release_v5.4-858a988d-v1-cn\esp32p4\include\soc\esp32p4\register\soc\gpio_struct.h 有如下定义:
extern gpio_dev_t GPIO;
typedef struct gpio_dev_t {
volatile gpio_bt_select_reg_t bt_select;
volatile gpio_out_reg_t out;
volatile gpio_out_w1ts_reg_t out_w1ts;
volatile gpio_out_w1tc_reg_t out_w1tc;
volatile gpio_out1_reg_t out1;
volatile gpio_out1_w1ts_reg_t out1_w1ts;
volatile gpio_out1_w1tc_reg_t out1_w1tc;
uint32_t reserved_01c;
volatile gpio_enable_reg_t enable;
volatile gpio_enable_w1ts_reg_t enable_w1ts;
volatile gpio_enable_w1tc_reg_t enable_w1tc;
volatile gpio_enable1_reg_t enable1;
volatile gpio_enable1_w1ts_reg_t enable1_w1ts;
volatile gpio_enable1_w1tc_reg_t enable1_w1tc;
volatile gpio_strap_reg_t strap;
volatile gpio_in_reg_t in;
volatile gpio_in1_reg_t in1;
volatile gpio_status_reg_t status;
volatile gpio_status_w1ts_reg_t status_w1ts;
volatile gpio_status_w1tc_reg_t status_w1tc;
volatile gpio_status1_reg_t status1;
volatile gpio_status1_w1ts_reg_t status1_w1ts;
volatile gpio_status1_w1tc_reg_t status1_w1tc;
volatile gpio_intr_0_reg_t intr_0;
volatile gpio_intr1_0_reg_t intr1_0;
volatile gpio_intr_1_reg_t intr_1;
volatile gpio_intr1_1_reg_t intr1_1;
volatile gpio_status_next_reg_t status_next;
volatile gpio_status_next1_reg_t status_next1;
volatile gpio_pin_reg_t pin[57];
volatile gpio_func_in_sel_cfg_reg_t func_in_sel_cfg[256]; /* func0-func255: reserved for func0, 46, 67, 72, 73, 79, 81, 82, 84, 85, 87, 88, 115, 116, 119-125, 157, 204-213 */
volatile gpio_func_out_sel_cfg_reg_t func_out_sel_cfg[57];
volatile gpio_intr_2_reg_t intr_2;
volatile gpio_intr1_2_reg_t intr1_2;
volatile gpio_intr_3_reg_t intr_3;
volatile gpio_intr1_3_reg_t intr1_3;
volatile gpio_clock_gate_reg_t clock_gate;
uint32_t reserved_650[44];
volatile gpio_int_raw_reg_t int_raw;
volatile gpio_int_st_reg_t int_st;
volatile gpio_int_ena_reg_t int_ena;
volatile gpio_int_clr_reg_t int_clr;
volatile gpio_zero_det_filter_cnt_reg_t zero_det_filter_cnt[2];
volatile gpio_send_seq_reg_t send_seq;
volatile gpio_recive_seq_reg_t recive_seq;
volatile gpio_bistin_sel_reg_t bistin_sel;
volatile gpio_bist_ctrl_reg_t bist_ctrl;
uint32_t reserved_728[53];
volatile gpio_date_reg_t date;
} gpio_dev_t;
其中的out_w1ts 和 out_w1tc 可以用来一次性设置多个 GPIO 为高或者为低。
测试代码:
#include "soc/gpio_struct.h" // GPIO
void setup() {
pinMode(7,OUTPUT);pinMode(8,OUTPUT);
pinMode(30,OUTPUT);pinMode(32,OUTPUT);pinMode(33,OUTPUT);pinMode(49,OUTPUT);
pinMode(50,OUTPUT);pinMode(52,OUTPUT);
digitalWrite(7,LOW);digitalWrite(8,LOW);
digitalWrite(30,LOW);digitalWrite(32,LOW);digitalWrite(33,LOW);digitalWrite(49,LOW);
digitalWrite(50,LOW);digitalWrite(52,LOW);
}
void loop() {
GPIO.out_w1ts.val = (1<<30)|(1<<8)|(1<<7);
GPIO.out1_w1ts.val=(1<<(33-32))|(1<<(32-32))|(1<<(52-32))|(1<<(49-32))|(1<<(50-32));
GPIO.out_w1tc.val = (1<<30)|(1<<8)|(1<<7);
GPIO.out1_w1tc.val=(1<<(33-32))|(1<<(32-32))|(1<<(52-32))|(1<<(49-32))|(1<<(50-32));
delay(1000);
}
硬件上GPIO33, 32, 30, 8, 7, 52, 49, 50分别连接到逻辑分析的4-11通道。
其中的 GPIO 初始化使用 Arduino 代码来完成,GPIO 的设置直接使用位操作。超过31的GPIO要在out1_w1ts和out1_w1tc上设置。
最终抓到的结果可以从下面看到:

参考:
1.https://github.com/maarten-pennings/howto/blob/main/esp32-fast-gpio/esp32-fast-gpio.md