AmmAsm - x86-64 Assembler

Version 3.0.1

Chapter 3: The AmmAsm syntax

3.1: Modified-Intel like syntax

AmmAsm supports Intel like syntax by default like assemblers such as Nasm, Fasm, etc. However it has some key difference. The most essential difference is addressing syntax, to learn more - see section X.X.

3.2: DQ, DD, DW, DB: Declaration initialization directives

AmmAsm support only these 4 types of directives. dq - 8 byte, dd - 4 bytes, dw - 2 bytes, db - 1 byte. They can be placed anywhere, at any section, but if you place it in .BSS - AmmAsm will ignore the data inside the directives. Directives can placed after labels or without them. All directives except db - will reverse data in little-endian order (0xAABB -> BB AA)

However, AmmAsm has restrictions, for example:

General examples:

; db
foo: db "Hello, World!\n", 0
bar: db "Hello,", " World", 10, 0
db 0x62 0xf4 0x7f 0x18 0x42 0xc0  ; hex-decimal

; dw
foo: dw 1122, 12000, 67  ; decimal
bar: dw 0b1111000011110000 ; binary
dw 0o123456                ; octal

; dd
foo: dd 10, 20, 30, 40
bar: dd 10.0, 20.0, 30.0, 40.0 ; float (IEEE-754)

; dq
foo: dq $-msg, msg+5, msg+8, 0xdeadbeef
bar: dq (((((10 * 2) << 2) + $) & 0xFF) | 0x100) - label  ; yeee, I did it!
dq 'A' + 0 - 'B'
        

3.3: RESQ, RESD, RESW, RESB: Declaration uninitialized data

AmmAsm supports only these 4 types of uninitialized data. RESQ - 8 bytes, RESD - 4 bytes, RESW - 2 bytes, RESB - 1 byte. They can be placed in .BSS section, if placed in another place - AmmAsm will ignore them if ELF64 object file mode (-c option). Expressions are not delivered. They can be defined with or without labels. For example:

section .bss
resb 0x10        ; 16   bytes
foo: resw 10     ; 20   bytes
bar: resd 90     ; 360  bytes
resq 100000      ; 800k bytes
        

Since AmmAsm can generate different target of ELF64 format, the behavior of uninitialized data is different between executable(s) and object files.

For ET_REL, as expected, AmmAsm gathers the total amount of bytes from .bss, add them together and writes this number to shdrs[3].sh_size. However, behavior for ET_EXEC and ET_PIE is different. Unlike object file, executables is placing zero bytes directly to file , as for executable - AmmAsm produces 1 segment-only with RWX permissions, see section X.X. Why was this decision made? And why I didn't use p_memsz - p_filesz formula? Here is the answer:

Basically the size of file does not equal to size of memory usage of RAM at runtime (even without using heap). 1 segment requires at least 4 KiB of mapped memory (1 page). 3 segments (acting as separate sections) = 12 kilobytes, so AmmAsm combines .data + .text to 1 section/segment. And res* does not touch p_memsz, because it can be placed before this 2 sections, and probably will have label behind to address this bytes. But basically .bss bytes are going at end of file in standard ELF64 structure. Also, for this case, res* does not require .bss section, it can be placed anywhere. I hope you understood me :). For example:

; build: `aasm input.asm -o out'
label: resb 5 ; semantically same as `db 0 0 0 0 0'

section .text
_start:
    add rax, [b=label]
        

result is:

$ hexdump -C out
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  05 10 40 00 00 00 00 00  |..>.......@.....|
00000020  40 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |@...............|
00000030  00 00 00 00 40 00 38 00  01 00 00 00 00 00 00 00  |....@.8.........|
00000040  01 00 00 00 07 00 00 00  00 10 00 00 00 00 00 00  |................|
00000050  00 10 40 00 00 00 00 00  00 10 40 00 00 00 00 00  |..@.......@.....|
00000060  0c 00 00 00 00 00 00 00  0c 00 00 00 00 00 00 00  |................|
00000070  00 10 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000  00 00 00 00 00 48 03 05  f9 ff ff ff              |.....H......|
0000100c
$
        

Here, the first 5 zero bytes are produced by resb 5. Unlike ET_REL, they are physically present in the executable file. AmmAsm also aims e_entry to start of code, see section X.X. PE32+ — experimental, not documented yet.

3.4: Sections: How to define and how works "under the hood"

-------------

3.5: Labels

AmmAsm supports labels (however obvious it may be...), and strtab section for ELF64 object files, see section X.X. supported global and local labels. A label which starts with '.'(dot) AmmAsm recognize as local label and "scopes" it to last global label, by concatenating strings (In code, works whith snprintf()). If local label used without any global label - AmmAsm will give error message and exit. Labels can be defined with and without colons. Golden rule: If AmmAsm meets the unknown symbol without colon, which is not defined in list of instruction names and key-words — it will interpret this symbol as label. AmmAsm has it's own 2 pass build-in linker to resolve address for labels, see section X.X.

Also supports global/extern directives for labels (only for ET_REL, they make sense). see section X.X.

For example:

some_global_label:

.loop:
        ; some code
another_global_label:

.loop:  
        ; Namespace preserved. 
        ; While parsing, AmmAsm replaces ".loop" to last label that it was scoped to (another_global_label) 
        jmp .loop ; same as jmp another_global_label.loop
        
global _start, strcmp
extern printf, __pthread_unregister_cancel_restore