UEFI 笔记 002 —(PrintfLib 不是 Print 是 Format)+(String Op)
声明:个人笔记,概不负责
严谨性声明:行文随意使用 UEFI 或 EDK2。事实上,先有 EDK2 再有 UEFI;抠字眼,EDK2 只是 UEFI 的一种实现;实际上,本人没接触过 EDK2 以外的实现。
UEFI 的 PrintLib 只是 格式化字符串, 并不打印输出.
格式化小语言
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PrintLib.h
典型实现
https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BasePrintLib/PrintLib.c
UefiLib 是真的 打印输出 Print
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/UefiLib.h#L1091
ShellLib 格式化小语言 ShellPrintEx
https://github.com/tianocore/edk2/blob/master/ShellPkg/Include/Library/ShellLib.h#L921
主要函数清单(此处只是 Format,不是真的 Output)
// MdePkg\Include\Library\PrintLib.h
//
// This function is similar as snprintf_s defined in C11.
UnicodeSPrint(OUT CHAR16 *, IN UINTN, IN const CHAR16 *, ...);
UnicodeSPrintAsciiFormat(OUT CHAR16 *, IN UINTN, IN const CHAR8 *, ...);
AsciiSPrint(OUT CHAR8 *, IN UINTN, IN const CHAR8 *, ...);
AsciiSPrintUnicodeFormat(OUT CHAR8 *, IN UINTN, IN const CHAR16 *, ...);
// // Supporting variants
// UnicodeVSPrint(OUT CHAR16 *, IN UINTN, IN const CHAR16 *, IN VA_LIST);
// UnicodeBSPrint(OUT CHAR16 *, IN UINTN, IN const CHAR16 *, IN BASE_LIST);
// UnicodeVSPrintAsciiFormat(OUT CHAR16 *, IN UINTN, IN const CHAR8 *, IN VA_LIST);
// UnicodeBSPrintAsciiFormat(OUT CHAR16 *, IN UINTN, IN const CHAR8 *, IN BASE_LIST);
//
// AsciiVSPrint(OUT CHAR8 *, IN UINTN, IN const CHAR8 *, IN VA_LIST);
// AsciiBSPrint(OUT CHAR8 *, IN UINTN, IN const CHAR8 *, IN BASE_LIST);
// AsciiVSPrintUnicodeFormat(OUT CHAR8 *, IN UINTN, IN const CHAR16 *, IN VA_LIST);
// AsciiBSPrintUnicodeFormat(OUT CHAR8 *, IN UINTN, IN const CHAR16 *, IN BASE_LIST);
//
// Converts a decimal value to a Null-terminated Unicode string.
UnicodeValueToStringS(IN OUT CHAR16 *, IN UINTN, IN UINTN, IN INT64, IN UINTN);
AsciiValueToStringS(IN OUT CHAR8 *, IN UINTN, IN UINTN, IN INT64, IN UINTN);
// Returns the number of characters that would be produced, not including the Null-terminator.
SPrintLength(IN const CHAR16 *, IN VA_LIST);
SPrintLengthAsciiFormat(IN const CHAR8 *, IN VA_LIST);
// MdePkg\Library\BasePrintLib\PrintLib.c
// MdePkg\Library\BasePrintLib\PrintLibInternal.h
//
// 有趣的 “隐藏技能” (使用方式 见最后) 将 Value 转换成, 任意基数表达的字符串. (基数 16 以内不会出错)
//
// Internal function that convert a number to a string in Buffer.
BasePrintLibValueToString(IN OUT CHAR8 *Buffer, IN INT64 Value, IN UINTN Radix);
==
(此处只是 Format,不是真的 Output)
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/UefiLib.h
// MdePkg\Include\Library\UefiLib.h
// MdePkg\Library\UefiLib\UefiLibPrint.c
// https://github.com/tianocore/edk2/blob/master/MdePkg/Library/UefiLib/UefiLibPrint.c#L693
// BufferToReturn = AllocatePool (SizeRequired); // 新分配, 不会释放 喂入的 String 参数
//
//
// 貌似有意思, ***实际 容易用错(有毛病的)*** EDK2 基本函数
//
// Appends a formatted Unicode string to a Null-terminated Unicode string
// The caller is responsible for freeing the returned string.
//
CatSPrint(IN CHAR16 *String, IN const CHAR16 *FormatString, ...);
CatVSPrint(IN CHAR16 *String, IN const CHAR16 *FormatString, IN VA_LIST);
//
// ———— 连续使用时, 必须 **手工清除** **每个** 中间产生的 String
// 1. 第一次用 NULL 当 String 传入, 产生第一个 结果;
// 2. 之后, 把返回的结果 继续喂入, 继续 Cat // 需要 手工 FreePool () 这次 喂入的 String 参数
// 3. 依此类推 // 需要 手工 FreePool () 这次 喂入的 String 参数
// 4. caller 需要 FreePool () 最后返回的 String
//
// 事实上, EDK2 自己 在 2-3 这里, 错的一塌糊涂!
// 典型错误 in EDK2
// ShellPkg\Library\UefiHandleParsingLib\UefiHandleParsingLib.c
// LoadedImageProtocolDumpInformation ()
// RetVal = CatSPrint (
// RetVal,
//
// 结论:这个 CatSPrint () 的本意是连续 Cat , 流畅使用
// 但这个 脑子不清楚 的实现, 全给搞砸了 ———— 我有着闲工夫 每次都 FreePool() 吗?
//
// 2025-11-02:
// 突然发现 CatSPrint () 非常好用, 当 String 参数为 NULL 时, 同时做到了 Allocate + Format
// PrintLib 那些 Format 函数, 都需要提供 Buffer + Size 参数, 随机打些东西, 无比烦恼.
==
(字符串 操作,依赖 ShellPkg)
https://github.com/tianocore/edk2/blob/master/ShellPkg/Include/Library/ShellLib.h
// ShellPkg\Include\Library\ShellLib.h
// ShellPkg\Library\UefiShellLib\UefiShellLib.c
//
// 用法与 CatSPrint () 类似,但好在没啥毛病,虽然实现有点 傻了吧唧.
//
StrnCatGrow(IN OUT CHAR16 **Destination, IN OUT UINTN *CurrentSize, IN const CHAR16 *Source, IN UINTN Count);
//
// 内部用 StrSize () 算出当前尾巴, 然后用 StrnCatS () 进行 Cat // 好傻, 但简化了输入参数
// 若是 *CurrentSize == 0 则, 初始分配内存 // 有点妖, 不是根据 *Destination 直接判断.
// 若是 Size 不够,就会 ReallocatePool (), 不会错用内存泄漏 // 好傻, 实际每次都会 Reallocate
// caller 只要 FreePool () 最后的 String 就行 // 好棒!
////
ShellCopySearchAndReplace(IN CHAR16 const *, IN OUT CHAR16 *, IN UINTN, IN const CHAR16 *, IN const CHAR16 *, IN const BOOLEAN, IN const BOOLEAN);
ShellStrToUintn(IN const CHAR16 *);
ShellHexStrToUintn(IN const CHAR16 *);
ShellIsHexaDecimalDigitCharacter(IN CHAR16);
ShellIsDecimalDigitCharacter(IN CHAR16);
ShellIsHexOrDecimalNumber(IN const CHAR16 *, IN const BOOLEAN, IN const BOOLEAN);
ShellConvertStringToUint64(IN const CHAR16 *, OUT UINT64 *, IN const BOOLEAN, IN const BOOLEAN);
==
(字符串 操作,无需担心 依赖,核心库提供)
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/BaseLib.h
// MdePkg\Include\Library\BaseLib.h
StrnLenS(IN const CHAR16 *, IN UINTN)
StrnSizeS(IN const CHAR16 *, IN UINTN)
StrCpyS(OUT CHAR16 *, IN UINTN, IN const CHAR16 *)
StrnCpyS(OUT CHAR16 *, IN UINTN, IN const CHAR16 *, IN UINTN)
StrCatS(IN OUT CHAR16 *, IN UINTN, IN const CHAR16 *)
StrnCatS(IN OUT CHAR16 *, IN UINTN, IN const CHAR16 *, IN UINTN)
StrDecimalToUintnS(IN const CHAR16 *, OUT CHAR16 **EndPointer, OUT UINTN *)
StrDecimalToUint64S(IN const CHAR16 *, OUT CHAR16 **EndPointer, OUT UINT64 *)
StrHexToUintnS(IN const CHAR16 *, OUT CHAR16 **EndPointer, OUT UINTN *)
StrHexToUint64S(IN const CHAR16 *, OUT CHAR16 **EndPointer, OUT UINT64 *)
AsciiStrnLenS(IN const CHAR8 *, IN UINTN)
AsciiStrnSizeS(IN const CHAR8 *, IN UINTN)
AsciiStrCpyS(OUT CHAR8 *, IN UINTN, IN const CHAR8 *)
AsciiStrnCpyS(OUT CHAR8 *, IN UINTN, IN const CHAR8 *, IN UINTN)
AsciiStrCatS(IN OUT CHAR8 *, IN UINTN, IN const CHAR8 *)
AsciiStrnCatS(IN OUT CHAR8 *, IN UINTN, IN const CHAR8 *, IN UINTN)
AsciiStrDecimalToUintnS(IN const CHAR8 *, OUT CHAR8 **EndPointer, OUT UINTN *)
AsciiStrDecimalToUint64S(IN const CHAR8 *, OUT CHAR8 **EndPointer, OUT UINT64 *)
AsciiStrHexToUintnS(IN const CHAR8 *, OUT CHAR8 **EndPointer, OUT UINTN *)
AsciiStrHexToUint64S(IN const CHAR8 *, OUT CHAR8 **EndPointer, OUT UINT64 *)
StrLen(IN const CHAR16 *)
StrSize(IN const CHAR16 *)
StrCmp(IN const CHAR16 *, IN const CHAR16 *)
// 真没有 StriCmp
StrnCmp(IN const CHAR16 *, IN const CHAR16 *, IN UINTN)
StrStr(IN const CHAR16 *, IN const CHAR16 *)
StrDecimalToUintn(IN const CHAR16 *)
StrDecimalToUint64(IN const CHAR16 *)
StrHexToUintn(IN const CHAR16 *)
StrHexToUint64(IN const CHAR16 *)
StrToIpv6Address(IN const CHAR16 *, OUT CHAR16 **EndPointer, OUT IPv6_ADDRESS *, OUT UINT8 *PrefixLength)
StrToIpv4Address(IN const CHAR16 *, OUT CHAR16 **EndPointer, OUT IPv4_ADDRESS *, OUT UINT8 *PrefixLength)
StrToGuid(IN const CHAR16 *, OUT GUID *)
StrHexToBytes(IN const CHAR16 *, IN UINTN, OUT UINT8 *, IN UINTN)
UnicodeStrToAsciiStrS(IN const CHAR16 *, OUT CHAR8 *, IN UINTN) // 只处理 ASCII 范围内的,若不是则 ASSERT
UnicodeStrnToAsciiStrS(IN const CHAR16 *, IN UINTN, OUT CHAR8 *, IN UINTN, OUT UINTN *)
AsciiStrLen(IN const CHAR8 *)
AsciiStrSize(IN const CHAR8 *)
AsciiStrCmp(IN const CHAR8 *, IN const CHAR8 *)
AsciiStriCmp(IN const CHAR8 *, IN const CHAR8 *) // 没有对应的 StriCmp // 见下文的 用力过猛
AsciiStrnCmp(IN const CHAR8 *, IN const CHAR8 *, IN UINTN)
AsciiStrStr(IN const CHAR8 *, IN const CHAR8 *)
AsciiStrDecimalToUintn(IN const CHAR8 *)
AsciiStrDecimalToUint64(IN const CHAR8 *)
AsciiStrHexToUintn(IN const CHAR8 *)
AsciiStrHexToUint64(IN const CHAR8 *)
AsciiStrToIpv6Address(IN const CHAR8 *, OUT CHAR8 **EndPointer, OUT IPv6_ADDRESS *, OUT UINT8 *PrefixLength)
AsciiStrToIpv4Address(IN const CHAR8 *, OUT CHAR8 **EndPointer, OUT IPv4_ADDRESS *, OUT UINT8 *PrefixLength)
AsciiStrToGuid(IN const CHAR8 *, OUT GUID *)
AsciiStrHexToBytes(IN const CHAR8 *, IN UINTN, OUT UINT8 *, IN UINTN)
AsciiStrToUnicodeStrS(IN const CHAR8 *, OUT CHAR16 *, IN UINTN)
AsciiStrnToUnicodeStrS(IN const CHAR8 *, IN UINTN, OUT CHAR16 *, IN UINTN, OUT UINTN *)
CharToUpper(IN CHAR16) // 只处理 ASCII 范围内的 a-z 若不是则 原样返回
AsciiCharToUpper(IN CHAR8)
Base64Encode(IN const UINT8 *, IN UINTN, OUT CHAR8 *Destination, IN OUT UINTN *)
Base64Decode(IN const CHAR8 *Source, IN UINTN, OUT UINT8 *Destination, IN OUT UINTN *)
DecimalToBcd8(IN UINT8)
BcdToDecimal8(IN UINT8)
==
(字符串 操作,用力过猛)
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Include/Library/SortLib.h
// https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/BaseSortLib/BaseSortLib.c
// https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/UefiSortLib/UefiSortLib.c
//
// 真心用力过猛了,
// BaseSortLib 没有实现,用了就 ASSERT (FALSE)
// UefiSortLib 居然依赖 gEfiUnicodeCollation2ProtocolGuid 协议,DXE 环境里 **必须有** 一个实现
//
// 三观 必须得 飘起来?干嘛?我就处理英文啊,就处理英文啊,不能再朴素了?不能再有自我了?
//
StringNoCaseCompare(IN const VOID *, IN const VOID *)
StringCompare(IN const VOID *, IN const VOID *)
==
(字符串 操作,自己得上)
StriCmp(IN const CHAR16 *, IN const CHAR16 *) // 这个真没有,自己得上
StrnCmpIgnoreCase(IN const CHAR16 *, IN const CHAR16 *, IN UINTN) // 过于先进,没有提供,自己得上
// 没有这么细腻的类型,自己得上
StrDecimalToUint32S
StrDecimalToUint16S
StrDecimalToUint8S
//
StrHexToUint32S
StrHexToUint16S
StrHexToUint8S
// 这么离谱的 Smart 操作,自己得上
StrHexOrDecimalToUintnS
==
使用 隐藏技能:
- Copy function declaration to your C file, just build it.
- The linker will actually resolve the symbol - found the func impl from lib.
Why?
The function declaration is for compiling.
The linker will find the impl of the func.
The LibraryClass of EDK2 building system is actually based-on C library.
Why++ ?
(1) If a function is not exposed by public header file of the LibraryClass, we can use it by declaring it by ourselves.
(2) To prevent such dirty use for LibraryClass, specify STATIC on your function impl.
函数清单获取方式:安装 VSCode list-symbols 插件, 截取头文件,手工清理.
see also
浙公网安备 33010602011771号