Every compiler on Linux calls GNU as behind the scenes, and knowing how to drive it directly turns a mystery link error into a five-minute check. You will turn a short assembly source file into a relocatable ELF object, then confirm its target and machine code are what you meant. The examples use GNU as from Binutils 2.42 on Ubuntu, installed as binutils-common, binutils-x86-64-linux-gnu and binutils-aarch64-linux-gnu. Allow about 10 minutes if the assembler and object inspection tools are already installed.
You need a shell, GNU as, and the matching file, readelf and disassembler tools. No root privileges are needed: assembling just writes an ordinary object file wherever you have permission to write. This guide produces a relocatable object, not a runnable executable; the linker is a separate step.
There are three relevant command names on this machine. as targets the host x86-64 assembler. x86_64-linux-gnu-as is the explicit x86-64 name, while aarch64-linux-gnu-as targets 64-bit Arm. The aliases do not make one assembler understand the other architecture's instruction syntax.
Create a source file containing one function. This follows the System V x86-64 convention: the integer argument arrives in %rdi, the result is returned in %eax:
cat > add-one.s <<'EOF'
.text
.globl add_one
.type add_one, @function
add_one:
leal 1(%rdi), %eax
ret
.size add_one, .-add_one
EOF
The source mixes directives with instructions. .text selects the code section, .globl exports the function name, and .type and .size describe it in the ELF symbol table. GNU as interprets these directives itself; they are not CPU instructions.
Now assemble the file, choosing the output name explicitly with -o:
as -o add-one-x86-64.o add-one.s
A successful run prints nothing and returns status 0. Skip -o and the manpage says the default output is a.out, an easy way to lose track of the file in a busier directory.
Confirm the file format and disassemble the code:
file add-one-x86-64.o
readelf -h add-one-x86-64.o | grep -E 'Class:|Machine:'
objdump -d add-one-x86-64.o
Expect identifying lines like these:
add-one-x86-64.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Class: ELF64
Machine: Advanced Micro Devices X86-64
0000000000000000 <add_one>:
0: 8d 47 01 lea 0x1(%rdi),%eax
3: c3 ret
Addresses and spacing can vary between tool versions, but the file must be relocatable ELF, the machine must be x86-64, and the function must add one and return. If the machine is wrong, fix the assembler command here rather than linking an object built for the wrong target.
Use the target-prefixed assembler with AArch64 syntax and registers:
cat > add-one-aarch64.s <<'EOF'
.text
.globl add_one
.type add_one, %function
add_one:
add w0, w0, 1
ret
.size add_one, .-add_one
EOF
aarch64-linux-gnu-as -o add-one-aarch64.o add-one-aarch64.s
file add-one-aarch64.o
readelf -h add-one-aarch64.o | grep -E 'Class:|Machine:'
aarch64-linux-gnu-objdump -d add-one-aarch64.o
On the installed Binutils 2.42 tools, the identifying output includes ELF 64-bit LSB relocatable, ARM aarch64, Class: ELF64 and Machine: AArch64, and the disassembly contains add w0, w0, #0x1 and ret. This object is still not executable: it needs an AArch64 linker and usually a suitable runtime or entry point.
GNU as accepts one or more input files and reads them left to right as one source program. With no filename it reads standard input, which usually just leaves a terminal waiting. Prefer an explicit file, or deliberately pass -- for standard input when that is what you mean.
Use -g when you want assembler debugging information for later inspection. Use -a with listing sub-options for a source and machine-code listing; -alh, for example, includes assembly and high-level source. Treat a listing as diagnostic output, never a substitute for checking the ELF object itself.
Warnings usually let assembly continue; errors stop it. In automated builds, --fatal-warnings turns warnings into failures, useful when a warning means the object must not be trusted. -w suppresses warnings, so avoid it while diagnosing a build. Warning: -Z lets an object be generated even when errors occurred; treat that as a deliberate diagnostic experiment only, and never feed such an object into a normal link.
The --defsym NAME=VALUE option defines a symbol from the command line. The @FILE form reads whitespace-separated options from a file, with quoting and recursive response files, both handy in build systems. Review a generated option file as carefully as source code: it changes the assembler invocation without appearing as an ordinary argument.
If assembly fails, keep the source and the error output. Check the target command first, then register syntax and directives. Remove only the object from the failed attempt if you no longer need it:
rm -- add-one-x86-64.o
That is destructive for one file only. Never substitute a wildcard such as rm -- *.o in a project directory unless you have checked every matching object first; nothing here needs elevated privileges or leaves other system-wide state to undo.
If the object assembled but inspection shows the wrong architecture, delete it, pick the correct target-prefixed assembler, and assemble again. If a linker later reports incompatible architecture or ABI attributes, come back to this inspection step and compare every input object before touching linker flags.
file reports a relocatable ELF object for the intended architecture.readelf -h confirms the expected class and machine.