embas — the EmbCC NASM-syntax assembler#
This page is the reference for embas, EmbCC's standalone assembler for
x86-64 source in NASM (Intel) syntax. It is for people who assemble
hand-written .asm files, such as an operating-system kernel's entry and
interrupt code, without NASM. It lists the syntax, directives and
instructions embas accepts and what it does with anything else. Assembly
in GNU syntax (.s, .S) is handled by embcc itself; see
GNU-syntax assembly at the end of this page.
NAME#
embas — assemble NASM-syntax x86-64 source into an ELF64 object
SYNOPSIS#
embas [-f elf64|bin] [-o FILE] INPUT.asm
DESCRIPTION#
embas reads one source file in NASM syntax, expands its macros,
assembles it for 64-bit mode, and writes an x86-64 ELF64 relocatable
object. Short jumps are relaxed as NASM relaxes them: a jump to a label
in the same section uses an 8-bit displacement when the target is in
range and a 32-bit one otherwise. A local label (one whose name begins
with .) belongs to the most recent non-local label, and is written to
the symbol table as GLOBAL.local, as NASM does.
embas accepts a subset of NASM: the directives and instructions listed
below. A mnemonic it does not know is an error, not a guess. The same
assembler runs when embcc is given a .asm file:
embcc -c entry.asm -o entry.o
With embcc, the output name defaults to the input name with .asm
replaced by .o, and -E is refused with
embcc: error: -E does not apply to assembly.
OPTIONS#
-f FORMAT#
Select the output format. FORMAT is elf64 (the default) or bin.
-f bin (a flat binary) is accepted on the command line but not
implemented: the file is assembled and then refused with
embas: -f bin not yet implemented, exit status 1.
Any other format is refused with embas: unknown format 'FORMAT', exit
status 2. Without an argument: embas: -f needs a format, exit status 2.
-o FILE#
Write the object to FILE. The default is a.out. Without an argument:
embas: -o needs a file, exit status 2.
Any other argument beginning with - is refused with
embas: unknown option 'ARG', exit status 2. There is no --help.
INPUT#
One source file. If several file names are given, only the last is assembled.
Lines, comments and labels#
- A
;begins a comment that runs to the end of the line, except inside a quoted string. - A label is a name followed by a colon:
entry:. The colon is required; a name alone on a line is read as an instruction. Names consist of letters, digits,_,.and$. Several labels may precede one statement. - A label beginning with
.is local to the preceding non-local label. References to it (jne .loop) are resolved in the same scope. - Mnemonics and directive names are case-insensitive; register names and symbol names are case-sensitive and registers must be written in lower case.
Operands#
| Form | Meaning |
|---|---|
rax ... r15, eax ... r15d |
a 64-bit or 32-bit general register; 16-bit and 8-bit registers are not supported |
123, 0x7f, -8, ~0xFF |
an integer: decimal, 0x hexadecimal, or octal with a leading 0, optionally with unary -, + or ~ |
[reg], [reg+disp], [reg-disp] |
memory at a base register plus a constant displacement |
[gs:disp], [fs:disp], [gs:reg+disp] |
memory with a segment override; a bare displacement is an absolute address |
[rel symbol] |
RIP-relative memory; supported by mov only (see below) |
symbol |
a label or external symbol |
byte, word, dword, qword, oword, tword, yword |
a size keyword before an operand; accepted and ignored |
Arithmetic between symbols or constants (label+4, 2*8) is not
supported. NASM's 0FFh and $-prefixed hexadecimal forms are not
recognized.
DIRECTIVES#
| Directive | Effect |
|---|---|
section NAME, segment NAME |
switch to section NAME, one of .text, .rodata, .data, .bss; any other name is an error (unknown section NAME) |
global NAME, globl NAME |
export NAME; one name per directive |
extern NAME |
declare an external symbol; one name per directive |
db, dw, dd, dq |
emit bytes, words, doublewords or quadwords: comma-separated integers, quoted strings (bytes, no escape sequences), or symbol names |
resb N, resw N, resd N, resq N |
reserve N units of uninitialized space (in .bss) |
align N |
pad the current section with zero bytes to a multiple of N |
incbin "PATH" |
insert the contents of a file; PATH is relative to the current working directory |
[BITS 64], bits 64, default ..., cpu ... |
accepted and ignored; embas always assembles 64-bit code. Any other line beginning with [ is ignored too |
%macro NAME N ... %endmacro |
define a macro; %1 through %9 in its body are replaced by the arguments of an invocation |
A macro is invoked by writing its name as the first word of a line,
followed by comma-separated arguments. The expanded lines are scanned
again, so a macro may invoke another. Expansion more than 32 levels deep
stops with embas: macro 'NAME' nests more than 32 deep (does it invoke itself?).
A symbol name in dd or dq produces an absolute relocation
(R_X86_64_32 or R_X86_64_64). This is correct only in .text: in any
other section the relocation is recorded against .text and the object is
wrong. In db and dw, symbol names are not supported.
Not supported: equ, times, %define, %include, %if and the rest
of the NASM preprocessor except %macro, struc, and section attributes.
They are reported as unknown instructions, for example
unsupported instruction times.
INSTRUCTIONS#
| Mnemonics | Operand forms |
|---|---|
cli, sti, hlt, nop, ret, leave, pushfq, popfq, cpuid, rdtsc, rdmsr, wrmsr, syscall, sysret, swapgs, iretd |
none |
iret, iretq |
none; both assemble as iretq (48 cf), as NASM does in 64-bit mode |
o64 sysret |
none; the 64-bit sysret (48 0f 07) |
push |
register, immediate (8-bit or 32-bit form chosen by value), memory |
pop |
register |
mov |
register to register; memory to register; register to memory; immediate to register; symbol to register (as movabs with R_X86_64_64); [rel symbol] to register and register to [rel symbol] (with R_X86_64_PC32) |
lea |
memory to register |
add, or, and, sub, xor, cmp |
register and register; register and immediate |
test |
register and register |
imul |
register and register (two-operand form) |
shl, sal, shr, sar |
register and immediate (D1 form for a count of 1) |
call, jmp |
register (indirect) or symbol |
jo, jno, jb, jae, je, jz, jne, jnz, jbe, ja, js, jns, jl, jge, jle, jg |
symbol |
fxsave, fxrstor, lgdt, lidt |
memory |
A move of an immediate to a 64-bit register uses the shortest form NASM
uses: a 32-bit mov when the value fits in 32 bits unsigned, a
sign-extended 32-bit immediate when it fits signed, and a 64-bit
immediate otherwise.
A form not in this table is refused, for example
assembler error: bad ALU operand add for add rax, [rbx] or
assembler error: bad mov for mov al, 1. A mnemonic not in the table
gives assembler error: unsupported instruction MNEMONIC, with the
mnemonic in lower case.
Some invalid forms are not detected and produce wrong code. Do not use them:
- an index register or scale in a memory operand:
[rax+rbx*8]assembles as[rax]; [rel symbol]with any instruction other thanmov:lea rax, [rel sym]assembles aslea rax, [rbp];- a reference to a symbol that is neither defined in the file nor
declared with
extern: the assembler terminates abnormally instead of reporting it. Declare every external symbol withextern.
OUTPUT#
An x86-64 ELF64 relocatable object. It has the sections .text,
.rodata, .data and .bss that received content, in the order in
which the source first used them. Its symbol table holds an STT_FILE
symbol naming the input, a section symbol per section, the labels
(non-exported labels as local symbols), and the extern symbols as
undefined globals. A reference to a symbol defined in the same object is
relocated against its section symbol plus an offset; a reference to an
external symbol is relocated against the symbol.
| Construct | Relocation |
|---|---|
call symbol (external or in another section), mov reg, [rel symbol], mov [rel symbol], reg |
R_X86_64_PC32, addend −4 |
mov reg, symbol, dq symbol |
R_X86_64_64 |
dd symbol |
R_X86_64_32 |
A call or jump to a label in the same section is resolved in the
object and needs no relocation.
DIAGNOSTICS#
Errors have the form:
FILE:LINE: assembler error: MESSAGE
embas stops at the first error. The line number counts the lines
after comments and blank lines are removed and macros are expanded, so it
can be smaller than the line's number in the source file.
EXIT STATUS#
| Status | Meaning |
|---|---|
| 0 | the object was written |
| 1 | the source could not be read (embas: cannot open FILE) or did not assemble, or -f bin was requested |
| 2 | a usage error: no input, unknown option or format, missing option argument |
If the output file cannot be written, embas prints
embcc: cannot write 'FILE' and exits with a nonzero status.
ENVIRONMENT#
embas reads no environment variables.
EXAMPLES#
A system-call stub:
; exit.asm -- void sys_exit(int code)
[BITS 64]
section .text
global sys_exit
sys_exit:
mov rax, 60
syscall
.hang:
hlt
jmp .hang
embas -f elf64 exit.asm -o exit.o
An interrupt stub built from a macro:
%macro STUB 1
global isr%1
isr%1:
push qword 0
push %1
jmp isr_common
%endmacro
section .text
extern isr_dispatch
STUB 0
STUB 1
isr_common:
call isr_dispatch
iretq
GNU-SYNTAX ASSEMBLY#
embas reads NASM syntax for x86-64 only. Assembly in GNU syntax is
assembled by embcc itself, for the ARM (Thumb), AArch64, RISC-V, MIPS32,
LoongArch64, Xtensa, TriCore, SPARC, PowerPC, RX, ColdFire and AVR
targets:
embcc --target=thumbv7m-none-eabi -c startup.S -o startup.o
A .S file is preprocessed first; a .s file is not. GNU-syntax files
for x86-64 are refused. Instructions are encoded by the same per-target
assembler that inline __asm__ uses, so the accepted instructions are the
inline-assembly vocabulary listed in Inline assembly;
driver behavior and output naming are described in
Invoking EmbCC.
The assembler reads what a CMSIS or vendor startup file and an RTOS port
are written in. Each Cortex-M startup file in ARM's CMSIS_5 and every
STM32F4 one in ST's cmsis-device-f4 assembles to the object clang's
assembler makes from it -- the same sections, instructions, symbols and
relocations -- which tests/golden/gas-gnu.sh checks.
Source#
- Comments.
/* ... */anywhere, also across lines;#and//;@on ARM,!on SPARC,;on AVR and RX and|on ColdFire. On ARM, AArch64, SPARC, RX and ColdFire,#starts a comment only as a line's first character (an immediate's prefix on ARM, RX and ColdFire,#functionin SPARC's.type). - Statements.
;separates two statements on one line (not on AVR and RX, where it is the comment; RX separates them with!). Any number oflabel:may precede one. Numeric local labels0:to9:are referred to as1band1f. - Letter case. Mnemonics, register names, conditions and operand
keywords are case-insensitive (
MRS r0, PRIMASK); symbols are not.
Expressions#
Wherever a directive takes a value: numbers in C's notation and 0b
binary, 'c' characters, symbols, . (here), and GNU as's operators with
GNU as's precedence -- * / % << >> bind tightest, then | &
^ !, then + - and the comparisons, then && ||. A label minus a
label in the same section is a plain value, a label plus a value stays an
address, and an address in a data word becomes a relocation (a label in
this file or an external symbol, with any addend). Anything that mixes
addresses otherwise is refused.
Directives#
| Directive | Effect |
|---|---|
.text, .data, .rodata, .bss |
switch to that section |
.section NAME[, "FLAGS"[, @TYPE[, ENTSIZE]]] |
switch to NAME, creating it. With no flags, a name GNU as knows gets its flags (.text.* code, .data.* and .bss.* writable, .rodata.* read-only, .init_array and friends, .tdata/.tbss) and any other name none at all (not allocated), as in GNU as. FLAGS from a w x M S T R y; TYPE progbits, nobits, note, init_array, fini_array, preinit_array. Declaring a section again with other flags or type is refused |
.pushsection ..., .popsection, .previous |
switch, and switch back |
.byte, .short/.half/.hword/.2byte, .word, .long/.int/.4byte, .quad/.dword/.8byte |
values of 1, 2, 4 (2 on AVR for .word), 4 and 8 bytes; each an expression |
.ascii, .asciz, .string |
string data; .asciz and .string add a terminating zero |
.space N[, FILL], .zero N, .skip N[, FILL] |
N bytes of FILL (0), or reserved space in a NOBITS section |
.fill REPEAT[, SIZE[, VALUE]] |
REPEAT values of SIZE bytes |
.org OFFSET |
advance to OFFSET in this section |
.align N, .p2align N (and w/l variants) |
align to 2^N bytes; code is padded with the target's no-op (with zeros on Xtensa, whose instructions are three bytes; with GNU as's multi-byte nops on RX, and a bra.b over the rest of a gap of 8 or more). On Xtensa, SPARC, RX and ColdFire .align N counts bytes, a power of two, as GNU as reads it there |
.balign N (and w/l variants) |
align to N bytes |
.equ NAME, EXPR, .set NAME, EXPR, NAME = EXPR, .equiv NAME, EXPR |
define NAME: absolute for a value, an alias for an address; it may name a label further down |
.thumb_set NAME, EXPR |
as .set, and NAME is a Thumb function (how a startup file aliases weak handlers to its default one) |
.global/.globl, .weak, .local, .hidden, .internal, .protected |
a comma-separated list of names |
.type NAME, %function/%object/%notype (also @...) |
the symbol's type |
.size NAME, EXPR |
the symbol's size, usually . - NAME |
.lcomm NAME, SIZE[, ALIGN], .comm ... |
reserve space in .bss (.comm makes it global) |
.inst, .inst.n, .inst.w |
an instruction by its encoding |
.ltorg, .pool |
place the literal pool here (ARM) |
.literal_position, .literal NAME, EXPR, ... |
Xtensa: a literal pool here; words in the current pool, NAME on the first. A section's pools are at its start, at each .literal_position and before the labels of each entry, and movi aN, EXPR whose value is a symbol or wider than 12 bits loads from the latest one -- as GNU as's --text-section-literals places them |
.begin NAME, .end NAME |
Xtensa: GNU as's relaxation blocks; no-transform, literal_prefix, schedule, density, target-align and their no- forms are accepted (nothing here relaxes), longcalls and absolute-literals refused |
.macro NAME [PARAMS] ... .endm, .purgem, .exitm |
a macro: parameters with =default, :req or :vararg, used as \name; \@ counts expansions and \() separates |
.rept N, .irp SYM, A, B..., .irpc SYM, CHARS ... .endr |
repetition |
.if EXPR, .ifdef, .ifndef, .ifeq, .ifne, .ifgt, .ifge, .iflt, .ifle, .ifb, .ifnb, .ifc, .ifnc, .ifeqs, .ifnes, .else, .elseif, .endif |
conditional assembly, decided from values known where the .if stands (numbers and earlier .equ/.set; not a label's address) |
.include "FILE" |
the file, found as given or beside this one |
.end |
the rest of the file is not assembled (on MIPS .end f and on Xtensa .end NAME close a block instead) |
.error "MSG", .warning "MSG", .print "MSG" |
a diagnostic |
.cpu, .arch, .arch_extension, .fpu, .syntax, .thumb, .code 16, .eabi_attribute, .object_arch, .file, .ident, .loc, .cfi_*, .attribute, .option |
accepted; the target comes from --target/-mcpu |
.fnstart, .fnend, .cantunwind, .save, .vsave, .setfp, .pad, .movsp, .personality, .personalityindex, .handlerdata, .unwind_raw |
accepted; no unwind table is written (an exception unwinding through this code stops) |
Refused by name: .arm and .code 32 (an M-profile core has no ARM
state), subsections (.text 1), .weakref, and any other directive
(directive ".NAME" is not one this assembler knows).
ARM specifics#
ldr rd, =EXPR. A constant a move can make is that move --mov.wfor a modified immediate,mvn.wfor its complement,movwup to 0xffff -- and anything else, an address included, is a word in the literal pool, loaded pc-relative. The pool is placed at the next.ltorg/.poolor at the end of the section; identical entries share a word. None of these sets the flags, asldrdoes not.- Relaxation. A branch and a literal load are assembled in their
two-byte form when the target is in reach, and in the four-byte form
otherwise;
.wand.nforce one. The passes repeat until no label moves, so the layout is the one GNU as and clang produce. - Symbols. A branch to an external symbol, to a label in another
section or to a weak symbol is relocated (
R_ARM_THM_CALL,R_ARM_THM_JUMP24), since the linker decides where those end up..thumb_func,.type NAME, %functionand.thumb_setmake a symbol a Thumb function, which carries the interworking bit..Llabels stay out of the symbol table. - The object has
$t/$dmapping symbols in sections that contain code, and the same.ARM.attributesthe compiler writes for the target, so a disassembler decodes it and the linker can check it.
RX specifics#
- Names. A C symbol is
_namein an RX object, and an assembly file writes it so (.global _main,bsr _c_function), as with GNU as; the object's names are the file's. In a file-scope block or a naked function in C,_nameis C'sname. - Relaxation. A branch written without a size takes the shortest form
that reaches, as GNU as relaxes it:
bra.s/.b/.w/.a,bsr.w/.a,beq/bne.s/.b/.wand then the inverse over abra.a, any other condition.band then the inverse over abra.worbra.a. A form only ever lengthens, so the passes settle; a written size is kept, and refused if it does not reach. - Sections are
.text,.data,.bssand so on, as GNU as names them with-muse-conventional-section-names(its default is Renesas'sP,D_1,B_1), and each is padded to its alignment at its end, as GNU as pads it..wordis four bytes, as GNU as makes it here.
ColdFire specifics#
- Syntax. GNU as's Motorola syntax, the register
%optional and either case, MIT'sAn@(d)accepted; FreeRTOS's ColdFire V2 port assembles as it is. - Relaxation. A branch written without a size takes
.sor.w, whichever reaches; the MCF5208 has no 32-bit branch, so.lis refused and a label beyond 32 KiB is reached withjmp.move.l #n,Dnismoveqfor -128..127 andadd/sub #1..8isaddq/subq, as GNU as makes them. - Data. Big-endian;
.wordis two bytes, as GNU as makes it for m68k.
A symbol that the file does not define may be named only in the instruction forms that carry a relocation:
| Target | Forms |
|---|---|
| RISC-V | call SYMBOL, la REG, SYMBOL |
| ARM | bl SYMBOL, b SYMBOL, ldr REG, =SYMBOL, movw/movt with #:lower16:/#:upper16: |
| AArch64 | bl SYMBOL |
| AVR | call, jmp, rcall, rjmp and conditional branches to a symbol; lds/sts with a symbol address; ldi REG, lo8(SYMBOL), hi8(...), pm_lo8(...), pm_hi8(...), and lo8(gs(SYMBOL)), hi8(gs(SYMBOL)) |
| SPARC | call SYMBOL (R_SPARC_WDISP30), a branch to one (R_SPARC_WDISP22); sethi %hi(SYMBOL) and any %lo(SYMBOL) operand -- an or, an add, a load's or store's offset -- (R_SPARC_HI22, R_SPARC_LO10), and set SYMBOL, REG (both), relocated even for a label of this file |
| ColdFire | bra/bsr SYMBOL as jmp/jsr to its address (R_68K_32), bcc SYMBOL and a written .w as .w (R_68K_PC16); a bare SYMBOL or #SYMBOL operand of jsr, jmp, lea, pea, move, an ALU instruction (R_68K_32); .long SYMBOL (R_68K_32), .word SYMBOL (R_68K_16) |
| RX | mov.l #SYMBOL, REG (R_RX_DIR32); bra/bsr SYMBOL (.a, R_RX_DIR24S_PCREL), beq/bne SYMBOL (.w, R_RX_DIR16S_PCREL), another bCND SYMBOL (.b, R_RX_DIR8S_PCREL), or the size written; .long/.word SYMBOL |
| PowerPC | b, bl (R_PPC_REL24), ba, bla (R_PPC_ADDR24) and a conditional branch (R_PPC_REL14) to a symbol; SYMBOL@ha, @h and @l in any 16-bit immediate or offset (R_PPC_ADDR16_HA, _HI, _LO), relocated even for a label of this file |
Any other use is refused, for example
"STATEMENT" names 'NAME', which is not defined in this file, in a form this target's assembler cannot relocate.
Errors have the form FILE:LINE: error: MESSAGE.