Unified Extensible Firmware Interface (UEFI) is a well-established specification that defines a software interface to control the start-up of complex microprocessor systems, including initializing firmware, loading drivers and applications, all the way through to launching an operating system. UEFI on ARM is an excellent solution to control the booting of ARM-based servers and client computing devices, as it is small, fast and offers remote access capabilities.
Porting and debugging this flexible and powerful interface, however, is not a trivial task if you are not well geared for it. This blog shows step-by-step how to build the UEFI software and debug it at source level, including all dynamically loaded modules (DLLs), using the ARM Development Studio 5 (DS-5) toolchain.UEFI has been developed by a community of companies, and ARM has been actively contributing to this initiative for several years. The "Tianocore" project offers Open Source implementations of the UEFI specification, and ARM ports are available to users under the BSD license. In addition, Linaro is maintaining a UEFI tree based on the Tianocore git tree.The toolchains supported for compiling UEFI for ARM include the ARM Compiler (a rebranded version of the RealView Compilation Tools a.k.a RVCT) as supplied in DS-5 Professional and DS-5 Ultimate Editions, GCC and XCode.UEFI is quite a large package of source code, and porting this to your own ARM-based platform can be a daunting task. Thankfully, ready-made example ports for ARM-based systems, BeagleBoard and Samsung Origen are already available. DS-5 Professional and Ultimate Editions provide a complete UEFI development environment that enables you to:
The DS-5 Debugger component in ARM DS-5 toolchain allows you to debug all phases of the UEFI control flow, including SEC (Security - Set up Temp memory), PEI (Pre-EFI - Initialize Memory), DXE (Driver Execution - Dispatch list of UEFI / DXE drivers) and BDS (Boot Device Selection - Run Setup) phases.
First launch DS-5 on a host machine. If you do not have the DS-5 toolchain installed on your machine, you can download a free 30-day evaluation license. Then set up DS-5 for UEFI development as described on GitHub, including creating a debug launch configuration that loads the UEFI firmware RTSM_VE_CORTEX-A9_EFI.fd . Establish a connection to the Cortex-A9x4 software model by double-clicking on your debug launch configuration. After connecting to the model and loading the UEFI firmware, DS-5 Debugger shows you something like this:
DS-5 Debugger screen views after connecting to the model and loading the UEFI firmware
The first few instructions are 32-bit ARM assembler that performs some essential CPU initialization and sets up a basic C run-time environment (e.g. stack). You can single-step through this assembler, but the more interesting C code sections come fairly soon after, compiled as 16-/32-bit Thumb.
UEFI is modular, and is structured as a set of DLLs that are loaded into memory at run time. The addresses at which the modules are loaded are stored in tables in memory. The DLLs are compiled by the ARM Compiler into ELF files containing DWARF debug data. These are converted into PECOFF as required by the UEFI Specification. UEFI loads the PECOFF DLLs at run-time, and DS-5 Debugger loads the DWARF debug data from the ELF files at debug time.
To debug UEFI at source-level, a Jython script is provided in the ArmPlatformPkg. This script (cmd_load_symbols.py) handles all the complexities of searching memory for loaded modules, and calculating offset addresses to load debug information for symbols. In the Command field, enter:
cmd_load_symbols.py
source C:\git\edk2/ArmPlatformPkg/Scripts/Ds5/cmd_load_symbols.py -f (0x08000000,0x00280000) -m (0x80000000,0x40000000) -a
This will load all the debug symbols for the platform with firmware at 0x08000000 and DRAM at 0x80000000, for all currently loaded modules. At this stage, only ArmPlatformSec.dll has been loaded, so only debug symbols for this will be loaded. The full list of symbols for this module then appears in the Functions view, including CEntryPoint. Set a breakpoint on CEntryPoint with: break CEntryPoint
break CEntryPoint
Then run to this breakpoint by clicking on the Continue button (or pressing F8). Program execution will stop at CEntryPoint in Sec.c:
Debugging UEFI's C entry point at source level
Now that debugging information has been loaded into DS-5 Debugger, you can now debug the code in ArmPlatformSec.dll at source level. You can run, stop, set breakpoints, view variables, view registers, view disassembly, view memory, and so on in the normal way.
Resume program execution (Continue/F8), and stop it after a few seconds (Interrupt/F9). UEFI will have started loading other modules, as shown in its serial console output:
The log of UEFi's module loading activity
This output shows which DLLs have been loaded at which addresses, and (as an alternative to using cmd_load_symbols.py) which add-symbol-file command to use to debug them at source level.
DS-5's Jython scripting capabilities provide powerful ways to execute scripts and visualise system states. For example, you can drag'n'drop cmd_load_symbols.py from the file system into the Scripts view. You can also associate parameters with the script, for example, the parameters "-f (0x08000000,0x00280000) -m (0x80000000,0x40000000) -a" mentioned earlier, by clicking on the "(...)" button on the Scripts view menu bar:
Associating parameters with a script in the Scripts view
Having done that, you can then simply double-click on the script in the Scripts view to execute it with its parameters. Debug symbols will now be loaded into DS-5 Debugger for the other loaded modules.To see which DLLs have symbols loaded by the cmd_load_symbols.py script, add the "-v" switch to the list of parameters. You will then see the loaded DLLs listed in the Commands view:
UEFI's loaded DLLs listed in the Commands view
Resume program execution (Continue/F8), and let UEFI continue to load all the modules, and complete its count-down before attempting to launch an OS. UEFI's command-line boot loader output appears in the model's simulated display:
and run to this breakpoint (Continue/F8). Program execution will stop at IrqInterruptHandler in PL390GicDxe.c. You can now single-step through the interrupt handler, with full source-level symbols, and view Variables, Memory, Registers, etc.
Debugging UEFI's interrupt handler
To summarize, the DS-5 toolchain provides a complete UEFI development environment that enables you to fetch, build, download and run the UEFI software, and to debug its loaded DLLs at source-level, using the powerful Jython scripting features of DS-5 Debugger. It is also possible to debug the UEFI Self Certification Test (SCT) with DS-5, using the same Jython scripts.
The Self Certification Test is not easy to debug but, thanks to DS-5, this process can be made much easier. More advanced UEFI implementations are already available, for example, UEFI for the ARMv8 (AArch64) architecture, and DS-5 Debugger is able to debug this too, including its loaded DLLs, using its flexible Jython scripting!
Hi, Theobald:
I am debug UEFI through you provide method, only ArmPlatformSec.dll has loaded, other modules has not loaded, and show following log:
"Add symbols of z:\win10\uefi\edk2\Build\XxxxBoard\DEBUG_ARMGCC\ARM\XxxxBoardPkg\Sec\Sec\DEBUG\ArmPlatformSec.dll at 0x180
Note: no symbols have been found in System Memory (possible cause: the UEFI permanent memory has been installed yet)"
when other modules is loaded? only ArmPlatformSec.dll can source level debug current.