汇编调用WriteConsole实现简化版printf
汇编调用WriteConsole实现简化版printf
简介
我所编写的printf能够实现可见字符型转义输出以及回车换行的输出。也能够实现字符串、字符型、32位有符号数和16位有符号数的输出。后面的参数为可变参数,具体调用方法和C语言版的printf大致相同,所不同的是,我的printf后所有传的参数都是地址。
调用方法:
> invoke LFYPrintf , offset arg1 [, offset arg2] [,offset arg3,] […]
arg1为格式化匹配的字符串,后面的arg2,arg3表示arg1中用类似%s,%d表示的类型的打印参数。
例子:
数据定义:
arg1 byte ‘%s think Microcomputer principle and interface technology is too hard’,0
arg2 byte ‘Li Fangyi’,0
调用LFYPrintf:
invoke LFYPrintf, offset arg1, offset arg2
显示输出:
Li Fangyi think Microcomputer principle and interface technology is too hard
注:
%d、%hd、%c也是相同的调用方法。
原理
1.操作系统中有控制台写函数,该函数为:
Syntax C:
BOOL WINAPI WriteConsole(
In HANDLE hConsoleOutput,
In const VOID *lpBuffer,
In DWORD nNumberOfCharsToWrite,
Out_opt LPDWORD lpNumberOfCharsWritten,
Reserved LPVOID lpReserved
);
该函数的声明存放在kernel32.lib库中。
2.WriteConsole函数的解析
第一个参数是一个控制台句柄,该句柄通过如下函数获取:
Syntax C:
HANDLE WINAPI GetStdHandle(
In DWORD nStdHandle
);
第二个参数是向控制台写的一个字符串指针,WriteConsole函数只能向控制台写入字符串。
第三个参数是从lpBuffer起始写入多少个字符。
第四个参数是一个指针变量,用于WriteConsole返回究竟写入了多少字符
第五个参数是保留参数,给0即可
3.GetStdHandle函数解析
作用:获取控制台句柄
参数:用于描述是要获取输入句柄还是输出句柄,具体取值见下表
Value Meaning
STD_INPUT_HANDLE (DWORD) -10 The standard input device. Initially, this is the console input buffer, CONIN.
STD_ERROR_HANDLE (DWORD) -12 The standard error device. Initially, this is the active console screen buffer, CONOUT$.
返回值:该函数返回值存储在eax寄存器中。
4.简化版printf实现思路
通过调用系统API函数WriteConsole和GetStdHandle函数可以直接输出字符串,那么我就只需要将printf后不同类型的参数全部转化为字符串然后依次输出即可。
5.不同数据类型转为字符串的方法
char(byte)型:
char型占用一字节,相当于一个只有一个字符的字符串,那么我只需要调用WriteConsole函数,将输出长度设置为1即可。
short(sword)型:
short占用两字节,首先需要确定它的正负号,确定正负号可以使用cmp命令,然后通过查看sf(符号标志位来判断正负号)。可以先事先声明一个尽量大的字符数组,假设为该数组为outputbuffer,声明一个用于记录outputbuffer存储字符数量的变量,假设为outputNum。当使用cmp和零比较后即可得出该数字的正负号。如果是负号将‘-’存入outputbuffer,outputNum相应增加。
之后将该数字转化为正数,然后使用idiv命令进行除10运算。每次将余数压栈,并且设置一个变量记录压入的数量。当eax小于10时,结束压栈。将eax的低8位al和相应吧bl放入outputbuffer中。这样outputbuffer中就有了符号位(如果是负数的话)、最高位和次高位,然后依次将堆栈中的各位弹出放入outputbuffer中。这样short型就转化为字符串了。
int(sdword)型:
该类型的转化方法与short类似,所不同的是使用除法的寄存器为32位,要使用edx.eax而已。
byte数组型:
该类型可直接调用WriteConsole输出。
6.转义实现
因考虑到%会被匹配到输出格式,所以添加了可打印字符和回车的转义字符输出。使用方法如下:
\ 输出:
\n 输出:换行符
% 输出:%
在具体实现中是匹配第一个\,如果匹配到\,则输出后续第一个字符。只有‘\n’。
流程图
实现环境
MASM6.0
依赖
kerne32.lab
源代码
.686
.model flat,stdcall
option casemap:none
includelib kernel32.lib
ExitProcess proto, :DWORD
GetStdHandle proto, :DWORD
WriteConsoleA proto,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
WriteConsole equ <WriteConsoleA>
STD_OUTPUT_HANDLE = -11
.data
outhandle dword ?
outputbuffer byte 1024 dup(0) ;用于暂存格式化输出串
outputNum dword 0 ;用于暂存格式化输出串的长度
outsize dword ?
zhx byte 'k'
lfy sword 1024
zhang sdword 100
printS byte 'riqi %s , renshu %d, 123,%hd, \n, %c \\ \% %d',0
fushu sdword -1999
dog byte 'dog dog dog',0
.code
LFYPrintf proto c printfStr:dword, argv:vararg
main proc
invoke LFYPrintf,offset printS, offset dog, offset zhang, offset lfy, offset zhx,offset fushu
main endp
LFYPrintf proc c printStr:dword,argv:vararg
mov outputNum,0 ;带输出字符串长度为零
;获取输出句柄
invoke GetStdHandle, STD_OUTPUT_HANDLE
mov outhandle,eax
mov esi,printStr ;将字符串保存在esi中
mov al,ds:[esi] ;将第一个字符保存到al中
mov ecx,0 ;ecx作为printStr字符串的指针
mov edx,0 ;edx为argv参数的指针
.while(al!=0) ;字符串结尾是0时退出
.if(al =='%')
pushad
invoke WriteConsole,outhandle,addr outputbuffer,outputNum,addr outsize,0
popad
inc ecx
mov al,ds:[esi+ecx]
.if(al == 'c') ;如果是char
pushad
invoke WriteConsole,outhandle,argv[edx],1,addr outsize,0 ;输出第一个参数
popad
add edx,4 ;edx指向第下一个参数
.elseif(al == 'h')
inc ecx
mov al,ds:[esi+ecx]
.if(al == 'd') ;如果是short
pushad
mov outputNum,0
mov ecx,0 ;用于记录栈中压入了多少位
push esi
mov esi,argv[edx]
mov ax,sword ptr ds:[esi]
;mov ax,sword ptr argv[edx] ;因为第二个为%hd所以占16位,将其放入ax中
pop esi
cmp ax,0 ;跟零比较判断ax正负
jns positive ;如果是正数,直接跳转
push ebx
mov ebx,outputNum
mov outputbuffer[ebx],'-' ;如果是负数,将负号填入outputbuffer
pop ebx
add outputNum,1 ;待输出长度增加
neg ax ;将负数转化为正数好计算
positive:
muldiv:
xor dx,dx ;dx.ax / bx = ax...bx
mov bx,10 ;将除数赋值为10
idiv bx
.if(ax>=10)
push dx ;依次从低位到高位压栈
inc ecx ;压入位数递增
jmp muldiv
.endif
push ebx
mov ebx,outputNum
add ax,48
mov outputbuffer[ebx],al ;将最高位放入
pop ebx
add outputNum,1
push ebx
mov ebx,outputNum
add dx,48
mov outputbuffer[ebx],dl ;将次高位放入
pop ebx
add outputNum,1
ecxNotZero:
.if(ecx >0) ;将其余压入栈中的位数放入
pop ax
push ebx
mov ebx,outputNum
add ax,48
mov outputbuffer[ebx],al
pop ebx
add outputNum,1
dec ecx
jmp ecxNotZero
.endif
pushad
invoke WriteConsole,outhandle,addr outputbuffer,outputNum,addr outsize,0 ;将short输出
popad
popad
inc ecx
add edx,4 ;edx指向下一个参数
.endif
.elseif(al == 'd') ;如果是int
pushad
mov outputNum,0
push esi
mov esi,argv[edx]
mov eax,sdword ptr ds:[esi]
;mov ax,sword ptr argv[edx] ;因为第二个为%hd所以占16位,将其放入ax中
pop esi
mov ecx,0 ;用于记录栈中压入了多少位
cmp eax,0
jns positive2
push ebx
mov ebx,outputNum
mov outputbuffer[ebx],'-' ;如果是负数,将负号填入outputbuffer
pop ebx
add outputNum,1 ;待输出长度增加
neg eax
positive2:
muldiv2:
xor edx,edx
mov ebx,10
idiv ebx
.if(eax >= 10)
push edx
inc ecx
jmp muldiv2
.endif
push ebx
mov ebx,outputNum
add eax,48
mov outputbuffer[ebx],al ;将最高位放入
add outputNum,1
inc ebx
add edx,48
mov outputbuffer[ebx],dl ;将次高位放入
add outputNum,1
pop ebx
ecxNotZero2:
.if(ecx >0) ;将其余压入栈中的位数放入
pop eax
push ebx
mov ebx,outputNum
add eax,48
mov outputbuffer[ebx],al
pop ebx
add outputNum,1
dec ecx
jmp ecxNotZero2
.endif
pushad
invoke WriteConsole,outhandle,addr outputbuffer,outputNum,addr outsize,0 ;将short输出
popad
popad
add edx,4
inc ecx
.elseif(al == 's')
pushad
mov esi,argv[edx]
mov ecx,0 ;统计字符串有多少自服务
flag1:
mov al,ds:[esi+ecx]
.if(al != 0)
inc ecx
jmp flag1
.endif
invoke WriteConsole,outhandle,argv[edx],ecx,addr outsize,0 ;输出第一个参数
popad
add edx,4 ;edx指向第下一个参数
.endif
mov outputNum,0
jmp willend ;如果匹配到%那么跳转
.elseif(al == '\')
pushad
invoke WriteConsole,outhandle,addr outputbuffer,outputNum,addr outsize,0
popad
inc ecx
pushad
mov outputNum,0
mov al,ds:[esi+ecx]
.if(al=='n')
mov ebx,0
mov outputbuffer[ebx],10
inc outputNum
pushad
invoke WriteConsole,outhandle,addr outputbuffer,outputNum,addr outsize,0
popad
.elseif(al !='n')
mov ebx,0
mov outputbuffer[ebx],al
inc outputNum
pushad
invoke WriteConsole,outhandle,addr outputbuffer,outputNum,addr outsize,0
popad
.endif
popad
mov outputNum,0
jmp willend
.endif
push ebx
mov ebx,outputNum
mov outputbuffer[ebx],al
pop ebx
inc outputNum
willend:
inc ecx
mov al,ds:[esi+ecx]
.endw
ret
LFYPrintf endp
end main
上一篇: zj集训 8.5
下一篇: PHP 实现定时任务的几种方法对比