CSC373 Oct02

slide version

single file version

Contents

  1. IA32 Assembler Language
  2. Compilation Steps
  3. Preprocessor
  4. Compiler
  5. Assembler
  6. Linker
  7. Generating Assembler Code from C
  8. Example 1
  9. IA32 Assembler Code Generated for the Example
  10. No Source Code
  11. Intro to Intel Assembler: Registers
  12. Register Use
  13. Stack Frames and the Call Stack and Registers
  14. Return Values and Registers
  15. IA32 Assembler for: assignment statements (1)
  16. IA32 Assembler for: assignment statements (1)
  17. IA32 Assembler for: assignment statements (2)
  18. IA32 Assembler for: if statements (0)
  19. IA32 Assembler for: C if statements (1)
  20. IA32 Assembler for: if statements (2)
  21. IA32 Assembler for: if statements (3)
  22. IA32 Assembler for: loop statements (1)
  23. IA32 Assembler for: loop statements (2)
  24. IA32 Assembler for: loop statements (3)
  25. The IA32 movl and addl instructions
  26. The IA32 leal instruction
  27. Memory Operands
  28. Practice: memory operands
  29. Practice: movl and leal

IA32 Assembler Language[1] [top]

Compilers are very good at generating high quality assembler code with options for generating optimized code.

So why learn anything about assembler language?

Compilation Steps[2] [top]

Recall that the C compiler command, gcc actually invokes several other programs to transform a source file sumFirst.c file into a machine executable program file.

  1. preprocessor (cpp): sumFirst.c ==> sumFirst.i

  2. compiler: sumFirst.i ==> sumFirst.s

  3. assembler (as): sumFirst.s ==> sumFirst.o

  4. linker (ld): sumFirst.o plus other object files ==> sumFirst

Preprocessor[3] [top]

C PreProcessor (cpp) is first called to handle #include's, #ifdef, #define, etc preprocessor statements in the source code.

The gcc command can execute the preprocessor and then stop without doing the other steps:

  gcc -E sumFirst.c    
    

This produces the file sumFirst.i

Compiler[4] [top]

The actual C compiler is invoked to translate the output of the preprocessor into an equivalent assembly language program (sumFirst.s). Each assembler instruction corresponds to a machine instruction, but the operation code (op code) is a human readable string such as "addl" or "jmp".

The gcc command can produce the assembly file sumFirst.s and the stop without producing the object file by:

  gcc -S sumFirst.c      
    

Assembler[5] [top]

The assembler (as) translates sumFirst.s to an object file sumFirst.o where each instruction now consists of a sequence of bytes with op codes encoded in as binary. E.g "addl" is replaced by a single byte, for example 0x01, 0x02, ..., or 0x05. Each different op code for "addl" determines what form the two operands will have and where each operand is located (in memory, in a register).

The gcc command can produce the object file sumFirst.o and stop without linking:

  gcc -c sumFirst.c      
    

Linker[6] [top]

The linker (ld) is invoked to combine sumFirst.o with a C runtime object file (crt.o) and pre compiled object code for C library functions such as printf.o to produce an executable file containing the same machine code instructions, but with address references to printf, etc., fixed to their actual offsets within the executable.

Generating Assembler Code from C[7] [top]

The GNU C compiler option -S generates a text file with the assembler code that is generated by the compiler for a C source code file.

$ gcc -S sample.c	
      

This generates a file sample.s

Example 1[8] [top]

    1	int sumFirst(int n)
    2	{
    3	  int ans = 0;
    4	  int i;
    5	  for(i = 1; i <= n; i++) {
    6	      ans += i;
    7	  }
    8	  return ans;
    9	}

In default settings for the gcc C compiler, for loops can't declare loop variables. That is, a compile error is generated for:

      for(int i = 1; i <= n; i++) {
    

This should be valid in the new C standard (C99), but many compilers only partially implement C99 including gcc.

So the gcc compiler default is the earlier standard.

The partial C99 features implemented by gcc are used when the option -std=c99 is present.

IA32 Assembler Code Generated for the Example[9] [top]

_sumFirst:
        pushl   %ebp                ; Set up the stack for
        movl    %esp, %ebp          ; the local variables 
        subl    $8, %esp            ; of the sumFirst function

        ans = 0;
        movl    $0, -4(%ebp)        ; -4(%ebp) is ans
        i = 1;
        movl    $1, -8(%ebp)        ; -8(%ebp) is i
L2:     i <= n
        movl    -8(%ebp), %eax      ; copy i value from memory into register %eax
        cmpl    8(%ebp), %eax       ; 8(%ebp) is n; compare n and i
        jg      L3                  ; jump to end of for loop (location L3) if n > i
        
        ans += i;
        movl    -8(%ebp), %eax      ; copy i to %eax
        leal    -4(%ebp), %edx      ; copy address of ans into %edx
        addl    %eax, (%edx)        ; add i to ans
        
        i++
        leal    -8(%ebp), %eax      ; load address of i in %eax
        incl    (%eax)              ; increment i
        jmp     L2                  ; jump back to label L2 for loop beginning

L3:     return ans;
        movl    -4(%ebp), %eax      ; copy return value, ans, into register %eax
        leave                       ; pop local variables from the stack
        ret                         ; and return to the instruction 
                                    ; after the call to sumFirst
      

No Source Code[10] [top]

What if you don't have the source file (sumFirst.c) but do have the executable file sumFirst or the object file sumFirst.o?

The sumFirst.o file of machine code bytes is generated from sumFirst.s by the assembler that is called by gcc.

In this case you can't use gcc with the -S option since that would require having sumFirst.c.

The general utility od lets you examine the bytes of any file whether they contain printable characters or not.

The od name is short for octal dump, meaning that each byte of a file is displayed in base 8 notation by default. However, you can change that to hex or other formats.

Intro to Intel Assembler: Registers[11] [top]

Registers        Special Use
%eax
%ecx
%edx
%ebx
%esi
%edi
%esp              Stack pointer
%ebp              Frame pointer

%eip              Program counter

Register Use[12] [top]

The arithmetic machine instructions such as addl can't have both its operands be in memory. At least one of them has to be in a register.

The first 6 registers can usually be used as general purpose registers.

Registers        Special Use
%eax
%ecx
%edx
%ebx
%esi
%edi
%esp              Stack pointer
%ebp              Frame pointer

%eip              Program counter

Stack Frames and the Call Stack and Registers[13] [top]

The registers %ebp and %esp are used to hold the addresses of the bottom and the top, respectively of the stack frame of the currently executing function in a program.

Snapshot of memory during execution after main has called sumFirst:

  Linked machine instructions
  Global Initialized Data
  Global Initialized Data
  Area for dynamic allocation (new or malloc)
%esp ->  Stack Frame for sumFirst
%ebp ->  
  Stack Frame for main

Return Values and Registers[14] [top]

When a function returns, the %ebp and %esp registers are reset to point to the stack frame of the calling function.

So effectively, the current stack frame is popped off the stack at the return.

Where is the return value?

Typically an integer return value is placed in a register and this is usally register %eax.

That is, %eax is usually thought of as a general purpose register. But it often has this special use for a function return value.

IA32 Assembler for: assignment statements (1)[15] [top]

int sum(int a, int b)
{
  int ans;
  ans = a + b;
  return ans;
}

Assignment typically uses the mov instruction.

Addition is the addl instruction.

IA32 Assembler for: assignment statements (1)[16] [top]

The return value for a function is always expected to be in register %eax.

So the assembler ret instruction doesn't have an operand. The return value is implicitly whatever is in register %eax.

In the following examples register %eax will be denoted by reg1

int sum(int a, int b)    sum: 			       
{			 						       
  int ans;               reg1 = b		       
  ans = a + b;           add a, reg   // reg = reg + a 
  return ans;            mov reg1, ans		       
}                        mov ans, reg1
                         return                        

IA32 Assembler for: assignment statements (2)[17] [top]

 sum: 		     sum:					            
 		     	pushl	%ebp					
		     	movl	%esp, %ebp	
	       	     	subl	$16, %esp	
 reg1 = b	     	movl	12(%ebp), %eax		       
 add a, reg1   	     	addl	8(%ebp), %eax	
 mov reg1, ans	     	movl	%eax, -4(%ebp)		       
 mov ans, reg1        	movl	-4(%ebp), %eax		       
 return            	leave			          
		     	ret                     

IA32 Assembler for: if statements (0)[18] [top]

There is no direct IA32 assembler instruction for 'if' or 'while'.

The assembler instructions to use are of the following form:

  compare ...               // result of compare is stored somewhere
  if ( result ) goto Label  // conditional jumps
  goto Label                // unconditional jumps

Example: if (x >= y) goto L2

  cmpl y,x
  jge L2

Note the order of the operands of cmpl!!!

IA32 Assembler for: C if statements (1)[19] [top]

<max(int x, int y)>:          int max(int x, int y) 
			      {		      
     int maxVal;	        int maxVal;	      
     if (x >= y) goto L2        if ( x < y ) {       
     maxVal = y		          maxVal = y;	      
     goto L4		        } else {	       
L2:  maxVal = x		          maxVal = x;	      
L4:  return maxVal	        }		       
			        return maxVal;      
			      }		      
			                            
			                          

IA32 Assembler for: if statements (2)[20] [top]

max:				  <max(int x, int y)>:          
	pushl	%ebp		
	movl	%esp, %ebp	
	subl	$16, %esp	
	movl	8(%ebp), %eax	    reg1 = x
	cmpl	12(%ebp), %eax	    [compare y, reg1]
	jge	.L2		    if (reg1 >= y) goto L2
	movl	12(%ebp), %eax	    reg1 = y
	movl	%eax, -4(%ebp)	    maxVal = reg1
	jmp	.L4		    goto L4
.L2:				 L2:                           
	movl	8(%ebp), %eax	    reg1 = x               
	movl	%eax, -4(%ebp)	    maxVal = reg1
.L4:		                 L4:		
	movl	-4(%ebp), %eax	    reg1 = maxVal
	leave			  
	ret                         return 

Notes:

IA32 Assembler for: if statements (3)[21] [top]

Compiled with optimization level 2:

  gcc -S -O2 if1.c
max:				
	pushl	%ebp		
	movl	%esp, %ebp	
	movl	8(%ebp), %edx	  reg2 = x
	movl	12(%ebp), %eax	  reg1 = y
	cmpl	%edx, %eax	  [cmp reg2, reg1]
	jge	.L2		  if (reg1 >= reg2) goto L2
	movl	%edx, %eax	  reg1 = reg2
.L2:				L2:
	popl	%ebp		  
	ret                       return 

IA32 Assembler for: loop statements (1)[22] [top]


int sumFirst(int n)
{
  int sum = 0;
  while(n > 0) {
    sum += n;
    n--;
  }
  return sum;
}

There is no instruction that directly implements 'while' in IA32.

The instructions available are the same form as used for 'if statements':

  compare ...
  if ( result ) goto Label  // conditional jumps
  goto Label                // unconditional jumps

IA32 Assembler for: loop statements (2)[23] [top]


int sumFirst(int n)       sumFirst:		 
{		       						 
  int sum = 0;            sum = 0		 
                          goto L2		 
  while(n > 0)         L3:			 
                          reg1 = n		 
    sum += n;             sum = sum + R[%eax]	 
    n--;                  n = n - 1		 
                       L2:			 
  }                       if(n > 0) goto L3	 
  return sum              reg1 = sum		 
                          return                 
}

IA32 Assembler for: loop statements (3)[24] [top]

sumFirst:                             sumFirst:
	pushl	%ebp
	movl	%esp, %ebp            		 
	subl	$16, %esp          						 
	movl	$0, -4(%ebp)	      sum = 0		 
	jmp	.L2		      goto L2		 
.L3:				   L3:			 
	movl	8(%ebp), %eax	      reg1 = n		 
	addl	%eax, -4(%ebp)	      sum = sum + reg1
	subl	$1, 8(%ebp)	      n = n - 1		 
.L2:				   L2:			 
	cmpl	$0, 8(%ebp)	      [compare 0, n]
	jg	.L3		      if(n > 0) goto L3	 
	movl	-4(%ebp), %eax	      reg1 = sum
	leave
	ret                           return

The IA32 movl and addl instructions[25] [top]

If an operand is in memory, an instruction might want to know just the location (i.e., in order to store some value there).

 movl %eax, -4(%ebp)  ; move contents of %eax to memory location -4(%ebp)

 M[-4(%ebp)] = R[%eax]
    

On the other hand, if the first operand of movl is a memory location, it moves the contents to the destination register.

 movl -4(%ebp), %eax

 R[%eax] = M[-4(%ebp)]     
    

An add instruction would need to know both the values stored at the locations of its operands and the location of the second operand:

 addl %eax, -4(%ebp)  ; add contents of memory register %eax to location -4(%ebp).      

 M[-4(%ebp)] = M[-4(%ebp)] + R[%eax]
    

Note: In both cases these instructions need the contents of the first operand!

The IA32 leal instruction[26] [top]

leal is the Load Effective Address instruction.

The first operand should be a memory location and the second operand should be a register.

However, in contrast to movl and addl, leal moves that memory address to the destination register.

 leal -4(%ebp), %eax

 R[%eax] = -4(%ebp)  ; calculate the address -4(%ebp) and
                     ; store that address in register %eax
    

Compare this with movl:

 movl -4(%ebp), %eax

 R[%eax] = M[-4(%ebp)] ; calculate the address -4(%ebp) and
                       ; store the contents of
                       ; that address in register %eax
    

Memory Operands[27] [top]

Note registers %ebx and %esi are used in the table below just as examples.

However, any other registers can be used in the same way.

Name Form Denotes
Memory Address
Indirect (%ebx) R[%ebx]
base + displacement 8(%ebx) R[%ebx] + 8
Indexed (%ebx, %esi) R[%ebx] + R[%esi])
Indexed 4(%ebx, %esi) R[%ebx] + R[%esi] + 4
Scaled indexed (%ebx, %esi, 4) R[%ebx] + 4 * R[%esi]
Scaled indexed 8(%ebx, %esi, 4) R[%ebx] + 4 * R[%esi] + 8
Scaled indexed 8(,%esi, 4) 4 * R[%esi] + 8

Practice: memory operands[28] [top]

Assume the following register contents:

Register Contents
%eax 0x8000
%edx 3

Each of the following IA32 assembler expressions indicate a memory location. Fill in the address for each one.

Operand Memory Address
(%eax)  
4(%eax)  
-4(%eax)  
9(%eax, %edx)  
(%eax,%edx,1)  
(%eax,%edx,4)  
0x8000(,%edx,8)  

Practice: movl and leal[29] [top]

Given the following register and memory contents,

Register Contents
%edx 0x8000
%ebx 2
Memory Address Contents
0x8000 0x5
0x8004 0xA
0x8008 0xF

fill in the value stored in register %eax after execution of each instruction:

Instruction Value stored in %eax
leal (%edx), %eax  
movl (%edx), %eax  
leal 4(%edx), %eax  
movl 4(%edx), %eax  
leal (%edx, %ebx, 4), %eax  
movl (%edx, %ebx, 4), %eax