pretty code

顯示具有 UEFI 標籤的文章。 顯示所有文章
顯示具有 UEFI 標籤的文章。 顯示所有文章

2022年6月2日 星期四

阿伯出事了XD

好久沒有關於 UEFI 的文章了!雖然我幫自己及同事寫了很多 UEFI Sell 下的小程式,但大部份都是基於 pure C 的程式,很少有用到 UEFI 相關的東西,最多用到一些 SetVariable 以及呼叫兩個 Services 的 API 居多。

今天為了檢查前天進辦公室跑的測試問題,很早就進來辦公室,居然發現是因為網路的問題而不是我原本想的系統問題?然而最近幾次進辦公室都有用 Wireshark 來檢查 ARP 廣播封包的數量好確保 UEFI 網路可以正常工作,我們公司的人確實大部份都是 WFH,故不像以前有七八十趴的 ARP 廣播封包,況且出問題時是凌晨四點多鐘,我是不相信那時候公司有什麼人啦!但你知、我知、獨眼龍也知 UEFI network stack 本來就只是堪用,實在不建議拿來用在專案上,無奈我只是個細漢,只好一直面對這種跟程式無關的 issue。

說也湊巧,前一天也是為了解決跟我自己程式無關的 issue(UB,嚴格來說是未定義行為),更新了一些 code 來做防衛駕駛,既然前一個測試被中斷了,想說還是先花一點時間來跑測試驗證這個問題好結案,不然真的很阿雜。沒想到中途又一直遇到網路問題,此時又拿出了 Wireshark 來檢查 ARP 廣播封包,發現一樣剩不到 15%,故應該不是廣播封包問題?

在我使出了任何絕招都無效後,正準備發個郵件問一下 IT 公司網路最近是否有異動?但據我之前經驗,除了廣播封包這個問題外,我們 IT 在收到申請網路特殊用途後,其實不太會去改變申請的網路設定,無奈一時又沒有其他想法!

就在這個 moment,我突然想到好幾年前,我們開始一個新專案後,我本來用的 UDK base 似乎也要跟著更新,當初我最後用的是 UDK2018,不然網路就是會有奇怪的問題,此時 Wireshark 看到的現象就是掉封包或是 network stack 有很多奇怪的行為。剛好今年 WFH 時,我並沒有特別準備 UDK2018 來 build 我的網路程式,而是延用另一個 tool 內建的 UDK base,雖然我無法看出是那個版本,但我直覺應該是 UDK2014 左右的 base。

知道問題後並不是就沒事了,我以後要怎樣避免這個 UDK base 的問題?雖然說寫文件可以解決這個問題,但我們公司的專案並沒有很系統的規劃,導致文件散落一地,我想就算我寫了文件,其他人也不見得遇到網路問題就會想到是這個原因而來我寫文件的資料夾找答案(這就是沒有系統規劃專案的壞處,沒有固定的地方來放文件,所以你也不會有事就去找文件)?

本想 Google 看看是否有 UDK base 的相關 define 來做編譯檢查,無奈這是個很冷門的問題,我用英文在網路上找不到答案。

我又試著用 file search 關鍵字的技巧,看能不能硬在 UDK base 裡找到 define,無奈用了 UDK2018VERSION 等關鍵字都沒有幫助,因為這個搜尋的結果太多,我不太可能一個一個 review,只能粗略的看一眼但仍然沒有答案。

最後我決定在 edksetup.bat 做手腳,我在檔案開頭加了以下指令:

echo #define UDK2018 > Stdlib\Include\ABC.h

然後我在我程式的 main.c 加了一個 #include <ABC.h> 並寫清楚註解以提醒後人。

因為我只在我公司電腦的 UDK2018 base 加了這個 trick,故當我這個專案被別人用在別的 UDK base 就一定會遇到 compile error,此時他看我的註解就知道問題是什麼,這樣就達到提醒的作用了,因為那個別人也包括未來的我XD

就在寫此文章的同時,又突然想到用 2.7(UEFI_Spec_2_7,目前最新是 2.8)來搜尋,果然在 MdePkg\Include\Uefi\UefiSpec.h 找到一些 defines,故我們可以用 EFI_SPECIFICATION_VERSION 是否等於 0x20046 或是 EFI_2_70_SYSTEM_TABLE_REVISION 這個 define 是否存在,並配合 #error 這個前置處理器語法來觸發 compiler error。

2021年12月14日 星期二

UEFI Network 相關 code 位置

最近在研究一個 Bug,實在很難找到實作的地方,今天中午正準備吃飯時,先找到了 error code 的定義,後來憑著 error code 也順利的找到了 TCP Recev 的位置,記錄一下,避免以後忘記。

我要找的值其實就是 104
#define EFI_CONNECTION_FIN      EFIERR (104)
#define EFI_CONNECTION_RESET    EFIERR (105)
#define EFI_CONNECTION_REFUSED  EFIERR (106)

StdLib\LibC\Uefi\select.c - select function
StdLib\LibC\Uefi\SysCalls.c - poll function -> StdLib\BsdSocketLib\poll.c - BslSocketPoll function

DEBUG Status
Status 都用 %r 印出較多,如果 index 有定義在 mStatusString 這個 Array,便會印出定義好的字串;如果沒有,其值就是一個 16 進位值。

Stdlib\EfiSocketLib\Socket.c
EslSocketRxStart 這支函數,在檢查 pPort->pSocket->RxError 如果發現有 error,其值可能是來自下面其中之一:

MdeModulePkg\Universal\Network\Tcp4Dxe\Tcp4Main.c - Tcp4Receive
MdeModulePkg\Universal\Network\Tcp4Dxe\SockInterface.c - SockRcv

順序是先 Tcp4Receive,裡面會再呼叫 SockRcv。

我先用 build -p MdeModulePkg\MdeModulePkg.dsc -a X64 -t VS2015x86,build 出我自己的 Tcp4Dxe.efi,過程中會出錯,那是因為我是整個 Module 全 build,故跑到最後可能因為設定問題會失敗,但不影響我要的檔案產出。

後來查了一下,也可以只 build Tcp4Dex.efi。
build -p MdeModulePkg\MdeModulePkg.dsc -m MdeModulePkg\Universal\Network\Tcp4Dxe\Tcp4Dxe.inf -a X64 -t VS2015x86

接著使用 drivers 找到系統內建的 Tcp4 Driver,查到第一個欄位 DRV 的值,接著使用 unload XX 去卸載它。

最後則是用 load Tcp4Dxe.efi 的方式載入我 build 出來的檔案。

這樣我就可以看到相關訊息,至少我找到了 EFI_CONNECTION_FIN  這個錯誤發生的地方了XD

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。

https://github.com/tylpk1216/ShellOpt

2020年5月21日 星期四

The Delay in UEFI Shell Entry

I did a lot of tests in past two weeks. Due to our tool issue, we must reboot system after testing one bit. The system took 24 seconds to reboot, but our one bit test just took 55 ~ 56 seconds. This is a heavy wasting for completing a test. I cannot do any improvements for this issue because I am not the owner of this function. However, I can do another way to decrease the boot time.

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: 編譯器發生內部錯誤

今天跟了某同事的 commit 後,編譯 UEFI Application 時,會發生如下的錯誤。
看了一下同事的 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 編譯時期前置作業概略

我們在 build UEFI Application 時,第一個步驟便是會呼叫 edksetup.bat,以下是針對我感興趣的部份作記錄。

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 預設視警告為錯誤

一般來說,撰寫 UEFI Application 時,如果編譯過程中遇到警告,會出現 C2220 錯誤訊息。

這個是因為在 UDK\Conf\tools_def.txt 檔案中,在所屬的 VC 版本編譯選項中預設有 /WX 選項,故會將警告視為錯誤。

下面以我最常用的 VC 版本為例:
DEBUG_VS2012x86_X64_CC_FLAGS  = /nologo /c /WX /GS- /X /W4 /Gs32768 /D UNICODE /O1ib2s /GL /Gy /FIAutoGen.h /EHs-c- /GR- /GF /Zi /Gm

如果我們想忽略某警告,最簡單的方式便是在 UEFI Application 的 INF 中加上 /wd9999 選項,9999 便是警告編號。

還有一種方式是去動態改變 warning 層級,假設 9999level 1 的 warning,而我們現在是使用 level 2 check,只要指定 /w39999 後,9999 便會變成 level 3 warning,故不會產生 C9999 警告訊息,在某些情境下還蠻好用的。

另外,LINKER 也有 /WX 選項,這個倒是可以用 /WX:NO 覆寫。

2019年11月28日 星期四

Free Software EFI file system drivers

之前就看過有人提到此類 driver 的存在。最近在查資料時,又找了一下,記錄下來以備不時之需。

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 試用

Lua 是一個輕巧的 script language,可以輕易的與 C 或其他語言結合。

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

一般我們在寫 UEFI Application 時,有 3 種方式可以寫,不同方式有不同的 Entry 及套用的 Library,一般來說越下層的 Entry 其檔案 size 也越大,注意這裡指的下層是指呼叫的層級順序,非指底層。

不同的 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

最近常常操作 unsigned long long 的 2 維陣列,故突發奇想,是否可以使用 memset 來 init 1 個大於 1byte 且非 0 的值呢?

上面是 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 釋疑

原本以為 LShiftU64 是我們自己寫的函數,後來才發現是 Library\BaseLib 裡面的函數,看了一下說明,跟直接使用 Standard C 操作似乎並沒兩樣?

我唯一能想到的合理解釋就是為了方便 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 編譯警告取消方式

UEFI 下存取記憶體時,有時會有 C4305 和 C4306 的編譯警告,但是那個 address 就是我們要的,暫時解法可以使用 marco 來取消此警告。

#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

趁著手上的事暫時告一段落的空檔,把幾天前 Google 到的資料整理一下,免得之後還要再查一次。

RU Tool 有一個可以看 BIOS E820 Table 的功能,E820 名稱是從指令 INT15h, AX=E820h 來的,因為我們對 AX 設值 E820,故命名為 E820 Table。Legacy BIOS 會把這個 Table 建好,OS 就知道那些 RAM 是可以用的。

來到了 UEFI 的時代,取而代之的是 UEFI Memory Map,我們可以透過 BootServiceGetMemoryMap 函數來取得相關資訊,雖然我沒試過,但應該會等於 Shell memmap 指令的結果。

另外,從網路上查到的資料,Linux 似乎傾向使用 E820 Table,故會將 UEFI Memory Map 的資料轉換後填寫到原 E820 Structure。

有興趣的可以看此篇文章


2020/06/03 更新

最近在看 dmesg log 時,無意間看到 E820 資訊,拍張照片記錄一下。

2019年7月10日 星期三

UEFI IndexVar in for loop

英文果然不是我的菜,一直卡在 IndexVar 怎麼命名都不對的情況!

查了一下 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 Shell 下跑來跑去,各個 Shell 的常用語法都不同,乾脆記錄一下,才不用每次都 google。

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

在搞懂了相關結構後,剩下的就是 coding 了,在 UDK Base 翻找後,便寫出了下面的程式,可以讀取所有的 Boot Variables,Just for fun。


#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 相關函數實作

寫 UEFI Application 時,如果是跟 UEFI 有關的東西,常常需要去 UDKBase 裡翻找,我一般都是使用 "FileSeek v3.3" 這個工具,可以幫助我很快的找到關鍵字。可惜的是,即使發現在某支 .c 檔,如果想要在該檔案中查找相關函數,我目前用的 UltraEdit 並沒有辦法在函式清單中列出來所有函數,這個是跟 UEFI 慣用的函式實作寫法有關,目前至少發現有 2 種寫法,故我後來都是使用 grep 來找出函式名稱。

 Rgex 下法
1. "^\w+\s*\("

兩者的差別在函數名稱後面到左括弧間有無空格,之後如果有新發現再來補充。