2025年2月更新,Step to UEFI 文章索引:
Step to UEFI (300)改写SMBIOS
通过 SMBIOS 识别当前系统型号是最简单的方法。
通常情况下 SMBIOS是直接写在BIOS中的,可以使用一些工具重新写入,但是因为 IBV 的差别,没有通用工具。
最近我忽然意识到,对于 UEFI 来说,这个信息是存储在内存中的,如果在启动之前直接修改内存,那么就可以实现修改的目的。
代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/Smbios.h>
#include <IndustryStandard/SmBios.h>
#include <Guid/SmBios.h>
#include <string.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
/**
* 获取 SMBIOS 字符串
*/
CHAR8*
GetSmbiosStringByOffset (
IN UINT8 *StructureStart,
IN UINT8 StructureLength,
IN UINT8 StringNumber
)
{
CHAR8 *String;
UINTN Index;
if (StringNumber == 0) {
return NULL;
}
// 跳过结构体,到达字符串区域
String = (CHAR8 *)(StructureStart + StructureLength);
// 查找指定编号的字符串
for (Index = 1; Index < StringNumber; Index++) {
while (*String != 0) {
String++;
}
String++;
if (*String == 0) {
return NULL; // 没有更多字符串
}
}
return String;
}
/**
* 获取下一个 SMBIOS 结构的地址
*/
UINT8*
GetNextSmbiosStructure (
IN UINT8 *CurrentStructure
)
{
SMBIOS_STRUCTURE *Header;
UINT8 *StringPtr;
Header = (SMBIOS_STRUCTURE *)CurrentStructure;
// 跳过结构体到字符串区域
StringPtr = CurrentStructure + Header->Length;
// 跳过所有字符串,直到遇到双 NULL 终止符
while (!(StringPtr[0] == 0 && StringPtr[1] == 0)) {
StringPtr++;
}
// 跳过双 NULL 终止符
return StringPtr + 2;
}
/**
* 处理 SMBIOS Type 1 (System Information)
*/
VOID
ProcessType1SystemInfo (
IN UINT8 *Structure
)
{
SMBIOS_TABLE_TYPE1 *Type1;
CHAR8 *Manufacturer;
CHAR8 *ProductName;
CHAR8 *Version;
CHAR8 *SerialNumber;
Type1 = (SMBIOS_TABLE_TYPE1 *)Structure;
Print(L"=== System Information (Type 1) ===\n");
Manufacturer = GetSmbiosStringByOffset(Structure, Type1->Hdr.Length, Type1->Manufacturer);
if (Manufacturer != NULL) {
}
ProductName = GetSmbiosStringByOffset(Structure, Type1->Hdr.Length, Type1->ProductName);
if (ProductName != NULL) {
Print(L"Product Name: %a\n", ProductName);
strcpy(ProductName, "lab-z.com");
}
Version = GetSmbiosStringByOffset(Structure, Type1->Hdr.Length, Type1->Version);
if (Version != NULL) {
}
SerialNumber = GetSmbiosStringByOffset(Structure, Type1->Hdr.Length, Type1->SerialNumber);
if (SerialNumber != NULL) {
}
Print(L"UUID: %g\n", &Type1->Uuid);
Print(L"\n");
}
/**
* 解析 SMBIOS 2.1 表
*/
EFI_STATUS
ParseSmbios21Table (
IN SMBIOS_TABLE_ENTRY_POINT *Smbios21Entry
)
{
UINT8 *TableAddress;
UINT8 *CurrentStructure;
UINT8 *TableEnd;
SMBIOS_STRUCTURE *Header;
UINTN StructureCount = 0;
Print(L"=== SMBIOS 2.1 Entry Point ===\n");
Print(L"Anchor String: %.4a\n", Smbios21Entry->AnchorString);
Print(L"Major Version: %d\n", Smbios21Entry->MajorVersion);
Print(L"Minor Version: %d\n", Smbios21Entry->MinorVersion);
Print(L"Table Length: %d bytes\n", Smbios21Entry->TableLength);
Print(L"Table Address: 0x%x\n", Smbios21Entry->TableAddress);
Print(L"Number of Structures: %d\n", Smbios21Entry->NumberOfSmbiosStructures);
Print(L"\n");
TableAddress = (UINT8 *)(UINTN)Smbios21Entry->TableAddress;
TableEnd = TableAddress + Smbios21Entry->TableLength;
CurrentStructure = TableAddress;
// 遍历所有 SMBIOS 结构
while (CurrentStructure < TableEnd &&
StructureCount < Smbios21Entry->NumberOfSmbiosStructures) {
Header = (SMBIOS_STRUCTURE *)CurrentStructure;
// 检查是否到达表尾 (Type 127)
if (Header->Type == 127) {
break;
}
StructureCount++;
//Print(L"Structure %d: Type %d, Length %d, Handle 0x%04x\n",
// StructureCount, Header->Type, Header->Length, Header->Handle);
// 处理特定类型的结构
switch (Header->Type) {
case 1: // System Information
ProcessType1SystemInfo(CurrentStructure);
break;
default:
// 其他类型暂时只显示基本信息
break;
}
// 移动到下一个结构
CurrentStructure = GetNextSmbiosStructure(CurrentStructure);
}
Print(L"Total structures processed: %d\n", StructureCount);
return EFI_SUCCESS;
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status;
SMBIOS_TABLE_ENTRY_POINT *SmbiosTable = NULL;
Status = EfiGetSystemConfigurationTable (
&gEfiSmbiosTableGuid,
(VOID **)&SmbiosTable
);
if (!EFI_ERROR (Status)) {
Print (L"SmbiosTable Version %d.%d\n",
SmbiosTable->MajorVersion,
SmbiosTable->MinorVersion);
Print (L"SmbiosTable Address: %x \n",
SmbiosTable->TableAddress);
ParseSmbios21Table(SmbiosTable);
} else {
Print (L"Can't read SMBIOS\n");
}
return EFI_SUCCESS;
}
基本流程:
- ParseSmbios21Table() 找到 SMBIOS Table
- switch (Header->Type) case 1: 确认是System Information ,交给ProcessType1SystemInfo()函数
- 这里面找到ProductName,用我们的字符串替代之前的
实体机上测试结果如下:
原本是 Lenovo 的小新:

修改为 lab-z.com 的

在使用的时候需要特别注意可能发生覆盖之前字符串的问题,不要溢出,不要破坏之前的结构。
Windows 下制造蓝屏的简单方法
Windows 11 管理员权限打开 cmd 窗口,输入如下命令即可:
Taskkill /fi "pid ge 1" /f
无需任何工具100% 0x000007f或f4蓝屏,重启后对系统无任何影响。
UEFI TIPS: 在一个程序中启动另外一个程序
这里提供一个在一个EFI程序中启动另外一个EFI 的例子,没有使用 UEFI Shell API ,放置在 ESP 分区后,可以启动当前的 Windows。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Guid/FileInfo.h>
/**
* 启动指定路径的 EFI 应用程序
*/
EFI_STATUS
StartEfiApplication (
IN EFI_HANDLE ParentImageHandle,
IN CHAR16 *ApplicationPath
)
{
EFI_STATUS Status;
EFI_HANDLE ChildImageHandle;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
EFI_LOADED_IMAGE_PROTOCOL *ParentLoadedImage;
UINTN ExitDataSize;
CHAR16 *ExitData;
Print(L"=== Starting EFI Application: %s ===\n", ApplicationPath);
// 步骤 1: 获取父镜像的 LoadedImage 协议
Status = gBS->HandleProtocol(
ParentImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID**)&ParentLoadedImage
);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Failed to get parent LoadedImage protocol: %r\n", Status);
return Status;
}
Print(L"SUCCESS: Got parent LoadedImage protocol\n");
// 步骤 2: 构建目标应用的设备路径
DevicePath = FileDevicePath(ParentLoadedImage->DeviceHandle, ApplicationPath);
if (DevicePath == NULL) {
Print(L"ERROR: Failed to create device path for %s\n", ApplicationPath);
return EFI_OUT_OF_RESOURCES;
}
Print(L"SUCCESS: Created device path\n");
// 步骤 3: 加载目标镜像
Status = gBS->LoadImage(
FALSE, // BootPolicy - FALSE 表示不是启动策略
ParentImageHandle, // ParentImageHandle - 父镜像句柄
DevicePath, // DevicePath - 目标文件的设备路径
NULL, // SourceBuffer - NULL 表示从设备路径加载
0, // SourceSize - 0 表示从设备路径加载
&ChildImageHandle // ImageHandle - 返回的子镜像句柄
);
// 释放设备路径内存
FreePool(DevicePath);
if (EFI_ERROR(Status)) {
Print(L"ERROR: LoadImage failed: %r\n", Status);
return Status;
}
Print(L"SUCCESS: Image loaded successfully, Handle = 0x%lx\n", (UINTN)ChildImageHandle);
// 步骤 4: 启动镜像
Print(L"Starting image...\n");
Status = gBS->StartImage(
ChildImageHandle, // ImageHandle - 要启动的镜像句柄
&ExitDataSize, // ExitDataSize - 返回退出数据大小
&ExitData // ExitData - 返回退出数据
);
// 步骤 5: 处理启动结果
if (EFI_ERROR(Status)) {
Print(L"ERROR: StartImage failed: %r\n", Status);
// 如果有退出数据,显示它
if (ExitData != NULL && ExitDataSize > 0) {
Print(L"Exit Data Size: %d bytes\n", ExitDataSize);
Print(L"Exit Data: %s\n", ExitData);
// 释放退出数据内存
gBS->FreePool(ExitData);
}
} else {
Print(L"SUCCESS: Image started and returned: %r\n", Status);
// 处理正常退出的数据
if (ExitData != NULL && ExitDataSize > 0) {
Print(L"Application returned data: %s\n", ExitData);
gBS->FreePool(ExitData);
}
}
// 步骤 6: 卸载镜像(如果需要)
Print(L"Unloading image...\n");
gBS->UnloadImage(ChildImageHandle);
Print(L"=== Application execution completed ===\n\n");
return Status;
}
/**
* 检查文件是否存在
*/
EFI_STATUS
CheckFileExists (
IN EFI_HANDLE DeviceHandle,
IN CHAR16 *FilePath
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
EFI_FILE_PROTOCOL *Root;
EFI_FILE_PROTOCOL *File;
// 获取文件系统协议
Status = gBS->HandleProtocol(
DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid,
(VOID**)&FileSystem
);
if (EFI_ERROR(Status)) {
return Status;
}
// 打开根目录
Status = FileSystem->OpenVolume(FileSystem, &Root);
if (EFI_ERROR(Status)) {
return Status;
}
// 尝试打开目标文件
Status = Root->Open(
Root,
&File,
FilePath,
EFI_FILE_MODE_READ,
0
);
if (!EFI_ERROR(Status)) {
Print(L"File exists: %s\n", FilePath);
File->Close(File);
} else {
Print(L"File not found: %s (Status: %r)\n", FilePath, Status);
}
Root->Close(Root);
return Status;
}
/**
* 主入口函数
*/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
EFI_INPUT_KEY Key;
// 清屏
gST->ConOut->ClearScreen(gST->ConOut);
Print(L"UEFI StartImage Example Application\n");
Print(L"====================================\n\n");
// 获取当前镜像信息
Status = gBS->HandleProtocol(
ImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID**)&LoadedImage
);
if (EFI_ERROR(Status)) {
Print(L"Failed to get LoadedImage protocol: %r\n", Status);
return Status;
}
// 示例 : 启动 Windows Boot Manager
Print(L"Example 1: Starting Windows Boot Manager\n");
CheckFileExists(LoadedImage->DeviceHandle, L"EFI\\Boot\\bootx64.efi");
Status = StartEfiApplication(ImageHandle, L"EFI\\Boot\\bootx64.efi");
Print(L"Windows Boot Manager result: %r\n\n", Status);
// 等待用户按键
Print(L"Press any key to exit...\n");
gST->ConIn->Reset(gST->ConIn, FALSE);
while (gST->ConIn->ReadKeyStroke(gST->ConIn, &Key) == EFI_NOT_READY) {
gBS->Stall(10000); // 等待 10ms
}
return EFI_SUCCESS;
}
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = sat
FILE_GUID = 4ea97c46-7491-2025-1125-747010f3ce5f
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
ENTRY_POINT = UefiMain
#
# VALID_ARCHITECTURES = IA32 X64 IPF
#
[Sources]
StartImageTest.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
UefiBootServicesTableLib
[Protocols]
gEfiLoadedImageProtocolGuid
gEfiSimpleFileSystemProtocolGuid
gEfiSimpleTextInProtocolGuid
gEfiSimpleTextOutProtocolGuid
[BuildOptions]
[Guids]
SMBIOS 2.X 和 3.X 区别
今天偶然发现 SMBIOS 2.X 和 3.X 存在一些差别,在处理的时候代码需要不同对待。
1. 入口点结构 (Entry Point Structure)定义的差异:
SMBIOS 2.X Entry Point
typedef struct {
UINT8 AnchorString[4]; // "_SM_"
UINT8 EntryPointStructureChecksum;
UINT8 EntryPointLength; // 0x1F
UINT8 MajorVersion;
UINT8 MinorVersion;
UINT16 MaxStructureSize;
UINT8 EntryPointRevision;
UINT8 FormattedArea[5];
UINT8 IntermediateAnchorString[5]; // "_DMI_"
UINT8 IntermediateChecksum;
UINT16 TableLength; // 表长度
UINT32 TableAddress; // 32位表地址
UINT16 NumberOfSmbiosStructures;
UINT8 SmbiosBcdRevision;
} SMBIOS_TABLE_ENTRY_POINT;
SMBIOS 3.X Entry Point
typedef struct {
UINT8 AnchorString[5]; // "_SM3_"
UINT8 EntryPointStructureChecksum;
UINT8 EntryPointLength; // 0x18
UINT8 MajorVersion;
UINT8 MinorVersion;
UINT8 DocRev;
UINT8 EntryPointRevision;
UINT8 Reserved;
UINT32 TableMaximumSize; // 表最大长度
UINT64 TableAddress; // 64位表地址
} SMBIOS_TABLE_3_0_ENTRY_POINT;
2. 主要技术差异
| 特性 | SMBIOS 2.X | SMBIOS 3.X |
|---|---|---|
| 地址空间 | 32位地址 | 64位地址 |
| 表大小限制 | 最大 65535 字节 | 最大 4GB |
| 入口点标识 | “SM” + “DMI“ | “SM3“ |
| 入口点大小 | 31 字节 (0x1F) | 24 字节 (0x18) |
| 结构计数 | 明确指定结构数量 | 不指定,需遍历到Type 127 |
| 校验和 | 两个校验和 | 一个校验和 |
实践发现,目前机器有不同的实现方式,比如:声明了 3.0 但是实际上仍然是 2.0 的结构;3.0 和 2.0 同时共存,这种情况下看起来 Windows 更倾向于使用 3.o 提供的信息。
基于 FireBeetle P4 制作一个USB 麦克风
在 FireBeetle P4 板子上,有一个 PDM 的麦克风。

基本实现原理是:通过ESP32 IDF编程使用 TinyUSB 架构将 P4 模拟为 USB UAC 设备,然后通过这个麦克风获得环境声音,这样就得到了一个 USB 麦克风。
需要特别注意的是 PDM 的初始化和 I2S 的会有一些差异,DFRobot 选择的这个数字麦克风资料很少,看起来只支持一个 24K 的采样率:
void init_pdm_rx(void) {
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_new_channel(&chan_cfg, NULL, &rx);
i2s_pdm_rx_config_t pdm_cfg = {
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE),
//.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.slot_cfg = I2S_PDM_RX_SLOT_PCM_FMT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.clk = MIC_I2S_CLK, // PDM clock
// QUESTION - what about the LR clock pin? No longer relevant? Do we ties it high or low?
.din = MIC_I2S_DATA, // PDM data
.invert_flags = { .clk_inv = false },
},
};
pdm_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; // single mic
i2s_channel_init_pdm_rx_mode(rx, &pdm_cfg);
i2s_channel_enable(rx);
}
上述设置之后,就可以在回调函数中填充需要对主机反馈的数据了:
static esp_err_t usb_uac_device_input_cb(uint8_t *buf, size_t len, size_t *bytes_read, void *arg)
{
if (!rx) {
return ESP_FAIL;
}
//memcpy(buf,Buff,len);
//*bytes_read=len;
//return ESP_OK;
return i2s_channel_read(rx, buf, len, bytes_read, portMAX_DELAY);
}
完整的代码:
工作的视频
eSPI 综述
这篇文章来自 MicroChip ,原文在【参考1】,标题是“是时候迁移到 eSPI 总线了吗?”(Is It Time to Migrate to the eSPI Bus?)。
大多数计算机用户都知道高速总线的存在,比如 PC 上配备的 PCI Express® (PCIe®) 附加卡或 USB 接口。然而,他们可能不知道所有计算机上都存在低速总线。多年来,这种总线一直用于连接各种设备,例如嵌入式控制器 (EC,笔记本上使用)、基板管理控制器 (BMC,服务器上用于远程管理)、Super I/O (SIO, 台式机) 、用于存储 BIOS 代码的SPINOR以及可信平台模块 (TPM) 到系统核心逻辑。这种低速总线最初被称为低引脚数 (LPC) 总线。
随着计算行业需求的不断发展,更加灵活高效的增强型串行外设接口 (eSPI) 总线应运而生,以克服 LPC 总线的局限性。这款一体化总线由最新的 PC 计算芯片组支持,旨在取代 LPC 总线以及 SPI 总线、SMbus 和Sideband信号。这种情况下可以通过一个 GPIO 控制这个设备的供电重新给让它工作起来,这个 GPIO 就可以称作 Out band)。对于计算应用设计人员而言,从 LPC 总线迁移到 eSPI 总线具有以下优势:
- 节省成本:由于 LPC 总线需要大量边带信号来实现电源排序和睡眠模式支持,因此它使用 13 个引脚连接到系统处理器。eSPI 协议使用虚拟线来实现其中一些信号,因此大多数实现中只需要五六个引脚,从而减少了引脚数量和成本。
- 更低电压:LPC总线需要3.3VI/O信号,而eSPI总线使用1.8V,显著降低系统功耗。
- 简化电路板布局和设计:LPC 总线需要同步 24 MHz 或 33 MHz 时钟,因此需要仔细的电路板布局,以确保时钟和数据信号长度与所有设备匹配。eSPI 总线使用来自系统处理器的主驱动时钟,从而简化了电路板布局和设计。
- 低功耗状态:LPC 总线只能在系统处于 S0 状态时运行,而 eSPI 总线则可以在系统处于低功耗 S5 状态时运行。这可以实现许多系统改进,包括:
- 用于支持电源排序的边带信号可以打包在eSPI 中传输从而变成虚拟线,就无需在硬件上拉出来线路。
- EC可以在启动时共享系统SPI存储,从而无需在系统中添加额外的SPI芯片,从而降低系统成本。
- 在 S5 状态下,eSPI 总线可用于核心逻辑与 EC 之间的通信。这样可以移除额外的边带通信总线,例如 I²C 和 PECI,从而减少电路板上的额外信号。
以下两个图表显示了基于 LPC 的系统和基于 eSPI 的系统之间的差异。

图-LPC系统图

图 – eSPI 系统图
从上图可以看到,很多总线和功能能够“打包”到 eSPI中。
eSPI 规范指定了几种可通过总线进行通信的模式或通道:
- 外设通道用于与位于 EC、BMC 和 SIO 中的设备(以前位于 LPC 总线上)进行通信。这些设备包括 UART、邮箱寄存器、端口 80 寄存器、嵌入式内存接口和键盘控制器。外设通道还支持总线主控通道。总线主控功能允许 EC 直接从主系统内存读取/写入数据。
- 虚拟线通道用于将边带信号信息传输到/接收自 EC、BMC 和 SIO。来自外围设备(例如 UART)的中断也通过虚拟线通道传输。与 LPC 总线相比,该通道大大减少了 eSPI 总线的引脚数量和成本。
- 带外 (OOB) 消息通道用于通过 eSPI 传输 SMBus 流量。这些消息可以包括系统逻辑和处理器温度值,或 SMBus 管理组件传输协议 (MCTP) 数据包。
- 闪存访问通道允许系统处理器在 BIOS、管理引擎 (ME)、EC、BMC 和 SIO 之间共享系统 SPI Flash。这通过减少系统中 SPI Flash 芯片的数量来降低系统成本。
如果您准备将您的设计迁移到支持 eSPI 总线,我们的 MEC14xx 和 MEC17xx 嵌入式控制器是绝佳选择。Microchip 是首批支持 eSPI 总线的公司之一,并被英特尔® 选为其 eSPI 开发的验证合作伙伴。这意味着我们的设备已通过英特尔 eSPI 主站的全面验证。英特尔还选择了我们的 EC 作为其参考验证平台,确保它们获得英特尔的全面支持。访问我们的嵌入式控制器设计中心 ,了解更多关于如何将您的计算设计迁移到这项新总线技术的信息。
参考:
PY 更改默认值
Py 是 Python 的 Launcher,在安装的时候可以选择:

装好之后,运行 py 可以直接打开 Python。它的作用是让你方便的在不同版本之间切换。比如,你的系统安装了多个 Python,可以使用 py –list 进行查看:

默认情况下运行 py ,会运行 3.14 版本的。
一些资料上说可以通过修改 py.ini 的方法修改上面的列表,但是这个文件安装之后不会自动生成,有需要的话可以手工添加,例如下面的文件保存为 py.ini 然后放在 py.exe 同一个目录下,每次运行 py 会自动调用 Python 3.8:
;
; This is an example of how a Python Launcher .ini file is structured.
; If you want to use it, copy it to py.ini and make your changes there,
; after removing this header comment.
; This file will be removed on launcher uninstallation and overwritten
; when the launcher is installed or upgraded, so don't edit this file
; as your changes will be lost.
;
[defaults]
; Uncomment out the following line to have Python 3 be the default.
python=3.8
[commands]
; Put in any customised commands you want here, in the format
; that's shown in the example line. You only need quotes around the
; executable if the path has spaces in it.
;
; You can then use e.g. #!myprog as your shebang line in scripts, and
; the launcher would invoke e.g.
;
; "c:\Program Files\MyCustom.exe" -a -b -c myscript.py
;
;myprog="c:\Program Files\MyCustom.exe" -a -b -c
此外,还有一种方法是设定一个环境变量,例如:set py_python=3.13,再次运行py 会直接调用 python 3.13

上述方法来自【参考1】。
但是,上述方法并不能完全解决问题,比如,我的编译环境中会调用 py -3 来启动 Python ,测试下来 -3 参数会导致py 自动调用当前系统中最新版本的 Python。我这边研究之后的解决方法是:重新编译 Python Source Code 直接写死:
在Python-3.14.0\Python-3.14.0\PC\launcher2.c 中,首先在开头处定义需要的版本:
static FILE * log_fp = NULL;
wchar_t MyTag[] = L"3.8";
void
debug(wchar_t * format, ...)
{
va_list va;
然后修改代码
if (argLen > 0) {
if (STARTSWITH(L"2") || STARTSWITH(L"3")) {
// All arguments starting with 2 or 3 are assumed to be version tags
//LAB-Z_Debug search->tag = arg;
//LAB-Z_Debug search->tagLength = argLen;
//LAB-Z_Debug_Start
wchar_t MyTag[] = L"3.8";
search->tag = MyTag;
search->tagLength = 3;
//LAB-Z_Debug_End
search->oldStyleTag = true;
search->restOfCmdLine = tail;
} else if (STARTSWITH(L"V:") || STARTSWITH(L"-version:")) {
编译后的 py.exe 替换之前的 py.exe即可。
修改后的代码下载:
重新编译后的 py.exe 下载(固定调用 3.8)
参考:
1.https://docs.python.org/3/using/windows.html
2.调试时可以设置 set PYLAUNCHER_DEBUG=1 这样会打开 Py.exe 的调试输出,方便研究。
UEFI TIPS: 这样会导致内存泄漏吗?
最近编写代码的时候,忽然提出一个问题,按照下面的写法会导致内存泄漏的问题吗?
for (UINTN i=0;i<1000;i++) {
UINTN x;
Print(L"%x\n",x++);
}
为了验证这个,编写一个完整的 UEFI 代码进行测试:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
/***
Print a welcoming message.
Establishes the main structure of the application.
@retval 0 The application exited normally.
@retval Other An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
for (UINTN i=0;i<1000;i++) {
UINTN x;
Print(L"%x\n",x++);
}
return(0);
}
对应的,在 INF 文件中定义生成汇编代码:
[BuildOptions]
MSFT:*_*_X64_CC_FLAGS = /FAsc /Od
查看生成的 cod文件:
$LN6:
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 48 83 ec 48 sub rsp, 72 ; 00000048H
; 27 :
; 28 : for (UINTN i=0;i<1000;i++) {
0000e 48 c7 44 24 20
00 00 00 00 mov QWORD PTR i$1[rsp], 0
00017 eb 0d jmp SHORT $LN4@ShellAppMa
$LN2@ShellAppMa:
00019 48 8b 44 24 20 mov rax, QWORD PTR i$1[rsp]
0001e 48 ff c0 inc rax
00021 48 89 44 24 20 mov QWORD PTR i$1[rsp], rax
$LN4@ShellAppMa:
00026 48 81 7c 24 20
e8 03 00 00 cmp QWORD PTR i$1[rsp], 1000 ; 000003e8H
0002f 73 2a jae SHORT $LN3@ShellAppMa
; 29 : UINTN x;
; 30 : Print(L"%x\n",x++);
00031 48 8b 44 24 28 mov rax, QWORD PTR x$2[rsp]
00036 48 89 44 24 30 mov QWORD PTR tv68[rsp], rax
0003b 48 8b 54 24 30 mov rdx, QWORD PTR tv68[rsp]
00040 48 8d 0d 00 00
00 00 lea rcx, OFFSET FLAT:??_C@_17KDIHCDGM@?$AA?$CF?$AAx?$AA?6@
00047 e8 00 00 00 00 call Print
0004c 48 8b 44 24 28 mov rax, QWORD PTR x$2[rsp]
00051 48 ff c0 inc rax
00054 48 89 44 24 28 mov QWORD PTR x$2[rsp], rax
; 31 : }
00059 eb be jmp SHORT $LN2@ShellAppMa
$LN3@ShellAppMa:
可以看到:变量 X 就是 QWORD PTR i$1[rsp],在循环中并不会每次重新分配内存。因此,无需担心内存泄漏的问题。
查了一下资料:
“在C语言的早期版本中,局部变量的声明必须集中在函数或代码块的开头,位于任何可执行语句之前。这种限制在C89/ANSI C标准中得到了明确的规范。
随着C语言标准的发展,C99标准引入了更灵活的变量声明方式,允许在代码块的任意位置声明局部变量,只要遵循“先定义后使用”的原则即可。这种改进使得程序员可以在需要使用变量的地方才进行声明,从而提高了代码的可读性和编写灵活性。“
Flex Windows 下的简单测试
一般来说,计算机系的毕业生很容易编写出来一个词法分析器,能够将输入的文本解析为 Token 。但是业界已经有了成熟完善的方法和工具,Flex 就是其中的一个
Flex是一种用于生成词法分析器的工具,通过读取包含正则表达式和对应C代码的规则文件,自动生成可识别特定词法模式的C语言源代码。其输入文件由定义区、规则区、用户代码区组成,支持与语法分析器生成工具Bison协同工作。生成的词法分析器可应用于编译器开发、复杂系统建模、教学实验等领域,具有正则表达式语法兼容Lex、错误处理机制完善、自定义函数扩展灵活等技术特性。
Flex通过解析用户定义的正则表达式规则,生成C语言实现的词法分析器源码,可自动将输入文本分解为预定义的词法单元。生成的词法分析器包含默认入口函数,支持通过重定义宏实现自定义输入源。工具保持与Lex语法的高度兼容性,生成的代码可直接嵌入到C/C++工程项目中使用。
从上面也可以看到使用 Flex 的好处是:可以通过定义正则表达式规则来进行此法分析,方便编写C代码,此外生成的结果可以配合 Bison进行语法分析。之前提到过, ACPICA 提供的套件就是基于 Flex和Bison 编写的。
1.根据【参考1】配置环境
2.编写 lexer.l 代码如下:
%{
#include <stdio.h>
#include <stdlib.h>
// 手动定义Token类型(若无Bison)
#define NUMBER 256
#define ID 257
#define PLUS 258
int line_num = 1;
%}
DIGIT [0-9]
LETTER [a-zA-Z]
%%
{DIGIT}+ { printf("NUMBER: %s\n", yytext); return NUMBER; }
{LETTER}+ { printf("IDENTIFIER: %s\n", yytext); return ID; }
"+" { printf("PLUS\n"); return PLUS; }
"\n" { line_num++; }
[ \t] ; // 忽略空白字符
. { printf("Unknown char: %s\n", yytext); }
%%
int yywrap() { return 1; }
3.Visual C++ 2019 编写 flextest.c 文件(不是 .cpp)如下:
#include <stdio.h>
#pragma warning(disable:4996)
#include "lex.yy.c" // 包含Flex生成的代码
int main() {
yyin = fopen("input.txt", "rb");
if (!yyin) {
perror("Failed to open input file");
return 1;
}
while (yylex()) {
}
fclose(yyin);
return 0;
}
4.使用时,先运行 flex lexer.l 生成 lex.yy.c(如果需要 debug 可以使用 flex -d lexer.l)。
5.编译flextest.c,然后配合如下测试文件
123
abc
+
xyz 456
@
123123
1222
4dd
6.运行结果如下

可以看到输出了识别到的各种Token
EDK2 202508 来了
今年8月份,EDK2 202508正式发布在:
https://github.com/tianocore/edk2/releases/tag/edk2-stable202508
从 History 来看,增加了一点新的功能
- update to openssl 3.5.1
- MdeModule: Update oniguruma to v6.9.10
- Support Standalone MM on OVMF
- MdePkg/IndustryStandard: update Tpm2Acpi table to revision 5
- Adding FF-A memory management library
- Add UUID-GUID conversion interfaces in ArmFfaLib
- BaseTools: Add support for mingw-w64
- Remove UGA support
- Add support for ARM GICv5
- RISC-V: Support PEI booting
在上面的链接下载代码之后,大小是 25MB 左右,但是无法直接编译(build -a X64 -p EmulatorPkg\EmulatorPkg.dsc -t VS2019),会出现下面的错误
MdePkg.dec(33): error 000E: File/directory not found in workspace
C:\BuildBs\edk2508\MdePkg\Library\MipiSysTLib\mipisyst\library\include
产生的原因是这个目录下缺少文件,可以通过和抓到的完整版进行比较补全:

补全上面的之后,还是会碰到类似问题,也是因为缺少文件导致的,重复上述步骤补全。最终得到一个可以完整编译EmulatorPkg的。
于是,这次提供三个 Pacakge
1.原始的 EDK2 202508 (24.6MB)
edk2-edk2-stable202508_ORG.zip
链接: https://pan.baidu.com/s/1onGGn_4UFN3loVktKNrVGQ?pwd=labz 提取码: labz
2.修改后的EDK2 202508,可以编译 EmulatorPkg (207MB)
edk2508.7z
链接: https://pan.baidu.com/s/1fhPY4Hqqf5jOE6iqJ6Dheg?pwd=labz 提取码: labz
3.完整的 EDK2 202508 , 包括所有的第三方源代码(2.27G)
edk22508Full.7z
链接: https://pan.baidu.com/s/1TSbmW8v7jTtmdsXc40ScQQ?pwd=labz 提取码: labz