pretty code
2022年6月2日 星期四
阿伯出事了XD
2021年12月14日 星期二
UEFI Network 相關 code 位置
2020年11月27日 星期五
UefiMain Entry 的應用
忘記在哪裡看過,只要有 UDK Base,其實一個簡單的 Hello World 小程式也可以成為一個簡單的 Shell,我曾經試過 main Entry 的 Hello World,但只會出現 Disk Error 的錯誤訊息。
昨天看了 LAB-Z 的文章,裡面有提到一個日本人寫的書,我才恍然大悟為什麼之前無法成功,差別在我們需要使用 UefiMain Entry,而不是 StdLib 的 main Entry。
雖然曾稍微看過 Shell.c 的 code,也知道它是使用 UefiMain Entry,但我一直以為寫 Shell 有點像是寫 Windows Service 或者像在寫一支 Driver,有對應的介面需實作,這樣 OS 才能跟你寫的程式溝通,搞了半天,原來一切都是我想太多!
只要下面簡單幾行,並把編譯完的檔案改名成 BootX64.efi,放在隨身碟 EFI\Boot 下,開機時就會執行你的小程式。
#include <Library/UefiBootServicesTableLib.h>
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
// Clear the screen
EFI_STATUS Status;
Status = gST->ConOut->ClearScreen(gST->ConOut);
if (EFI_ERROR(Status)) {
return (Status);
}
// turn off the watchdog timer
gBS->SetWatchdogTimer (0, 0, 0, NULL);
gST->ConOut->OutputString(gST->ConOut, L"Enter UefiMain \r\n");
while (1) {
}
return EFI_SUCCESS;
}
2020年10月26日 星期一
UEFI ShellOpt 概述
最近看 Spec 看到有點乏味,突然想到之前一直想試沒試的 ShellOpt,於是便來嘗試看看。
ShellOpt 是一個特殊的 UEFI Env Variable,照 Spec 所說,設了這個環境變數後,像是 Shell 的等待使用者按鍵時間,要不要印出版本資訊等,都可以透過這個環境變數來控制,不用像我之前一樣,需要修改 Shell.c 的預設值,編譯出自己的 Shell 執行檔。
嘗試的過程中遇到了一些問題,雖然還未完全解決,還是記錄一下遇到的問題及解決方式。
01. -delay 秒數的傳遞
-delay 是控制使用者按鍵時間的參數,當 set 指令遇到 - 符號時,會誤判為 set 的使用參數,解決方式便是把它跳脫,正常是使用 ^ 符號來跳脫特殊字元,但這裡要用 set ShellOpt "^^-delay 8" 來設定才能成功。
02. Shell 解析 Argv 不成功,如何 Debug
因為 UDK 的 code 有問題,導致無法使用使用者的設定值。為了判斷問題的地方,需要印出一些訊息,我這裡是使用下面方式來除錯。
CHAR16 DebugString[128];
UnicodeSPrint(DebugString, sizeof(DebugString), L"%02d - (%s) \r\n", LoopVar, CurrentArg);
gST->ConOut->OutputString(gST->ConOut, DebugString);
測試的結果,問題是出在最後一個 gEfiShellParametersProtocol->Argv,不知為什麼會多出一個奇怪的字串,嘗試列印來看,是一個重覆的無意義字串?也許是我還沒看懂 Shell 該段 Code 的意義,仍需要繼續嘗試。
我目前暫時的解決方式是先忽略最後一個有問題的 Argv,且傳遞給 -delay 的秒數一定要有 2 位數,不足便在前面補 0,這樣便可以解決 ShellOpt 的解析問題。
最終解決方式
下午繼續奮戰這個問題,最後確定奇怪字串是因為 set command 來的,應該是過程中底層不知道那個環節出錯?目前看來只能自己寫程式來設定此變數。
一開始試著使用 ShellSetEnvironmentVariable 來設定 ShellOpt,問題還是依舊,直到使用 gRT->SetVariable 函數,ShellOpt 的值才能順利設定,不過,當下無法反映在 set command 的顯示結果,另外 GUID 需使用 gShellVariableGuid,而不是 gEfiGlobalVariableGuid。
2020/10/27 更新
今天本來想寫支程式,抓取使用者的 argv,檢查後再幫忙設定 ShellOpt 變數。由於個人習慣使用 main Entry,故所有的字串都是使用 ASCII,本想最後再使用 UnicodeSPrintAsciiFormat 函數來轉換,無意間才發現,UEFI 在這些類似的函數中,需要使用 "%a" 來表示 ASCII 字串,而不是傳統 C 語言的 "%s",這讓我不禁思考,使用 main Entry 撰寫 UEFI Application 到底是好還是不好呢?
相關 Format 選項可以參考 MdePkg\Library\BasePrintLib\PrintLibInternal.c - BasePrintLibSPrintMarker。
2020年5月21日 星期四
The Delay in UEFI Shell Entry
In original UEFI Shell, it waits 5 seconds to prevent from user wants to stop launching the "startup.nsh". If we can decrease this time, it can also reduce our test time.
The UEFI Shell Entry is in "Root\ShellPkg\Application\Shell\Shell.c". We can search the key word "UefiMain" and we can find the setting is in this "ProcessCommandLine" function. The setting is stored in the "ShellInfoObject.ShellInitSettings.Delay" variable.
After modifying this variable, we can just wait 1 second, this can decrease 4 seconds in our one bit test time. It is not bad. Just for fun.
2020/05/21 Update
I am a stupid man. We can use "argv" to set up this delay time. This information is described in "3.2 Invocation" of "UEFI_Shell Specification". We can pass "-delay n" to UEFI Shell.
I thought one question when I ate lunch. How do I pass "-delay n" in boot time? I think this is a good question. I don't know right now !
2020年3月12日 星期四
fatal error C1001: 編譯器發生內部錯誤
看了一下同事的 code,左看右看都看不出個所以然,連那個 f: 的路徑都不知從哪來的?
後來實在沒辦法,把 inf 裡的最佳化從 "/Oi-" 改成 "/Od" 便暫時解決了此問題。
一整個下午的心情都被搞成很阿雜!
2020/03/13 更新
那個提示訊息是有意義的,是同事的 code 出問題無誤,非那個奇怪的 f: 路徑。
原本有加上 /FAcs 想看錯誤行數的 assembly code,是否能看出最佳化後的組語長怎樣?不過因為是到那邊出錯,故整個函數的 assembly code 都未產出到檔案。
後來再仔細研究了同事 commit 的函數,居然是錯在函數的參數被拿來計算,導致最佳化出錯,真是個奇特的經驗。
unsigned int XXXX(void* buffer, unsigned int len, unsigned int ans)
{
unsigned char* buf = (unsigned int*)buffer;
unsigned int i;
for (i = 0; i < len; ++i) {
if (i == 8) ans = 0;
ans += (unsigned int)(*buf++);
}
return ans;
}
2019年12月24日 星期二
EDK2 編譯時期前置作業概略
1. Call edksetup.bat, it will call BaseTools\toolsetup.bat.
2. In BaseTools\toolsetup.bat, if it can't find Conf\target.txt and Conf\tools_def.txt, it will copy BaseTools\Conf\target.template and BaseTools\Conf\tools_def.template to Conf folder to generate Conf\target.txt and Conf\tools_def.txt.
3. When you call build.exe(BaseTools\Source\Python\build\build.py), it will pass these two files in LoadConfiguration function.
2019年12月23日 星期一
EDK2 預設視警告為錯誤
還有一種方式是去動態改變 warning 層級,假設 9999 是 level 1 的 warning,而我們現在是使用 level 2 check,只要指定 /w39999 後,9999 便會變成 level 3 warning,故不會產生 C9999 警告訊息,在某些情境下還蠻好用的。
2019年11月28日 星期四
Free Software EFI file system drivers
https://efi.akeo.ie/
上述是一個 open source 專案,裡面有很多 file system 的 EFI driver。以讀取 NTFS partition 來說,這個專案比較方便,直接使用 load 方式載入即可。不像另外一個 rufus 專案,還需要動到 dd command 才行。
唯一要注意的是 NTFS 只能 read,不支援 write。想想也很合理,畢竟微軟並沒有提供正式的 Spec,大部份的解決方式都是靠 open source 社群摸索出來的。
2019年9月9日 星期一
UEFI application - Lua 試用
UDK base 中已經含有 porting 好的 Lua project,其版本為 5.2.3。
不像 Python,Lua 在 UDK 編譯中完全沒有問題。
使用 Lua,其檔案需為 ASCII 編碼。
目前實測僅有一個地方覺得怪怪的,那就是 os.execute( ) 這個函數,感覺每次都會再帶一個新的 UEFI Shell 起來,如果記憶體回收沒做好,也許會有問題?
2019/09/09 更新
寫了一個簡單的 script 來測試 memory 使用情形,看得出來執行後,可用記憶體減少了約 1MB,好像也還好?
print('start')
os.execute('memmap > before.txt');
for a = 1, 100, 1 do
print(a)
os.execute('Counter.efi')
os.execute('stall 300000')
end
os.execute('memmap > after.txt');
print('end')
2019年7月31日 星期三
UEFI Application - EntryPoint
不同的 Entry,其預設的字串編碼也不同,UEFI 預設是使用 Unicode ﹝UINT16﹞,而標準 C 則是使用 ASCII﹝char﹞。
不過,為了 porting 的方便,我大部份都使用 main 型式,反正還是可以呼叫 UEFI 相關函數。
| C code Entry | UefiMain | ShellAppMain | main |
| parameter | EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable |
UINTN Argc, CHAR16 **Argv |
int argc, char *argv[] |
| include file | #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiApplicationEntryPoint.h> |
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> |
|
| [Defines] (INF) ENTRY_POINT |
UefiMain | ShellCEntryLib | ShellCEntryLib |
| [Packages] (INF) | MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec |
MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec |
MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec StdLib/StdLib.dec |
| [LibraryClasses] (INF) | UefiApplicationEntryPoint UefiLib |
ShellCEntryLib UefiLib |
ShellCEntryLib UefiLib LibC |
為了避免不必要的誤解,我都習慣不將不需要的東西放入專案,這樣一來,Code 看起來才會賞心悅目。
2019年7月22日 星期一
memset in UEFI
上面是 C11 規格書中提到的 memset 定義,我們可以看到第 2 個參數雖然接受的是 1 個 int,但最後會轉成 1 個 unsigned char,故看起來應該是不行。
至於 UEFI 中的 memset 實作是在 Stdlib\LibC\String\Misc.c 中,細節如下,看起來應該是跟 Stanard C 一樣,會被轉成 unsigned char。
#if !((defined(MDE_CPU_ARM) || defined(MDE_CPU_AARCH64)) && defined(__GNUC__))
/** The memset function copies the value of c (converted to an unsigned char)
into each of the first n characters of the object pointed to by s.
@return The memset function returns the value of s.
**/
void *
memset(void *s, int c, size_t n)
{
return SetMem( s, (UINTN)n, (UINT8)c);
}
#endif
不過,UEFI 另外有 InternalMemSetMem64 的函數,其實作位於 MdePkg\Library\BaseMemoryLib\MemLibGeneric.c 中,看來我們可以使用這個函數。
/**
Fills a target buffer with a 64-bit value, and returns the target buffer.
@param Buffer The pointer to the target buffer to fill.
@param Length The count of 64-bit value to fill.
@param Value The value with which to fill Length bytes of Buffer.
@return Buffer
**/
VOID *
EFIAPI
InternalMemSetMem64 (
OUT VOID *Buffer,
IN UINTN Length,
IN UINT64 Value
)
{
for (; Length != 0; Length--) {
((UINT64*)Buffer)[Length - 1] = Value;
}
return Buffer;
}
2019年7月19日 星期五
LShiftU64 釋疑
我唯一能想到的合理解釋就是為了方便 porting,例如為了 32bit 的執行檔,只是不知道這樣是否真有意義?反正我對這行業的 Code 早已經見怪不怪了XD
#include <stdio.h>
#include <Library/BaseLib.h>
int main(void)
{
unsigned long long a = 0x0000000000000001;
UINT64 b = LShiftU64(a, 2);
UINT64 c = a << 2;
printf("UEFI 0x%016llX, 0x%016llX \n", a, b);
printf("StdC 0x%016llX, 0x%016llX \n", a, c);
return 0;
}
執行結果
UEFI 0x0000000000000001, 0x0000000000000004
StdC 0x0000000000000001, 0x0000000000000004
後記
似乎真的是跟 porting 有關,在網路上找到 2 篇相關文章。
[edk2] [Patch] MdeModulePkg: use LShiftU64() instead of "<<" to avoid IA32 build error.
Fix ScsiLib build break by << operator, which is replaced by LShiftU64 of BaseLib.
2019年7月12日 星期五
VC 編譯警告取消方式
#pragma warning(push)
#pragma warning(disable:4305)
#pragma warning(disable:4306)
//your code.
#pragma warning(pop)
另外一種方式是使用 command line 參數
/wd4305 /wd4306
UEFI Boot Variable 取值注意事項
一般來說,我們在取 Boot Variable 時,會先取得 BootOrder 這個變數的內容,因為裡面的 1 個開機項目是 2 Byte,故我們會把取到的 BootOrder size 除以 2 好方便我們取得所有項目個數,之後就可以用 for 迴圈依序取得 Boot Variable。
好死不死的,我們系統的的 BootOrder 是長底下這樣,故我先取 Boot0001 當然會取不到,害我一直往 EDK1/EDK2 的方向去找問題。另外,變數名稱前面不需要加上 "Efi:"。
sample code 如下:
orderValue = mGetVariable(L"BootOrder", &gEfiGlobalVariableGuid, &size);
if (orderValue == NULL) {
printf("Can't get BootOrder Variable \n");
return ERROR;
}
varCount = size / 2;
for (i = 0; i < varCount; i++) {
ptr = (char*)orderValue;
num = (UINT16*)(ptr + i * 2);
UnicodeSPrint(bootString, sizeof(bootString), L"Boot%04x", *num);
value = mGetVariable(bootString, &gEfiGlobalVariableGuid, &size);
if (value == NULL) {
printf("Can't get Boot%04X Variable \n", i);
return ERROR;
}
// 底下省略
}
UEFI Memory Map and BIOS E820 Table
RU Tool 有一個可以看 BIOS E820 Table 的功能,E820 名稱是從指令 INT15h, AX=E820h 來的,因為我們對 AX 設值 E820,故命名為 E820 Table。Legacy BIOS 會把這個 Table 建好,OS 就知道那些 RAM 是可以用的。
來到了 UEFI 的時代,取而代之的是 UEFI Memory Map,我們可以透過 BootService 的 GetMemoryMap 函數來取得相關資訊,雖然我沒試過,但應該會等於 Shell memmap 指令的結果。
另外,從網路上查到的資料,Linux 似乎傾向使用 E820 Table,故會將 UEFI Memory Map 的資料轉換後填寫到原 E820 Structure。
有興趣的可以看此篇文章。
2020/06/03 更新
最近在看 dmesg log 時,無意間看到 E820 資訊,拍張照片記錄一下。
2019年7月10日 星期三
UEFI IndexVar in for loop
查了一下 Code,發現 Code 只允許一個字元!
靜一下心,再把文字說明看一下,原來 IndexVar 只允許一個字元,真不知道是那個人想出來的,都不能使用有意義的變數名稱。
If after expansion no such files are found, the literal string itself is kept. Indexvar is any alphabet character from ‘a’ to ‘z’ or ‘A’ to ‘Z’, and they are case sensitive. It should not be a digit (0-9) because %digit will be interpreted as a positional argument on the command line that launches the script. The namespace for index variables is separate from that for environment variables, so if indexvar has the same name as an existing environment variable, the environment variable will remain unchanged by the for loop.
2019年7月1日 星期一
不同 OS Shell 常用語法
| Windows | Linux | UEFI | |
| Declare Variable | set ABC=XXX | ABC=XXX | set ABC XXX |
| Use Variable | echo %ABC% | echo $ABC | echo %ABC% |
| Error Indicator | echo %ERRORLEVEL% | echo $? | echo %LASTERROR% |
| The variable scope in other script | global | must use EXPORT | ?? |
2019年6月13日 星期四
UEFI Application - Get Boot Variables
#include <stdio.h>
#include <Library/UefiLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Library/BaseLib.h>
#include <Library/DevicePathLib.h>
// The caller is responsible for freeing this buffer with FreePool().
VOID* mGetVariable(CHAR16 *name, EFI_GUID *guid, UINTN *size)
{
EFI_STATUS status;
UINTN realSize;
VOID *value;
// Try to get the variable size.
value = NULL;
realSize = 0;
status = gRT->GetVariable(name, guid, NULL, &realSize, value);
if (status != EFI_BUFFER_TOO_SMALL) {
return NULL;
}
// Allocate buffer to get the variable.
value = AllocatePool(realSize);
if (value == NULL) {
return NULL;
}
// Get the variable data.
status = gRT->GetVariable(name, guid, NULL, &realSize, value);
if (EFI_ERROR(status)) {
FreePool(value);
return NULL;
}
*size = realSize;
return value;
}
int main(void)
{
VOID *value;
UINTN size;
UINTN varCount;
UINTN i;
UINT16 bootString[10];
UINT16 *desc;
UINT16 filePathLen;
EFI_DEVICE_PATH_PROTOCOL *protocol;
char *ptr;
value = mGetVariable(L"BootOrder", &gEfiGlobalVariableGuid, &size);
if (value == NULL) {
printf("Can't get BootOrder Variable \n");
return -1;
}
FreePool(value);
varCount = size / 2;
for (i = 0; i < varCount; i++) {
UnicodeSPrint(bootString, sizeof(bootString), L"Boot%04x", i);
value = mGetVariable(bootString, &gEfiGlobalVariableGuid, &size);
if (value == NULL) {
printf("Can't get Boot%04X Variable \n", i);
return -1;
}
ptr = (char*)value;
desc = (UINT16*)(ptr + 6);
Print(L"Boot%04X : %s \n", i, desc);
filePathLen = *((UINT16*)(ptr + 4));
protocol = (EFI_DEVICE_PATH_PROTOCOL*)(ptr + 6 + 2*(StrLen(desc)+1));
Print(L"FilePath Length %02X \n", filePathLen);
Print(L"FilePath.Type %02X \n", protocol->Type);
Print(L"FilePath.SubType %02X \n", protocol->SubType);
Print(L"%s \n", ConvertDevicePathToText(protocol, TRUE, TRUE));
Print(L"\n");
FreePool(value);
}
return 0;
}
Boot0000 : USB: ADATA USB Flash Drive
FilePath Length 27
FilePath.Type 05
FilePath.SubType 01
BBS(HD,USB: ADATA USB Flash Drive)
Boot0001 : UEFI USB: ADATA USB Flash Drive
FilePath Length 1C
FilePath.Type 02
FilePath.SubType 01
PciRoot(0x0)/Pci(0x10,0x7)/USB(0x3,0x0)
Boot0002 : Internal EDK Shell
FilePath Length 2C
FilePath.Type 04
FilePath.SubType 07
Fv(92E111AA-5F63-49D5-96C7-947422BDD1AA)/FvFile(C57AD6B7-0515-40A8-9D21-551652854E37)
2019年6月10日 星期一
UEFI Application - 找出 UEFI 相關函數實作
Rgex 下法
1. "^\w+\s*\("
兩者的差別在函數名稱後面到左括弧間有無空格,之後如果有新發現再來補充。









