EmbCCdocs Repository

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 (set CC= 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#