0
点赞
收藏
分享

微信扫一扫

Asm-实践篇

斗米 2022-07-18 阅读 113

title:汇编语言 tags: Study categories: Language description: keywords: date: 2022.06.21

代码模板

8086.exe

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
assume cs:code , ds:data, ss:stack
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;数据源S
data segment
db 128 dup('x')
data ends
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;栈段
stack segment stack
db 128 dup('y')
stack ends
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
code segment
start:
;main code
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;数据源S
;为数据来源source分配大小
mov ax,data
mov ds,ax
;数据地D
;数据的目的地destation
mov ax,7e00h
mov es,ax
;栈
;申请了一段栈空间
mov ax,stack
mov ss,ax
mov sp,128

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx






over:
mov ax,4c00h;初始化内中段的参数
int 21h;调用内中段
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;function



;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
code ends
end start
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

对于上面的数据它是一个顺序结构的存储

也就是你的源代码数据怎么写,那么你的数据就怎么存储,好比你的data写在了code上面,那么的话IDA里面看到的也是

data在code上面

386.exe

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;对于上面的lib,inc还不是很理解

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
.data
A_Title db 'My_First_Box !',0
A_Text db 'Hello, Dqx_Ghost !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;栈断分配一些数据
.stack 1024
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke MessageBox, \
NULL, \
offset A_Text, \
offset A_Title, \
MB_OK
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

x86_ELF

section .data   

A_str db "I am Dqx_Gh0st",10,0
format db "%s",0


;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section .bss


;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section .text

extern printf
;extern func

global main
main:
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

push rbp
mov rbp,rsp


mov rax, 0
mov rsi, A_str
mov rdi, format
call printf

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
leave
ret
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

;func
;参数
;返回值
;注意事项



;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

public _start
_start proc near
; __unwind {
endbr64
xor ebp, ebp
mov r9, rdx ; rtld_fini
pop rsi ; argc
mov rdx, rsp ; ubp_av
and rsp, 0FFFFFFFFFFFFFFF0h
push rax
push rsp ; stack_end
mov r8, offset __libc_csu_fini ; fini
mov rcx, offset __libc_csu_init ; init
mov rdi, offset $_53_1_ ; main //你自己函数入口是一以rdi的形式给了函数cs:__libc_start_main_ptr
call cs:__libc_start_main_ptr
hlt
; } // starts at 401040
_start endp

寻址方式

寻址公式一:[立即数]

寻址公式二:[reg] reg代表寄存器可以是8个通用寄存器中的任意一个

寻址公式三:[reg+立即数]

寻址公式四: [reg1+reg2*{1,2,4,8}]

寻址公式五:[reg1+reg2*{1,2,4,8}+立即数]

子函数调用

栈stack

栈结构

SS:SP   数据      说明  


0019FF54 12345678 立即数-局部变量
0019FF58 00403000 .data:aSsssssssss-局部变量
0019FF5C 41530000 字符串3-局部变量
0019FF60 41414141 字符串2-局部变量
0019FF64 45414141 字符串1-局部变量
------------------------------------在子函数分配栈空间之前
0019FF68 0019FF84 Stack[00003F3C]:0019FF84 || EBP
0019FF6C 00401009 start+9||IP
0019FF70 0000000A 参数2-main变量
0019FF74 00000064 参数1-main变量

一个main函数栈的使用



这里就是子函数栈的部分

[esp+0]

在这之间的栈是用来传递参数的

[esp+40]:40个字节的字符串
[esp+80]:float的浮点数
[esp+84]:float计算结果
[esp+88]:计算的的字符串长度
[esp+92]:字符串的大小
然而C语言的函数声明,我还不知道他怎么分配栈空间的
float weight, volume;
int size, letters;
char name[40];

栈数据的获取(不是非要去pop来获取)

每次push一下

sp就会向上移动减少

如果你取栈数据

[sp]其实他会向下的方向获取一个type的数据

栈里的数据是取出时从低位到高位拿出

好比

push ax
mov bp,sp
push cx
push dx
mov bx,ss:[bp]

这样的话,你是可以获取数据ax的

这里的bp也给你记录了位置

但是我们通常不会去改变ebp的值

不要随性的push 与 pop

你一定要记住,不要随性的push

push的东西也是有讲究的

你要用它,你要临时保存它,你才会去push

否者的话,当你要用的时候,pop的东西,又不是你想要的

联合使用push与pop

数据的传递

通常是

mov ax,ds:[si]

mov es:[di],ax

我们也可以这样

push ax:[si]

pop es:[di]

栈指令

16位栈指针sp/bp

32位指针esp/ebp

64位rsp/rbp

push/pop

esp指向了当前的数据,

如果再push

数据会放在esp的下一个

esp然后-4

不是说x86的sp一定是+或-4

当push 16位数据就sp-2

当push 32位数据就是sp-4

当push 立即数,默认就是sp-4

不能push 8位数据

xor eax,eax                                                                                                                                                                                        
;mov al,10
;push al
mov ax,10
push ax
mov eax,10
push eax

pop eax
pop ax

一些异常

push eax
pop ax

push 进一个32位,esp-=4

pop 出一个16位,esp+=2

这导致堆栈不平衡

pushfd/popfd

可以对比一下8086的pushf与popf..然后就明白了

它到底push了啥????

EFL有些啥????






0


0


1



高位左边

IF

TF

SF

ZF

0

AF

0

PF

1

CF

低位右边

如何用专门的变量保存EFL以供使用?

.data
recive dd 0
.code
pushfd
pop recive;得是一个32位的接收者

pushad/popad/pusha/popa

把所有寄存器入栈出栈

对于pusha/popa是针对16位数据的寄存器

push什么? AX,CX,DX,BX,.SP,BP,SI,DI

pop就是一个逆序

对于pushad是针对32位数据的寄存器

push/pop同理

使用的注意事项,一个函数的返回值用一个寄存器来作为接收者,你用了pushad/popad会把返回值给覆盖了,返回无效

leave

我们之前会有一个

push ebp
mov ebp,esp
sub esp,20
....
然后leave

首先我们要明确,函数栈的开辟我们不一定会把局部变量给全部使用完..所以esp要回到起点!也就是恢复到进来的时候!

mov esp,ebp
pop ebp

进来push ebp,mov ebp,esp

出去 mov esp,ebp,pop ebp

进来,ebp要发生变化,esp要指向新的栈顶

出去,esp要恢复,ebp要还原

如果在x86的模式下,我们源码用ret

在IDA就会生成leave

如果源码写为

mov esp,epb

pop ebp

retn

就不会出现leave

enter

与leave相对应

enter出现在子函数的开头部分

enter有2个参数,都是立即数

参数一时开辟的字节数,得是4的倍数,有利于寻址

参数二,就写0,以后遇到再说吧

enter 12,0

等价于

push ebp
mov esp,ebp
sub ebp,12

关于enter与leave的应用

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
call my
invoke ExitProcess,NULL
my proc
enter 12,0

mov dword ptr [ebp-4],10
mov dword ptr [ebp-8],20
mov esp,ebp

leave
ret
my endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start

IDA

fun proc near

var_8= dword ptr -8
var_4= dword ptr -4

enter 0Ch, 0
mov [ebp+var_4], 0Ah
mov [ebp+var_8], 14h
mov esp, ebp
leave
retn
fun endp

8086栈指令
pushf/popf

就是对所有的flag寄存器给push一下或者pop一下

它会把8位的二进制寄存器合成2位的16进制然后出入栈

pusha/popa

x86说16位可以pusha/popa,但是我试了一下,好像不可以

栈维护

用uses临时创建变量

关于uses和[ebp+8]

uses指令的顺序先于push ebp,mov ebp,esp

所以的话,

你的[ebp+8]就不再是传入的第一个参数

[ebp+8+N],N是你是你传入的那些参数,具体遇到再说

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data
;--------------------------------------------------------
;func proto :byte,:byte
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
call func
invoke ExitProcess,NULL
;--------------------------------------------------------
func PROC uses eax ebx
mov eax,1
mov ebx,2
ret
func ENDP
;--------------------------------------------------------
end start

关于uses的变量内部入栈,他在内部平衡

func proc near
push eax
push ebx
mov eax, 1
mov ebx, 2
pop ebx
pop eax
retn
func endp

用local创建变量

它的特点就是

栈空间的申请是内部​​add esp,负数​

然后栈的平衡内部是mov esp,ebp,pop ebp

也就是一个leave就解决

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data
arr1 db 16 dup('s')
;--------------------------------------------------------
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
call func
invoke ExitProcess,NULL
;--------------------------------------------------------
func proc
local x[10]:byte
local y:ptr byte
mov byte ptr x[9],6
mov byte ptr x[0],3
mov y,offset arr1
ret
func endp

;--------------------------------------------------------
end start

IDA

; Attributes: bp-based frame

fun proc near

push ebp
mov ebp, esp
add esp, 0FFFFFFF0h
mov byte ptr [ebp-1], 6
mov byte ptr [ebp-10], 3
mov dword ptr [ebp-16], offset aSsssssssssssss ; "ssssssssssssssss"
leave
retn
fun endp

可以看到,我创建了14字节

它给我分配了16字节

关于local建立的变量,他的本质是一个push,先push谁,取决于你先写谁

invoke 和调用约定

都是实现push,然后call

参数的传递在子函数栈空间的下方

内部平衡就​​retn xx​

外部平衡就​​add esp,xx​

调用约定

参数压栈顺序

平衡堆栈

__cdecl

从右至左入栈

调用者清理栈

__stdcall

从右至左入栈

自身清理堆栈

__fastcall

ECX/EDX传送前两个剩下:从右至左入栈

自身清理堆栈

int __cdecl Plus(int a, int b)        
{
return a+b;
}

push 2
push 1
call @ILT+15(Plus) (00401014)
add esp,8

  2、int __stdcall Plus(int a, int b)        
{
return a+b;
}

push 2
push 1
call @ILT+10(Plus) (0040100f)

函数内部:

ret 8

                        
3、int __fastcall Plus(int a, int b)
{
return a+b;
}

mov edx,2
mov ecx,1
call @ILT+0(Plus) (00401005)

函数内部:

ret


4、int __fastcall Plus4(int a, int b,int c,int d)
{
return a+b+c+d;
}

push 4
push 3
mov edx,2
mov ecx,1
call @ILT+5(Plus) (0040100a)

函数内部:

ret 8

所以,如果根据​​ret 8​​是无法直接判断有几个参数

因为可能是fastcall

如果你直接看出了一个函数有几个参数,

那些参数也有可能不是当前call

也可能是内嵌call的

所以如何看一个函数用了多少个参数?

(0).不用管是什么调用约定

(1). 看函数内部用了哪些对应的内存和寄存器

(2). 结合最后ret n的n

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include kernel32.inc
includelib kernel32.lib
;对于上面的lib,inc还不是很理解

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
.data
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;c_var1 dd 10
;c_var2 dd 20
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

sub1 proto C,:DWORD,:DWORD
sub2 proto StdCall,:DWORD,:DWORD
sub3 proto sysCall,:DWORD,:DWORD
sub4 proto PASCAl,:DWORD,:DWORD
sub5 proto BASIC,:DWORD,:DWORD
sub6 proto fortran,:DWORD,:DWORD


start:

xor eax,eax

invoke sub1,1,2
invoke sub2,1,2
invoke sub3,1,2
invoke sub4,1,2
invoke sub5,1,2
invoke sub6,1,2

push 0
call ExitProcess
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
sub1 proc C,c_var1,c_var2
mov eax,c_var1
mov ebx,c_var2
ret
sub1 endp

sub2 proc StdCall,c_var1,c_var2
mov eax,c_var1
mov ebx,c_var2
ret
sub2 endp;在内部 retn 8,他们是在外部add esp,8

sub3 proc sysCall,c_var1,c_var2
mov eax,c_var1
mov ebx,c_var2
ret
sub3 endp

sub4 proc PASCAl,c_var1,c_var2
mov eax,c_var1
mov ebx,c_var2
ret
sub4 endp

sub5 proc BASIC,c_var1,c_var2
mov eax,c_var1
mov ebx,c_var2
ret
sub5 endp

sub6 proc fortran,c_var1,c_var2
mov eax,c_var1
mov ebx,c_var2
ret
sub6 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

x64栈对齐

栈帧是一个过程

好比

push pbp
mov rbp,rsp

mov rsp,rbp
pop rbp

为什么需要这2个东西

我们得知道一个函数栈的建立是不会触及栈底rbp的修改的,那为什么还要push rbp呢????

这就是一个16字节的栈对齐了

一些函数的约定或者是个啥要求栈的地址必须是16的倍数

对于x64而言,push一下就是8字节

对于一个函数的调用

call一下8字节

push rbp 8字节

这几构成了16字节,达到了16字节对齐

还有一种栈对齐的方法

and rsp , 0xfffffffffffffff0 ; 16 byte align the stack

堆栈图

就代码

PUSH EBP
MOV EBP ,ESP
SUB ESP,4B
------这3个push是保护现场的
PUSH EBX
PUSH ESI
PUSH EDI
LEA EDI,DwoRD PTR SS: [EBP一40]
mov Ecx,10
mov EAx,ccccccCc ;之所以写0xCC,是为了防止缓冲区溢出,跑去0xcc那里执行
REP Stos DwORD PTR ES:[EDI]

----------------------------------------------上面的操作好比以炒菜前的准备

MOV EAx,DwORD PTR SS: [ EBp+8 ]
ADD EAx, DwoRD PTR SS:[EBP+C]

----------------------------------------------下面的操作就像是炒菜后的刷锅
PoP EDI
POP ESI
POP EBx
MOV ESP,EBP
POP EBP
retn

所以你把char给传递进去,其实是被扩展

func(char x,char y)

所以传递1/2个字节的参数都会被转化为4字节

为什么要扩展?因为效率的原因

char i 他会分配4个字节给i
char o[2] 他会分配4个字节
char p[7] 他会分配8个字节

常用指令

invoke

它只是属于masm32的伪指令,不具有移植性

用法

invoke 函数名 参数1 参数2 参数3

//参数的个数由函数确定

例如

invoke  MessageBox, NULL, offset A_Text, offset A_Title, MB_OK

假设是stdcall的调用方式

你在invoke写的正序

入栈的时候是逆序

invoke 不能用addr,只能offset,为什么????

注意事项

如果你的参数是小于32位的寄存器eax/edx,最后invoke会有一个扩展到32位,使得数据的丢失

如果数据重要,你就把他保存一下

还有一个东西巨坑,我们之前说过,我们不能push一个8位的数据,所以你不能传入一个字节的东西,除非强制类型转化

于是invoke是不能带有8位的参数的,否则就无法识别

addr

取地址的操作

对于全局变量

addr会转化为offset

对于局部变量

addr会转化为lea指令

addr怎么用,我好不懂

见<<琢石成器>>.Page76

addr只能和invoke一起用

addr的参数只能是常熟,不能是变量?????

proc /endp 函数声明

在8086里面,我们定义一个函数直接是标号就🆗

在x86下,我们是定义一个函数的是定义一个过程,称之为函数的声明

函数的名字    proc [ 距离] [ 调用方式 ] [可视区域] [uses 寄存器列表] [参数名称:参数类型,参数名称:参数类型] [vararg]
local 局部变量

请写明参数的类型,否则将无法识别,或者偷鸡摸狗耍小聪明

main proc
call func1
push 0
call j_ExitProcess
main endp

fucn1 proc
ret
func1 endp

proto 函数声明

类比C语言,它是一个函数的声明

用法 ,参考proc

好比

MessageBox Proto :dword, :dword,:dword,:dword,

通常这样写是的等价的

MessageBox Proto hwnd:dword, text:dword,title:dword,Type:dword,

使用例子

sub2  proto StdCall,:DWORD,:DWORD
invoke sub2,1,2
sub2 proc StdCall,c_var1:DWORD,c_var2:DWORD
mov eax,c_var1
mov ebx,c_var2
ret
sub2 endp;在内部 retn 8,他们是在外部add esp,8

作用1:外部声明

作用2:检查参数列表,proc与proto是否一致

extern 函数声明

怎么用,遇到再说

inlcudelib 导入库

8086的汇编,在调用中断时,我们只需要int一个编号就可以寻址

可能是因为中断不多,,,,

在win32-API中,类似的int很多,,,,我在这里称之为dll文件…根据不同的函数实现要求..我们的obj要选取不同的dll文件

此时就需要include来调用dll

uses 伪指令,一个入栈的操作

uses连接的东西是没有逗号的

func_sum PROC   uses ecx  esi

xor eax,eax
sum_loop:
add al,[esi]
add esi,type arr1
loop sum_loop
ret
func_sum ENDP

在IDA里面就是

start_0 proc near
xor eax, eax
mov ecx, 10
mov esi, offset unk_384000
call sub_382051
mov byte_38400A, al
push 0 ; uExitCode
call j_ExitProcess
start_0 endp

你可以把这个C代码翻译为汇编看看

#include <stdio.h>
char str[10]="Dqx+Gh0st";
void func(int);
int main(void)
{
int x2=4,y2=5,z2=6;
char temp[10]="Dqx";
puts(str);
func(100);
return 0;
}
void func(int w)
{
int x3=1,y3=2,z3=3;
printf("%d %d %d",x3,y3,z3);
}

IDA

public _main
_main proc near

push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 30h
call ___main
mov dword ptr [esp+44], 4
mov dword ptr [esp+40], 5
mov dword ptr [esp+36], 6
//上面分别是你在main函数定义的变量
mov dword ptr [esp+26], 787144h
//这是你在main函数定义的字符串,你的字符串是10个字节,前3个字节被初始化了,也就是787144h
//它用4字节的dword装下了你的3字节字符串
//剩下了6字节,用了dword与word来初始化为0
mov dword ptr [esp+30], 0
mov word ptr [esp+34], 0
mov dword ptr [esp], offset _str ; "Dqx+Gh0st"
//对于全局变量他用offset取地址
//mov dword ptr [esp], offset _str ; "Dqx+Gh0st"==push dword ptr [esp]
call _puts
mov dword ptr [esp], 64h ; 'd' ; w
/方式对[esp]操作的都==push xxxx
call _func
mov eax, 0
leave
retn
_main endp

进入子函数,前面已经传入参数了,

public _func
_func proc near

push ebp
mov ebp, esp
sub esp, 28h
mov dword ptr [ebp-12], 1
mov dword ptr [ebp-16], 2
mov eax, [ebp+8]
mov [esp+12], eax
mov eax, [ebp-16]
mov [esp+8], eax
mov eax, [ebp-12]
mov [esp+4], eax
mov dword ptr [esp], offset Format ; "%d %d %d"
call _printf
leave
retn
_func endp

//下级函数栈
//EBP
//IP
//------------上级函数栈
//参数1
//参数2

所以的话,传入的参数都是通过ebp+8开始,是第一个参数

local 定义局部变量

local创建的变量是不可传递的参数,只能在函数内部使用

另外局部变量不能和形式参数同名

local定义的类型在函数声明proc后,在程序执行CS:IP之前

local在win32模式下,默认数据的长度是32位的dd,如果主观的定义一个32位的的数据长度,我们是不需要指出数据长度的

local不支持和dup指令一起使用…

local变量肯定不是在data区,它是在栈区

相比于proc的函数参数列表声明,它可以在invoke的时候传入参数的

现在有个问题,如果invoke只是一个call与push,那么我们是可以通过local来接受传递过来的参数的

也就是好比这个意思

int func(int x)
{
int y=x;
}

于是局部变量y就接受了传递过来的参数

local 是enter与leave的高级版

用local会自动在最后使用leave指令,就不用你去手动的释放,最后再ret

func proc
local arr[16]:byte
local Num
;local unk:WNDCLASS
xor eax,eax
....

然后利用local变量得到的数据不适用于一些指令

对于上面的代码在IDA的显示是

push    ebp
mov ebp, esp
add esp, 0FFFFFFECh
mov eax, [ebp+var_14]

0FFFFFFECh的绝对值是20,也就是16+4的长度

其实我们用​​sub esp,20​​会更加的形象

注意不可对局部变量取地址,我也不知道为什么

func proc

local arr[16]:byte
local Num
;mov esi,offset arr
;mov esi,lea arr
;mov esi,arr
mov eax,Num

上面被注释的指令都是不合法的!!!!!!!!!

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
call my
invoke ExitProcess,NULL
my proc
local arr[10]:word
ret
my endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start

在IDA里面

; Attributes: bp-based frame

fun proc near
push ebp
mov ebp, esp
add esp, 0FFFFFFECh
leave
retn
fun endp

lahf/sahf

lahf 把寄存器的低8位给了ah
sahf 把ah给了寄存器的低8位

变量声明与初始化/存储位置

全局变量

全局变量在data区

.data:00403004                 public _str
.data:00403004 ; char str[]
.data:00403004 _str db 'Dqx+Gh0st',0 ; DATA XREF: _main+26↑o
.data:0040300E align 10h
.data:00403010 public __charmax
.data:00403010 ; int _charmax
.data:00403010 __charmax dd 255

可以看到.数据在data区域

我定义的数组长度是10,然后它​​ align 10h​​进行了10字节对齐

main函数定义的变量只能在main区域使用,也叫局部变量

在函数外定义的变量才叫全局变量,不知道说对了没有

全局变量的调用,一是去data区取地址,用esp把地址放在栈区

mov     dword ptr [esp], offset _str ; "Dqx+Gh0st"

main变量

#include <stdio.h>

int main(void)
{
int x=4,y=5,z=6;
char temp[15]="Dqx-20019";
printf(temp);
return 0;
}

32位

//int的初始化
mov dword ptr [esp+44], 4
mov dword ptr [esp+40], 5
mov dword ptr [esp+36], 6
//字符串初始化
mov dword ptr [esp+21], 2D787144h
mov dword ptr [esp+25], 31303032h
mov dword ptr [esp+29], 39h ; '9'
mov word ptr [esp+33], 0
mov byte ptr [esp+35], 0
//可以看到数据放在了.text区域
//然后放在了esp高栈位

lea     eax, [esp+21]
mov [esp], eax ; Format
call _printf
//数据的使用就放在了低栈位

64位

局部变量

[ebp-n]的为局部变量

x64的局部变量声明

好比这样一个子函数

circle:

section .data
.fmt_area db "The area is %f",10,0
.fmt_circum db "The circumference is %f",10,0

section .text

push rbp
mov rbp, rsp

call area
mov rdi,.fmt_area
mov rax,1 ; area in xmm0
call printf
call circum
mov rdi,.fmt_circum
mov rax,1 ; circumference in xmm0
call printf

leave
ret

局部变量的名字带有一个点​​.​

在section .data声明

然后不要忘记section .text写代码

子函数的使用

为什么有子函数这个东西

使用子程序

为什么使用子程序?就好比为什么使用函数一样?

当一个过程..

①,比较繁琐

②.需要移植到很多处地方重复使用

那么我们就把它封装为一个函数

一个简单的函数栈建立

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
call my
invoke ExitProcess,NULL
my proc
push ebp
mov ebp,esp
sub esp,8
mov dword ptr [ebp-4],10
mov dword ptr [ebp-8],20
mov esp,ebp
;add esp,8
;add 和这里的 mov 的效果是一样的
pop ebp
ret
my endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start

写汇编的时候,局部变量使用符号...

x equ dword ptr [ebp-4]
y equ dword ptr [ebp-8]

my proc
push ebp
mov ebp,esp
sub esp,8
mov x,10
mov y,20
mov esp,ebp
pop ebp
ret
my endp

有些离谱的传入参数

mov     dword ptr [esp+4], 3
mov dword ptr [esp], 2
call func

其实上面2菊花等价于

push 3
push 2

push 3可以让dword ptr [esp+4]=3

push 2可以让dword ptr [esp+0],=2

为什么会有ebp的寻址

ebp获取的数据都是传入的参数,而参数的使用需要转移到esp中

我还不知道为什么要这么复杂

调用子函数前我们push 了2次,push 2,push 3

假设函数开辟了3*4字节.push ebp,mov ebp,esp,sub esp,12



esp+0//​​sub esp,12​



esp+4



esp+8



ebp/​​/push ebp​

A old ebp


Next IP//​​cal func​

0xbalablalbla


A old esp+4//​​push3​

3


A old esp//​​push 2​

2


所以的话要传递2,3的话



就用[ebp+8]和[ebp+12]来获取,传入函数栈用eax媒介,然后使用esp来最终的使用

参数传递与返回值

传递

参数传递的本质

把原来的数据复制一份到堆栈,然后对堆栈里面的数据进行操作

所以这就解决了形参和实参的问题

在masm32中

sub3  proc  sysCall,c_var1,c_var2
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
push 1
push 2
call sub3
;invlok sub3 C,var1,var2//不知道为什么这样写不可以

在栈中传递参数

①.push到栈里面

②.直接mov [esp+xxx],num,向栈里面直接写入数据.其实它等价于第一种

③. 通过寄存器传递参数

如何在32位下传入一个64位的参数???

Way-1

sub esp,8
fld xxx
fstp qword ptr [esp]
call func
add esp,8

把栈顶指针做一些修改就可以了

Way-2

先把高4字节入栈

再把低4字节入栈

如何在32位环境下,返回一个64位的值

将结果拆分为2个32位的,然后分别放在eax,edx

在外面的时候,把eax,edx整合在栈中

mov [ebp-4],eax
mov [ebp-8],edx

传递的规律

参数是从 ebp+8开始

局部变量是从 ebp-4开始的

参数的传递是放在了堆栈里面

是4个字节放一个参数

//基于缓冲区溢出的HelloWorld
#include<stdio.h>


int main()
{
int x1 = 1;
int x2 = 2;
int x3 = 3;
int arr[3] = { 6,7,8};
int x4 = 4;
char str[10] = {9,7,7,7,7,7,7,7,7,10 };
return 0;
}

最先声明的 离 ebp最近

0019FE90  CCCCCCCC  
0019FE94 07070709 str[0,3]
0019FE98 07070707 str[4,7]
0019FE9C CCCC0A07 str[8,9] 余下2位是CC
0019FEA0 CCCCCCCC
0019FEA4 CCCCCCCC
0019FEA8 00000004 x4
0019FEAC CCCCCCCC
0019FEB0 CCCCCCCC
0019FEB4 00000006 arr[0]
0019FEB8 00000007 arr[1]
0019FEBC 00000008 arr[2]
0019FEC0 CCCCCCCC
0019FEC4 CCCCCCCC
0019FEC8 00000003 x3
0019FECC CCCCCCCC
0019FED0 CCCCCCCC
0019FED4 00000002 x2
0019FED8 CCCCCCCC
0019FEDC CCCCCCCC
0019FEE0 00000001 x1
0019FEE4 CCCCCCCC
0019FEE8 0019FF08 Stack[000027E0]:0 ebp
0019FEEC 00411F23 invoke_main+33 eip

子函数的定义

子程序的定义

//函数的声明
函数名字 proto [距离][调用方式][可视区域][uses 寄存器列表][参数名称:参数类型,参数名称:参数类
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//函数的定义
函数的名字 proc [距离][调用方式][可视区域][uses 寄存器列表][参数名称:参数类型,参数名称:参数类型][vararg]
local 局部变量

指令

函数的名字 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//函数的调用
push xx
call yy
或者用invoke指令
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

详情见<<琢式成器>>.Page-75

距离: ...决定了最后的ret的指令是哪种类型,好比retf,retn..????

调用方式: 好比stdcall,子函数的调用方式一般是缺省的,与源代码前面的option保持一致

可视化区域; ...可能就是什么私有公开权限啥的

uses寄存器: 可以以查看一下uses的伪指令

参数名称与参数列表: 现在还不会用...

vararg:一个参数的扩展

子程序的声明

联想一下C语言

#include<stdio.h>
void fun();
int main()
{
func();
return 0;
}
void func()
{

}

如果你不声明一下函数的话,main函数的fun()是无法使用的...除非你把fun()函数写在了main()之前

对于win32的函数同样如此

win32的报错会是​​error a2006:undefined symbol:找不到程序名​

于是此刻你要像C语言一样函数声明,那你就要用到proto指令

MessageBox Proto hwnd:dword, text:dword,title:dword,Type:dword,

函数的返回值

若是32位的程序,一般通过eax寄存器返回

若是64位,一般通过edx和eax寄存器返回

若是浮点数,用st(0)返回

如果函数不写return的话

计算的临时结果就hi放弃在栈里面

写了return ,计算的结果就会存入eax

x64的函数调用

首先参数的不是靠栈的传递

对于内置函数,他又一套专门的寄存器传参

对DIY的函数,你的传的参数取决与你子函数要用哪个寄存器

非浮点数

6个参数以内

参数传递的顺序 rdi,rsi,rdx,rcx,r8,r9

第7个参数以外,假设有10个,从右边往左边压入

压入第10个,压入第9个....压入第7个

mov rdi,fmt1  
mov rsi, first ; the correct registers
mov rdx, second
mov rcx, third
mov r8, fourth
mov r9, fifth

push tenth ; now start pushing in
push ninth ; reverse order
push eighth
push seventh
push sixth
mov rax, 0
call printf

浮点数

通过xmm寄存器传递

第一个是xmm0

第二个是xmm1

依次类推

例子

递归

求和

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
mov ecx,6 ; count = 10
mov eax,0 ; holds the sum
;------------------------------------------
;传入的参数ecx,eax
;功能,实现6一直加到1
;eax是累加值
;-------------------------------------------
call CalcSum
invoke ExitProcess,NULL

;--------------------------------------------------------
CalcSum PROC
cmp ecx,0
jz L2
add eax,ecx
dec ecx
call CalcSum
;每次调用都会不断的push IP of ret
;一旦pop 出去就会返回一个函数
L2: ret
CalcSum ENDP
;--------------------------------------------------------
end start

看一下函数栈

0019FF5C  | 00401023  | sub_401016:locret_401023
0019FF60 | 00401023 | sub_401016:locret_401023
0019FF64 | 00401023 | sub_401016:locret_401023
0019FF68 | 00401023 | sub_401016:locret_401023
0019FF6C | 00401023 | sub_401016:locret_401023
0019FF70 | 00401023 | sub_401016:locret_401023

你会发现栈里面的数据都是ret的地址,一旦达到函数的返回条件,他就不断的把ret的地址pop 到IP

阶乘

我的阶乘.有点简单

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
mov eax,5
mov ebx,eax
call func
invoke ExitProcess,NULL
;--------------------------------------------------------
func PROC
dec ebx
cmp ebx,0
jz over
mul ebx
call func
over:
ret
func ENDP
;--------------------------------------------------------
end start

整体实现的一个流程是

eax--,然后不断的push eax

直到eax=0,就让eax=1,开始在栈里面取数据做乘法

对书上的理解,我又写的

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
push 5
call func
invoke ExitProcess,NULL
;--------------------------------------------------------
func PROC
push ebp
mov ebp,esp
mov eax,[ebp+8]
cmp eax,1
jz q1
dec eax
push eax
call func
mov ebx,[ebp+8]
mul ebx
q1:
pop ebp
retn 4
;为什么pop
;ebp?为了让eip指向返回地址
;为什么ebx,[ebp+8]
;因为完全进入了一个完整的函数,参数的获取
;怎么保持的栈平衡?让eip指向了正确的地址
;为什么是retn 4
;为了让esp指ebp的位置


func ENDP
;--------------------------------------------------------
end start

书上的方法,很妙'很复杂

'

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

start:
push 5
call func
invoke ExitProcess,NULL
;--------------------------------------------------------
func PROC
push ebp
mov ebp,esp
mov eax,[ebp+8]
cmp eax,0
ja break_up
mov eax,1
jmp ok
break_up:
dec eax
push eax
call func
begin_mul:
mov ebx,[ebp+8]
mul ebx

ok: pop ebp
ret 4
;pop ip
;esp+=4
func ENDP
;--------------------------------------------------------
end start

x64 计数圆的周长/面积

section .data       

radius dq 10.0


;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section .bss


;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section .text

extern printf
extern area
extern circum
extern circle

global main
main:
mov rbp, rsp; for correct debugging
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
mov rbp, rsp;
push rbp
mov rbp, rsp

call circle

leave
ret
;------------------------------------------------------
circle:

section .data
.fmt_area db "The area is %f",10,0
.fmt_circum db "The circumference is %f",10,0
section .text

push rbp
mov rbp, rsp

call area
mov rdi,.fmt_area
mov rax,1 ; area in xmm0
call printf
call circum
mov rdi,.fmt_circum
mov rax,1 ; circumference in xmm0
call printf

leave
ret
;----------------------------------------------------
;function name:area
;no var get in
;proc: calc PI x R^2
;return xmm0

area:
section .data
.pi dq 3.141592654 ; local to area
section .text

push rbp
mov rbp, rsp

movsd xmm0, [radius]
mulsd xmm0, [radius]
mulsd xmm0, [.pi]

leave
ret
;----------------------------------------------------
;fuction name:circum
;no var get in
;proc: 2*PI*R
;return xmm0

circum:
section .data
.pi dq 3.14 ; local to circum
section .text

push rbp
mov rbp, rsp

movsd xmm0, [radius]
addsd xmm0, [radius]
mulsd xmm0, [.pi]

leave
ret
;----------------------------------------------------

裸函数

导入

void __declspec(naked) Function()         
{
...
}

上面的函数调用时,为什么会出错?

void __declspec(naked) Function()
{

__asm ret
}

对于第一种裸函数的写法,编译器不会给你生成任何东西,就连ret都不会给你生成

所以对于空的裸函数,你要写上​​ret​

无参数和返回值的裸函数

void __declspec(naked) Function()           
{
__asm
{
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push esi
push edi
lea edi,dword ptr ds:[ebp-0x40]
mov eax,0xCCCCCCCC
mov ecx,0x10
rep stosd

pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp

ret
}
}

有参数和返回值

int __declspec(naked) Function(int x,int y)   //实现x+y       
{
__asm
{
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push esi
push edi
lea edi,dword ptr ds:[ebp-0x40]
mov eax,0xCCCCCCCC
mov ecx,0x10
rep stosd

mov eax,dword ptr ds:[ebp+8]
add eax,dword ptr ds:[ebp+0xC]

pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp

ret
}
}

带局部变量的裸函数

int __declspec(naked) Function(int x,int y)           
{
__asm
{
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push esi
push edi
lea edi,dword ptr ds:[ebp-0x40]
mov eax,0xCCCCCCCC
mov ecx,0x10
rep stosd
mov dword ptr ds:[ebp-4],2
mov dword ptr ds:[ebp-8],3

mov eax,dword ptr ds:[ebp+8]
add eax,dword ptr ds:[ebp+0xC]

pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp

ret
}
}

MS-Windows

可以对比一下你8086学的int21h的ah=9,或者其它之类的中断……

8086的中断比较晦涩难懂

win32的API就比较好理解一些

有的API是有返回值的

如果返回值是32位寄存器放得下的,那么一定放在eax中,如果放不下

情况1,eax放一个返回值的指针

情况2,返回值到了buff区域

数据类型表

MS-Windows 类型

MASM类型

说明

BOOL, BOOLEAN

DWORD

布尔值 (TRUE 或 FALSE)

BYTE

BYTE

8 位无符号整数

CHAR

BYTE

8 位 Windows ANSI 字符

COLORREF

DWORD

作为颜色值的 32 位数值

DWORD

DWORD

32 位无符号整数

HANDLE

DWORD

对象句柄

HFILE

DWORD

用 OpenFile 打开的文件的句柄

INT

SDWORD

32 位有符号整数

LONG

SDWORD

32 位有符号整数

LPARAM

DWORD

消息参数,由窗口过程和回调函数使用

LPCSTR

PTR BYTE

32 位指针,指向由 8 位 Windows (ANSI)字符组成的空字节结束的字符串常量

LPCVOID

DWORD

指向任何类型的常量

LPSTR

PTR BYTE

32 位指针,指向由 8 位 Windows (ANSI) 字符组成的空字节结束的字符串

LPCTSTR

PTR WORD

32 位指针,指向对 Unicode 和双字节字符集可移植的字符串常量

LPTSTR

PTR WORD

32 位指针,指向对 Unicode 和双字节字符集可移植的字符串

LPVOID

DWORD

32 位指针,指向未指定类

LRESULT

DWORD

窗口过程和回调函数返回的 32 位数值

SIZE_T

DWORD

一个指针可以指向的最大字节数

UNIT

DWORD

32 位无符号整数

WNDPROC

DWORD

32 位指针,指向窗口过程

WORD

WORD

16 位无符号整数

WPARAM

DWORD

作为参数传递给窗口过程或回调函数的 32 位数值

函数一览表

下表为所有 Win32 控制台函数的一览表。在 ​​www.msdn.microsoft.com​​ 上可以找到 MSDN 库中每个函数的完整描述。

提示:Win32 API 函数不保存 EAX、EBX、ECX 和 EDX,因此程序员需自己完成这些寄存器的入栈和出栈操作。

所以你会发现,一个函数使用,你的寄存器会发生一些改变

函数

描述

AllocConsole

为调用进程分配一个新控制台

CreateConsoleScreenBuffer

创建控制台屏幕缓冲区

ExitProcess

结束进程及其所有线程

FillConsoleOutputAttribute

为指定数量的字符单元格设置文本和背景颜色属性

FillConsoleOutputCharacter

按指定次数将一个字符写入屏幕缓冲区

FlushConsoleInputBuffer

刷新控制台输入缓冲区

FreeConsole

将主调进程与其控制台分离

GenerateConsoleCtrlEvent

向控制台进程组发送指定信号,这些进程组共享与主调进程关联的控制台

GetConsoleCP

获取与主调进程关联的控制台使用的输入代码页

GetConsoleCursorInfo

获取指定控制台屏幕缓冲区光标大小和可见性的信息

GetConsoleMode

获取控制台输入缓冲区的当前输入模式或控制台屏幕缓冲区的当前输出模式

GetConsoleOutputCP

获取与主调进程关联的控制台使用的输出代码页

GetConsoleScreenBufferInfo

获取指定控制台屏幕缓冲区信息

GetConsoleTitle

获取当前控制台窗口的标题栏字符串

GetConsoleWindow

获取与主调进程关联的控制台使用的窗口句柄

GetLargestConsoleWindowSize

获取控制台窗口最大可能的大小

GetNumberOfConsoleInputEvents

获取控制台输入缓冲区中未读输入记录的个数

GetNumberOfConsoleMouseButtons

获取当前控制台使用的鼠标按钮数

GetStdHandle

获取标准输入、标准输出或标准错误设备的句柄

HandlerRoutine

与 SetConsoleCtrlHandler 函数一起使用的应用程序定义的函数

PeekConsoleInput

从指定控制台输入缓冲区读取数据,且不从缓冲区删除该数据

ReadConsole

从控制台输入缓冲区读取并删除输入字符

ReadConsoleInput

从控制台输入缓冲区读取并删除该数据

ReadConsoleOutput

从控制台屏幕缓冲区的矩形字符单元格区域读取字符和颜色属性数据

ReadConsoleOutputAttribute

从控制台屏幕缓冲区的连续单元格复制指定数量的前景和背景颜色属性

ReadConsoleOutputCharacter

从控制台屏幕缓冲区的连续单元格复制若干字符

ScrollConsoleScreenBuffer

移动屏幕缓冲区内的一个数据块

SetConsoleActiveScreenBuffer

设置指定屏幕缓冲区为当前显示的控制台屏幕缓冲区

SetConsoleCP

设置主调过程的控制台输入代码页

SetConsoleCtrlHandler

为主调过程从处理函数列表中添加或删除应用程序定义的 HandlerRoutine

SetConsoleCursorInfo

设置指定控制台屏幕缓冲区光标的大小和可见度

SetConsoleCursorPosition

设置光标在指定控制台屏幕缓冲区中的位置

SetConsoleMode

设置控制台输入缓冲区的输入模式或者控制台屏幕缓冲区的输出模式

SetConsoleOntputCP

设置主调过程的控制台输出代码页

SetConsoleScreenBufferSize

修改指定控制台屏幕缓冲区的大小

SetConsoleTextAttribute

设置写入屏幕缓冲区的字符的前景(文本)和背景颜色属性

SetConsoleTitle

为当前控制台窗口设置标题栏字符串

SetConsoleWindowInfo

设置控制台屏幕缓冲区窗口当前的大小和位置

SetStdHandle

设置标准输入、输出和标准错误设备的句柄.

WriteConsole

向由当前光标位置标识开始的控制台屏幕缓冲区写一个字符串

WriteConsoleInput

直接向控制台输入缓冲区写数据

WriteConsoleOutput

向控制台屏幕缓冲区内指定字符单元格的矩形块写字符和颜色属性数据

WriteConsoleOutputAttribute

向控制台屏幕缓冲区的连续单元格复制一组前景和背景颜色属性

WriteConsoleOutputCharacter

向控制台屏幕缓冲区的连续单元格复制一组字符

句柄

他只是用来表示各种资源的编号

书上说了这么一句话,不是很理解

加入你把句柄=11给了应用程序,应用程序不知道他是什么

你把句柄=11给了windows,windows就知道去查找哪个窗口

可能他说了句柄也有分类,线程局部,窗口句柄,文件句柄........

他还说,如果以前有10个窗口,你给了windows11号窗口

当原来的10个窗口关闭,留下5个,那么你的11号窗口就变了6号窗口

他说我们不需要关系句柄的值,用就对了

模块

一个模块代表了运行中的exe或者dll文件,用来代表这个文件中所有的代码与资源

每个不同的模块都有唯一的的模块句柄来标识,利用这个句柄我们可以访问文件的资源...

很多的API函数都要用到都要用到模块的句柄,以便利用程序的各种资源

在win32上,模块句柄的值==程序在内存中载入的启示地址

Messagebox A/W

Messagebox一般默认为MessageboxA

MessageboxA一般用于ANSI标准,这个标准你之前也是遇到过的,不能显示中文,

ANSI规定字符占一个字节,我们就用MessageboxA

Unicode码规定字符占2个字节,我们就用MessageboxW

MessageBoxA PROTO,
hWnd:DWORD, ;窗口句柄(可以为空)
lpText:PTR BYTE, ;字符串,对话框内
lpCaption:PTR BYTE, ;字符串,对话框标题
uType:DWORD ;内容和行为

hwnd 基于控制台的应用程序可以将 hWnd 设置为空,表示该消息框没有相关的包含窗口或父窗口。

lpText 消息框内容的指针

lpCaption 消息框标题的指针

uType 指定对话框的内容和行为

内容和行为

uType 参数包含的位图整数组合了三种选项:显示按钮、图标和默认按钮选择。几种可能的按钮组合如下:

  • MB_OK
  • MB_OKCANCEL
  • MB_YESNO
  • MB_YESNOCANCEL
  • MB_RETRYCANCEL
  • MB_ABORTRETRYIGNORE
  • MB_CANCELTRYCONTINUE

默认按钮

可以选择按钮作为用户点击 Enter 键时的自动选项。选项包括

MB_DEFBUTTON1(默认)、MB_DEFBUTTON2、MB_DEFBUTTON3 和 MB_DEFBUTTON4。

按钮从左到右,从 1 开始编号。

图标

有四个图标可用。有时多个常数会产生相同的图标:

  • 停止符:MB_ICONSTOP. MB_ICONHAND 或 MB_ICONERROR
  • 问号(?):MB_ICONQUESTION
  • 信息符(i):MB_ICONINFORMATION、MB_ICONASTERISK
  • 感叹号(!):MB_ICONEXCLAMATION、MB_ICONWARNING

返回值

如果 MessageBoxA 失败,则返回零;

否则,它将返回一个整数以表示用户在关闭对话框时点击的按钮。

选项包括 IDABORT、IDCANCEL、IDCONTINUE、IDIGNORE、IDNO、IDOK、IDRETRY、IDTRYAGAIN,以及 IDYES。

如果想要消息框窗口浮动于桌面所有其他窗口之上,就在传递的最后一个参数(uType 参数)值上添加 MB_SYSTEMMODAL 选项

例子

include   Dqx.inc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data

captionW BYTE "Warning",0
warningMsg BYTE "The current operation may take years "
BYTE "to complete.",0

captionQ BYTE "Question",0
questionMsg BYTE "A matching user account was not found."
BYTE 0dh,0ah,"Do you wish to continue?",0

captionC BYTE "Information",0
infoMsg BYTE "Select Yes to save a backup file "
BYTE "before continuing,",0dh,0ah
BYTE "or click Cancel to stop the operation",0

captionH BYTE "Cannot View User List",0
haltMsg BYTE "This operation not supported by your "
BYTE "user account.",0


;--------------------------------------------------------
.code
start:
; 显示感叹号图标和 OK 按钮
INVOKE MessageBoxA,
NULL,
ADDR warningMsg,
ADDR captionW,
MB_OK + MB_ICONEXCLAMATION

; 显示问号图标和 Yes/No 按钮
INVOKE MessageBoxA,
NULL,
ADDR questionMsg,
ADDR captionQ,
MB_YESNO + MB_ICONQUESTION

; 解释用户点击的按钮
cmp eax,IDYES ; YES button clicked?

; 显示信息图标和 Yes/No/Cancel 按钮
INVOKE MessageBoxA,
NULL,
ADDR infoMsg,
ADDR captionC,
MB_YESNOCANCEL + MB_ICONINFORMATION + MB_DEFBUTTON2

; 显示停止图标和 OK 按钮
INVOKE MessageBoxA,
NULL,
ADDR haltMsg,
ADDR captionH,
MB_OK + MB_ICONSTOP

invoke ExitProcess,NULL
end start
;-----------------------------------------------------

窗口输入

ReadConsole 读取输入

函数 ReadConsole 为读取文本输入并将其送入缓冲区提供了便捷的方法。其原型如下所示:

  ReadConsole PROTO,
hConsoleInput: HANDLE z ;输入句柄
lpBuffer:PTR BYTE, ;缓冲区指针
nNumberOfCharsToRead:DWORD, ;读取的字符数
lpNumberOfCharsRead:PTR DWORD, ;指向读取字节数的指针
lpReserved:DWORD ;未使用

hConsoleInput 是函数 GetStdHandle 返回的可用控制台输入句柄。

lpBuffer 是字符数组的偏移量。

nNumberOfCharsToRead 是一个 32 位整数,指明读取的最大字符数。

lpNumberOfCharsRead 是一个允许函数填充的双字指针,当函数返回时,字符数的计数值将被放入缓冲区。

最后一个参数未使用,因此传递的值为 0。

在调用 ReadConsole 时,输入缓冲区还要包含两个额外的字节用来保存行结束字符。

如果希望输入缓冲区里是空字节结束字符串,则用空字节来代替内容为 ODh 的字节。Irvine32.lib 的过程 ReadString 就是这样操作的。

注意:Win32 API 函数不会保存 EAX、EBX、ECX 和 EDX 寄存器。

例子

include     Dqx.inc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
max = 80
Input_Buff BYTE max DUP(?),0,0
An_Input_Handle HANDLE ?
len DWORD ?
captionC byte "Gh0st",0

;--------------------------------------------------------
.code
start:
; 获取标准输入句柄
INVOKE GetStdHandle, STD_INPUT_HANDLE
mov An_Input_Handle,eax

; 等待用户输入
INVOKE ReadConsole,
An_Input_Handle ,
ADDR Input_Buff,
max,
ADDR len,
0

INVOKE MessageBoxA,
NULL,
ADDR Input_Buff,
ADDR captionC,
MB_YESNOCANCEL + MB_ICONINFORMATION + MB_DEFBUTTON2

invoke ExitProcess,NULL
end start
;-----------------------------------------------------

它并没有等待我的输入,我根本就没啥输入的地方,以后慢慢解决

解决办法就是修改编译方式

LINK_FLAG = /subsystem:console

输入

I am here !
然后回车

得到长度13=2+11

IDA-data

 aIAmHere db 'I am here !',0Dh,0Ah,0

所以会有11个

GetLastError/FormatMessage 获取错误消息

GetLastError没有参数

FormatMessage

FormatMessage PROTO,         ;格式化消息
dwFlags:DWORD, ;格式化选项
lpSource:DWORD, ;消息定义的位置
dwMsgID:DWORD, ;消息标识符
dwLanguageID:DWORD, ;语言标识符
lpBuffer:PTR BYTE, ;缓冲区接收字符串指针
nSize:DWORD, ;缓冲区大小
va_list: DWORD ;参数列表指针

下面简要列出了最常用的参数值。除了 lpBuffer 是输出参数外,其他都是输入参数

  1. dwFlags

保存格式化选项的双字整数,包括如何解释参数 lpSource。

它规定怎样处理换行,以及格式化输出行的最大宽度.

建议值为 FORMAT_MESSAGE_ALLOCATE_BUFFER 和 FORMAT_MESSAGE_FROM_SYSTEM。

  1. lpSource

消息定义位置的指针。若按照建议值设置 dwFlags,则 lpSource 设置为 NULL(0)。

  1. dwMsgID

调用 GetLastError 后返回的双字整数。

  1. dwLanguageID

语言标识符。若将其设置为 0,则消息为语言无关,否则将对应于用户的默认语言环境。

  1. lpBuffer( 输出参数 )

接收空字节结束消息字符串的缓冲区指针。如果使用了 FORMAT_MESSAGE_ALLOCATE_BUFFER 选项,则会自动分配缓冲区。

  1. nSize

用于指定一个缓冲区来保存消息字符串。如果 dwFlags 使用了上述建议选项,则该参数可以设置为 0。

  1. va_list

数组指针,该数组包含了可以插入到格式化消息的值。由于没有格式化错误消息,这个参数可以为 NULL(0)。

例子

include   Dqx.inc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data

messageId DWORD ?
pErrorMsg DWORD ? ;指向错误消息


;--------------------------------------------------------
.code
start:


.code
call GetLastError
mov messageId,eax
INVOKE FormatMessage,
FORMAT_MESSAGE_ALLOCATE_BUFFER + FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
messageId,
0,
ADDR pErrorMsg,
0,
NULL
INVOKE LocalFree, pErrorMsg
invoke ExitProcess,NULL
end start
;-----------------------------------------------------

ReadChar/ReadKey 获取当前按键

  • ReadChar:等待键盘输入一个 ASCII 字符,并用 AL 返回该字符。
  • ReadKey:过程执行无等待键盘检查。
    如果控制台输入缓冲区中没有等待的按键,则ZF = 1。
    如果发现有按键,则ZF=0且 AL 等于零或 ASCII 码。EAX 和 EDX 的高 16 位被覆盖。

如果 AL 等于 0,那么用户可能按下了特殊键(功能键、光标箭头等)。

AH 寄存器为键盘扫描码。DX 为虚拟键 码,EBX 为键盘控制键状态信息。

下表为控制键值列表。调用 ReadKey 之后,可以用 TEST 指令检查各种键值。


含义


含义

CAPSLOCK_ON

CAPSLOCK 指示灯亮

RIGHT_ALT_PRESSED

右 ALT 键被按下

ENHANCED_KEY

被按下增强的

RIGHT_CTRL_PRESSED

右 CTRL 键被按下

LEFT_ALT_PRESSED

该键是左 ALT 键

SCROLLLOCL_ON

SCROLLLOCK 指示灯亮

LEFT_CTRL_PRESSED

左 CTRL 键被按下

SHIFT_PRESSED

SHIFT 键被按下

NUMLOCK_ON

NUMLOCK 指示灯亮



GetKeyState 获取当前按下的键

通过测试单个键盘按键可以发现当前按下的是哪个键。方法是调用 API 函数 GetKeyState。

GetKeyState PROTO, nVirtKey:DWORD

向该函数传递如下表所示的虚拟键值。测试程序必须按照同一个表来测试 EAX 里面的返回值。

按键

虚拟键符号

EAX 中被测试的位

NumLock

VK_NUMLOCK

0

Scroll Lock

VK_SCROLL

0

Left Shift

VK_LSHIFT

15

Right Shift

VK_tRSHIFT

15

Left Ctrl

VK_LCONTROL

15

Right Ctrl

VK_RCONTROL

15

Left Menu

VK_LMENU

15

Right Menu

VK_RMENU

15

窗口输出

WriteConsole 输出

函数 WriteConsole 在控制台窗口的当前光标所在位置写一个字符串,并将光标留着字符串末尾右边的字符位置上。

WriteConsole PROTO,
hConsoleOutput:HANDLE,
lpBuffer:PTR BYTE,
nNumberOfCharsToWrite:DWORD,
lpNumberOfCharsWritten:PTR DWORD,
lpReserved:DWORD

hConsoleOutput 是控制台输出流句柄;

lpBuffer 是输出字符数组的指针;

nNumberOfCharsToWrite 是数组长度;

lpNumberOfCharsWritten 是函数返回时实际输出字符数量的整数指针。

最后一个参数未使用,因此将其设置为 0。

例子

include   Dqx.inc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
endl EQU <0dh,0ah>

b_str label byte
BYTE "hello I am here",0Ah
BYTE "I have no friends ",0Ah
BYTE "This is my friend.", endl
len DWORD ($-b_str)

A_Output_Handle HANDLE 0 ; 标准输出设备句柄
how_much DWORD ? ; 输出字节数

;--------------------------------------------------------
.code
start:

INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov A_Output_Handle,eax

INVOKE WriteConsole,
A_Output_Handle, ; 控制台输出句柄
ADDR b_str, ; 字符串指针
len, ; 字符长度
ADDR how_much, ; 返回输出字节数
0 ; 未使用


invoke ExitProcess,NULL
end start
;-----------------------------------------------------

WriteConsoleOutputCharacter

WriteConsoleOutputCharacter PROTO,
hConsoleOutput:HANDLE, ;控制台输出句柄
lpCharacter :PTR BYTE, ;缓冲区指针
nLength: DWORD, ;缓冲区大小
dwWriteCoord: COORD, ;第一个单元格的坐标
lpNumberOfCharsWritten: PTR DWORD ;输出计数器

下面是一个失败的例子

还要修改

include   Dqx.inc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
endl EQU <0dh,0ah>

b_str label byte
BYTE "hello I am here",0Ah
BYTE "I have no friends ",0Ah
BYTE "This is my friend.", endl
len DWORD ($-b_str)

A_Output_Handle HANDLE 0 ; 标准输出设备句柄
how_much DWORD ? ; 输出字节数
dwWriteCoord COORD <0,0>
;--------------------------------------------------------
.code
start:

INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov A_Output_Handle,eax

INVOKE WriteConsoleOutputCharacter,
A_Output_Handle, ; 控制台输出句柄
ADDR b_str, ; 字符串指针
len, ; 字符长度
dwWriteCoord, ; 返回输出字节数
ADDR how_much ; 未使用


invoke ExitProcess,NULL
end start
;-----------------------------------------------------

文件 处理

CreateFile

函数 CreateFile 可以创建一个新文件或者打开一个已有文件。

如果调用成功,函数返回打开文件的句柄;

否则,返回特殊常数 INVALID_HANDLE_VALUEO

原型如下:

CreateFile PROTO,                           ;创建新文件
lpFilename:PTR BYTE, ;文件名指针
dwDesiredAccess:DWORD, ;访问模式
dwShareMode:DWORD, ;共享模式
lpSecurityAttributes:DWORD, ;安全属性指针
dwCreationDisposition:DWORD, ;文件创建选项
dwFlagsAndAttributes:DWORD, ;文件属性
hTemplateFile:DWORD ;文件模板句柄

下表对参数进行了说明。如果函数调用失败则返回值为零。

参数

说明

lpFileName

指向一个​​0​​空字节结束字符串,该串为部分或全部合格的文件名 例如(C:\path\filename)

dwDesiredAccess

指定文件访问方式(读或写)

dwShareMode

控制多个程序对打开文件的访问能力

lpSecurityAttributes

指向安全结构,该结构控制安全权限

dwCreationDisposition

指定文件存在或不存在时的操作

dwFlagsAndAttributes

包含位标志指定文件属性,如存档、加密、隐藏、普通、系统和临时

hTemplateFile

包含一个可选的文件模板句柄,该文件为已创建的文件提供文件属性和扩展属性;如果不使用该参数,就将其设置为 0

关于上面说到的参数

dwDesiredAccess

参数 dwDesiredAccess 允许指定对文件进行读访问、写访问、读/写访问,或者设备查询访问。

可以从下表列出的值中选择,也可以从表中未列出的更多特定标志值选择。


含义

0

为对象指定设备查询访问。应用程序可以查询设备属性而无需访问设备,也可以检查文件是否存在

GENERIC_READ

读访问。可以从文件中读取数据,文件指针可以移动。与 GENERIC_WRITE 一起使用为读/写访问

GENERIC_WRITE

写访问。可以向文件中写入数据,文件指针可以移动。与 GENERIC_READ 一起使用为读/写访问

dwCreationDisposition

参数 dwCreationDisposition 指定当文件存在或不存在时应采取怎样的操作。可从下表中选择一个值。


含义

CREATE_NEW

创建一个新文件。要求将参数 dwDesiredAccess 设置为 GENERIC_WRITE。如果文件已经存在,则函数调用失败

CREATE_ALWAYS

创建一个新文件。如果文件已存在,则函数会覆盖原文件,清除现有属性,并合并文件 属性与预定义的常数 FILE_ATTRIBUTES_ARCHIVE 中属性参数指定的标志。要求将参数 dwDesiredAccess 设置为 GENERIC WRITE

OPEN_EXISTING

打开文件。如果文件不存在,则函数调用失败。可用于读取和/或写入文件

OPEN_ALWAYS

如果文件存在,则打开文件。如果不存在,则函数创建文件,就好像CreateDisposition 的值为 CREATE NEW

TRUNCATE_EXISTING

打开文件。一旦打开,文件将被截断,使其大小为零。要求将参数 dwDesiredAccess 设置为 GENERIC_WRITE。如果文件不存在,则函数调用失败

下表列出了参数 dwFlagsAndAttributes 比较常用的值。

允许任意属性组合,除了 FILE_ATTRIBUTE_NORMAL 会被其他 所有属性覆盖。

这些值能映射为 2 的幂,因此可以用汇编时 OR 运算符或 + 运算符将它们组 合为一个参数:

例如

FILE_ATTRIBUTE_HIDDEN OR FILE_ATTRIBUTE_READONLY
FILE_ATTRIBUTE_HIDDEN + FILE_ATTRIBUTE_READONLY

属性

含义

FILE_ATTRIBUTE_ARCHIVE

文件存档。应用程序使用这个属性标记文件以便备份或移动

FILE_ATTRIBUTE_HIDDEN

文件隐藏。不包含在普通目录列表中

FILE_ATTRIBUTE_NORMAL

文件没有其他属性设置。该属性只在单独使用时有效

FILE_ATTRIBUTE_READONLY

文件只读。应用程序可以读文件但不能写或删除文件

FILE_ATTRIBUTE_TEMPORARY

文件被用于临时存储

打开并读取(输入)已存在文件:

INVOKE CreateFile,
ADDR filename, ;文件名指针
GENERIC_READ, ;读文件
DO_NOT_SHARE, ;共享模式
NULL, ;安全属性指针
OPEN_EXISTING, ;打开已存在文件
FILE_ATTRIBUTE_NORMALA ;普通文件属性
0 ;未使用

打开并写入(输出)已存在文件。

文件打开后,可以通过写入覆盖当前数据,

或者将文件指针移到末尾,向文件添加新数据(参见11.1.6节的SetFilePointer):

INVOKE CreateFile,
ADDR filename,
GENERIC_WRITEZ, ;写文件
DO_NOT_SHARE, ;共享模式
NULL, ;安全属性指针
OPEN_EXISTIN, ;文件必须存在
FILE_ATTRIBUTE_NORMAL,
0 ;未使用

创建有普通属性的新文件,并删除所有已存在的同名文件:

INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE, ;写文件
DO _NOT_SHARE,
NULL,
CREATE_ALWAYS, ;覆盖已存在的文件
FILE_ATTRIBUTE_NORMAL,
0

若文件不存在,则创建文件;否则打开并输出现有文件:

INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE, ;写文件
DO_NOT_SHARE,
NULL,
CREATE_NEW, ;不删除已存在文件
FILE_ATTRIBUTE_NORMAL,
0

CloseHandle

函数 CloseHandle 关闭一个打开的对象句柄。其原型如下

CloseHandle PROTO,
hObject: HANDLE ;对象句柄

ReadFile

函数 ReadFile 从输入文件中读取文本。其原型如下:

ReadFile PROTO,
hFile:HANDLE, ;输入句柄
lpBuffer:PTR BYTE, ;缓冲区指针
nNumberOfBytesToRead:DWORD, ;读取的字节数
lpNumberOfBytesRead:PTR DWORD, ;实际读出的 字节数
lpOverlapped:PTR DWORD ;异步信息指针

其中:

  • hFile 是由 CreateFile 返回的打开文件的句柄;
  • lpBuffer 指向的缓冲区接收从该文件读取的数据;
  • nNumberOfBytesToRead 定义从该文件读取的最大字节数;
  • lpNumberOfBytesRead 指向的整数为函数返回时实际读取的字节数;
  • lpOverlapped 应被设置为 NULL(0)。若函数调用失败,则返回值为零。

如果对同一个打开文件的句柄进行多次调用,那么 ReadFile 就会记住最后一次读取的位置,并从这个位置开始读。换句话说,函数有一个内部指针指向文件内的当前位置。

WriteFile

函数 WriteFile 用输出句柄向文件写入数据。

句柄可以是屏幕缓冲区句柄,

也可以是分配给文本文件的句柄。

函数从文件内部位置指针所指向的位置开始写数据。

写操作完成后,文件位置指针按照实际写入的字节数进行调整。函数原型如下:

WriteFile PROTO,
hFile:HANDLE, ;输出句柄
lpBuffer:PTR BYTE, ;缓冲区指针
nNumberOfBytesToWrite:DWORD, ;缓冲区大小
lpNumberOfBytesWritten:PTR DWORD, ;写入字节数
lpOverlapped:PTR DWORD ;异步信息指针

中:

  • hFile 是已打开文件的句柄;
  • lpBuffer 指向的缓冲区包含了写入到文件的数据;
  • nNumberOfBytesToWrite 指定向文件写入多少字节;
  • lpNumberOfBytesWritten 指向的整数为函数执行后实际写入的字节数;
  • 若为同步操作,则 lpOverlapped 应被设置为 NULL。若函数调用失败,则返回值为零。

SetFilePointer

函数 SetFilePointer 移动打开文件的位置指针。该函数可以用于向文件添加数据,或是执行随机访问记录处理:

SetFilePointer PROTO,
hFile:HANDLE, ;文件句柄
lpDistanceToMove:SDWORD, ;指针移动 字节数
lpDistanceToMoveHigh:PTR SDWORD, ;指针移动字节数,高双字
dwMoveMethod:DWORD ;起点

lpDistance移动距离本身为 64 位有符号整数值,分为两个部分:

  • lpDistanceToMove:低 32 位
  • lpDistanceToMoveHigh:含有高 32 位的变量指针

dwMoveMode 指定文件指针移动的起点,选择项为 3 个预定义符号:FILE_BEGIN、FILE_CURRENT 和 FILE_END。

若函数调用失败,则返回值为零。

如果 lpDistanceToMoveHigh 为空,则只用 lpDistanceToMove 的值来移动文件指针。例如,下面的代码准备添加到一个文件末尾:

INVOKE SetFilePointer,
fileHandle, ;文件句柄
0, ;距离低32位
0, ;距离高32位
FILE_END ;移动模式

控制台窗口操作

下列函数影响的是控制台窗口及其相对于屏幕缓冲区的位置:

  • SetConsoleWindowInfo:设置控制台窗口相对于屏幕缓冲区的大小和位置。
  • GetConsoleScreenBufferInfo:返回(还包括其他一些信息)控制台窗口相对于屏幕缓冲区的矩形坐标。
  • SetConsoleCursorPosition:将光标设置在屏幕缓冲区内的任何位置;如果区域不可见,则移动控制台窗口直到光标可见。
  • ScrollConsoleScreenBuffer:移动屏幕缓冲区中的一些或全部文本,本函数会影响控制台窗口显示的文本。

SetConsoleTitle

改变控制台窗口的标题

.data
.titleStr BYTE "Console title", 0
.code
INVOKE SetConsoleTitle, ADDR titleStr

GetConsoleScreenBufferInfo

函数 GetConsoleScreenBufferInfo 返回控制台窗口的当前状态信息。它有两个参数:控制台屏幕的句柄和指向该函数填充的结构的指针:

GetConsoleScreenBufferInfo PROTO,
hConsoleOutput:HANDLE,
lpConsoleScreenBufferInfo:PTR CONSOLE_SCREEN_BUFFER_INFO

函数示例调用如下所示:

.data
consoleInfo CONSOLE_SCREEN_BUFFER_INFO <>
outHandle HANDLE ?
.code
INVOKE GetConsoleScreenBufferInfo, outHandle,
ADDR consoleInfo

CONSOLE_SCREEN_BUFFER_INFO 结构如下:

CONSOLE_SCREEN_BUFFER_INFO STRUCT
dwSize COORD <>
dwCursorPosition COORD <>
wAttributes WORD ?
srWindow SMALL_RECT <>
dwMaximumWindowSize COORD <>
CONSOLE_SCREEN_BUFFER_INFO ENDS

dwSize 按字符行列数返回屏幕缓冲区大小。

dwCursorPosition 返回光标的位置。这两个字段都是 COORD 结构。

wAttributes 返回字符的前景色和背景色,字符由诸如 WriteConsole 和 WriteFile 等函数写到控制台。

srWindow 返回控制台窗口相对于屏幕缓冲区的坐标。

dwMaximumWindowSize 以当前屏幕缓冲区的大小、字体和视频显示大小为基础,返回控制台窗口的最大尺寸。

SetConsoleWindowInfo

函数 SetConsoleWindowInfo 可以设置控制台窗口相对于其屏幕缓冲区的大小和位置。函数原型如下:

SetConsoleWindowInfo PROTO,
hConsoleOutput:HANDLE, ;屏幕缓冲区句柄
bAbsolute:DWORD, ;坐标类型
lpConsoleWindow:PTR SMALL_RECT ;矩形窗口指针

bAbsolute 说明如何使用结构中由 lpConsoleWindow 指出的坐标。

如果 bAbsolute 为真,则坐标定义控制台窗口新的左上角和右下角。

如果 bAbsolute 为假,则坐标与当前窗口坐标相加。

什么叫真真假假????写一个1,他也是32位的BOOL值

例子

include   Dqx.inc

; Appending to a File (AppendFile.asm)

; This program appends text to an existing file.

.data
message BYTE ": This line of text was written "
BYTE "I am Dqx_Gh0st!",0dh,0ah

messageSize DWORD ($-message)
outHandle HANDLE 0 ; 标准输出句柄
bytesWritten DWORD ? ; 已写入字节数
lineNum DWORD 0
windowRect SMALL_RECT <0,0,60,11> ; 上,下,左,右


.code
start:

INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov outHandle,eax

.REPEAT

INVOKE WriteConsole,
outHandle, ; 控制台输出句柄
ADDR message, ; 字符串指针
messageSize, ; 字符串长度
ADDR bytesWritten, ; 返回已写字节数
0 ; 未使用
inc lineNum ; 下一行编号
.UNTIL lineNum > 5

; 调整控制台窗口相对于屏幕缓冲区的大小和位置
INVOKE SetConsoleWindowInfo,
outHandle,
TRUE,
ADDR windowRect

invoke ExitProcess,NULL
end start
;-----------------------------------------------------

SetConsoleScreenBufferSize

可以将屏幕缓冲区设置为 X 列 * Y 行。其原型如下:

SetConsoleScreenBufferSize PROTO,
hConsoleOutput:HANDLE, ;屏幕缓冲区句柄
dwSize:COORD ;新屏幕缓冲区大小

控制台光标设置函数

Win32 API 提供了函数用于设置控制台应用光标的大小、可见度和屏幕位置。

与这些函数相关的重要是 CONSOLE_CURSOR_INFO,其中包含了控制台光标的大小和可见度信息:

CONSOLE_CURSOR_INFO STRUCT
dwSize DWORD ?
bVisible DWORD ?
CONSOLE_CURSOR_INFO ENDS

wSize 为光标填充的字符单元格的百分比(从 1 到 100)。

如果光标可见,则 bVisible 等于 TRUE(1)。

GetConsoleCursorInfo

返回控制台光标的大小和可见度。需向其传递指向结构 CONSOLE_CURSOR_INFO 的指针:

GetConsoleCursorInfo PROTO,
hConsoleOutput:HANDLE,
lpConsoleCursorInfo:PTR CONSOLE_CURSOR_INFO

默认情况下,光标大小为 25,这表示光标占据了 25% 的字符单元格

SetConsoleCursorInfo

设置光标的大小和可见度。需向其传递指向结构 CONSOLE_CURSOR_INFO 的指针:

SetConsoleCursorInfo PROTO,
hConsoleOutput:HANDLE,
lpConsoleCursorInfo:PTR CONSOLE_CURSOR_INFO

SetConsoleCursorPosition

设置光标的 X、Y 位置。向其传递一个 COORD 结构和控制台输岀句柄:

SetConsoleCursorPosition PROTO,
hConsoleOutput:DWORD, ;输入模式句柄
dwCursorPosition:COORD ;屏幕 X、Y 坐标

控制台文本颜色

控制台窗口中的文本颜色有两种控制方法。

  • 通过调用 SetConsoleTextAttribute 来改变当前文本颜色,这种方法会影响控制台中所有后续输出文本。
  • 调用 WriteConsoleOutputAttribute 来设置指定单元格的属性。函数 GetConsoleScreenBufferlnfo 返回当前屏幕的颜色以及其他控制台信息。

SetConsoleTextAttribute

函数 SetConsoleTextAttribute 可以设置控制台窗口所有后续输出文本的前景色和背景色。原型如下:

SetConsoleTextAttribute PROTO,
hConsoleOutput:HANDLE, ;控制台输出句柄
wAttributes : WORD ;颜色属性

颜色值保存在 wAttributes 参数的低字节中。

WriteConsoleOutputAttribute

函数 WriteConsoleOutputAttribute 从指定位置开始,向控制台屏幕缓冲区的连续单元格复制一组属性值。原型如下:

WriteConsoleOutputAttribute PROTO,
hConsoleOutput:DWORD, ;输出句柄
lpAttribute:PTR WORD, ;写属性
nLength:DWORD, ;单元格数
dwWriteCoord :COORD, ;第一个单元格坐标
lpNumberOfAttrsWritten:PTR DWORD ;输出计数

其中:

  • lpAttribute 指向属性数组,其中每个字节的低字节都包含了颜色值;
  • nLength 为数组长度;
  • dwWriteCoord 为接收属性的开始屏幕单元格;
  • lpNumberOfAttrsWritten 指向一个变量,其中保存的是已写单元格的数量。

例子

这个例子有误,后续改进

include   Dqx.inc

.data
outHandle HANDLE ?
cellsWritten DWORD ?
xyPos COORD <10,2>
; 字符编号数组
buffer BYTE 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
BYTE 16,17,18,19.20
BufSize DWORD ($ - buffer)
; 属性数组
attributes WORD 0Fh,0Eh,0Dh,0Ch,0Bh,0Ah,9,8,7,6
WORD 5,4,3,2,1,0F0h,0E0h,0D0h,0C0h,0B0h

.code
start:

; 获取控制台标准输出句柄
INVOKE GetStdHandle,STD_OUTPUT_HANDLE
mov outHandle,eax
; 设置相邻单元格颜色
INVOKE WriteConsoleOutputAttribute,
outHandle, ADDR attributes,
BufSize, xyPos,
ADDR cellsWritten
; 写 1 到 20 号字符
INVOKE WriteConsoleOutputCharacter,
outHandle, ADDR buffer, BufSize,
xyPos, ADDR cellsWritten


invoke ExitProcess,NULL
end start
;-----------------------------------------------------

Win32时间与日期函数

函数一览表

函数

说明

CompareFileTime

比较两个 64 位的文件时间

DosDateTimeToFileTime

把 MS-DOS 日期和时间值转换为一个 64 位的文件时间

FileTimeToDosDateTime

把 64 位文件时间转换为 MS-DOS 日期和时间值

FileTimeToLocalFileTime

把 UTC(通用协调时间)文件时间转换为本地文件时间

FileTimeToSystemTime

把 64 位文件时间转换为系统时间格式

GetFileTime

检索文件创建、最后访问和最后修改的日期与时间

GetLocalTime

检索当前本地日期和时间

GetSystemTime

以 UTC 格式检索当前系统日期和时间

GetSystemTimeAdjustment

决定系统是否对其日历钟进行周期性时间调整

GetSystemTimeAsFileTime

以 UTC 格式检索当前系统日期和时间

GetTickCount

检索自系统启动后经过的毫秒数

GetTimeZoneInformation

检索当前时区参数

LocalFileTimeToFileTime

把本地文件时间转换为基于 UTC 的文件时间

SetFileTime

设置文件创建、最后访问和最后修改的日期与时间

SetLocalTime

设置当前本地时间与日期

SetSystemTime

设置当前系统时间与日期

SetSystemTimeAdjustment

启用或禁用对系统日历钟进行周期性时间调整

SetTimeZoneInformation

设置当前时区参数

SystemTimeToFileTime

把系统时间转换为文件时间

SystemTimeToTzSpecificLocalTime

把 UTC 时间转换为指定时区对应的本地时间

SYSTEMTIME 结构

SYSTEMTIME 结构由 Windows API 的日期和时间函数使用:

SYSTEMTIME STRUCT
wYear WORD ? ;年(4 个数子)
wMonth WORD ? ;月(1 ~ 12)
wDayOfWeek WORD ? ;星期(0 ~ 6)
wDay WORD ? ;日(1 ~ 31)
wHour WORD ? ;小时(0 ~ 23)
wMinute WORD ? ;分钟(0 ~ 59)
wSecond WORD ? ;秒(0 ~ 59)
wMilliseconds WORD ? ;毫秒(0 ~ 999)
SYSTEMTIME ENDS

字段 wDayOfWeek 的值依序为星期天 = 0,星期一 = 1,以此类推。wMilliseconds 中的值不确定,因为系统可以与时钟源同步周期性地刷新时间。

FILETIME

FILETIME 结构把 64 位四字分割为两个双字:

FILETIME STRUCT
loDateTime DWORD ?
hiDateTime DWORD ?
FILETIME ENDS

GetLocalTime 和 SetLocalTime

函数 GetLocalTime 根据系统时钟返回日期和当前时间。

时间要调整为本地时区。

调用该函数时,需向其传递一个指针指向 SYSTEMTIME 结构:

GetLocalTime PROTO,
lpSystemTime:PTR SYSTEMTIME

函数 GetLocalTime 调用示例如下:

.data
sysTime SYSTEMTIME <>
.code
INVOKE GetLocalTime, ADDR sysTime

函数 SetLocalTime 设置系统的本地日期和时间。

调用时,需向其传递一个指针指向包含了期望日期和时间的 SYSTEMTIME 结构:

SetLocalTime PROTO,
lpSystemTime:PTR SYSTEMTIME

如果函数执行成功,则返回非零整数;如果失败,则返回零

GetTickCount

返回从系统启动起经过的毫秒数:

GetTickCount PROTO               ; EAX 为返回值

由于返回值为一个双字,因此当系统连续运行 49.7 天后,时间将会回绕归零

。可以使用这个函数监视循环经过的时间,并在达到时间限制时终止循环。

Sleep

有些时候程序需要暂停或延迟一小段时间。虽然可以通过构造一个计算循环或忙循环来保持处理器工作,但是不同的处理器会使得执行时间不同

Win32 函数 Sleep 按照指定毫秒数暂停当前执行的线程

Sleep PROTO,
dwMilliseconds:DWORD

GetDateTime

GetDateTime 以 100 纳秒为间隔,返回从 1601 年 1 月 1 日起经过的时间间隔数。

如果想要为日期计算准备系统日期/时间值,Win32 SDK 建议采用如下步骤:

  • 调用函数,如 GetLocalTime,填充 SYSTEMTIME 结构。
  • 调用函数 SystemTimeToFileTime,将 SYSTEMTIME 结构转换为 FILETIME 结构。
  • 将得到的 FILETIME 结构复制到 64 位的四字。

图形化的Windows应用程序

下表列出了编写该程序时需要的各种链接库和头文件。

文件名

说明

WinApp.asm

程序源代码

GraphWin.asm

头文件,包含程序要使用的结构、常量和函数原型

kernel32.lib

本章前面使用的 MS-Windows API 链接库

user32.lib

其他 MS-Windows API 函数

必要的结构

结构 POINT 以像素为单位,定义屏幕上一个点的 X 坐标和 Y 坐标。它可以用于定位图形对象、窗口和鼠标点击:

POINT STRUCT
ptX DWORD ?
ptY DWORD ?
POINT ENDS

结构 RECT 定义矩形边界。成员 left 为矩形左边的 X 坐标,成员 top为矩形上边的 Y 坐标。成员 right 和 bottom 保存矩形类似的值

RECT STRUCT
left DWORD ?
top DWORD ?
right DWORD ?
bottom DWORD ?
RECT ENDS

结构 MSGStruct 定义 MS-Windows 需要的数据:

MSGStruct STRUCT
msgWnd DWORD ?
msgMessage DWORD ?
msgWparam DWORD ?
msgLparam DWORD ?
msgTime DWORD ?
msgPt POINT <>
MSGStruct ENDS

结构 WNDCLASS 定义窗口类。

程序中的每个窗口都必须属于一个类,并且每个程序都必须为其主窗口定义一个窗口类。

在主窗口可以显示之前,这个类必须要注册到操作系统:

WNDCLASS STRUC
style DWORD ? ;窗口样式选项
lpfnWndProc DWORD ? ; winProc 函数指针
cbClsExtra DWORD ? ;共享内存
cbWndExtra DWORD ? ;附加字节数
hlnstance DWORD ? ;当前程序句柄
hlcon DWORD ? ;图标句柄
hCursor DWORD ? ;光标句柄
hbrBackground DWORD ? ;背景刷句柄
IpszMenuName DWORD ? ;菜单名指针
IpszClassName DWORD ? ; WinCZLass 名指针
WNDCLASS ENDS

下面对WNDCLASS上述参数进行简单小结:

  • style 是不同样式选项的集合,比如 WS_CAPTION 和 WS_BORDER,用于控制窗口外观和行为。
  • lpfnWndProc 是指向函数的指针,该函数接收并处理由用户触发的事件消息。
  • cbClsExtra 指向一个类中所有窗口使用的共享内存。可以为空。
  • cbWndExtra 指定分配给后面窗口实例的附加字节数。
  • hInstance 为当前程序实例的句柄。
  • hIcon 和 hCursor 分别为当前程序中图标资源和光标资源的句柄。
  • hbrBackground 为背景(颜色)刷的句柄。
  • lpszMenuName 指向一个菜单名。
  • lpszClassName 指向一个空字节结束的字符串,该字符串中包含了窗口的类名称。

WinMain过程

每个 Windows 应用程序都需要一个启动过程,通常将其命名为 WinMain,该过程负责下述任务:

  • 得到当前程序的句柄。
  • 加载程序的图标和光标。
  • 注册程序的主窗口类,并标识处理该窗口事件消息的过程。
  • 创建主窗口。
  • 显示并更新主窗口。
  • 开始接收和发送消息的循环,直到用户关闭应用程序窗口

WinMain 包含一个名为 GetMessage 的消息处理循环,

从程序的消息队列中取出下一条可用消息。

如果 GetMessage 取出的消息是 WM_QUIT,则返回零,即通知 WinMain 暂停程序。

对于其他消息,WinMain 将它们传递给 DispatchMessage 函数,该函数再将消息传递给程序的 WinProc 过程。若

WinProc过程

WinProc 程接收并处理所有与窗口有关的事件消息。这些事件绝大多数是由用户通过点击和拖动鼠标、按下键盘按键等操作发起的。这个过程的工作就是解码每个消息,如果消息得以识别,则在应用程序中执行与该消息相关的任务

过程声明如下:

WinProc PROC,
hWnd: DWORD, ;窗口句柄
localMsg: DWORD, ;消息 ID
wParam:DWORD, ;参数 1 (可变)
lParam: DWORD ;参数 2 (可变)

根据具体的消息 ID,

第三个和第四个参数的内容可变。

比如,若点击鼠标,那么 lParam 就为点击位置的 X 坐标和 Y 坐标。

在后面的示例程序中,WinProc 过程处理了三种特定的消息:

  • WM_LBUTTONDOWN,用户按下鼠标左键时产生该消息
  • WM_CREATE,表示刚刚创建主窗口
  • WM_CLOSE,表示将要关闭应用程序主窗口

ErrorHandler

过程 ErrorHandler 是可选的,如果在注册和创建程序主窗口的过程中系统报错,则调用该过程。

比如,如果成功注册程序主窗口,则函数 RegisterClass 返回非零值。

但是,如果该函数返回值为零,那么就调用 ErrorHandler( 显示一条消息 ) 并退出程序

例子

INVOKE RegisterClass, ADDR MainWin
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF

过程 ErrorHandler 需要执行几个重要任务:

  • 调用 GetLastError 取得系统错误号。
  • 调用 FormatMessage 取得合适的系统格式化的错误消息字符串。
  • 调用 MessageBox 显示包含错误消息字符串的弹出消息框。
  • 调用 LocalFree 释放错误消息字符串使用的内存空间。

又一个无法运行的例子

; Windows Application                   (WinApp.asm)

; This program displays a resizable application window and
; several popup message boxes.
; Thanks to Tom Joyce for creating a prototype
; from which this program was derived.

INCLUDE Dqx.inc
;==================== DATA =======================
.data

AppLoadMsgTitle BYTE "Application Loaded",0
AppLoadMsgText BYTE "This window displays when the WM_CREATE "
BYTE "message is received",0

PopupTitle BYTE "Popup Window",0
PopupText BYTE "This window was activated by a "
BYTE "WM_LBUTTONDOWN message",0

GreetTitle BYTE "Main Window Active",0
GreetText BYTE "This window is shown immediately after "
BYTE "CreateWindow and UpdateWindow are called.",0

CloseMsg BYTE "WM_CLOSE message received",0

ErrorTitle BYTE "Error",0
WindowName BYTE "ASM Windows App",0
className BYTE "ASMWin",0

; Define the Application's Window class structure.
MainWin WNDCLASS <NULL,WinProc,NULL,NULL,NULL,NULL,NULL, \
COLOR_WINDOW,NULL,className>

msg MSGStruct <>
winRect RECT <>
hMainWnd DWORD ?
hInstance DWORD ?

;=================== CODE =========================
.code
WinMain PROC
; Get a handle to the current process.
INVOKE GetModuleHandle, NULL
mov hInstance, eax
mov MainWin.hInstance, eax

; Load the program's icon and cursor.
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov MainWin.hIcon, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov MainWin.hCursor, eax

; Register the window class.
INVOKE RegisterClass, ADDR MainWin
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF

; Create the application's main window.
; Returns a handle to the main window in EAX.
INVOKE CreateWindowEx, 0, ADDR className,
ADDR WindowName,MAIN_WINDOW_STYLE,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,NULL,NULL,hInstance,NULL
mov hMainWnd,eax

; If CreateWindowEx failed, display a message & exit.
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF

; Show and draw the window.
INVOKE ShowWindow, hMainWnd, SW_SHOW
INVOKE UpdateWindow, hMainWnd

; Display a greeting message.
INVOKE MessageBox, hMainWnd, ADDR GreetText,
ADDR GreetTitle, MB_OK

; Begin the program's message-handling loop.
Message_Loop:
; Get next message from the queue.
INVOKE GetMessage, ADDR msg, NULL,NULL,NULL

; Quit if no more messages.
.IF eax == 0
jmp Exit_Program
.ENDIF

; Relay the message to the program's WinProc.
INVOKE DispatchMessage, ADDR msg
jmp Message_Loop

Exit_Program:
INVOKE ExitProcess,0
WinMain ENDP

;-----------------------------------------------------
WinProc PROC,
hWnd:DWORD, localMsg:DWORD, wParam:DWORD, lParam:DWORD
; The application's message handler, which handles
; application-specific messages. All other messages
; are forwarded to the default Windows message
; handler.
;-----------------------------------------------------
mov eax, localMsg

.IF eax == WM_LBUTTONDOWN ; mouse button?
INVOKE MessageBox, hWnd, ADDR PopupText,
ADDR PopupTitle, MB_OK
jmp WinProcExit
.ELSEIF eax == WM_CREATE ; create window?
INVOKE MessageBox, hWnd, ADDR AppLoadMsgText,
ADDR AppLoadMsgTitle, MB_OK
jmp WinProcExit
.ELSEIF eax == WM_CLOSE ; close window?
INVOKE MessageBox, hWnd, ADDR CloseMsg,
ADDR WindowName, MB_OK
INVOKE PostQuitMessage,0
jmp WinProcExit
.ELSE ; other message?
INVOKE DefWindowProc, hWnd, localMsg, wParam, lParam
jmp WinProcExit
.ENDIF

WinProcExit:
ret
WinProc ENDP

;---------------------------------------------------
ErrorHandler PROC
; Display the appropriate system error message.
;---------------------------------------------------
.data
pErrorMsg DWORD ? ; ptr to error message
messageID DWORD ?
.code
INVOKE GetLastError ; Returns message ID in EAX
mov messageID,eax

; Get the corresponding message string.
INVOKE FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM,NULL,messageID,NULL,
ADDR pErrorMsg,NULL,NULL

; Display the error message.
INVOKE MessageBox,NULL, pErrorMsg, ADDR ErrorTitle,
MB_ICONERROR+MB_OK

; Free the error message string.
INVOKE LocalFree, pErrorMsg
ret
ErrorHandler ENDP

END WinMain

窗口

一些变量前缀

WS :Windows Style

WM:windows message

函数(供查看)

GetModuleHandle 获取句柄

invoke  GetModuleHandle ,lpMouleName(含有模块的字符串指针)

他的返回值放在了eax中(之前说过)

但是我们通常会用到这么一个返回值,于是我们要建立一个变量去保存它

比如

.data
str db "user32.dll"
hInstance dd ?
.code
.....
invoke GetModuleHandle ,addr str
mov hInstance,eax

如果,lpMouleName=NULL

表示调用自己


RegisterClassEx 窗口注册

用于窗口的注册

invoke  RegisterClassEx, 窗口结构体类型的指针

在这之前,那个窗口结构体要完成初始化

好比这个样子

mov @stWndClass.hCursor,    eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize, sizeof WNDCLASSEX
mov @stWndClass.style, CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc, offset _ProcWinMain
mov @stWndClass.hbr
Background, COLOR_WINDOW + 1
mov @stWndClass.lpszClassName, offset szClassName
invoke RegisterClassEx, addr @stWndClass

关于窗口结构体的与成员介绍

wndclassex    struct    


hIcon; dd ? ;窗口图标
hIconSm; dd ? ;小图标
hCursor; dd ? ;窗口光标
lpszMenuName; dd ? ;窗口菜单
hInstance; dd ? ;实例句柄
cbSize dd ? ;结构体字节数,用sizeof计算
style; dd ? ;类风格
hbrBackground; dd ? ;背景色
lpszClassName; dd ? ;Class_Name的字符串地址
cbClsExtra; dd ? ;
cbWndExtra; dd ? ;
lpfnWndProc; dd ? ;窗口过程的地址

wndclassex ends

hIcon; dd ? ;窗口图标

显示在窗口左上角的图标,

LoadIcon函数获取资源文件的图标,如果想自定义图标就去资源文件中定义


hCursor; dd ? ;窗口光标

鼠标在窗口的样子

LoadCursor函数指定鼠标在窗口光标的形状,,如果想自定义光标就去资源文件中定义


lpszMenuName; dd ? ;窗口菜单

详情见<<琢石成器>Page.101


hInstance; dd ? ;实例句柄

指定注册的窗口属于哪个模块

在书上的代码中是这样初始化的

    push  hInstance     ;hinstance是已经被初始化的一个句柄
pop @stWndClass.hInstance ;这这是一个引用的


cbSize dd ? ;结构体字节数,用sizeof计算

详情见详情见<<琢石成器>Page.101


style; dd ? ;

窗口的风格,常见的参数,CS_HREADRAW与CS_VREDRAW

小写cs_hredraw,cs_vredraw

还有个CS_DBLCLKS,小写cs_dblclks,指定了它,windows才会把快速双击的鼠标的行为翻译为WM_LNUTTONDBLCLK发送给窗口过程

小写wm_lbuttondblclk,


hbrBackground; dd ? ;背景色

br是brush的意思,h句柄

windows与预定义了一些刷子,用GetStockObject 函数

invoke GetStockObject,WHITE_BRUSH

windows

预定义了一些颜色,如COLOR_MENU,使用类似的颜色要​​+1​

mov @stWndClass.hbrBackground,  COLOR_WINDOW + 1


lpszClassName; dd ? ;Class_Name的字符串地址


cbClsExtra/cbWndExtra

cbClsExtra;   dd  ? ;
cbWndExtra; dd ?

分别代表了windows内部保存的窗口结构体和类结构体给我们预留的内存大小

用于存放自定义的数据


lpfnWndProc; dd ? ;窗口过程的地址

信息的交互需要使用它

它好像是窗口的窗口的地址,就是procMain

有了它windows就知道DispatchMessage函数把窗口的信息发送到哪里去

CreateWindowEx 建立窗口

invoke  CreateWindowEx,\
dwExStyle,\
lpClassName,\
lpwindowName,\
dwStyle,\
x,y,\
nWidth,nHeight,\
hWndParent,\
hMenu,\
hInstance,\
lpParam

例子

invoke  CreateWindowEx,
WS_EX_CLIENTEDGE,
offset szClassName,
offset szCaptionMain,\
WS_OVERLAPPEDWINDOW,\
100,100,600,400,\
NULL,NULL,hInstance,NULL

lpClassName 之前注册的窗口类

初始化后目标是使用lpClassName类建立窗口

lpwindowName 窗口标题栏字符串地址

是一个字符串的指针

hMenu 菜单句柄

如果参数是NULL,就去使用之前在注册窗口时的menu句柄

如果参数是一个新的menu,就去使用新的句柄

如果注册窗口类是没有muen,这里也没有menu句柄,就啥也不显示

lpParam

见<<琢石成器>>.103

hinstance

指定窗口所属的程序模块

hWndParent 窗口所属父窗口

主要用于在父窗口销毁时一同将子窗口销毁

x,y

指定窗口左上角的位置

默认可指定CW_USEDEFAULT

nWidth,nHeight

窗口的宽度高度

默认可指定CW_USEDEFAULT

dwStyle/dwExStyle

详情见见<<琢石成器>>.104

Showwindows 显示窗口

主要用于窗口的显示状态(显示/隐藏)

大小控制

是否激活(当前窗口还是背后窗口)

invoke  ShowWindow,hWinMain,SW_SHOWNORMAL

第一个参数是窗口的句柄..啊?什么时候定义的句柄?

在Crreatwindows的时候

invoke CreateWindowEx,WS_EX_OVERLAPPEDWINDOW,offset szClassName,offset szCaptionMain,\ WS_OVERLAPPEDWINDOW,\ 100,100,600,400,\ NULL,NULL,hInstance,NULL mov hWinMain,eax

这里的eax是一个返回的窗口句柄

第二个参数是显示方式的预定义值

详情见见<<琢石成器>>.106

UpdateWindow 绘制客户区

本质是向窗口发送WM_PAINT消息

invoke  UpdateWindow,hWinMain

参数是窗口的句柄

CreatewindowEx 建立子窗口

Windows有很多预定义的子窗口

好比Button/Edit,要建立一个子窗口就把lpClassName指向"Button"或者啥的

.data
szButton db "button",0
szButtonText db "&OK",0
...
invoke CreateWindowEx,NULL,\
offset szButton,offset szButtonText,\
WS_CHILD or WS_VISIBLE,\
10,10,65,22,\
hWnd,1,hInstance,NULL
;这里的1是ID

消息循环

代码示意

.while  TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw

消息循环用到的结构体

MSG struct
hwnd dd ?;消息要发送到哪里去,
message dd ?;消息标识符,在头文件WM开头的有预定义值
wparam dd ?
lParam dd ?
time dd ?
pt POINT ?;一个POINT数据结构,表示消息放入消息队列的鼠标坐标
MSG ends

hwnd:消息要发送到哪里去,

message:消息标识符,在头文件WM开头的有预定义值

wParam:消息参数之一

lParam:消息参数之二

time:消息放入消息队列的时间

pt:一个POINT数据结构,表示消息放入消息队列的鼠标坐标

GetMessage

invoke GetMessage,lpMsg,hWnd,wMsgFilterMin,wMsgFilterMax

lpMsg: 指向一个Msg结构,函数会在这里返回取到的消息

hWnd:指定获取哪个窗口的信息,如果是NULL,表示获取所拥有本程序所属窗口的信息(不太理解)

wMsgFilterMin,wMsgFilterMax:表示获取所拥有编号的信息

GetMessage函数会从消息队列里获取信息,填写好MSG结构并返回,如果获取信息是​​MSG_QUIT​

那么eax返回值是0,否者返回非零,

于是循环推出的条件就是​​.if !eax .break​

TranslateMessage

他将MSG的结构体信息给windows,然后进行一些键盘信息的转换,


在windows小,你敲击键盘会有扫描码与断码,产生的消息队列是WM_KEYDOWN/WM_KEYUP

或者WM_SYSKEYDOWN/WM_YSYSKEYUP

windows对这个过程的处理很麻烦


TranslateMessage直接把你的扫描码转化为ASCII,并在消息队列中插入WM_CHAR或者WM_SYSCHAR

对于一些非键盘的输入,函数不做处理

DispatchMessage

在上面2个函数下,DispatchMessage函数将消息发送到消息对应的窗口过程去处理,处理过程返回,DispatchMessage才返回

然后开始一轮新的循环

其它形式的消息循环

例题的代码是

.while  TRUE
invoke GetMessage, addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage, addr @stMsg
invoke DispatchMessage, addr @stMsg
;获取
;翻译
;发送
.endw

这让CPU一直在干3件事情,而这3件事情根本没有变化,这样浪费了CPU

我很疑惑,什么叫有消息???????,什么叫消息队列有消息???

invoke  CreateWindow,
invoke ShowWindow,
invoke UpdateWindows,
.while dwQuitFlag==0
.endw
invoke ExitProcsee,0

对于这种方式我不是特别的理解

他是如何GetMessage的

.while TRUE
invoke PeekMessage,addr @stMsg,NULL,0,0,PM_REMOVE
.if eax
.break .if @stMsg.message==WM_QUIT
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.else
invoke MessageBox,NULL,offset szText1,offset szCaption1,MB_OK
.endif
.endw

对于下面的代码我没有调试明白

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 3rd Edition>
; by 罗云彬, http://www.win32asm.com.cn
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; FirstWindow.asm
; 窗口程序的模板代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff FirstWindow.asm
; Link /subsystem:windows FirstWindow.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include gdi32.inc
includelib gdi32.lib
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?

.const
szClassName db 'MyClass',0
szCaptionMain db 'My first Window !',0
szText db 'Win32 Assembly, Simple and powerful !',0
szCaption1 db 'A MessageBox !',0
szText1 db 'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;程序的另一半
_ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam
local @stPs:PAINTSTRUCT
local @stRect:RECT
local @hDc

mov eax,uMsg
;********************************************************************
.if eax == WM_PAINT
invoke BeginPaint,hWnd,addr @stPs
mov @hDc,eax

invoke GetClientRect,hWnd,addr @stRect
invoke DrawText,@hDc,addr szText,-1,\
addr @stRect,\
DT_SINGLELINE or DT_CENTER or DT_VCENTER

invoke EndPaint,hWnd,addr @stPs
;********************************************************************
.elseif eax == WM_CLOSE
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
;********************************************************************
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
;********************************************************************
xor eax,eax
ret

_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
;这个窗口的"WNDCLASSEX"结构体类型是已经被定义好的,现在在局部变量定义就好了
;这里还有一个MSG类型,wok!!
local @stWndClass:WNDCLASSEX
local @stMsg:MSG
;API-1
invoke GetModuleHandle,NULL
mov hInstance,eax
;API-2
invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
;********************************************************************
; 注册窗口类
;********************************************************************
;LoadCursor指定鼠标在窗口光标的形状,这里的IDC_AERROW是预定义的
invoke LoadCursor,0,IDC_ARROW
;初始化窗口类Class的成员
mov @stWndClass.hCursor, eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize, sizeof WNDCLASSEX
mov @stWndClass.style, CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc, offset _ProcWinMain
mov @stWndClass.hbrBackground, COLOR_WINDOW + 1
mov @stWndClass.lpszClassName, offset szClassName
invoke RegisterClassEx, addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
;上面的初始化是一些class共性的初始化,而DIY的初始化在这里
invoke CreateWindowEx,WS_EX_OVERLAPPEDWINDOW,offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW,\
100,100,600,400,\
NULL,NULL,hInstance,NULL
mov hWinMain,eax
;eax传回来了窗口的句柄,用来备用
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
;SHowWindos只会显示窗口,不会显示u内容
invoke UpdateWindow,hWinMain
;Update才会先睡文本
;********************************************************************
; 消息循环
;********************************************************************

.while TRUE
invoke PeekMessage,addr @stMsg,NULL,0,0,PM_REMOVE
.if eax
.break .if @stMsg.message==WM_QUIT
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.else
pushad
invoke MessageBox,NULL,offset szText1,offset szCaption1,MB_OK
popad
.endif
.endw

ret

_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
call _WinMain
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start

不理解为什么他会进入那3个函数

      .if eax
.break .if @stMsg.message==WM_QUIT
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg

或者为什么进入

.else
pushad
invoke MessageBox,NULL,offset szText1,offset szCaption1,MB_OK
popad

是什么东西在影响你的消息队列

窗口过程

My_ProcWinMain  proc  hWnd,uMsg,wParam,lParam

hWnd:窗口回调要指明的操作窗口

uMsg:...

My_ProcWinMain  proc  uses ebx edi esi hWnd,uMsg,wParam,lParam

关于那几个参数的入栈

uses ebx edi esi

为什么入栈?因为他们会被偷偷的改变,就像MessageBox修改ecx,eax..那样,他会对后面的判断做一些影响

uMsg参数有一定的范围,其他的不多说..这里会用到SendMesage参数来传递自定义的参数

....详情就在看书

wParam/IParam参数是消息附带的,他们的职能会根据不同的调用而不同,详情见书

处理了不同的消息必须返回规定的值给windows,对于不同的过程会有不同的返回值

好比程序无法完成初始化就会返回-1

WM_CLOSE消息是按下窗口的​​叉叉​​按钮,主要负责释放内存,保存工作,提示用户是否需要保持

....

...上面很杂很多很难理解..主要是缺乏应用,对那些结构体的认识不够到位

...后面回来慢慢看

收到消息的顺序

收到消息不一定是从循环开始,而是在CreateWindow中开始了

显示和刷新窗口的函数ShowWindow和UpdateWindow

也向窗口过程发送信息

窗口间的通信

动态内存分配

动态内存分配 (dynamic memory allocation),又被称为堆分配 (heap allocation),

汇编语言程序有两种方法进行动态分配:

  • 方法一:通过系统调用从操作系统获得内存块。
  • 方法二:实现自己的堆管理器来服务更小的对象提出的请求。

函数一览表

利用下表中的几个 Win32 API .表中所有的函数都会覆盖通用寄存器,因此程序实现重要寄存器的入栈和出栈操作。

函数

描述

GetProcessHeap

用 EAX 返回程序现存堆区域的 32 位整数句柄。如果函数成功,则 EAX 中的返回值为堆句柄。 如果函数失败,则 EAX 中的返回值为 NULL

HeapAlloc

从堆中分配内存块。如果成功,EAX 中的返回值就为内存块的地址。如果失败,则 EAX 中的返 回值为 NULL

HeapCreate

创建新堆,并使其对调用程序可用。如果函数成功,则 EAX 中的返回值为新创建堆的句柄。如果失败,则 EAX 的返回值为 NULL

HeapDestroy

销毁指定堆对象,并使其句柄无效。如果函数成功,则 EAX 中的返回值为非零

HeapFree

释放之前从堆中分配的内存块,该堆由其地址和堆句柄进行标识。如果内存块释放成功,则返回值为非零

HeapReAlloc

对堆中内存块进行再分配和调整大小。如果函数成功,则返回值为指向再分配内存块的指针。如果函数失败,且没有指定 HEAP GENERATE EXCEPTIONS,则返回值为 NULL

HeapSize

返回之前通过调用 HeapAlloc 或 HeapReAlloc 分配的内存块的大小。如果函数成功,则 EAX 包含被分配内存块的字节数。如果函数失败,则返回值为 SIZE_T-1 ( SIZE_T 等于指针能指向的最大字节数 )

GetProcessHeap 返回已用堆区的地址

没有参数

如果使用的是当前程序的默认堆,那么 GetProcessHeap 就足够了。这个函数没有参数,EAX 中的返回值就是堆句柄:

GetProcessHeap PROTO

例子

.data
hHeap HANDLE ?
.code
INVOKE GetProcessHeap
.IF eax == NULL ;不能获取句柄
jmp quit
.ELSE
mov hHeap,eax ;句柄 ok
.ENDIF

HeapCreate 创建堆区

为当前程序创建一个新的私有堆:

HeapCreate PROTO,
flOptions:DWORD, ;堆分配选项
dwInitialSize:DWORD, ;按字节初始化堆大小
dwMaximumSize:DWORD ;最大堆字节数

flOptions 设置为 NULL。

dwInitialSize 设置为初始堆字节数,其值的上限为下一页的边界。

如果 HeapAlloc 的调用超过了初始堆大小,那么堆最大可以扩展到 dwMaximumSize 参数中指定的大小(上限为下一页的边界)。

调用后,EAX 中的返回值为空就表示堆未创建成 功。HeapCreate 的调用示例如下

HEAP_START = 2000000 ; 2 MB
HEAP_MAX = 400000000 ; 400 MB
.data
hHeap HANDLE ? ; 堆句柄
.code
INVOKE HeapCreate, 0, HEAP_START, HEAP_MAX
.IF eax == NULL ; 堆未创建 call WriteWindowsMsg ; 显示错误消息
jmp quit
.ELSE
mov hHeap,eax ; 句柄 OK
.ENDIF

HeapDeatroy 销毁堆区

销毁一个已存在的私有堆(由 HeapCreate 创建)。需向其传递堆句柄

HeapDestroy PROTO,
hHeap:DWORD ;堆句柄

如果堆销毁失败,则 EAX 等于 NULL。下面为示例调用,其中使用了 WriteWindowsMsg 过程

.data
hHeap HANDLE ? ;堆句柄
.code
INVOKE HeapDestroy, hHeap
.IF eax == NULL
call WriteWindowsMsg ;显示错误消息
.ENDIF

HeapAlloc 堆区中分配内存块

从已存在堆中分配一个内存块:

为什么还要分配!!!!!!!!!!!!!!!!!

HeapAlloc PROTO,
hHeap:HANDLE, ;现有堆内存块的句柄
dwFlags :DWORD, ;堆分配控制标志
dwBytes:DWORD ;分配的字节数

需传递下述参数:

  • hHeap:32 位堆句柄,该堆由 GetProcessHeap 或 HeapCreate 初始化。
  • dwFlags:一个双字,包含了一个或多个标志值。可以选择将其设置为 HEAP_ZERO_MEMORY,即设置内存块为全零。
  • dwBytes:一个双字,表示堆分配的字节数。

如果 HeapAlloc 成功,则 EAX 包含指向新存储区的指针;

如果失败,则 EAX 中的返回值为 NULL。

下面的代码用 hHeap 标识一个堆,从该堆中分配了一个 1000 字节的数组,并将数组初始化为全零:

.data
hHeap HANDLE ? ;堆句柄
pArray DWORD ? ;数组指针
.code
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, 1000
.IF eax == NULL
mWrite "HeapAlloc failed"
jmp quit
.ELSE
mov pArray,eax
.ENDI

HeapFree 释放在堆区分配的内存块

函数 HeapFree 释放之前从堆中分配的一个内存块,该堆由其地址和堆句柄标识:

HeapFree PROTO,
hHeap:HANDLE,
dwFlags:DWORD,
lpMem:DWORD

第一个参数是包含该内存块的堆的句柄。

第二个参数通常为零,

第三个参数是指向将被释放内存块的指针。

如果内存块释放成功,则返回值非零。非0是多少?????

如果该块不能被释放,则函数返回零。

例子

INVOKE HeapFree, hHeap, 0, pArray

Error Handling

若在调用 HeapCreate、HeapDestroy 或 GetProcessHeap 时遇到错误,可以通过调用 API 函数 GetLastError 来获得详细信息。

内嵌汇编

__asm 源于vs-2010

提示:在“asm”的前面有两个下划线。

编写内嵌汇编代码时允许:

  • 使用 x86 指令集内的大多数指令。
  • 使用寄存器名作为操作数。
  • 通过名字引用函数参数。
  • 引用在 asm 块之外定义的代码标号和变量。(这点很重要,因为局部函数变量必须在 asm 块的外面定义。)
  • 使用包含在汇编风格或 C 风格基数表示法中的数字常数。比如,0A26h 和 0xA26 是等价的,且都能使用。
  • 在语句中使用 PTR 运算符,比如 inc BYTE PTR[esi]。
  • 使用 EVEN 和 ALIGN 伪指令。

限制

编写内嵌汇编代码时不允许:

  • 使用数据定义伪指令,如 DB(BYTE)和 DW(WORD)。
  • 使用汇编运算符(除了 PTR 之外)。
  • 使用 STRUCT、RECORD, WIDTH 和 MASK。
  • 使用宏伪指令,包括 MACRO、REPT、IRC、IRP 和 ENDM,以及宏运算符(<>、!、&、% 和 .TYPE)。
  • 通过名字引用段。(但是,可以用段寄存器名作为操作数。)

对于printf输出字符串,还是要传入字符串的地址,用addr,不能用数组名.woc

还有就是%d的操作对象是32位的数据

_asm _ 源于linux系统的nasm

每一行有不同的情况结束

基本内联

1️⃣

__ asm __();

2️⃣

汇编里面每一句都用​​""​​括起来

最后一句不用分号

"mov rax,x;"  //形式1
"mov rax,a \n" //形式2
"mov prod,rax" //最后一句

#include <stdio.h>

int x=11,y=12,sum,prod;
int subtract(void);
void multiply(void);

int main(void)
{
printf("The numbers are %d and %d\n",x,y);
__asm__
(
".intel_syntax noprefix;"
"mov rax,x;"
"add rax,y;"
"mov sum,rax"
);

printf("The sum is %d.\n",sum);
printf("The difference is %d.\n",subtract());
multiply();
printf("The product is %d.\n",prod);

}

int subtract(void)
{
__asm__
(
".intel_syntax noprefix;"
"mov rax,x;"
"sub rax,y" // return value in rax
);
}

void multiply(void)
{
__asm__
(
".intel_syntax noprefix;"
"mov rax,x;"
"imul rax,y;"
"mov prod,rax" //no return value, result in prod
);
}

扩展内联

1️⃣.寄存器约束

  :"=a"(esum)
:"d"(x), "c"(y)

a->​​rax.eax.ax.al​​

b->rbx.ebx.bx.bl

c->​​rcx.ecx.cx.cl​​

d->rdx.edx.dx.dl

s->​​rsi.esi.si​​

d->rdi.edi.di

r->任意寄存器

2️⃣.

可选项以​​:​​开头

  :"=a"(eproduct)
:"d"(x), "c"(y)
:"rbx"
//这里的rdx会被认为是被破坏的,将恢复原始值,但它不会引起崩溃

3️⃣.

  :"=a"(esum)
:"d"(x), "c"(y)

表示输出是rax,rax引用变量esum

输入是rdx,rcx,分别引用x,y

// inline2.c

#include <stdio.h>
int a=12; // global variables
int b=13;
int bsum;

int main(void)
{

printf("The global variables are %d and %d\n",a,b);
__asm__(
".intel_syntax noprefix\n"
"mov rax,a \n"
"add rax,b \n"
"mov bsum,rax \n"
:::"rax"
);

printf("The extended inline sum of global variables is %d.\n\n", bsum);

int x=14,y=16, esum, eproduct, edif; // local variables

printf("The local variables are %d and %d\n",x,y);

__asm__(
".intel_syntax noprefix;"
"mov rax,rdx;"
"add rax,rcx;"
:"=a"(esum)
:"d"(x), "c"(y)
);
printf("The extended inline sum is %d.\n", esum);

__asm__(
".intel_syntax noprefix;"
"mov rbx,rdx;"
"imul rbx,rcx;"
"mov rax,rbx;"
:"=a"(eproduct)
:"d"(x), "c"(y)
:"rbx"
);
printf("The extended inline product is %d.\n", eproduct);

__asm__(
".intel_syntax noprefix;"
"mov rax,rdx;"
"sub rax,rcx;"
:"=a"(edif)
:"d"(x), "c"(y)
);
printf("The extended inline asm difference is %d.\n", edif);

}

中断程序

8086

①. 内中断

就是CPU立刻需要一个程序去处理的中途发生的异常,好像类似于​​C++异常​

什么是内中断?就是程序运行后,你不去物理的碰它,它发生的中断

关于这本书讲的中断安装….什么意思??????

也就是说当你打开你的DOS-BOX,然后你运行了你的中断代码.你的程序就修改了系统的一下配置,

但你再运行你的DOS-BOX,前提是你没有关闭,你就可以再次运行,你的系统默认中断就已经被修改了

好比这个安装

assume cs:code,ss:stack

stack segment
db 32 dup('S')
stack ends

code segment
start:

mov ax,stack
mov ss,ax
mov sp,32

push cs
pop ds
mov si,offset int9

mov ax,0
mov es,ax
mov di,204h

mov cx,offset int9_end - offset int9

cld
rep movsb

push es:[9*4+0]
pop es:[200h+0]
push es:[9*4+2]
pop es:[200h+2]

cli
mov word ptr es:[9*4+0],204h
mov word ptr es:[9*4+2],0
sti


mov ax,4c00h
int 21h

int9:
push ax
push bx
push cx
push es

in al,60h
pushf
call dword ptr cs:[200h];调用int 9的中断

cmp al,48h;F1-->3bh
jne int9ret

mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s:
inc byte ptr es:[bx]
add bx,2
loop s

int9ret:
pop es
pop cx
pop bx
pop ax
iret
int9_end: nop


code ends
end start

当执行了这个代码后,你按一下​​↑​​就可以修改屏幕的颜色

关于写中断要注意什么????

你的中断不能影响原来寄存器的的状态…好比通用寄存器,标志寄存器..不是不可以动,,,只是不要动main的程序

简要介绍

好比这样一个位置,中断向量表开始位置0000:0000

0000:4*0x7C

上面这个地址表示的是index=0x7C的中断类型码

在位置0000:4*0x7C下

[0000:4*0x7C+0]=某个中断例程的IP, 比如IP=0x200

[0000:4*0x7C+2]=某个中断例程的CS,比如CS=0000

于是你调用中断

​int 7ch​

那么系统去0000:0000找到7ch

然后按去往IP=[0000:4 * 0x7C+0],CS=[0000:4 * 0x7C+2]的地址处执行中断例程

中断的过程的入栈

调用​​int index​​​就会​​push 所有的寄存器,然后push cs,push ip​

最后的出栈..它会调用​​iret​​​把上面的东西都​​pop 出去​

调用了​​int​​,如果你想退出程序,就在初始化int函数时,写上下面的代码

这个代码什么意思?

mov ax,4c00h
int 21h

调用​​int 21h​​​中断,在调用前,初始化​​指向子函数ah=4c,然后配置al=0​

push 寄存器???

标志寄存器有假设10个?

其中8个他们的状态是11001010,于是我们把它看作一个8位的数据,可以达到​​11001010b​​​=​​0xCA​​​,于是我们就把​​0xCA​​入栈

然后我们再push ​​IF,TF​​​,他们与外中断的屏蔽有关,,调用内中断时,会让​​IF=0​​,屏蔽了可屏蔽的外中断

于是我们才会用入栈6字节的数据,​​4字节是CS:IP,2字节是8个寄存器值和2个寄存器值​

如何DIY一个中断

把它封装为一个函数

call init_do0
//然后去写中断函数,安装中断函数

书写中断函数

调用复制功能的函数

//调用代码复制函数,因为你无法直接向中断向量的地址处写入数据,只能复制过去
初始化DS:[SI]
初始化ES:[di]
初始化CX//涉及到了offset的取地址
//这里是依据把中断函数写好了然后去复制
执行
cld
rep movsb

然后初始化一下中断向量表指向那个地址
mov ax,0
mov es,ax
mov word ptr es:[0*4+0],200h
mov word ptr es:[0*4+2],0

这个代码的意思就是

初始化​​0号​​​中断,让​​0号中断​​执行中断例程的地址0000:0200

代码一

assume cs:code ,  ss:stack

stack segment
db 128 dup('y')
stack ends

code segment
start:
mov ax,stack
mov ss,ax;
mov sp,128


call init_do0;他只会push ip
mov ax,1000h
mov bh,1
div bh

mov ax,4c00h
int 21h
;-----------------------------------------------------------------------------------------------------------------

init_do0:
;先是修改数据的指向,然后是复制中断代码到中断区,中断区域的调用是自动的
;--------------------------------------------
;设置数据来源 S指->CS:[do0]
mov ax,cs
mov ds,ax
mov si,offset do0;这样取地址就非常的妙..不想你之前根本想不到

;设置数据D->0000:[0200]
mov ax,0
mov es,ax
mov di,200h

mov cx,offset do0end - offset do0;循环的次数

cld
rep movsb


;数据指向完成后,初始化一下中断向量表,第[0]个向量表的地址0000:0200
mov ax,0
mov es,ax
mov word ptr es:[0*4+0],200h
mov word ptr es:[0*4+2],0


ret





;--------------------------------------
do0:
jmp short do0start
db "over flow"
do0start:
mov ax,cs;
mov ds,ax
mov si,202h;这里的si可能是在指向字符串的偏移地址,jmp占据2个字节,然后他后面的就是字符串数据

;y要显示的字符串
mov ax,0b800h
mov es,ax

mov cx,2000
mov dx,0700h
mov bx,0
clean_screen:
mov es:[bx],dx
add bx,2
loop clean_screen

mov di,12*160+36*2

mov cx,9;字符串的长度
S:
mov al,ds:[si]
mov ah,2
mov es:[di],ax
inc si
add di,2
loop S

iret;他会pop ip,所以他就是类似于一个ret



do0end:
nop



code ends
end start

怎么调用一个中断
  1. 程序崩溃时调用
  2. 人为的直接int index 调用

我自己写的

assume cs:code ,  ss:stack

stack segment
db 32 dup('0')
stack ends

code segment
start:
;栈的初始化,容易忘记出初始化sp
mov ax,stack
mov ss,ax
mov sp,32

;初始化内中断实例
call init_do7ch

;向显存写东西
mov ax,0b800h
mov es,ax
mov di,160*20+30

;初始化一些吸入什么东西
mov ah,2
mov al,'!'
;打印的个数
mov cx,4
;安全的让bp=0
mov bp,0
;待会ip要回走,所以需要一个地址的间隔
mov bx,offset s0end -offset s0
s0:
;开始写入显存
mov es:[di],ax
;遍历
add di,2
;调用
int 7ch
s0end:
;标志一下位置
nop

mov ax,4c00h
int 21h

init_do7ch:
;source
mov ax,cs
mov ds,ax
mov si,offset do7ch
;destaion
mov ax,0
mov es,ax
mov di,200h
;复制多少字节
mov cx,offset do7chend - offset do7ch

cld
rep movsb
;初始化向量表
mov word ptr es:[7ch*4+0],200h
mov word ptr es:[7ch*4+2],0

ret

do7ch:
;判断是否结束
dec cx
jcxz lpret
;保存一下bp,因为bp=sp,在发生变化
push bp
mov bp,sp
sub ss:[bp+2],bx;让栈了里里面的ip重新指向开始的地方
pop bp

lpret:
iret
do7chend:nop


code ends
end start

int 10h->ah=2 光标中断

int 10h->ah=2置光标

bh->第几页

dh->第几行

dl->第几列

mov ah,2; int 10h的子程序
mov bh,0 ;bh是di某页
mov dh,5 ;行号
mov dl,12 ;列号
int 10h ;10h中断例程

80x25的字符模式下

0~24共25行

0~79共80列

0~7共8页

在80x25的模式下,B800~BFFF有32kb默认显示第0页

int 10h->ah=9 显示一个字符

int 10h->ah=9,在光标处显示字符

al->显示的字符

bl->颜色

bh->第几页

cx->重复显示的个数,往后挪

mov ah,9;调用子函数
mov al,'?';显示字符'?'
mov bl,7;颜色的属性
mov bh,0;第0页
mov cx,3;字符重复的个数
int 10h;调用中断

int 21h->ah=9 输出以‘$’结尾的字符串

ds->数据来源的短地址

dx->数据来源的偏移地址

ah->9功能编号,输出字符串,遇到​​$​​结束

int 21h

mov ax,data
mov ds,ax
mov dx,0
mov ah,9
in 21h

②. 外中断

简述

外中断->1.可以被屏蔽 2.不可以屏蔽(其中断类型码只能是​​2​​)

之前内中断我们初始化了IF

对于可屏蔽的中断,

​IF=1,表示屏蔽中断,IF=0,表示不屏蔽中断​

指令

​sti,置IF=1 cli 置IF=0 ​

PC机I/O键盘处理过程

键盘主板寄存器的端口是​​60h​

按下一个键,芯片产生一个扫描码,叫​​通码​

松开一个键,芯片也会产生一个扫描码,叫​​断码​

断码=通码+80h

80h==1000 0000b

可见最开头那位是一个开关之类的东西

​int 9​​​只是对你的键入做了一个判断与执行..至于你的键入后怎么操作..这就得看你怎么对​​int 9​​去DIY的配置

我还是不太明白​​int9​​到底有个啥用!!!!!!!!!!!!!!!!


int 9中断 打印ascii的abcef

键盘的输入达到​​60h​​​端口时,芯片就会自动的向CPU发出​​int 9​​​的中码断,如果CPU检测到​​IF=1​​​则表示接受外界键入,然后去指向​​int 9​​​中断过程如果​​IF=0​​,表述拒绝接受键入

assume cs:code,ds:data,ss:stack

stack segment
db 32 dup('S')
stack ends

data segment
db 128 dup('D')
data ends

code segment

start:

mov ax,data
mov ds,ax

mov ax,stack
mov ss,ax
mov sp,32

mov ax,0b800h
mov es,ax

call init_9h;专门成立一个函数去初始会化int9,而且更大程度的保障了寄存器不发生更多的变化
;===========================================================

mov cx,0
;从感叹号开始显示
mov cl,'!'
;颜色从2开始
mov ch,2
;开始的地点是10行20列
mov di,160*10+20

show:
;把配置输入到显存
mov es:[di],cx
;加缓延时
call delay;delay函数不能与cmp与jne交叉,以为delay内部会有指令修改寄存器的操作
;指向下一个像素
add di,2
;指向下一个字符
inc cl
;重点是'~'
cmp cl,'~'
jna show

;备份int9的原始向量
mov ax,0
mov es,ax

;原始向量恢复到向量表,,,否者DOS系统运行完毕后键盘将屏蔽你的外中断
push ds:[0]
pop es:[9*4+0]
push ds:[2]
pop es:[9*4+2]


mov ax,4c00h
int 21h
;============================================================
delay:
;它的原理我不太懂
mov ax,0
mov bx,1h
delay_continue:
sub ax,1;它是控制速度的
sbb bx,0
cmp ax,0
jne delay_continue
cmp bx,0
jne delay_continue

ret
;============================================================
init_9h:
push es

mov ax,0
mov es,ax

;备份int 9的中断向量表
push es:[9*4+0]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]

cli
mov word ptr es:[9*4+0],offset start_9h
mov es:[9*4+2],cs
sti

pop es
ret

start_9h:
;你键入的数据在端口60处
in al,60h;al从端口接收了你的键入
;模拟int9的过程,push全部寄存器,然后push cs,IP
pushf
call dword ptr ds:[0];int9自己有iret
cmp al,48h;48h是 ↑
jne start_9h_end;如果不是上就跳过然后结束,如果这里用je会比较麻烦!!!!!!!!!!!!!!!!
inc ch
start_9h_end:
iret
;=========================================================================

code ends
end start

x64

printf_syscall

mov   rax,1
mov rdi,1
mov rsi,str1
mov rdx,len1
syscall

rax的1代表了写入

rdi的1代表了标准输出

rsi代表了字符串输出的地址

rdx代表了输出的长度,最后的0不计数

syscall调用系统内部的和中断

这些寄存器可否用于其他的用途

于是你封装函数的时候,就传递一些动态参数,初始化一些固定的参数

scanf_syscall

rax=0,表示读取

rdi=1,表示标准输入

rsi=输入字符串的地址

rdx=输入字符串的长度

它没有格式化输入与输出

你的rsi是字符串,你就只能输入字符串???

你的rsi指向dd.你就只能输入数值??

ret_syscall

下面的代码等效于main函数的return,好比ret

  mov     rax, 60   ; 60 = exit
mov rdi, 0 ; 0 = success exit code
syscall ; quit

端口

类比一下中断程序

因为书上没有怎么介绍..依次我就没有多多的解释

(一). 读写指令

从​​右->左​​看

(读) in 寄存器/数据,  端口        //端口S的数据读进D寄存器
(写) out 端口, 寄存器/数据 //把S的数据写进

从端口读入到接收者

端口是一个编号,也可以是装有编号的寄存器

写入的数据是8位就只能用AL

如果是16位就用AX

把数据写入端口

端口是一个编号,也可以是装有编号的寄存器

写入的数据是8位就只能用AL

(二). CMOS RAM 芯片端口

简要介绍

该芯片内部有2个端口,

端口地址

可以是70h,代表了地址写入的端口

可以是71h,代表了数据读取/写入的端口

如何读取CMOS RAM的2号单元

①. 把2号写入71h ​​out 70h,2​

②. 71h数据读入​​ in ax,71h​

检测点

assume cs:code,ss:stack


data segment
db 'i really love love you','$'
data ends


stack segment
db 128 dup('y')
stack ends

code segment

start:
mov ax,stack
mov ss,ax
mov sp,128

mov ax,0
mov al,2

out 70h,al
mov ax,0
in al,71h

mov ax,4c00h
int 21h

code ends
end start

CMOS下的时间单元

什么叫BCD码

​4位​​​的二进制就构成一个​​BCD码​

好比10进制的​​26​​​就是由​​0010​​​和​​0110​​构成

​0010​​​代表​​2​

​0110​​​代表​​6​

时间单元













9

8

7

4

2

0

打印月份

assume cs:code,ss:stack


data segment
db 'i really love love you','$'
data ends


stack segment
db 128 dup('y')
stack ends

code segment

start:
mov ax,stack
mov ss,ax
mov sp,128

mov ax,0
mov al,8;把al写入数据端口
out 70h,al;把对于单元的数据读取到al
in al,71h

mov ah,al
mov cl,4
shr ah,cl

and al,00001111b

add al,30h
add ah,30h

push ax

mov ax,0b800h
mov es,ax
mov di,0

mov cx,2000
mov bx,0700h
s:
mov es:[di],bx
add di,2
loop s

pop ax
mov di,160*10+20
mov cl,11001010b

mov es:[di+0],ah
mov es:[di+1],cl
mov es:[di+2],al
mov es:[di+3],cl

mov ax,4c00h
int 21h

code ends
end start

打印年月日时分秒

assume cs:code,ss:stack, ds:data

data segment
db "2000/00/00/00:00:00-TIME-Dqx-Ghost",'$'
db 9,8,7,4,2,0
data ends

stack segment
db 128 dup('y')
stack ends

code segment
start:

mov ax,stack
mov ss,ax
mov sp,128

call clean_screen

mov ax,data
mov ds,ax

k:
call init_time
call init_xy_and_show
jmp k

;==========================================
mov ax,4c00h
int 21h


;======================================================================================
clean_screen:
mov ax,0b800h
mov es,ax
mov di,0

mov cx,2000
mov bx,0700h
clean_screen_start:
mov es:[di],bx
add di,2
loop clean_screen_start
ret
;======================================================================================
init_time:
;数据源已经被初始化了
;循环6次
mov cx,6
;初始化数据的指向
mov si,35
mov bx,2
;循环体部分
init_time_start:
push cx
;初始化端口编号
mov al,ds:[si]
;写入端口
out 70h,al
in al,71h

mov ah,al
mov cl,4
shr ah,cl

and al,00001111b

add al,30h
add ah,30h

mov byte ptr ds:[bx+0],ah
mov byte ptr ds:[bx+1],al

add bx,3
inc si
pop cx
loop init_time_start

ret
;========================================================================


init_xy_and_show:
mov ah,2
mov bh,0

mov dl,20
mov dh,10
int 10h

;数据源已经被初始化了
mov dx,0
mov ah,9
int 21h

ret

code ends
end start

利用BIOS进行键盘的读写

这里涉及到一个​​int 16h​​中断

mov ax,0
int 16h

它的运行结果会是怎么样?

①.寄存器ax中会有一份值

ah:扫描码的通码

al:扫描码的ASCII码

②.键盘缓冲区中也会有一份值,高位通码,低位ASCII码,组成了一个字单元

这样的字单元会有15个

值得注意的是

​int 16h​​会从键盘缓冲区读取数据,,

如果键盘缓冲区有数据就读取一个字节​​(至于读取谁,可以想象一下栈,它会读取最后进来那个)​

如果没有数据,程序会一直等待你的输入..不会退出..直到键盘缓冲区有数据被它吃才会结束

核心的原理

读取键盘的输入,然后保存

如果输入back_space就让buff区那位置为0

如果输入enter,也是,然后那个位置置为0

assume cs:code,ss:stack,ds:data

data segment
Arr db 128 dup('D')
Table_A dw Store_ascii,show_str,Delete_Ascii
which_one dw 0;函数指针的index
index dw 0;输入的ascii有多少个
input dw 0;你的每一个键入的ascii的缓冲区
data ends

stack segment
db 128 dup('S')
stack ends

code segment


start:
mov ax,data
mov ds,ax

mov ax,stack
mov ss,ax
mov sp,128

mov di,20*2;你的输入在第一行的[20][0]处开始

call option

mov ax,4c00h
int 21h


;--------------------------------------------------------------------
option:

option_start:
mov ax,0
int 16h

cmp al,20h;20h是最小的ASCII,之后的是不可见字符
jnb main1
cmp al,08h;退格键
jz back_space
cmp al,0dh
jz enter_A

jmp option_start

option_ret:
ret
;-----------------------------------------------------------------------
main1:
mov which_one,0*2;调用store函数
mov bx,which_one
call word ptr Table_A[bx]

mov which_one,1*2
mov bx,which_one
call word ptr Table_A[bx]

jmp option_start
;'----------------------------------------------------------------------------'
back_space:
mov which_one,2*2
mov bx,which_one
call word ptr Table_A[bx]

mov which_one,1*2
mov bx,which_one
call word ptr Table_A[bx]

jmp option_start
;'----------------------------------------------------------------------------'
enter_A:
mov which_one,2*2
mov bx,which_one
call word ptr Table_A[bx]

mov which_one,1*2
mov bx,which_one
call word ptr Table_A[bx]

jmp option_ret
;'----------------------------------------------------------------------------'



Store_ascii:

mov input,al
mov bx,index
mov byte ptr [bx],al
inc word ptr index
ret

;'----------------------------------------------------------------------------'
Delete_Ascii:
dec word ptr index
mov input,0
mov bx,index
mov word ptr [bx],0

;退格太多,不能退太多,要回到初始位置
cmp di,40
jb back
jnb back_fail
back:
mov di,40
back_fail:
ret
;'----------------------------------------------------------------------------'
show_str:
push ax
push es

mov ax,0b800h
mov es,ax
mov ax,160


cmp input,0
jne ok2
je ok1


ok1:
sub di,2;把原来那个给删除了
mov byte ptr es:[di],0
jmp over
ok2:
mov al,input
mov ah,2
mov es:[di],ax;初始化当前的
add di,2 ;然后指向下一个`

over:
pop es
pop ax
ret
;'----------------------------------------------------------------------------'
code ends
end start

我没看懂它的输出位置的原理,这么久输出在了di+50的地方

你也没有把它的push与pop给看懂,乱七八糟的push与pop

assume cs:code


data segment
db 128 dup('D')
data ends

code segment
start:



mov ax,data
mov ds,ax

call func

mov ax,4c00h
int 21h

func:
push ax
option:
mov ah,0
int 16h

cmp al,20h
jb nochar

mov ah,0
call main1

mov ah,2
call main1

jmp option
nochar:
cmp ah,0eh
je backspace
cmp ah,1ch
je enter_
jmp option
backspace:
mov ah,1
call main1
mov ah,2
call main1
jmp option
enter_:
mov al,0
mov ah,0
call main1
mov ah,2
call main1
pop ax
ret

main1:
jmp short Begin
table_ dw Store_Ascii,Delete_Ascii,show
check dw 0
Begin:
push bx
push dx
push di
push es

cmp ah,2
ja main1_end

mov bl,ah
mov bh,0
add bx,bx;让bx翻倍
jmp word ptr table_[bx];为什么index一定是16位?


Store_Ascii:
mov bx,check
mov ds:[bx],al
inc check
jmp main1_end
Delete_Ascii:
cmp check,0
je main1_end

dec check
mov bx,check
jmp main1_end
show:
mov bx, 0b800h
mov es,bx
mov al,160
mov ah,0
mov di,ax
;下面这3行代码我真看不懂
;add dl,dl
;mov dh,0
;add di,dx
mov bx,0

is_KG_or_not:
cmp bx,check
jne show_ascii
mov byte ptr es:[di-1],2
mov byte ptr es:[di+0],' '
jmp main1_end
show_ascii:
mov al,[bx]
mov es:[di],al
inc bx
add di,2
jmp is_KG_or_not
main1_end:
pop es
pop di
pop dx
pop bx
ret


code ends
end start

利用BIOS进行磁盘的读写

软盘

分为2面

每面软盘80个磁道

每个磁道18个扇区

每个扇区512个字节

使用参数

寄存器

写入->几个面

al



写入->背面还是反面

dh=(0或者1)

写入->哪个磁道

ch

写入->哪个扇区

cl(1开始计数)



写入->哪个磁盘?A/B/C/D

dl

数据S/D的短地址

ES

数据S/D的偏移地址

BX

使用哪个功能

ah

中断

int 13h



逻辑扇区​​x​

​(80xdh+ch)x18+cl-1​

面号​​dh​

​x/1440​

磁道号​​ch​

​(x%1400)/18​

扇区号​​cl​

​x%1400%18+1​

把显存的4000字节依次写入1号,2号…8号扇区

assume cs:code,ss:stack,ds:data

data segment

data ends

stack segment
db 128 dup('S')
stack ends

code segment

start:
mov ax,data
mov ds,ax

mov ax,stack
mov ss,ax
mov sp,128


mov ax,0b800h;段地址
mov es,ax
mov bx,0;偏移地址

mov al,8;写入8个扇区
mov dl,0;在正面写入

mov dh,0;0面
mov ch,0;0道
mov cl,1;1扇区



mov ah,3;读取
int 13h

mov ax,4c00h
int 21h


;'----------------------------------------------------------------------------'
code ends
end start

我很疑惑它写入了多少? 以什么样的格式写入

写入的是db,dw???

ascii?

x64的文件处理

源代码

; file.asm
section .data
;条件汇编表达式
CREATE equ 1 ;创建
OVERWRITE equ 1 ;重写
APPEND equ 1 ;追加
O_WRITE equ 1 ;二进制写入
READ equ 1 ;读取
O_READ equ 1 ;二进制读取
DELETE equ 1 ;删除

;系统调用符号
NR_read equ 0
NR_write equ 1
NR_open equ 2
NR_close equ 3
NR_lseek equ 8
NR_create equ 85
NR_unlink equ 87

;创建状态标志
O_CREAT equ 00000100q ;二进制创建
O_APPEND equ 00002000q ;二进制追加

;访问模式
O_RDONLY equ 000000q
O_WRONLY equ 000001q
O_RDWR equ 000002q

;创建权限
S_IRUSR equ 00400q ;user read permission
S_IWUSR equ 00200q ;user write permission

NL equ 0xa
bufferlen equ 64

fileName db "testfile.txt",0
FD dq 0 ; 文件描述符号

text1 db "1. Hello...to everyone!",NL,0
len1 dq $-text1-1 ;remove 0
text2 db "2. Here I am!",NL,0
len2 dq $-text2-1 ;remove 0
text3 db "3. Alife and kicking!",NL,0
len3 dq $-text3-1 ;remove 0
text4 db "Adios !!!",NL,0
len4 dq $-text4-1

error_Create db "error creating file",NL,0
error_Close db "error closing file",NL,0
error_Write db "error writing to file",NL,0
error_Open db "error opening file",NL,0
error_Append db "error appending to file",NL,0
error_Delete db "error deleting file",NL,0
error_Read db "error reading file",NL,0
error_Print db "error printing string",NL,0
error_Position db "error positioning in file",NL,0

success_Create db "File created and opened",NL,0
success_Close db "File closed",NL,NL,0
success_Write db "Written to file",NL,0
success_Open db "File opened for reading/(over)writing/updating",NL,0
success_Append db "File opened for appending",NL,0
success_Delete db "File deleted",NL,0
success_Read db "Reading file",NL,0
success_Position db "Positioned in file",NL,0

section .bss
buffer resb bufferlen
section .text
global main
main:
push rbp
mov rbp,rsp

%IF CREATE
;创建打开,然后关闭
mov rdi, fileName
call createFile
mov qword [FD], rax ; save descriptor

; write to file #1
mov rdi, qword [FD]
mov rsi, text1
mov rdx, qword [len1]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

%IF OVERWRITE
;OPEN AND OVERWRITE A FILE, THEN CLOSE ---------------------------------------
; open file
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; write to file #2 OVERWRITE!
mov rdi, qword [FD]
mov rsi, text2
mov rdx, qword [len2]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

%IF APPEND
;OPEN AND APPEND TO A FILE, THEN CLOSE ---------------------------------------
; open file to append
mov rdi, fileName
call appendFile
mov qword [FD], rax ; save file descriptor

; write to file #3 APPEND!
mov rdi, qword [FD]
mov rsi, text3
mov rdx, qword [len3]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

%IF O_WRITE
;OPEN AND OVERWRITE AT AN OFFSET IN A FILE, THEN CLOSE -----------------------
; open file to write
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; position file at offset
mov rdi, qword[FD]
mov rsi, qword[len2] ;offset at this location
mov rdx, 0
call positionFile

; write to file at offset
mov rdi, qword[FD]
mov rsi, text4
mov rdx, qword [len4]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF


%IF READ
;OPEN AND READ FROM A FILE, THEN CLOSE ---------------------------------------
; open file to read
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; read from file
mov rdi, qword [FD]
mov rsi, buffer
mov rdx, bufferlen
call readFile
mov rdi,rax
call printString

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

%IF O_READ
;OPEN AND READ AT AN OFFSET FROM A FILE, THEN CLOSE ---------------------------------------
; open file to read
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; position file at offset
mov rdi, qword[FD]
mov rsi, qword[len2] ;skip the first line
mov rdx, 0
call positionFile

; read from file
mov rdi, qword [FD]
mov rsi, buffer
mov rdx, 10 ;number of characters to read
call readFile
mov rdi,rax
call printString

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

%IF DELETE
;DELETE A FILE --------------------------------------------------
; delete file UNCOMMENT NEXT LINES TO USE
mov rdi, fileName
call deleteFile
%ENDIF


leave
ret

; FILE MANIPULATION FUNCTIONS-------------------------------------
;-----------------------------------------------------------------
global readFile
readFile:
mov rax, NR_read
syscall ; rax contains # of characters read
cmp rax, 0
jl readerror
mov byte [rsi+rax],0 ; add a terminating zero to the string
mov rax, rsi

mov rdi, success_Read
push rax ; caller saved
call printString
pop rax ; caller saved
ret
readerror:
mov rdi, error_Read
call printString
ret
;-----------------------------------------------------------------
global deleteFile
deleteFile:
mov rax, NR_unlink
syscall
cmp rax, 0
jl deleteerror
mov rdi, success_Delete
call printString
ret
deleteerror:
mov rdi, error_Delete
call printString
ret
;-----------------------------------------------------------------
global appendFile
appendFile:
mov rax, NR_open
mov rsi, O_RDWR|O_APPEND
syscall
cmp rax, 0
jl appenderror
mov rdi, success_Append
push rax ; caller saved
call printString
pop rax ; caller saved
ret
appenderror:
mov rdi, error_Append
call printString
ret
;-----------------------------------------------------------------
global openFile
openFile:
mov rax, NR_open
mov rsi, O_RDWR
syscall
cmp rax, 0
jl openerror
mov rdi, success_Open
push rax ; caller saved
call printString
pop rax ; caller saved
ret
openerror:
mov rdi, error_Open
call printString
ret
;-----------------------------------------------------------------
global writeFile
writeFile:
mov rax, NR_write
syscall
cmp rax, 0
jl writeerror
mov rdi, success_Write
call printString
ret
writeerror:
mov rdi, error_Write
call printString
ret

;-----------------------------------------------------------------
global positionFile
positionFile:
mov rax, NR_lseek
syscall
cmp rax, 0
jl positionerror
mov rdi, success_Position
call printString
ret
positionerror:
mov rdi, error_Position
call printString
ret
;-----------------------------------------------------------------
global closeFile
closeFile:
mov rax, NR_close
syscall
cmp rax, 0
jl closeerror
mov rdi, success_Close
call printString
ret
closeerror:
mov rdi, error_Close
call printString
ret
;-----------------------------------------------------------------
global createFile
createFile:
mov rax, NR_create
mov rsi, S_IRUSR |S_IWUSR
syscall
cmp rax, 0 ; file descriptor in rax
jl createerror
mov rdi, success_Create
push rax ; caller saved
call printString
pop rax ; caller saved
ret
createerror:
mov rdi, error_Create
call printString
ret

; PRINT FEEDBACK
;-----------------------------------------------------------------
global printString
printString:

; Count characters
mov r12, rdi
mov rdx, 0
strLoop:
cmp byte [r12], 0
je strDone
inc rdx ;length in rdx
inc r12
jmp strLoop
strDone:
cmp rdx, 0 ; no string (0 length)
je prtDone
mov rsi,rdi
mov rax, 1
mov rdi, 1
syscall
prtDone:

ret

注意一些寄存器调用约定

好比eax是返回值,那么的话,eax就不要被偷偷的修改掉

一个函数肯定会有返回值,只是你用不用返回值

当eax有用,你就push

当eax无用,你就随意了

函数

输出

global printString
;参数
;rdi 字符串地址
;功能
;实现字节计数,放在rdx,然后syscall输出
;
printString:

; Count characters
mov r12, rdi
mov rdx, 0
strLoop:
cmp byte [r12], 0
je strDone
inc rdx ;length in rdx
inc r12
jmp strLoop
strDone:
cmp rdx, 0 ; no string (0 length)
je prtDone
mov rsi,rdi
mov rax, 1
mov rdi, 1
syscall
prtDone:
ret

创建

  mov   rdi, fileName
call createFile

;-----------------------------------------------------------------
global createFile
;参数 rdi:文件的名字
;功能: 创建一个文件,成功就打印超过,失败就打印失败
;返回值 rax
;注意事项: 一些函数会默默地修改寄存器,我们得事先把它push一下
createFile:
;mov rdi, fileName
mov rsi, S_IRUSR | S_IWUSR ;user read permission | user write permission
mov rax, NR_create ;85
syscall
cmp rax, 0
jl createerror
mov rdi, success_Create
push rax ; caller saved
call printString
pop rax ; caller saved
ret
createerror:
mov rdi, error_Create
call printString
ret

; PRINT FEEDBACK
;----------------------------------------------------------------

mov   qword [FD], rax ; save descriptor

写入

mov   rdi, qword [FD]
mov rsi, text1
mov rdx, qword [len1]
call writeFile

;-----------------------------------------------------------------
;文件名 writeFile
;参数
;rdi 文件句柄
;rsi 字符串地址
;rdx 字符串长度

;功能 把一个字符串写入文件

;返回值 没用用到
;注意事项
global writeFile
writeFile:
;rdi 文件句柄
;rsi 字符串地址
;rdx 字符串长度
mov rax, NR_write ;1
syscall
cmp rax, 0 ;返回rax 写入失败就返回负数
jl writeerror
mov rdi, success_Write
call printString
ret
writeerror:
mov rdi, error_Write
call printString
ret

;-----------------------------------------------------------------

关闭

    mov     rdi, qword [FD]
call closeFile

;函数名 关闭文件
;参数 rdi, qword [FD],文件句柄
;功能 关闭文件,失败就打印1失败,成功就打印成功
;返回值: 未使用
;注意事项:null


global closeFile
closeFile:
mov rax, NR_close ;3
syscall
cmp rax, 0
jl closeerror
mov rdi, success_Close
call printString
ret
closeerror:
mov rdi, error_Close
call printString
ret

打开

  mov   rdi, fileName 
call openFile

;函数名 打开文件
;参数 rdi, fileName
;功能 打开文件
;返回值 rax 文件句柄
;注意事项
global openFile
openFile:
;rdi, fileName
mov rsi, O_RDWR ;访问模式
mov rax, NR_open ;系统调用符号
syscall
cmp rax, 0
jl openerror
mov rdi, success_Open
push rax ; caller saved
call printString
pop rax ; caller saved
ret
openerror:
mov rdi, error_Open
call printString
ret

mov   qword [FD], rax ; save file descriptor

追加形式打开

mov     rdi, fileName 
call appendFile

;;函数名 文件追加
;参数 文件名rdi, fileName
;功能
;返回值 文件句柄
;注意事项 文件指针的还原
global appendFile
appendFile:
;rdi, fileName
mov rsi, O_RDWR|O_APPEND ;访问模式|追加状态标志
mov rax, NR_open ;系统调用符号
syscall
cmp rax, 0
jl appenderror
mov rdi, success_Append
push rax ; caller saved
call printString
pop rax ; caller saved
ret
appenderror:
mov rdi, error_Append
call printString
ret

mov   qword [FD], rax ; save file descriptor

定位偏移量

; position file at offset
mov rdi, qword[FD]
mov rsi, qword[len2] ;offset at this location
mov rdx, 0
call positionFile

它是怎么设置偏移量的???

为什么最后关闭文件的时候还是用了改变后的指针.?/?/?/?

;函数名 定位文件偏移量
;参数

;rdi, qword[FD] 文件句柄
;rsi, qword[len2] ;偏移量
;rdx, 0

;功能 改变句柄?
;注意事项 指针的还原
;返回值

global positionFile
positionFile:
;rdi 文件句柄
;rsi 偏移量
;rdx 0
mov rax, NR_lseek
syscall
cmp rax, 0
jl positionerror
mov rdi, success_Position
call printString
ret
positionerror:
mov rdi, error_Position
call printString
ret

读取

mov   rdi, qword [FD]
mov rsi, buffer
mov rdx, bufferlen
call readFile

;函数名 读取文件
;参数
;rdi, qword [FD] 文件指针
;rsi, buffer 字符串缓冲区
;rdx, bufferlen 缓冲区长度;syscall读取长度
;功能
;返回值 字符串地址

global readFile
readFile:
;rdi, qword [FD]
;rsi, buffer
;rdx, bufferlen
mov rax, NR_read
syscall ; rax contains # of characters read
cmp rax, 0
;返回值是一个文件字符串的长度
jl readerror
mov byte [rsi+rax],0 ; add a terminating zero to the string
mov rax, rsi

mov rdi, success_Read
push rax ; caller saved
call printString
pop rax ; caller saved
ret
readerror:
mov rdi, error_Read
call printString
ret

返回字符串地址

  mov   rdi,rax
call printString

删除文件

;函数名 删除文件
;参数 mov rdi, fileName
global deleteFile
deleteFile:
;mov rdi, fileName
mov rax, NR_unlink
syscall
cmp rax, 0
jl deleteerror
mov rdi, success_Delete
call printString
ret
deleteerror:
mov rdi, error_Delete
call printString
ret

例子

创建/写入/关闭

%IF CREATE
;创建打开,然后关闭
mov rdi, fileName
call createFile
mov qword [FD], rax ; save descriptor

; write to file #1
mov rdi, qword [FD]
mov rsi, text1
mov rdx, qword [len1]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

打开/覆盖/关闭

%IF OVERWRITE
;OPEN AND OVERWRITE A FILE, THEN CLOSE ---------------------------------------
; open file
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; write to file #2 OVERWRITE!
mov rdi, qword [FD]
mov rsi, text2
mov rdx, qword [len2]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

追加/写入/关闭

%IF APPEND
;OPEN AND APPEND TO A FILE, THEN CLOSE ---------------------------------------
; open file to append
mov rdi, fileName
call appendFile
mov qword [FD], rax ; save file descriptor

; write to file #3 APPEND!
mov rdi, qword [FD]
mov rsi, text3
mov rdx, qword [len3]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

打开/定位偏移量/追加型覆盖写入/关闭

%IF O_WRITE
;OPEN AND OVERWRITE AT AN OFFSET IN A FILE, THEN CLOSE -----------------------
; open file to write
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; position file at offset
mov rdi, qword[FD]
mov rsi, qword[len2] ;offset at this location
mov rdx, 0
call positionFile

; write to file at offset
mov rdi, qword[FD]
mov rsi, text4
mov rdx, qword [len4]
call writeFile

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

打开/读取/关闭

%IF READ
;OPEN AND READ FROM A FILE, THEN CLOSE ---------------------------------------
; open file to read
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; read from file
mov rdi, qword [FD]
mov rsi, buffer
mov rdx, bufferlen
call readFile
mov rdi,rax
call printString

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

打开/定位偏移/读取/关闭

%IF O_READ
;OPEN AND READ AT AN OFFSET FROM A FILE, THEN CLOSE ---------------------------------------
; open file to read
mov rdi, fileName
call openFile
mov qword [FD], rax ; save file descriptor

; position file at offset
mov rdi, qword[FD]
mov rsi, qword[len2] ;skip the first line
mov rdx, 0
call positionFile

; read from file
mov rdi, qword [FD]
mov rsi, buffer
mov rdx, 10 ;number of characters to read
call readFile
mov rdi,rax
call printString

; close file
mov rdi, qword [FD]
call closeFile
%ENDIF

删除文件

%IF DELETE
;DELETE A FILE --------------------------------------------------
; delete file UNCOMMENT NEXT LINES TO USE
mov rdi, fileName
call deleteFile
%ENDIF

汇编调用C库函数

调用C库函数会默认修改一些寄存器

.text:00000000004012F1 push    rsi
.text:00000000004012F2 push rdx
.text:00000000004012F3 push rcx
.text:00000000004012F4 push rbp
.text:00000000004012F5 call _printf
.text:00000000004012FA pop rbp
.text:00000000004012FB pop rcx
.text:00000000004012FC pop rdx
.text:00000000004012FD pop rsi

所以的话,见到这些代码也不要惊讶...

这些push不是传递参数

并且printf的参数rdi也没有被传递进去

这些push只不过是默默地保存一下参数

x86

首先在头文件引入库

includelib    msvcrt.lib

一个应用的例子

include Dqx.inc 

;对要用的函数做一些声明
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg

.data
n_input byte "%d %d",0
n_output byte "%d+%d=%d",10,0
s_output BYTE "%s",10,0

str1 byte "welcome to my world",10
byte "please input 2 NUm",0

a dd 0
b dd 0

.code
start:
invoke printf,addr s_output,addr str1
invoke scanf,addr n_input,addr a,addr b
mov eax,0
add eax,a
add eax,b
invoke printf,addr n_output,a,b,eax
INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
end start

1.对于参数的传递,他得是32位的数据长度

好比一个void fun(char ascii),函数的汇编会有一个强制类型的转化,把你传入的一个字节变为32位的长度

2.参数传递的顺序,参数会从右到左依次push或者mov [esp+4],,,,,

x64

仍然需要在外部声明

linux下

调用printf

rax为0,表示0个非浮点数

rdi 格式化字符串的地址,第一个参数

rdx放第二个参数

rsi 数值/字符串地址, 数值好比[var],字符串地址就用数组的名字

遇到浮点数

rax=3,表示你传入3个浮点数参数,

于是函数依次把xmm0,xmm1,xmm2,作为参数

movq传入当精度浮点数,

movsd传入当精度浮点数

mov rax,1
movq xmm0,[f_Num]
mov rdi,fmat
call printf

问题来了,它怎么知道我有几个参数???? 我传入rdi,一个字符串的地址,可是它会不会读取其它的寄存器呢???

rdi,第一个参数,xmm0第二个参数????

; hello2.asm 

section .data
str1 db "welcome to my world",10,0 ;中断调用没有换行,这里手动添加一个
len1 equ $-str1-1
str2 db "I am Dqx_Ghost",0
len2 equ $-str2-1

num dq 2001
pi dq 3.14

fmt_int db "%d",10,0
fmt_double db "%lf",10,0
fmt_str db "%s",10,0

section .bss
section .text
extern printf
global main
main:

push rbp
mov rbp,rsp

mov rax,1
mov rdi,1
mov rsi,str1
mov rdx,len1
syscall


mov rax,0
mov rsi,str2
mov rdi,fmt_str
call printf

mov rax,0
mov rsi,[num]
mov rdi,fmt_int
call printf


mov rax,1
movq xmm0,[pi]
mov rdi,fmt_double
call printf


mov rsp,rbp
pop rbp

mov rax, 60 ; 60 = exit
mov rdi, 0 ; 0 = success exit code
syscall ; quit

windows下

printf

rcx是第一个参数

堆栈对齐

.text:00000000004012F1 push    rsi
.text:00000000004012F2 push rdx
.text:00000000004012F3 push rcx
.text:00000000004012F4 push rbp
.text:00000000004012F5 call _printf
.text:00000000004012FA pop rbp
.text:00000000004012FB pop rcx
.text:00000000004012FC pop rdx
.text:00000000004012FD pop rsi

其实他没有必要push那么多的东西

但是一定要堆栈对齐

于是他就push了一些没有的东西用于了堆栈对齐

实验

8086 课程设计

1

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
data segment
;年份
db '1975','1976','1977','1978','1979','1980','1981'
db '1982','1983','1984','1985','1986','1987','1988'
db '1989','1990','1991','1992','1993','1994','1995'

;收入
dd 16,22,382,1356,2398,8000,16000
dd 24486,50065,97479,140417,197514,345980,590827
dd 803528,1183000,1843000,2759000,3753000,4649000,5937000

;员工数量
dw 3,7,9,13,28,38,130
dw 220,476,778,1001,1442,2258,2793
dw 4037,5635,8226,11542,14438,15257,17800
data ends

table segment
db 21 dup ('0123456789ABCDEF')
table ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends

string segment
db 10 dup('0')
string ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
;数据地D
mov ax,7e00h
mov es,ax
;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call input_table
call clear_screen
call output_table

over:
mov ax,4c00h
int 21h
;=======================================================
;function
input_table:
mov ax,data
mov ds,ax

mov ax,table
mov es,ax

mov si,0
mov di,21*4*2
mov bx,0
mov cx,21

inputtable:
;year
;这个push与pop非常的妙,把4个字节,巧妙的传送
push ds:[si]
pop es:[bx]
push ds:[si+2]
pop es:[bx+2]

;x
mov ax,ds:[si+21*4+0]
mov dx,ds:[si+21*4+2]
mov es:[bx+5],ax
mov es:[bx+7],dx
;y
push ds:[di]
pop es:[bx+10]
;z
div word ptr es:[bx+10]

mov es:[bx+13],ax

add si,4
add di,2
add bx,16
loop inputtable

ret
clear_screen:
mov ax,0b800h
mov es,ax
mov bx,0
mov dx,0700h
mov cx,2000
clearscreen:
mov es:[bx],dx
add bx,2
loop clearscreen

ret
output_table:
mov ax,table
mov ds,ax

mov ax,string
mov es,ax

mov si,0
mov cx,21
mov di,160*3
mov bx,9
;[9]是10个当中最后一个位置
outputtable:
call show_year
call show_x
call show_y
call show_z

add di,160
add si,16
loop outputtable

ret
show_year:
push ax
push bx
push cx
push dx
push ds
push si
push es
push di

mov ax,0b800h
mov es,ax

add di,3*2
mov cx,4
showyear:
mov ah,2
mov al,ds:[si]
mov es:[di],ax
inc si
add di,2
loop showyear

pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
ret
show_x:
push ax
push bx
push cx
push dx
push ds
push si
push es
push di

mov ax,ds:[si+5]
mov dx,ds:[si+7]

call is_short_div
call init_reg
add di,10*2
call show_string


pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
ret
show_y:
push ax
push bx
push cx
push dx
push ds
push si
push es
push di

mov ax,ds:[si+10]
mov dx,0
call is_short_div
call init_reg
add di,2*20
call show_string

pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
ret
show_z:
push ax
push bx
push cx
push dx
push ds
push si
push es
push di

mov ax,ds:[si+13]
mov dx,0
call is_short_div
call init_reg
add di,2*40
call show_string

pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
ret

is_short_div:
mov cx,dx
jcxz short_div
call long_div
jmp is_short_div
div_ret:
ret
short_div:
mov cx,10
div cx
add dx,30h
mov es:[bx],dl
mov cx,ax
jcxz div_ret
mov dx,0
dec bx
jmp short_div
long_div:
mov cx,10
push ax
mov bp,sp
mov ax,dx
mov dx,0
div cx
push ax
mov ax,ss:[bp]
div cx
mov cx,dx
add cx,30h
mov es:[bx],cl
dec bx
pop dx
add sp,2

ret
init_reg:
push ax
mov ax,string
mov ds,ax
mov ax,0b800h
mov es,ax
pop ax
ret
show_string:
push ax
push bx
push cx
push dx
push ds
push si
push es
push di
showstring:
mov cx,0
mov cl,ds:[bx]
jcxz show_ret
mov ch,2
mov es:[di],cx
add di,2
inc bx
jmp showstring
show_ret:
pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
ret

;=======================================================
code ends
end start
;=======================================================

又写了一遍

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
data segment
;年份
db '1975','1976','1977','1978','1979','1980','1981'
db '1982','1983','1984','1985','1986','1987','1988'
db '1989','1990','1991','1992','1993','1994','1995'

;收入
dd 16,22,382,1356,2398,8000,16000
dd 24486,50065,97479,140417,197514,345980,590827
dd 803528,1183000,1843000,2759000,3753000,4649000,5937000

;员工数量
dw 3,7,9,13,28,38,130
dw 220,476,778,1001,1442,2258,2793
dw 4037,5635,8226,11542,14438,15257,17800
data ends

table segment
db 21 dup ('0123456789ABCDEF')
table ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends

string segment
db 16 dup(0)
string ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
;数据地D
mov ax,7e00h
mov es,ax
;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================

call clean_screen
call init_table
call work

over:
mov ax,4c00h
int 21h
;=======================================================
;function

clean_screen:
mov ax,0b800h
mov es,ax
mov bx,0
mov cx,2000
mov dx,0700h
clean:
mov es:[bx],dx
add bx,2
loop clean

ret
init_table:
mov ax,data
mov ds,ax
mov ax,table
mov es,ax

mov cx,21
mov si,0
mov di,21*4*2
mov bx,0
init_small:
;0123456789ABCDEF
push ds:[si+0]
pop es:[bx]
push ds:[si+2]
pop es:[bx+2]



push ds:[si+21*4+0]
pop es:[bx+5]
push ds:[si+21*4+2]
pop es:[bx+7]

push ds:[di]
pop es:[bx+10]

mov ax,es:[bx+5]
mov dx,es:[bx+7]
div word ptr es:[bx+10]


mov es:[bx+13],ax

mov al,58 ;:
mov es:[bx+4],al
mov al,47 ;/
mov es:[bx+9],al
mov al,61 ;=
mov es:[bx+12],al

add bx,16
add si,4
add di,2
loop init_small

ret
work:
mov ax,table
mov ds,ax
mov ax,string
mov es,ax

mov cx,21
mov bx,0
mov si,0
mov di,160*3

work_small:
call show_year
call show_x
call show_y
call show_z

add di,160
add si,16
loop work_small
ret
show_year:
push si
push es
push di
push cx
add di,10

mov ax,0b800h
mov es,ax
mov cx,4
year_small:
mov al,ds:[si]
mov es:[di],al
inc si
add di,2
loop year_small

mov al,ds:[4]
mov es:[di],al

mov al,ds:[9]
mov es:[di+30],al

mov al,ds:[12]
mov es:[di+50],al

pop cx
pop di
pop es
pop si
ret

;0123456789ABCDEF
show_x:
push cx
push di
add di,40
mov ax,ds:[si+5]
mov dx,ds:[si+7]
mov bx,15
call is_short_div
call show_string

pop di
pop cx
ret

show_y:
push cx
push di
add di,60
mov ax,ds:[si+10]
mov dx,0
mov bx,15

call is_short_div
call show_string

pop di
pop cx
ret
show_z:
push cx
push di
add di,80
mov ax,ds:[si+13]
mov dx,0
mov bx,15
call is_short_div
call show_string

pop di
pop cx
ret
is_short_div:
mov cx,dx
jcxz short_div
call long_div
jmp is_short_div
div_ret:
ret
short_div:
mov cx,10
div cx
add dl,30h
mov es:[bx],dl
mov dl,0
mov es:[bx-1],dl
dec bx
mov cx,ax
jcxz div_ret
mov dx,0
jmp short_div
long_div:
push ax
mov bp,sp
mov ax,dx
mov dx,0
mov cx,10
div cx
push ax
mov ax,ss:[bp]
div cx
add dx,30h
mov es:[bx],dl
mov dl,0
mov es:[bx-1],dl
dec bx

pop dx
add sp,2
ret

show_string:
push es
push ds
push di

mov bx,15

mov ax,string
mov ds,ax
mov ax,0b800h
mov es,ax
show_str:
mov ah,2
mov al,ds:[bx]
mov es:[di],ax

sub di,2
dec bx

mov cx,0
mov cl,ds:[bx]
jcxz show_ret
jmp show_str
show_ret:
pop di
pop ds
pop es
ret
;=======================================================
code ends
end start
;=======================================================

出现的问题

  1. push的保存没注意先后顺序,也就是临时保存无效
  2. 对结构的化编程不是很清晰,老是走一步看一步
  3. 一个小小的寄存器错误,就导致中断,程序崩溃
  4. 如果数据溢出是会中断的,而不是报错

8086 课后实验

模拟jcxz

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
data segment
db 256 dup('A')
data ends
;=======================================================
stack segment stack
db 128 dup('w')
stack ends
;=======================================================
code segment
start:
;=======================================================
;栈
mov ax,stack
mov ss,ax
mov sp,128

;数据段
mov ax,2000h
mov ds,ax
mov bx,0

;=======================================================

mov cx,10
mov al,1
init: mov byte ptr ds:[bx], al
inc bx
inc al
loop init

mov bx,0
fun: mov cl,byte ptr ds:[bx]
jcxz over
inc bx
jmp fun


;=======================================================
over: mov ax,4c00h
int 21h
;=======================================================
code ends
end start
;=======================================================

还是要注意数据的初始化

另外要注意循环是哪里开始的,不要乱循环

关于loop与cx去检索数据段中0的位置

s:
mov cl,ds:[bx]
mov ch,0
inc cx
inc bx
loop s
ok:
dec bx
mov dx,bx

它的检索,是靠bx++的来依次检索

但是又靠cx!=0来推动检索

所以才达到避免cx单一化,又推动了片段的检索

代码要求是得到位置

因此最后会有一个

dec bx

mov dx,bx

一个经典的指令复制实验-jmp指令的本质

;=======================================================
assume cs:code
;=======================================================

;=======================================================

;=======================================================
code segment

;=======================================================
mov ax,4c00h
int 21h
start:
mov ax,0
s:
nop
nop

mov di,offset s
mov si,offset s2
mov ax,cs:[si]
mov cs:[di],ax

s0:
jmp short s

no_use:
mov ax,0
int 21h
mov ax,0

s2:
jmp short no_use
nop

code ends
end start

;=======================================================
;main code

;=======================================================
;fun area



;=======================================================

;=======================================================
code ends
end start
;=======================================================

假如我们有指令

jmp s1

在复制jmp s1的时候

它不是复制jmp s1

而是复制他的汇编指令

加入它的指令是EBF6

F6-然后1111 0110然后取反0000 1001然后+1为然后0000 1010然后10进制为10

说明它以前往后面跳了10个位移

debug那个代码

以前的

jmp short no_use

它的汇编就是EBF6,于是就复制了EBF6

可以看到,以前的nop,nop

变为了EBF6

那么的话

因为我们复制的是EBF6

不是复制jmp short ptr no_use

所以的话

出现jmp 0000也不要诧异

为什么是0000

因为

...
076A:0008 EBF6 jmp 0000
076A:000A BF0800 mov di,0008

于是从000A处往上面从0开始数到10

就可以得到地址 0000

同时我们事先就安排好了,0000处是程序的结束位置

在屏幕中央显示带颜色的字符串‘welcome to masm’

DOS窗口的

一行有160个字节

一共有25行

一页有4000个字节

一共有8页

偶数地址存放ASCII字符

基数地址存放字符的颜色

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
data segment

db 'welcome to masm!'
db 00000010b
db 00100100b
db 01110001b

data ends
;=======================================================
stack segment stack
db 128 dup('m')
stack ends
;=======================================================
code segment
start:
;=======================================================
;栈
mov ax,stack
mov ss,ax
mov sp,128
;数据源
mov ax,data
mov ds,ax
mov si,0
mov bx,16

;数据目的地
mov ax,0B800h;显存的地址0xB800
mov es,ax
mov di,160*12+30*2;第[12]行第[60]字节处
;=======================================================
;main code

jmp show

over:
mov ax,4c00h
int 21h
;=======================================================
;fun area
show:
mov cx,3;大循环3次

point:
push cx
push di
mov cx,16;初始化小循环16次
mov si,0
mov dh,ds:[bx]
;规律是高位修改color颜色,低位修改ASCII字符,dh是奇数位,al是偶数位,连续地址 [00] [01] 中,在dx的排布是[01] [00]
show_str:
;把ASCII与颜色依次打入显存
mov dl,ds:[si]
mov es:[di],dx
;检索下一个字符
add di,2;传输是字符与颜色的共同传输
inc si;字符的检索
loop show_str

pop di;还原di
pop cx;还原大循环cx的次数
add di,160;把下行打入显存
inc bx;配置下一个颜色
loop point;再一次循环

jmp over

;=======================================================
code ends
end start
;=======================================================

我自己再写一遍

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 'welcome to masm!'
db 00000010b
db 00100100b
db 01110001b
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0 ;si=0;si<16;si++
mov bx,16 ;bx=0;bx<3;bx++

;数据地D
mov ax,0B800h
mov es,ax
mov di,160*12+30*2 ;di=160*12+30*2;di<xx+160*3;di+=160

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================

;把颜色,ascii打入字符串当中
;颜色在cx=3大循环中打入,ASCII在小循环cx=16中打入
;用bx寻颜色
;用si,di寻ASCII

mov cx,3
big:

push cx;cx=3,在小循环cx=16,会用到
push di

mov dh,ds:[bx]
mov cx, 16
mov si,0


small:
mov dl,ds:[si]
mov es:[di],dx
inc si
add di,2
loop small

pop di
pop cx

add di,160
inc bx
loop big
over:
mov ax,4c00h
int 21h
;=======================================================
;function



;=======================================================
code ends
end start
;=======================================================

出现的问题

没注意数据的长度,体现在

small:
mov dl,ds:[si]
mov es:[di],dx
inc si
inc di
loop small

这里的话

应该是

add di,2

不是

di++

复制的脚本

assume cs:code


code segment

mov bx,0
mov cx,16

func:
mov ax,0FFFFh
mov ds,ax
mov dl,ds:[bx] ;dl=[0xFFFF:bx]

mov ax,0020h
mov ds,ax
mov ds:[bx],dl ;[0020:bx]=dl

inc bx
loop func

mov ax,4c00h
int 21h

code ends

end

另外一种

assume cs:code


code segment

mov ax,0ffffh
mov ds,ax ;ds 数据从哪里来

mov ax,0020h
mov es,ax ;es 数据给到哪里去

mov bx,0
mov cx,16

func:
mov dl,ds:[bx] ;取出来
mov es:[bx],dl ;放进去
inc bx ;遍历
loop func

mov ax,4c00h
int 21h

code ends

end

指令复制

assume cs:code 


data segment
db 256 dup('A')
data ends

stack segment stack
db 128 dup('w')
stack ends

code segment

start:
;ss初始化
mov ax,stack
mov ss,ax
mov sp,128

s:
mov ax,bx
mov si,offset s
mov di, offset s0

mov dx,cs:[si]
mov cs:[di],dx

s0:
nop
nop



end:
mov ax,4c00h
int 21h

code ends

end start

这里只是复制了一条指令

把指令 17行的指令 mov ax,bx占2个字节

复制到 29行处 mov ax,bx

你也可以通过循环复制多条指令

但是,指令的长度,你必须要好好考虑一下

打印小写与大写字符串

类型一

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 'dengquxiang are',0
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call init_reg
call clean_screen
call show_small
call init_big
call show_big



over:
mov ax,4c00h
int 21h
;=======================================================
;function
init_reg:
mov bx,data
mov ds,bx

mov bx,0b800h
mov es,bx
ret
clean_screen:
mov bx,0
mov dx,0700h
mov cx,2000
cleanscreen:
mov es:[bx],dx
add bx,2
loop cleanscreen
ret
show_small:
mov si,0
mov di,160*10+30*2

call show_str


ret
show_str:
push cx
push ds
push es
push si
push di

mov cx,0
showstr:
mov cl,ds:[si]
jcxz show_ret
mov es:[di],cl
add di,2
inc si
jmp showstr
ret
show_ret:
pop di
pop si
pop es
pop ds
pop cx
ret
init_big:
mov si,0
call capital
ret
capital:
push cx
push ds
push si
push di
mov cx,0
point:
mov cl,ds:[si]
jcxz capital_ret
and byte ptr ds:[si],11011111b
inc si
jmp point

capital_ret:
pop di
pop si
pop ds
pop cx
ret
show_big:
mov si,0
mov di,160*11+30*2
call show_str

ret
;=======================================================
code ends
end start
;=======================================================

我写的

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 'tinyxiangxiangs',0
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
;初始化指向
;显示数据
;修改数据
;显示数据
;数据显示是一个函数
call init_reg
call init_screen
call show_small
call show_big
over:
mov ax,4c00h
int 21h
;=======================================================
;function
;0700h是默认颜色 160*10+30*2是显示的位置,显存的地址0b800h

init_reg:
;指向的地址
mov ax,0b800h
mov es,ax
mov si,0;si+=1
mov di,160*10+30*2;di+=2
ret
init_screen:
mov cx,2000
mov ax,0
push di
mov di,0
loop1:
mov es:[di],ax
add di,2
loop loop1
pop di
ret
show_small:
push si
push di
mov cx,16
mov ah,11011111b
loop2:
mov al,ds:[si]
mov es:[di],ax
inc si
add di,2
loop loop2
pop di
pop si
ret
show_big:
push si
push di
mov di,160*11+30*2
mov cx,16
mov ah,11011111b
loop3:
mov al,ds:[si]
and al,11011111b
mov es:[di],ax
inc si
add di,2
loop loop3
pop di
pop di
ret

;=======================================================
code ends
end start
;=======================================================

类型2

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 'wind',0
db 'good',0
db 'word',0
db 'unix',0
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call init_reg
call clean_screen
call make_big
black:
mov ax,1000h
jmp black




over:
mov ax,4c00h
int 21h
;=======================================================
;function
init_reg:
mov bx,data
mov ds,bx
mov bx,0b800h
mov es,bx
ret
clean_screen:
mov bx,0
mov dx,0700h
mov cx,2000
cleanscreen:
mov es:[bx],dx
add bx,2
loop cleanscreen
ret
make_big:
mov si,0
mov di,160*10+30*2
mov cx,4
makebig:
call show_str
call capital_letter
call show_word
add di,160
add si,5
loop makebig

ret
show_str:
push cx
push ds
push es
push di
push si

mov cx,0


showstr:
mov cl,ds:[si]
jcxz show_str_ret
mov es:[di],cl
add di,2
inc si
jmp showstr

show_str_ret:
pop si
pop di
pop es
pop ds
pop cx

ret
capital_letter:
push cx
push ds
push si
push di

mov cx,0
capitalletter:
mov cl,ds:[si]
jcxz capital_ret
and byte ptr ds:[si],11011111b
inc si
jmp capitalletter

capital_ret:
pop di
pop si
pop ds
pop cx
ret
show_word:
push di
add di,10*2
call show_str
pop di
ret
;=======================================================
code ends
end start
;=======================================================

我写的

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 'wind',0
db 'good',0
db 'word',0
db 'unix',0
db 0,0,0,0,0
;5个5个的循环,160++的显示
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================

call init_reg
call clean_screen
call show_small
call show_big






over:
mov ax,4c00h
int 21h
;=======================================================
;function

init_reg:
mov ax,0b800h
mov es,ax
mov di,160*10+30*2

ret
;=======================================================
clean_screen:

mov cx,2000
mov ax,0700h
push di
mov di,0
lop1:
mov es:[di],ax
add di,2
loop lop1

pop di
ret
;=======================================================
show_small:
mov si,0
mov bx,si
;大循环
push di
lop3:
mov cx,0
mov cl,ds:[bx]
jcxz small_ret
push di

;小循环
lop2:
mov cl,0
mov cl,ds:[si]

jcxz lop2_out
mov es:[di],cl
mov byte ptr es:[di+1],11011111b
inc si
add di,2
jmp lop2

lop2_out:
pop di
add di,160
add bx,5
inc si ;容易忽略
jmp lop3
small_ret:
pop di
ret

;=======================================================
show_big:
mov bx,0
mov cx,0
mov si,0
mov di,160*10+30*2+12;本身就存在10个字节的长度,ASCII+颜色,就是2个字节,并且di的位置起点必须还是偶数,否则后面的数据会错位填补
mov bx,si
;大循环
push di
lop5:
mov cx,0
mov cl,ds:[bx]
jcxz big_ret
push di

;小循环
lop4:
mov cl,0
mov cl,ds:[si]

jcxz lop4_out
and cl,11011111b
mov es:[di],cl
mov byte ptr es:[di+1],11011111b
inc si
add di,2
jmp lop4

lop4_out:
pop di
add di,160
add bx,5
inc si ;容易忽略
jmp lop5
big_ret:
pop di
ret


;=======================================================
code ends
end start
;=======================================================

没有去注意数据的位置有何意义,也没有注意数据的长度

打印一个字符串

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 'welcome to masm!',0
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================

call init_reg
call show



over:
mov ax,4c00h
int 21h
;=======================================================
;function
init_reg:
mov dh,23
mov dl,3
mov cl,2

mov ax,0b800h
mov es,ax

mov ax,0
mov al,dh
mov bx, 160
mul bx
mov di,ax

mov al,dl
mov bx,0
mov bl,2
mul bl
add di,ax


ret
show:

mov cx,0
mov cl,ds:[si]
jcxz show_ret
mov ch,2
mov es:[di],cx
add di,2
inc si
jmp show

show_ret:
ret

;=======================================================
code ends
end start
;=======================================================

出现的问题

对于cx中带颜色,jcxz怎么办

jcxz中jmp的位置不对

jcxz发现cx一直不为0,woc

直接用

mov di,160*23+6

这样更快

源代码->ASCII的打印

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
dd 0ffffh,5678,65535,0
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call init_reg
call show_number

over:
mov ax,4c00h
int 21h
;=======================================================
;function
init_reg:

mov ax,0b800h
mov es,ax

ret

Break_Up_Digit: ;将数字, 好比1234, 利用%的方法去分解为 ,1 2, 3, 4, 然后打入显存
push ax
push bx
push cx
push dx
push ds
push es
push si
push di

;初始化 被除数 / 除数 = ?
mov ax,ds:[si]
mov dx,ds:[si+2]
mov bx,10

lp1:
div bx
mov dh,2
add dl,30h ;余数->ASCII
mov es:[di],dx ;ASCII-显存
sub di,2 ;右往左依次填补
mov cx,ax ;取商到 cx
jcxz fun_ret ;如果商为0,就退出->当数据本身就是0,也不影响它的输出
mov dx,0 ;清空dx,它本身用来装东西的
jmp lp1

fun_ret:
pop di
pop si
pop es
pop ds
pop dx
pop cx
pop bx
pop ax

ret

show_number:
mov si,0
mov di,160*10+40*2
mov cx,4 ;我们有4行数据
shownumber:
call Break_Up_Digit
add si,4 ;指向数据段的下一个word数据
add di,160 ;显示在下一行
loop shownumber ;循环往复

ret

;=======================================================
code ends
end start
;=======================================================

里面用到了看数的分解

用32位除法的余数dx去有效的代替%,那么就可以自动的判断了

我自己写的

怎么处理 over_flow 除法??????

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 01,02,03,04,05,06,07,08,09,10
;所以要10次?

data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call Init_Reg
call Show_Data




over:
mov ax,4c00h
int 21h
;=======================================================
;function

Init_Reg:

mov ax,0b800h
mov es,ax
mov di,10*160+30*2

ret

Show_Data:
;采用数分解的方式

mov cx,10
mov si,0
mov bx,0

lp2:
push cx
push di


mov ah,0
mov al,ds:[si]

mov dx,0
mov bx,10
;余数在dx中
lp1:
div bx
add dl,30h
mov dh,2
mov es:[di],dx
mov cx,ax
jcxz continue_x
sub di,2
mov dx,0
jmp lp1


continue_x:
pop di
pop cx
add di,160
inc si
loop lp2


ret

;=======================================================
code ends
end start
;=======================================================

出现的问题

对循环而言,不知道那些指令是要循环的,哪些指令它是不需要循环的

对数据经不起考验,也就是代码不严谨

好比对于除法,每一次它的计算,你最好要去初始化一下,否者很容易数据错位而算出奇奇怪怪的东西


屏幕复制

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 128 dup('x')
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
;数据地D
mov ax,7e00h
mov es,ax
;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================

call init_reg

call cpy_screen




over:
mov ax,4c00h
int 21h
;=======================================================
;function
init_reg:

mov bx,0b800h
mov ds,bx
mov es,bx
ret
cpy_screen:

mov cx,24
mov si,160
mov di,0
cpy_screen_cmd:
push cx
push si
push di
mov cx,80

cld
rep movsw

pop di
pop si
pop cx
add si,160
add di,160
loop cpy_screen_cmd

ret


;=======================================================
code ends
end start
;=======================================================

指令复制

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
db 128 dup('x')
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
;数据地D
mov ax,7e00h
mov es,ax
;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call copy_cmd





over:
mov ax,4c00h
int 21h
;=======================================================
;function

cmd: mov ax,10
mov ax,10
mov ax,10
mov ax,10
mov ax,10
cmd_end: nop

copy_cmd:
mov bx,cs
mov ds,bx
mov si,offset cmd

mov bx,0
mov es,bx
mov di,7e00h

mov cx,offset cmd_end-cmd
cld
rep movsb

ret


;=======================================================
code ends
end start
;=======================================================

Copy_Code函数

用到了几个参数

:代表了循环的次数

​CX ​

代表了复制的功能

sb就是byte的一种复制

​cld rep movsb ​

要复制什么代码

复制到哪里去

​ds:[si] es:[di] ​

用中断模拟没有loop 的的loop

它的原理是修改栈里的IP

然后pop出去被修改的IP

assume cs:code ,  ss:stack

stack segment
db 32 dup('0')
stack ends

code segment
start:
mov ax,stack
mov ss,ax;
mov sp,32

call init_do7ch;他只会push ip

mov ax,0b800h
mov es,ax
mov di,160*12

mov bx,offset s - offset send
mov cx,8
s:

mov byte ptr es:[di+0],'!'
mov byte ptr es:[di+1],2
add di,2
int 7ch
send:
nop

mov ax,4c00h
int 21h

;-----------------------------------------------------------------------------------------------------------------

init_do7ch:
;先是修改数据的指向,然后是复制中断代码到中断区,中断区域的调用是自动的
;--------------------------------------------
;设置数据来源 S指->CS:[do7ch]
mov ax,cs
mov ds,ax
mov si,offset do7ch;这样取地址就非常的妙..不想你之前根本想不到

;设置数据D->0000:[0200]
mov ax,0
mov es,ax
mov di,200h

mov cx,offset do7chend - offset do7ch;循环的次数

cld
rep movsb


;数据指向完成后,初始化一下中断向量表,第[0]个向量表的地址0000:0200
mov ax,0
mov es,ax
mov word ptr es:[7ch*4+0],200h
mov word ptr es:[7ch*4+2],0

ret

;--------------------------------------
do7ch:
push bp
mov bp,sp;因为这里让bp发生了改变...所以我们要保存bp的初始化值
dec cx
jcxz lpret
add ss:[bp+2],bx;woc,这里不是默认的ds:
lpret:
pop bp
iret

do7chend:
nop



code ends
end start

在一定的光标位置显示字符

assume cs:code,ss:stack

stack segment
db 128 dup('y')
stack ends

code segment

start:
mov ax,stack
mov ss,ax
mov sp,128

;bh 总 是 代 表 着 页 数
;ah总 是 代 表 着 编 号

mov ah,2
mov bh,0
mov dh,5
mov dl,2
int 10h

mov ah,9
mov al,'a'

mov bl,11001010b
mov bh,0
mov cx,6
int 10h

mov ax,4c00h
int 21h

code ends
end start

int 7ch 计算sqrt

assume cs:code ,  ss:stack

stack segment
db 128 dup('y')
stack ends

code segment
start:
mov ax,stack
mov ss,ax;
mov sp,128


call init_do7ch;他只会push ip
mov ax,8
int 7ch
add ax,ax
adc dx,dx

mov ax,4c00h
int 21h
;-----------------------------------------------------------------------------------------------------------------

init_do7ch:
;先是修改数据的指向,然后是复制中断代码到中断区,中断区域的调用是自动的
;--------------------------------------------
;设置数据来源 S指->CS:[do7ch]
mov ax,cs
mov ds,ax
mov si,offset do7ch;这样取地址就非常的妙..不想你之前根本想不到

;设置数据D->0000:[0200]
mov ax,0
mov es,ax
mov di,200h

mov cx,offset do7chend - offset do7ch;循环的次数

cld
rep movsb


;数据指向完成后,初始化一下中断向量表,第[0]个向量表的地址0000:0200
mov ax,0
mov es,ax
mov word ptr es:[7ch*4+0],200h
mov word ptr es:[7ch*4+2],0


ret





;--------------------------------------
do7ch:
mul ax
iret


do7chend:
nop



code ends
end start

int 7ch 大小写转化

assume cs:code ,  ss:stack

stack segment
db 16 dup('0')
stack ends



code segment
start:
mov ax,stack
mov ss,ax;
mov sp,16


;只是设置一个光标
mov ah,2

mov bh,0

mov dh,5
mov dl,12

int 10h

;只修改颜色配置的属性然后输出对应的字符串
mov ah,9
mov al,'&'
mov bl,11001010b
mov bh,0
mov cx,3
int 10h

;call int_7ch

mov ax,4c00h
int 21h







code ends
end start

手写实现 int21h的ah=9

打印字符串的函数

assume cs:code ,  ss:stack ,ds:data

stack segment
db 16 dup('0')
stack ends

data segment
db "hello! I am Dqx-Gh0st...weclome to my world",0
data ends


code segment
start:
mov ax,stack
mov ss,ax
mov sp,16

call init_do7ch
;行号
mov dh,10
;列好
mov dl,10
;颜色
mov cl,11001010b
;数据源
mov ax,data
mov ds,ax
mov si,0

int 7ch

mov ax,4c00h
int 21h



init_do7ch:

mov ax,cs
mov ds,ax
mov si, offset do7ch_start

mov ax,0
mov es,ax
mov di,200h

mov cx,offset do7ch_end - offset do7ch_start

cld
rep movsb

mov word ptr es:[7ch*4+0],200h
mov word ptr es:[7ch*4+2],0

ret

do7ch_start:

mov ax,0b800h
mov es,ax

mov ax,0
mov al,160
mul dh
;结果放在ax中

mov di,ax
mov ax,0
mov al,2
mul dl
;又得到数据放在了ax中
add di,ax


s:
push cx
mov cx,0
mov cl,ds:[si]
jcxz retend
pop cx;还原那个颜色属性的配置...cl
mov ch,ds:[si];再次赋予数据
mov byte ptr es:[di+0],ch
mov byte ptr es:[di+1],cl

add di,2
inc si
jmp s

retend:

add sp,2;这里会比较容易的去忘记.....只管跑...东西都忘记了拿
iret

do7ch_end:nop


code ends
end start

assume cs:code ,  ss:stack ,ds:data

stack segment
db 16 dup('0')
stack ends

data segment
db "123456789",0
data ends


code segment
start:
mov ax,stack
mov ss,ax
mov sp,16

call init_do7ch

mov ax,0b800h
mov es,ax
mov di,160*12
mov bx, offset send - offset s
mov cx,10

s:
mov byte ptr es:[di+0],'?'
mov byte ptr es:[di+1],2
add di,2
int 7ch
send:nop
mov ax,4c00h
int 21h



init_do7ch:

mov ax,cs
mov ds,ax
mov si, offset do7ch_start

mov ax,0
mov es,ax
mov di,200h

mov cx,offset do7ch_end - offset do7ch_start

cld
rep movsb

mov word ptr es:[7ch*4+0],200h
mov word ptr es:[7ch*4+2],0

ret

do7ch_start:
dec cx
jcxz retend
mov bp,0
push bp
mov bp,sp
sub ss:[bp+2],bx
pop bp
retend:
iret

do7ch_end:nop


code ends
end start

输出4句诗

assume cs:code ,  ss:stack 

stack segment
db 16 dup('0')
stack ends

code segment

s1: db "Good,better,best",',','$'
s2: db "Never let it rest",',','$'
s3: db "Till good is bettre",',','$'
s4: db "And better ,best.",',','$'
s: dw offset s1, offset s2, offset s3, offset s4
row: db 2,4,6,8


start:
mov ax,stack
mov ss,ax
mov sp,16


mov ax,cs
mov ds,ax
mov bx,offset s
mov si,offset row
mov cx,4


ok:
mov ah,2;2函数

mov bh,0;0页
;mov dh,byte ptr ds:[si] ;si+=2;第某某行
mov dh,cs:[si]
mov dl,10
int 10h

;mov dx,bx
mov dx,cs:[bx]
mov ah,9
int 21h

add bx,2
inc si
loop ok

mov ax,4c00h
int 21h

mov ax,4c00h
int 21h


code ends
end start

用int9打印一首五颜六色的情诗

assume cs:code,ss:stack,ds:data

stack segment
db 32 dup('S')
stack ends

data segment
db 16 dup('D')
db "How do I love you? Let me count the ways.",0
db "I love you to the depth and breath and height.",0
db "My soul can reach ,when feeling out sight.",0
db "For the ends of being and ideal Grace.",0
db "I love you to the level of ideal everyday's.",0
db "Most quiet need,by sun and candlelight.",0
data ends

code segment
row: db 10,11,12,13,14,15
start:
mov ax,data
mov ds,ax

mov ax,stack
mov ss,ax
mov sp,32

mov ax,0b800h
mov es,ax
;---------------------------------------
call init_9h
;---------------------------------------
mov cx,6;有6句诗
mov si,16;si指向字符串开始位置
mov bx,offset row;记住bx指针,而不是指向的值
mov dh,2;它是颜色,因为dx很少用,避免冲突,我们就取dx

;--------------------------------============
k:
call show_str;
loop k
;----------------把键盘的控制权还给int9

mov ax,0
mov es,ax

push ds:[0]
pop es:[9*4+0]
push ds:[2]
pop es:[9*4+2]
;------------------------------------
mov ax,4c00h
int 21h


init_9h:
;修改的位置指向0000:9*4
push es
mov ax,0
mov es,ax

;保存一下以前的int9,后面我们还要调用
push es:[9*4+0]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]
;DIY一下int9,让它的中断向量表发生一些变化
mov word ptr es:[9*4+0],offset start_9h
mov es:[9*4+2],cs

pop es
ret

;DIY的int9长这样
start_9h:
in al,60h;呼叫端口,我们的键入都在端口那里,al接收了我们的键入
pushf;模拟int9的入栈
call dword ptr ds:[0];模拟int9的入栈
cmp al,48h;如果键入是 ↑
jne start_9h_end;不是就退出
inc dh;就修改颜色
start_9h_end:
iret



show_str:
mov al,160;每一行160
mul byte ptr cs:[bx];每一行x那个行数,bx是指针,bx+=2
mov di,ax;然目的地址接收结果
add di,20*2;指向20列
show_str_sontinue:
mov dl,ds:[si];si指针指针指向了字符串
cmp dl,0;如果是0,就退出,然后+1,跳过0,指向下一个字符串
je show_end
call delay;缓冲一下
mov es:[di],dx
add di,2;指向下一个像素
inc si;指向下一个字符
jmp show_str_sontinue;继续循环

show_end: inc bx;指向下一行
inc si;跳过0
ret

delay:
push ax
push bx
mov ax,0
mov bx,01h
delay_continue:
sub ax,1
sbb bx,0
cmp ax,0
jne delay_continue
cmp bx,0
jne delay_continue

pop bx
pop ax
ret

code ends
end start

在代码据放data segment的坏处

好处?

就是寻址很方便,因为代码区,你可以用指令​​offset​​来寻址,

但就问题就存在

mov bx,offset s1
mov si,offset s2
...
mov dx,cs:[bx]
...
mov dh,cs:[si]
......

可以在上面看到其实也没啥/…

但是cs:[~],你​​~​​​只能用​​纯数字,bx寄存器,ip,si,di​​,,…si,di有重要的用途,,,,,ip无法用…..bx有太少

安装DIY的int9中断,然后运行,,,可以修改屏幕的颜色

assume cs:code,ss:stack

stack segment
db 32 dup('S')
stack ends

code segment
start:

mov ax,stack
mov ss,ax
mov sp,32

push cs
pop ds
mov si,offset int9

mov ax,0
mov es,ax
mov di,204h

mov cx,offset int9_end - offset int9

cld
rep movsb

push es:[9*4+0]
pop es:[200h+0]
push es:[9*4+2]
pop es:[200h+2]

cli
mov word ptr es:[9*4+0],204h
mov word ptr es:[9*4+2],0
sti


mov ax,4c00h
int 21h

int9:
push ax
push bx
push cx
push es

in al,60h
pushf
call dword ptr cs:[200h];调用int 9的中断

cmp al,48h;F1-->3bh
jne int9ret

mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s:
inc byte ptr es:[bx]
add bx,2
loop s

int9ret:
pop es
pop cx
pop bx
pop ax
iret
int9_end: nop


code ends
end start

运行之后你就可以按一下

自己写了一遍..发现了很多的问题

①. 程序没有不接收.你没有​​mov ax,4c00h;int 21h​

②. 对原有的中断向量表做了保存却不去修改原有的向量表

③. 我们是先键入,再调用int9去

assume cs:code,ss:stack,ds:data

stack segment
db 128 dup('S')
stack ends

data segment
db 128 dup('D')
data ends

code segment
start:

mov ax,stack
mov ss,ax
mov sp,128

mov ax,cs
mov ds,ax
mov si,offset init_int9

mov ax,0
mov es,ax
mov di,204h


mov cx , offset init_int9_end - offset init_int9
cld
rep movsb


push es:[9*4+0]
pop es:[200h+0]
push es:[9*4+2]
pop es:[200h+2]


cli
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti

mov ax,4c00h
int 21h

init_int9:
push ax
push es
push bx
push cx

in al,60h

;是先发生键入,再调用外中断
pushf
call dword ptr cs:[200h];这里有个问题,如果mov ax,0 mov ds,ax call word ptr ds:[200h]就没有用

cmp al,48h
jne int9_ret

mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
change_screen:
inc byte ptr es:[bx]
add bx,2
loop change_screen

int9_ret:
pop cx
pop bx
pop es
pop ax
iret
init_int9_end:nop

code ends
end start

教材实验

assume cs:code,ss:stack

stack segment
db 32 dup('S')
stack ends

code segment
start:

mov ax,stack
mov ss,ax
mov sp,32

push cs
pop ds
mov si,offset int9

mov ax,0
mov es,ax
mov di,204h

mov cx,offset int9_end - offset int9

cld
rep movsb

push es:[9*4+0]
pop es:[200h+0]
push es:[9*4+2]
pop es:[200h+2]

cli
mov word ptr es:[9*4+0],204h
mov word ptr es:[9*4+2],0
sti


mov ax,4c00h
int 21h




int9:
push ax
push bx
push cx
push es

;in al,60h
;pushf
;call dword ptr cs:[200h];调用int 9的中断

;cmp al,3bh;F1-->3bh
;jne int9ret
;它探测的是你的断码..不是同码....因此就不用再去2次call int9

in al,60h
pushf
call dword ptr cs:[200h]
cmp al,3bh+80h
jne int9ret
show:
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,1000
s:
inc byte ptr es:[bx]
mov byte ptr es:[bx-1],'?'
add bx,2
loop s

int9ret:
pop es
pop cx
pop bx
pop ax
iret
int9_end: nop


code ends
end start

DIY中断安装,执行4个函数的功能

出了很多的错

还是那些问题

①.

对整个代码的思路不是很清晰….老师写了一半发现自己写乱了..思路不明确

②.

提取了原有的中断到一定的位置去,却不修改原有的中断==没有修改中断

③.

call far ptr但是错用ret..导致堆栈错误….不平衡

assume cs:code,ss:stack,ds:data

stack segment
db 32 dup('S')
stack ends

data segment
db 32 dup('D')
data ends

code segment
start:
mov ax,stack
mov ss,ax
;对整个代码的思路不是很清晰….老师写了一半发现自己写乱了..思路不明确
call init_int9

mov ax,4c00h
int 21h

init_int9:
mov ax,0
mov ds,ax

push ds:[4*9+0]
pop ds:[200h]
push ds:[4*9+2]
pop ds:[202h]

;提取了原有的中断到一定的位置去,却不修改原有的中断==没有修改中断
cli
mov word ptr ds:[4*9+0],200h
mov word ptr ds:[4*9+2],0
sti

;源地址
push cs
pop ds
mov si,offset copy_start
;目的地址
mov ax,0
mov es,ax
mov di,204h
;循环次数
mov cx,offset copy_end - offset copy_start

cld
rep movsb
ret

copy_start:
;担心害怕就把所有的寄存器给备份
pushf
push ax
push bx
push cx
push dx
push ds
push si
push es
push di

;获取扫描码
in al,60h

pushf
call dword ptr cs:[200h]

cmp al,2h;把扫描码与通码比较
je set0

cmp al,3h;
je set1

cmp al,4h;2
je set2

cmp al,5h;3
je set3

jmp real_ret


set0: mov ah,0
jmp do9_ret
set1: mov ah,1
jmp do9_ret
set2: mov ah,2
jmp do9_ret
set3: mov ah,3
jmp do9_ret



do9_ret:
call setscreen
;call far ptr但是错用ret..导致堆栈错误….不平衡
real_ret:

pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
popf

iret

;做一个屏幕的清理 ------------------
;初始会配置
setscreen:
push ax

cmp ah,0
je do1
cmp ah,1
je do2
cmp ah,2
je do3
cmp ah,3
je do4
jmp sret

do1:
call sub1
jmp short sret
do2:
call sub2
jmp short sret
do3:
call sub3
jmp short sret
do4:
call sub4
jmp short sret
sret:

pop ax
ret

sub1:
push bx
push cx
push es

mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
subls:
mov byte ptr es:[bx],' '
mov byte ptr es:[bx+1],0
add bx,2

loop subls


pop es
pop cx
pop bx
ret
;设置前景色-------------------
sub2:
push bx
push cx
push es
push ax

mov bx,0b800h
mov es,bx

mov bx,1

mov cx,2000

sub2s:
push cx
mov al,byte ptr es:[bx]
mov ah,al
mov cl,4

;头4位
shr al,cl
inc al
shl al,cl

shl ah,cl
shr ah,cl

or al,ah

mov byte ptr es:[bx],al
add bx,2
pop cx
loop sub2s

pop ax
pop es
pop cx
pop bx
ret
;设置背景色----------------------------
sub3:
push bx
push cx
push es
push ax

mov bx,0b800h
mov es,bx

mov bx,1

mov cx,2000

sub3s:
push cx

mov al,byte ptr es:[bx]
mov ah,al

mov cl,4
;后4位
;我是u怎么发现问题的????
;还是对位运算不理解呀....
shl al,cl
shr al,cl
inc al

shr ah,cl
shl ah,cl

or al,ah
;mov al,11001010b;我是怎么发现问题的?????对里面的参数去做修改,然后看发生了什么变化
mov byte ptr es:[bx],al
add bx,2
pop cx
loop sub3s

pop ax
pop es
pop cx
pop bx
ret
sub4:
push cx
push si
push di
push es
push ds

mov si,0b800h
mov es,si
mov ds,si
mov si,160;指向[1]第二行
mov di,0;指向[0]第一行
cld
mov cx,24;指向[24]最后一行

;处理1行来上移
sub4s:
push cx
mov cx,160
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,0
;最后一行置空
sub4s1:
mov byte ptr [160*24+si],'?'
mov byte ptr [160*24+si+1],0
add si,2
loop sub4s1

pop ds
pop es
pop di
pop si
pop cx
ret
copy_end: nop


code ends
end start

如何让做到inc每个配置??????????????

assume cs:code,ss:stack,ds:data

stack segment
db 32 dup('S')
stack ends

data segment
db 32 dup('D')
data ends

code segment
start:
mov ax,stack
mov ss,ax

call init_int9

mov ax,4c00h
int 21h

init_int9:
mov ax,0
mov ds,ax

push ds:[4*9+0]
pop ds:[200h]
push ds:[4*9+2]
pop ds:[202h]

;错误②
cli
mov word ptr ds:[4*9+0],200h
mov word ptr ds:[4*9+2],0
sti

;源地址
push cs
pop ds
mov si,offset copy_start
;目的地址
mov ax,0
mov es,ax
mov di,204h
;循环次数
mov cx,offset copy_end - offset copy_start

cld
rep movsb
ret

copy_start:
;担心害怕就把所有的寄存器给备份
pushf
push ax
push bx
push cx
push dx
push ds
push si
push es
push di
;获取扫描码
in al,60h
pushf
call dword ptr cs:[200h]

cmp al,2;把扫描码与通码比较
je set0

cmp al,3;
je set1

cmp al,4;2
je set2

cmp al,5;3
je set3

jmp real_ret


set0: mov ah,0
jmp do9_ret
set1: mov ah,1
;mov al,00000010b
jmp do9_ret
set2: mov ah,2
;mov al,10100000b
jmp do9_ret
set3: mov ah,2
jmp do9_ret



do9_ret:
call setscreen
real_ret:

pop di
pop es
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
popf

iret

;做一个屏幕的清理 ------------------
;初始会配置
setscreen:
cmp ah,0
je do1
cmp ah,1
je do2
cmp ah,2
je do3
cmp ah,3
je do4
jmp sret

do1:
call sub1
jmp short sret
do2:
call sub2
jmp short sret
do3:
call sub3
jmp short sret
do4:
call sub4
sret:
ret

sub1:
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
subls:
mov byte ptr es:[bx],' '
or byte ptr es:[bx+1],0
add bx,2

loop subls


pop es
pop cx
pop bx
ret
;设置前景色-------------------
sub2:
push bx
push cx
push es
push ax

mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s:
push cx
mov al,byte ptr es:[bx]
mov ah,al
mov cl,4

;头4位
shr al,cl
inc al
shl al,cl

shl ah,cl
shr ah,cl

or al,ah

mov byte ptr es:[bx],al
add bx,2
pop cx
loop sub2s

pop ax
pop es
pop cx
pop bx
ret
;设置背景色----------------------------
sub3:
push bx
push cx
push es
push ax

mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000

sub3s:
push cx
mov al,byte ptr es:[bx]
mov ah,al
mov cl,4
;后4位
shl al,cl
inc al
shr al,cl

shr ah,cl
shl ah,cl

or al,ah

mov byte ptr es:[bx],al
add bx,2
pop cx
loop sub3s

pop ax
pop es
pop cx
pop bx
ret
sub4:
push cx
push si
push di
push es
push ds

mov si,0b800h
mov es,si
mov ds,si
mov si,160;指向[1]第二行
mov di,0;指向[0]第一行
cld
mov cx,24;指向[24]最后一行

;处理1行来上移
sub4s:
push cx
mov cx,160
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,0
;最后一行置空
sub4s1:
mov byte ptr [160*24+si],'?'
mov byte ptr [160*24+si+1],0
add si,2
loop sub4s1

pop ds
pop es
pop di
pop si
pop cx
ret


copy_end: nop


code ends
end start

8086CPU->div的Interger_over_flow解决方案

这里系统自动把结果的低16位给了ax

我们人为的把结果的高16位给了dx

人为的把余数放在cx中

使得溢出的数据不再丢失

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
dd 1234567 ;87 D6 12 00
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
call div_number

over:
mov ax,4c00h
int 21h
;=======================================================
;function

div_number:
;87 D6 12 00 0x0012D687
mov ax,ds:[0] ;0xD687
mov dx,ds:[2] ;0x0012

mov cx,10
push ax ;push 0xD687
mov bp,sp
;取bp,自动向下获取数据

call long_div
add sp,2

ret

long_div:
mov ax,dx ;ax=高8位
mov dx,0 ;dx=0,避免干扰
div cx ;
push ax ;把高位的数据保存一下,余数作为低位除法的高位

mov ax,ss:[bp] ;bp记录了低8位原始数据的地址,ax=低8位
div cx ;
mov cx,dx ;我们自定义把最后的余数给了cx
pop dx ;把溢出的数据给了dx

ret
;=======================================================
code ends
end start
;=======================================================

例子就是

自我尝试

;=======================================================
assume cs:code , ds:data, ss:stack
;=======================================================
;数据源S
data segment
dd 1234567
data ends
;=======================================================
;栈段
stack segment stack
db 128 dup('y')
stack ends
;=======================================================
code segment
start:
;main code
;=======================================================
;数据源S
mov ax,data
mov ds,ax
mov si,0
mov bx,0

;数据地D
mov ax,7e00h
mov es,ax
mov di,0

;栈
mov ax,stack
mov ss,ax
mov sp,128

;=======================================================
mov ax,ds:[0]
mov dx,ds:[2]
mov cx,10

push ax
mov bp,sp
mov ax,dx
mov dx,0
div cx

push ax

mov ax,ss:[bp]
div cx

mov cx,dx
pop dx

add sp,2;堆栈的平衡
over:
mov ax,4c00h
int 21h
;=======================================================
;function



;=======================================================
code ends
end start
;=======================================================

它背后的思想是什么?

把数据大数化小,然后一句不变的性质取解除答案

再次书写出现的问题

1.老是忘记mov dx,0

2.add sp,2没有加对位置

3.对于ASCII的打印还不是很理解

x86 课堂代码

复制字符串

我的

title Dqx_Gh0st

.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD

.data
arr1 db "hello,I am dqx_Gh0st",0
arr2 db sizeof arr1 dup(0)
.code
main PROC


xor eax,eax
mov ecx ,sizeof arr1
mov esi , offset arr1
mov edi, offset arr2
add_num:
mov al,[esi]
mov [edi],al
inc esi
inc edi
loop add_num

INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
main ENDP
END main

书上的

xor eax,eax                                                                                                                                                                                        
mov ecx ,sizeof arr1
mov esi , 0
add_num:
mov al,arr1[esi]
mov arr2[esi],al
inc esi
loop add_num

逆序字符串,IDA无法调试,xdebug可以

title Dqx_Gh0st

.386
.MODEL flat,stdcall
ExitProcess PROTO, dwExitCode:DWORD
.stack 128
.data
arr1 BYTE "I am Dqx_Gh0st!",0
len=($-arr1)-1
arr2 db len dup (0)
.code
main PROC


xor eax,eax
mov ecx,len
mov esi,offset arr1
push_str:
mov al,byte ptr [esi]
push ax
inc esi
loop push_str

mov ecx,len
mov edi,offset arr2
pop_str:
pop ax
mov byte ptr [edi],al
inc edi
loop pop_str


INVOKE ExitProcess,0
;---------------------------------------------------------------------------
main ENDP
END main

strcmp 汇编版-无高级调用

2个比较的字符串数据长度不一致

1️⃣.结果肯定是不相等

2️⃣.我们还是在小的范围里面比较一下

比较情况

1️⃣.数据尺度一致,一直比较到字符串不相等的地方

2️⃣.数据尺度不一致

在前面长度内,他们的数据都一样,导致ZF一直为1,如果你通过检测到0而采取退出的话,最后的ZF就还是1,而逻辑上的ZF=0,毕竟他们不相等

因此,就算我们检测到0,也还是要比较一下,除非2个字符串都检测到0,说明前面都相等,后面也就0,0自然也相等

include   Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "abcdABCD",0
B1 db "abcdabcd",0
len dd lengthof A1
;--------------------------------------------------------
;function
;--------------------------------------------------------
strcmp proto :ptr byte,:ptr byte

;--------------------------------------------------------
.code
;--------------------------------------------------------
start:

invoke strcmp,addr A1,addr B1
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function

;--------------------------------------
strcmp proc uses eax edx esi edi,str1:ptr byte,str2:ptr byte
mov esi, str1
mov edi, str2
check_continue:
mov al, [esi]
mov dl, [edi]
cmp al, 0
jne check
cmp dl, 0
jne check
;上面2个cmp操作
;当最后字符串末尾都是0就不再检查
;如果其中一个是0,另外一个不是0,那么cmp一下,肯定不相等
jmp no_check
check:
cmp al,dl
pushfd
inc esi
inc edi
popfd
je check_continue
no_check:
ret
strcmp endp

;--------------------------------------------------------
end start
;--------------------------------------------------------

inc会影响ZF寄存器,woc

strlen 汇编版-无高级调用

include   Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "abcd",0
B1 db "abcdabcd",0
len dd lengthof A1
;--------------------------------------------------------
;function
;--------------------------------------------------------
strlen proto :ptr byte
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:

invoke strlen,addr A1
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function

;--------------------------------------
strlen proc uses edi,pStr:ptr byte

mov edi, pStr
mov eax, 0
check_continue:
cmp byte ptr [edi],0
je check_end
inc edi
inc eax
jmp check_continue
check_end:
ret
strlen endp

;--------------------------------------------------------
end start
;--------------------------------------------------------

有输入的strlen

值得注意的是,scanf遇到输入空格就会停止,所以你在键入的时候,就不要输入空格

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include Dqx.inc
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
.data

arr db 128 dup(0)
in_fmt db "%s",0
out_fmt db "%d",0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:

invoke scanf,offset in_fmt, offset arr

mov al,0
lea edi,arr
mov ecx,10
cld
repne scasb
dec edi

lea eax,arr
sub edi,eax


invoke printf,addr out_fmt,edi
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start

strcpy 汇编版-无高级调用

include     Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "Dqx",0
len = lengthof A1
B1 db len dup (0)
;--------------------------------------------------------
;function
;--------------------------------------------------------
Strcopy proto source:PTR BYTE, target:PTR BYTE
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:

invoke Strcopy ,addr A1,addr B1
over:
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------
;--------------------------------------
Strcopy PROC USES eax ecx esi edi,
source:PTR BYTE, ; source string
target:PTR BYTE ; target string
;将字符串从源串复制到目的串。
;要求:目标串必须有足够空间容纳从源复制来的串。
;--------------------------------------
mov ecx,len
mov esi, source
mov edi, target
cld ;
rep movsb
ret
Strcopy ENDP

;--------------------------------------------------------
end start
;--------------------------------------------------------

strtrim 删除末尾特定的字符

就一个字符串,假设末尾有字符"#",然后我们就把它删除

这个函数的巧妙之处就是它是从后面开始遍历的,而不是从头开始

include   Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "Dqx##",0
len = lengthof A1
B1 db len dup (0)
;--------------------------------------------------------
;function
;--------------------------------------------------------
Strtrim proto source:PTR BYTE, : BYTE
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:

invoke Strtrim ,addr A1,"#"
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------
;Str_trim
;从字符串末尾删除所有与给定分隔符匹配的字符。
;返回:无
;-------------------------------------
Strtrim PROC USES eax ecx edi,
pStr:PTR BYTE,
char: BYTE
mov edi,pStr
mov eax,len-1 ;我们用len计算的长度是+上了最后一个字节了的
cmp eax,0 ;长度为0,就结束这里的bug就是长度为0,0-1=无符号的大数
je over
mov ecx, eax
dec eax
add edi,eax ;让inde指向最后一个
check_continue:
mov al, [edi]
cmp al,char
jne get_it
dec edi
loop check_continue
get_it:
mov BYTE PTR [edi+1 ],0
over:
ret
Strtrim ENDP

;--------------------------------------------------------
end start
;--------------------------------------------------------

toupper 字符串大写话

include   Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "I_am_Dqx_Gh0st",0
len = lengthof A1
B1 db len dup (0)
;--------------------------------------------------------
;function
;--------------------------------------------------------
toupper proto source:PTR BYTE
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:

invoke toupper ,addr A1
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------
;Str_trim
;从字符串末尾删除所有与给定分隔符匹配的字符。
;返回:无
;-------------------------------------
;---------------------------------
;Str_ucase
;将空字节结束的字符串转换为大写字母。
;返回:无
;---------------------------------
toupper proc uses eax esi,
pStr:PTR BYTE
mov esi,pStr
work:
mov al, [esi] ;取字符
cmp al, 0 ;字符串是否结束?
je over ;是:退出
cmp al, 'a' ;小于"a" ?
jb next
cmp al, 'z' ;大于"z" ?
ja next
and byte ptr [esi], 11011111b ;转换字符
next:
inc esi ;下一个字符
jmp work
over: ret
toupper endp

;--------------------------------------------------------
end start
;--------------------------------------------------------

tolower 字符串小写化

include   Dqx.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
;--------------------------------------------------------
A1 db "I_am_Dqx_Gh0st",0
len = lengthof A1
B1 db len dup (0)
;--------------------------------------------------------
;function
;--------------------------------------------------------
toupper proto source:PTR BYTE
;--------------------------------------------------------
.code
;--------------------------------------------------------
start:

invoke toupper ,addr A1
invoke ExitProcess,NULL
;--------------------------------------------------------
;function
;--------------------------------------
;Str_trim
;从字符串末尾删除所有与给定分隔符匹配的字符。
;返回:无
;-------------------------------------
;---------------------------------
;Str_ucase
;将空字节结束的字符串转换为大写字母。
;返回:无
;---------------------------------
toupper proc uses eax esi,
pStr:PTR BYTE
mov esi,pStr
work:
mov al, [esi] ;取字符
cmp al, 0 ;字符串是否结束?
je over ;是:退出
cmp al, 'A' ;小于"a" ?
jb next
cmp al, 'Z' ;大于"z" ?
ja next
or byte ptr [esi], 00100000b ;转换字符
next:
inc esi ;下一个字符
jmp work
over: ret
toupper endp

;--------------------------------------------------------
end start
;--------------------------------------------------------

__asm 两数相加

#include<stdio.h>
#include<windows.h>
int main()
{
printf("hello\n");
int x,y,z;
scanf("%d %d",&x,&y);
__asm
{
mov eax,x
add eax,y
mov z,eax
}
printf("%d\n",z);
system("pause");
return 0;
}

这是一个简单的相加

__asm 数组求和

#include<stdio.h>
#include<windows.h>
int func(int*,int);
int main()
{
int arr[]={2,4,6,8,10};
int len=sizeof(arr)/sizeof(arr[0]);
int sum=0;
printf("wecome to my world\n");
sum=func(arr,len);
printf("%d\n",sum);
system("pause");
return 0;
}
int func(int *arr,int len)
{
int sum=0;
__asm
{
mov esi,arr
mov ecx,len
mov ebx,0
mov eax,0
flag:
add eax,[esi+ebx*4]
inc ebx
loop flag
mov sum,eax
}
return sum;
}

别看你在func函数里面只写了几句汇编,其实你的func函数还是有很多的东西的

好比寄存器的入栈,函数栈的开辟

__asm 10进制value->2进制字符串

#include<stdio.h>
#include<windows.h>
void func(int,char*);
int main()
{
char str[32]={0};
printf("wecome to my world\n");
func(52,str);
printf("Dec %d= Bin %s\n",52,str);
system("pause");
return 0;
}
void func(int value,char *string)
{
__asm
{
mov esi,string
mov eax,value
mov byte ptr [esi],'0'
//数值最高位是0,字符串最低位是0
bsr ecx,value
jz over
mov byte ptr[esi],'1'
//数值最高位是1,字符串最低位是1

flag:
bt value,0
adc byte ptr[esi+ecx],'0'
//把字符串从最高位往最低位初始化
//数值从最低位到最高位读取
shr value,1
loop flag
over:
}
}

关于bsr指令和bt指令,见目录寻找

取余运算,难看

C码

if ((c+1) % 8 == 0) 
printf(" ");

IDA码

add     rax, 1
and eax, 7 ; 这是一个取模的运输
test rax, rax ; rax=0,ZF=1
jnz short loc_401384

7的二进制00000111

当一个数是0,&7才会是0,其他情况都是非0

所以从0到111,就是一个循环

处理 输入/输出 溢出

; console2.asm

section .data
msg1 db "Hello, World!",10,0
msg2 db "Your turn (only a-z): ",0
msg3 db "You answered: ",0
inputlen equ 10 ;inputbuffer
NL db 0xa

section .bss
input resb inputlen+1 ;provide space for ending 0
section .text
global main
main:
push rbp
mov rbp,rsp
mov rdi, msg1 ; print first string
call prints

mov rdi, msg2 ; print second string, no NL
call prints

mov rdi, input ; address of inputbuffer
mov rsi, inputlen ; length of inputbuffer
call reads ; wait for input

mov rdi, msg3 ; print third string and add the input string
call prints

mov rdi, input ; print the inputbuffer
call prints

mov rdi,NL ; print NL
call prints

leave
ret
;----------------------------------------------------------
prints:
push rbp
mov rbp, rsp
push r12 ; callee saved

; Count characters
xor rdx, rdx ; length in rdx
mov r12, rdi
.lengthloop:
cmp byte [r12], 0
je .lengthfound
inc rdx
inc r12
jmp .lengthloop
.lengthfound: ; print the string, length in rdx
cmp rdx, 0 ; no string (0 length)
je .done
mov rsi,rdi ; rdi contains address of string
mov rax, 1 ; 1 = write
mov rdi, 1 ; 1 = stdout
syscall
.done:
pop r12
leave
ret
;----------------------------------------------------------
reads:
section .data
section .bss
.inputchar resb 1
section .text
push rbp
mov rbp, rsp
push r12 ; callee saved
push r13 ; callee saved
push r14 ; callee saved
mov r12, rdi ; address of stringbuffer
mov r13, rsi ; max length in r13
xor r14, r14 ; character counter
.readc:
mov rax, 0 ; read
mov rdi, 1 ; stdin
lea rsi, [.inputchar] ; address of input
mov rdx, 1 ; # of characters to read
syscall
mov al, [.inputchar] ; char is NL?
cmp al, byte[NL]
je .done ; NL end
cmp al, 97 ; lower than a?
jl .readc ; ignore it
cmp al, 122 ; higher than z?
jg .readc ; ignore it
inc r14 ; inc counter
cmp r14, r13
ja .readc ; buffer max reached, ignore
mov byte [r12], al ; safe the char in the buffer
inc r12 ; point to next char in buffer
jmp .readc
.done:
inc r12
mov byte [r12],0 ; add end 0 to stringbuffer
pop r14 ; callee saved
pop r13 ; callee saved
pop r12 ; callee saved
leave
ret

他的思路

它使用了一些寄存器来保存参数

用r12,r13来捕捉缓冲区的字符串长度,缓冲区的字符串地址

用r14来做字符串缓冲区的指针,满足条件就移动r14来填入输入的数据

关于输入,他把你的输入作为单个字符,传入一个char buff=0的缓冲区,然后检buff

如果buff是换行符就读取结束

如果读取是不满足条件的字符就读取下一个数据,字符串缓冲区的指针不发生变化,继续读取下一个字符

如果满足对应的字符,查看输入的字符串长度是否超过了,如果超过了,不移动指针,继续读取下一个字符

如果没有超过,就移动指针,往字符串地址里面天厨char 的buff

就是这样一个解决读取溢出的

读取是读取到一个一旦长度的buff区域

,读取溢出解决,输出就解决了

win32

helloword

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
c
;对于上面的lib,inc还不是很理解

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
.data
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
A_Title db 'My_First_Box !',0
A_Text db 'Hello, Dqx_Ghost !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke MessageBox, NULL, offset A_Text, offset A_Title, MB_OK
;传递了4个参数,NULL,标题,内容,MB_OK
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDA

public start
start proc near
push 0 ; 参数1
push offset Caption ; 参数2,标题"My_First_Box !"
push offset Text ; 参数3,内容"Hello, Dqx_Ghost !"
push 0 ; 参数4,hwnd????
call MessageBoxA
push 0 ; uExitCode
call ExitProcess
start endp

第一个窗口

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 3rd Edition>
; by 罗云彬, http://www.win32asm.com.cn
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; FirstWindow.asm
; 窗口程序的模板代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff FirstWindow.asm
; Link /subsystem:windows FirstWindow.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include gdi32.inc
includelib gdi32.lib
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ? ;用来接收资源模块的地址
hWinMain dd ? ;hWinMain会接受一个窗口的地址

.const
szClassName db 'MyClass',0
szCaptionMain db 'My first Window !',0
szText db 'Win32 Assembly, Simple and powerful !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam
local @stPs:PAINTSTRUCT
local @stRect:RECT
local @hDc

mov eax,uMsg
;********************************************************************
.if eax == WM_PAINT
invoke BeginPaint,hWnd,addr @stPs
mov @hDc,eax

invoke GetClientRect,hWnd,addr @stRect
invoke DrawText,@hDc,addr szText,-1,\
addr @stRect,\
DT_SINGLELINE or DT_CENTER or DT_VCENTER

invoke EndPaint,hWnd,addr @stPs
;********************************************************************
.elseif eax == WM_CLOSE
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
;********************************************************************
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
;********************************************************************
xor eax,eax
ret

_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
local @stWndClass:WNDCLASSEX
local @stMsg:MSG

;参数0,代表0号模块
invoke GetModuleHandle,NULL
mov hInstance,eax

;参数
;结构体起始地址
;sizeof(结构体)
;功能初始化结构体为0
invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
;********************************************************************
; 注册窗口类
;重要的参数
;hInstance
;@stWndClass.lpfnWndProc
;@stWndClass.lpszClassName
;********************************************************************
invoke LoadCursor,0,IDC_ARROW
mov @stWndClass.hCursor,eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize,sizeof WNDCLASSEX
mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc,offset _ProcWinMain
mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
mov @stWndClass.lpszClassName,offset szClassName

;参数
;一个已经初始化好的结构体
;功能:把已经准备好的结构体信息给zhuche3函数
invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;重要参数
;szClassName 这个参数好像不怎么重要,就是一个名字,或者是一个指针的名字
;hInstance
;********************************************************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW,\
100,100,600,400,\
NULL,NULL,hInstance,NULL
mov hWinMain,eax
;重要的参数是hWinMain
;参数是窗口的地址
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
.while TRUE
;参数 @stMsg,
;函数的功能获取信息然后初始化@stMsg
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
ret

_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
call _WinMain
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start

第二个窗口

修改IP的方法?

call 一个位置

然后 jmp到一个位置

最后 ret xx

这就让ip发生了鬼鬼祟祟的变化

这就是回调函数?

asm->avx

打印16进制

.text:000000000040137C print_xmm       proc near               ; CODE XREF: apply_mxcsr+50↑p
.text:000000000040137C push rbp
.text:000000000040137D mov rbp, rsp
.text:0000000000401380 mov rdi, offset hex ; "0x"
.text:000000000040138A call _printf
.text:000000000040138F mov ecx, 8 ;它只会打印8字节,默认的
.text:0000000000401394
.text:0000000000401394 print_xmm_loop: ; CODE XREF: print_xmm+29↓j
.text:0000000000401394 xor rdi, rdi
.text:0000000000401397 mov dil, [rcx+404143h]
.text:000000000040139E push rcx
.text:000000000040139F call print_hex ;一个字节字节的打印
.text:00000000004013A4 pop rcx
.text:00000000004013A5 loop print_xmm_loop
.text:00000000004013A7 leave
.text:00000000004013A8 retn
.text:00000000004013A8 print_xmm endp

.text:000000000040142B print_hex       proc near               ; CODE XREF: print_xmm+23↑p
.text:000000000040142B
.text:000000000040142B var_4 = byte ptr -4
.text:000000000040142B
.text:000000000040142B ; __unwind {
.text:000000000040142B endbr64
.text:000000000040142F push rbp
.text:0000000000401430 mov rbp, rsp
.text:0000000000401433 sub rsp, 10h

.text:0000000000401437 mov eax, edi
.text:0000000000401439 mov [rbp+var_4], al
.text:000000000040143C cmp [rbp+var_4], 15
.text:0000000000401440 ja short loc_40144C
.text:0000000000401442 mov edi, 30h ; '0' ; c
.text:0000000000401447 call _putchar
.text:000000000040144C
.text:000000000040144C loc_40144C: ; CODE XREF: print_hex+15↑j
.text:000000000040144C movzx eax, [rbp+var_4]
.text:0000000000401450 mov esi, eax
.text:0000000000401452 lea rdi, format ; "%x"
.text:0000000000401459 mov eax, 0
.text:000000000040145E call _printf
.text:0000000000401463 nop
.text:0000000000401464 leave
.text:0000000000401465 retn
.text:0000000000401465 ; } // starts at 40142B
.text:0000000000401465 print_hex endp

打印二进制

.text:00000000004013A9 print_mxcsr     proc near               ; CODE XREF: apply_mxcsr+6C↑p
.text:00000000004013A9 ; apply_mxcsr+88↑p
.text:00000000004013A9
.text:00000000004013A9 var_18 = qword ptr -18h
.text:00000000004013A9 var_10 = qword ptr -10h
.text:00000000004013A9 var_8 = qword ptr -8
.text:00000000004013A9
.text:00000000004013A9 ; __unwind {
.text:00000000004013A9 endbr64
.text:00000000004013AD push rbp
.text:00000000004013AE mov rbp, rsp
.text:00000000004013B1 sub rsp, 20h
.text:00000000004013B5 mov [rbp+var_18], rdi
.text:00000000004013B9 mov [rbp+var_10], 0Fh
.text:00000000004013C1 jmp short loc_401417
.text:00000000004013C3 ; ---------------------------------------------------------------------------
.text:00000000004013C3
.text:00000000004013C3 loc_4013C3: ; CODE XREF: print_mxcsr+73↓j
.text:00000000004013C3 mov rax, [rbp+var_10]
.text:00000000004013C7 mov edx, eax
.text:00000000004013C9 mov rax, [rbp+var_18]
.text:00000000004013CD mov ecx, edx
;用于判断空格输出
.text:00000000004013CF sar rax, cl
.text:00000000004013D2 mov [rbp+var_8], rax
.text:00000000004013D6 mov rax, [rbp+var_10]
.text:00000000004013DA add rax, 1
.text:00000000004013DE and eax, 3
.text:00000000004013E1 test rax, rax
.text:00000000004013E4 jnz short loc_4013F0
.text:00000000004013E6 mov edi, 20h ; ' ' ; c
.text:00000000004013EB call _putchar
.text:00000000004013F0
.text:00000000004013F0 loc_4013F0: ; CODE XREF: print_mxcsr+3B↑j
.text:00000000004013F0 mov rax, [rbp+var_8]
.text:00000000004013F4 and eax, 1
.text:00000000004013F7 test rax, rax
.text:00000000004013FA jz short loc_401408
.text:00000000004013FC mov edi, 31h ; '1' ; c
.text:0000000000401401 call _putchar
.text:0000000000401406 jmp short loc_401412
.text:0000000000401408 ; ---------------------------------------------------------------------------
.text:0000000000401408
.text:0000000000401408 loc_401408: ; CODE XREF: print_mxcsr+51↑j
.text:0000000000401408 mov edi, 30h ; '0' ; c
.text:000000000040140D call _putchar
.text:0000000000401412
.text:0000000000401412 loc_401412: ; CODE XREF: print_mxcsr+5D↑j
.text:0000000000401412 sub [rbp+var_10], 1
.text:0000000000401417
.text:0000000000401417 loc_401417: ; CODE XREF: print_mxcsr+18↑j
.text:0000000000401417 cmp [rbp+var_10], 0
.text:000000000040141C jns short loc_4013C3
.text:000000000040141E mov edi, 0Ah ; 换行
.text:0000000000401423 call _putchar
.text:0000000000401428 nop
.text:0000000000401429 leave
.text:000000000040142A retn
.text:000000000040142A ; } // starts at 4013A9
.text:000000000040142A print_mxcsr endp

实验的收获

对于重复的代码,尽量少些,多去调用

循环遍历的方法

jcxz+jmp

假设字符串 最后以0来结尾

可能适用于你不知道字符串的长度,然后用0来表示标志位的结尾

push  si
push di
flag:
mov cx,ds:[si]
mov es:[di],cx
jcxz over
inc si
add di,2
over:
pop di
pop si
ret

loop与cx

可能适用于你知道cx的长度,然后去遍历它的字符串长度

push si
push di
mov cx,16
flag:
mov ax,ds:[si]
mov es:[di],ax
inc si
add di,2
loop flag
over:
pop di
pop si
ret

数据获取string dup (0)

对于

0000000000000000

然后填入数据

00000000002222222

获取数据时,我们常常jcxz一下

于是获取完毕

但是下一次获取的时候

数据89

00000000022222289

前面的(上一次的)数影响了

于是怎么办?

就每次填入数据的时候,就是初始化数据的时候,就多写一个标志0

0000022222222

第二次初始化

0000022222089

在89前面就有一个0作为标志位

程序死机的原因

你没有写

mov ax,4c00h
int 21h

关于该数据要用多少位的寄存器存放?

一个无符号的bit位,假设n位,其最大值就是​​2^n-1​

一个有符号的bit位,假设n位,其最大值就是​​2^(n-1)-1​

遗留的问题(浮点数)

#include <stdio.h>


int main(void)
{
double x=3.1415926;
char z='I';
int y=(int)x;
char ascii1='O';
puts("Good!");
return 0;
}

include Dqx.inc 
printf proto C :ptr sbyte,:vararg
scanf proto C :ptr sbyte,:vararg

.data
n_input byte "%d %d",0
n_output byte "%d+%d=%d",10,0
s_output BYTE "%s",10,0

str1 byte "welcome to my world",10
byte "please input 2 NUm",0

a dd 0
b dd 0

.code
start:
invoke printf,addr s_output,addr str1
invoke scanf,addr n_input,addr a,addr b
mov eax,0
add eax,a
add eax,b
invoke printf,addr n_output,a,b,eax



INVOKE ExitProcess,0;调用结束的函数,
;---------------------------------------------------------------------------
end start

输入2个数字,然后相加

welcome to my world
please input 2 NUm
10 20
10+20=30

windows下 C/C++学习-调试

x86

count++ % 3 == 0 吐了

汇编翻译吐了

/* count.c -- using standard I/O */
#include <stdio.h>
int main(int argc, char *argv[])
{
while ((ch = getc(in)) != EOF)
{
if (count++ % 3 == 0)
...
}
return 0;
}

//int版本
mov ecx, [esp+1Ch]
lea eax, [ecx+1]
mov [esp+28], eax
mov edx, 55555556h
mov eax, ecx
imul edx
mov eax, ecx
sar eax, 31
sub edx, eax
mov eax, edx
mov edx, eax
add edx, edx
add edx, eax
mov eax, ecx
sub eax, edx
test eax, eax
jnz short loc_4015A9
//byte版本
movzx ecx, byte ptr [esp+31]
mov eax, ecx
add eax, 1
mov [esp+31], al
movsx ax, cl
imul eax, 86
shr ax, 8
mov edx, eax
mov eax, ecx
sar al, 7
sub edx, eax
mov eax, edx
mov edx, eax
add edx, edx
add edx, eax
mov eax, ecx
sub eax, edx
test al, al
jnz short loc_4015AD

它的意思是

#include <stdio.h>
//byte版本
int main(int argc, char *argv[])
{
int result=0;//eax
int count=6;//ecx, byte ptr [esp+31]
result=count-3*((count*86)>>8-(count>>7));
return 0;
}
//int版本
/*
int result=0;
int count=10;
result-=count-3*(count*0x55555556>>32-count>>31);
*/

我还是看不懂

ds:__iob_func

​__iob_func()​​​返回一个指向​​FILE​​描述符数组的指针,

该数组包含​​stdin​​​、​​stdout​​​和通过 C 运行时库打开的​​stderr​​任何对象。

RTC_CheckEsp

参数 ZF寄存器

它主要分布在一些函数调用之间,

也就是函数前mov esi,ebp

函数结束后就cmp esi,ebp

验证​​esp​​​, stack, register 正确性的调用。调用它以确保​​esp​​在函数调用中保存 的值。

0044EE35  mov         esi,esp
.....一些操作
......
0044EE4D cmp esi,esp
0044EE4F call @ILT+6745(__RTC_CheckEsp) (42BA5Eh)

在那一顿操作过后,他就检测栈是否还原

就是比较esi与esp

关于函数__RTC_CheckEsp

_RTC_CheckEsp:
00475A60 jne esperror (475A63h)
00475A62 ret //如果相等就返回
//不相等就死了
esperror:
00475A63 push ebp
00475A64 mov ebp,esp
00475A66 sub esp,0
00475A69 push eax
00475A6A push edx
00475A6B push ebx
00475A6C push esi
00475A6D push edi
00475A6E mov eax,dword ptr [ebp+4]
00475A71 push 0
00475A73 push eax
00475A74 call _RTC_Failure (42C34Bh)
00475A79 add esp,8
00475A7C pop edi
00475A7D pop esi
00475A7E pop ebx
00475A7F pop edx
00475A80 pop eax
00475A81 mov esp,ebp
00475A83 pop ebp
00475A84 ret

@__security_check_cookie

基于Cookie的缓冲区溢出安全检查!

这个Cookie的变量位于函数体内的局部变量和EBP的存放地址之间,具体表示就是:[EBP-4]。

__security_init_cookie()这个函数对__security_cookie变量再次初始化

//获取cooike变量
//每个函数的ebp保持不变且不同,于是coike与ebp异或
//把cooike压入ebp-4
mov eax,dword ptr [___security_cookie (4B7A74h)]
xor eax,ebp
mov dword ptr [ebp-4],eax

关于cooike在栈中的位置

就是最开始压入EBP后ESP的值,

ebp减4就刚好挨着一进函数时压入的EBP的地址减4。好了!Cookie变量已经在栈帧中了。

.text:00991010 push    ebp          //这里是ebp位置
.text:00991011 mov ebp, esp
.text:00991013 sub esp, 13Ch //esp的开辟

00F5FBA0  66DDC4AD                //cooike
00F5FBA4 00F5FBF4 Stack[000022C4]:00F5FBF4 //ebp
00F5FBA8 0099170F ___tmainCRTStartup+1BF

与EBP异或当然有好处。

  1. 可以增加随机性,尽可能使不同函数的安全Cookie都不同。
  2. 可以检查EBP是否被破坏,因为在函数结束检查Cookie时,还会将Cookie变量值再次与EBP异或,如果EBP的值没有变化,那么就能恢 复成原来的___security_cookie值。

一系列过程允许后又回到

//取出cokkie变量
//还原cookie变量
//检查是否和原来一样
mov ecx,dword ptr [ebp-4]
xor ecx,ebp
call @ILT+920(@__security_check_cookie@4) (43739Dh)

好像函数@__security_check_cookie@4采用了某个调用约定,所以的话ecx是第一个参数

.text:00991220 ; void __fastcall __security_check_cookie(uintptr_t StackCookie)  //某个调用约定
.text:00991220 @__security_check_cookie@4 proc near ; CODE XREF: _main_0+153↑p
.text:00991220 ; failwithmessage(void *,int,int,char const *)+208↓p ...
.text:00991220 cmp ecx, ___security_cookie //把参数于原始的cooike做一个比较
.text:00991226 jnz short $failure$26820 //不相等就死翘翘
.text:00991228 rep retn
.text:0099122A ; ---------------------------------------------------------------------------
.text:0099122A
.text:0099122A $failure$26820: ; CODE XREF: __security_check_cookie(x)+6↑j
.text:0099122A jmp ___report_gsfailure //到这里来了后就处理cooike不正常的处理
.text:0099122A @__security_check_cookie@4 endp

@_RTC_CheckStackVar

顾名思义,就是用来检查局部数据是否访问越界。

相对来说,

这种检查只能起到一定的作用,并不会所有越界访问都能检查到。

既然是检查局部的,那么在函数内定义的static类型数组或者函数外部的全局数组并不会采用此检查,

既然是检查数组,那么如果函数内没有局部数组时,此检查也不会存在。

背景

在VS的debug版本下,局部变量之间并不是连续存放在栈内存里的

而是以4字节对齐的方式,前后都会有保护字节的。保护字节占4个字节,可能是值为0xcc,很明显这是汇编指令int 3中断的代码字节

原始代码void __fastcall _RTC_CheckStackVars( void *_Esp, _RTC_framedesc *_Fd );

参数

ecx 原来函数的ebp

edx 一个fd结构体指针

参数传递

.text:0099114B mov     ecx, ebp    //参数1                     ; Esp
.text:0099114D push eax
.text:0099114E lea edx, Fd //参数2
.text:00991154 call @_RTC_CheckStackVars@8

关于fd结构体类型

typedef struct _RTC_framedesc

{
int varCount; // 要检查的数组的个数
_RTC_vardesc *variables; // 要检查的数组的相关信息

} _RTC_framedesc;

关于嵌套的结构体 _RTC_vardesc

typedef struct _RTC_vardesc

{
int addr; // 数组的首地址相对于EBP的偏移量
int size; // 数组的大小字节数
char *name; // 数组的名字
} _RTC_vardesc;

起反汇编代码

也不是很难.....

瞎看C代码--基于vs2008

typedef struct _RTC_vardesc 
{
int addr;
int size;
char* name;
} _RTC_vardesc;

typedef struct _RTC_framedesc
{
int varCount;
_RTC_vardesc* variables; //结构体指针
} _RTC_framedesc;

void __fastcall _RTC_CheckStackVars( void* _Esp, _RTC_framedesc* _Fd ) //ecx,edx
{
if ( _Fd->varCount == 0 )
return;
//如果数组个数是0,就不检测

int _RetAddr = 0;
__asm
{
mov eax, ebp
add eax, 4
mov _RetAddr, eax // 保存返回地址
}

int i = 0;
while ( i < _Fd->varCount ) //i<数组的个数
{
char* pAddr = ( char* )_Esp + _Fd->variables[i].addr - 4;
if ( *( int* )pAddr != 0xcccccccc )
__asm int 3 // 引发中断

int ofs = _Fd->variables[i].addr + _Fd->variables[i].size;
pAddr = ( char* )_Esp + ofs;

if ( *( int* )pAddr != 0xcccccccc )
__asm int 3 // 引发中断
++i;
}
}

vs2010的IDA伪代码

void __fastcall _RTC_CheckStackVars(void *Esp, _RTC_framedesc *Fd)
{
int v2; // edi
_RTC_vardesc *variables; // eax
int addr; // ecx
int i; // [esp+Ch] [ebp-4h]
void *retaddr; // [esp+14h] [ebp+4h]

v2 = 0;
for ( i = 0; i < Fd->varCount; ++i )
{
variables = Fd->variables; //包含数组信息的结构体
addr = variables[v2].addr; //[index]小结构体的指针
//检测对应数组2边的保护字节????0xcc
if ( *(_DWORD *)((char *)Esp + addr - 4) != 0xCCCCCCCC
|| *(_DWORD *)((char *)Esp + addr + variables[v2].size) != 0xCCCCCCCC )
{
_RTC_StackFailure(retaddr, variables[v2].name);
}
++v2;
}
}

vs2008的汇编

.text:00991260 ; void __fastcall _RTC_CheckStackVars(void *Esp, _RTC_framedesc *Fd)
.text:00991260 @_RTC_CheckStackVars@8 proc near ; CODE XREF: _main_0+144↑p
.text:00991260
.text:00991260 var_4= dword ptr -4
.text:00991260
.text:00991260 mov edi, edi

.text:00991262 push ebp
.text:00991263 mov ebp, esp

.text:00991265 push ecx
.text:00991266 push ebx
.text:00991267 push esi
.text:00991268 push edi

.text:00991269 xor edi, edi
.text:0099126B mov esi, edx // 将_RTC_framedesc结构指针赋值给esi
.text:0099126D mov ebx, ecx // 将函数的栈帧赋值给ebx
.text:0099126F mov [ebp+var_4], edi // 这里的i应该是循环变量,后面根据数组的个数循环检测

.text:00991272 cmp [esi], edi //cmp fd,0 比较数值个数是否为0

.text:00991274 jle short loc_9912BE //没有数组,万事大吉,通过检测
.text:00991276 jmp short loc_991280 //进一步检测
.text:00991276 ; ---------------------------------------------------------------------------
.text:00991278 align 10h
.text:00991280
.text:00991280 loc_991280: ; CODE XREF: _RTC_CheckStackVars(x,x)+16↑j
.text:00991280 ; _RTC_CheckStackVars(x,x)+5C↓j
.text:00991280 mov eax, [esi+4] // +4之后就是_RTC_framedesc.variables指针
.text:00991283 mov ecx, [eax+edi] // _RTC_vardesc->addr了,就是数组的首地址相对于TestVars的EBP的偏
.text:00991286 cmp dword ptr [ecx+ebx-4], 0CCCCCCCCh // 如果不等于0xcccccccc就报错_RTC_StackFailure
.text:0099128E jnz short loc_99129F
.text:00991290 mov edx, [eax+edi+4]
.text:00991294 add edx, ecx
.text:00991296 cmp dword ptr [edx+ebx], 0CCCCCCCCh
.text:0099129D jz short loc_9912B0
.text:0099129F
.text:0099129F loc_99129F: ; CODE XREF: _RTC_CheckStackVars(x,x)+2E↑j
.text:0099129F mov ecx, [eax+edi+8] //即是_RTC_vardesc->name,用于报错提示
.text:009912A3 mov edx, [ebp+4]
.text:009912A6 push ecx ; char * // 传入越界的数组名
.text:009912A7 push edx ; void *
// 传入edx=EBP+4的地址,此地址正是_RTC_CheckStackVars的返回地址,用于定位
.text:009912A8 call ?_RTC_StackFailure@@YAXPAXPBD@Z ; _RTC_StackFailure(void *,char const *)
// 调用此函数后,弹出异常MessageBox,提示哪个数组越界
.text:009912AD add esp, 8
.text:009912B0
.text:009912B0 loc_9912B0: ; CODE XREF: _RTC_CheckStackVars(x,x)+3D↑j
.text:009912B0 mov eax, [ebp+var_4] // 存在多个数组需要检查时有用
.text:009912B3 inc eax
.text:009912B4 add edi, 0Ch // 定位到下一个_RTC_vardesc结构
.text:009912B7 mov [ebp+var_4], eax
.text:009912BA cmp eax, [esi]
.text:009912BC jl short loc_991280
.text:009912BE

.text:009912BE loc_9912BE: ; CODE XREF: _RTC_CheckStackVars(x,x)+14↑j
.text:009912BE pop edi
.text:009912BF pop esi
.text:009912C0 pop ebx
.text:009912C1 mov esp, ebp
.text:009912C3 pop ebp
.text:009912C4 retn
.text:009912C4 @_RTC_CheckStackVars@8 endp

基于数组缓冲区溢出修改ip

#include<stdio.h>

void HelloWord()
{
printf("Hello World");
}
void Fun()
{
int arr[5] = {1,2,3,4,5};
arr[6] = (int)HelloWord;
}
int main()
{
Fun();
}
//运行环境 devc++ x86 debug
//代码可以打印出"Hello World"

数组只有5个,强行写了6个

数值最后一个元素在[ebp-4]

强行写的arr[6],超出来2个

arr[5]已经超出来,arr[5]是ebp,ebp里面存放了原来的ebp

arr[6]已经超出来,arr[6]是ebp-4,ebp-4存放了push 进去的ip 现在ip被修改为了一个函数的地址

于是就可以运行

push    ebp
mov ebp, esp
sub esp, 20h
mov dword ptr [ebp-20], 1
mov dword ptr [ebp-16], 2
mov dword ptr [ebp-12], 3
mov dword ptr [ebp-8], 4
mov dword ptr [ebp-4], 5
mov eax, offset __Z9HelloWordv ; HelloWord(void)
mov [ebp+4], eax
leave
retn

缓冲区溢出修改其它变量

用不休止的hello world

#include<stdio.h>
//exe会 用不休止的hello world
void Fun()
{
int i;
int arr[5] = {0};
for(i=0;i<=5;i++)
{
arr[i] = 0;
puts("Hello World!...");
}
}
int main()
{
Fun();
}

push    ebp
mov ebp, esp
sub esp, 38h
mov dword ptr [ebp-32], 0
mov dword ptr [ebp-28], 0
mov dword ptr [ebp-24], 0
mov dword ptr [ebp-20], 0
mov dword ptr [ebp-16], 0
mov dword ptr [ebp-12], 0
jmp short loc_40154D
; ---------------------------------------------------------------------------

loc_401532: ; CODE XREF: Fun(void)+51↓j
mov eax, [ebp-12]
mov dword ptr [ebp+eax*4-32], 0
mov dword ptr [esp], offset Buffer ; "Hello World!..."
call _puts
add [ebp+i], 1

loc_40154D: ; CODE XREF: Fun(void)+30↑j
cmp [ebp+i], 5
jle short loc_401532
leave
retn

通过arr数组缓冲区溢出,修改了变量i,最后又再一次为0

gets内存越界

内存越界

#include <stdio.h>

int main( )
{
char str[5];
char a;

printf( "Enter a value :");
fgets( str,5,stdin );
//gets(str);

printf( "result: \n");
puts( str );
printf("a: %c \n",a);
return 0;
}

输出

Enter a value :123456789
result:
1234
a: x

确定哪个call是主要的call

比如

int* ptr=(int*)calloc(1024,sizeof(int));

跟进函数calloc

然后calloc函数里面可能还会有很多的call

于是你就看哪个call对eax,或者ptr做了修改,那么那个call就可能是主要的call

x64

printf

参数1是rcx

参数2是rdx

参数3是r8f

参数4是r9d

举报

相关推荐

0 条评论