One comparison, one block – and the loop to go with it: without a label of your own and without writing the jump.
Instead of cmp, a conditional jump and a label you write what you mean: if ebx == [0x04+eax], the block and endif. The assembler writes the comparison and the jump of the opposite condition itself. It is no expression and no macro language: a direct comparison of two operands, and with < and > you say whether it is signed or unsigned.
For .arch i386 (32 bit), anywhere in the code and in a func too. The bytes come from the real assembler.
The block runs only if the comparison holds. The assembler skips it with the jump of the opposite condition: a jne for ==.
if ebx == [0x04+eax] mov dword [0x04+eax], 0xFFFFFFFF endif mov eax, [eax]
cmp ebx, [0x04+eax] jne skip ; the opposite condition mov dword [0x04+eax], 0xFFFFFFFF skip: mov eax, [eax]
3B 58 04 75 07 C7 40 04 FF FF FF FF
8B 00
if, else, do, while)
your lines, unchanged
The assembler makes the label itself (asmogen_if…, it is not shown in the program). The debugger runs the if line, the comparison and the jump, as one step.
if [signed|unsigned] operand comparison operand, the block, optionally else with a second block, and endif. The operands are everything cmp takes: registers, numbers, labels, memory in square brackets.
| You write | Generated and bytes | Note |
|---|---|---|
if eax == ebx nop endif | cmp eax, ebx jne skip nop skip: 3B C3 75 01 90 | equal: cmp, then jne over the block (75 01 skips the one byte of the block) |
if eax != 5 nop endif | cmp eax, 5 je skip nop skip: 83 F8 05 74 01 90 | not equal: je over the block; the right side may be a number |
if dword [0x1000] == 5 nop endif | cmp dword [0x1000], 5 jne skip nop skip: 83 3D 00 10 00 00 05 75 01 90 | memory against a number needs the size (byte, word, dword), as with cmp |
if signed eax < ebx nop endif | cmp eax, ebx jge skip nop skip: 3B C3 7D 01 90 | an ordered comparison says signed or unsigned; signed < jumps over the block with jge |
if unsigned eax >= [ebx] nop endif | cmp eax, [ebx] jb skip nop skip: 3B 03 72 01 90 | memory may stand on one side; unsigned >= jumps over the block with jb |
if, endif)
your lines, unchanged
The comparisons are ==, !=, <, <=, > and >=. In a func the names of the parameters and locals work in the comparison too: if signed eax < b.
The same bits are different in size depending on how you see them: 0xFFFFFFFF is -1 signed and 4294967295 unsigned. So -1 < 1 holds signed and not unsigned. Addresses, lengths and counters are mostly unsigned, numbers with a sign are not – so Asmonite wants the answer in every line (or with .compare, see below). == and != do not need it.
| Comparison | signed | jump over the block | unsigned | jump over the block |
|---|---|---|---|---|
< | if signed eax < ebx | jge 7D 01 | if unsigned eax < ebx | jae 73 01 |
<= | if signed eax <= ebx | jg 7F 01 | if unsigned eax <= ebx | ja 77 01 |
> | if signed eax > ebx | jle 7E 01 | if unsigned eax > ebx | jbe 76 01 |
>= | if signed eax >= ebx | jl 7C 01 | if unsigned eax >= ebx | jb 72 01 |
The jumps in the right columns are those of the opposite condition: they skip the block if the comparison does not hold. Signed they are jl, jle, jg, jge, unsigned jb, jbe, ja, jae.
With & instead of a comparison the if becomes test: the block runs if at least one bit is set in both operands. & is not a calculation here but the whole condition – one &, then at most == 0 or != 0. There is no signed or unsigned word: whether something is zero does not depend on the sign.
| You write | The block runs if … | Generated |
|---|---|---|
if eax & ebx | a bit is set in both | test eax, ebx + jz 85 D8 74 01 |
if eax & ebx != 0 | as above (another way to write it) | test eax, ebx + jz 85 D8 74 01 |
if eax & ebx == 0 | no bit is set in both | test eax, ebx + jnz 85 D8 75 01 |
if al & 0x80 | the top bit of al is set | test al, 0x80 + jz A8 80 74 01 |
if byte [ebx] & 1 | the lowest bit of the byte at [ebx] is set | test byte [ebx], 1 + jz F6 03 01 74 01 |
As with cmp, memory against a number needs the size (byte, word, dword). test has no "register against memory" form, only "memory against register"; for if eax & [ebx] Asmonite swaps the operands by itself (this makes no difference for test). else, endif, nesting and the names of a func work as with every if.
if zeroIf the flags are there already (after dec, sub, add, and ...) there is no need to compare: if names the condition with a word and writes only the jump around the block. The words are those of the jump instructions: zero (also equal), carry, sign, overflow, parity, and above / below (unsigned) and less / greater (signed). not may stand in front, or equal behind the last four.
| You write | The block runs if ... | Generated (with the instruction in front) |
|---|---|---|
dec ecxif zero | the result is 0 | dec ecx + jnz 49 75 01 |
sub eax, 5if not zero | the result is not 0 | sub eax, 5 + jz 83 E8 05 74 01 |
add eax, ecxif carry | there was a carry | add eax, ecx + jnc 03 C1 73 01 |
cmp eax, ebxif below or equal | eax <= ebx, unsigned | cmp + ja 3B C3 77 01 |
cmp eax, ebxif not less | eax >= ebx, signed | cmp + jl 3B C3 7C 01 |
Which flags count is decided by the instruction in front, not by if: if zero after a mov tests the flag of an earlier instruction. There is no signed or unsigned word here, above / below and less / greater say it themselves. else, endif and nesting work as with every if.
do ... whileThe block between do and while runs once and then as long as the condition behind while holds. The condition has the same forms as with if (a comparison, a & b or a flag word); the jump goes back to the do. The assembler makes the label there itself (asmogen_do…).
do dec ecx while not zero
again: dec ecx jnz again ; the condition itself, back
49 75 FD
do inc eax while signed eax < 10
again: inc eax cmp eax, 10 jl again
40 83 F8 0A 7C FA
if, else, do, while)
your lines, unchanged
With while the jump is the condition itself (with if its opposite): while not zero is jnz, while signed eax < 10 is jl. Ordered comparisons need signed or unsigned here too (or a .compare in front). Loops nest in each other and with if; they must lie cleanly inside each other (an endif in an open do is an error). In the debugger the while line, comparison and jump, is one step.
An example with printf is in the ZIP: examples\func\func_loop.asm (the sum of 1 to 10).
If you write a whole piece with signed numbers you need not type signed every time: .compare signed (or .compare unsigned) holds for all if behind it, until the next .compare. .compare none takes it back. A word in the if line always wins.
.compare signed if eax < ebx ; signed (from .compare) nop endif if unsigned eax < ebx ; the word in the line wins nop endif .compare none ; back to: a word is required
3B C3 7D 01 90 3B C3 73 01 90
Without .compare and without a word an ordered comparison is an error – there is no silent default. The directive is shown in the same blue as the keywords, so it stands out in the source.
else adds a jmp over the second block behind the first; an if may stand in an if, every endif closes the innermost open one.
if eax != 0 inc eax else dec eax endif
cmp eax, 0 je else1 ; if not: to else inc eax jmp end1 ; then part done else1: dec eax end1:
83 F8 00 74 03 40 EB 01 48
if eax == 1 if ebx == 2 nop endif endif
cmp eax, 1 jne end1 cmp ebx, 2 jne end2 nop end2: end1:
83 F8 01 75 06 83 FB 02 75 01 90
if, else, do, while)
your lines, unchanged
The example from the ZIP: the larger of two parameters. The names a and b become [ebp+8] and [ebp+12] as usual in func.
func Max(a, b) begin mov eax, a if signed eax < b mov eax, b endif end
Max: push ebp mov ebp, esp mov eax, [ebp+8] cmp eax, dword [ebp+12] jge skip mov eax, [ebp+12] skip: pop ebp ret 8
55 8B EC 8B 45 08 3B 45 0C 7D 03 8B
45 0C 5D C2 08 00
func and if
your line, the names are replaced
The end of the function writes pop ebp and ret 8; an open if in front of an end is an error.
It is a comparison, not an expression: if eax+1 == ebx does not calculate, and there is no && or || (a single & is the bit test above). If you need more, write two if or the jumps yourself. The messages name the line of the source.
| You write | Message | Case |
|---|---|---|
if eax < ebx nop endif | if eax < ebx: say whether it is signed or unsigned (if signed eax < ebx / if unsigned ..., or .compare signed / .compare unsigned before) | an ordered comparison without a word |
if eax = ebx nop endif | use == to compare: if eax == ebx | a single = |
if eax == ebx == ecx nop endif | only one comparison behind if, no expressions | two comparisons |
if eax == ebx
nop | endif is missing for the if in line 1 | endif is missing |
nop
endif | endif without if | endif without if |
if eax == ebx nop else nop else nop endif | a second else for the if in line 1 | a second else |
while eax != 1 | while without do | while without do |
do
nop | while is missing for the do in line 1 | while is missing |
do nop endif | endif without if (the open block is the do in line 1, while is missing) | an endif in an open do |
if foo nop endif | if needs a comparison between two operands: ==, !=, <, <=, > or >=, or a flag condition (zero, carry, ...) | neither a comparison nor a flag word |
For .arch i386, .arch x64, .arch arm64 and .arch thumb. In x64 the assembler still uses the long form for jumps forward (6 instead of 2 bytes). In ARM 64 and Thumb you write the operands in ARM notation (if w0 == 5, if r1 != r2; a number gets its # by itself); the jump is b.ne, b.lt (Thumb: bne, blt) and so on, in Thumb the assembler picks the wide form of the jump by itself when the block is too big, if a & b is tst, there are no memory operands (load first) and no parity. carry is the flag C (after a subtraction C = 1 means nothing was borrowed). A label in front of the line (L: if …) works.
The ZIP has func_if.asm and func_loop.asm (folder examples\func); from the download to the first breakpoint in 60 seconds.