在《32位汇编语言学习笔记(13)--函数的调用》曾分析过c函数的调用过程,对于c函数的默认调用约定cdecl,要求函数参数的压栈顺序是从右向左,由调用方来清理栈。下面示例程序会使用libc库的几个函数:
char *fgets( char *string, int n, FILE *stream );
int printf( const char *format [, argument]... );
int scanf( const char *format [,argument]... );
示例程序如下:
[SECTION .data] ; Section containing initialised data
SPrompt db 'Enter string data, followed by Enter: ',0
IPrompt db 'Enter an integer value, followed by Enter: ',0
IFormat db '%d',0
SShow db 'The string you entered was: %s',10,0
IShow db 'The integer value you entered was: %5d',10,0
[SECTION .bss] ; Section containing uninitialized data
IntVal resd 1 ; Reserve an uninitialized double word
InString resb 128 ; Reserve 128 bytes for string entry buffer
[SECTION .text] ; Section containing code
extern stdin ; Standard file variable for input
extern fgets
extern printf
extern scanf
global main ; Required so linker can find entry point
main:
push ebp ; Set up stack frame for debugger
mov ebp,esp
push ebx ; Program must preserve ebp, ebx, esi, & edi
push esi
push edi
;;; Everything before this is boilerplate; use it for all ordinary apps!
; First, an example of safely limited string input using fgets:
push SPrompt ; Push address of the prompt string
call printf ; Display it
add esp,4 ; Stack cleanup for 1 parm
push dword [stdin] ; Push file handle for standard input
push 72 ; Accept no more than 72 chars from keybd
push InString ; Push address of buffer for entered chars
call fgets ; Call fgets
add esp,12 ; Stack cleanup: 3 parms X 4 bytes = 12
push InString ; Push address of entered string data buffer
push SShow ; Push address of the string display prompt
call printf ; Display it
add esp,8 ; Stack cleanup: 2 parms X 4 bytes = 8
; Next, use scanf() to enter numeric data:
push IPrompt ; Push address of the integer input prompt
call printf ; Display it
add esp,4 ; Stack cleanup for 1 parm
push IntVal ; Push the address of the integer buffer
push IFormat ; Push the address of the integer format string
call scanf ; Call scanf to enter numeric data
add esp,8 ; Stack cleanup: 2 parms X 4 bytes = 8
push dword [IntVal] ; Push integer value to display
push IShow ; Push base string
call printf ; Call printf to convert & display the integer
add esp,8 ; Stack cleanup: 2 parms X 4 bytes = 8
;;; Everything after this is boilerplate; use it for all ordinary apps!
pop edi ; Restore saved registers
pop esi
pop ebx
mov esp,ebp ; Destroy stack frame before returning
pop ebp
ret ; Return control to Linux
程序分析:
这里只分析fgets函数的调用,其他类似。
push dword [stdin] //stdin是libc库中的符号,表示标准输入,实际是一个地址,fgets调用需要的是一个句柄,而不是地址值,因此要通过间接寻址获取句柄值。(对应的入参是stream)
push 72 //从标准输入读取的最大字符串长度是72。
push InString //缓存地址,InString分配的空间是128个字节,所以最大长度设为72是没有问题的。
call fgets //调用fgets
add esp,12 //清理堆栈,压入了3个参数,使用了12个字节。
makefile文件内容:
charsin: charsin.o
gcc charsin.o -o charsin
charsin.o: charsin.asm
nasm -f elf -g -F stabs charsin.asm
测试:
[root@bogon charsin]# make
nasm -f elf -g -F stabs charsin.asm
gcc charsin.o -o charsin
[root@bogon charsin]# ./charsin
Enter string data, followed by Enter: hello world
The string you entered was: hello world
Enter an integer value, followed by Enter: 123456
The integer value you entered was: 123456