In systems programming, it is often necessary to access and control the hardware, such as CPU registers and I/O port locations, etc. In these situations, assembly code becomes necessary. It is therefore important to know how to link C programs with assembly code.
1. Programming in Assembly
C code to Assembly Code
/************* a c file ********************/
#include <stdio.h>
extern int B();
int A(int x, int y)
{
int d, e, f;
d = 4; e = 5; f = 6;
f = B(d,e);
}
Explanations of the Assembly Code
The assembly code generated by GCC consists of three parts:
- Entry: also called the prolog, which establishes stack frame, allocates local variables and working space on stack
- Function body, which performs the function task with return value in AX register
- Exit: also called the epilog, which deallocates stack space and return to caller
The GCC generated assembly code are explained below, along with the stack contents
The entry code first saves FP (%bp) on stack and let FP point at the saved FP of the caller. The stack contents become
Then it shift SP downward 24 bytes to allocate space for locals variables and working area.
While inside a function, FP points at a fixed location and acts as a base register for accessing local variables, as well as parameters. As can be seen, the 3 locals d, e, f, each 4 bytes long, are at the byte offsets -20, -16, -12 from FP. After assigning values to the local variables, the stack contents become
2. Implement Functions in Assembly
Example 1: Get CPU registers. Since these functions are simple, they do not need to establish and deallocate stack frames.
#============== s.s file ===============
.global get_esp, get_ebp
get_esp:
movl %esp, %eax
ret
get_ebp:
movl %ebp, %eax
ret
#======================================
int main()
{
int ebp, esp;
ebp = get_ebp();
esp = get_esp();
printf(“ebp=%8x esp=%8x\n”, ebp, esp);
}
Example 2: Assume int mysum(int x, int y) returns the sum of x and y. Write mysum() function in ASSEMBLY. Since the function must use its parameters to compute the sum, we show the entry, function body and exit parts of the function code.
# ============ mysum.s file ===================
.text # Code section
.global mysum, printf # globals: export mysum, import printf
mysum:
# (1) Entry:(establish stack frame)
pushl %ebp
movl %esp, %ebp
# (2): Function Body Code of mysum: compute x+y in AX register
movl 8(%ebp), %eax # AX = x
addl 12(%ebp), %eax # AX += y
# (3) Exit Code: (deallocate stack space and return)
movl %ebp, %esp
pop %ebp
ret
# =========== end of mysum.s file ==========================
int main() # driver program to test mysum() function
{
int a,b,c;
a = 123;
b = 456;
c = mysum(a, b);
printf(“c=%d\n”, c); // c should be 579
}
3. Call C functions from Assembly
Example 3: Access global variables and call printf()
int a, b; int main()
{
a = 100; b = 2 0 0;
sub();
}
#========== Assembly Code file ===========
.text
.global sub, a, b, printf
sub:
pushl %ebp
movl %esp, %ebp
pushl b
pushl a
pushl $fmt # push VALUE (address) of fmt
call printf # printf(fmt, a, b);
addl $12, %esp
movl %ebp, %esp
popl %ebp
ret
.data
fmt: .asciz “a=%d b=%d\n”
#========================================
Source: Wang K.C. (2018), Systems Programming in Unix/Linux, Springer; 1st ed. 2018 edition.