--- created_at: '2017-10-21T20:04:05.000Z' title: UEFI Programming – First Steps (2009) url: http://x86asm.net/articles/uefi-programming-first-steps/ author: kqr2 points: 120 story_text: comment_text: num_comments: 18 story_id: story_title: story_url: parent_id: created_at_i: 1508616245 _tags: - story - author_kqr2 - story_15523363 objectID: '15523363' year: 2009 --- [Source](http://x86asm.net/articles/uefi-programming-first-steps/ "Permalink to UEFI Programming - First Steps") # UEFI Programming - First Steps [x86asm.net][1] * * * [Articles][2] * * * [Others][3] * * * [Getting the hardware][4] [Getting the software][5] [Booting into EFI Shell][6] [Building an UEFI application][7] [UEFI Programming][8] [UEFI Programming with FASM][9] [Comments][10] # UEFI Programming - First Steps | ----- | | **vid**, 2009-03-31 | Revision: [1.0][11] | In this article, I will describe steps needed to start on with development of real UEFI applications on x86 PC, and share some practical experiences with problems doing so. I will focus on 64-bit version of UEFI, because the 32-bit version isn't much used in this area (most likely due to Microsoft decision not to support UEFI in 32-bit Vista). So, to follow some of my steps here, you'll need a 64-bit CPU (but not 64-bit OS, you can use any 32-bit OS as well). We will finish this article with EFI Hello World application. This article is continuation of my previous article [Introduction to UEFI][12]. Make sure to understand things described there, before reading on. Of course, anything you try according to this article, you are doing at your own risk. ## Getting the hardware To start UEFI development, first of all you need to get a motherboard whose BIOS has UEFI support. (more precisely we should probably say "whose firmware has UEFI support", but I will use this form). Finding whether particular BIOS has UEFI support often turns out to be quite complicated task. Motherboard manufacturers license BIOS from other companies, usually from AMI (Aptio, AMIBIOS), Phoenix (SecureCore, TrustedCore, AwardCore) or Insyde (InsydeH20). Forget about determining UEFI support just by end-user stats you see in most shops. Since UEFI support is still only in somewhat experimental state, in many cases it isn't even listed in motherboard technical specification. In such case you are left to googling and asking on forums, where you often get only internal brand name that is often hard to match with end-user product designation. One trick that I found out to work for Intel boards (but it may very well work for other boards as well) is to look at BIOS Update Release Notes, e.g. the document which lists changes and fixes of BIOS. If board has UEFI support, you will probably find UEFI mentioned there (and **only** there in case of Intel). In short, determining UEFI support is much harder than it may seem. Some machines that have this technology are listed [here][13]. I use Intel DG33BU board (it was marketed as Intel DG33BUC for some reason). You will also need some place to boot from. In theory just USB pen should be enough, but in practice none of 4 brands I tried worked with my board's UEFI implementation. So you may have to use harddrive. I strongly suggest IDE drive, because SATA drives may need some tweaking of BIOS settings, or they may not work at all. Like the USB pens, USB keyboard might be a problem too. I wouldn't fear this that much, but if you can use PS/2 keyboard, do so. ## Getting the software To go on with UEFI development, you will need two development packages: EFI Development Kit (EDK) and EFI Toolkit. First package, the **EFI Development Kit** contains the TianoCore (public part of reference UEFI implementation by Intel) source code, along with many examples and binaries of EFI Shell (we'll talk about this later), all ready to be built with nice make system. It even can be built into a Win32 UEFI emulator for more convinient development and testing, but I won't cover that here. I also won't demonstrate usage of EDK build system in this article. Even though it might be a good idea for real world project, I want to give you bit more insight about what goes "under the hood" here. Second package, the **EFI Toolkit** is set of extra UEFI applications like FTP client, Python port, text editor, etc. Strictly speaking, we don't really need this, but it has a set of C headers that is somewhat easier to use than those of EDK (unless you take advantage of EDK build system for your project). However, note that it doesn't contain all headers yet - you will quickly run into this problem if you try some real development with it. Along with headers, you also will need documentation of the UEFI interface. That can be downloaded after filling simple form at . This is the official UEFI specification. Except for UEFI specification, there is also another document called Platform Initialization Specification. This describes implementation of UEFI initialization stages (before drivers are loaded and applications can be executed), and more importantly for us, it also describes interface of routines used to implement UEFI. We can understand this Platform Initialization Specification as description of Tiano UEFI implementation. It provides more lowlevel control than UEFI, in cases when such control is needed. Strictly speaking, someone may implement UEFI Specification without implementing anything from Platform Initialization Specification, but that's not very likely to happen in real world. Last but not least, you will need 64-bit C compiler. I suggest Microsoft Visual C++, whose 64-bit version is freely available in Windows DDK. You can get it [here][14]. ## Booting into EFI Shell Up to this point, you may have lacked any visual idea about UEFI. It was just a programatic interface, after all. Purpose of this chapter would be to overcome this, by booting the so-called EFI Shell. EFI shell is much like any other shell you know: you have a command line, through which you can enter commands to browse disk, run applications, configure system, etc. Only difference is that EFI shell is only built using UEFI services, and as such it doesn't require any operating system to run. Working in EFI shell feels much like working in some very simple operating system. From this shell you will also run your applications (later in this article). Actual steps of booting EFI shell might vary a lot among different BIOS brands. Some BIOSes (mostly on MACs where EFI is the primary standard) have extra options for specifying file to boot, or can even have EFI shell built-in inside ROM. However, I only have experience with non-MAC Intel UEFI implementation, that only seems to support the very minimum of features required to install EFI versions of Windows. Microsoft requirements on UEFI implementation, among other things, specify that UEFI boot loader must use fixed path to file to boot, and if it can't boot UEFI for some reason, it must silently switch to default boot. That causes lot of headache when your UEFI doesn't boot as it should, and you must find out what the problem is without any information from loader. For 64-bit UEFI implementations, the path to file that is booted is EFIBOOTBOOTX64.EFI. UEFI boot loader searches all filesystems it can access for this file, and when it finds it, it is executed. As I already said, if the file isn't found, booting continues with legacy BIOS. UEFI boot loader can read MBR or GPT partition tables, and can only read FAT32 partitions. This includes USB drives, so it is possible to boot EFI shell from FAT32-formatted USB pen. Unfortunately, in my tests 3 of 4 USB pens didn't work, and the 4th stopped working too after BIOS update. I had similar problem with one of two SATA drives not working with UEFI. Therefore, I strongly suggest to use IDE drive, if you have any problems. If you already have at least one FAT32 partition on your drive, you can use it, otherwise you need to create fresh new one. Now you need to copy binary of EFI shell to that partition. You can find 64-bit binary of EFI shell in EFI Development Kit: {EDK}OtherMaintainedApplicationUefiShellbinx64Shell_full.efi. Copy it to your FAT32 partition as EFIBOOTBOOTX64.EFI. Now reboot, and enter into BIOS settings. In the **Boot** tab, you should see an **UEFI Boot** option, enable it. If everything is allright (likely not), now after reboot you should end up in the EFI Shell. If you did, congratulations. If you didn't, and instead your regular OS booted as usual, it most likely means that UEFI boot manager wasn't able to access the drive. First try to enter Boot Menu during BIOS screen (F10 on my machine). If the EFI Shell on FAT32 partition was detected, but didn't boot, you will see it as one of option (in my case [`Internal EFI Shell--Harddrive]`). If you see it, just run it. This might possibly happen if you already have some EFI operating system installed, and it has written itself as default EFI boot entry to NVRAM. If you don't see EFI Shell in the Boot Menu, it means UEFI Boot Loader wasn't able to find any FAT32 drive with EFIBOOTBOOTX64.EFI. If you are trying to boot EFI shell from USB key, try tweaking USB emulation settings in BIOS. Same applies to SATA disks and SATA/IDE settings. If none of settings work, or your machine failed to boot from IDE drive, I can't help you any more than to doublecheck everything I wrote so far (especially typos in path to EFI shell). If you have some experience with booting EFI shell not covered in this chapter, please [drop me a message][10], so I can update this article. ## Building an UEFI application So, we are in EFI shell. That means we can finally test any (64-bit) EFI application we write. Time to start writing one. We will of course write and compile applications in normal operating system, not in EFI shell. EFI application or driver is just a plain Windows PE DLL file, just with different subsystem value in header. There are 3 new values: EFI application = 10, EFI boot service driver = 11, efi runtime driver = 12 (numbers are decimal). Question how to set subsystem will be answered later, for now let's focus on the DLL file. EFI PE application doesn't have any fancies we have in Windows PEs, like symbol tables, exports, static exception handling data, etc. It does not even have imports - all you will ever need in EFI application is passed as argument to entry point function. Only thing needed apart from data and code are relocations. So, this is simplest EFI application: #include EFI_STATUS main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { return EFI_SUCCESS; } Compiling it with MS Visual C (supply your own path to EFI toolkit): cl /c /Zl /I"{EFI_Toolkit}includeefi" /I"{EFI_Toolkit}includeefiem64t" hello.c Here we set path to common EFI headers, and to platform-specific EFI headers. The `/c` switch disables linking (we will link separately for better readability), and `/Zl` disables dependency on default libc libraries. Linking: link /entry:main /dll /IGNORE:4086 hello.obj The `/entry:main` overrides default libc entry point to our `main()` function. The `/dll` forces creation of relocations. And the `IGNORE:4086` disables warning `LNK4086`, that is caused by nonstandard `main()` arguments. Now we have windows DLL, we just need to change PE subsystem value to EFI. For that, EFI Toolkit contains simple (buggy) utility that changes subsystem to one of 3 EFI values. We find this utility in EFI_Toolkitbuildtoolsbinfwimage.exe. To set subsystem to EFI app, we'll use it like this: fwimage app hello.dll hello.efi Produced file `hello.efi` should now be functional empty EFI application. We just need to copy it to our FAT32 partition, reboot to EFI shell, and test it: Shell> fs0: fs0:> .hello.efi If we don't get any error message, application worked. ## UEFI Programming Now we finally can get deeper into UEFI programming. Your main source for this information should be the UEFI specification, I will only sum up most basic points. First of all, we should know something about environemnt of UEFI applications. I will describe only environment of 64-bit x86 UEFI here (other to be found in UEFI specification). UEFI runs in uniprocessor flat 64-bit long mode. Usually UEFI runs with memory mapping disabled (physical RAM address = virtual RAM address), but since 64-bit x86 mode requires mapping to be enabled, UEFI maps entire memory so that virtual address is same as physical (i.e. mapping is transparent). Calling convention is usual 64-bit fastcall (first 4 arguments in `RCX`, `RDX`, `R8`, `R9` with space reserved on stack; rest of arguments passed by stack; `RAX`, `R10`, `R11` and `XMM0`-`XMM5` not preserved by called function), so you don't need to worry about special compiler settings. Notable feature of EFI is that for every supported architecture, it defines exact binary interface (ABI). Now let's look at how our application interacts with UEFI services. First, UEFI provides set of services, called Boot Services. These are available to EFI drivers, applications, and to OS boot loader during boot. At some point during OS booting, OS loader can decide to drop them, and after that point those services become unavailable. There is also a little number of services that always remain available, called "Runtime Services". Apart from these two sets of services, all that UEFI offers is available through so-called protocols. Protocol is very much like a class in object oriented programming. UEFI defines set of protocols itself (for example protocols handling USB, filesystem, compression, network, …), and application can define its own protocols (hence the "Extensible" in "Unified Extensible Firmware Interface"). Protocols are identified by GUID (Global Unique Identifier, google it if you don't know what it is). Only very few protocols are mandatory in UEFI specification, and all the rest may or may not be implemented in particular firmware. If protocol isn't implemented in firmware, you can load a driver that implements it, or even write such driver yourself. Now let's look at how to access these services. As I already explained, all you ever need is passed as argument to entry point function. Prototype of entry point function looks like this: EFI_STATUS main( EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable ) First argument is handle of our process, nothing extra to say about it. Second is pointer to `EFI_SYSTEM_TABLE`, the top-level EFI structure, which keeps references to everything there is: boot/runtime services, drivers, protocol implementations, memory maps, etc. It is good idea to always save both these arguments in a global variable, so you can access them from anywhere in source code. You can find detailed description of EFI System Table in UEFI Specification chapter 4 - EFI System Table. Its C definition looks like this: typedef struct _EFI_SYSTEM_TABLE { EFI_TABLE_HEADER Hdr; CHAR16 *FirmwareVendor; UINT32 FirmwareRevision; EFI_HANDLE ConsoleInHandle; SIMPLE_INPUT_PROTOCOL *ConIn; EFI_HANDLE ConsoleOutHandle; SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; EFI_RUNTIME_SERVICES *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; EFI_CONFIGURATION_TABLE *ConfigurationTable; } EFI_SYSTEM_TABLE; Here we see references to boot and runtime services, three standard I/O handles (as implementations of `SIMPLE_TEXT_OUTPUT` and `SIMPLE_INPUT` protocols), and pointer to Configuration Table. Configuration Table holds references to all other protocol implementations currently active in system. First we will show example of using Boot Service. The `EFI_BOOT_SERVICES` is just a structure that holds pointers to functions described in UEFI Specification chapter 6: Services - Boot Services. For now we will use only simple `Exit()` function, that terminates current EFI application immediately. #include EFI_STATUS main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { SystemTable->BootServices->Exit(); // we never get here return EFI_SUCCESS; } Now, we will show simple Hello World application, using the ConOut implementation of `SIMPLE_TEXT_OUTPUT` protocol. This protocol is described in UEFI Specification chapter 11.4 - Simple Text Output Protocol. Its C header looks like this: typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { EFI_TEXT_RESET Reset; EFI_TEXT_STRING OutputString; EFI_TEXT_TEST_STRING TestString; EFI_TEXT_QUERY_MODE QueryMode; EFI_TEXT_SET_MODE SetMode; EFI_TEXT_SET_ATTRIBUTE SetAttribute; EFI_TEXT_CLEAR_SCREEN ClearScreen; EFI_TEXT_SET_CURSOR_POSITION SetCursorPosition; EFI_TEXT_ENABLE_CURSOR EnableCursor; SIMPLE_TEXT_OUTPUT_MODE *Mode; } EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL; We are of course interested in `OutputString()` function, whose prototype is: EFI_STATUS OutputString ( IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, IN CHAR16 *String ); Note that UEFI uses Unicode strings only, hence the `CHAR16 *String`. `This pointer` meaning is exactly same as in any object oriented programming. With this info, we should be able to write Hello World app easily: #include EFI_STATUS main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello Worldrn"); return EFI_SUCCESS; } Also note that UEFI uses CRLF line terminators (`rn`) instead of just LF (`n`), and when we use native EFI functions, there is no layer which reinterprets LF to CRLF. Normally, applications use additional library called EFILIB which does the LF->CRLF transform. ## UEFI Programming with FASM As an extra, I will also demonstrate same Hello World example in assembly (using [FASM][15], that currently has experimental UEFI support since version 1.67.28): First we need some to create simple UEFI headers ([efi.inc][16]): ;for 32/64 portability and automatic natural align in structure definitions struc int8 { . db ? } struc int16 { align 2 . dw ? } struc int32 { align 4 . dd ? } struc int64 { align 8 . dq ? } struc intn { align 8 . dq ? } struc dptr { align 8 . dq ? } ;symbols EFIERR = 0x8000000000000000 EFI_SUCCESS = 0 EFI_LOAD_ERROR = EFIERR or 1 EFI_INVALID_PARAMETER = EFIERR or 2 EFI_UNSUPPORTED = EFIERR or 3 EFI_BAD_BUFFER_SIZE = EFIERR or 4 EFI_BUFFER_TOO_SMALL = EFIERR or 5 EFI_NOT_READY = EFIERR or 6 EFI_DEVICE_ERROR = EFIERR or 7 EFI_WRITE_PROTECTED = EFIERR or 8 EFI_OUT_OF_RESOURCES = EFIERR or 9 EFI_VOLUME_CORRUPTED = EFIERR or 10 EFI_VOLUME_FULL = EFIERR or 11 EFI_NO_MEDIA = EFIERR or 12 EFI_MEDIA_CHANGED = EFIERR or 13 EFI_NOT_FOUND = EFIERR or 14 EFI_ACCESS_DENIED = EFIERR or 15 EFI_NO_RESPONSE = EFIERR or 16 EFI_NO_MAPPING = EFIERR or 17 EFI_TIMEOUT = EFIERR or 18 EFI_NOT_STARTED = EFIERR or 19 EFI_ALREADY_STARTED = EFIERR or 20 EFI_ABORTED = EFIERR or 21 EFI_ICMP_ERROR = EFIERR or 22 EFI_TFTP_ERROR = EFIERR or 23 EFI_PROTOCOL_ERROR = EFIERR or 24 ;helper macro for definition of relative structure member offsets macro struct name { virtual at 0 name name end virtual } ;structures struc EFI_TABLE_HEADER { .Signature int64 .Revision int32 .HeaderSize int32 .CRC32 int32 .Reserved int32 } struct EFI_TABLE_HEADER struc EFI_SYSTEM_TABLE { .Hdr EFI_TABLE_HEADER .FirmwareVendor dptr .FirmwareRevision int32 .ConsoleInHandle dptr .ConIn dptr .ConsoleOutHandle dptr .ConOut dptr .StandardErrorHandle dptr .StdErr dptr .RuntimeServices dptr .BootServices dptr .NumberOfTableEntries intn .ConfigurationTable dptr } struct EFI_SYSTEM_TABLE struc SIMPLE_TEXT_OUTPUT_INTERFACE { .Reset dptr .OutputString dptr .TestString dptr .QueryMode dptr .SetMode dptr .SetAttribute dptr .ClearScreen dptr .SetCursorPosition dptr .EnableCursor dptr .Mode dptr } struct SIMPLE_TEXT_OUTPUT_INTERFACE And here is the assembly code itself ([hello.asm][17]): format pe64 dll efi entry main section '.text' code executable readable include 'efi.inc' main: sub rsp, 4*8 ; reserve space for 4 arguments mov [Handle], rcx ; ImageHandle mov [SystemTable], rdx ; pointer to SystemTable lea rdx, [_hello] mov rcx, [SystemTable] mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut] call [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString] add rsp, 4*8 mov eax, EFI_SUCCESS retn section '.data' data readable writeable Handle dq ? SystemTable dq ? _hello du 'Hello World',13,10,'(From EFI app written in FASM)',13,10,0 section '.reloc' fixups data discardable Compile and link it with fasm.exe hello_world.asm. That's all for now, hope you enjoyed yourselves. * * * ## Comments Continue to [discussion board][18]. You can contact the author using e-mail [vid@x86asm.net][19]. Visit author's [home page][20]. * * * ## Revisions | ----- | | 2009-03-31 | 1.0 | First public version | vid | (dates format correspond to [ISO 8601][21]) [1]: http://x86asm.net/index.html [2]: http://x86asm.net/index.html "Articles section" [3]: http://x86asm.net/others/index.html "Others section" [4]: http://x86asm.net#Getting-the-hardware [5]: http://x86asm.net#Getting-the-software [6]: http://x86asm.net#Booting-into-EFI-Shell [7]: http://x86asm.net#Building-an-UEFI-application [8]: http://x86asm.net#UEFI-Programming [9]: http://x86asm.net#UEFI-Programming-with-FASM [10]: http://x86asm.net#Comments [11]: http://x86asm.net#rev_history "Revision History" [12]: http://x86asm.net/articles/introduction-to-uefi/index.html [13]: http://software.intel.com/en-us/articles/uefi-architecture-and-technical-overview/?cid=sw:iacb011409d [14]: http://www.microsoft.com/whdc/devtools/ddk/default.mspx [15]: http://flatassembler.net [16]: http://x86asm.net/efi.inc [17]: http://x86asm.net/hello.asm [18]: http://board.x86asm.net/viewtopic.php?t=617 [19]: mailto:vid%40x86asm.net [20]: http://fasmlib.x86asm.net [21]: http://www.w3.org/QA/Tips/iso-date#good