Getting started#
This page takes you from a source checkout to running programs: building EmbCC and its libraries, checking the build, installing it, compiling a first program for the host, and cross-compiling for each supported board and running the result under QEMU. It is for people who are new to EmbCC. The command-line reference is Invoking EmbCC. A release installs without a source checkout; see Installing.
All commands on this page are run from the top of the EmbCC source tree unless they say otherwise.
Host requirements#
EmbCC builds and runs on macOS and Linux. To build the compiler you need:
- a C99 compiler, run as
cc(setCC=to use another); - GNU
make.
Nothing else is needed for embcc itself. The libraries, the test suites
and running cross-compiled programs need further tools:
| Tool | Needed for |
|---|---|
x86_64-elf-ar (or set EMBCC_X86_AR) |
the x86-64 C and C++ libraries (libc-x86_64, libc-linux-x86_64, libcxx-x86_64, libcxx-linux-x86_64, libc-emblinkos) |
aarch64-elf-ar (or set EMBCC_AARCH64_AR) |
the AArch64 C and C++ libraries (libc-aarch64, libc-linux-aarch64, libcxx-aarch64, libcxx-linux-aarch64) |
llvm-ar, or the system ar (or set EMBCC_AR) |
the embedded compiler runtimes (rt-embedded) |
qemu-system-arm, qemu-system-riscv32, qemu-system-riscv64, qemu-system-avr, qemu-system-aarch64, qemu-system-x86_64 |
running cross-compiled programs and the test suites |
x86_64-elf-gcc, x86_64-elf-ld, x86_64-elf-objcopy and a newlib built for x86_64-elf |
make check and make test on any host other than Linux x86-64 |
aarch64-elf-gcc, aarch64-elf-ld and a newlib built for aarch64-elf |
make test-arm64 |
llvm-mc, clang |
the parts of some golden tests that use them as a reference |
A golden test whose tools are missing reports SKIP with the reason; it
is never counted as a pass.
Building from source#
Build the toolchain with:
make
This compiles the sources into build/, links ./embcc and the other
tools at the top of the tree, and builds every target's runtime and C
library, so that a program links for the host (x86_64-linux-gnu on a
Linux PC, which is the first thing CMake's compiler check does) and
firmware links for every board. make -j8 runs it in parallel. To build
for some targets only, see Choosing the targets.
Each piece is also a target of its own:
| Target | Builds |
|---|---|
make embcc |
the compiler driver, with the integrated linker inside it |
make embld |
the linker, embld, needed to link firmware |
make embas |
the NASM-syntax x86-64 assembler, embas |
make embread |
the EMBX image reader, embread |
make embls |
the language server, embls |
make embidx |
the cross-unit index, embidx |
make embdbg |
the debugger, embdbg |
make all |
every tool, and no runtime |
make runtimes |
every target's runtime and C library (make libc-linux-x86_64 and the others, one target each) |
make clean |
removes build/ and the tool binaries other than embidx |
The build can be adjusted with these make variables:
| Variable | Default | Meaning |
|---|---|---|
CC |
cc |
the host C compiler |
CFLAGS |
-std=c99 -Wall -Wextra -Werror -g |
flags for the host compiler |
BUILD |
build |
the object directory |
TARGETS |
all |
the target families to build; see Choosing the targets |
DEFAULT_TARGET |
empty (x86_64-elf) |
the target embcc uses when given no --target= |
make CC=clang BUILD=/tmp/embcc-clang /tmp/embcc-clang/embcc builds a
separate compiler entirely inside /tmp/embcc-clang, leaving ./embcc
untouched.
Every object depends on every header under src/, so after editing one
make rebuilds the whole compiler.
Choosing the targets#
By default EmbCC is built for every target it supports, and so are its runtimes. To build only the targets you use, as GCC is configured for one target, name their families:
make TARGETS="arm riscv" # or TARGETS=arm,riscv
That embcc contains the ARM and RISC-V backends and no other. The other
families' sources are not compiled, so the build is faster and the binary
smaller (a one-family embcc is a little over half the size of the full
one). make runtimes, a plain make and make install build and install
the runtimes and C libraries of those families' triples only. The
families are the files of the target database, src/targets/:
| Family | Targets |
|---|---|
x86 |
x86-64: x86_64-elf, x86_64-linux-gnu, x86_64-apple-darwin, x86_64-windows-gnu, EmbLinkOS |
aarch64 |
AArch64: aarch64-elf, aarch64-linux-gnu, aarch64-apple-darwin, EmbLinkOS |
arm |
Cortex-M (thumbv6m, thumbv7m, thumbv7em, thumbv8m.base, thumbv8m.main, with their -eabihf forms) and ARMv7-A (armv7a-none-eabi) |
riscv |
riscv32-unknown-elf, riscv64-unknown-elf |
avr |
avr (ATmega) |
mips |
mipsel-none-elf, mips-none-elf, mips64el-none-elf, mips64-none-elf |
loongarch |
loongarch64-unknown-elf |
tricore |
tricore-none-elf |
xtensa |
xtensa-none-elf (ESP32) |
ppc |
powerpc-none-eabi |
rx |
rx-none-elf |
sparc |
sparc-none-elf (LEON3) |
m68k |
m68k-none-elf (ColdFire) |
TARGETS=all is every family. The names of the backend directories are
accepted for their family too: thumb for arm, coldfire for m68k,
x86_64 for x86, riscv32 and riscv64 for riscv. An unknown name
stops make with the list of families.
embcc --version lists the families a compiler has. A target from another
family is refused before anything is compiled, with the families it has
and the TARGETS= that adds the missing one:
$ ./embcc --target=riscv32-unknown-elf -c main.c
embcc: error: this embcc was built without the riscv backend, which riscv32-unknown-elf needs
embcc: it was built with: arm
embcc: rebuild with make TARGETS="arm riscv" to add it (docs/manual/getting-started.md, "Choosing the targets")
The linker in such a compiler likewise refuses another family's objects.
A compiler built without x86 uses its first family's target when given
no --target= (thumbv7m-none-eabi for TARGETS=arm), unless
DEFAULT_TARGET names another; see the next section.
A subset's objects go to a directory of their own,
build/targets-arm-riscv, so switching between TARGETS= values rebuilds
only what that selection has not built yet. make check and tests/run.sh
report the x86-64 suite as skipped when the compiler has no x86.
To remember the choice, as GCC's configure does, run ./configure
once. It writes config.mk, which every later make reads:
./configure --targets=arm,riscv --prefix=/opt/embcc
make
make install
./configure also takes --default-target=TRIPLE (the same as
DEFAULT_TARGET). A variable on the make command line still overrides
config.mk, and make distclean removes it.
Choosing the default target#
A compiler that is used mostly for one board can be built to target it
when no --target= is given:
make clean
make DEFAULT_TARGET=riscv32-unknown-elf embcc
./embcc --print-target # riscv32-unknown-elf
./embcc -c main.c -o main.o # compiled for RV32
The binary still contains every backend that TARGETS names (all of
them by default), and --target= still selects any of them. Run make clean first: the value is compiled into one object, and make does not
rebuild objects when only a variable changes.
The environment variable EMBCC_DEFAULT_TARGET overrides the built-in
default for one shell, and --target= overrides both. See
The default target.
Building the libraries#
EmbCC's C library (lib/libc), C++ runtime (lib/libcxx) and compiler
runtime (lib/rt) are compiled by the ./embcc just built. Each target
below builds embcc first if it is out of date.
| Target | Produces | For --target= |
|---|---|---|
libc-x86_64 |
build/libc/x86_64/libc.a |
x86_64-elf |
libc-aarch64 |
build/libc/aarch64/libc.a |
aarch64-elf |
libc |
both of the above | |
libc-linux-x86_64 |
build/libc/linux-x86_64/libc.a, librt.a, crt1.o |
x86_64-linux-gnu |
libc-linux-aarch64 |
build/libc/linux-aarch64/libc.a, librt.a, crt1.o |
aarch64-linux-gnu |
libc-linux |
both of the above | |
libcxx-x86_64, libcxx-aarch64 |
build/libcxx/x86_64/libcxx.a, build/libcxx/aarch64/libcxx.a |
x86_64-elf, aarch64-elf |
libcxx-linux-x86_64, libcxx-linux-aarch64 |
build/libcxx/linux-x86_64/libcxx.a, build/libcxx/linux-aarch64/libcxx.a |
the -linux-gnu targets |
libcxx |
libcxx-x86_64 and libcxx-aarch64 |
|
libc-linux-all |
libc-linux and both Linux C++ runtimes |
|
rt-embedded |
build/libc/TRIPLE/librt.a for avr, thumbv7m-none-eabi, thumbv7em-none-eabi, thumbv7em-none-eabihf, thumbv8m.main-none-eabi, thumbv8m.main-none-eabihf and riscv32-unknown-elf |
the embedded targets |
libc-embedded |
build/libc/TRIPLE/libc.a for thumbv7m-none-eabi, thumbv7em-none-eabi, thumbv7em-none-eabihf, thumbv8m.main-none-eabi, thumbv8m.main-none-eabihf, riscv32-unknown-elf and riscv64-unknown-elf |
the embedded targets, with no operating system |
libc-emblinkos |
build/libc/emblinkos/libc.a |
EmbLinkOS; needs the EmbLinkOS source tree (make libc-emblinkos EMBLINKOS=/path/to/EmbLinkOs, default $HOME/EmbLinkOs) |
There is no compiler runtime for riscv64-unknown-elf. The libraries and
what they contain are described in Libraries.
make runtimes, rt-embedded, libc-embedded and libcxx-embedded
build only the triples of the families TARGETS names, and the x86-64 and
AArch64 libraries only when x86 or aarch64 is one of them. A target
named explicitly, such as make libc-x86_64, is still attempted, and the
compiler refuses it if it was built without that family.
Checking the build#
| Target | Runs | Builds first |
|---|---|---|
make check |
every program in tests/exec and tests/cxx, compiled, linked and run; the quick check while working on the compiler |
embcc, libc-x86_64, libcxx-x86_64 |
make test |
the full suite: the programs above plus every golden test | embcc, embread, embld, embdbg, embls, embas, the x86-64 and x86-64 Linux C and C++ libraries |
make test-arm64 |
the suite for aarch64-elf, run under qemu-system-aarch64 |
embcc and the AArch64 C and C++ libraries |
make test-libstdcxx |
GCC's libstdc++ built by EmbCC, and the C++ tests linked against it, for both targets; takes several minutes | embcc |
The test programs run on the architecture they were compiled for. For
x86_64-elf, on a Linux x86-64 host they are linked with the host cc
and run directly; on any other host they are linked into a Multiboot image
against a cross newlib and booted under qemu-system-x86_64.
EMBCC_X86_RUNNER=host or EMBCC_X86_RUNNER=qemu forces one or the
other. The cross newlib is looked for in ~/cross/newlib-c99/x86_64-elf
(EMBCC_X86_NEWLIB) and ~/cross/newlib-aarch64-c99/aarch64-elf
(EMBCC_AARCH64_NEWLIB).
One golden test uses embidx, which make test does not build; run
make embidx first, or that test reports that it was skipped. The test
suite is described in Testing.
Installing#
EmbCC installs from a release, with the install script, Homebrew or a
.deb package, or from a source checkout with make install. Each puts
the same files into a prefix, as described in
The installed layout. Releases are built for
Linux and macOS, on x86-64 and arm64.
With the install script#
curl -fsSL https://raw.githubusercontent.com/EmbLinkOs/EmbCC/main/install.sh | sh
The script downloads the latest release for the machine's OS and
processor from GitHub Releases, checks it against its SHA-256 file, and
copies it into $HOME/.local, or into /usr/local when it runs as root.
It never runs sudo. When the prefix's bin directory is not on PATH,
it prints the line to add to the shell's startup file. It needs curl or
wget, tar, and sha256sum or shasum, and runs under any POSIX
shell.
GitHub never counts a pre-release as the latest release, so while every
release is one (0.0.1-beta is the first), the script needs
--version v0.0.1-beta, as in the example below.
Options follow sh -s --:
curl -fsSL https://raw.githubusercontent.com/EmbLinkOs/EmbCC/main/install.sh |
sh -s -- --prefix /opt/embcc --version v0.0.1-beta
| Option | Meaning |
|---|---|
--prefix DIR |
install into DIR/bin, DIR/lib/embcc and DIR/share |
--version TAG |
install the release TAG rather than the latest one |
--from-source |
download the release's source and build it with cc and GNU make; with it, --version main builds the main branch |
--help |
list the options |
A tarball that does not match its checksum stops the script before anything is written. Where no release is built for the machine, or the one there is does not run (the Linux programs are built against Ubuntu 22.04's glibc, and do not run with an older one or with musl), the script says so and builds from source instead, which takes a few minutes. Installing a version again replaces it.
With Homebrew#
The tap, EmbLinkOs/homebrew-embcc, is not published yet; until it is,
the formula is in packaging/homebrew/embcc.rb. Once it is:
brew install EmbLinkOs/embcc/embcc
Homebrew builds EmbCC from the release's source, which takes a minute or
two, and links embcc and the other tools into its bin directory.
brew install --HEAD EmbLinkOs/embcc/embcc builds the main branch.
From a .deb package#
Each release has a .deb for Debian and Ubuntu on x86-64 (amd64) and
arm64. It installs into /usr:
sudo apt install ./embcc_VERSION_amd64.deb
From a tarball#
Each release has a tarball for each OS and processor,
embcc-VERSION-OS-ARCH.tar.gz (OS is linux or macos, ARCH is
x86_64 or aarch64), and its SHA-256 file. It unpacks into one
directory, which works wherever it is put:
shasum -a 256 -c embcc-VERSION-macos-aarch64.tar.gz.sha256
tar -xzf embcc-VERSION-macos-aarch64.tar.gz
embcc-VERSION-macos-aarch64/bin/embcc --version
On Linux, sha256sum -c checks the file.
From source#
make -j8 all embdbg
make install PREFIX=/opt/embcc
PREFIX defaults to /usr/local. make install builds the toolchain,
as a plain make does: every tool, embdbg included, and every target's
runtime and C library, archived with EmbCC's own embar. With TARGETS=
(or ./configure --targets=) the compiler and the installed runtimes are
those families' only, and PREFIX/lib/embcc/VERSION/ has a directory for
each of their triples and no other. Only once all
of it is built does it copy embcc, embld, embas, embread,
embdbg, embls and embidx into PREFIX/bin, so make -j8 install
is safe. A tool that is missing stops the install with an error.
DESTDIR stages the installation under another root, as package builds
do: make install DESTDIR=/tmp/stage PREFIX=/usr writes
/tmp/stage/usr/.... make install-files performs only the copying step,
without building anything. make uninstall PREFIX=... removes the
binaries and the versioned library directory.
The installed layout#
Every installation has this layout:
PREFIX/bin/embcc (and embld, embas, embar, embdbg, embsim, ... the other tools)
PREFIX/lib/embcc/VERSION/
include/ C library headers
include/c++/ C++ library headers
freestanding/ <stddef.h>, <stdarg.h>, <stdint.h>, <limits.h>, <float.h>, ...
x86_64-elf/ libc.a, libcxx.a
aarch64-elf/ libc.a, libcxx.a
x86_64-linux-gnu/ crt1.o, libc.a, librt.a, libcxx.a, link.ld
aarch64-linux-gnu/ crt1.o, libc.a, librt.a, libcxx.a, link.ld
TRIPLE/ librt.a, and libc.a where libc-embedded builds one
PREFIX/share/man/man1/embcc.1
PREFIX/share/embcc/cmake/embcc.cmake
VERSION is the version embcc --version prints (0.0.1-beta).
Nothing in the binary refers to PREFIX: the compiler finds this
directory relative to its own location, so an installation can be moved
or unpacked anywhere. Check what a compiler found with:
/opt/embcc/bin/embcc --print-search-dirs
A compiler in a build tree uses the tree's lib/ and build/ directories
in the same way, so ./embcc works without installing. Setting
EMBCC_PREFIX points a compiler at a different installation. The search
rules are in
How the compiler finds its own files.
A first program on the host#
Linux on x86-64#
The x86_64-linux-gnu target produces static executables that run on a
Linux kernel with no other C library, linked in the same embcc process.
Build the compiler and the Linux library, then compile and link:
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
return 0;
}
make embcc libc-linux-x86_64
./embcc --target=x86_64-linux-gnu --opt=speed hello.c -o hello
chmod +x hello
./hello
The linker writes the executable without execute permission, hence the
chmod. On a host that is not Linux x86-64, the same image can be booted
as the only process of a Linux kernel under QEMU with
tests/harness/linux/run.sh x86_64 hello. That script needs a kernel
image, which is not part of the repository: it looks for
tests/harness/linux/vmlinuz-x86_64 or the file named by
EMBCC_LINUX_KERNEL_X86_64, and exits with status 126 when there is none.
To make x86_64-linux-gnu the default, see
Choosing the default target. C++ programs
also need make libcxx-linux-x86_64.
Against the system's glibc: --libc=system#
EmbCC's own C library has no POSIX threads and few POSIX interfaces. A
program written for Linux -- <pthread.h>, <unistd.h>, sockets -- is
compiled against the system's C library instead, glibc, as GCC compiles
it: glibc's headers, its start files and its shared objects, linked by
the system's linker.
./embcc --target=x86_64-linux-gnu --libc=system -pthread app.c -o app
./app
This needs glibc's development files (Debian's libc6-dev, Fedora's
glibc-devel) and a linker: x86_64-linux-gnu-ld, ld.lld or, on Linux
itself, ld (binutils). It works the same for aarch64-linux-gnu. To
build for another machine, name its root with --sysroot=DIR, and the
linker with --linker=PATH when it is not one of those;
tools/glibc-sysroot.sh amd64|arm64 DIR makes such a root from Debian
12's packages. See The C library.
macOS#
On macOS, EmbCC writes Mach-O objects and links them with Apple's
linker (/usr/bin/ld, from the Command Line Tools), as clang does. On
Apple silicon:
./embcc --target=aarch64-apple-darwin --opt=speed hello.c -o hello
./hello
The link takes -L, -l, -framework NAME and -Wl, options;
-Wl,--gc-sections and -Wl,-Map=FILE are passed as ld64's
-dead_strip and -map FILE. After libSystem it links EmbCC's compiler
runtime, for the routines its code calls that libSystem does not export
(a complex division's __divdc3, the 128-bit shifts and multiply).
EMBCC_SHOW_LINK=1 prints the linker's command line.
On an Intel Mac use --target=x86_64-apple-darwin. The driver also takes
Apple's spelling of the same choice, -arch arm64 or -arch x86_64, and
the other flags Apple's toolchain and CMake pass: -isysroot DIR,
-mmacosx-version-min=V (the oldest macOS to run on, recorded in each
object and given to the linker; 11.0 on arm64 and 10.13 on x86-64
without it) and -pthread.
The headers are the macOS SDK's, so a program sees the C library it links
against, POSIX headers such as <pthread.h> and <unistd.h> included.
EmbCC uses the SDK named by -isysroot, else by $SDKROOT, else the
Command Line Tools or Xcode SDK. A few SDK headers are corrected for a
compiler that does not define __GNUC__ (include/darwin/). -g gives
a warning and no debug information for the Darwin targets. See
Targets.
Bare-metal x86-64 and EmbLinkOS#
x86_64-elf (the default target) and x86_64-emblink are freestanding:
embcc links them only when the program defines its own _start.
Normally you compile with -c and link with embld
together with your start-up code, as described in
Bare-metal programming. To run a hosted-style program on
bare-metal x86-64 under QEMU, see
x86-64 and AArch64 under QEMU.
Cross-compiling for a board#
A firmware build has three steps: compile each file with --target= and
-c, link the objects together with start-up code using embld, and run
the image. The directories under tests/harness/ hold the start-up code
(boot.c or boot.S), a minimal UART output (io.c, which provides
writec, puts_ and putn), and link.sh and run.sh scripts for one
QEMU board each. They are what EmbCC's own tests use, and they are a
working starting point for your own firmware.
Build the compiler and the linker first:
make embcc embld
The boards#
| QEMU board | Processor | --target= |
Harness | embld options |
|---|---|---|---|---|
microbit |
Cortex-M0 | thumbv6m-none-eabi |
tests/harness/thumb-m0 |
-e reset -Ttext 0x0 -Tdata 0x20000000 |
lm3s6965evb |
Cortex-M3 | thumbv7m-none-eabi |
tests/harness/thumb |
-e reset -Ttext 0x0 -Tdata 0x20000000 |
mps2-an386 |
Cortex-M4F | thumbv7em-none-eabihf |
tests/harness/thumb-m4f |
-e reset -Ttext 0x0 -Tdata 0x20000000 |
mps2-an505 |
Cortex-M33 | thumbv8m.main-none-eabi |
tests/harness/thumb-m33 |
-e reset -Ttext 0x10000000 -Tdata 0x10100000 |
virt |
RV32 | riscv32-unknown-elf |
tests/harness/riscv |
-e _start -Ttext 0x80000000 -Tstack 0x80800000 |
virt |
RV64 | riscv64-unknown-elf |
tests/harness/riscv |
-e _start -Ttext 0x80000000 -Tstack 0x80800000 |
uno |
ATmega328P | avr |
tests/harness/avr |
-e __vectors -Ttext 0x0 -Tdata 0x100 --rom-limit 32768 |
-Ttext is where the image is stored and executed from. -Tdata is where
writable data is addressed in RAM; embld stores its initial contents
after the text and defines the symbols the start-up code uses to copy it
into place and clear .bss. -Tstack makes embld set the stack pointer
before jumping to the entry point. The options are described in
embld.
Cortex-M3, step by step#
This program uses the harness's output functions:
void puts_(const char *s);
void putn(long v);
static int square(int x) { return x * x; }
int main(void)
{
puts_("hello from EmbCC\n");
for (int i = 1; i <= 5; i++)
putn(square(i));
puts_("\nDONE\n");
return 0;
}
Compile the start-up code, the output functions and the program, then link and run:
T=--target=thumbv7m-none-eabi
mkdir -p out/m3
./embcc $T -c tests/harness/thumb/boot.c -o out/m3/boot.o
./embcc $T -c tests/harness/thumb/io.c -o out/m3/io.o
./embcc $T --opt=speed -c hello.c -o out/m3/hello.o
./embld -e reset -Ttext 0x0 -Tdata 0x20000000 \
out/m3/boot.o out/m3/io.o out/m3/hello.o -o out/m3/hello.elf
sh tests/harness/thumb/run.sh out/m3/hello.elf
hello from EmbCC
1 4 9 16 25
DONE
The vector table in boot.c is placed at address 0 by its .vectors
section; the reset handler copies .data, clears .bss and calls
main. When main returns, the handler executes a trap instruction,
the processor locks up, and QEMU stops. run.sh hides QEMU's own
messages, including the register dump it prints on lock-up, and limits
the run to EMBCC_QEMU_TIMEOUT seconds (default 10).
tests/harness/thumb/link.sh OUT.elf PROGRAM.o... runs the same embld
command, taking boot.o and io.o from the directory named by
EMBCC_THUMB_HARNESS and embld from EMBLD (default ./embld):
EMBCC_THUMB_HARNESS=out/m3 sh tests/harness/thumb/link.sh out/m3/hello.elf out/m3/hello.o
Cortex-M4F and Cortex-M33#
The other two ARM boards work the same way with their own harness directory, target and link addresses:
# Cortex-M4F with its FPU, hard-float calling convention
T=--target=thumbv7em-none-eabihf
H=tests/harness/thumb-m4f
./embcc $T -c $H/boot.c -o out/m4f/boot.o
./embcc $T -c $H/io.c -o out/m4f/io.o
./embcc $T --opt=speed -c hello.c -o out/m4f/hello.o
./embld -e reset -Ttext 0x0 -Tdata 0x20000000 \
out/m4f/boot.o out/m4f/io.o out/m4f/hello.o -o out/m4f/hello.elf
sh $H/run.sh out/m4f/hello.elf
# Cortex-M33 (ARMv8-M Mainline)
T=--target=thumbv8m.main-none-eabi
H=tests/harness/thumb-m33
./embcc $T -c $H/boot.c -o out/m33/boot.o
./embcc $T -c $H/io.c -o out/m33/io.o
./embcc $T --opt=speed -c hello.c -o out/m33/hello.o
./embld -e reset -Ttext 0x10000000 -Tdata 0x10100000 \
out/m33/boot.o out/m33/io.o out/m33/hello.o -o out/m33/hello.elf
EMBCC_QEMU_UNTIL=DONE sh $H/run.sh out/m33/hello.elf
(Create the out/m4f and out/m33 directories first.) These two
start-up files loop forever when main returns, so QEMU does not stop by
itself. The Cortex-M4F run ends at the timeout (EMBCC_QEMU_TIMEOUT,
default 10 seconds). The Cortex-M33 run.sh also accepts
EMBCC_QEMU_UNTIL=TEXT, which ends the run as soon as the program prints
TEXT.
The ARM processor and floating-point options (--cpu=, --fpu=,
--float-abi=) are described in
Invoking EmbCC.
RISC-V#
T=--target=riscv32-unknown-elf
H=tests/harness/riscv
mkdir -p out/rv32
./embcc $T -c $H/boot.c -o out/rv32/boot.o
./embcc $T -c $H/io.c -o out/rv32/io.o
./embcc $T --opt=speed -c hello.c -o out/rv32/hello.o
./embld -e _start -Ttext 0x80000000 -Tstack 0x80800000 \
out/rv32/boot.o out/rv32/io.o out/rv32/hello.o -o out/rv32/hello.elf
sh $H/run.sh out/rv32/hello.elf 32
For RV64 use --target=riscv64-unknown-elf and pass 64 (the default)
as the second argument of run.sh, which selects qemu-system-riscv64.
The virt board has a test device that the start-up code writes to after
main returns, so QEMU exits by itself.
AVR (ATmega328P)#
The AVR start-up code is assembly, assembled by EmbCC itself. The
ATmega328P has no divide instruction, so the program also needs the
compiler runtime's 32-bit routines (lib/rt/avr.c; lib/rt/avr64.c adds
the 64-bit ones):
T=--target=avr
H=tests/harness/avr
mkdir -p out/avr
./embcc $T -c $H/boot.S -o out/avr/boot.o
./embcc $T --opt=size -c $H/io.c -o out/avr/io.o
./embcc $T --opt=size -c lib/rt/avr.c -o out/avr/rt.o
./embcc $T --opt=size -c hello.c -o out/avr/hello.o
./embld -e __vectors -Ttext 0x0 -Tdata 0x100 --rom-limit 32768 \
out/avr/boot.o out/avr/io.o out/avr/rt.o out/avr/hello.o -o out/avr/hello.elf
EMBCC_QEMU_UNTIL=DONE sh $H/run.sh out/avr/hello.elf
The entry point must be address 0, where the interrupt vector table is;
QEMU refuses an AVR image whose entry point is anything else.
--rom-limit 32768 makes embld refuse an image larger than the part's
32 KiB of flash. On this target .rodata is linked into the writable
segment with .data: an ordinary pointer dereference reads data memory,
not flash, so the start-up code copies both from flash to RAM before
main runs.
The compiler runtime#
Operations a processor has no instruction for, such as floating point on
a Cortex-M3 or 64-bit division on a 32-bit core, are compiled as calls to
routines with libgcc's names (__muldf3, __divdi3, ...). EmbCC's
versions are in lib/rt. make rt-embedded builds one archive per
embedded target; name it after your objects on the embld command line:
make rt-embedded
./embld -e reset -Ttext 0x0 -Tdata 0x20000000 \
out/m3/boot.o out/m3/io.o out/m3/prog.o \
build/libc/thumbv7m-none-eabi/librt.a -o out/m3/prog.elf
Without it, a program that uses such an operation fails to link, and
embld explains the missing symbol:
embld: undefined symbol '__muldf3' (referenced by out/m3/prog.o)
note: this is a compiler-runtime helper (libgcc's __muldi3 family) ...
Because the runtime is an archive, only the routines a program calls are
linked in. For the AVR example above, rt.o could be replaced by
build/libc/avr/librt.a.
The C library on a board#
make libc-embedded builds EmbCC's C library for the embedded targets,
on a backend for a part with no operating system
(lib/libc/os/baremetal). It needs nothing from your program to link.
To see printf's output, define write; the library calls it for
standard output and standard error:
extern void uart_putc(int c); /* your board's */
long write(int fd, const void *buf, unsigned long n)
{
const unsigned char *p = buf;
for (unsigned long i = 0; i < n; i++)
uart_putc(p[i]);
return (long)n;
}
Without it, output is discarded and input is at end of file. malloc
takes memory from the end of the image (embld's _end) up to the
stack, and returns NULL rather than reaching it. exit stops in a
loop. There is one thread, no clock and no filesystem: those calls fail
with ENOSYS. A board that has any of them defines the classic function
(read, sbrk, _exit, open, close, lseek, isatty) and the
library uses it.
Name libc.a before librt.a, after your objects:
make libc-embedded rt-embedded
./embld -e reset -Ttext 0x0 -Tdata 0x20000000 \
out/m3/boot.o out/m3/prog.o \
build/libc/thumbv7m-none-eabi/libc.a \
build/libc/thumbv7m-none-eabi/librt.a -o out/m3/prog.elf
It is not built for avr, where the library's two-byte locks are not
one access.
x86-64 and AArch64 under QEMU#
The x86-64 and AArch64 harnesses run ordinary hosted-style C programs
(with <stdio.h>) on bare metal, against a newlib C library, with the
cross GNU toolchain doing the link. They need the tools listed under
Host requirements.
mkdir -p out/x64 out/a64
# x86-64: a Multiboot image booted by qemu-system-x86_64
./embcc --target=x86_64-elf --opt=speed -c hello.c -o out/x64/hello.o
sh tests/harness/x86_64/link.sh -o out/x64/hello.elf out/x64/hello.o
sh tests/harness/x86_64/run.sh out/x64/hello.elf
# AArch64: QEMU's virt board, output through ARM semihosting
./embcc --target=aarch64-elf --opt=speed -c hello.c -o out/a64/hello.o
sh tests/harness/aarch64/link.sh -o out/a64/hello.elf out/a64/hello.o
sh tests/harness/aarch64/run.sh out/a64/hello.elf
run.sh exits with the program's exit status. The newlib is looked for
in ~/cross/newlib-c99/x86_64-elf and
~/cross/newlib-aarch64-c99/aarch64-elf; set EMBCC_X86_NEWLIB or
EMBCC_AARCH64_NEWLIB to its TRIPLE directory if it is elsewhere.
Where to go next#
- Invoking EmbCC: every command-line option.
- Targets: each target's data model, ABI, predefined macros and limitations.
- Bare-metal programming: start-up code, interrupts, memory-mapped I/O and linking firmware for your own board.
- Libraries: the C library, the C++ runtime and the compiler runtime.
- Diagnostics: reading EmbCC's messages,
--explainand--fix. - Optimization and Debugging.
- embld: the linker's options.