This is an experiment to make building compiler-rt
for bare metal ARM targets
easy. It bypasses the CMake build system and is much quicker to setup.
This compiler-rt is libc-agnostic, but not freestanding. In other words, it requires you to link a libc into your code, but it will work with any (sane) libc. If you don't want to link against libc, your code has to provide implementations for the following functions:
void abort(void)
(fromstdlib.h
)
You can grab prebuilt packages from the Releases page. Those are deployed from Travis for tagged commits that I deem stable, so you'll usually be fine with the latest one.
Prebuilts are built using binutils-arm-none-eabi
from Ubuntu Trusty
repositories and clang-3.9
from LLVM repositories.
Those are all required:
arm-none-eabi
binutils- GNU make >= 3.81
One of those compilers is required:
arm-none-eabi
GCC- Clang >= 3.9.0
Note that you don't need a libc. If you want to build the armv7em-hf-v5
or
armv7em-hf-dp
targets with GCC your arm-none-eabi
binutils need to be >=
2.26, because gas
lacked VFPv5 support before that. Clang uses its own
assembler, so it doesn't need recent binutils. Other targets are not affected
by this problem.
compiler-rt
is provided as a submodule:
git clone https://github.com/ReservedField/arm-compiler-rt
cd arm-compiler-rt
git submodule init
git submodule update
When invoking make
you need to properly set CC
to point to your cross
compiler. A typical make invocation may look like CC=arm-none-eabi-gcc make
for GCC or CC=clang make
for Clang.
You need to specify CC
everytime you're building something, even though for
brevity it's not explicited in the Building section. The exceptions are of
course clean(-target)
, distclean
and dist
(for the latter, only if
everything's already built).
To build everything just issue make
. To clean the whole build use
make clean
.
You can also build and clean single targets using make target
and
make clean-target
. Supported targets:
armv6m
: ARMv6-M (e.g. Cortex-M0/M0+/M1). Outputs tolib/armv6-m
.armv7m
: ARMv7-M (e.g. Cortex-M3). Outputs tolib/armv7-m
.armv7em-sf
: ARMv7E-M, soft float ABI, no FPU (e.g. Cortex-M4/M7). Outputs tolib/armv7e-m
.armv7em-hf
: ARMv7E-M, hard float ABI, VFPv4 FPU (e.g. Cortex-M4F). Outputs tolib/armv7e-m/fpu
.armv7em-hf-v5
: ARMv7E-M, hard float ABI, single-precision VFPv5 FPU (e.g. Cortex-M7F with SP FPU). Outputs tolib/armv7e-m/fpu-v5
.armv7em-hf-dp
: ARMv7E-M, hard float ABI, double-precision VFPv5 FPU (e.g. Cortex-M7F with DP FPU). Outputs tolib/armv7e-m/fpu-dp
.armv7em
: all ARMv7E-M targets.
Parallel make (-j
option) is supported.
To create a distributable package use make dist
(will output to the dist
directory). To clean everything, including distribs, use make distclean
.
- Nested functions are not supported (we need bare-metal implementations for trampoline builtins).
You're allowed to use this project under the terms of the MIT license. See the
LICENSE
file for the full text.
A note on nested functions: they require trampolines, which means we need to
provide bare-metal implementations for enable_execute_stack.c
and
clear_cache.c
. Those are trivial if no OS is present. People using an RTOS
and nested functions will have to write implementations themselves.
The libc-agnostic build is obtained with a couple tricks. First, we build
with -ffreestanding
. This means that the standard headers will provide a
generic implementation, instead of falling back to the system/libc ones like a
hosted build (the assumption here is that compiler headers come before system
headers in the search path - if that's not true on your system you should fix
your evil ways). Some files do require libc headers (e.g. int_util.c
needs
stdlib.h
for abort()
). For this reason, extremely stripped-down headers are
provided in the include
directory. Since they only define standard prototypes
they're fairly generic. Note that our custom include directory is specified on
the compiler command line, and as such takes precedence over standard search
paths. This means that if your compiler is configured for a libc your libc
headers won't be used (which is what we want).
I'd like to expand on building this compiler-rt with Clang, since it will also expose some issues you might encounter if you start getting your hands dirty and some shortcomings on ARMv6-M.
First off, we're compiling the release_39
branch of compiler-rt, so it's
pretty logical to use Clang >= 3.9.0. In particular, the build will fail with
older versions because they lack ARM EHABI exceptions support
in unwind.h
, causing compile errors in the gcc_personality_v0.c
shipped
with compiler-rt release_39
(which supports them).
Another problem with older Clang versions is that __ELF__
is not defined
for arm-none-eabi
targets, resulting in compiler-rt/lib/builtins/assembly.h
generating incorrect assembler directives for the SYMBOL_IS_FUNC
macro. This
can of course be worked around by passing -D__ELF__
to the compiler.
Another thing you should be aware of is that relocation for 16-bit ARM branches
was only recently added (it's not even available in 3.9.0). This is relevant
because some handwritten assembly __aeabi
functions do branches to global
symbols. On ARMv7(E)-M you'll get 32-bit branches with 24-bit relocations. On
ARMv6-M there's no 32-bit branch, so you'll get a 16-bit branches with 11-bit
relocations. Clang didn't implement this relocation (GCC did), so it produced
an unsupported relocation on symbol
for those branches. Now, I removed
those __aeabi
functions from ARMv6-M targets in our compiler-rt for a couple
reasons. First, the compiler-rt CMake lists don't compile them for ARMv6-M.
Second, they'd require (at the time of writing) a bleeding-edge version of
Clang just to support ARMv6-M targets. That being said, this is not yet
satisfactory, because even if their absence won't be a problem for Clang (it
doesn't seem to emit calls to them) they're required by the ARM RTABI. The
catch here is that simply compiling them for ARMv6-M (resulting in 16-bit
branches) is not a good idea even if Clang can generate the 11-bit
relocation, because the ELF for ARM specification doesn't require linkers to
generate veeners for 16-bit branches (while it does for 32-bit ones). Hitting
an out-of-range branch at link time is never nice. The real solution is an
ARMv6-M forwarding for those functions (using BL
, as its 32-bit form is
supported, or a litpool).