0% found this document useful (0 votes)
17 views

X 86 Notes

Uploaded by

Dzogchen
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views

X 86 Notes

Uploaded by

Dzogchen
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Intel x86 Assembly Language Programming

CMST 385 – Systems and Database Administration

Tim Bower, Kansas State Universtity – Polytechnic Campus

The Intel x86 line of CPUs use the accumulator machine model.

Registers
Note that each register has 32 bit, 16 bit and 8 bit names. We will usually use just the 32 bit names for
the registers. See the diagrams of the registers on the following pages.

• The primary accumulator register is called EAX. The return value from a function call is saved in
the EAX register. Secondary accumulator registers are: EBX, ECX, EDX.

• EBX is often used to hold the starting address of an array.

• ECX is often used as a counter or index register for an array or a loop.

• EDX is a general purpose register.

• The EBP register is the stack frame pointer. It is used to facilitate calling and returning from
functions.

• ESI and EDI are general purpose registers. If a variable is to have register storage class, it is
often stored in either ESI or EDI. A few instructions use ESI and EDI as pointers to source and
destination addresses when copying a block of data. Most compilers preserve the value of ESI and
EDI across function calls — not generally true of the accumulator registers.

• The ESP register is the stack pointer. It is a pointer to the “top” of the stack.

• The EFLAGS register is sometimes also called the status register. Several instructions either set or
check individual bits in this register. For example, the sign flag (bit 7) and the zero flag (bit 6) are
set by the compare (cmp) instruction and checked by all the conditional branching instructions.

• The EIP register holds the instruction pointer or program counter (pc), which points to the next
instruction in the text section of the currently running program.

Memory Segmentation and Protection


The earliest processors in the x86 family had 16 bit registers, thus memory addresses were limited to 16
bits (64 Kbytes). This amount of memory is not large enough for both the code and the data of many
programs. The solution was to segment the memory into 64 K blocks. The code goes into one segment,

1
31 0
EAX
(Accumulator)
AH AL

AX

EBX
(Base)
BH BL

BX
ECX
(Count)
CH CL

CX
EDX
(Data)
DH DL

DX

EBP
(Base Pointer)
BP
ESI
(Source Index)
SI
EDI
(Destination Index)
DI
ESP
(Stack Pointer)
SP
General Register Designations for x86 CPUs.

2
0 Carry Flag
Parity Flag Zero Flag (zf)
Auxiliary Flag
Sign Flag (sf)
6
7
Trap Flag
Interrupt Enable
Direction Flag
Overflow Flag
I/O Privilege Level
Nested Task Flag
18 Resume Flag
Virtual 8086 Mode

Note: If not listed means CPU reserved, do not define.

31
EFLAGS Register.

Segment register
Memory
base address (4 MS bits)

address

offset (16 bits) +

Segment

1111111111
0000000000
0000000000
1111111111

Real Mode, Segmented Memory Model.

3
Segment register
GDTR or Memory
LDTR TI Index

GDT
access limit
base address
access limit
base address
access limit
base address
access limit
base address Segment
...

+ 1111111111
0000000000
0000000000
1111111111

address

offset (16 bits or 32 bits)

Protected Mode, Segmented Memory Model.

the data into another, and the stack is placed into a third segment. Each segment is given its own address
space of up to 64 Kbytes in length. The 16–bit addresses used by the program are actually an offset from
a segment base address. This is called real mode, segmented memory model and instructions and data are
referenced relative to a base address held in the segment register (see diagram). The segment registers
are CS (code segment), SS (stack segment), DS, ES, FS, GS (all data segments). The segmented model
increases the addressable memory size to 220 = 1M byte. The segment and offset registers are combined
in an unusual manner. The two registers are offset by four bits and added together to come up with a
20-bit address. This is the memory model used by DOS.
The only advantage to this mode was that it was very easy for developers to write their own device
drivers. Once DOS loaded a program, it stayed out of the way and the program had full control of the
CPU. The program can either let the BIOS handle the interrupts or handle them itself. This worked
great for small programs which could fit into the available memory and did not require multi-tasking.
BIOS: Software in read–only–memory of the computer with basic device drivers and interrupt han-
dlers for I/O devices (keyboard, drives, monitor, printer, mouse). BIOS is used when the computer is
turned on to load the operating system. Modern operating systems (Unix, Linux, Windows) do not use
the BIOS drivers once the operating system is running (booted).
For more demanding applications, the limitations of the real mode scheme were prohibitive. So
beginning with the Intel 80286 processor, a protected mode was also available. In protected mode, these
processors provide the following features:

Protection: Each program can be allocated a certain section of memory. Other programs cannot use

4
this memory, so each program is protected from interference from other programs.

Extended memory: Enables a single program to access more than 640K of memory.

Virtual memory: Expands the address space to 16 MB for 16–bit processors and 4 GB for 32–bit
processors (80386 and later).

Multitasking: Enables the microprocessor to switch from one program to another so the computer can
execute several programs at once.

In the protected mode, segmented memory model, the code segment contains an offset into the global
descriptor table, where more details about the base address and memory protection / limits are stored. A
special register called the GDTR points to the location of the GDT and the segment registers hold offsets
pointing to the desired entry called a segment descriptor in the GDT (see diagram). The Minix OS uses
a protected mode, segmented memory model. Minix boots into this mode and stays in protected mode.
Very complicated articles can be found in literature and on the Internet describing how a DOS program
can switch the processor to protected mode and then return to real mode when the program exits.
Modern x86 based operating systems (Windows and Linux) use a protected mode, flat memory model
where the base memory addresses in the segment descriptors in the GDT are all set to the same value. This
mode greatly simplifies things, making segmentation and memory protection a non-issue for programmers.

Summary
4004 First Intel CPU - 4 bit.

8088 16 bit CPU with 8 bit external data bus. DOS ran in real mode with segments.

8086 16 bit CPU.

80186 Used mainly with embedded systems. Added some new instructions.

80286 Added protected mode. Some versions of Unix (SC0 Xenix, minix) used protected mode with
segments.

80386 32 bit CPU. Windows 3.0, Linux used protected mode flat memory model.

80486 Math co-processor now included on CPU.

Pentium Faster; later Pentiums have a RISC core processor.

IA-64 aka Itanium - 64 bit processor.

Addressing Modes
The addressing mode refers to how operands are referenced in an assembly language instruction. We
will use the mov instruction here to describe the available addressing modes of the x86 family of processors.
The mov instruction copies data between two locations. It’s syntax is shown below — dest and source
represent the operands. Data is copied from the source to the destination.

5
mov dest, source
Register Mode A register mode operand simply names a register. Both operands use register mode
below. Here we copy the contents of register ECX to register EAX. Note that register names are
not case sensitive in the assembly code.
mov EAX, ECX
Immediate Mode An immediate mode operand is a constant listed directly in the code. Below, we
use immediate mode with the second operand to store the value 10 in the EAX register. The
immediate mode operand must be the source operand.
mov EAX, 10
Register Indirect (On SPARC, this same mode is called Register direct.) Here we use a register to
hold a pointer (address in main memory) of where data can be moved to or from. Both operands
of an instruction can not be register indirect — one of the operands must be either register mode
or immediate mode. Brackets are placed around the operand to indicate register indirect. In C
language terminology, brackets may be viewed as the dereference operator. Some compilers use
square brackets, others use parentheses.
mov [EAX], EDX ; contents of edx goes to address pointed to by eax.
mov ebx, [edx] ; data at address pointed to by edx goes to ebx.

; the semicolon designates the beginning of a comment for some


assemblers.
! other assemblers use the exclamation mark for comments.
Base Displacement Constants or offsets of 8–, 16– or 32–bits may also be added to the contents of
a register to come up with an effective address. As shown below, there are several forms of base
displacement. The other operand combined with a base displacement operand must be either
register mode or immediate mode.
mov EBX, 16[EBP] ; data at 16+EBP goes to EBX
mov ebx, [ebp+16] ; same as above
mov ebx, [ebp]16 ; same as above
mov [EDI][EBP], 10 ; 10 goes to EDI+EBP
mov [EDI][EBP+16], 18 ; 18 goes to EDI+EBP+16
The default operation with the mov instruction is to move 32– bits (double word) of data. Some
compilers (MS Visual C++), specify the type of operation even if it is the default.
mov EAX, DWORD PTR [EBX]
There are actually several ways of specifying a smaller quantity of data to be copied. The following
are all examples of instructions which copy 16–bits (word) of data.
mov EAX, WORD PTR [EBX]
mov AX, [EBX]
o16 mov -6(ebp), 3
The keyword byte or the 8–bit designation of a register may be used to copy 8 bits of data.
6
Basic Instructions
In the descriptions of the instructions, the following symbols are used to indicate the accepted addressing
modes.

Operator Type Definition


reg register mode operand
immed immediate mode operand (a constant)
mem operand is a memory address, either register indirect or base displacement operand.

Listed here are only the most commonly used instructions. Information on additional instructions
can be found from the Intel manual (/pub/cis450/Pentium.pdf or /pub/cis450/x86Instructions.ps)

Data Movement Instructions

Instruction Operands Notes


mov reg, immed Copy data
movb reg, reg movb copies one byte
reg, mem destination, source
mem, immed destination is overwritten
mem, reg
movsx reg, immed
reg, reg Copy data with sign extend
reg, mem
movzx reg, immed
reg, reg Copy data with zero extend
reg, mem
push reg Copy data to the top of the stack (esp)
immed The stack pointer (ESP) is decremented by 4 bytes.
pop reg Copy data from the top of the stack to a register
The stack pointer (ESP) is incremented by 4 bytes.
lea reg, mem Load a pointer (memory address) in a register

Integer Arithmetic Instructions


The destination register for all of these instructions must be one of the accumulator registers (EAX,
EBX, ECX, EDX).

7
Instruction Operands Notes
add reg, reg two’s complement addition
reg, immed first operand is used as source and overwritten as destination
reg, mem
sub reg, reg two’s complement subtraction
reg, immed first operand is used as source and overwritten as destination
reg, mem
inc reg increment the value in register
dec reg decrement the value in register
neg reg additive inverse
mul EAX, reg Unsigned multiply
EAX, immed Some compilers tend to use imul instead
EAX, mem
imul reg Signed multiply, EAX*reg → EAX
reg, reg
reg, immed
reg, mem
div reg Unsigned divide
mem EAX / reg,mem; EAX = quotient, EDX = remainder,
idiv reg Signed divide
mem EAX / reg,mem; EAX = quotient, EDX = remainder,

Structure of an assembly language file


In addition to the assembly instructions, there are a few other declarations in an assembly language
program produced by a compiler.
Here we review the elements of an assembly language program. These notes are for the Minix assem-
bler. There may be some variance with other assemblers.

Segment declaration
There are four different assembly segments: text, rom, data and bss. Segments are declared and selected
by the sect pseudo-op. It is customary to declare all segments at the top of an assembly file like this:

.sect .text; .sect .rom; .sect .data; .sect .bss

Then within the body of the code, segment declarations are used to begin the declarations for each
segment. Note that the ’.’ symbol refers to the location in the current segment.

Labels
There are two types: name and numeric. Name labels consist of a name followed by a colon (:).
The numeric labels are single digits. The nearest 0: label may be referenced as 0f in the forward
direction, or 0b backwards.

8
Statement Syntax
Each line consists of a single statement. Blank or comment lines are allowed.
The most general form of an instruction is
label: opcode operand1, operand2 ! comment

Local Variables and the Stack


The stack is used to store local variables. They may be put on the stack with either the push instruction
or by first allocating space on the stack (subtract from esp) and then using the mov instruction to store
data in the allocated space. Here we will show an example of how local variables are used from the stack.
Recall that the stack is upside down from how stacks are normally viewed in that the “top” of the
stack has the lowest memory address of the stack data. The processor maintains a special register (ESP)
which is a pointer to the memory address of the ‘top’ of the stack. Another important register associated
with the stack is the frame pointer (EBP). The frame pointer is sort of a book-mark or reference point
in the stack. Nearly all memory references are relative to the frame pointer. Management of the frame
pointer is critical to how functions are called and more importantly, how the program returns to the
calling function. Function calls will be covered in more detail later.
C compilers implement a restriction that each function may only access (i.e. scope) those elements on
the stack which are within the function’s Activation Record. The Activation Record for each function
includes the following:

function parameters
return address
old frame pointer ←− frame pointer (ebp)
local variables ←− stack pointer (esp)

To set up the frame pointer at the beginning of each function (including main), the following two
lines of assembly code are used.
push ebp
mov ebp,esp
So first, the old frame pointer is pushed onto the stack for use when the function returns to the calling
(parent) function. Then, since the old frame pointer is now at the top of the stack, we can use the pointer
value in the esp register to copy a pointer to where the old frame pointer was stored to the ebp register,
making this the new frame pointer.
Here is a simple example of how local variables in the stack are managed. Try to draw a memory map
of the stack.

Function Calls and the Stack


The stack is also used to store data that is used for making calls to functions. Data is pushed onto the
stack when a function is called and is removed from the stack when the function returns.
9
.sect .text; .sect .rom; .sect .data; .sect .bss
.extern _main
#include <stdio.h> .sect .text
_main:
int main(void) push ebp
{ mov ebp,esp
char c = ’a’; sub esp,12
int i; push esi
short j; movb -1(ebp),97
mov esi,10
i = 10; o16 mov -10(ebp),5
j = 5; movsx eax,-10(ebp)
i += j; add esi,eax
} pop esi
leave
ret

Recall that C compilers implement a restriction that each function may only access (i.e. scope) those
elements on the stack which are within the function’s Activation Record. The Activation Record for
each function includes the following:

function parameters
return address
old frame pointer ←− frame pointer (ebp)
local variables ←− stack pointer (esp)

The steps for a function are the same for every C function. It should be pointed out that this is the
scheme used by compilers. Some assembly programmers follow this scheme for hand written assembly
code. But many assembly programmers never worry about setting the frame pointer.

1. The calling function pushes the function parameters onto the stack prior to the function call.
2. The call instruction pushes the return address (EIP register) onto the stack which is used on function
exit by the ret (return) instruction which loads the EIP register with this address.
3. The function (assembly code) pushes the old frame pointer onto the stack and sets the EBP register
to point to this location on the stack.
push ebp
mov ebp,esp
4. During the execution of the function, the frame pointer is used as a reference point to the rest of
the memory in the activation record. On function exit, the leave instruction loads the EBP register
from this saved value so that when control returns to the calling function, the frame pointer is still
correct.
10
5. Local variables are stored on the stack and are removed from the stack when the function exits.

6. If the function returns data to the calling function, the return value is placed in the EAX register.

7. The calling function removes and discards the function parameters when control is returned from
the function.

8. The calling function looks to the EAX register for a return value.

int main(void)
{
... k | c |
f(a, b, c); j | b |
... i | a |
} | ret addr |
| old fp | <--- fp (ebp)
void f(int i, int j, int k) | x |
{ | y |
int x, y, z; | z | <--- sp (esp)
...
}

Some instructions related to function calls are:

call 1. push eip


2. Jump to the new location (set eip to the location of the instructions for the called function).

leave 1. mov esp,ebp — throw away local variables


2. pop ebp — set frame pointer back to old value

ret n 1. pop eip — set pc to return to calling function


2. pop n words and discard — n is almost always 0.

Here is a more extensive example, again try to draw a memory map. Check your memory map with
the memory map posted on the class web page for ar.c. This example includes examples of global and
static data which are saved in the bss and data section of memory.

11
#include <stdio.h>

int gbss;
int gdata = 5;

int f( int, int, int );

int main(void)
{
int lauto1, lauto2, lauto3;
static int lbss;

gbss = 10;
lbss = 20;
lauto1 = f( gdata, gbss, lbss );
lauto2 = 5;
lauto3 = 15;
printf( "%d %d %d\n", lauto1, lauto2, lauto3 );
printf( "%d\n", f( lauto3, lauto2, 5 ));

return 0;
}

int f( int a, int b, int c )


{
static int d;
int e;

d += a + b + c;
e = d*a;
return e;
}

1 .sect .text; .sect .rom; .sect .data; .sect .bss


2 .extern _gdata
3 .sect .data
4 _gdata:
5 .extern _main
6 .data4 5 ! gdata = 5 in data section
7 .sect .text
8 _main:
9 push ebp ! save old frame pointer
10 mov ebp,esp ! new frame pointer goes to ebp
11 sub esp,4 ! lauto1 = -4(ebp)

12
12 push esi ! lauto3 = esi -- note: register without asking
13 push edi ! lauto2 = edi
14 .sect .bss
15 .comm I_1,4 ! 4 bytes in bss (I_1) for static int lbss
16 .sect .text
17 mov (_gbss),10 ! gbss = 10
18 mov edx,20
19 mov (I_1),edx ! lbss (I_1) = edx = 20
20 push edx
21 push (_gbss) ! push params in reverse order
22 push (_gdata)
23 call _f
24 add esp,12 ! remove params from stack
25 mov -4(ebp),eax ! lauto1 = f(...)
26 mov edi,5 ! lauto2 = 5
27 mov esi,15 ! lauto3 = 15
28 push esi
29 push edi ! push params in reverse order
30 push -4(ebp)
31 push I_2 ! format ... "%d %d %d\n"
32 call _printf
33 add esp,16 ! remove params
34 push 5
35 push edi
36 push esi
37 call _f
38 add esp,12 ! remove params
39 push eax ! push return value to stack
40 push I_3 ! format ... "%d\n"
41 call _printf
42 pop ecx
43 pop ecx ! remove params, alternate to ‘add esp,8’
44 xor eax,eax ! return 0
45 pop edi
46 pop esi ! restore registers
47 leave ! restore old frame pointer from stack
48 ret ! return address comes from stack
49 .sect .rom ! rom is part of text
50 I_3:
51 .data4 680997 ! format ... "%d\n"
52 I_2: ! format ... "%d %d %d\n"
53 .data4 622879781
54 .data4 1680154724
55 .extern _f
56 .data4 10

13
57 .sect .text
58 _f:
59 push ebp ! save old frame pointer
60 mov ebp,esp ! new frame pointer goes to ebp
61 sub esp,4 ! e = -4(ebp)
62 .sect .bss
63 .comm I_4,4 ! 4 bytes in bss (I_4) for static int d
64 .sect .text
65 mov edx,12(ebp)
66 add edx,8(ebp) ! add parameters (a, b, c)
67 add edx,16(ebp)
68 add edx,(I_4) ! d += a + b + c
69 mov (I_4),edx
70 imul edx,8(ebp) ! edx = d*a
71 mov eax,edx ! return e; note -- no need to save edx to -4(ebp)
72 leave ! restore old frame pointer from stack
73 ret ! return address comes from stack
74 .extern _gbss
75 .sect .bss
76 .comm _gbss,4 ! 4 bytes in bss for global int lbss
77 .sect .text

Additional Instructions
Logical Instructions

Instruction Operands Notes


not reg logical not (one’s complement operation)
and reg, reg
reg, mem logical and
reg, immed
or reg, reg
reg, mem logical or
reg, immed
xor reg, reg
reg, mem logical xor
reg, immed
cmp reg, reg Compare (dest - source)
reg, mem result in EFLAGS sf and zf
reg, immed see control instructions
mem, immed
test reg, reg
reg, mem logical and, EFLAGS set based on result
reg, immed see control instructions

14
0

Logical Shift Right

Arithmetic Shift Right

Rotate Shift Right

A logical shift moves the bits a set number of positions to the right or left. Positions which are not
filled by the shift operation are filled with a zero bit. An arithmetic shift does the same, except the sign
bit is always retained. This variation allows a shift operation to provide a quick mechanism to either
multiply or divide 2’s–complement numbers by 2.

Instruction Operands Notes


sal reg, immed arithmetic shift left
shl reg, immed logical shift left
sar reg, immed arithmetic shift right
shr reg, immed logical shift right
rol reg, immed rotate shift left
ror reg, immed rotate shift right

15
Example: Multiply and Divide by multiple of 2

Control Instructions
The following instructions are used to implement various control constructs (if, while, do while, for).
Conditional branch instructions follow a cmp or test instruction and evaluate the sign and zero flag (SF,
ZF) bit in the EFLAGS register. For each of these instructions, the operand is the name of a label found
in the assembly code.
See the notes below on control flow for examples of how they are used.

Instruction Operands Notes


jmp label unconditional jump
jg label jump if greater than zero
jnle
jge label jump if greater than or equal to zero
jnl
jl label jump if less than zero
jnge
jle label jump if less than or equal to zero
jng
je label jump if zero
jz
jne label jump if not zero
jnz

Iterative Instructions
The above control instructions can be used to implement looping constructs, but there are also some
special instructions just for the purpose of looping.

Instruction Operands Notes


loop label decrement ecx and if ecx is not equal to zero, jump
loope label jump if ZF in EFLAGS is set and ecx is not equal to zero
ecx is decremented
loopne label jump if ZF in EFLAGS is not set and ecx is not equal to zero
ecx is decremented
rep instruction execute the instruction and decrement ecx until ecx is zero.

String Handling Instructions


These instructions are all used to copy data from one string to another. In each case the source location
is the address in esi while destination is the address in edi. After the move, the esi and edi registers are
either incremented and decremented by the appropriate amount depending on the direction flag (DF) in
the EFLAGS register. If DF is 0 (CLD instruction was executed), the registers are incremented. If DF
is 1 (STD instruction was executed), the registers are decremented.

16
Instruction Notes
movs move one byte from [esi] to [edi]
movsb
movsw move one word (2 bytes) from [esi] to [edi]
movsd move one double word (4 bytes) from [esi] to [edi]

Here is a quick example:

lea edi, -20(ebp) ! destination


lea esi, -40(ebp) ! source
mov ecx,10 ! copy 10 bytes
cld ! increment esi and edi
rep movsb ! move 10 bytes, one at a time

Miscellaneous Instructions

Instruction Notes
cld Clear the direction flag; used with string movement instructions
std Set the direction flag; used with string movement instructions
cli Clear or disable interrupts; Reserved for the OS
sti Set or enable interrupts; Reserved for the OS
nop no operation, used to make a memory location addressable

17
Input/Output Instructions

Instruction Operands Notes


in acc,port Read data in and save to eax, ax or al.
The port is the base memory address for
the hardware being read from (eg., a sound
card).
out acc,port Write data in eax, ax or al to an I/O port.
insb
insw Read string data in and save to memory.
The I/O port is taken from the edx register
(eg., a keyboard or serial port). The des-
tination is taken from the edi register. If
used in a loop or with rep, the destination
address is incremented or decremented de-
pending on the direction flag.
outsb
outsw Write string data from memory to I/O
port. The I/O port is taken from the edx
register (eg., a keyboard or serial port).
The source is taken from the esi register.
If used in a loop or with rep, the source
address is incremented or decremented de-
pending on the direction flag.

Control Flow
In assembly language, the instructions used to implement control constructs is the various forms of the
jump instructions. This is usually accomplished with a comparison (cmp) instruction to evaluate a logical
expression following a conditional jump instruction.

if block

false
expr

if( expr ) { body


body
}
18
Note that in the assembly language code, the 28 neg eax ; b -> eax
jump is made if we will not execute the body; 29 sub DWORD PTR _a$[ebp], eax
therefore, the jump statement chosen tests if the 30
expr evaluates to false. 31 ; 8 : c++;
32
33 inc DWORD PTR _c$[ebp]
main() 34 $L28:
{ 35 $L24:
int a, b, c; 36
37 ; 9 : }
if (a <= 17) { 38 ; 10 : }
a = a - b; 39
c++; 40 pop edi
} 41 pop esi
} 42 pop ebx
43 leave
44 ret 0
45 _main ENDP
46 _TEXT ENDS
1 PUBLIC _main
47 END
2 _TEXT SEGMENT
3 _a$ = -4
4 _b$ = -8
5 _c$ = -12
6 _main PROC NEAR
7
8 ; 3 : {
9
10 push ebp
11 mov ebp, esp
12 sub esp, 12
13 push ebx
14 push esi
15 push edi
16
17 ; 4 : int a, b, c; if else
18 ; 5 :
19 ; 6 : if (a <= 17) {
20
21 cmp DWORD PTR _a$[ebp], 17
22 jg $L28
23 if( expr ) {
24 ; 7 : a = a - b; body1
25 } else {
26 xor eax, eax ; 0 -> eax body2
27 sub eax, DWORD PTR _b$[ebp] }
19
7
8 ; 2 : void main(){
9
10 push ebp
false 11 mov ebp, esp
expr
12 sub esp, 12
13 push ebx
14 push esi
15 push edi
16
17 ; 3 : int a, b, c;
body1
18 ; 4 :
19 ; 5 : if (a <= 17) {
20
21 cmp DWORD PTR _a$[ebp], 17
22 jg $L28
23
24 ; 6 : a = a - b;
body2 25
26 xor eax, eax
27 sub eax, DWORD PTR _b$[ebp]
28 neg eax
29 sub DWORD PTR _a$[ebp], eax
30
main()
31 ; 7 : c++;
{
32
int a, b, c;
33 inc DWORD PTR _c$[ebp]
34
if (a <= 17) {
35 ; 8 : } else {
a = a - b;
36
c++;
37 jmp $L29
} else {
38 $L28:
b = a;
39
c = b;
40 ; 9 : b = a;
}
41
}
42 mov eax, DWORD PTR _a$[ebp]
43 mov DWORD PTR _b$[ebp], eax
44
45 ; 10 : c = b;
46
1 PUBLIC _main
47 mov eax, DWORD PTR _b$[ebp]
2 _TEXT SEGMENT
48 mov DWORD PTR _c$[ebp], eax
3 _a$ = -4
49 $L29:
4 _b$ = -8
50 $L24:
5 _c$ = -12
51
6 _main PROC NEAR
20
52 ; 11 : }
53 ; 12 : }
1 PUBLIC _main
54
2 _TEXT SEGMENT
55 pop edi
3 _a$ = -4
56 pop esi
4 _b$ = -8
57 pop ebx
5 _c$ = -12
58 leave
6 _main PROC NEAR
59 ret 0
7
60 _main ENDP
8 ; 2 : {
61 _TEXT ENDS
9
62 END
10 push ebp
11 mov ebp, esp
while loop 12 sub esp, 12
13 push ebx
while( expr ) { 14 push esi
body 15 push edi
} 16 $L29:
17
18 ; 3 : int a, b, c;
19 ; 4 :
20 ; 5 : while (a <= 17) {
21
false 22 cmp DWORD PTR _a$[ebp], 17
expr
23 jg $L30
24
25 ; 6 : a = a - b;
26
27 xor eax, eax
body 28 sub eax, DWORD PTR _b$[ebp]
29 neg eax
30 sub DWORD PTR _a$[ebp], eax
31
32 ; 7 : c++;
33
34 inc DWORD PTR _c$[ebp]
35
main() 36 ; 8 : }
{ 37
int a, b, c; 38 jmp $L29
39 $L30:
while (a <= 17) { 40 $L24:
a = a - b; 41
c++; 42 ; 9 : }
} 43
} 44 pop edi
21
45 pop esi 4 _b$ = -8
46 pop ebx 5 _c$ = -12
47 leave 6 _main PROC NEAR
48 ret 0 7
49 _main ENDP 8 ; 2 : {
50 _TEXT ENDS 9
51 END 10 push ebp
11 mov ebp, esp
do loop 12 sub esp, 12
13 push ebx
do { 14 push esi
body 15 push edi
} while( expr ); 16 $L28:
17
18 ; 3 : int a, b, c;
19 ; 4 :
20 ; 5 : do {
body 21 ; 6 : a = a - b;
22
23 xor eax, eax
24 sub eax, DWORD PTR _b$[ebp]
25 neg eax
26 sub DWORD PTR _a$[ebp], eax
true 27
expr
28 ; 7 : c++;
29
30 inc DWORD PTR _c$[ebp]
false
31 $L29:
32
33 ; 8 : } while (a <= 17);
main()
34
{
35 cmp DWORD PTR _a$[ebp], 17
int a, b, c;
36 jle $L28
37 $L30:
do {
38 $L24:
a = a - b;
39
c++;
40 ; 9 : }
} while (a <= 17);
41
}
42 pop edi
43 pop esi
44 pop ebx
45 leave
46 ret 0
1 PUBLIC _main
47 _main ENDP
2 _TEXT SEGMENT
48 _TEXT ENDS
3 _a$ = -4
22
49 END 1 PUBLIC _main
2 _TEXT SEGMENT
3 _a$ = -4
for loop
4 _b$ = -8
for( expr1; expr2; expr3 { 5 _c$ = -12
body 6 _i$ = -16
} 7 _main PROC NEAR
8
9 ; 3 : {
expr1 10
11 push ebp
12 mov ebp, esp
13 sub esp, 16
expr3 14 push ebx
15 push esi
16 push edi
17
18 ; 4 : int a, b, c;
false 19 ; 5 : int i;
expr2 20 ; 6 :
21 ; 7 : for (i = 1; i <= 17; ++i) {
22
23 mov DWORD PTR _i$[ebp], 1
24 jmp $L29
body 25 $L30:
26 inc DWORD PTR _i$[ebp]
27 $L29:
28 cmp DWORD PTR _i$[ebp], 17
29 jg $L31
30
31 ; 8 : a = a - b;
32
main() 33 xor eax, eax
{ 34 sub eax, DWORD PTR _b$[ebp]
int a, b, c; 35 neg eax
int i; 36 sub DWORD PTR _a$[ebp], eax
37
for (i = 1; i <= 17; i++) { 38 ; 9 : c++;
a = a - b; 39
c++; 40 inc DWORD PTR _c$[ebp]
} 41
} 42 ; 10 : }
43
44 jmp $L30
45 $L31:

23
46 $L24: 8
47 9 push ebp
48 ; 11 : } 10 mov ebp, esp
49 11 sub esp, 12
50 pop edi 12 push ebx
51 pop esi 13 push esi
52 pop ebx 14 push edi
53 leave 15
54 ret 0 16 ; 3 : int i;
55 _main ENDP 17 ; 4 : int j;
56 _TEXT ENDS 18 ; 5 :
57 END 19 ; 6 : switch(i) {
20
switch 21 mov eax, DWORD PTR _i$[ebp]
22 mov DWORD PTR -12+[ebp], eax
Switch statements are implemented differently de- 23 jmp $L27
pending on the number of branches (case state- 24 $L31:
ments) in the switch structure. 25
In the following example, the number of 26 ; 7 : case 1: j = 1; break;
branches is small and the compiler puts the test 27
variable on the stack at -12[ebp] and uses a sequence 28 mov DWORD PTR _j$[ebp], 1
of cmp and jump statements. 29 jmp $L28
30 $L32:
main() 31
{ 32 ; 8 : case 2: j = 2; break;
int i; 33
int j; 34 mov DWORD PTR _j$[ebp], 2
35 jmp $L28
switch(i) { 36 $L33:
case 1: j = 1; break; 37
case 2: j = 2; break; 38 ; 9 : case 3: j = 3; break;
case 3: j = 3; break; 39
default: j = 4; 40 mov DWORD PTR _j$[ebp], 3
} 41 jmp $L28
} 42 $L34:
43
44 ; 10 : default: j = 4;
45
46 mov DWORD PTR _j$[ebp], 4
1 PUBLIC _main
47
2 _TEXT SEGMENT
48 ; 11 : }
3 _i$ = -4
49
4 _j$ = -8
50 jmp $L28
5 _main PROC NEAR
51 $L27:
6
52 cmp DWORD PTR -12+[ebp], 1
7 ; 2 : {
24
53 je $L31
54 cmp DWORD PTR -12+[ebp], 2
1 PUBLIC _main
55 je $L32
2 ; COMDAT _main
56 cmp DWORD PTR -12+[ebp], 3
3 _TEXT SEGMENT
57 je $L33
4 _i$ = -4
58 jmp $L34
5 _j$ = -8
59 $L28:
6 _main PROC NEAR
60 $L24:
7
61
8 ; 2 : {
62 ; 12 : }
9
63
10 push ebp
64 pop edi
11 mov ebp, esp
65 pop esi
12 sub esp, 76
66 pop ebx
13 push ebx
67 leave
14 push esi
68 ret 0
15 push edi
69 _main ENDP
16 lea edi, DWORD PTR [ebp-76]
70 _TEXT ENDS
17 mov ecx, 19
71 END
18 mov eax, -858993460 ; ccccccccH
19 rep stosd
20
21 ; 3 : int i;
The following example, which has a few more 22 ; 4 : int j;
branches, uses a simple jump table to determine 23 ; 5 :
which branch to take. This code also fills an area of 24 ; 6 : switch(i) {
the stack from -76[ebp] to -13[ebp] with alternating 25
ones and zeros (0xcccccccc). I do not know why this 26 mov eax, DWORD PTR _i$[ebp]
is done. It does not appear to accomplish anything. 27 mov DWORD PTR -12+[ebp], eax
28 mov ecx, DWORD PTR -12+[ebp]
int main() 29 sub ecx, 1
{ 30 mov DWORD PTR -12+[ebp], ecx
int i; 31 cmp DWORD PTR -12+[ebp], 7
int j; 32 ja SHORT $L44
33 mov edx, DWORD PTR -12+[ebp]
switch(i) { 34 jmp DWORD PTR $L49[edx*4]
case 1: j = 1; break; 35 $L37:
case 3: j = 3; break; 36
case 8: j = 8; break; 37 ; 7 : case 1: j = 1; break;
case 6: j = 6; break; 38
case 2: j = 2; break; 39 mov DWORD PTR _j$[ebp], 1
case 7: j = 7; break; 40 jmp SHORT $L34
case 4: j = 4; break; 41 $L38:
default: j = 9; break; 42
} 43 ; 8 : case 3: j = 3; break;
} 44
25
45 mov DWORD PTR _j$[ebp], 3 90 pop ebp
46 jmp SHORT $L34 91 ret 0
47 $L39: 92 $L49:
48 93 DD $L37 ; case 1
49 ; 9 : case 8: j = 8; break; 94 DD $L41 ; case 2
50 95 DD $L38 ; case 3
51 mov DWORD PTR _j$[ebp], 8 96 DD $L43 ; case 4
52 jmp SHORT $L34 97 DD $L44 ; case 5 - default
53 $L40: 98 DD $L40 ; case 6
54 99 DD $L42 ; case 7
55 ; 10 : case 6: j = 6; break; 100 DD $L39 ; case 8
56 101 _main ENDP
57 mov DWORD PTR _j$[ebp], 6 102 _TEXT ENDS
58 jmp SHORT $L34 103 END
59 $L41:
60 In the next example, the values in the the case
61 ; 11 : case 2: j = 2; break; statements are not are not close together, so the
62 compiler uses a two stage jump table. One table
63 mov DWORD PTR _j$[ebp], 2 hold an index into the second table which lists the
64 jmp SHORT $L34 location to jump to.
65 $L42:
66 int main()
67 ; 12 : case 7: j = 7; break; {
68 int i;
69 mov DWORD PTR _j$[ebp], 7 int j;
70 jmp SHORT $L34
71 $L43: switch(i) {
72 case 10: j = 1; break;
73 ; 13 : case 4: j = 4; break; case 33: j = 3; break;
74 case 85: j = 8; break;
75 mov DWORD PTR _j$[ebp], 4 case 66: j = 6; break;
76 jmp SHORT $L34 case 20: j = 2; break;
77 $L44: case 79: j = 7; break;
78 case 41: j = 4; break;
79 ; 14 : default: j = 9; break; default: j = 9; break;
80 }
81 mov DWORD PTR _j$[ebp], 9 }
82 $L34:
83
84 ; 16 : }
85
86 pop edi
1 PUBLIC _main
87 pop esi
2 ; COMDAT _main
88 pop ebx
3 _TEXT SEGMENT
89 mov esp, ebp
4 _i$ = -4
26
5 _j$ = -8 50
6 _main PROC NEAR 51 ; 9 : case 85: j = 8; break;
7 52
8 ; 2 : { 53 mov DWORD PTR _j$[ebp], 8
9 54 jmp SHORT $L34
10 push ebp 55 $L40:
11 mov ebp, esp 56
12 sub esp, 76 57 ; 10 : case 66: j = 6; break;
13 push ebx 58
14 push esi 59 mov DWORD PTR _j$[ebp], 6
15 push edi 60 jmp SHORT $L34
16 lea edi, DWORD PTR [ebp-76] 61 $L41:
17 mov ecx, 19 62
18 mov eax, -858993460 ;ccccccccH 63 ; 11 : case 20: j = 2; break;
19 rep stosd 64
20 65 mov DWORD PTR _j$[ebp], 2
21 ; 3 : int i; 66 jmp SHORT $L34
22 ; 4 : int j; 67 $L42:
23 ; 5 : 68
24 ; 6 : switch(i) { 69 ; 12 : case 79: j = 7; break;
25 70
26 mov eax, DWORD PTR _i$[ebp] 71 mov DWORD PTR _j$[ebp], 7
27 mov DWORD PTR -12+[ebp], eax 72 jmp SHORT $L34
28 mov ecx, DWORD PTR -12+[ebp] 73 $L43:
29 sub ecx, 10 74
30 mov DWORD PTR -12+[ebp], ecx 75 ; 13 : case 41: j = 4; break;
31 cmp DWORD PTR -12+[ebp], 75 76
32 ja SHORT $L44 77 mov DWORD PTR _j$[ebp], 4
33 mov eax, DWORD PTR -12+[ebp] 78 jmp SHORT $L34
34 xor edx, edx 79 $L44:
35 mov dl, BYTE PTR $L49[eax] 80
36 jmp DWORD PTR $L50[edx*4] 81 ; 14 : default: j = 9; break;
37 $L37: 82
38 83 mov DWORD PTR _j$[ebp], 9
39 ; 7 : case 10: j = 1; break; 84 $L34:
40 85
41 mov DWORD PTR _j$[ebp], 1 86 ; 16 : }
42 jmp SHORT $L34 87
43 $L38: 88 pop edi
44 89 pop esi
45 ; 8 : case 33: j = 3; break; 90 pop ebx
46 91 mov esp, ebp
47 mov DWORD PTR _j$[ebp], 3 92 pop ebp
48 jmp SHORT $L34 93 ret 0
49 $L39: 94 $L50:

27
95 DD $L37 ; entry 0 - case 10 140 DB 7
96 DD $L41 ; case 20 141 DB 7
97 DD $L38 ; case 33 142 DB 7
98 DD $L43 ; case 41 143 DB 7
99 DD $L40 ; case 66 144 DB 7
100 DD $L42 ; case 79 145 DB 7
101 DD $L39 ; case 85 146 DB 7
102 DD $L44 ; entry 7, default 147 DB 7
103 $L49: 148 DB 7
104 DB 0 ; 10 149 DB 7
105 DB 7 150 DB 7
106 DB 7 151 DB 7
107 DB 7 152 DB 7
108 DB 7 153 DB 7
109 DB 7 154 DB 7
110 DB 7 155 DB 7
111 DB 7 156 DB 7
112 DB 7 157 DB 7
113 DB 7 158 DB 7
114 DB 1 ; 20 159 DB 7
115 DB 7 160 DB 4 ; 66
116 DB 7 161 DB 7
117 DB 7 162 DB 7
118 DB 7 163 DB 7
119 DB 7 164 DB 7
120 DB 7 165 DB 7
121 DB 7 166 DB 7
122 DB 7 167 DB 7
123 DB 7 168 DB 7
124 DB 7 169 DB 7
125 DB 7 170 DB 7
126 DB 7 171 DB 7
127 DB 2 ; 33 172 DB 7
128 DB 7 173 DB 5 ; 79
129 DB 7 174 DB 7
130 DB 7 175 DB 7
131 DB 7 176 DB 7
132 DB 7 177 DB 7
133 DB 7 178 DB 7
134 DB 7 179 DB 6 ; 85
135 DB 3 ; 41 180 _main ENDP
136 DB 7 181 _TEXT ENDS
137 DB 7 182 END
138 DB 7
139 DB 7

28
break, continue 20 ; 7 : for (i = 1; i <= 17; i++) {
21
void main()
22 mov DWORD PTR _i$[ebp], 1
{
23 jmp $L28
int a, b;
24 $L29:
int i;
25 inc DWORD PTR _i$[ebp]
26 $L28:
for (i = 1; i <= 17; i++) {
27 cmp DWORD PTR _i$[ebp], 17
if (a == 0) continue;
28 jg $L30
if (b == 0) break;
29
}
30 ; 8 : if (a == 0) continue;
31
while (i <= 17) {
32 cmp DWORD PTR _a$[ebp], 0
if (a == 0) continue;
33 jne $L31
if (b == 0) break;
34 jmp $L29
}
35 $L31:
36
do {
37 ; 9 : if (b == 0) break;
if (a == 0) continue;
38
if (b == 0) break;
39 cmp DWORD PTR _b$[ebp], 0
} while (i <= 17);
40 jne $L32
}
41 jmp $L30
42 $L32:
43
44 ; 10 : }
45
46 jmp $L29
1 PUBLIC _main
47 $L30:
2 _TEXT SEGMENT
48 $L34:
3 _a$ = -4
49
4 _b$ = -8
50 ; 11 :
5 _i$ = -12
51 ; 12 : while (i <= 17) {
6 _main PROC NEAR
52
7
53 cmp DWORD PTR _i$[ebp], 17
8 ; 3 : {
54 jg $L35
9
55
10 push ebp
56 ; 13 : if (a == 0) continue;
11 mov ebp, esp
57
12 sub esp, 12
58 cmp DWORD PTR _a$[ebp], 0
13 push ebx
59 jne $L36
14 push esi
60 jmp $L34
15 push edi
61 $L36:
16
62
17 ; 4 : int a, b;
63 ; 14 : if (b == 0) break;
18 ; 5 : int i;
64
19 ; 6 :
29
65 cmp DWORD PTR _b$[ebp], 0 88 jne $L42
66 jne $L37 89 jmp $L40
67 jmp $L35 90 $L42:
68 $L37: 91 $L39:
69 92
70 ; 15 : } 93 ; 20 : } while (i <= 17);
71 94
72 jmp $L34 95 cmp DWORD PTR _i$[ebp], 17
73 $L35: 96 jle $L38
74 $L38: 97 $L40:
75 98 $L24:
76 ; 16 : 99
77 ; 17 : do { 100 ; 21 : }
78 ; 18 : if (a == 0) continue; 101
79 102 pop edi
80 cmp DWORD PTR _a$[ebp], 0 103 pop esi
81 jne $L41 104 pop ebx
82 jmp $L39 105 leave
83 $L41: 106 ret 0
84 107 _main ENDP
85 ; 19 : if (b == 0) break; 108 _TEXT ENDS
86 109 END
87 cmp DWORD PTR _b$[ebp], 0

Floating Point Arithmetic


The floating point arithmetic unit, called the floating point unit (FPU), contains eight registers which
function as a stack machine. The register which is currently at the top of the stack is referred to as ST.
All floating point instructions specify operands relative to ST.

30
Floating Point Arithmetic Instructions

Instruction Operands Notes


finit initialize the FPU
fld mem Push data onto the FPU stack
fldz Push 0.0 onto the FPU stack
fst mem Store ST (top of stack) to memory
fstp mem Store ST to memory and pop ST
fadd mem Add data to ST and store result in ST
fsub mem Subtract data from ST and store result in ST
fsubr mem Subtract ST from data and store result in ST
fmul mem Multiply data with ST and store result in ST
fdiv mem Divide ST by data and store result in ST
fdivr mem Divide data by ST and store result in ST
frndint Round ST to an integer and store result in ST
fchs Change the sign of ST (ST = -ST)
fcom mem Compare floating point values, setting FPU flags C0–C3
ftst Compare ST to 0.0, setting FPU flags C0–C3
ftsw AX Copy FPU status word to AX
The following example was generated using the Linux gcc compiler1 ; however, to avoid confusion, I
changed the instruction names and the operand order to be consistent with Intel’s Manual and other x86
C compilers.

# include <stdio.h> 6 .string "%f\n"


7 .text
int main(void) 8 .align 4
{ 9 .globl main
float pi=3.14159; 10 .type main,@function
float r = 0.25; 11 main:
12 push %ebp
printf("%f\n", pi*r*r); 13 mov %ebp,%esp
return 0; 14 sub %esp,8
} 15 mov -4(%ebp),1078530000 ! 0x40490fd0
16 mov -8(%ebp),1048576000 ! 0x3e800000
17 fld -4(%ebp)
18 fmul -8(%ebp)
19 fmul -8(%ebp)
20 sub %esp,8
1 .file "area.c" 21 fstp (%esp)
2 .version "01.01" 22 push $.LC0
3 gcc2_compiled.: 23 call printf
4 .section .rodata 24 add %esp,12
5 .LC0: 25 xor %eax,%eax
1
“gcc -S foo.c” will generate assembly code in foo.s

31
26 jmp .L1 31 .Lfe1:
27 .p2align 4,,7 32 .size main,.Lfe1-main
28 .L1: 33 .ident
29 leave 34 "GCC: (GNU) egcs-2.91.66 19990314/Linux"
30 ret

32

You might also like