In the previous lecture of this free linux kernel development course you built a complete aarch64-unknown-linux-gnu toolchain with crosstool-NG. A toolchain sitting on disk is not useful until you know how to reach it from your shell, prove it actually produces target binaries, and pull configuration details back out of it when something goes wrong months later. That is the whole subject of this lecture.
file command
gcc -dumpmachine
free linux development course
ELF binary inspection
toolchain sysroot
What You Will Learn
- How to add a freshly built toolchain to your
PATHcorrectly - How to cross-compile a small original program and prove it is a target binary, not a host one
- How to query a cross compiler for its configuration, version, and built-in search paths
- How to locate the toolchain’s sysroot and target libraries when debugging link errors
Prerequisites
- A toolchain already built under
~/x-tools/<triplet>, as covered in the previous lecture - Basic familiarity with
gcccommand-line flags on the host
Adding the Toolchain to Your PATH
Every crosstool-NG toolchain places its binaries under a bin/ directory named after the target triplet. For the aarch64-unknown-linux-gnu toolchain built earlier, that is:
$ ls ~/x-tools/aarch64-unknown-linux-gnu/bin/ | head
aarch64-unknown-linux-gnu-addr2line
aarch64-unknown-linux-gnu-ar
aarch64-unknown-linux-gnu-as
aarch64-unknown-linux-gnu-gcc
aarch64-unknown-linux-gnu-gdb
aarch64-unknown-linux-gnu-ld
aarch64-unknown-linux-gnu-nm
aarch64-unknown-linux-gnu-objdump
aarch64-unknown-linux-gnu-strip
Rather than permanently polluting your shell’s global PATH, prepend it only for the current session so it never shadows your host compiler by accident:
$ export PATH=~/x-tools/aarch64-unknown-linux-gnu/bin:$PATH
$ which aarch64-unknown-linux-gnu-gcc
/home/you/x-tools/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-gcc
Every command in this lecture — every compiler, linker or binutils tool — is prefixed with aarch64-unknown-linux-gnu-. That prefix is the whole point of a cross toolchain: it lets host and target tools with the same short name (gcc, ld, nm) coexist without collision.
Cross-Compiling a First Program
Write a small, original test program — not a copy of any book listing, just enough to prove the pipeline works:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("ep_hello running on PID %d\n", getpid());
return 0;
}
Compile it with the cross compiler exactly as you would with the host gcc, just using the prefixed name:
$ aarch64-unknown-linux-gnu-gcc -Wall -O2 ep_hello.c -o ep_hello
The resulting ep_hello binary will not run on your x86_64 or host development machine — that is expected and correct. Prove what architecture it actually targets with the file command:
$ file ep_hello
ep_hello: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux-aarch64.so.1,
for GNU/Linux 6.1.0, not stripped
The ARM aarch64 field and the target interpreter path confirm this is genuinely a target binary. If you copy ep_hello onto real target hardware and run it over a serial console or SSH session, you should see the same output you’d expect from the host build, just with a PID assigned by the target’s kernel.
Querying the Cross Compiler Itself
A toolchain you inherited from a colleague, a CI artifact, or a vendor SDK rarely comes with clear documentation of exactly how it was configured. GCC itself is the best source of truth. A handful of flags answer almost every question you will ever have:
| Command | What It Tells You |
|---|---|
aarch64-unknown-linux-gnu-gcc --version |
GCC version and the toolchain vendor string baked in at build time |
aarch64-unknown-linux-gnu-gcc -dumpmachine |
The exact target triplet the compiler was configured for |
aarch64-unknown-linux-gnu-gcc -v |
Full configure-time options, including which C library and thread model were selected |
aarch64-unknown-linux-gnu-gcc -print-sysroot |
The path to the target sysroot the compiler searches for headers and libraries |
aarch64-unknown-linux-gnu-gcc -print-search-dirs |
Every directory GCC searches for libraries, plugins and the executable subprograms it invokes internally |
$ aarch64-unknown-linux-gnu-gcc --version
aarch64-unknown-linux-gnu-gcc (crosstool-NG 1.28.0) 14.2.0
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
$ aarch64-unknown-linux-gnu-gcc -dumpmachine
aarch64-unknown-linux-gnu
$ aarch64-unknown-linux-gnu-gcc -print-sysroot
/home/you/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot
The sysroot path is worth remembering on its own: it is where the compiler’s headers and target libraries actually live, and it is the first place to look whenever you hit a “cannot find -lfoo” link error or a missing-header compile error while cross-building a larger project against this toolchain.
Inspecting the Sysroot
Browsing the sysroot directly demystifies a lot of cross-build confusion:
$ ls $(aarch64-unknown-linux-gnu-gcc -print-sysroot)
lib usr
$ ls $(aarch64-unknown-linux-gnu-gcc -print-sysroot)/usr/include | head -5
aio.h
alloca.h
ar.h
argp.h
argz.h
Anything your target program #includes that is not a compiler-internal header comes from here — it is the target’s own C library headers, not your host’s /usr/include. When you later cross-build a third-party library against this toolchain and it fails to find a header, checking whether that header actually exists under this sysroot is almost always the fastest diagnosis.
Real-World Use Cases
- CI pipelines: archive the built toolchain as a tarball artifact and unpack it fresh on every build agent, rather than rebuilding crosstool-NG on every CI run.
- Reproducing a vendor SDK: if a board vendor only ships prebuilt binaries, use
-vand-dumpmachineagainst their compiler to reverse-engineer close-enough crosstool-NG settings for a from-source rebuild. - Cross-compiling a device driver’s userspace test harness: once the toolchain is on your
PATH, ordinary Makefiles just needCC=aarch64-unknown-linux-gnu-gccto target the board instead of the host.
Common Mistakes and Troubleshooting
- Running the target binary locally and seeing “cannot execute binary file”: that is correct and expected — the binary is for a different architecture. Copy it to the target instead.
- Forgetting the target triplet prefix: typing plain
gccsilently uses the host compiler and produces a host binary with no error at all — always double-check withfileon the output. - Header not found during a larger cross-build: confirm the header actually exists under
$(CC -print-sysroot)/usr/includebefore assuming your-Iflags are wrong. - PATH pollution: exporting the toolchain’s
bin/permanently in.bashrccan shadow host tools with the same short names if multiple toolchains’ directories end up onPATHat once — prefer setting it per project or per shell session.
Best Practices
- Never rely on memory for a toolchain’s configuration —
-vand-dumpmachineare always faster and more accurate than guessing. - Keep a project-local environment script (a simple
source env.sh) that setsPATHandCCfor that toolchain, instead of editing your global shell profile. - Always verify a freshly built binary’s architecture with
filebefore assuming a build “worked” — a silently host-compiled binary is a very common false positive.
Summary and Key Takeaways
- Add the toolchain’s
bin/directory toPATHper-session, and always call tools with their full target-triplet prefix. fileon the compiled output is the fastest sanity check that a build actually targeted the right architecture.gcc -v,-dumpmachine, and-print-sysrootturn any mystery toolchain into a fully documented one in seconds.
Conclusion
Building a toolchain is only half the story — knowing how to reach it, prove it works, and interrogate its configuration is what makes it genuinely usable day to day. These same file and gcc -v habits will save you real debugging time throughout the rest of this free embedded systems course, especially once you start cross-compiling the kernel and real device drivers in later lectures.
Frequently Asked Questions
Why won’t my cross-compiled binary run on my development machine?
It was built for the target architecture, not your host’s. That is expected — copy it to the actual target hardware to run it.
How can I tell what C library a prebuilt cross toolchain uses?
Run its gcc -v and look at the --with-... configure options in the output, or check -print-sysroot and inspect the library files directly.
What is the difference between -dumpmachine and -v?
-dumpmachine prints only the target triplet. -v prints the full configure-time options, thread model, and C library selection.
Should I add the cross toolchain to my system-wide PATH?
Generally no — scope it to a project or shell session so it cannot shadow your host compiler or a different target’s toolchain.
What does -print-sysroot actually point to?
The root directory containing the target’s own headers and libraries that the cross compiler searches by default — separate from your host’s /usr.
How do I use this toolchain inside a Makefile-based project?
Set CC=aarch64-unknown-linux-gnu-gcc (and similarly AR, LD, STRIP) so the existing build rules target the board instead of the host.
Can I run the cross-compiled binary under QEMU instead of real hardware?
Yes — qemu-aarch64 user-mode emulation can run a single aarch64 binary directly on an x86_64 host for quick testing before deploying to real hardware.
Keep Going With This Free Embedded Linux Course
Next up: cross-compiling and configuring the Linux kernel itself with the toolchain you just built and verified.
