This continues our series about how a C/C++ application can be moved over to UEFI using Phoenix's libraries. In this week, we dig a little deeper into how the C library entry point actually works.
In the next few sections, we’re going to look at library source code which implements mainCRTStartup. This code is for UEFI and DXE boot service drivers only (not UEFI runtime drivers or UEFI applications).
This library entry point has six main tasks:
- Initialize global variables
- Initialize library services (and construct static C++ objects)
- Prepare command-line arguments
- Call the entry point
- Clean up library services (and destruct static C++ objects)
- Return status to the caller.
Not all of these steps are necessary for every type of UEFI executable. For example, command-line arguments are only really used for UEFI shell applications. Also, the Phoenix libraries use a slightly different name for the entry point, depending on the type, but more on that later. We'll look at the UEFI (non-shell) application as an example:
EXTERN_C
int
wmain();
EFI_HANDLE gImageHandle;
EFI_SYSTEM_TABLE *gST;
EFI_BOOT_SERVICES *gBS;
EFI_RUNTIME_SERVICES *gRT;
EFI_LOADED_IMAGE_PROTOCOL *gLoadedImage;
EFI_GUID gEfiLoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
#ifdef _EFI_DXE_DRIVER
EFI_DXE_SERVICES *gDS;
EFI_GUID gEfiDxeServicesTableGuid = EFI_DXE_SERVICES_TABLE_GUID;
#endif
This portion sets up the global variables used by the libraries and the actual driver. For DXE drivers there is one additional global variable gDS, which points to the DXE Services.
EXTERN_C
EFI_STATUS
EFIAPI
mainCRTStartup(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS status;
gImageHandle = ImageHandle;
gST = SystemTable;
gBS = gST->BootServices;
gRT = gST->RuntimeServices;
#ifdef _EFI_DXE_DRIVER
gDS=(EFI_DXE_SERVICES*)EfiGetSystemConfigurationTable(&gEfiDxeServicesTableGuid);
#endif
gBS->HandleProtocol ( // Retrieve LoadedImageProtcol
ImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID**) &gLoadedImage
);
LoadedImage->Unload=(EFI_IMAGE_UNLOAD)CleanUp; // Clean up for Exit()
Initialize(); // Initialize library/static objs
status = wmain(); // Main entry point
if (status != EFI_SUCCESS) { // If error, clean up
CleanUp(ImageHandle);
}
return status;
}
Initialization of library services and construction of static C++ objects is handled by the Initialize() function . Clean up of library services and destruction of static C++ objects is handled by the CleanUp() function.
void
Initialize()
{
_initterm( __xi_a, __xi_z );
_initterm( __xc_a, __xc_z );
}
void
Shutdown()
{
_initterm( __xp_a, __xp_z );
_initterm( __xt_a, __xt_z );
}
void while ( &fStart[i] < fEnd ) { #pragma comment(linker, "/merge:.CRT=.rdata") The Initialize() function calls all of the non-NULL function pointers in the list from __xi_a to __xi_z and from __xc_a to __xc_z. Likewise the Shutdown() function calls all of the non-NULL function pointers in the list from __xp_a to __xp_z and from __xt_a to __xt_z. Wait! __xi_a and __xi_z are defined right next to each other. How do you get a function pointer in there? Well, look at the following piece of code from the C libraries (init_libc.cpp): _CRTALLOC(".CRT$XID") static _PIFV pinit = InitializeLibC; At this point, we need to explain a little bit about how linkers work. Normally, when processing all of the sections to create the final executable, the linker concatenates all the data from identically named sections. That’s why .text from one OBJ file and .text from another .OBJ file end up adjacent in the EXE. But $ in a section name changes the game. The linker treats the portion of the name preceding the $ as the final section name. So, .CRT$XID, .CRT$XIA, and .CRT$XIZ all end up together in a section called .CRT. So let’s say we use one of the C standard library file functions, such as fopen() in our program. As part of the library’s initialization for a single executable, it needs to set up the list of standard file handles (such as the ones for stdin, stdout and stderr). However, it would be a waste of space if no one is using the file functions to perform this initialization. So, the source file which implements fopen() inserts a pointer to InitializeLibC in the section named .CRT$XID, which is between .CRT$XIA and .CRT$XIZ. In this way, if the file functions are used, the pointer is added. If the file functions are not used, the pointer is not added. .CRT Calling the constructors of C++ objects which are declared statically is similar. The pointer to the constructor is placed between .CRT$XCA and .CRT$XCZ. Library shutdown routines and destructors for statically declared C++ objects are similar. Just change the segment names. One final note before we move on: the #pragma comment(linker, "/merge:.CRT=.rdata") statement cases the linker to act as if “/merge:.CRT=.rdata” were typed on its command-line when the object file is finally linked. In this case, the /merge option causes the .CRT section to be combined together with the .rdata section in the final executable image. The .rdata section typically contains read-only, initialized data.
Next week, we'll look at the other ways that the C/C++ library supports the compiler, including 64-bit support, security features, debug add-ons, performance monitoring and special C++ operators.
_initterm (
void(_cdecl *fStart[])(void),
void(_cdecl *fEnd[])(void)
)
{
int i = 0;
if ( fStart == NULL || fEnd == NULL ) {
return;
}
if ( fStart[i] != NULL ) {
(*fStart[i])();
}
i++;
}
}
_CRTALLOC(".CRT$XIA") _PIFV __xi_a[] = { NULL };
_CRTALLOC(".CRT$XIZ") _PIFV __xi_z[] = { NULL };
_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { NULL };
_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { NULL };
_CRTALLOC(".CRT$XPA") _PVFV __xp_a[] = { NULL };
_CRTALLOC(".CRT$XPZ") _PVFV __xp_z[] = { NULL };
_CRTALLOC(".CRT$XTA") _PVFV __xt_a[] = { NULL };
_CRTALLOC(".CRT$XTZ") _PVFV __xt_z[] = { NULL };
Now, what in the world is going on with all of those strange declarations at the end? Well, for the Visual C++ compiler, these actually create named segments.
_CRTALLOC(".CRT$XTB") static _PVFV pterm = _LIBC_Cleanup;
.CRT$XID? .CRT$XTB? Why the funny section names? It's a bit complicated. Notice that these section names are very similar to .CRT$XIA/.CRT$XIB and .CRT$XTA/.CRT$XTB pairs.
Ok. What about the XID, XIA and XIZ part of the section name? What happens to them? Well, the linker sorts the sections alphabetically based on the parts of the section name after the $, so that all of the pieces in $XIA will be first, $XIB will be second and so on, with $XIZ appearing last.
So the final .CRT section will look like this:
__xi_a:
DD 0
pinit:
DD OFFSET _InitializeLibC
__xi_z:
DD 0Next Week



Comments