You can do three things with (code) labels: transfer control to a label via a (conditional or unconditional) jump instruction, call a label via the call instruction, and take the address of a label. Taking the address of a label is useful when you want to indirectly transfer control to that address
at a later point in your program.
Statement labels you define within a proc/endp procedure are local to that procedure
option casemap:none
.code
hasLocalLbl proc
localStmLbl:
ret
hasLocalLbl endp
; Here is the "asmMain" function.
asmMain proc
asmLocal: jmp asmLocal ;This is okay
jmp localStmtLbl ;Undefined in asmMain
asmMain endp
end
If you really want to access a statement (or any other) label outside a procedure, you can use the option directive to turn off local scope within a section of your program
option noscoped
option scoped
;; example
option casemap:none
.code
hasLocalLbl proc
localStmLbl:
option noscoped
notLocal:
option scoped
isLocal:
ret
hasLocalLbl endp
; Here is the "asmMain" function.
asmMain proc
lea rcx, localStmtLbl ;Generates an error
lea rcx, notLocal ;Assembles fine
lea rcx, isLocal ;Generates an error
asmMain endp
end
Initializing Arrays with Label Addresses
MASM also allows you to initialize quad-word variables with the addresses of statement labels.
option casemap:none
.data
lblsInProc qword globalLbl1, globalLbl2 ;From procWLabels
.code
;procWLabels-
; Just a procedure containing private (lexically scoped)
; and global symbols. This really isn't an executable
; procedure.
procWLabels proc
privateLbl:
nop ;"No operation" just to consume space
option noscoped
globalLbl1: jmp globalLbl2
globalLbl2: nop
option scoped
privateLbl2:
ret
dataInCode qword privateLbl, globalLbl1
qword globalLbl2, privateLbl2
procWLabels endp
end
Trampolines
In the rare case you need to branch to a location beyond the range of the 6-byte jcc instructions you can use a trampoline such as the on below :
jncc skipJmp ; Opposite jump of the one you want to use
jmp destPtr ; JMP PC-relative is also limited to ±2GB
destPtr qword destLbl ; so code must use indirect jump
skipJmp:
The opposite conditional branch transfers control to the normal fall though point in the code. If the condition is true, control transfers to a memory indirect jump that jumps to the original target location via a 64-bit pointer.
Conditional Move Instructions
Because branches can be somewhat expensive to execute, the x86-64 CPUs support a set of conditional move instructions, cmovcc.
In addition, a set of conditional floating-point move instructions (fcmovcc) will move data between ST0 and one of the other FPU registers on the FPU stack. Sadly, these instructions aren’t all that useful in modern programs.
Implementing Common Control Structures in MASM
; if ((x > y) && (z < t)) or (a != b) c = d;
; Implemented as:
; if (a != b) then goto DoIf:
mov eax, a
cmp eax, b
jne DoIf
; if not (x > y) then goto EndOfIf:
mov eax, x
cmp eax, y
jng EndOfIf
; if not (z < t) then goto EndOfIf:
mov eax, z
cmp eax, t
jnl EndOfIf
; THEN block:
DoIf:
mov eax, d
mov c, eax
; End of IF statement.
EndOfIf:
Complex if Statements Using Complete Boolean Evaluation
; if(((x < y) && (z > t)) || (a != b))
; Stmt1
mov eax, x
cmp eax, y
setl bl ; Store x < y in BL
mov eax, z
cmp eax, t
setg bh ; Store z > t in BH
and bl, bh ; Put (x < y) && (z > t) into BL
mov eax, a
cmp eax, b
setne bh ; Store a != b into BH
or bl, bh ; Put (x < y) && (z > t) || (a != b) into BL
je SkipStmt1 ; Branch if result is false
Code for Stmt1 goes here
SkipStmt1:
Short-Circuit Boolean Evaluation
; if((x < y) && (z > t)) then ...
mov eax, x
cmp eax, y
jnl TestFails
mov eax, z
cmp eax, t
jng TestFails
Code for THEN clause of IF statement
TestFails:
; if(ch < 'A' || ch > 'Z')
; then printf("Not an uppercase char");
; endif;
cmp ch, 'A'
jb ItsNotUC
cmp ch, 'Z'
jna ItWasUC
ItsNotUC:
Code to process ch if it's not an uppercase character
ItWasUC:
; if(((x < y) && (z > t)) || (a != b)) Stmt1 ;
mov eax, a
cmp eax, b
jne DoStmt1
mov eax, x
cmp eax, y
jnl SkipStmt1
mov eax, z
cmp eax, t
jng SkipStmt1
DoStmt1:
Code for Stmt1 goes here
SkipStmt1:
Switch Statement
if/else Implementation of switch
; if/then/else/endif form:
mov eax, i
test eax, eax ; Check for 0
jnz Not0
Code to print "i = 0"
jmp EndCase
Not0:
cmp eax, 1
jne Not1
Code to print "i = 1"
jmp EndCase
Not1:
cmp eax, 2
jne EndCase;
Code to print "i = 2"
EndCase:
Indirect Jump switch Implementation
; Indirect Jump Version.
mov eax, i
lea rcx, JmpTbl
jmp qword ptr [rcx][rax * 8]
JmpTbl qword Stmt0, Stmt1, Stmt2
Stmt0:
Code to print "i = 0"
jmp EndCase;
Stmt1:
Code to print "i = 1"
jmp EndCase;
Stmt2:
Code to print "i = 2"
EndCase:
Noncontiguous Jump Table Entries and Range Limiting
; SWITCH statement specifying cases 1, 2, 4, and 8
; with a DEFAULT clause:
mov eax, i
cmp eax, 1
jb DefaultCase
cmp eax, 8 ; Verify that i is in the range
ja DefaultCase ; 1 to 8 before the indirect jmp
lea rcx, JmpTbl
jmp qword ptr [rcx][rax * 8 – 1 * 8] ; 1 * 8 compensates for zero index
JmpTbl qword Stmt1, Stmt2, DefaultCase, Stmt4
qword DefaultCase, DefaultCase, DefaultCase, Stmt8
Stmt1:
Code to print "i = 1"
jmp EndCase
Stmt2:
Code to print "i = 2"
jmp EndCase
Stmt4:
Code to print "i = 4"
jmp EndCase
Stmt8:
Code to print "i = 8"
jmp EndCase
DefaultCase:
Code to print "i does not equal 1, 2, 4, or 8"
EndCase:
Sparse Jump Tables
The current implementation of the switch statement has a problem. If the case values contain nonconsecutive entries that are widely spaced, the jump table could become exceedingly large.
; Assume expression has been calculated into EAX.
cmp eax, 100
jb t
ja try1000_10000
Code to handle case 100 goes here
jmp AllDone
try1_10:
cmp eax,1
je case1
cmp eax, 10
jne defaultCase
Code to handle case 10 goes here
jmp AllDone
case1:
Code to handle case 1 goes here
jmp AllDone
try1000_10000:
cmp eax, 1000
je case1000
cmp eax, 10000
jne defaultCase
Code to handle case 10000 goes here
jmp AllDone
case1000:
Code to handle case 1000 goes here
jmp AllDone
defaultCase:
Code to handle defaultCase goes here
AllDone:
State Machines and Indirect Jumps
Another control structure commonly found in assembly language programs is the state machine. A state machine uses a state variable to control program flow.
So what is a state machine? In basic terms, it is a piece of code that keeps track of its execution history by entering and leaving certain states.
Suppose you have a procedure and want to perform one operation the first time you call it, a different operation the second time you call it, yet something else the third time you call
it, and then something new again on the fourth call. After the fourth call, it repeats these four operations in order.
; A simple state machine example
option casemap:none
nl = 10
.const
fmtStr0 byte "Calling StateMachine, "
byte "state=%d, EAX=5, ECX=6", nl, 0
fmtStr0b byte "Calling StateMachine, "
byte "state=%d, EAX=1, ECX=2", nl, 0
fmtStrx byte "Back from StateMachine, "
byte "state=%d, EAX=%d", nl, 0
fmtStr1 byte "Calling StateMachine, "
byte "state=%d, EAX=50, ECX=60", nl, 0
fmtStr2 byte "Calling StateMachine, "
byte "state=%d, EAX=10, ECX=20", nl, 0
fmtStr3 byte "Calling StateMachine, "
byte "state=%d, EAX=50, ECX=5", nl, 0
.data
state byte 0
.code
externdef printf:proc
StateMachine proc
cmp state, 0
jne TryState1
; State 0: Add ecx to eax and switch to State 1:
add eax, ecx
inc state ; State 0 becomes state 1
jmp exit
TryState1:
cmp state, 1
jne TryState2
; State 1: Subtract ecx from eax and switch to state 2:
sub eax, ecx
inc state ; State 1 becomes state 2.
jmp exit
TryState2: cmp state, 2
jne MustBeState3
; If this is State 2, multiply ecx by eax and switch to state 3:
imul eax, ecx
inc state ; State 2 becomes state 3.
jmp exit
; If it isn't one of the above states, we must be in State 3,
; so divide eax by ecx and switch back to State 0.
MustBeState3:
push rdx ; Preserve this 'cause it gets whacked by div.
xor edx, edx ; Zero extend eax into edx.
div ecx
pop rdx ; Restore edx's value preserved above.
mov state, 0 ; Reset the state back to 0.
exit: ret
StateMachine endp
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbp
mov rbp, rsp
sub rsp, 48 ;Shadow storage
mov state, 0 ;Just to be safe
; Demonstrate state 0:
lea rcx, fmtStr0
movzx rdx, state
call printf
mov eax, 5
mov ecx, 6
call StateMachine
lea rcx, fmtStrx
mov r8, rax
movzx edx, state
call printf
; Demonstrate state 1:
lea rcx, fmtStr1
movzx rdx, state
call printf
mov eax, 50
mov ecx, 60
call StateMachine
lea rcx, fmtStrx
mov r8, rax
movzx edx, state
call printf
; Demonstrate state 2:
lea rcx, fmtStr2
movzx rdx, state
call printf
mov eax, 10
mov ecx, 20
call StateMachine
lea rcx, fmtStrx
mov r8, rax
movzx edx, state
call printf
; Demonstrate state 3:
lea rcx, fmtStr3
movzx rdx, state
call printf
mov eax, 50
mov ecx, 5
call StateMachine
lea rcx, fmtStrx
mov r8, rax
movzx edx, state
call printf
; Demonstrate back in state 0:
lea rcx, fmtStr0b
movzx rdx, state
call printf
mov eax, 1
mov ecx, 2
call StateMachine
lea rcx, fmtStrx
mov r8, rax
movzx edx, state
call printf
leave
ret ;Returns to caller
asmMain endp
end
Technically, this procedure is not the state machine. Instead, the variable state and the cmp/jne instructions constitute the state machine. The procedure is little more than a switch statement implemented via the if/then/elseif construct.
It’s common to use an indirect jump to implement a state machine in assembly language. Rather than having a state variable that contains a value like 0, 1, 2, or 3, we could load the state variable with the address of the code to execute upon entry into the procedure.
; An indirect jump state machine example
option casemap:none
nl = 10
.const
fmtStr0 byte "Calling StateMachine, "
byte "state=0, EAX=5, ECX=6", nl, 0
fmtStr0b byte "Calling StateMachine, "
byte "state=0, EAX=1, ECX=2", nl, 0
fmtStrx byte "Back from StateMachine, "
byte "EAX=%d", nl, 0
fmtStr1 byte "Calling StateMachine, "
byte "state=1, EAX=50, ECX=60", nl, 0
fmtStr2 byte "Calling StateMachine, "
byte "state=2, EAX=10, ECX=20", nl, 0
fmtStr3 byte "Calling StateMachine, "
byte "state=3, EAX=50, ECX=5", nl, 0
.data
state qword state0
.code
externdef printf:proc
; StateMachine version 2.0- using an indirect jump.
option noscoped ;statex labels must be global
StateMachine proc
jmp state
; State 0: Add ecx to eax and switch to State 1:
state0: add eax, ecx
lea rcx, state1
mov state, rcx
ret
; State 1: Subtract ecx from eax and switch to state 2:
state1: sub eax, ecx
lea rcx, state2
mov state, rcx
ret
; If this is State 2, multiply ecx by eax and switch to state 3:
state2: imul eax, ecx
lea rcx, state3
mov state, rcx
ret
state3: push rdx ; Preserve this 'cause it gets whacked by div.
xor edx, edx ; Zero extend eax into edx.
div ecx
pop rdx ; Restore edx's value preserved above.
lea rcx, state0
mov state, rcx
ret
StateMachine endp
option scoped
; Here is the "asmMain" function.
public asmMain
asmMain proc
push rbp
mov rbp, rsp
sub rsp, 48 ;Shadow storage
lea rcx, state0
mov state, rcx ;Just to be safe
; Demonstrate state 0:
lea rcx, fmtStr0
call printf
mov eax, 5
mov ecx, 6
call StateMachine
lea rcx, fmtStrx
mov rdx, rax
call printf
; Demonstrate state 1:
lea rcx, fmtStr1
call printf
mov eax, 50
mov ecx, 60
call StateMachine
lea rcx, fmtStrx
mov rdx, rax
call printf
; Demonstrate state 2:
lea rcx, fmtStr2
call printf
mov eax, 10
mov ecx, 20
call StateMachine
lea rcx, fmtStrx
mov rdx, rax
call printf
; Demonstrate state 3:
lea rcx, fmtStr3
call printf
mov eax, 50
mov ecx, 5
call StateMachine
lea rcx, fmtStrx
mov rdx, rax
call printf
; Demonstrate back in state 0:
lea rcx, fmtStr0b
call printf
mov eax, 1
mov ecx, 2
call StateMachine
lea rcx, fmtStrx
mov rdx, rax
call printf
leave
ret ;Returns to caller
asmMain endp
end
Loops
While loops
mov i, 0
WhileLp:
cmp i, 100
jnl WhileDone
inc i
jmp WhileLp;
WhileDone:
Do-While loops
do_while_loop:
; Body of the loop
mov eax, counter
call PrintNumber ; Assume PrintNumber prints EAX
; Increment counter
inc counter
; Check condition: while (counter < 5)
mov eax, counter
cmp eax, 5
jl do_while_loop ; Jump if less than 5
ret
main ENDP
; Dummy print procedure for illustration
PrintNumber PROC
; Implement your print logic here
ret
PrintNumber ENDP
forever/endfor Loops
foreverLabel:
call getchar ; Assume it returns char in AL
cmp al, '.'
je ForIsDone
mov cl, al ; Pass char read from getchar to putchar
call putcchar ; Assume this prints the char in CL
jmp foreverLabel
ForIsDone:
for Loops
xor rbx, rbx ; Use RBX to hold loop index
WhileLp:
cmp ebx, 7
jnl EndWhileLp
lea rcx, fmtStr ; fmtStr = "Array Element = %d", nl, 0
lea rdx, S
mov rdx, [rdx][rbx * 4] ; Assume SomeArray is 4-byte ints
call printf
inc rbx
jmp WhileLp;
EndWhileLp:
The break and continue Statements
To convert a break statement to pure assembly language, just emit a goto/jmp instruction that transfers control to the first statement following the end of the loop to exit. You can do this by placing a label after the loop body and jumping to that label.
// Breaking out of a FOR(;;) loop:
for (;;) {
// Statements before break
// break;
goto BreakFromForever;
// Statements after break (unreachable)
}
BreakFromForever:
// Breaking out of a FOR loop:
for (initStmt; expr; incStmt) {
// Statements before break
// break;
goto BrkFromFor;
// Statements after break (unreachable)
}
BrkFromFor:
// Breaking out of a WHILE loop:
while (expr) {
// Statements before break
// break;
goto BrkFromWhile;
// Statements after break (unreachable)
}
BrkFromWhile:
// Breaking out of a REPEAT/UNTIL loop (similar to DO/WHILE):
do {
// Statements before break
// break;
goto BrkFromRpt;
// Statements after break (unreachable)
} while (!expr);
BrkFromRpt:
In pure assembly language, convert the appropriate control structures to assembly and replace the goto with a jmp instruction.
; for(;;)/continue/endfor
; Conversion of FOREVER loop with continue
; to pure assembly:
for(;;)
{
Stmts
continue;
Stmts
}
; Converted code:
foreverLbl:
Stmts
; continue;
jmp foreverLbl
Stmts
jmp foreverLbl
______________________________________________________________________
; while/continue/endwhile
; Conversion of WHILE loop with continue
; into pure assembly:
while(expr)
{
Stmts
continue;
Stmts
}
; Converted code:
whlLabel:
Code to evaluate expr
jcc EndOfWhile ; Skip loop on expr failure
Stmts
; continue;
jmp whlLabel ; Jump to start of loop on continue
Stmts
jmp whlLabel ; Repeat the code
EndOfWhile:
______________________________________________________________________
; for/continue/endfor
; Conversion for a FOR loop with continue
; into pure assembly:
for(initStmt; expr; incStmt)
{
Stmts
continue;
Stmts
}
; Converted code:
initStmt
ForLpLbl:
Code to evaluate expr
jcc EndOfFor ; Branch if expression fails
Stmts
; continue;
jmp ContFor ; Branch to incStmt on continue
Stmts
ContFor:
incStmt
jmp ForLpLbl
EndOfFor:
______________________________________________________________________
;repeat/continue/until
repeat
Stmts
continue;
Stmts
until(expr);
do
{
Stmts
continue;
Stmts
} while (!expr);
; Converted code:
RptLpLbl:
Stmts
; continue;
jmp ContRpt ; Continue branches to termination test
Stmts
ContRpt:
Code to test expr
jcc RptLpLbl ; Jumps if expression evaluates false
Registers in loops
Registers are more efficient than memory locations and are perfect for manipulating data. But due to their limited availability, they must used alongside the stack. Whenever a register is to be accessed, it's value must be preserved and should be restored after we are done with it.
mov cx, 8
loop1:
push rcx
mov cx, 4
loop2:
Stmts
dec cx
jnz loop2;
pop rcx
dec cx
jnz loop1
;; or
mov dx,8
loop1:
mov cx, 4
loop2:
Stmts
dec cx
jnz loop2
dec dx
jnz loop1
Register corruption is one of the primary sources of bugs in loops in assembly language programs, so always keep an eye out for this problem.
Loop Performance Improvements
Moving the Termination Condition to the End of a Loop.
Executing the Loop Backward
Using Loop-Invariant Computations - A loop-invariant computation is a calculation that appears within a loop that always yields the same result. You needn’t do such computations inside the
loop. You can compute them outside the loop and reference the value of the computations inside the loop.
Using Induction Variables - you can reduce the execution time of the body of the loop by using induction variables. An induction variable is one whose value depends entirely on the value of another variable.