32位汇编语言学习笔记(45)--测试简单文件操作接口(完)



这是《Assembly Language step by step programming with linux》书中的最后一个程序,也是全书中的最复杂的一个程序。
首先看一下这个程序使用的一些新的c接口:
FILE *fopen( const char *filename, const char *mode );
int fclose( FILE *stream );
char *fgets( char *string, int n, FILE *stream );
int fprintf( FILE *stream, const char *format [, argument ]...);
int sscanf( const char *buffer, const char *format [, argument ] ... );

此程序分成两个文件:linlib.asm和textfile.asm。linlib.asm实现几个公共函数,主程序在textfile.asm中。
linlib.asm文件内容如下:

[SECTION .data]		; Section containing initialised data

[SECTION .bss]		; Section containing uninitialized data

[SECTION .text]		; Section containing code

extern printf		; All of these are in the standard C library glibc	
extern rand	
extern srand
extern time
		
global seedit		; Seeds the random number generator with a time value
global pull31		; Pulls a 31-bit random number
global pull16		; Pulls a 16-bit random number; in the range 0-65,535
global pull8		; Pulls an 8-bit random number; in the range 0-255
global pull7		; Pulls a 7-bit random number; in the range 0-127
global pull6		; Pulls a 6-bit random number; in the range 0-63
global pull4		; Pulls a (marginal) 4-bit random number; range 0-15
global newline		; Outputs a specified number of newlines to stdout

pull31: mov ecx,0		; For 31 bit random, we don't shift
	jmp pull
pull16: mov ecx,15		; For 16 bit random, shift by 15 bits
	jmp pull
pull8:	mov ecx,23		; For 8 bit random, shift by 23 bits
	jmp pull
pull7:  mov ecx,24		; For 7 bit random, shift by 24 bits
	jmp pull
pull6:	mov ecx,25		; For 6 bit random, shift by 25 bits
	jmp pull
pull4:	mov ecx,27		; For 4 bit random, shift by 27 bits
pull:	push ecx		; rand trashes ecx; save shift value on stack
	call rand		; Call rand for random value; returned in eax
	pop ecx			; Pop stashed shift value back into ECX
	shr eax,cl		; Shift the random value by the chosen factor
				;  keeping in mind that part we want is in CL
	ret			; Go home with random number in eax
	
seedit:	push dword 0		; Push a 32-bit null pointer to stack, since
				;  we don't need a buffer. 
	call time		; Returns time_t value (32-bit integer) in eax
	add esp,4		; Clean up stack
	push eax		; Push time value in eax onto stack
	call srand		; Time value is the seed value for random gen.
	add esp,4		; Clean up stack
	ret			; Go home; no return values

newline:
	mov ecx,10		; We need a skip value, which is 10 minus the
	sub ecx,eax		;  number of newlines the caller wants.
	add ecx,nl		; This skip value is added to the address of
	push ecx		;  the newline buffer nl before calling printf.
	call printf		; Display the selected number of newlines
	add esp,4		; Clean up the stack
	ret			; Go home
nl	db 10,10,10,10,10,10,10,10,10,10,0	

程序分析:
这个文件包含的几个函数在《32位汇编语言学习笔记(43)--生成随机数》一文中都有分析:
pull函数用于产生规定位数的随机数。
seedit函数用于设置随机数种子的初值。
newline函数用于根据eax值,打印换行符的个数,比如eax=1,就打印一个换行符。

textfile.asm是主程序:

[SECTION .data]			; Section containing initialised data
		
IntFormat   dd '%d',0
WriteBase   db 'Line #%d: %s',10,0	
NewFilename db 'testeroo.txt',0			
DiskHelpNm  db 'helptextfile.txt',0
WriteCode   db 'w',0
OpenCode    db 'r',0			
CharTbl	    db '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-@'	
Err1        db 'ERROR: The first command line argument must be an integer!',10,0
HelpMsg     db 'TEXTTEST: Generates a test file.  Arg(1) should be the # of ',10,0
HELPSIZE    EQU $-HelpMsg
            db 'lines to write to the file.  All other args are concatenated',10,0
            db 'into a single line and written to the file.  If no text args',10,0
            db 'are entered, random text is written to the file.  This msg  ',10,0
            db 'appears only if the file HELPTEXTFILE.TXT cannot be opened. ',10,0
HelpEnd     dd 0
		
[SECTION .bss]			; Section containing uninitialized data

LineCount   resd 1		; Reserve integer to hold line count
HELPLEN     EQU 72		; Define length of a line of help text data
HelpLine    resb HELPLEN	; Reserve space for disk-based help text line
BUFSIZE     EQU 64		; Define length of text line buffer buff
Buff        resb BUFSIZE+5	; Reserve space for a line of text 
		
[SECTION .text]			; Section containing code

;; These externals are all from the glibc standard C library:	 
extern fopen
extern fclose
extern fgets	
extern fprintf
extern printf		
extern sscanf
extern time

;; These externals are from the associated library linlib.asm:
extern seedit			; Seeds the random number generator
extern pull6			; Generates a 6-bit random number from 0-63
extern newline			; Outputs a specified number of newline chars

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!	

	call seedit		; Seed the random number generator
	
	;; First test is to see if there are command line arguments at all.
	;; If there are none, we show the help info as several lines.  Don't
	;; forget that the first arg is always the program name, so there's
	;; always at least 1 command-line argument!
	mov eax,[ebp+8]		; Load argument count from stack into EAX
	cmp eax,1		; If count is 1, there are no args
	ja chkarg2		; Continue if arg count is > 1
	mov ebx,DiskHelpNm	; Put address of help file name in ebx 
	call diskhelp		; If only 1 arg, show help info...
	jmp gohome		; ...and exit the program
	
	;; Next we check for a numeric command line argument 1:
chkarg2:	
	mov ebx,[ebp+12]	; Put pointer to argument table into ebx
	push LineCount		; Push address of line count integer for sscanf
	push IntFormat		; Push address of integer formatting code
	push dword [ebx+4]	; Push pointer to arg(1)
	call sscanf		; Call sscanf to convert arg(1) to an integer
	add esp,12		; Clean up the stack
	cmp eax,1		; Return value of 1 says we got a number
	je chkdata		; If we got a number, go on; else abort
	mov eax,Err1		; Load eax with address of error message #1
	call showerr		; Show the error message
	jmp gohome		; Exit the program

	;; Here we're looking to see if there are more arguments.  If there
	;; are, we concatenate them into a single string no more than BUFSIZE
	;; chars in size.  (Yes, I *know* this does what strncat does...)
chkdata:
	cmp dword [ebp+8],3	; Is there a second argument?
	jae getlns		; If so, we have text to fill a file with
	call randline		; If not, generate a line of random text
	                        ; Note that randline returns ptr to line in esi
	jmp genfile		; Go on to create the file

	;; Here we copy as much command line text as we have, up to BUFSIZE
	;; chars, into the line buffer buff. We skip the first two args
	;; (which at this point we know exist) but we know we have at least
	;; one text arg in arg(2).  Going into this section, we know that
	;; ebx contains the pointer to the arg table. All other bets are off.
getlns:	mov edx,2		; We know we have at least arg(2), start there
	mov edi,Buff		; Destination pointer is start of char buffer
	xor eax,eax		; Clear eax to 0 for the character counter
	cld			; Clear direction flag for up-memory movsb

grab:	mov esi,[ebx+edx*4]	; Copy pointer to next arg into esi
.copy:  cmp byte [esi],0	; Have we found the end of the arg?
	je .next		; If so, bounce to the next arg
	movsb			; Copy char from [esi] to [edi]; inc edi & esi
	inc eax			; Increment total character count
	cmp eax,BUFSIZE		; See if we've filled the buffer to max count
	je addnul		; If so, go add a null to buff & we're done
	jmp .copy
	
.next:	mov byte [edi],' '	; Copy space to buff to separate args
	inc edi			; Increment destion pointer for space
	inc eax			; Add one to character count too
	cmp eax,BUFSIZE		; See if we've now filled buff
	je addnul		; If so, go down to add a nul and we're done
	inc edx			; Otherwise, increment the argument count
	cmp edx, dword [ebp+8]	; Compare against argument count
	jae addnul		; If edx = arg count, we're done
	jmp grab		; And go back and copy it

addnul:	mov byte [edi],0	; Tuck a null on the end of buff
	mov esi,Buff		; File write code expects ptr to text in esi
	
	;; Now we create a file to fill with the text we have:	
genfile:
	push WriteCode		; Push pointer to file write/create code ('w')
	push NewFilename	; Push pointer to new file name
	call fopen		; Create/open file
	add esp,8		; Clean up the stack
	mov ebx,eax		; eax contains the file handle; save in ebx

	;; File is open.  Now let's fill it with text:	
	mov edi,[LineCount]	; The number of lines to be filled is in edi

	push esi		; esi is the pointer to the line of text
	push 1			; The first line number
	push WriteBase		; Push address of the base string
	push ebx		; Push the file handle of the open file
	
writeline:
	cmp dword edi,0		; Has the line count gone to 0?
	je donewrite		; If so, go down & clean up stack
	call fprintf		; Write the text line to the file
	dec edi			; Decrement the count of lines to be written
	add dword [esp+8],1	; Update the line number on the stack
	jmp writeline		; Loop back and do it again
donewrite:		
	add esp,16		; Clean up stack after call to fprintf
	
	;; We're done writing text; now let's close the file:
closeit:	
	push ebx		; Push the handle of the file to be closed
	call fclose		; Closes the file whose handle is on the stack
	add esp,4
	
	;;; Everything after this is boilerplate; use it for all ordinary apps!
gohome:	pop edi			; Restore saved registers
	pop esi
	pop ebx
	mov esp,ebp		; Destroy stack frame before returning
	pop ebp
	ret			; Return control to to the C shutdown code

diskhelp:
	push OpenCode		; Push pointer to open-for-read code "r"
	push ebx		; Pointer to name of help file is passed in ebx
	call fopen		; Attempt to open the file for reading
	add esp,8		; Clean up the stack
	cmp eax,0		; fopen returns null if attempted open failed
	jne .disk		; Read help info from disk, else from memory
	call memhelp		
	ret
.disk:	mov ebx,eax		; Save handle of opened file in ebx
.rdln:	push ebx		; Push file handle on the stack
	push dword HELPLEN	; Limit line length of text read
	push HelpLine		; Push address of help text line buffer
	call fgets		; Read a line of text from the file
	add esp,12		; Clean up the stack
	cmp eax,0		; A returned null indicates error or EOF
	jle .done		; If we get 0 in eax, close up & return
	push HelpLine		; Push address of help line on the stack
	call printf		; Call printf to display help line
	add esp,4		; Clean up the stack
	jmp .rdln
	
.done:	push ebx		; Push the handle of the file to be closed
	call fclose		; Closes the file whose handle is on the stack
	add esp,4		; Clean up the stack
	ret			; Go home
	
memhelp:
	mov eax,1
	call newline
	mov ebx,HelpMsg		; Load address of help text into eax
.chkln:	cmp dword [ebx],0	; Does help msg pointer point to a null?
	jne .show		; If not, show the help lines
	mov eax,1		; Load eax with number of newslines to output
	call newline		; Output the newlines
	ret			; If yes, go home
.show:	push ebx		; Push address of help line on the stack
	call printf		; Display the line
	add esp,4		; Clean up the stack
	add ebx,HELPSIZE	; Increment address by length of help line
	jmp .chkln		; Loop back and check to see if we done yet
	
showerr:
	push eax		; On entry, eax contains address of error message
	call printf		; Show the error message
	add esp,4		; Clean up the stack
	ret			; Go home; no returned values
	
randline:	
	mov ebx,BUFSIZE		; BUFSIZE tells us how many chars to pull
	mov byte [Buff+BUFSIZE],0  ; Put a null at the end of the buffer first
.loop:	dec ebx			; BUFSIZE is 1-based, so decrement
	call pull6		; Go get a random number from 0-63
	mov cl,[CharTbl+eax]	; Use random # in eax as offset into table
	                        ;  and copy character from table into cl
	mov [Buff+ebx],cl	; Copy char from cl to character buffer
	cmp ebx,0		; Are we done having fun yet?
	jne .loop		; If not, go back and pull another
	mov esi,Buff		; Copy address of the buffer into esi
	ret			;   and go home

程序分析:
main:
    push ebp  //保存栈帧指针
 mov ebp,esp  //ebp=esp,保存栈指针作为新的栈帧位置
 push ebx  //保存通用寄存器
 push esi
 push edi

 call seedit  //调用seedit函数,设置随机数种子
 
 mov eax,[ebp+8] //命令行参数个数放入eax
 cmp eax,1  //比较eax和1
 ja chkarg2  //如果命令行参数个数大于1,则跳转至chkarg2
 mov ebx,DiskHelpNm //ebx= DiskHelpNm
 call diskhelp  //如果命令行参数个数为1,调用diskhelp函数
 jmp gohome  //退出程序

chkarg2:    //命令行参数个数大于1,会跳转到这
 mov ebx,[ebp+12] //ebx=字符串指针数组地址
 push LineCount  //LineCount变量的地址
 push IntFormat     //格式化字符串地址,作为format参数
 push dword [ebx+4] //第一个命令行字符串参数的地址,作为buffer参数
 call sscanf  //调用sscanf函数,会把argv的第一个字符串参数当做一个整数来处理,保存到LineCount中。
 add esp,12  //清理栈
 cmp eax,1  //比较sscanf函数的返回值和1
 je chkdata  //如果eax=1跳转到chkdata
 mov eax,Err1  //否则eax= Err1
 call showerr  //调用showerr函数,显示出错信息
 jmp gohome  //退出程序

chkdata:
 cmp dword [ebp+8],3 //比较命令行参数的个数与3
 jae getlns  //如果大于等于3,表示有文本需要写入文件,跳转到getlns。
 call randline  //调用randline函数,随机产生一段文本。
 jmp genfile  //跳转到genfile,生成文件。

getlns: mov edx,2  //edx=2
 mov edi,Buff  //edi=Buff,目的缓存
 xor eax,eax  //eax=0
 cld   //内存地址变化方向从低到高

grab: mov esi,[ebx+edx*4] //esi=命令行参数字符串指针(从argv[2]开始)
.copy:  cmp byte [esi],0 //是否是空指针,如果是空指针,说明字符串已经到了结束。
 je .next  //如果是空指针,跳转到next
 movsb  //edi=esi,esi=esi+1,edi=edi+1
 inc eax   //eax=eax+1,字符串的字符数计数
 cmp eax,BUFSIZE  //比较eax和BUFSIZE(避免溢出)
 je addnul  //如果eax等于BUFSIZE,则跳转到addnul,结束拷贝。
 jmp .copy   //跳转到.copy,继续拷贝字符串。
 
.next: mov byte [edi],' ' //拷贝到edi一个空格
 inc edi   //edi=edi+1
 inc eax   //eax=eax+1,加了一个空格所以加1
 cmp eax,BUFSIZE  //比较eax和BUFSIZE(避免溢出)
 je addnul  //如果eax等于BUFSIZE,则跳转到addnul,结束拷贝。
 inc edx   //否则edx=edx+1
 cmp edx, dword [ebp+8]  //edx与命令行参数个数相比较
 jae addnul  //如果edx大于等于命令行参数个数,则跳转到addnul
 jmp grab  //跳转到grab处理下一个命令行参数字符串。

addnul: mov byte [edi],0 //edi=0,字符串用0结束
 mov esi,Buff  //esi = Buff

genfile:
 push WriteCode  // 'w'参数入栈,用于创建和写文件。
 push NewFilename  //文件名参数入栈
 call fopen  //调用fopen函数
 add esp,8  //清理栈
 mov ebx,eax  //文件句柄保存到ebx

 mov edi,[LineCount] //把要写的行数装入edi中

 push esi  // Buff参数,对应WriteBase的%s
 push 1  //第一行,对应WriteBase的%d
 push WriteBase  //格式化字符串地址
 push ebx  //文件句柄
 
writeline:
 cmp dword edi,0  //比较edi和0
 je donewrite  //如果等于0,表示写完,跳转到donewrite,清理栈
 call fprintf  //调用fprintf函数,把格式化字符串写入到文件。
 dec edi   //edi = edi-1
 add dword [esp+8],1 //更新栈上的行号信息
 jmp writeline  //跳转到writeline,继续循环
donewrite:  
 add esp,16  //清理栈

closeit: 
 push ebx  //文件句柄压入栈
 call fclose  //调用fclose函数
 add esp,4    //清理栈
 
gohome: pop edi   //恢复寄存器
 pop esi
 pop ebx
 mov esp,ebp  //恢复栈指针
 pop ebp         //恢复栈帧指针
 ret   

diskhelp:
 push OpenCode  //’r’
 push ebx  // DiskHelpNm文件名
 call fopen  //调用fopen函数,打开文件
 add esp,8  //清理栈
 cmp eax,0    //比较eax和0
 jne .disk  //eax不等于0,说明读取文件成功,跳转到.disk
 call memhelp //读取失败则调用memhelp函数
 ret
.disk: mov ebx,eax  //ebx=eax,保存文件句柄
.rdln: push ebx  //文件句柄压入堆栈(stream)
 push dword HELPLEN //从文件读取的字节数(n)
 push HelpLine  //读入到HelpLine(string)
 call fgets  //调用fgets函数,读入文件数据到缓存
 add esp,12  //清理栈
 cmp eax,0  //比较eax和0
 jle .done  //如果小于等于0跳转到.done(小于等于0意味着出错或读完文件)
 push HelpLine  //HelpLine压入栈
 call printf  //打印帮助信息
 add esp,4  //清理栈
 jmp .rdln     //继续循环

.done: push ebx  //文件句柄压入堆栈
 call fclose  //关闭文件
 add esp,4  //清理栈
 ret   

memhelp:
 mov eax,1
 call newline          //打印一个换行符
 mov ebx,HelpMsg  //ebx= HelpMsg
.chkln: cmp dword [ebx],0 //ebx与0比较
 jne .show  //如果不等于0,跳转到.show
 mov eax,1  //eax=1
 call newline  //打印一个换行符
 ret   
.show: push ebx  // ebx压入栈
 call printf  //打印一行帮助信息
 add esp,4  //清理栈
 add ebx,HELPSIZE //ebx=ebx+ HELPSIZE,HELPSIZE是一行帮助信息的长度
 jmp .chkln  //继续循环
 
showerr:
 push eax  //eax保存错误信息字符串地址
 call printf     //打印错误提示信息
 add esp,4  //清理栈
 ret   
 
randline: 
 mov ebx,BUFSIZE  //ebx= BUFSIZE
 mov byte [Buff+BUFSIZE],0  //Buff[BUFSIZE] = 0
.loop: dec ebx   //ebx = ebx -1
 call pull6  //产生6位随机数
 mov cl,[CharTbl+eax] //cl= CharTbl[eax],eax是产生的6位随机数,大小在0到63之间。
 mov [Buff+ebx],cl //保存cl到Buff[ebx]
 cmp ebx,0  //ebx与0比较
 jne .loop  //如果不等于0,继续循环
 mov esi,Buff  //esi=Buff
 ret 
 
makefile文件内容:

textfile: textfile.o linlib.o
	gcc textfile.o linlib.o -o textfile
textfile.o: textfile.asm
	nasm -f elf -g -F stabs textfile.asm
linlib.o: linlib.asm
	nasm -f elf -g -F stabs linlib.asm

测试:

[root@bogon textfile]# make
nasm -f elf -g -F stabs textfile.asm
nasm -f elf -g -F stabs linlib.asm
gcc textfile.o linlib.o -o textfile
[root@bogon textfile]# ./textfile 5 hello world!
[root@bogon textfile]# cat testeroo.txt 
Line #1: hello world! 
Line #2: hello world! 
Line #3: hello world! 
Line #4: hello world! 
Line #5: hello world! 


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值