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

来源:互联网 发布:天猫淘宝棉拖鞋 编辑:程序博客网 时间:2024/05/01 03:24


这是《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 codeextern printf; All of these are in the standard C library glibcextern randextern srandextern timeglobal seedit; Seeds the random number generator with a time valueglobal pull31; Pulls a 31-bit random numberglobal pull16; Pulls a 16-bit random number; in the range 0-65,535global pull8; Pulls an 8-bit random number; in the range 0-255global pull7; Pulls a 7-bit random number; in the range 0-127global pull6; Pulls a 6-bit random number; in the range 0-63global pull4; Pulls a (marginal) 4-bit random number; range 0-15global newline; Outputs a specified number of newlines to stdoutpull31: mov ecx,0; For 31 bit random, we don't shiftjmp pullpull16: mov ecx,15; For 16 bit random, shift by 15 bitsjmp pullpull8:mov ecx,23; For 8 bit random, shift by 23 bitsjmp pullpull7:  mov ecx,24; For 7 bit random, shift by 24 bitsjmp pullpull6:mov ecx,25; For 6 bit random, shift by 25 bitsjmp pullpull4:mov ecx,27; For 4 bit random, shift by 27 bitspull:push ecx; rand trashes ecx; save shift value on stackcall rand; Call rand for random value; returned in eaxpop ecx; Pop stashed shift value back into ECXshr eax,cl; Shift the random value by the chosen factor;  keeping in mind that part we want is in CLret; Go home with random number in eaxseedit: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 eaxadd esp,4; Clean up stackpush eax; Push time value in eax onto stackcall srand; Time value is the seed value for random gen.add esp,4; Clean up stackret; Go home; no return valuesnewline:mov ecx,10; We need a skip value, which is 10 minus thesub ecx,eax;  number of newlines the caller wants.add ecx,nl; This skip value is added to the address ofpush ecx;  the newline buffer nl before calling printf.call printf; Display the selected number of newlinesadd esp,4; Clean up the stackret; Go homenldb 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 dataIntFormat   dd '%d',0WriteBase   db 'Line #%d: %s',10,0NewFilename db 'testeroo.txt',0DiskHelpNm  db 'helptextfile.txt',0WriteCode   db 'w',0OpenCode    db 'r',0CharTbl    db '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-@'Err1        db 'ERROR: The first command line argument must be an integer!',10,0HelpMsg     db 'TEXTTEST: Generates a test file.  Arg(1) should be the # of ',10,0HELPSIZE    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,0HelpEnd     dd 0[SECTION .bss]; Section containing uninitialized dataLineCount   resd 1; Reserve integer to hold line countHELPLEN     EQU 72; Define length of a line of help text dataHelpLine    resb HELPLEN; Reserve space for disk-based help text lineBUFSIZE     EQU 64; Define length of text line buffer buffBuff        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 fopenextern fcloseextern fgetsextern fprintfextern printfextern sscanfextern time;; These externals are from the associated library linlib.asm:extern seedit; Seeds the random number generatorextern pull6; Generates a 6-bit random number from 0-63extern newline; Outputs a specified number of newline charsglobal main; Required so linker can find entry pointmain:    push ebp; Set up stack frame for debuggermov ebp,esppush ebx; Program must preserve EBP, EBX, ESI, & EDIpush esipush 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 EAXcmp eax,1; If count is 1, there are no argsja chkarg2; Continue if arg count is > 1mov 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 ebxpush LineCount; Push address of line count integer for sscanfpush IntFormat; Push address of integer formatting codepush dword [ebx+4]; Push pointer to arg(1)call sscanf; Call sscanf to convert arg(1) to an integeradd esp,12; Clean up the stackcmp eax,1; Return value of 1 says we got a numberje chkdata; If we got a number, go on; else abortmov eax,Err1; Load eax with address of error message #1call showerr; Show the error messagejmp 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 withcall randline; If not, generate a line of random text                        ; Note that randline returns ptr to line in esijmp 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 theremov edi,Buff; Destination pointer is start of char bufferxor eax,eax; Clear eax to 0 for the character countercld; Clear direction flag for up-memory movsbgrab: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 argmovsb; Copy char from [esi] to [edi]; inc edi & esiinc eax; Increment total character countcmp eax,BUFSIZE; See if we've filled the buffer to max countje addnul; If so, go add a null to buff & we're donejmp .copy.next:mov byte [edi],' '; Copy space to buff to separate argsinc edi; Increment destion pointer for spaceinc eax; Add one to character count toocmp eax,BUFSIZE; See if we've now filled buffje addnul; If so, go down to add a nul and we're doneinc edx; Otherwise, increment the argument countcmp edx, dword [ebp+8]; Compare against argument countjae addnul; If edx = arg count, we're donejmp grab; And go back and copy itaddnul:mov byte [edi],0; Tuck a null on the end of buffmov 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 namecall fopen; Create/open fileadd esp,8; Clean up the stackmov 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 edipush esi; esi is the pointer to the line of textpush 1; The first line numberpush WriteBase; Push address of the base stringpush ebx; Push the file handle of the open filewriteline:cmp dword edi,0; Has the line count gone to 0?je donewrite; If so, go down & clean up stackcall fprintf; Write the text line to the filedec edi; Decrement the count of lines to be writtenadd dword [esp+8],1; Update the line number on the stackjmp writeline; Loop back and do it againdonewrite: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 closedcall fclose; Closes the file whose handle is on the stackadd esp,4;;; Everything after this is boilerplate; use it for all ordinary apps!gohome:pop edi; Restore saved registerspop esipop ebxmov esp,ebp; Destroy stack frame before returningpop ebpret; Return control to to the C shutdown codediskhelp:push OpenCode; Push pointer to open-for-read code "r"push ebx; Pointer to name of help file is passed in ebxcall fopen; Attempt to open the file for readingadd esp,8; Clean up the stackcmp eax,0; fopen returns null if attempted open failedjne .disk; Read help info from disk, else from memorycall memhelpret.disk:mov ebx,eax; Save handle of opened file in ebx.rdln:push ebx; Push file handle on the stackpush dword HELPLEN; Limit line length of text readpush HelpLine; Push address of help text line buffercall fgets; Read a line of text from the fileadd esp,12; Clean up the stackcmp eax,0; A returned null indicates error or EOFjle .done; If we get 0 in eax, close up & returnpush HelpLine; Push address of help line on the stackcall printf; Call printf to display help lineadd esp,4; Clean up the stackjmp .rdln.done:push ebx; Push the handle of the file to be closedcall fclose; Closes the file whose handle is on the stackadd esp,4; Clean up the stackret; Go homememhelp:mov eax,1call newlinemov 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 linesmov eax,1; Load eax with number of newslines to outputcall newline; Output the newlinesret; If yes, go home.show:push ebx; Push address of help line on the stackcall printf; Display the lineadd esp,4; Clean up the stackadd ebx,HELPSIZE; Increment address by length of help linejmp .chkln; Loop back and check to see if we done yetshowerr:push eax; On entry, eax contains address of error messagecall printf; Show the error messageadd esp,4; Clean up the stackret; Go home; no returned valuesrandline:mov ebx,BUFSIZE; BUFSIZE tells us how many chars to pullmov byte [Buff+BUFSIZE],0  ; Put a null at the end of the buffer first.loop:dec ebx; BUFSIZE is 1-based, so decrementcall pull6; Go get a random number from 0-63mov cl,[CharTbl+eax]; Use random # in eax as offset into table                        ;  and copy character from table into clmov [Buff+ebx],cl; Copy char from cl to character buffercmp ebx,0; Are we done having fun yet?jne .loop; If not, go back and pull anothermov esi,Buff; Copy address of the buffer into esiret;   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.ogcc textfile.o linlib.o -o textfiletextfile.o: textfile.asmnasm -f elf -g -F stabs textfile.asmlinlib.o: linlib.asmnasm -f elf -g -F stabs linlib.asm

测试:

[root@bogon textfile]# makenasm -f elf -g -F stabs textfile.asmnasm -f elf -g -F stabs linlib.asmgcc 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! 


0 0
原创粉丝点击