In Episode 12, I wrote a simple “Hello World!” application in ‘C’ using the built-in UEFI shell functions. In Episode 13, I failed in an attempt to re-write that application using standard ‘C’ library functions, such as printf(). I’ve learned a lot since then – here’s how to write more sophisticated programs.
From Episode 12 Writing UEFI Applications, I took a simple ‘C’ program and adapted it to print “Hello World” to the screen.
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
Print(L"Hello World \n");
return EFI_SUCCESS;
}
Note that this program differs from other simple ‘C’ programs in that the entry point is not the familiar main(INT argc, CHAR16 **argv) that I wanted to use to pass in a command line string. Also, it uses the UEFI shell “print” command rather than the “printf” that I am used to.
I decided to start with a program that echoes the command line to the screen, similar to the “echo” shell command.
So, one step at a time. I first wanted to learn how to modify my program to accept command line parameters and manipulate them. I found out that I needed to change the module entry point from “UefiMain” to “ShellAppMain” to pass parameters in on the command line. And, to do that, the HelloWorld.inf file must be updated to have ENTRY_POINT set to ShellCEntryLib, Packages must include ShellPkg/ShellPkg.dec, and LibraryClasses must include ShellCEntryLib. And finally, the DuetPkgX64.dsc file must have the path to ShellCEntryLib explicitly added:
ShellCEntryLib|ShellPkg/Library/UefiShellCEntryLib/UefiShellCEntryLib.inf
So here is a “new and improved” version of MyHelloWorld.c that takes the user input from the command line and echoes it back on the screen:
/**
My Hello World!
**/
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv[]
)
{
int i;
for (i = 1; i < Argc; i++)
Print(L"%s ", Argv[i]);
Print(L"\n");
return EFI_SUCCESS;
}
The MyHelloWorld.inf file that is used within the build is as follows:
## @file
#
#
##
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyHelloWorld
FILE_GUID = 6467c5d1-d0f0-4b47-a6a4-0545624972ef
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = ShellCEntryLib
#
# The following information is for reference only and not required by the build.
#
VALID_ARCHITECTURES = X64
#
[Sources]
MyHelloWorld.c
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
ShellCEntryLib
[Guids]
[Ppis]
[Protocols]
[FeaturePcd]
[Pcd]
And finally, the DuetPkgX64.dsc file which is the driver for the build (that is, it uses the defined source code, packages and library classes to build the application) is as follows:
## @file
# An EFI/Framework Emulation Platform with UEFI HII interface supported.
#
# Developer's UEFI Emulation. DUET provides an EFI/UEFI IA32/X64 environment on legacy BIOS,
# to help developing and debugging native EFI/UEFI drivers.
#
# Copyright (c) 2010 – 2013, Intel Corporation. All rights reserved.<BR>
#
# This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution. The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
##
################################################################################
#
# Defines Section – statements that will be processed to create a Makefile.
#
################################################################################
[Defines]
PLATFORM_NAME = DuetPkg
PLATFORM_GUID = 199E24E0-0989-42aa-87F2-611A8C397E72
PLATFORM_VERSION = 0.4
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/DuetPkgX64
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS = DEBUG
SKUID_IDENTIFIER = DEFAULT
FLASH_DEFINITION = DuetPkg/DuetPkg.fdf
################################################################################
#
# Library Class section – list of all Library Classes needed by this Platform.
#
################################################################################
[LibraryClasses]
#
# Entry point
#
PeimEntryPoint|MdePkg/Library/PeimEntryPoint/PeimEntryPoint.inf
DxeCoreEntryPoint|MdePkg/Library/DxeCoreEntryPoint/DxeCoreEntryPoint.inf
UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
#
# Basic
#
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
SynchronizationLib|MdePkg/Library/BaseSynchronizationLib/BaseSynchronizationLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
CpuLib|MdePkg/Library/BaseCpuLib/BaseCpuLib.inf
IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
PciLib|MdePkg/Library/BasePciLibCf8/BasePciLibCf8.inf
PciCf8Lib|MdePkg/Library/BasePciCf8Lib/BasePciCf8Lib.inf
PciExpressLib|MdePkg/Library/BasePciExpressLib/BasePciExpressLib.inf
CacheMaintenanceLib|MdePkg/Library/BaseCacheMaintenanceLib/BaseCacheMaintenanceLib.inf
PeCoffLib|MdePkg/Library/BasePeCoffLib/BasePeCoffLib.inf
PeCoffGetEntryPointLib|MdePkg/Library/BasePeCoffGetEntryPointLib/BasePeCoffGetEntryPointLib.inf
#
# UEFI & PI
#
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
UefiRuntimeLib|MdePkg/Library/UefiRuntimeLib/UefiRuntimeLib.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
UefiHiiServicesLib|MdeModulePkg/Library/UefiHiiServicesLib/UefiHiiServicesLib.inf
HiiLib|MdeModulePkg/Library/UefiHiiLib/UefiHiiLib.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
UefiDecompressLib|MdePkg/Library/BaseUefiDecompressLib/BaseUefiDecompressLib.inf
DxeServicesLib|MdePkg/Library/DxeServicesLib/DxeServicesLib.inf
DxeServicesTableLib|MdePkg/Library/DxeServicesTableLib/DxeServicesTableLib.inf
UefiCpuLib|UefiCpuPkg/Library/BaseUefiCpuLib/BaseUefiCpuLib.inf
ShellCEntryLib|ShellPkg/Library/UefiShellCEntryLib/UefiShellCEntryLib.inf
#
# Generic Modules
#
UefiUsbLib|MdePkg/Library/UefiUsbLib/UefiUsbLib.inf
UefiScsiLib|MdePkg/Library/UefiScsiLib/UefiScsiLib.inf
OemHookStatusCodeLib|MdeModulePkg/Library/OemHookStatusCodeLibNull/OemHookStatusCodeLibNull.inf
GenericBdsLib|IntelFrameworkModulePkg/Library/GenericBdsLib/GenericBdsLib.inf
SecurityManagementLib|MdeModulePkg/Library/DxeSecurityManagementLib/DxeSecurityManagementLib.inf
CapsuleLib|MdeModulePkg/Library/DxeCapsuleLibNull/DxeCapsuleLibNull.inf
PeCoffExtraActionLib|MdePkg/Library/BasePeCoffExtraActionLibNull/BasePeCoffExtraActionLibNull.inf
CustomizedDisplayLib|MdeModulePkg/Library/CustomizedDisplayLib/CustomizedDisplayLib.inf
#
# Platform
#
PlatformBdsLib|DuetPkg/Library/DuetBdsLib/PlatformBds.inf
TimerLib|DuetPkg/Library/DuetTimerLib/DuetTimerLib.inf
#
# Misc
#
PerformanceLib|MdePkg/Library/BasePerformanceLibNull/BasePerformanceLibNull.inf
DebugAgentLib|MdeModulePkg/Library/DebugAgentLibNull/DebugAgentLibNull.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
HobLib|MdePkg/Library/DxeHobLib/DxeHobLib.inf
ExtractGuidedSectionLib|MdePkg/Library/DxeExtractGuidedSectionLib/DxeExtractGuidedSectionLib.inf
PlatformHookLib|MdeModulePkg/Library/BasePlatformHookLibNull/BasePlatformHookLibNull.inf
SerialPortLib|MdeModulePkg/Library/BaseSerialPortLib16550/BaseSerialPortLib16550.inf
MtrrLib|UefiCpuPkg/Library/MtrrLib/MtrrLib.inf
LockBoxLib|MdeModulePkg/Library/LockBoxNullLib/LockBoxNullLib.inf
CpuExceptionHandlerLib|UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeCpuExceptionHandlerLib.inf
LocalApicLib|UefiCpuPkg/Library/BaseXApicLib/BaseXApicLib.inf
#
# To save size, use NULL library for DebugLib and ReportStatusCodeLib.
# If need status code output, do library instance overriden as below DxeMain.inf does
#
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
ReportStatusCodeLib|MdePkg/Library/BaseReportStatusCodeLibNull/BaseReportStatusCodeLibNull.inf
[LibraryClasses.common.DXE_CORE]
HobLib|MdePkg/Library/DxeCoreHobLib/DxeCoreHobLib.inf
MemoryAllocationLib|MdeModulePkg/Library/DxeCoreMemoryAllocationLib/DxeCoreMemoryAllocationLib.inf
################################################################################
#
# Pcd Section – list of all EDK II PCD Entries defined by this Platform
#
################################################################################
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdReportStatusCodePropertyMask|0x0
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x0
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x0
gEfiMdeModulePkgTokenSpaceGuid.PcdResetOnMemoryTypeInformationChange|FALSE
[PcdsFeatureFlag]
gEfiMdeModulePkgTokenSpaceGuid.PcdTurnOffUsbLegacySupport|TRUE
###################################################################################################
#
# Components Section – list of the modules and components that will be processed by compilation
# tools and the EDK II tools to generate PE32/PE32+/Coff image files.
#
# Note: The EDK II DSC file is not used to specify how compiled binary images get placed
# into firmware volume images. This section is just a list of modules to compile from
# source into UEFI-compliant binaries.
# It is the FDF file that contains information on combining binary files into firmware
# volume images, whose concept is beyond UEFI and is described in PI specification.
# Binary modules do not need to be listed in this section, as they should be
# specified in the FDF file. For example: Shell binary (Shell_Full.efi), FAT binary (Fat.efi),
# Logo (Logo.bmp), and etc.
# There may also be modules listed in this section that are not required in the FDF file,
# When a module listed here is excluded from FDF file, then UEFI-compliant binary will be
# generated for it, but the binary will not be put into any firmware volume.
#
###################################################################################################
[Components]
DuetPkg/DxeIpl/DxeIpl.inf {
<LibraryClasses>
#
# If no following overriden for ReportStatusCodeLib library class,
# All other module can *not* output debug information even they are use not NULL library
# instance for DebugLib and ReportStatusCodeLib
#
ReportStatusCodeLib|MdeModulePkg/Library/DxeReportStatusCodeLib/DxeReportStatusCodeLib.inf
}
MdeModulePkg/Core/Dxe/DxeMain.inf {
#
# Enable debug output for DxeCore module, this is a sample for how to enable debug output
# for a module. If need turn on debug output for other module, please copy following overriden
# PCD and library instance to other module's override section.
#
<PcdsFixedAtBuild>
gEfiMdePkgTokenSpaceGuid.PcdReportStatusCodePropertyMask|0x07
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000042
<LibraryClasses>
DebugLib|IntelFrameworkModulePkg/Library/PeiDxeDebugLibReportStatusCode/PeiDxeDebugLibReportStatusCode.inf
ReportStatusCodeLib|DuetPkg/Library/DxeCoreReportStatusCodeLibFromHob/DxeCoreReportStatusCodeLibFromHob.inf
}
MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
MdeModulePkg/Universal/WatchdogTimerDxe/WatchdogTimer.inf
MdeModulePkg/Core/RuntimeDxe/RuntimeDxe.inf
MdeModulePkg/Universal/MonotonicCounterRuntimeDxe/MonotonicCounterRuntimeDxe.inf
DuetPkg/FSVariable/FSVariable.inf
MdeModulePkg/Universal/CapsuleRuntimeDxe/CapsuleRuntimeDxe.inf
MdeModulePkg/Universal/MemoryTest/NullMemoryTestDxe/NullMemoryTestDxe.inf
MdeModulePkg/Universal/SecurityStubDxe/SecurityStubDxe.inf
MdeModulePkg/Universal/Console/ConPlatformDxe/ConPlatformDxe.inf
MdeModulePkg/Universal/Console/ConSplitterDxe/ConSplitterDxe.inf {
<LibraryClasses>
PcdLib|MdePkg/Library/DxePcdLib/DxePcdLib.inf
}
MdeModulePkg/Universal/HiiDatabaseDxe/HiiDatabaseDxe.inf
MdeModulePkg/Universal/SetupBrowserDxe/SetupBrowserDxe.inf
MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf
MdeModulePkg/Universal/Console/GraphicsConsoleDxe/GraphicsConsoleDxe.inf
MdeModulePkg/Universal/Console/TerminalDxe/TerminalDxe.inf
MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
MdeModulePkg/Universal/SmbiosDxe/SmbiosDxe.inf
DuetPkg/SmbiosGenDxe/SmbiosGen.inf
#DuetPkg/FvbRuntimeService/DUETFwh.inf
DuetPkg/EfiLdr/EfiLdr.inf {
<LibraryClasses>
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
NULL|IntelFrameworkModulePkg/Library/LzmaCustomDecompressLib/LzmaCustomDecompressLib.inf
}
IntelFrameworkModulePkg/Universal/BdsDxe/BdsDxe.inf {
<LibraryClasses>
PcdLib|MdePkg/Library/DxePcdLib/DxePcdLib.inf
}
MdeModulePkg/Universal/EbcDxe/EbcDxe.inf
UefiCpuPkg/CpuIo2Dxe/CpuIo2Dxe.inf
UefiCpuPkg/CpuDxe/CpuDxe.inf
PcAtChipsetPkg/8259InterruptControllerDxe/8259.inf
DuetPkg/AcpiResetDxe/Reset.inf
DuetPkg/LegacyMetronome/Metronome.inf
PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf
PcAtChipsetPkg/8254TimerDxe/8254Timer.inf
DuetPkg/PciRootBridgeNoEnumerationDxe/PciRootBridgeNoEnumeration.inf
DuetPkg/PciBusNoEnumerationDxe/PciBusNoEnumeration.inf
IntelFrameworkModulePkg/Bus/Pci/VgaMiniPortDxe/VgaMiniPortDxe.inf
IntelFrameworkModulePkg/Universal/Console/VgaClassDxe/VgaClassDxe.inf
# IDE/AHCI Support
DuetPkg/SataControllerDxe/SataControllerDxe.inf
MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AtaAtapiPassThru.inf
MdeModulePkg/Bus/Ata/AtaBusDxe/AtaBusDxe.inf
MdeModulePkg/Bus/Scsi/ScsiBusDxe/ScsiBusDxe.inf
MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDiskDxe.inf
# Usb Support
MdeModulePkg/Bus/Pci/UhciDxe/UhciDxe.inf
MdeModulePkg/Bus/Pci/EhciDxe/EhciDxe.inf
MdeModulePkg/Bus/Usb/UsbBusDxe/UsbBusDxe.inf
MdeModulePkg/Bus/Usb/UsbKbDxe/UsbKbDxe.inf
MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassStorageDxe.inf
# ISA Support
PcAtChipsetPkg/IsaAcpiDxe/IsaAcpi.inf
IntelFrameworkModulePkg/Bus/Isa/IsaBusDxe/IsaBusDxe.inf
IntelFrameworkModulePkg/Bus/Isa/IsaSerialDxe/IsaSerialDxe.inf
IntelFrameworkModulePkg/Bus/Isa/Ps2KeyboardDxe/Ps2keyboardDxe.inf
IntelFrameworkModulePkg/Bus/Isa/IsaFloppyDxe/IsaFloppyDxe.inf
MdeModulePkg/Universal/Disk/DiskIoDxe/DiskIoDxe.inf
MdeModulePkg/Universal/Disk/UnicodeCollation/EnglishDxe/EnglishDxe.inf
MdeModulePkg/Universal/Disk/PartitionDxe/PartitionDxe.inf
# Bios Thunk
DuetPkg/BiosVideoThunkDxe/BiosVideo.inf
#
# Sample Application
#
# MdeModulePkg/Application/HelloWorld/HelloWorld.inf
MyHelloWorld/MyHelloWorld.inf
###################################################################################################
#
# BuildOptions Section – Define the module specific tool chain flags that should be used as
# the default flags for a module. These flags are appended to any
# standard flags that are defined by the build process. They can be
# applied for any modules or only those modules with the specific
# module style (EDK or EDKII) specified in [Components] section.
#
###################################################################################################
[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /FAsc /FR$(@R).SBR
Now I’ll go through a detailed step-by-step process description for the build.
I put the ‘C’ source code, MyHelloWorld.inf file and the modified DuetPkgX64.dsc file into a folder entitled MyHelloWorld within the MyWorkSpace folder.
Firstly, launch “Developer Command Prompt for VS2013”.
Navigate (using the “cd” change directory command) to the MyWorkSpace directory that contains all the build files.
Type in “edksetup”.
Type in “build –p MyHelloWorld/DuetPkgX64.dsc”.
The application builds perfectly and echoes the command line arguments out to the screen. For example, if you type:
MyHelloWorld.efi This is a test!
You see the “This is a test!” echoed back on the following line. Pretty cool.
A couple of interesting notes:
On the HDMI monitor I’ve hooked the Minnowboard to, I simply see the text “This is a test!” echoed to the screen. But on the CoolTerm application I’ve got hooked into the serial port, I see the following:
FS0:\> MyHelloWorld.efi This is a test!
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 762CBC00
PDB = c:\myworksspace2\Build\DuetPkgX64\DEBUG_VS2012x86\X64\MyHelloWorld5\MyHelloWorld\DEBUG\MyHelloWorld.pdb
Loading driver at 0x00077CD1000 EntryPoint=0x00077CD12C0 MyHelloWorld.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 762BAC58
InstallProtocolInterface: 752F3136-4E16-4FDC-A22A-E5F46812F4CA 78851848
This is a test!
Also, I’ve noted that the compiler rejects the following:
for (int i = 1; i > Argc; i++)
But, rather, it wants the variable declaration to be in a distinct statement:
int i;
for (i = 1; i > Argc; i++)
I don’t know why that is. Maybe I am using an older version of Visual Studio (VS2013)? That’s something to figure out for another day.
To summarize, in this episode I’ve graduated from creating a simple ‘C’ program that printed “Hello World” to the screen, to actually taking shell command parameters and echoing those out to the terminal. It may seem like a small step, but it really helped me understand how the ‘C’ source and build files interact with each other. This should enable me to write more sophisticated code going forward, and to understand how more complex programs, like drivers, are put together.
For others who might also like to take such a self-taught journey, a good debugger is indispensable. SourcePoint is probably the best UEFI hardware-assisted tool available; read more at the product website page SourcePoint for Intel Platforms.