diff --git a/OLVASSEL.md b/OLVASSEL.md new file mode 100644 index 0000000..655c9c9 --- /dev/null +++ b/OLVASSEL.md @@ -0,0 +1,341 @@ +POSIX-UEFI +========== + +
Hányunk attól az ocsmány UEFI API-tól, a megszokott POSIX-ot akarjuk!
+ +Ez egy nagyon minimális fordító környezet, ami segít UEFI-re fejleszteni Linux (vagy más POSIX kompatíbilis) rendszer alatt. +Nagy hatással volt rá a [gnu-efi](https://sourceforge.net/projects/gnu-efi) (minden elismerésem nekik), de annál kissebb, +egyszerűbb és könnyebben integrálható (működik LLVM Clang és GNU gcc környezettel is), valamint könnyebben is használható, +mivel POSIX-szerű API-t biztosít az UEFI alkalmazásod felé. + +Az UEFI környezet két komponensből áll: a GUID protokoll interfészű firmverből és egy felhasználói könyvtárból. Az előbbit +nem tudjuk lecserélni, de az utóbbit barátságosabbá tehetjük. És pont ez az, amit a POSIX-UEFI csinál. Egy vékony +függvénykönyvtár ami becsomagolja a GUID protokollos API-t, nem több, nem kevesebb, nem egy teljes értékű libc implementáció. + +Kétféleképp tudod integrálni a projektedbe: + +Statikus Függvénykönyvtárként +----------------------------- + +Az `uefi` könyvtárban futtasd a következő parancsot +```sh +$ USE_GCC=1 make +``` +Ez létrehozza az `build/uefi` mappát, minden szükséges fájllal együtt. Ezek: + + - **crt0.o**, futásidejű kód, ami elind0tja a POSIX-UEFI-t + - **link.ld**, linkelő szkript, amit a POSIX-UEFI-vel használni kell (ugyanaz, mint gnu-efi esetén) + - **libuefi.a**, a függvénykönyvtár maga + - **uefi.h**, minden az egyben C / C++ fejlécfájl + +Ezzel összeszerkesztheted a programodat, de nem fogod tudni újrafordítani, és a linkeléssel és konvertálással is magadra +maradtsz. + +Szigorúan véve csak a **crt0.o** és a **link.ld** fájlokra van szükség, ez elég ahhoz, hogy elindítsa és meghívja az alkalmazásod +"main()" eljárását. Viszont ahhoz, hogy a libc funkciókat (mint pl. memcmp, strcpy, malloc vagy fopen) is használhasd, linkelned +kell a **libuefi.a** fájllal. + +Egyenlőre ez csak gcc-vel működik, mivel a Clang úgy van beállítva, hogy direktben PE fájlokat hoz létre, ezért nem tud statikus +ELF .a fájlokat generálni, sem linkelni velük. + +Forrásként terjesztve +--------------------- + +Ez a javasolt mód, mivel ez biztosít egy Makefile-t is ami megfelelően beállítja a fordítókörnyezetedet. + + 1. másold be a `uefi` könyvtárat a forrásfádba (vagy állíts be egy git submodule-t). Egy tucat fájl, kb. 132K összesen. + 2. csinálj egy hihetetlenül egyszerű **Makefile**-t, mint például az alábbi + 3. fordítsd le a programodat UEFI-re egy `make` hívással + +``` +TARGET = helloworld.efi +include uefi/Makefile +``` +Egy minta **helloworld.c** így néz ki: +```c +#include + +int main(int argc, char **argv) +{ + printf("Hello World!\n"); + return 0; +} +``` +Alapértelmezetten Clang + lld környezetet keres és állít be, ami direktben PE fájlt hoz létre konvertálás nélkül. Ha a `USE_GCC` +környezeti változó be van állítva, akkor a hoszt natív GNU gcc + ld használatával egy megosztott függvénykönyvtárat fordít, amit +aztán átkonvertál .efi fájllá. + +Ha kikommentezed az `USE_UTF8` opciót az uefi.h fájl elején, akkor minden karakter `wchar_t` típusú lesz, és nem fogja konvertálni +a sztringeket a programod és az UEFI interfészek között. Ez azt is jelenti, hogy minden konstansnál `L""` és `L''` előtagot kell +használni, valamint hogy a main függvényed `wchar_t **argv` paramétert fog kapni. + +### Elérhető Makefile opciók + +| Változó | Leírás | +|------------|------------------------------------------------------------------------------------------------------| +| `TARGET` | a cél program (szükséges megadni) | +| `SRCS` | források listája, amiket fordítani kell (alapértelmezetten minden \*.c \*.S fájl) | +| `CFLAGS` | további fordító opciók (alapértelmezetten üres, pl. "-Wall -pedantic -std=c99") | +| `LDFLAGS` | további linkelő opciók (nem hiszem, hogy valaha is szökség lesz erre, csak a teljesség kedvéért) | +| `LIBS` | további függvénykönyvtárak, amikkel linkelni szeretnél (pl "-lm", csak statikus .a jöhet szóba) | +| `USE_GCC` | ha beállítod, akkor natív GNU gcc + ld + objccopy környzetet használ LLVM Clang + Lld helyett | +| `ARCH` | a cél architektúra | + +Itt van egy teljesebb **Makefile** példa: +``` +ARCH = x86_64 +TARGET = helloworld.efi +SRCS = $(wildcard *.c) +CFLAGS = -pedantic -Wall -Wextra -Werror --std=c11 -O2 +LDFLAGS = +LIBS = -lm + +USE_GCC = 1 +include uefi/Makefile +``` +A fordítási környezet konfiguráló úgy lett kialakítva, hogy akárhány architektúrával elboldogul, azonban eddig csak +az `x86_64` crt0 lett alaposan letesztelve. Van egy `aarch64` crt0 is, de mivel nekem nincs ARM UEFI-s gépem, teszteletlen. +Elvileg kéne működnie. + +Lényeges eltérések a POSIX libc-től +----------------------------------- + +Ez a függvénykönyvtár korántsem annyira teljes, mint a glibc vagy a musl például. Csakis a legszükségesebb libc funkciókat +biztosítja, mivel egyszerűségre törekszik. A legjobb talán UEFI API burkolóként tekinteni rá, és nem pedig egy teljes POSIX +kompatíbilis libc-ként. + +UEFI alatt minden sztring 16 bit széles karakterekkel van tárolva. A függvénykönyvtár biztosít egy `wchar_t` típust ehhez, +és egy `USE_UTF8` define opciót a transzparens `char` és `wchar_t` közötti konvertáláshoz. Ha kikommentezed a `USE_UTF8`-at, +akkor például a main() függvényed NEM `main(int argc, char **argv)` lesz, hanem `main(int argc, wchar_t **argv)`. Az összes +többi sztring függvény (mint például az strlen() is) ezt a széles karaktertípust fogja használni. Emiatt az összes sztring +konstansot `L""`-el, a karakterkonstansokat pedig `L''`-el kell definiálni. Hogy mindkét konfigurációt kezelni lehessen, +adott egy `char_t` típus, ami vagy `char` vagy `wchar_t`, és a `CL()` makró, ami `L` előtagot ad a konstansokhoz, amikor kell. +Azok a funkciók, amik a karaktereket int típusként kezelik (pl. `getchar`, `putchar`), nem unsigned char-ra csonkítanak, hanem +wchar_t-re. + +A fájl típusok a dirent-ben nagyon limitáltak, csak könyvtár és fájl megengedett (DT_DIR, DT_REG), de a stat pluszban az +S_IFDIR és S_IFREG típusokhoz, S_IFIFO (konzol folyamok: stdin, stdout, stderr), S_IFBLK (Block IO esetén) és S_IFCHR +(Serial IO esetén) típusokat is visszaadhat. + +Továbbá a `getenv` és `setenv` sem POSIX sztandard, mivel az UEFI környezeti változók bináris bitkolbászok. + +Nagyjából ennyi, minden más a megszokott. + +Az elérhető POSIX funkciók listája +---------------------------------- + +### dirent.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| opendir | megszokott, de széles karakterű sztringet is elfogadhat | +| readdir | megszokott | +| rewinddir | megszokott | +| closedir | megszokott | + +Mivel az UEFI számára ismeretlen az eszközfájl és a szimbólikus link, a dirent mezők eléggé limitáltak, és csak DT_DIR +valamint DT_REF típusok támogatottak. + +### stdlib.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| atoi | megszokott, de széles karakterű sztringet is elfogadhat és "0x" prefix | +| atol | megszokott, de széles karakterű sztringet is elfogadhat és "0x" prefix | +| strtol | megszokott, de széles karakterű sztringet is elfogadhat | +| malloc | megszokott | +| calloc | megszokott | +| realloc | megszokott | +| free | megszokott | +| abort | megszokott | +| exit | megszokott | +| exit_bs | az egész UEFI szörnyűség elhagyása (exit Boot Services) | +| mbtowc | megszokott (UTF-8 karakter wchar_t-á) | +| wctomb | megszokott (wchar_t-ról UTF-8 karakterré) | +| mbstowcs | megszokott (UTF-8 sztringről wchar_t sztringé) | +| wcstombs | megszokott (wchar_t sztringről UTF-8 sztringé) | +| srand | megszokott | +| rand | megszokott, de EFI_RNG_PROTOCOL-t használ, ha lehetséges | +| getenv | eléggé UEFI specifikus | +| setenv | eléggé UEFI specifikus | + +```c +int exit_bs(); +``` +Exit Boot Services, az UEFI sárkánylakta vidékének elhagyása. Siker esetén 0-át ad vissza. + +```c +uint8_t *getenv(char_t *name, uintn_t *len); +``` +A `name` környezeti változó értékének lekérdezése. Siker esetén `len` be lesz állítva, és egy frissen allokált buffert +ad vissza. A hívó felelőssége a visszaadott buffer felszabadítása, ha már nem kell. Hiba esetén NULL-t ad vissza. + +```c +int setenv(char_t *name, uintn_t len, uint8_t *data); +``` +A `name` környezeti változó beállítása `len` hosszú `data` értékkel. Siker esetén 1-el tér vissza, hibánál 0-val. + +### stdio.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| fopen | megszokott, de széles karakterű sztringet is elfogadhat, mode esetén is | +| fclose | megszokott | +| fflush | megszokott | +| fread | megszokott, csak igazi fájlok és blk io (nem stdin) | +| fwrite | megszokott, csak igazi fájlok és blk io (nem lehet stdout se stderr) | +| fseek | megszokott, csak igazi fájlok és blk io (nem stdin, stdout, stderr) | +| ftell | megszokott, csak igazi fájlok és blk io (nem stdin, stdout, stderr) | +| feof | megszokott, csak igazi fájlok és blk io (nem stdin, stdout, stderr) | +| fprintf | megszokott, de széles sztring is lehet, BUFSIZ, fájl, ser, stdout, stderr | +| printf | megszokott, de széles sztring is lehet, max BUFSIZ, csak stdout | +| sprintf | megszokott, de széles sztring is lehet, max BUFSIZ | +| vfprintf | megszokott, de széles sztring is lehet, BUFSIZ, fájl, ser, stdout, stderr | +| vprintf | megszokott, de széles sztring is lehet, max BUFSIZ, csak stdout | +| vsprintf | megszokott, de széles sztring is lehet, max BUFSIZ | +| snprintf | megszokott, de széles sztring is lehet | +| vsnprintf | megszokott, de széles sztring is lehet | +| getchar | megszokott, blokkol, csak stdin (nincs átirányítás), UNICODE-ot ad vissza | +| getchar_ifany | nem blokkoló, 0-át ad vissza ha nem volt billentyű, egyébként UNICODE-ot | +| putchar | megszokott, csak stdout (nincs átriányítás) | + +Fájl megnyitási módok: `"r"` olvasás, `"w"` írás, `"a"` hozzáfűzés. UEFI sajátosságok miatt, `"wd"` könyvtárat hoz létre. + +A sztring formázás limitált: csak számokat fogad el prefixnek, `%d`, `%x`, `%X`, `%c`, `%s`, `%q` és `%p`. Ha `USE_UTF8` nincs +definiálva, akkor a formázás wchar_t-t használ, ezért ilyenkor támogatott a nem szabványos `%S` (UTF-8 sztring kiírás), `%Q` +(eszképelt UTF-8 sztring kiírás). Ezek a funkciók nem foglalnak le memóriát, cserébe a teljes hossz `BUFSIZ` lehet (8k ha nem +definiálták másképp), kivéve azokat a variánsokat, amik elfogadnak maxlen hossz paramétert. Kényelmi okokból támogatott a `%D` +aminek `efi_physical_address_t` paramétert kell adni, és a memóriát dumpolja, 16 bájtos sorokban. A szám módosítókkal lehet +több sort is dumpoltatni, például `%5D` 5 sort fog dumpolni (80 bájt). + +Speciális "eszköz fájlok", amiket meg lehet nyitni: + +| Név | Leírás | +|---------------------|----------------------------------------------------------------------| +| `/dev/stdin` | ST->ConIn | +| `/dev/stdout` | ST->ConOut, fprintf | +| `/dev/stderr` | ST->StdErr, fprintf | +| `/dev/serial(baud)` | Serial IO protokoll, fread, fwrite, fprintf | +| `/dev/disk(n)` | Block IO protokoll, fseek, ftell, fread, fwrite, feof | + +Block IO esetén az fseek és a buffer méret fread és fwritenál az eszköz blokméretére lesz igazítva. Például fseek(513) +az 512. bájtra pozicionál szabvány blokkméretnél, de 0-ra nagy 4096-os blokkoknál. A blokkméret detektálásához az fstat-ot +lehet használni. +```c +if(!fstat(f, &st)) + block_size = st.st_size / st.st_blocks; +``` +A partíciós GPT tábla értelmezéséhez típusdefiníciók állnak a rendelkezésre, mint `efi_partition_table_header_t` és +`efi_partition_entry_t`, amikkel a beolvasott adatokra lehet mutatni. + +### string.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| memcpy | megszokott, mindenképp bájt | +| memmove | megszokott, mindenképp bájt | +| memset | megszokott, mindenképp bájt | +| memcmp | megszokott, mindenképp bájt | +| memchr | megszokott, mindenképp bájt | +| memrchr | megszokott, mindenképp bájt | +| memmem | megszokott, mindenképp bájt | +| memrmem | megszokott, mindenképp bájt | +| strcpy | széles karakterű sztringet is elfogadhat | +| strncpy | széles karakterű sztringet is elfogadhat | +| strcat | széles karakterű sztringet is elfogadhat | +| strncat | széles karakterű sztringet is elfogadhat | +| strcmp | széles karakterű sztringet is elfogadhat | +| strncmp | széles karakterű sztringet is elfogadhat | +| strdup | széles karakterű sztringet is elfogadhat | +| strchr | széles karakterű sztringet is elfogadhat | +| strrchr | széles karakterű sztringet is elfogadhat | +| strstr | széles karakterű sztringet is elfogadhat | +| strtok | széles karakterű sztringet is elfogadhat | +| strtok_r | széles karakterű sztringet is elfogadhat | +| strlen | széles karakterű sztringet is elfogadhat | + +### sys/stat.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| stat | megszokott, de széles karakterű sztringet is elfogadhat | +| fstat | UEFI alatt nincs fd, ezért FILE\*-ot használ | +| mkdir | megszokott, de széles karakterű sztringet is elfogadhat, mode nem használt | + +Mivel az UEFI számára ismeretlen az eszköz major és minor valamint az inode szám, a struct stat mezői limitáltak. +Az `fstat` implementációja az stdio.c-ben található, mivel el kell érnie bizonyos ott definiált statikus változókat. + +### time.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| localtime | paraméterek nem használtak, mindig a pontos időt adja vissza struct tm-ben | +| mktime | megszokott | +| time | megszokott | + +### unistd.h + +| Funkció | Leírás | +|---------------|----------------------------------------------------------------------------| +| usleep | megszokott (BS->Stall hívást használ) | +| sleep | megszokott | +| unlink | megszokott, de széles karakterű sztringet is elfogadhat | +| rmdir | megszokott, de széles karakterű sztringet is elfogadhat | + +UEFI szolgáltatások elérése +--------------------------- + +Elég valószínű, hogy direktben UEFI szolgáltatást kell majd hívni. Ehhez a POSIX-UEFI néhány globális változót biztosít +az `uefi.h`-ban definiálva: + +| Globális változó | Leírás | +|------------------|----------------------------------------------------------| +| `*BS`, `gBS` | *efi_boot_services_t*, mutató a Boot Time Services-ra | +| `*RT`, `gRT` | *efi_runtime_t*, mutató a Runtime Services-ra | +| `*ST`, `gST` | *efi_system_table_t*, mutató az UEFI System Table-re | +| `IM` | a betöltött fájlod *efi_handle_t*-je (loaded image) | + +Az EFI struktúrák, enumok, típusdefiníciók, defineok mind ANSI C szabványos POSIX stílusúra lettek konvertálva, például +BOOLEAN -> boolean_t, UINTN -> uintn_t, EFI_MEMORY_DESCRIPTOR -> efi_memory_descriptor_t, és persze +EFI_BOOT_SERVICES -> efi_boot_services_t. + +UEFI funkciók hívása pont olyan egyszerű, mint EDK II esetén, csak meg kell hívni, nincs szükség "uefi_call_wrapper"-re: +```c + ST->ConOut->OutputString(ST->ConOut, L"Hello World!\r\n"); +``` +(Megjegyzés: a printf-el ellentétben az OutputString-nél mindig kell `L""` és ki kell írni `L"\r"`-t a `L"\n"` előtt. Ezek +azok az apróságok, amikkel a POSIX-UEFI kényelmesebbé teszi az életedet.) + +Van továbbá két, nem POSIX szabványos függvény a könyvtárban. Az egyik az `exit_bs()` az UEFI elhagyására, a másik a nem +blokkoló `getchar_ifany()`. + +A gnu-efi-vel ellentétben a POSIX-UEFI nem szennyezi a névteret nemhasznált GUID változókkal. Csak define-okat biztosít, +ezért mindig neked kell létrehozni a GUID példányt, ha és amikor szükséged van rá. + +Példa: +```c +efi_guid_t gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; +efi_gop_t *gop = NULL; + +status = BS->LocateProtocol(&gopGuid, NULL, (void**)&gop); +``` + +Szintén a gnu-efi-vel ellentétben a POSIX-UEFI nem biztosítja a szabványos UEFI fejlécfájlokat. Úgy veszi, hogy azokat +vagy az EDk II-ből vagy a gnu.efi-ből átmásoltad az /usr/include/efi alá, és biztosítja, hogy be legyenek húzva névtér +ütközés nélkül. Maga a POSIX-UEFI csak egy nagyon minimális mennyiségű típusdefiniciót biztosít (és azokat is POSIX-osított +névvel). +```c +#include +#include /* ez működik! Mind a POSIX-UEFI és az EDK II / gnu-efi typedef-ek elérhetők */ +``` +Ennek az az előnye, hogy használhatod a POSIX-UEFI egyszerű könyvtárát és fordító környezetét, ugyanakkor hozzáférhetsz +a legfrissebb protokoll és interfész definíciókoz egyaránt. + +Licensz +------- + +POSIX_UEFI az MIT licensz alatt kerül terjesztésre. + +Ez minden, + +bzt diff --git a/README.md b/README.md index 46ac8f4..c380f47 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ POSIX-UEFI ========== -
We hate that horrible and ugly UEFI API, we want POSIX!
+
We hate that horrible and ugly UEFI API, we want the usual POSIX!
This is a very small build environment that helps you to develop for UEFI under Linux (and other POSIX systems). It was greatly inspired by [gnu-efi](https://sourceforge.net/projects/gnu-efi) (big big kudos to those guys), but it is a lot -smaller, easier to integrate (works with Clang and GNU gcc both) and easier to use because it provides a POSIX like API -for your UEFI application. +smaller, easier to integrate (works with LLVM Clang and GNU gcc both) and easier to use because it provides a POSIX like +API for your UEFI application. An UEFI environment consist of two parts: a firmware with GUID protocol interfaces and a user library. We cannot change the former, but we can make the second friendlier. That's what POSIX-UEFI does for your application. It is a small API @@ -19,7 +19,7 @@ Distributing as Static Library In the `uefi` directory, run ```sh -$ make +$ USE_GCC=1 make ``` This will create `build/uefi` with all the necessary files in it. These are: @@ -34,6 +34,9 @@ the linking and converting. Strictly speaking you'll only need **crt0.o** and **link.ld**, that will get you started and will call your application's "main()", but to get libc functions like memcmp, strcpy, malloc or fopen, you'll have to link with **libuefi.a**. +For now this only works with gcc, because Clang is configured in a way to directly create PE files, so it cannot create +nor link with static ELF .a files. + Distributing as Source ---------------------- @@ -45,7 +48,6 @@ This is the preferred way, as it also provides a Makefile to set up your toolcha ``` TARGET = helloworld.efi - include uefi/Makefile ``` An example **helloworld.c** goes like this: @@ -140,7 +142,7 @@ Because UEFI has no concept of device files nor of symlinks, dirent fields are l | strtol | as usual, but might accept wide char strings | | malloc | as usual | | calloc | as usual | -| realloc | as usual (needs testing) | +| realloc | as usual | | free | as usual | | abort | as usual | | exit | as usual |