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:
- db can accept only literals such as strings, integers and chars. Commas for this directive is optional.
- dw can accept only integers literals. Commas for this directive is optional.
- dd can accept only integers and float number literals. Commas for this directive is optional.
- dq can accept expressions, integers literals. Commas are optional only for integers. Expressions can consist of labels, integers, $(current pc), -, +, chars, parentheses and other arithmetic and bitwise operations.
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