UEFI Boot#### variable auslesen



  • Hallo zusammen,

    ich habe mich gerade eben hier registriert da ich Probleme dabei habe einen Wert den ich von der Methode GetFirmwareEnvironmentVariable zurück bekomme richtig darzustellen.

    Ich habe ein wenig Erfahrung mit C# und auch schon einige Projekte für die Arbeit fertig gestellt allerdings habe ich mit C/C++ einige Probleme weil mir die Sprache sehr "abstrakt" vorkommt.

    Hier zu meinem Problem:
    Ich will den Wert der UEFI Variable Boot#### auslesen. Dieser Wert sollte informationen zu den Boot Devices beinhalten wie z. B. den Namen wie er auch in der Boot Order im UEFI angezeigt wird. Außerdem steht in der UEFI Spec:

    Each Boot#### variable contains an EFI_LOAD_OPTION. Each Boot#### variable is the
    name “Boot” appended with a unique four digit hexadecimal number. For example, Boot0001,
    Boot0002, Boot0A02, etc.

    Hier ist mein Code:

    BYTE Val[4096];
    const TCHAR guid[] = TEXT("{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}");
    
    	dwLen = GetFirmwareEnvironmentVariable(
    		L"Boot0003", guid, Val, 4096);
    

    Der Wert den ich von der Methode über pBuffer zurück bekomme beinhaltet bei mir folgendes:

    -	Val	0x001cee3c "\x1"	unsigned char[4096]
    	[0]	1 '\x1'	unsigned char
    	[1]	0 '\0'	unsigned char
    	[2]	0 '\0'	unsigned char
    	[3]	0 '\0'	unsigned char
    	[4]	86 'V'	unsigned char
    	[5]	0 '\0'	unsigned char
    	[6]	77 'M'	unsigned char
    	[7]	0 '\0'	unsigned char
    	[8]	115 's'	unsigned char
    	[9]	0 '\0'	unsigned char
    	[10]	84 'T'	unsigned char
    	[11]	0 '\0'	unsigned char
    	[12]	101 'e'	unsigned char
    	[13]	0 '\0'	unsigned char
    	[14]	109 'm'	unsigned char
    	[15]	0 '\0'	unsigned char
    	[16]	112 'p'	unsigned char
    	[17]	0 '\0'	unsigned char
    	[18]	0 '\0'	unsigned char
    	[19]	0 '\0'	unsigned char
    	[20]	2 '\x2'	unsigned char
    	[21]	1 '\x1'	unsigned char
    	[22]	12 '\f'	unsigned char
    	[23]	0 '\0'	unsigned char
    	[24]	208 'Ð'	unsigned char
    	[25]	65 'A'	unsigned char
    	[26]	3 '\x3'	unsigned char
    	[27]	10 '\n'	unsigned char
    	[28]	0 '\0'	unsigned char
    	[29]	0 '\0'	unsigned char
    	[30]	0 '\0'	unsigned char
    	[31]	0 '\0'	unsigned char
    	[32]	1 '\x1'	unsigned char
    	[33]	1 '\x1'	unsigned char
    	[34]	6 '\x6'	unsigned char
    	[35]	0 '\0'	unsigned char
    	[36]	0 '\0'	unsigned char
    	[37]	28 '\x1c'	unsigned char
    	[38]	1 '\x1'	unsigned char
    	[39]	1 '\x1'	unsigned char
    	[40]	6 '\x6'	unsigned char
    	[41]	0 '\0'	unsigned char
    	[42]	0 '\0'	unsigned char
    	[43]	0 '\0'	unsigned char
    	[44]	3 '\x3'	unsigned char
    	[45]	23 '\x17'	unsigned char
    	[46]	16 '\x10'	unsigned char
    	[47]	0 '\0'	unsigned char
    	[48]	1 '\x1'	unsigned char
    	[49]	0 '\0'	unsigned char
    	[50]	0 '\0'	unsigned char
    	[51]	0 '\0'	unsigned char
    	[52]	0 '\0'	unsigned char
    	[53]	8 '\b'	unsigned char
    	[54]	13 '\r'	unsigned char
    	[55]	2 '\x2'	unsigned char
    	[56]	0 '\0'	unsigned char
    	[57]	0 '\0'	unsigned char
    	[58]	106 'j'	unsigned char
    	[59]	112 'p'	unsigned char
    	[60]	4 '\x4'	unsigned char
    	[61]	1 '\x1'	unsigned char
    	[62]	42 '*'	unsigned char
    	[63]	0 '\0'	unsigned char
    	[64]	1 '\x1'	unsigned char
    	[65]	0 '\0'	unsigned char
    	[66]	0 '\0'	unsigned char
    	[67]	0 '\0'	unsigned char
    	[68]	34 '\"'	unsigned char
    	[69]	0 '\0'	unsigned char
    	[70]	0 '\0'	unsigned char
    	[71]	0 '\0'	unsigned char
    	[72]	0 '\0'	unsigned char
    	[73]	0 '\0'	unsigned char
    	[74]	0 '\0'	unsigned char
    	[75]	0 '\0'	unsigned char
    	[76]	0 '\0'	unsigned char
    	[77]	32 ' '	unsigned char
    	[78]	3 '\x3'	unsigned char
    	[79]	0 '\0'	unsigned char
    	[80]	0 '\0'	unsigned char
    	[81]	0 '\0'	unsigned char
    	[82]	0 '\0'	unsigned char
    	[83]	0 '\0'	unsigned char
    	[84]	78 'N'	unsigned char
    	[85]	2 '\x2'	unsigned char
    	[86]	92 '\\'	unsigned char
    	[87]	0 '\0'	unsigned char
    	[88]	243 'ó'	unsigned char
    	[89]	101 'e'	unsigned char
    	[90]	165 '¥'	unsigned char
    	[91]	65 'A'	unsigned char
    	[92]	143 ''	unsigned char
    	[93]	145 '‘'	unsigned char
    	[94]	115 's'	unsigned char
    	[95]	87 'W'	unsigned char
    	[96]	97 'a'	unsigned char
    	[97]	96 '`'	unsigned char
    	[98]	196 'Ä'	unsigned char
    	[99]	92 '\\'	unsigned char
    	[100]	2 '\x2'	unsigned char
    	[101]	2 '\x2'	unsigned char
    	[102]	127 ''	unsigned char
    	[103]	255 'ÿ'	unsigned char
    	[104]	4 '\x4'	unsigned char
    	[105]	0 '\0'	unsigned char
    

    Ich habe bei Github das Projekt bootmgr gefunden und hier wird folgendes gemacht:

    129   UINT16        LDAttr; 
    [...]
    197         BootVariable = mGetVariable(Name, &gEfiGlobalVariableGuid, &BootVariableSize, NULL); 
    198         //print attribute 
    199         LDAttr = BootVariable[0]; 
    200         if (opts.show_verbose){ 
    201         	i = 6;   //for adjust display 
    202           if (LDAttr == 0) 
    203         	Print(L"CB*", i--);     //category boot 
    204           if (LDAttr & 1) 
    205            	Print(L"A* ", i--);      //active 
    206           if (LDAttr & 2) 
    207            	Print(L"FR*", i--);     //force reconnect 
    208           if (LDAttr & 8) 
    209            	Print(L"H* ", i--);      //hidden 
    210           if (LDAttr & 0x100) 
    211            	Print(L"CA*", i--);     //category app 
    212            //Print(L"\n"); 
    213            while (i--){ 
    214            	Print(L"   "); 
    215            }
    

    Ich habe nun zwei Fragen:
    1. Warum sieht der Wert den ich zurück bekomme so seltsam aus? Das Einzige was man erkennen kann ist der Name des Boot Devices: MsTemp aber der Rest ergibt keinen Sinn.
    2. Wie kann ich den Code den ich in dem C Projekt gefunden habe auf meines anwenden um die EFI_LOAD_OPTIONS herauszufinden?

    Danke schon mal für jede Hilfe!



  • voku schrieb:

    Ich habe nun zwei Fragen:
    1. Warum sieht der Wert den ich zurück bekomme so seltsam aus? Das Einzige was man erkennen kann ist der Name des Boot Devices: MsTemp aber der Rest ergibt keinen Sinn.

    Dochdoch, das ergibt alles Sinn.
    Das ist einfach ne EFI_LOAD_OPTION Struktur.

    Als erstes kommt der 32 Bit Wert Attributes .

    Dann kommt der 16 Bit Wert FilePathListLength .

    Dann kommt der nullterminierte 16 Bit String (vermutlich UTF-16 oder UCS2) Description .

    Dann kommt eine Reihe von EFI_DEVICE_PATH_PROTOCOL Einträgen. In FilePathListLength steht dabei die Länge dieses Arrays (in Bytes!). Weiters sollte der Typ des letzten Eintrags entsprechend gesetzt sein (Type = 0x7F, SubType = 0xFF).

    Der Rest ist das Array OptionalData . (In deinem Fall leer.)

    voku schrieb:

    2. Wie kann ich den Code den ich in dem C Projekt gefunden habe auf meines anwenden um die EFI_LOAD_OPTIONS herauszufinden?

    Das sind die EFI_LOAD_OPTIONS . Wenn du an bestimmte Einträge der Struktur drankommen willst musst du das Ding halt parsen.



  • Heißt das, dass ich um an die Attributes zu kommen z.B. mit einer for Schleife die ersten 32 Bit lese und dann in diesem Fall zu UINT32 caste?
    Ich hab mit so etwas noch nie gearbeitet und deshalb leider keine Ahung wie ich da vorgehen muss.



  • Erstell dir einfach eine Struktur (wie unter UEFI Boot Manager angegeben):

    EFI_LOAD_OPTION
    {
      UINT32 Attributes;
      UINT16 FilePathListLength;
      // CHAR16 Description[];
      // EFI_DEVICE_PATH_PROTOCOL FilePathList[];
      // UINT8 OptionalData[];
    }
    

    (da die variablen Anteile [] nicht in Strukturen festgelegt werden können, mußt du diese selber parsen)
    Mittels

    EFI_LOAD_OPTION *p_load_option = (EFI_LOAD_OPTION*)pBuffer;
    
    p_load_option->Attributes;
    p_load_option->FilePathLength;
    

    kommst du zumindestens an die ersten beiden Werte dran.
    Die restlichen Werte mußt du dann allerdings selber per Iteration und Zeigerarithmetik parsen.



  • @voku
    Im Prinzip ja.
    Für die ersten beiden Werte könntest du das machen was Th69 vorgeschlagen hat. *
    Für den Rest brauchst du aber doch was anderes, also kannst du mit diesem anderen auch gleich alles machen.

    z.B. könntest du ne simple "Byte Range" Implementierung machen, und dann Funktionen die dir aus der Byte-Range verschiedene Dinge auslesen.

    Quasi

    struct byte_range
    {
        uint8_t* current;
        uint8_t* end;
    
        // ...
    
        size_t size() const { return end - current; }
        bool empty() const { return size() == 0; }
    
        uint8_t peek() const
        {
            if (empty())
                throw std::logic_error("empty");
            return *current;
        }
    
        uint8_t read()
        {
            uint8_t const result = peek();
            current++;
            return result;
        }
    
        byte_range take(size_t n)
        {
            if (n > size())
                throw std::logic_error("meh");
            byte_range result = *this;
            result.end = current + n;
            current = result.end;
            return result;
        }
    };
    
    uint16_t read_uint16(byte_range& r)
    {
        uint8_t const b1 = r.read();
        uint8_t const b2 = r.read();
        return b1 + (b2 << 8);
    }
    
    uint32_t read_uint32(byte_range& r)
    {
        uint16_t const w1 = read_uint16(r);
        uint16_t const w2 = read_uint16(r);
        return w1 + (w2 << 16);
    }
    
    std::u16string read_u16string(byte_range& r)
    {
        std::u16string buffer;
        for (;;)
        {
            char16_t const ch = read_uint16(r);
            if (ch)
                buffer.push_back(ch);
            else
                return buffer;
        }
    }
    
    struct EFI_DEVICE_PATH_PROTOCOL
    {
       ...
    };
    
    EFI_DEVICE_PATH_PROTOCOL read_EFI_DEVICE_PATH_PROTOCOL(byte_range& r)
    {
       ...
    }
    
    struct EFI_LOAD_OPTION
    {
        uint32_t Attributes;
        uint16_t FilePathListLength;
        std::u16string Description;
        std::vector<EFI_DEVICE_PATH_PROTOCOL> FilePathList;
        std::vector<uint8_t> OptionalData;
    }
    
    EFI_LOAD_OPTION read_EFI_LOAD_OPTION(byte_range& r)
    {
        EFI_LOAD_OPTION result;
        result.Attributes = read_uint32(r);
        result.FilePathListLength = read_uint16(r);
        result.Description = read_u16string(r);
    
        byte_range fpl = r.take(result.FilePathListLength);
        while (!fpl.empty())
            result.push_back(read_EFI_DEVICE_PATH_PROTOCOL(fpl));
    
        byte_range rest = r.take(r.size());
        result.OptionalData = std::vector<uint8_t>(rest.current, rest.end);
    
        return result;
    }
    
    // ...
    

    Damit hast du ruck-zuck die ganzen Deserialisierungsfunktionen die du brauchst.

    EDIT:
    *: Wobei ich nicht weiss welche Byte-Order die EFI Strukturen verwenden. Könnte sein immer "native", dann wäre die Variante von Th69 OK (und mein Beispielcode müsste angepasst werden!). Könnte aber auch sein immer Big-Endian oder immer Little-Endian. Dann würde die Variante von Th69 auf Systemen die nicht die spezifizierte Byte-Order falsche Daten liefern. Der Teil sollte aber trivial anzupassen sein, wollte es nur erwähnen weil man oft auf sowas vergisst.



  • Vielen Dank hustbaer, dein Code hat mir sehr weitergeholfen!

    Bis auf die read Methoden verstehe ich eigentlich alles. In read_uint16 ließt du immer zwei bytes, machst dann einen Bitshift auf den zweiten Wert und addierst die Variablen. Wird der Bitshift gemacht um aus zwei Bytes einen uint16 zu machen und dabei nicht den Wert zu verfälschen?
    Wenn man sich den Inhalt von pBuffer ansieht, erkennt man ja ganz gut bei der Description, dass nach jedem Buchstabe 0 kommt. Das heißt, dass bei dem Bitshift eigentlich nie etwas passiert.



  • Naja wenn du dir anguckst wie ein uint16 im Speicher liegt, ... also so macht man es halt normalerweise wenn man 16-, 32- oder 64-bittige Integers aus einem "Byte-Strom" einliest.

    voku schrieb:

    Wird der Bitshift gemacht um aus zwei Bytes einen uint16 zu machen und dabei nicht den Wert zu verfälschen?

    Ja, du musst die Werte halt irgendwie zusammenbauen.
    0x1234 liegt halt als 0x34, 0x12 im Speicher.
    Damit du wieder 0x1234 rausbekommst musst du also den 2. Wert um 8 Bit nach links verschieben. Bzw. mit 256 multiplizieren ginge natürlich genau so - is ja hübsch das selbe.

    Sorry, hab grad wenig Zeit, daher keine genauere Erklärung. Google vielleicht mal nach "endianness", da solltest du ein paar gute Info zu dem Thema finden.

    voku schrieb:

    Wenn man sich den Inhalt von pBuffer ansieht, erkennt man ja ganz gut bei der Description, dass nach jedem Buchstabe 0 kommt. Das heißt, dass bei dem Bitshift eigentlich nie etwas passiert.

    Haha, ja. Aber nur weil da keine Zeichen vorkommen die nicht mit Latin-1 darstellbar sind. Sobald du fremdsprachige Strings hast sieht das anders aus. FilePathListLength wird ja auch als uint16 gelesen. Und da macht es sehrwohl nen Unterschied.


Anmelden zum Antworten