msgi db "please input a string:$"显示信息,提示输入一串字符
buf db 100,0,100 dup (?)输入字符串缓冲区
digit db 100 dup (?)数字字符缓冲区
leter db 100 dup (?)字母字符缓冲区
other db 100 dup (?)其它字符缓冲区
dn db ?数字字符个数
ln db ?字母字符个数
on db ?其它字符个数
msgd db 0dh,0ah,"digits are:$"显示信息,提示数字字符显示
msgl db 0dh,0ah,"leters are:$"显示信息,提示字母字符显示
msgo db 0dh,0ah,"others are:$"显示信息,提示其它字符显示
msgdn db 0dh,0ah,"digits:$"显示信息,提示数字字符个数显示
msgln db 0dh,0ah,"leter :$"显示信息,提示字母字符个数显示
msgon db 0dh,0ah,"other:$"显示信息,提示其它字符个数显示
DSEG ENDS
CSEG SEGMENT
assume cs:CSEG, ds:DSEG
MAIN PROC FAR 主程序入口
mov ax, dseg
mov ds, ax
lea dx,msgi显示信息,提示输入一串字符
mov ah,9
int 21h
lea dx,buf输入字符串
mov ah,0ah
int 21h
lea si,buf
mov cl,[si+1]输入字符实际个数存入CX中
mov ch,0
add si,2输入字符起始地址存入SI
lea bx,digit数字字符起始地址存入BX
lea di,leter字母字符起始地址存入DI
lea bp,other其它字符起始地址存入BP
fenlei:
mov al,[si]取出一个字符
cmp al,30h判断该字符是否为数字
jb oth
cmp al,39h
ja ulet
mov [bx],al若为数字字符,则存入相应缓冲区
inc bx
inc dn并将数字字符个数加1
jmp next
ulet:
cmp al,41h判断该字符是否为大写字母
jb oth
cmp al,5ah
ja llet
mov [di],al若为大写字母字符,则存入相应缓冲区
inc di
inc ln并将字母字符个数加1
jmp next
llet:
cmp al,61h判断该字符是否为小写字母
jb oth
cmp al,7ah
ja oth
mov [di],al若为小写字母字符,则存入相应缓冲区
inc di
inc ln并将字母字符个数加1
jmp next
oth:
mov ds:[bp],al为其它字符,则存入相应缓冲区
inc bp
inc on并将其它字符个数加1
next:
inc si调整地址,指向下一个字符
loop fenlei循环次数为实际输入字符个数
mov byte ptr [bx],'$'在数字字符串末尾加'$',目的用9号中断显示该串
mov byte ptr [di],'$'在字母字符串末尾加'$',目的用9号中断显示该串
mov byte ptr ds:[bp],'$'在其它字符串末尾加'$',目的用9号中断显示该串
lea dx,msgd显示数字字符
mov ah,9
int 21h
lea dx,digit
mov ah,9
int 21h
lea dx,msgl显示字母字符
mov ah,9
int 21h
lea dx,leter
mov ah,9
int 21h
lea dx,msgo显示其它字符
mov ah,9
int 21h
lea dx,other
mov ah,9
int 21h
lea dx,msgln显示数字字符个数
mov ah,9
int 21h
mov bl,dn
call disp以十进制形式显示个数
lea dx,msgln显示字母字符个数
mov ah,9
int 21h
mov bl,ln
call disp
lea dx,msgon显示其它字符个数
mov ah,9
int 21h
mov bl,on
call disp
mov ah,1按任意键退出
int 21h
mov ax, 4c00h 程序结束,返回到 *** 作系统系统
int 21h
MAIN ENDP
disp proc near
mov ch,2
rotate:
mov cl,4
rol bl,cl
mov al,bl
and al,0fh
add al,30h
cmp al,3ah
jl printit
add al,7h
printit:
mov dl,al
mov ah,2
int 21h
dec ch
jnz rotate
ret
disp endp
CSEG ENDS
END MAIN
代码+注释承上.386
.model flat,stdcall 这里我们用stdcall 就是函数参数 压栈的时候从最后一个开始压,和被调用函数负责清栈
option casemap:none区分大小写
includelib msvcrt.lib 这里是引入类库 相当于 #include<stdio.h>了
printf PROTO C:DWORD,:VARARG 这个就是声明一下我们要用的函数头,到时候 汇编程序会自动到msvcrt.lib里面找的了
:VARARG 表后面的参数不确定 因为C就是这样的printf(const char *, ...)
这样的函数要注意 不是被调用函数负责清栈 因为它本身不知道有多少个参数
而是有调用者负责清栈 下面会详细说明
.data
szTextFmt BYTE '%d',0这个是用来类型转换的,跟C的一样,字符用字节类型
a dword 1000 假设
b dword 2000 处理数值都用双字 没有int 跟long 的区别
/////////////////////////////////////////////////////////////////////////////////////////
.code
_test proc A:DWORD,B:DWORD
push ebp
mov ebp,esp
mov eax,dword ptr ss:[ebp+8]
add eax,1
mov edx,dword ptr ss:[ebp+0Ch]
add edx,100
add eax,edx
pop ebp
retn 8
_test endp
_main proc
push dword ptr ds:b 反汇编我们看到的b就不是b了而是一个[*****]数字 dword ptr 就是我们在ds(数据段)把[*****]
开始的一个双字长数值取出来
push dword ptr ds:a 跟她对应的还有 byte ptr ****就是取一个字节出来 比如这样 mov al,byte ptr ds:szTextFmt
就把 % 取出来 而不包括 d
call _test
push eax 假设push eax的地址是×××××
push offset szTextFmt
call printf
add esp,8
ret
_main endp
end _main
////////////////////////////////////////////////////////////// 下面介绍堆栈的变化
首先要明白的是 *** 作堆栈段 ss 只能用 esp或ebp寄存器 其他的寄存器eax ebx edx等都不能够用 而 esp永远指向堆栈栈顶 ebp用来 在堆栈段
里面寻址
push 指令是压栈 ESP=ESP-4
pop 指令是出栈 ESP=ESP+4
我们假设main函数一开始堆栈定是 ESP=400
push dword ptr ds:b ESP-4=396 ->里面的值就是 2000 就是b的数值
push dword ptr ds:a ESP-4=392 ->里面的值就是 1000 就是a的数值
call test ESP-4=388->里面的数值是什么?这个太重要了 就是我们用来找游戏函数的原理所在。
里面的数值就是call test 指令下一条指令的地址->即push eax的地址×××××
到了test函数里面
push ebp ESP-4=384->里面保存了当前ebp的值 而不是把ebp清零
mov ebp,esp 这里ESP=384就没变化了,但是 ebp=esp=384,为什么要这样做呢 因为我们要用ebp到堆栈里面找参数
mov eax,dword ptr ss:[ebp+8] 反汇编是这样的 想想为什么a就是[ebp+8]呢
我们往上看看堆栈里地址392处就保存着a的值 这里ebp=384 加上8正好就是392了
这样就把传递过来的1000拿了出来eax=1000
add eax,1 相当于 a+1了 eax=1001
mov edx,dword ptr ss:[ebp+0Ch]0Ch=12 一样道理这里指向堆栈的地址是384+12=396 就是2000了 edx=2000
add edx,100 相当于 b+100 edx=2100
add eax,edx eax=eax+edx=1001+2100=3101 这里eax已经保存了最终的结果了
因为win32汇编一般用eax返回结果 所以如果最终结果不是在eax里面的话 还要把它放到eax
比如假设我的结果保存在变量nRet里面 最后还是要这样 mov eax,dword ptr nRet
pop ebp ESP=384+4=388 而保存在栈顶384的值 保存到 ebp中 即恢复ebp原来的值
因为一开始我们就把ebp的值压栈了,mov ebp,esp已经改变了ebp的值,这里恢复就是保证了堆栈平衡
retn 8ESP+8->396 这里retn是由系统调用的 我们不用管 系统会自动把EIP指针指向 原来的call的下一条指令
由于是系统自动恢复了call那里的压栈所以 真正返回到的时候ESP+4就是恢复了call压栈的堆栈
到了这个时候 ESP=400 就是函数调用开始的堆栈,就是说函数调用前跟函数调用后的堆栈是一样的
这就是堆栈平衡
由于我们用stdcall上面retn 8就是被调用者负责恢复堆栈的意思了,函数test是被调用者,所以负责把堆栈加8,call 那里是系统自动恢复的
push eaxESP-4=396->里面保存了eax的值3101
上面已经看到了eax保存着返回值,我们要把它传给printf也是通过堆栈传递
push offset szTextFmt ESP-4=392->里面保存了szTextFmt的地址 也就是C里面的指针 实际上没有什么把字符串传递的,我们传的都是地址
无论是在汇编或C 所以在汇编里没有什么字符串类型 用最多的就是DWORD。嘿嘿游戏里面传递参数 简单多了
call printf ESP-4=388->里面保存了下一条指令的地址
add esp,8 ESP+8=400 恢复了调用printf前的堆栈状态
上面说了由于printf后面参数是:VARARG 这样的类型是有调用者恢复堆栈的 所以printf里面没有retn 8之类的指令
这是由调用者负责清栈 main是调用者 所以下面一句就是 add esp,8 把堆栈恢复到调用printf之前
而call printf那里的压栈 是由系统做的 恢复的工作也是系统完成 我们不用理 只是知道里面保存是返回地址就够
了
ret main 函数返回 其他的事情是系统自动搞定 我们不用理 任务完成
微信小程序金山表单多人填写后汇总步骤。1、打开多人填写的金山表单。
2、点击数据工具,找到下拉菜单中的合并数据。
3、再找到多个工作簿合并成一个工作簿的选项。
4、点击即可完成。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)