2016年3月22日 星期二

使用 c++ 來開發 uefi/edk2 程式 (0)

進學致和,行方思遠。
20220622 補充:
在「write uefi program without edk2」之後, 我已經不用 edk2 來開發 uefi, 當然也不用在處理哪些可怕的 inf, dsc 檔案。

使用 c++ 也不需要像這篇這麼花功夫, 之後不會用本篇的方法來使用 c++, 這篇就留個紀錄。

我也知道為什麼 global object ctor 沒有被正常喚起, 因為沒做任何事情, 本來就不會被正常喚起, 手動呼叫 GLOBAL_XX function 算是正常用法。

以下原文:

edk2 並沒有支援用 c++ 來開發 uefi 程式, 我真是不敢相信還有這麼原始的開發環境, 竟然不支援 c++。我可是 c++ 愛好者, 不能用自己喜歡的語言開發程式, 感覺很不爽。

c++ 的好處我說過好幾次了 (疑! 我根本沒說過!!), c 我也是可以寫的, 為什麼堅持用 c++ 呢? 還真的說不上原因, 大概是網路上很多的討論都對 c++ 很不好, 什麼 XX 比不上 C, YY 比不上 JAVA, 這些言論更讓我對 c++ 抱不平, 決定要多多推廣 c++, 我又不善嘴炮言詞, 只好用程式碼來證明 c++ 的能耐。

UEFI原理与编程》第十章說明如何用 c++ (g++) 來開發 uefi 程式並提供了 GcppPkg 這個範例, 不過很可惜, 這部份書上寫的不夠詳細, 我花費了不少時間還是搞不定怎麼使用這個範例。

最後我做了和 GcppPkg 類似的事情, 再加上《UEFI原理与编程》第十章的說明, 搞出了自己的 c++ 作法。所以我雖然沒搞定書上的 GcppPkg 範例, 但是書中內容還是幫了我不少。

最困難的是編譯環境, 花了不少時間我才搞定 (沒有搞懂, 是搞定), 找了 edk2/AppPkg/Applications/Main/Main.c 來修改, 這是使用 c 語言 main 的開發方式。

我建立了 edk2/AppPkg/Applications/Main/m.cpp (從 edk2/AppPkg/Applications/Main/Main.c 複製而來), 也複製了一份 m.inf, m.inf 最重要的是 L27 ~ 42, 把 source code 的檔案填進去, build 就會去編譯這些檔案。

m.inf
 1 ## @file
 2 #   A simple, basic, application showing how the Hello application could be
 3 #   built using the "Standard C Libraries" from StdLib.
 4 #
 5 #  Copyright (c) 2010 - 2014, Intel Corporation. All rights reserved.<BR>
 6 #  This program and the accompanying materials
 7 #  are licensed and made available under the terms and conditions of the BSD License
 8 #  which accompanies this distribution. The full text of the license may be found at
 9 #  http://opensource.org/licenses/bsd-license.
10 #
11 #  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 #  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13 ##
14 
15 [Defines]
16   INF_VERSION                    = 0x00010006
17   BASE_NAME                      = m
18   FILE_GUID                      = 5ea97c46-7491-4dfd-b442-747010f3ce5f
19   MODULE_TYPE                    = UEFI_APPLICATION
20   VERSION_STRING                 = 0.1
21   ENTRY_POINT                    = ShellCEntryLib
22 
23 #
24 #  VALID_ARCHITECTURES           = IA32 X64
25 #
26 
27 [Sources]
28   m.cpp
29   bst.cpp    
30   #crtbegin.cpp  
31   eh.cpp      
32   mylist.cpp  
33   mymap.cpp     
34   myvec.cpp
35   cstring.cpp   
36   gdeque.cpp  
37   k_stdio.cpp  
38   mem.cpp  
39   myiostream.cpp  
40   mystring.cpp  
41   #my_setjmp.c
42   my_setjmp.S
43 
46 [Packages]
47   StdLib/StdLib.dec
48   MdePkg/MdePkg.dec
49   ShellPkg/ShellPkg.dec
50 
51 [LibraryClasses]
52   LibC
53   LibStdio
54 
55 [BuildOptions]
56    #GCC:*_*_X64_CC_FLAGS = -fno-exceptions -fno-rtti -ffreestanding -nostdlib -nodefaultlibs -std=c++11
57    MSFT:*_*_*_CC_FLAGS =
58 


再來是把這個 m.inf 加到 AppPkg.dsc。

AppPkg.dsc.diff
 1  [LibraryClasses]
 3    #
 4    # Entry Point Libraries
 5    #
 6 @@ -105,6 +106,7 @@
 7  ########################################################################
 8  
 9  [Components]
10  AppPkg/Applications/Main/m.inf         


以下是測試的 c++ 檔案。cout 和 vector 不是 edk2 提供的, 是移植我那個玩具標準 c++ 程式庫得來的, namespace 是 DS 而不是 std, 書上的範例是移植 stl 過來, 我既然已經有自己的版本, 玩具歸玩具, 沒道理不用的。

還順便測試一下目前最潮的 lambda 語法。

還有 setjmp/longjmp x64 的版本。

m.cpp
 1 #include  "myvec.h"
 2 #include  "myiostream.h"
 3 #include "my_setjmp.h"
 4 using namespace DS;
 5 
 6 class Obj
 7 {
 8   public:
 9     Obj()
10     {
11       i=10;
12       ::printf("ctor\n");
13     }
14     ~Obj()
15     {
16       ::printf("dtor\n");
17     }
18   private:
19     int i;
20 };
21 
22 jmp_buf jbuf;
23 
24 int lambda_test(int girls = 3, int boys = 4)
25 {
26   auto totalChild = [](int x, int y) ->int{return x+y;};
27   return totalChild(girls, boys);
28 }
29 
30 int main (IN int Argc, IN char **Argv)
31 {
32   Obj obj;
33 
34   int total = lambda_test();
35   cout << "total: " << total << endl;
36 
37   vector<int> vec_i;
38 
39   vec_i.push_back(1);
40   vec_i.push_back(2);
41   vec_i.push_back(3);
42   for (int i=0 ; i < vec_i.size() ; ++i)
43     cout << vec_i[i] << endl;
44 
45   char *area = (char*)mymalloc(20);
46   char *a1 = new char [20];
47 
48   int j_ret = 9;
49   //printf("aaa\n");
50   //getchar();
51   j_ret = my_setjmp(jbuf);
52 
53   if (j_ret == 0)
54   {
55     printf("00\n");
56   }
57   else 
58   {
59     printf("11\n");
60     return 0;
61   }
62 
63   my_longjmp(jbuf, 5);
64   return 0;
65 }


build -p AppPkg/AppPkg.dsc 就可以編譯出 m.efi 了, 不過先別急著打這個指令, 還要寫個 script gcc (書上提供了一個, 照抄後再加上 g++ option, ref list 1), 判斷是 .c 檔時出動 gcc, .cpp 檔時出動 g++。當然也可能要修改 uefi/edk2/BaseTools/Scripts/GccBase.lds, 不過目前就不要搞太複雜, 這樣就可以了。

list 1. gcc script
 1 #!/bin/sh
 3 iscpp=0
 4 for i in "$@" ; do
 5   if [ "-" != "${i:0:1}" ] ; then
 6     if [ "cpp" = "${i##*.}" ]; then
 7       iscpp=1
 8     fi
 9   fi
10 done
11 
12 if [ $iscpp = 0 ]; then
13   echo gcc
14   /usr/bin/gcc -DUEFI $@
15 else
16   echo g++
18   /usr/bin/g++ -DUEFI -fpermissive -I. -DSTM32 -fno-exceptions -fno-rtti -ffreestanding -nostdlib -nodefaultlibs -std=c++11 $@
19 fi


好了, 現在可以痛快的敲下 build -p AppPkg/AppPkg.dsc

fig 2 為測試畫面, ctor/dtor 正常發動了, lambda 也正常, DS::vector, DS::cout 也正常, 感謝 bjarne stroustrup; 感謝 c++。

fig 2模擬器測試畫面


為了保險起見, 我在真實機器上同樣也做了測試, 真的是可以跑的。

真實機器的 uefi 執行畫面


global object ctor/dtor 沒有搞定, 比我想的還複雜些, 這不是什麼問題, 不要用就好了。 XD

我是用手動呼叫 GLOBAL_XX function 來搞定這件事。

uefi-shceme 就是這麼做的。

uefi function 手冊: http://www.bluestop.org/edk2/docs/trunk/getchar_8c.html

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。