Lab 4
Lab 4
Lab 4
- The caller pushes the function's parameters on the stack, one after another, in
reverse order (right to left, so that the first argument specified to the function is
pushed last).
- The caller then executes a near CALL instruction to pass control to the callee.
- The callee receives control, and typically (although this is not actually necessary, in
functions which do not need to access their parameters) starts by saving the value
of ESP in EBP so as to be able to use EBP as a base pointer to find its parameters
on the stack. However, the caller was probably doing this too, so part of the calling
convention states that EBP must be preserved by any C function. Hence the callee,
if it is going to set up EBP as a frame pointer, must push the previous value first.
- The callee may then access its parameters relative to EBP. The doubleword
at [EBP] holds the previous value of EBP as it was pushed; the next doubleword,
at [EBP+4], holds the return address, pushed implicitly by CALL. The parameters
start after that, at [EBP+8]. The leftmost parameter of the function, since it was
pushed last, is accessible at this offset from EBP; the others follow, at successively
greater offsets. Thus, in a function such as printf which takes a variable number of
parameters, the pushing of the parameters in reverse order means that the function
knows where to find its first parameter, which tells it the number and type of the
remaining ones.
- The callee may also wish to decrease ESP further, so as to allocate space on the
stack for local variables, which will then be accessible at negative offsets
from EBP.
- The callee, if it wishes to return a value to the caller, should leave the value
in AL, AX or EAX depending on the size of the value. Floating-point results are
typically returned in ST0.
- Once the callee has finished processing, it restores ESP from EBP if it had allocated
local stack space, then pops the previous value of EBP, and returns
via RET (equivalently, RETN).
There is an alternative calling convention used by Win32 programs for Windows API
calls, and also for functions called by the Windows API such as window procedures: they
follow what Microsoft calls the __stdcall convention. This is slightly closer to the Pascal
convention, in that the callee clears the stack by passing a parameter to
the RET instruction. However, the parameters are still pushed in right-to-left order.
global _myfunc
_myfunc:
push ebp
mov ebp,esp
sub esp,0x40 ; 64 bytes of local stack space
mov ebx,[ebp+8] ; first parameter to function
At the other end of the process, to call a C function from your assembly code, you would
do something like this:
extern _printf
segment _DATA
myint dd 1234
mystring db 'This number -> %d <- should be
1234',10,0
To get at the contents of C variables, or to declare variables which C can access, you
need only declare the names as GLOBAL or EXTERN. (Again, the names require
leading underscores, as stated in section 9.1.1.) Thus, a C variable declared as int i can
be accessed from assembler as
extern _i
mov eax,[_i]
And to declare your own integer variable which C programs can access as extern int j,
you do this (making sure you are assembling in the _DATA segment, if necessary):
global _j
_j dd 0
To access a C array, you need to know the size of the components of the array. For
example, int variables are four bytes long, so if a C program declares an array as int
a[10], you can access a[3]by coding mov ax,[_a+12]. (The byte offset 12 is obtained by
multiplying the desired array index, 3, by the size of the array element, 4.) The sizes of
the C base types in 32-bit compilers are: 1 for char, 2 for short, 4 for int, long and float,
and 8 for double. Pointers, being 32-bit addresses, are also 4 bytes long.
To access a C data structure, you need to know the offset from the base of the structure
to the field you are interested in. You can either do this by converting the C structure
definition into a NASM structure definition (using STRUC), or by calculating the one
offset and using just that.
To do either of these, you should read your C compiler's manual to find out how it
organizes data structures. NASM gives no special alignment to structure members in its
own STRUC macro, so you have to specify alignment yourself if the C compiler
generates it. Typically, you might find that a structure like
struct {
char c;
int i;
} foo;
might be eight bytes long rather than five, since the int field would be aligned to a four-
byte boundary. However, this sort of feature is sometimes a configurable option in the
C compiler, either using command-line options or #pragma lines, so you have to find
out how your own compiler does it.
5.3. Interfacing to 64-bit C Programs (Unix)
Integer return values are passed in RAX and RDX, in that order.
Floating point is done using SSE registers, except for long double, which is 80 bits
(TWORD) on most platforms (Android is one exception; there long double is 64 bits
and treated the same as double.) Floating-point arguments are passed
in XMM0 to XMM7; return is XMM0 and XMM1. long double are passed on the
stack, and returned in ST0 and ST1.
Integer and SSE register arguments are counted separately, so for the case of
Getting the command line arguments from a DOS program is not an enjoyable
experience, because working with the PSP and having to worry about segments is
simply a pain. In Linux things are much simpler: all arguments are available on the
stack when the program starts, so to get them you just pop them off.
As an example, say you run a program called program and give it three arguments:
./program foo bar 42
The stack will then look as follows:
Now lets write the program program that takes the three arguments:
section .text
global _start
mov eax,1
mov ebx,0
After all that popping, EAX contains the number of arguments, EBX points to
wherever "foo" is stored in memory, ECX points to "bar" and EDX to "42". This is
obviously way more elegant and simple than in DOS. It took us just 5 lines to get the
arguments and even how many there are, while in DOS it takes 14 rather complicated
lines just to get one argument! Note that the 3rd pop overwrites the value we put in
EBX with the 2nd pop (which was the program name). Unless you have a really good
reason, you can usually chuck away the program name as we did here.
6. Macro
- Writing a macro is another way of ensuring modular programming in assembly
language
- NASM doesn't have procedure definitions like you may have used in TASM. That's
because procedures don't really exist in assembly: everything is a label. So if you
want to write a "procedure" in NASM, you don't use proc and endp, but instead just
put a label (eg. fileWrite:) at the beginning of the "procedure's" code. If you want
to, you can put comments at the start and end of the code just to make it look a bit
more like a procedure. Here's an example in both Linux and DOS:
Linux DOS
; proc fileWrite - write a string to a proc fileWrite
file mov ah,40h ; write DOS
fileWrite: service
mov eax,4 ; write mov bx,[filehandle] ; File
system call handle
mov ebx,[filedesc] ; File mov cl,[stuffLen]
descriptor mov dx,offset stuffToWrite
mov ecx,stuffToWrite int 21h
mov edx,[stuffLen] ret
int 80h endp fileWrite
ret
; endp fileWrite
-
8. Nội dung thực hành
8.1. Example 1 :
; ----------------------------------------------------------------
; A 64-bit Linux application that writes the first 90 Fibonacci
numbers. To
; assemble and run:
; nasm -felf64 fib.asm && gcc fib.o && ./a.out
; ----------------------------------------------------------------
global main
extern printf
Thực hành CTMT&HN-2019 Khoa CNTT – ĐHSPKT TP.HCM Trang - 6
section .text
main:
push rbx ; we have to save this since we use it
mov ecx, 90 ; ecx will countdown to 0
xor rax, rax ; rax will hold the current number
xor rbx, rbx ; rbx will hold the next number
inc rbx ; rbx is originally 1
print:
; We need to call printf, but we are using rax, rbx, and
rcx. printf
; may destroy rax and rcx so we will save these before the
call and
; restore them afterwards.
0
1
1
2
.
.
.
679891637638612258
section .text
mov ecx,'4'
mov edx, 1
sum:
ret
section .data
segment .bss
res resb 1
-
8.3. Example 2 (Macro)
- Following example shows defining and using macros
; A macro with two parameters
; Implements the write system call
%macro write_string 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start ;must be declared for using gcc
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
When a function is called the caller will first put the parameters in the correct registers
then issue the callinstruction. Additional parameters beyond those covered by the registers
will be pushed on the stack prior to the call. The call instruction puts the return address on
the top of stack. So if you have the function
Then on entry to the function, x will be in edi, y will be in esi, and the return address will
be on the top of the stack. Where can we put the local variables? An easy choice is on the
stack itself, though if you have enough regsters, use those.
If you are running on a machine that respect the standard ABI, you can leave rsp where it
is and access the "extra parameters" and the local variables directly from rsp for example:
+----------+
rsp-24 | a |
+----------+
rsp-16 | b |
+----------+
rsp-8 | c |
+----------+
rsp | retaddr |
rsp+8 | caller's |
| stack |
| frame |
| ... |
+----------+
global example
section .text
example:
mov qword [rsp-16], 7
mov rax, rdi
imul rax, [rsp+8]
add rax, rsi
ret
If our function were to make another call, you would have to adjust rsp to get out of the
way at that time.
On Windows you can’t use this scheme because if an interrupt were to occur, everything
above the stack pointer gets plastered. This doesn’t happen on most other operating
systems because there is a "red zone" of 128 bytes past the stack pointer which is safe from
these things. In this case, you can make room on the stack immediately:
example:
sub rsp, 24
+----------+
rsp | a |
+----------+
rsp+8 | b |
+----------+
rsp+16 | c |
rsp+24 | retaddr |
+----------+
rsp+32 | caller's |
| stack |
| frame |
| ... |
+----------+
Here’s the function now. Note that we have to remember to replace the stack pointer
before returning!
global example
section .text
example:
sub rsp, 24
mov qword [rsp+8], 7
mov rax, rdi
imul rax, [rsp+8]
add rax, rsi
add rsp, 24
ret
lit5: ; c=5;
mov eax,5 ; 5 is a literal constant
mov [c],eax ; store into c
pabc "c=5 " ; invoke the print macro
addb: ; c=a+b;
mov eax,[a] ; load a
add eax,[b] ; add b
mov [c],eax ; store into c
pabc "c=a+b" ; invoke the print macro
subb: ; c=a-b;
mov eax,[a] ; load a
sub eax,[b] ; subtract b
mov [c],eax ; store into c
pabc "c=a-b" ; invoke the print macro
mulb: ; c=a*b;
mov eax,[a] ; load a (must be eax for multiply)
imul dword [b] ; signed integer multiply by b
mov [c],eax ; store bottom half of product into c
pabc "c=a*b" ; invoke the print macro
diva: ; c=c/a;
mov eax,[c] ; load c
mov edx,0 ; load upper half of dividend with zero
idiv dword [a] ; divide double register edx eax by a
mov [c],eax ; store quotient into c
pabc "c=c/a" ; invoke the print macro
mov eax,0 ; exit code, 0=normal
ret ; main return to operating system
lit5: ; c=5.0;
fld qword [five] ; 5.0 constant
fstp qword [c] ; store into c
pabc "c=5.0" ; invoke the print macro
addb: ; c=a+b;
fld qword [a] ; load a (pushed on flt pt stack, st0)
fadd qword [b] ; floating add b (to st0)
fstp qword [c] ; store into c (pop flt pt stack)
pabc "c=a+b" ; invoke the print macro
subb: ; c=a-b;
fld qword [a] ; load a (pushed on flt pt stack, st0)
fsub qword [b] ; floating subtract b (to st0)
fstp qword [c] ; store into c (pop flt pt stack)
pabc "c=a-b" ; invoke the print macro
mulb: ; c=a*b;
fld qword [a] ; load a (pushed on flt pt stack, st0)
Thực hành CTMT&HN-2019 Khoa CNTT – ĐHSPKT TP.HCM Trang - 17
fmul qword [b] ; floating multiply by b (to st0)
fstp qword [c]; store product into c (pop flt pt stack)
pabc "c=a*b" ; invoke the print macro
diva: ; c=c/a;
fld qword [c] ; load c (pushed on flt pt stack, st0)
fdiv qword [a] ; floating divide by a (to st0)
fstp qword [c]; store quotient into c (pop flt pt stack)
pabc "c=c/a" ; invoke the print macro