汇编调用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库中。
  1. WriteConsole函数的解析
    第一个参数是一个控制台句柄,该句柄通过GetStdHandle获得。
    第二个参数是向控制台写的一个字符串指针,WriteConsole函数只能向控制台写入字符串。
    第三个参数是从lpBuffer起始写入多少个字符。
    第四个参数是一个指针变量,用于WriteConsole返回究竟写入了多少字符
    第五个参数是保留参数,给0即可。
    GetStdHandle:
    Syntax C:
HANDLE WINAPI GetStdHandle(
	_In_ DWORD nStdHandle
);
  1. GetStdHandle函数解析
    作用:获取控制台句柄
    参数:用于描述是要获取输入句柄还是输出句柄,具体取值见下表

    alueMeaning
    STD_INPUT_HANDLE (DWORD) -10标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。
    STD_OUTPUT_HANDLE (DWORD) -11标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。
    STD_ERROR_HANDLE (DWORD) -12标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$。

    返回值:该函数返回值存储在eax寄存器中。

  2. 简化版printf实现思路
    通过调用系统API函数WriteConsole和GetStdHandle函数可以直接输出字符串,那么我就只需要将printf后不同类型的参数全部转化为字符串然后依次输出即可。

  3. 不同数据类型转为字符串的方法
    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输出。

  4. 转义实现
    因考虑到%会被匹配到输出格式,所以添加了可打印字符和回车的转义字符输出。使用方法如下:
    \\ 输出:\
    \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
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值