Usually when you create a bare-metal image you specify the location in memory where the code and data will reside, and provide an entry point address where execution starts.
But what if you don't want to specify a fixed memory location at build time?
Security has become a crucial aspect of applications. One common attack to gain privilege on a system is through buffer overflows: this anomaly could potentially lead to the execution of malicious code, jeopardizing the security of the entire system through code injection.
Different techniques are used to make a hacker's life harder, including randomizing the address space layout (ASLR). This technique is widely used in several high-level Operating Systems like Android, iOS, Linux and Windows.
With ARM Compiler 6 you can extend this protection to bare-metal applications by creating Position Independent Executables (PIE), also known as Position Independent Code (PIC). A PIE is an executable that does not use fixed addresses to access memory. Rather, it can be executed at any suitably aligned address and the code automatically recalculates the required addresses.
ARM Compiler 6 provides the -fbare-metal-pie (armclang) and --bare_metal_pie (armlink) options to let you create a bare-metal PIE:
-fbare-metal-pie
armclang
--bare_metal_pie
armlink
armclang -fbare-metal-pie -target armv8a-arm-none-eabi source.c armlink --bare_metal_pie source.o
Note: armclang automatically passes the --bare_metal_pie option to armlink when you compile with -fbare-metal-pie.
-fbare-metal-pie.
Note: Bare-metal PIE is currently only supported for 32-bit targets.
Let's take a look at how this works in practice.
This example creates a very simple "Hello World" program in DS-5, uses ARM Compiler 6 to create a PIE, then uses the DS-5 Debugger and the AEMv8-A model to run the executable at an arbitrary position in memory.
PIEdemo
Add a new source file pie.c to the new project (right-click the project, then click New > Source File) with the following content:
pie.c
#include <stdio.h> const char *myString = "Hello World\n";int main() { puts(myString); return 0;}
Add the following command-line options:
armv8a-arm-none-eabi
-fbare-metal-pie -mfpu=none
Add the model parameter: -C cluster.cpu0.CONFIG64=0. This puts the model in AArch32 state, rather than the default AArch64 state.
-C cluster.cpu0.CONFIG64=0
On the Debugger tab, select Run control: connect only.
We want to load the image manually so that we can specify the load address.
Load the PIE by running the following command on the Commands tab:
loadfile PIEdemo/Debug/PIEdemo.axf 0x80000044
This loads the PIE at the arbitrary address 0x80000044, performs all necessary address relocations, and automatically sets the entry point:
0x80000044
Note: You can choose any address, but it must be suitably aligned and at a valid location in the AEMv8-A memory map. For more information about the AEMv8-A memory map, see AEMv8-A Base Platform - memory - map in the Fast Models Reference Manual
Note: You can ignore the TAB180 error for the purposes of this tutorial. For more information, see ARM Compiler 6: Bare-metal Hello World C using the ARMv8 model | ARM DS-5 Development Studio.
TAB180
Execute the PIE by running the following command on the Commands tab:
run
Check the Target Console tab to see the program output:
Position independent code uses PC-relative addressing modes where possible and otherwise accesses global data indirectly, via the Global Offset Table (GOT). When code needs to access global data it uses the GOT as follows:
We'll see this process in action later.
At link time, the linker does the following:
0x00000000
.preinit_array
Image$$StartOfFirstExecRegion
At execution time:
__arm_preinit_
__arm_relocate_pie
Our example from earlier contains the global string "Hello world". Let's see how relocation is used in the PIE to access this data regardless of where the image is loaded.
In the Project Explorer view, double-click on the .axf executable to see the sections it contains:
.axf
We can see that the GOT is located at address 0x00000EE0 in the original image.
0x00000EE0
Now load the image to address 0x80000044 by running the following command on the Commands tab:
Use the Disassembly view to view address 0x80000F24 (0x80000044 + 0x00000EE0). We can see that the GOT has been loaded, but it still contains unrelocated addresses:
0x80000F24
Now, set a breakpoint on main() and run the executable. This executes the setup code, including __arm_relocate_pie which relocates the addresses in the GOT. Run the following commands on the Commands tab:
main()
b mainrun
b main
Look at the GOT again, and note that the addresses have been relocated:
Now we'll see how the code uses the GOT to access the "Hello World" string.
Step to the next source instruction by running the following command on the Commands tab:
next
Jump to address $pc in the Disassembly view to view the code in main():
$pc
The code to print "Hello World" starts at 0x800000E4 and does the following:
0x800000E4
0xC
0x80000118
0xE30
0x800000F4
0xEE0
0x80000F30
0x80000F68
puts
You can single-step through the code and use the Registers view to see this working in DS-5 Debugger.
It is a very useful article for building PIE program.
However, if i need the semihosting supported, such as using fopen(), I know it has to be set the trap address.
How do I set the trap address of semihosting, and where can I get the trap address?