This article marks the start of a new column on the Phoenix Developer Network called BIOS Undercover. In this column we take a BIOS feature, show you how it works and how to use it in real-life. - Tim
Most of us have lines and lines of C/C++ source code sitting around. And some of it would be really useful running under UEFI. But the public implementations of UEFI (such as tianocore's ) don’t support the standard C library, or main(), or the C++ operators, such as new and delete.
So, starting this week, we’ve put together a series of articles which describe what is involved in C and C++ support under UEFI and how you can use the Phoenix C/C++ libraries to port over your programs to UEFI. Part 1 looks at how the entry point is handled. Part 2 looks at how C and C++ language features are supported. Part 3 looks at the Phoenix C/C++ libraries and how to build using them.
Under Windows and Linux, the compiler, linker and vendor-supplied libraries work together to create the environment expected by standard C or C++ programs. This environment usually breaks down into three categories:
- Pre-Entry Point Initialization. Prior to jumping to main(), the linker and the libraries initialize global variables, construct static objects, initialize portions of the library and create the argc and argv parameters.
- Language-Feature Support. In C++, new and delete usually generate calls to library functions. In C and C++, support for certain data types, such as floating-point and 64-bit integers usually generate calls to library functions.
- Optimization. In order to save space, some compilers generate calls to high-performance library routines for such common behaviors as buffer initialization and copying.
We’re going to look at each of these categories in detail and then show you how to bring it all together using the Phoenix C/C++ libraries.
Entry Points
The entry point for a typical UEFI sample driver usually looks something like this:
EFI_STATUS
EFIAPI
MyDriverInit (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EfiInitializeDriverLib (ImageHandle, SystemTable);
...
return EFI_SUCCESS;
}
From a UEFI perspective, here is what is happening:
-
In UEFI, executables are called images. Executable images are loaded into memory using the boot service LoadImage() and then their entry point is called using the boot service StartImage(). StartImage() calls the executable image’s entry point, which, in this case, is MyDriverInit().
- MyDriverInit() then calls EfiInitializeDriverlib() to initialize the library services.
- When EfiInitializeDriverLib() is finished, it returns back to MyDriverInit(), which then finishes its initialization and exits, either with EFI_SUCCESS (no error) or some other error code.
- If an error is returned to StartImage(), the driver is unloaded from memory.
So, how does UEFI (or any operating system) find the entry point of an application? UEFI applications and drivers use the same file format (PE/COFF) specification which Microsoft uses for all Windows products. You can download the specification and look at all of the gory details here . The PE/COFF file header contains several fields which describe how to decode and load the executable image into memory. One of the fields (AddressOfEntryPoint in the Optional Header, see section 3.4.1 of the PE/COFF specification) contains the relative address of the entry point.
The linker usually sets the entry point based on the type of file. For example, Windows console applications default to the function name [w]mainCRTStartup, GUI applications default to [w]WinMainCRTStartup and dynamic-load libraries default to _DllMainCRTStartup. The name can be changed using the /ENTRY: linker parameter.
Hold on. mainCRTStartup? _DllMainCRTStartup? That’s not main(). Exactly! mainCRTStartup and _DllMainCRTStartup are the actual entry points in the library that ships with the compiler. After the library has finished doing all of its pre-main() initialization, then it calls main() function. When the main() function ends, it returns to the library to handle cleanup.
The following diagram shows the typical flow for C/C++ applications:
- In UEFI, StartImage() calls the executable’s entry point, which is actually found in the C/C++ library.
- mainCRTStartup (or _DllCRTStartup) initializes the global variables and library services and then calls main().
- main()performs its initialization and then returns with its status.
- The library routine frees any memory allocated and then returns to StartImage(). If an error is returned to StartImage(), the driver is unloaded from memory.
So, why not use the standard C/C++ entry point? Well, there are a couple of reasons, both practical and historical:
- This is what is documented in the UEFI specification (see chapter 4.1 or the example in chapter 4.7.1).
- This doesn’t require any real support from the C/C++ compiler or linker, other than the ability to change the name of the driver’s entry point. All of the library initialization is handled by the first function call to EfiInitializeDriverLib.
- UEFI drivers don’t usually care about command-line parameters, so the standard main parameters (e.g. argv and argc) aren’t so useful.
But, could we use the standard C/C++ entry point for UEFI drivers and applications? Yes! It just requires a library which provides the mainCRTStartup function. For another example, you can see this excellent article on MSDN.
Changing The Entry Point
So how does this really work? Let’s start with the simplest example:
int
main() {
return 0;
}
int
mainCRTStartup()
return main();
}
Here’s what you’ll see when you compile (with Visual Studio 2005):
C:\temp>cl x.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
x.c
Microsoft (R) Incremental Linker Version 8.00.50727.762
Copyright (C) Microsoft Corporation. All rights reserved.
/out:x.exe
x.obj
This ends up creating a small executable:
C:\temp>dir x.exe
Volume in drive C has no label.
Volume Serial Number is E484-07D0
Directory of C:\temp
09/02/2008 04:52 PM 1,024 x.exe
1 File(s) 1,024 bytes
0 Dir(s) 15,762,825,216 bytes free
What exactly is in this file? Well, as mentioned before, a lot of headers, and then some code. Here’s the header information, as dumped by the Microsoft utility dumpbin:
C:\temp>dumpbin /headers x.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file x.exe
PE signature found
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (x86)
1 number of sections
48BDD1C8 time date stamp Tue Sep 02 16:52:40 2008
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
103 characteristics
Relocations stripped
Executable
32 bit word machine
OPTIONAL HEADER VALUES
10B magic # (PE32)
8.00 linker version
200 size of code
0 size of initialized data
0 size of uninitialized data
1010 entry point (00401010)
1000 base of code
2000 base of data
400000 image base (00400000 to 00401FFF)
1000 section alignment
200 file alignment
4.00 operating system version
0.00 image version
4.00 subsystem version
0 Win32 version
2000 size of image
200 size of headers
0 checksum
3 subsystem (Windows CUI)
400 DLL characteristics
No structured exception handler
100000 size of stack reserve
1000 size of stack commit
100000 size of heap reserve
1000 size of heap commit
0 loader flags
10 number of directories
0 [ 0] RVA [size] of Export Directory
0 [ 0] RVA [size] of Import Directory
0 [ 0] RVA [size] of Resource Directory
0 [ 0] RVA [size] of Exception Directory
0 [ 0] RVA [size] of Certificates Directory
0 [ 0] RVA [size] of Base Relocation Directory
0 [ 0] RVA [size] of Debug Directory
0 [ 0] RVA [size] of Architecture Directory
0 [ 0] RVA [size] of Global Pointer Directory
0 [ 0] RVA [size] of Thread Storage Directory
0 [ 0] RVA [size] of Load Configuration Directory
0 [ 0] RVA [size] of Bound Import Directory
0 [ 0] RVA [size] of Import Address Table Directory
0 [ 0] RVA [size] of Delay Import Directory
0 [ 0] RVA [size] of COM Descriptor Directory
0 [ 0] RVA [size] of Reserved Directory
You can see that the entry point is pointing at the logical address 401010. So, what’s at that address? We can look by using dumpbin again; this time to disassemble the file. Here is the actually assembly language from the entire program:
C:\temp>dumpbin /disasm x.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file x.exe
File Type: EXECUTABLE IMAGE
00401000: 55 push ebp
00401001: 8B EC mov ebp,esp
00401003: 33 C0 xor eax,eax
00401005: 5D pop ebp
00401006: C3 ret
00401007: CC int 3
00401008: CC int 3
00401009: CC int 3
0040100A: CC int 3
0040100B: CC int 3
0040100C: CC int 3
0040100D: CC int 3
0040100E: CC int 3
0040100F: CC int 3
00401010: 55 push ebp
00401011: 8B EC mov ebp,esp
00401013: E8 E8 FF FF FF call 00401000
00401018: 5D pop ebp
00401019: C3 ret
The entrypoint _mainCRTStartup() is at offset 0x00401010. It then jumps to main() at offet 0x00401000.
Next Week
Next week, in part 2, we'll look at how the C/C++ library entry point actually works.



Comments