Win32汇编:基础知识入门

汇编语言是所有程序设计语言中最古老的,它与计算机机器语言最为接近,通过汇编语言可以直接访问计算机的硬件,能够直接与CPU对话,可以说汇编语言是所有编程语言中语法格式最自由的,但自由的代价就是需要了解计算机体系结构和操作系统的大量细节,每编写一段程序都需要考虑各种硬件的状态,从而导致使用汇编写程序效率非常低.

自1946年第一台计算机问世以来,在短短的60多年中,已经历了由电子管计算机(1946年),晶体管计算机(1956年),集成电路计算机(1958年),超大规模集成电路计算机(1972年),这五代的更替,而且还在不断地向巨型化,微型化,网络化,智能化这四个方向不断发展.

从当今的X86架构的CPU说起,X86指令集是Intel为其第一块16位CPU(80x86)专门开发的,IBM公司1981年推出的世界第一台PC机中的CPU—i8088(i8086简化版)使用的也是X86指令,同时电脑中为提高浮点数据处理能力而增加的X87芯片系列协处理器则另外使用X87指令,为了提高处理器性能,就将X86指令集和X87指令集统称为X86指令集.

虽然随着CPU技术的不断发展,Intel公司陆续研制出更新型的i80386、i80486、Pentium直到今天,但为了保证电脑能继续运行以往开发的各类应用程序以保护和继承丰富的软件资源,所以Intel公司所生产的所有CPU仍然继续使用X86指令集,所以它的CPU仍属于X86系列,由于X86系列及其兼容CPU都使用X86指令集,所以就形成了今天庞大的X86系列及兼容CPU阵容.

谈完了处理器的基本发展过程,再来了解一下CPU指令集的分类吧.

处理器分为两大架构阵营,即RISC(精简指令集计算机)CISC(复杂指令集计算机)是当前CPU的两种架构,它们的区别在于不同的CPU设计理念和方法,CPU架构是厂商给属于同一系列的CPU产品定的一个规范,主要目的是为了区分不同类型CPU的重要标示.

早期的CPU全部是CISC架构,它的设计目的是要用最少的机器语言指令来完成所需的计算任务.比如对于乘法运算,在CISC架构的CPU上,您可能只需要一条指令就可以得到相应的结果,这些幕后的操作全部依赖于CPU中设计的逻辑电路来完成,这种架构会增加CPU结构的复杂性和对CPU制作工艺的要求,但对于编译器的开发却十分有利.

相比CISC架构的系统,RISC架构则要求软件来指定各个操作步骤,上面的乘法运算如果要在RISC架构上实现,则你需要具体指定其特定的实现步骤,使用这种架构生产CPU,可以很大程度上降低CPU的复杂性以及允许在同样的工艺水平下生产出功能更强大的CPU,但对于编译器的设计有更高的要求.

总结:当精简指令集出现后,所有人都说复杂指令集已经过时,英特尔密切关注,为了谨慎.英特尔同时开发复杂指令集CPU和精简指令集CPU.精简指令处理器上市后,复杂指令集CPU依旧热销.而精简指令集CPU因为无法兼容以前的软件,而销售量不好.英特尔得出复杂指令集生命依旧强大的结论,放弃在精简指令集方面的开发工作.

80x86处理器的几种基本工作模式

IA-32处理器有三种基本的工作模式:实地址模式,系统管理模式,保护模式,另外还有一种模式称为虚拟80x86模式,其实虚拟x86模式也是保护模式的一个特例,下面个将分别简要描述这几种系统模式:

实地址模式: 在该模式下,IA-32处理器使用20位地址线,可以访问1048576(1MB)字节的内存,其地址范围是0-FFFFF,但8086处理器的寄存器是16位的不能存放20位的地址,为了解决这个棘手的问题提出了一种称为分段内存的概念,所有内存被分为了多个64kb的区域,这些区域称为段(segment),我们使用段地址x16+偏移地址=绝对地址来计算出绝对地址.

保护模式: 在该模式下,每个程序可寻址4GB的内存,地址范围是0-FFFFFFFF,在该模式下编程无需进行复杂的公式计算,只需要使用一个32位整数就可以存放任何指令和变量的地址,处理器会在后台进行地址的计算和转换,这些工作对于汇编程序员变得透明了起来,保护模式下有三个段:CS:代码段,DS:数据段,SS:堆栈段,其他的段操作系统负责维护.

虚拟x86模式: 在该模式下,实际上是处理器在保护模式下创建的一个具有1MB地址空间的虚拟机,虚拟机对运行于实地址模式下的80x86计算机进行了模拟,在Windows NT系统下,打开一个控制台窗口,就创建了一个8086虚拟机,当然你也可同时打开多个控制台,他们之间是隔离的并不互相影响.

平坦分段模式: 在该模式下,所有段都被映射到32位的物理地址空间中,一个程序至少需要2个段:代码段(CS,数据段(DS),每个段都由一个段描述符定义,段描述符通常是一个存放在全局描述符表(GDT)中的一个64位地址.

内存分页机制: IA-32处理器支持一种称为分页(paging)的特性,允许一个段被分割成称为页(page)的4096字节的内存块,分页机制允许同时运行的程序使用总内存远大于计算机的物理内存,操作系统映射的所有页的集合称为虚拟内存,操作系统通常都会包含一个虚拟内存管理器的程序,分页机制会使人产生内存无限大的错觉,然而程序如果过度依赖于分页的话,其运行效率会非常低下.


CPU内部的寄存器组,以及每个寄存器的作用

寄存器是CPU内部的高速存储单元,由于是固化在CPU内部的组件,其访问速度快于内存,在当下的处理器中寄存器分为几种类型,其中8个通用寄存器(EAX,EBX,ECX,EDX,EBP,ESP,ESI,EDI),6个段寄存器(CS,SS,DS,ES,FS,GS),一个处理器状态标志寄存器(EFLAGS),和一个指令指针寄存器(EIP)寄存器.

通用寄存器: CPU内部有8个通用寄存器主要用于算数运算和数据的传送,这8个寄存器都可以作为一个32位的值或两个16位的值来寻址使用,还可以按照8位寄存器来使用,比如通用寄存器都可以被拆分为高低寄存器来存储数据,例如:EAX寄存器,可被拆分为(AX)16位寄存器来使用,而(AX)16位寄存器还可拆分为AH/AL(高低8位).

变址寄存器: CPU内部有2个通用寄存器ESI和EDI,寄存器ESI、EDI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量,用它可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便.变址寄存器不可分割成8位寄存器,在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特殊的功能,该寄存器默认和DS数据段寄存器相关联.

堆栈指针寄存器: CPU内部有2个通用寄存器EBP和ESP,寄存器EBP、ESP称为指针寄存器(Pointer Register),主要用于存放堆栈内存储单元的偏移量,它们主要用于访问堆栈内的存储单元并且规定,EBP为基址指针寄存器,ESP为堆栈指针寄存器,指针寄存器不可分割成8位寄存器,该寄存器默认和SS堆栈段寄存器相关联.

指令指针寄存器: CPU内部有1个指令指针寄存器EIP,该寄存器存放下一条要执行的指令的地址,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况,所以在理解它们的功能时,不考虑存在指令队列的情况,默认情况下EIP不可手动修改,一般都是由特殊的指令CALL,RET,PUSH等间接性的修改.

段寄存器: 段寄存器是根据内存分段的管理模式而设置的,内存单元的物理地址由段寄存器的值和一个偏移量组合而成的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址,常规段寄存器包括CS:代码段寄存器,DS:数据段寄存器,SS:堆栈段寄存器,ES:附加数据段寄存器这些寄存器通常是由编译器或这是操作系统来维护的.

标志寄存器: 标志寄存器(EFLAGS),该寄存器用来控制CPU的操作流程,或者反应CPU某些运算的结果的独立二进制位构成,常用的标志位包括CF(进位标志),ZF(零标志),PF(奇偶标志)等.


手动编译一段小程序

.386p
	.model flat,stdcall
	option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

MyDef equ 1024         ; 将数值指定名称

.data
	Main WORD 1024      ; 定义可赋值的变量
.data?                  ; 定义未知初始变量
	lyshark DWORD ?

.code
	main PROC
		xor eax,eax
		invoke ExitProcess,0
	main ENDP
END main

数据段的定义

MASM 定义了多种内部数据类型,每种数据类型都描述了该类型的变量和表达式的取值集合,汇编语言中数据类型的基本特征是以数据位数为度量单位:8,16,32,48,64,80位,而除此之外其他的特征如(符号,指针,浮点数)主要是为了方便我们记忆变量中存储的数据类型.

接下来看下表,表中是IEEE委员会发布的标准内部数据类型:

数据类型作用(无符号)数据类型作用(有符号)
BYTE8位无符号整数SBYTE8位有符号整数
WORD16位无符号整数SWORD16位有符号整数
DWORD32位无符号整数SWORD32位有符号整数
FWORD48位整数(远指针)QWORD64位整数定义
REAL432位(4字节)短实数REAL864位(8字节)长实数

数据类型定义语句为变量在内存中保留存储空间,并且可以选择为变量指定一个名字,在汇编语言中所有的数据无非就是BYTE的集合,数据的定义语句格式如下:

[变量名] 数据定义伪指令 初始值[....]

在数据定义语句中使用BYTE(定义字节)SBYTE(定义有符号字节)伪指令,可以为每一个或多个有符号或无符号字节分配存储空间,每个初始值必须是8位整数表达式或字符常量,例如下面的定义:

.data
	var1 BYTE 'A'      ; 定义字符常量
	var2 BYTE ?        ; 定义未初始化变量
	var3 BYTE 0        ; 最小的无符号字节常量
	var4 BYTE 255      ; 最大的无符号字节常量
	var5 SBYTE -128    ; 最小的有符号字节常量
	var6 SBYTE +127    ; 最大的有符号字节常量

如果一条数据定义语句中有多个初始值,那么标号仅仅代表第一个初始值的偏移,如下我们首先定义一个BYTE数组,然后通过反汇编查看地址的偏移变化就能看到效果啦:

.data
	list BYTE 10,20,30,40,50

00E71000 | B8 0030E700        | mov eax,main.E73000                 | E73000=10
00E71005 | B8 0130E700        | mov eax,main.E73001                 | E73001=20
00E7100A | B8 0230E700        | mov eax,main.E73002                 | E73002=30
00E7100F | B8 0330E700        | mov eax,main.E73003                 | E73003=40
00E71014 | B8 0430E700        | mov eax,main.E73004                 | E73004=50

并非所有的数据定义都需要标号,如果想继续定义以list开始的字节数组,可以在随后的行上接着上面的定义:

.data
	list BYTE 10,20,30,40,50
	list BYTE 60,70,80,90,100

当然除了定义整数字符以外,还可以定义字符串,要想定义字符串应将一组字符用单引号或双引号括起来.最常见的字符串是以空格结尾0h,在C/C++,JAVA中定义字符串无需添加结尾0h,这是因为编译器会在编译的时候自动的在字符串后面填充了0h,在汇编语言中我们需要手动添加字符串结尾的标志:

.data
	string1 BYTE "hello lyshark",0h
	string2 BYTE "good night",0h

00F23000  68 65 6C 6C 6F 20 6C 79 73 68 61 72 6B 00 67 6F hello lyshark.go 
00F23010  6F 64 20 6E 69 67 68 74 00 00 00 00 00 00 00 00 od night........

字符串也可以占用多行,而无须为每行都提供一个编号,如下代码也是合法的:

.data
	string1 BYTE "welcom to the Demo program"
			BYTE "created by lyshark",0dh,0ah,
			BYTE "url:lyshark"
			BYTE "send me a copy",0dh,0ah,0

十六进制0dh,0ah也称为CR/LF(回车换行符),或者是行结束的字符,在向标准输出设备上写的时候,回车换行符可以将光标移动到下一行的开头位置,从而继续填充新的字符串.

有时我们需要初始化一些空值的内存空间,在为内存地址分配空间的时候,DUP伪指令就显得尤为重要,初始化和未初始化数据均可使用DUP指令定义,其定义语法如下:

.data
	string1 BYTE 20 DUP(0)       ; 分配20字节,全部填充0
		BYTE 20 DUP(?)       ; 分配20字节,且未初始化
		BYTE 50 DUP("stack") ; 分配50字节,"stackstack..."

.data
	smallArray DOWRD 10 DUP(0) ; 分配40字节
	bigArray DOWOR 5000 DUP(?) ; 分配20000字节

除了上面的例子以外,我们也可以直接定义常量,常量是不可以动态修改的数据类型,一般情况下一旦定义,那么在程序运行期间不可以被修改,常量的定义很简单,只需要将.data换成.const即可.

.const
	var1 BYTE  "hello world",0h   ; 初始化为BYTE的字符串
	var2 DWORD 10                 ; 初始化为10的DWORD类型
	var3 DWORD 100 dup(1,2)       ; 200个DWORD的缓冲区
	var4 BYTE  1024 dup(?)        ; 1024字节的缓冲区
	var5 BYTE "welcome",0dh,0ah,0 ; 0dh,0ah为换行符

有时我们需要计算数组的大小,但手动计算显得特别麻烦,此时我们可以使用MASM提供的$符号来进行数组大小的计算过程,如下.

.data
	list BYTE 10,20,30,40,50
	listsize = ($ - list)       ; 计算字节数据大小
.data
	list WORD 1000h,2000h,3000h,4000h
	listsize = ($ - list) /2    ; 计算字数据大小
.data
	list DWORD 100000h,200000h,300000h,400000h
	listsize = ($ - list) /4    ; 计算双字数据大小
Post_1 equ 1000
Post_2 equ 2000
Post_3 equ 3000

标准输入输出

StdIn/StdOut: 使用masm32.inc提供的函数实现标准的输入与输出.

.386
	.model flat, stdcall
	
	include masm32.inc
	include kernel32.inc
	includelib masm32.lib
	includelib kernel32.lib

.data
	len equ 20
	OutText dw ?
	ShowText db "请输入一个数: ",0

.code
	main PROC
		invoke StdOut, addr ShowText    ; 输出提示信息
		invoke StdIn, addr OutText,len  ; 等待用户的输入
		invoke StdOut, addr OutText     ; 输出刚才输入的内容
		ret
	main ENDP
END main

WriteFile: 通过调用系统的API函数,来实现具体的输出,其过程比较复杂不推荐使用.

.386
	.model flat, stdcall
	
	include windows.inc
	include kernel32.inc
	includelib kernel32.lib
.data
	szText db "hello lyshark!",0
.data?
	hOut dd ?     ; 保存句柄
	hLen dd ?     ; 保存字符长度
.code
	main PROC
		invoke GetStdHandle,STD_OUTPUT_HANDLE     ; 获取设备控制台句柄
		mov hOut,eax                              ; 把获取到的句柄给hOut
		invoke lstrlen,addr szText                ; 取出字符串的长度
		mov hLen,eax
		invoke WriteFile,hOut,addr szText,hLen,0,0 ;具体的输出
		ret
	main ENDP
END main

crt_printf: 使用微软C标准库中的printf函数; msvscrt.inc 把它声明做 crt_printf

.386
	.model flat, stdcall
	
	include msvcrt.inc
	includelib msvcrt.lib
	
.data
	PrintText db "EAX=%d;EBX=%d;EDX=%d | InPut ->: ",0
	ScanFomat db "%s",0
	PrintTemp db ?
.code
	main PROC
		mov eax,10
		mov ebx,20
		mov ecx,30
		invoke crt_printf,addr PrintText,eax,ebx,ecx        ; 打印提示内容
		invoke crt_scanf, addr ScanFomat, addr PrintTemp    ; 输入内容并接收参数
		invoke crt_printf, addr PrintTemp                   ; 输出输入的内容
		ret
	main ENDP
END main

常用汇编指令

MOV指令: 从源操作数向目标操作数之间复制数据.

00A41000 | B8 24100000        | mov eax,1024                        |
00A41005 | 8BD8               | mov ebx,eax                         |
00A41007 | 66:B9 0010         | mov cx,1000                         |

MOVZX指令: 零扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值零扩展(zero-extend)至16位或者32位,该指令适用于无符号整数,其基本格式如下:

01301000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
01301004 | 0FB7C3             | movzx eax,bx                        | EAX = 0000A69B
01301007 | 0FB6D3             | movzx edx,bl                        | EDX = 0000009B
0130100A | 66:0FB6CB          | movzx cx,bl                         | CX = 009B

MOVSX指令: 符号扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值符号扩展(sign-extend)至16位或者是32位,该指令只能用于有符号整数,其基本格式如下:

00FD1000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
00FD1004 | 0FBFC3             | movsx eax,bx                        | EAX = FFFFA69B
00FD1007 | 0FBED3             | movsx edx,bl                        | EDX = FFFFFF0B
00FD100A | 66:0FBECB          | movsx cx,bl                         | CX = FF9B

XCHG指令: 数据交换指令,该指令用于交换两个操作数中的内容,但该指令不接受立即数操作数.

00D71000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00D71005 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
00D7100A | 93                 | xchg ebx,eax                        | EAX = 2000h;EBX = 1000h

INC/DEC指令: 数据递增与递减,INC指令用于对寄存器或内存数据的递增,DEC指令用于对寄存器或内存数据递减.

00881000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00881005 | 40                 | inc eax                             | EAX = 1001h
00881006 | 40                 | inc eax                             | EAX = 1002h
00881007 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
0088100C | 4B                 | dec ebx                             | EBX = 1FFFF
0088100D | 4B                 | dec ebx                             | EBX = 1FFFE
0088100E | 4B                 | dec ebx                             | EBX = 1FFFD

ADD指令: 操作数增加,该指令用于将源操作数和目的操作数相加,且不影响源操作数的值,而是改变目的操作数.

00BC1000 | B8 00100000        | mov eax,1000                        | EAX = 1000
00BC1005 | BB 00200000        | mov ebx,2000                        | EBX = 2000
00BC100A | 03D8               | add ebx,eax                         | EBX = EBX+EAX = 3000

SUB指令: 操作数减少,该指令用于将源操作数和目的操作数相减,且不影响源操作数的值,而是改变目的操作数.

00811000 | B8 00200000        | mov eax,2000                        | EAX = 2000
00811005 | BB 00100000        | mov ebx,1000                        | EBX = 1000
0081100A | 2BC3               | sub eax,ebx                         | EAX = EAX-EBX = 1000

AND/OR/XOR指令: 逻辑与/逻辑或/逻辑异或.

00DD100E | B8 01000000        | mov eax,1                              |
00DD1013 | BB 01000000        | mov ebx,1                              |
00DD1018 | B9 00000000        | mov ecx,0                              |
00DD101D | 21D8               | and eax,ebx                            |
00DD101F | 09CB               | or ebx,ecx                             |
00DD1021 | 31C0               | xor eax,eax                            |

OFFSET操作符: 返回数据标号的偏移地址,偏移地址代表标号距数据基址的距离,单位是字节.

.data
	var1 BYTE ?
	var2 WORD ?
	var3 DWORD ?
	var4 DWORD ?
.code
	main PROC
		mov esi,offset var1
		mov esi,offset var2
		mov esi,offset var3
		mov esi,offset var4
	main ENDP
END main

PTR操作符: 用来重载声明操作数的默认尺寸,这在试图以不同与变量声明时所使用的尺寸来访问变量时很有用.

.data
	temp DWORD 12345678h

.code
	main PROC
	mov eax,DWORD PTR [temp]  ; 将temp以双字取值并存储到eax
	mov ax,WORD PTR [temp]    ; 将temp以字为单位取值并存储到ax
	mov bx,WORD PTR [temp+2]  ; 在偏移基础上+2
	main ENDP
END main

00C11000 | A1 0030C100        | mov eax,dword ptr ds:[C13000]       | EAX = 12345678
00C11005 | 66:A1 0030C100     | mov ax,word ptr ds:[C13000]         | AX = 5678
00C1100B | 66:8B1D 0230C100   | mov bx,word ptr ds:[C13002]         | BX = 1234

LENGTHOF操作符: 计算数组元素的数目,元素由出现在的同一行的值定义.

.data
	ArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0h
	ArrayBT BYTE 1,2,3,4,5,6,7,8,9,0h
.code
	main PROC

		mov eax,lengthof ArrayDW
		mov eax,lengthof ArrayBT

		push 0
		call ExitProcess
	main ENDP
END main

TYPE操作符: 返回按照字节计算的单个元素的大小.

.data
	var1 BYTE ?
	var2 WORD ?
	var3 DWORD ?
	var4 QWORD ?
.code
	main PROC
		mov eax,TYPE var1       ; 1
		mov ebx,TYPE var2       ; 2
		mov ecx,TYPE var3       ; 4
		mov edx,TYPE var4       ; 8

		push 0
		call ExitProcess
	main ENDP
END main

SIZEOF操作符: 返回等于LENGTHOF(总元素数)和TYPE(每个元素占用字节)返回值的乘基.

.data
	var1 WORD 32 DUP(0)        ; 32*2
	var2 BYTE 10,20,30,40      ; 3
	var3 WORD 30 DUP(?),0,0    ; 30+2
	var4 DWORD 1,2,3,4         ; 4

.code
	main PROC
		mov eax,SIZEOF var1
		mov eax,SIZEOF var2
		mov eax,SIZEOF var3
		mov eax,SIZEOF var4
	main ENDP
END main

LOOP循环(普通循环): 该指令检测ECX寄存器的变化,每次循环寄存器自动减1,当ECX=0循环结束,否则继续循环.

.code
	main PROC
		mov ecx,10      ; 计数循环寄存器初始化为10
	top:                    ; 循环标号,编译器会将其转换成一个地址
		xor eax,eax
		mov eax,ecx
		loop top        ; loop跳转到指定地址,此处为top

		push 0
		call ExitProcess
	main ENDP
END main

LOOP循环(循环中使用ECX): 如果用光了所有的通用寄存器,但又必须要使用ECX的话,可以在循环开始将ECX保存.

.data
	count DWORD ?
.code
	main PROC

		mov ecx,10
	top:
		mov count,ecx       ; 将ecx寄存器放入count变量
		xor ecx,ecx
		mov ecx,1000        ; 重置ecx寄存器的数值
		add eax,ecx

		mov ecx,count       ; 处理完成后,恢复ECX寄存器
		loop top            ; 继续循环
		
		push 0
		call ExitProcess
	main ENDP
END main

LOOP循环(嵌套循环): 在循环内部创建另一个循环的时候,必须考虑外层ECX中的外层循环计数该如何处理,把外层循环计数保存在内存中,是非常的理想的.

.data
	count DWORD ?
.code
	main PROC

		mov ecx,10        ; 设置外层循环计数
	L1:
		mov count,ecx     ; 保存外层循环计数
			mov ecx,20    ; 设置内层循环计数
		L2:
			xor eax,eax
			xor ebx,ebx
			xor edx,edx
			loop L2      ; 重复内层循环计数

		mov ecx,count    ; 恢复外层循环计数器
		loop L1          ; 执行外层循环跳转
		push 0
		call ExitProcess
	main ENDP
END main

IF-ENDIF(伪指令):

.code
	main PROC
		mov eax,100
		mov ebx,200
		.IF (eax == ebx) && (ebx == ebx)
			xor eax,eax
			xor ebx,ebx
		.ELSEIF (eax >= 100) || (ebx == ebx)
			add eax,100
			add ebx,100
		.ENDIF
	main ENDP
END main

WHILE-ENDW(伪指令):

.data
	Count DWORD 10
	SumNum DWORD 0

.code
	main PROC
		xor eax,eax
		.WHILE (eax < Count)
			add SumNum,1
			inc eax
		.ENDW
	main ENDP
END main

REPEAT-UNTIL(伪指令): 以下代码利用循环伪指令,完成了1-10相加.

.data
	Count DWORD 10
	SumNum DWORD 0
.code
	main PROC
		xor eax,eax
		.REPEAT
			inc eax
			add SumNum,1
		.UNTIL (eax >= Count)
	main ENDP
END main

BREAK(伪指令): 以下是个死循环,当eax寄存器的值等于5时,则执行.break结束程序的运行.

.code
	main PROC
		mov eax,10
		.while (1)
			dec eax
			.break .if(eax == 5)
		.endw
		ret
	main ENDP
END main

CONTINUE(伪指令): 当EAX的值小于等于5时执行continue,否则执行inc ebx,总循环数为10.

.code
	main PROC
		mov eax,0
		mov ebx,0
		.repeat
			inc eax
			.continue .if(eax <= 5)
				inc ebx
		.until (eax >= 10)
		ret
	main ENDP
END main

FOR 字符替换(伪指令): 该伪指令并不是循环,而是分别将指定的指令批量的替换到程序中.

.code
	main PROC
		for num,<1,2,3>
			xor eax,eax
			add eax,DWORD PTR [num]
		endm
		ret
	main ENDP
END main

FORC字串替换(伪指令): 该伪指令并不是循环,而是分别将指定的字串批量的替换到程序中.

.code
	main PROC
		forc code,<@#$%^&*()<>>
			BYTE "&code"
		endm
		ret
	main ENDP
END main

内存寻址方式

Windows系统默认运行于保护模式下,当处理器运行于保护模式下时,每个程序可以寻址4GB的内存范围,地址范围是从十六进制数的0-FFFFFFFF,微软汇编器的平坦模式,适用于保护模式编程,在平坦模式下其内存寻址的方式包括,直接寻址,间接寻址,基址变址寻址,比例因子寻址等,接下来将分别来演示.

直接寻址

在声明变量名称的后面加上一个偏移地址,可以创建直接偏移(direct-offset)操作数,可以通过它来访问没有显示标号的内存地址,接下来看一个实验例子:

.data
	ArrayB BYTE 10h,20h,30h,40h,50h
	ArrayW WORD 100h,200h,300h,400h
	ArrayDW DWORD 1h,2h,3h,4h

.code
	main PROC
	; 针对字节的寻址操作
		mov al,[ArrayB]           ; al=10
		mov al,[ArrayB+1]         ; al=20
		mov al,[ArrayB+2]         ; al=30
	; 针对内存单元字存储操作
		mov bx,[ArrayW]           ; bx=100
		mov bx,[ArrayW+2]         ; bx=200
		mov bx,[ArrayW+4]         ; bx=300
	; 针对内存单元双字存储操作
		mov eax,[ArrayDW]         ; eax=00000001
		mov eax,[ArrayDW+4]       ; eax=00000002
		mov eax,[ArrayDW+8]       ; eax=00000003
	main ENDP
END main

间接寻址

在处理数组操作时完全使用直接寻址是不切实际的,我们不大可能为数组的每个元素都提供一个不同的标号,也不太可能使用非常多的常量偏移地址去寻址数组的各个元素,处理数组唯一可行的方法是用寄存器作为指针并操作寄存器的值,这种方法称为间接寻址(indirect addressing),操作数使用间接寻址时,就称为间接操作数(indirect operand).

通过ESI内存寻址: 通过使用ESI寄存器,外加偏移地址(此处DWORD=4字节),实现寻址.

.data
	ArrayDW DWORD 10000h,20000h,300000h
.code
	main PROC
		mov esi,offset ArrayDW  ; 获取数据段的内存基址
		mov eax,[esi]           ; 取出[esi]地址中的数据,并赋值给eax
		add esi,4               ; 每次esi指针加4,因为数据格式为DWORD
		mov eax,[esi]
		add esi,4
		mov eax,[esi]
	main ENDP
END main

通过ESP堆栈寻址: 通过ESP堆栈寄存器,实现寻址.

.code
	main PROC
		mov eax,100                ; eax=1
		mov ebx,200                ; ebx=2
		mov ecx,300                ; ecx=3
		push eax                   ; push 1
		push ebx                   ; push 2
		push ecx                   ; push 3

		mov edx,[esp + 8]          ; EDX = [ESP+8]=1
		mov edx,[esp + 4]          ; EDX = [ESP+4]=2 
		mov edx,[esp]              ; EDX = [ESP]=3
	main ENDP
END main

变址寻址

变址寻址,变址操作数(indexed operand)把常量和寄存器相加以得到一个有效地址,任何32位通用寄存器都可以作为变址寄存器,MASM允许使用两种不同的变址操作数据格式.

变量名+寄存器: 通过变量名和寄存器结合,变量名代表变量偏移地址的常量,通过变更ESI寄存器的值进行数据寻址.

.data
	ArrayDW DWORD 10000h,20000h,300000h
.code
	main PROC
		mov esi,0
		mov eax,[ArrayDW + esi]     ; 通过变量名+esi寄存器寻址

		mov ebx,8                   ; 增加8字节
		mov eax,[ArrayDW + ebx]     ; 定位第三个DW数据内存
	main ENDP
END main

基址+偏移: 通过把变址寄存器内存偏移常量结合,用寄存器存放数组基址,用常量标识各个数组元素.

.data
	ArrayW WORD 1000h,2000h,3000h,4000h
.code
	main PROC
		mov esi,offset ArrayW    ; 获取基址
		mov ax,[esi]             ; 显示第一个数据
		mov ax,[esi + 2]         ; 显示第二个数据
		mov ax,[esi + 4]         ; 最后一个
	main ENDP
END main

基址变址寻址: 通过计算公式,这里数组中每个元素占用4字节,所以需要乘以4,寄存器ECX为需要定位的元素偏移.

.data
	Array DWORD 1000h,2000h,3000h,4000h,0h
.code
	main PROC
		lea eax,Array
		mov ecx,2
		mov edx,DWORD PTR [eax + ecx * 4]      ;edx=3000h

		mov ecx,1
		mov edx,DWORD PTR [eax + ecx * 4]      ;edx=2000h
	main ENDP
END main

比例因子寻址: 通过使用比例因子,以下例子每个DWORD=4字节,且总元素下标=0-3,得出比例因子3* type arrayDW.

.data
	ArrayDW DWORD 1000h,2000h,3000h,4000h
.code
	main PROC

	; 第1种比例因子寻址
		mov esi,3*type ArrayDW        ;总共3个下标x每个元素的类型
		mov eax,ArrayDW[esi]
	; 第2种比例因子寻址
		mov esi,3                      ; 变更ESI下标,可实现定位不同的数据
		mov eax,ArrayDW[esi*4]         ; 其中4代表每个数据类型4字节
	; 第3种比例因子寻址
		mov esi,3
		mov eax,ArrayDW[esi*type ArrayDW]
	main ENDP
END main

指针寻址: 变量地址的变量称为指针变量(pointer variable),Intel处理器使用两种基本类型的指针,即near(近指针)far(远指针),保护模式下使用Near指针,所以它被存储在双字变量中.

.data
	ArrayB BYTE 10,20,30,40,50
	ArrayD DWORD 1,2,3,4,5

	ptrB DWORD OFFSET ArrayB    ; 指针ptrB --> ArrayB
	ptrD DWORD OFFSET ArrayD    ; 指针ptrD --> ArrayD
.code
	main PROC
	mov esi,ptrB      ; 指向数组ArrayB
	mov al,[esi]      ; 取出 10h
	mov esi,ptrD      ; 指向数组ArrayD
	mov eax,[esi]     ; 取出 1h
	main ENDP
END main

标志测试指令

在学习数据比较指令之前,需要先来了解一下标识寄存器这个东西,标志寄存器又称程序状态寄存器(Program Status Word,PSW),这是一个存放条件码标志,控制标志和系统标志的寄存器.

标志寄存器中存放的有条件标志,也有控制标志,它对于处理器的运行和整个过程的控制有着非常重要的作用.条件标志主要包括进位标志、奇偶标志、辅助进位标志、零标志、符号标志、溢出标志等,控制标志主要有跟踪标志,因为有标志寄存器的存在才能实现各种华丽的判断循环等,常用的标志有以下6个:

标志位标志全称标志序号标志位说明
CF(Carry Flag)进位标志位0当执行一个加法(或减法)运算,使最高位产生进位(或借位)时,CF为1;否则为0
PF(Parity Flag)奇偶标志位2当运算结果中,所有bit位(例:1001010)中1的个数为偶数时,则PF=1;为基数PF=0
AF(Auxiliary Flag)辅助进位标志4执行加法(减法)运算,结果的低4位向高4位有进位(借位)时,则AF=1;否则AF=0
ZF(Zero Flag)零标志位6若当前的运算结果为零,则ZF=1;否则ZF=0
SF(Sign Flag)符号标志位7若运算结果为负数,则SF=1;若为非负数则SF=0
TF(Trap Flag)陷阱标志位8为方便程序调试而设计的,TF=1单步执行指令,TF=0则CPU正常执行程序
IF(Interrupt)中断允许标志9当IF=1CPU可响应可屏蔽中断请求,当设置IF=0则CPU不响应可屏蔽中断请求
DF(Direction)方向标志位10当DF=0时为正向传送数据(cld),否则为逆向传送数据(std)
OF(Overflow)溢出标志位11记录是否产生了溢出,当补码运算有溢出时OF=1;否则OF=0

ZF零标志位: ZF标志相关指令执行后,结果为0则ZF=1;若结果不为0则ZF=0.

00C31000 | 90                   | nop                                              | ZF = 0
00C31001 | B8 01000000          | mov eax,1                                        | ZF = 0
00C31006 | 83E8 01              | sub eax,1                                        | ZF = 1

00C31000 | 90                   | nop                                              | ZF = 0
00C31001 | B8 02000000          | mov eax,2                                        | ZF = 0
00C31006 | 83E8 01              | sub eax,1                                        | ZF = 0

PF奇偶标志位: PF标志相关指令执行后,其结果所有bit位中的1若为偶数,则PF=1;若为奇数PF=0.

00C31000 | 90                   | nop                                              | PF = 0
00C31001 | B8 00000000          | mov eax,00000000                                 | PF = 0
00C31006 | 83C0 6F              | add eax,00000111                                 | PF = 1

00C31000 | 90                   | nop                                              | PF = 0
00C31001 | B8 00000000          | mov eax,00000000                                 | PF = 0
00C31006 | 83C0 6F              | add eax,00000011                                 | PF = 0

SF符号标志位: SF标志相关指令执行后,其结果是否为负,若为负则SF=1;若为非负SF=0.

00C3100B | 90                   | nop                                              | SF = 0
00C3100C | B8 E8030000          | mov eax,3E8                                      | SF = 0
00C31011 | 2D E9030000          | sub eax,3E9                                      | SF = 1

00C3100B | 90                   | nop                                              | SF = 0
00C3100C | B8 E8030000          | mov eax,3E8                                      | SF = 0
00C31011 | 2D E9030000          | sub eax,3E8                                      | SF = 0

CF进位标志位: CF标志相关指令执行后,在进行无符号运算时,如果表达式发生进位或借位则CF=1.

00C31016 | 90                   | nop                                              | CF = 0
00C31017 | 66:B8 FFFF           | mov ax,FFFF                                      | CF = 0
00C3101B | 66:83C0 01           | add ax,1                                         | CF = 1

00C31016 | 90                   | nop                                              | CF = 0
00C31017 | 66:B8 FFFF           | mov ax,FFFF                                      | CF = 0
00C3101B | 66:83C0 01           | sub ax,1                                         | CF = 0

OF溢出标志位: OF标志相关指令执行后,超出机器所能表示的范围称为溢出若发生了溢出OF=1;否则OF=0.

00C3101B | 90                   | nop                                              | OF = 0
00C3101C | B0 40                | mov al,64                                        | OF = 0
00C3101E | 04 40                | add al,64                                        | OF = 1

00C31020 | 90                   | nop                                              | OF = 0
00C31021 | B0 3F                | mov al,63                                        | OF = 0
00C31023 | 04 40                | add al,64                                        | OF = 0

TEST指令: 该操作与AND指令类似,唯一不同的是它不保存结果,常用来测试标志位状态.

00DD103B | B8 01000000        | mov eax,1                              | EAX = 1
00DD1040 | BB 00000000        | mov ebx,0                              | EBX = 0
00DD1045 | 85D8               | test eax,ebx                           | ZF = 1

00DD1051 | B8 01000000        | mov eax,1                              |
00DD1056 | A9 00000000        | test eax,0                             | ZF = 1
00DD105B | 83E0 00            | and eax,0                              | ZF = 1
00DD1062 | 83C8 01            | or eax,1                               | ZF = 0

CMP指令: 在源操作数和目标操作数进行减法操作,只影响标志位.

00DD1001 | B8 00010000        | mov eax,100                            | EAX = 100
00DD1006 | BB 50000000        | mov ebx,50                             | EBX = 50
00DD100B | 39D8               | cmp eax,ebx                            | eax - ebx
00DD100D | 0F87 EDFF62FF      | ja 401000                              | jump

条件跳转指令

注记符跳转条件描述信息
JZ/JEZF=1为零则跳转,(leftOp - rightOp = 0)
JNZ/JNEZF=0不为零则跳转,(leftOp - rightOp != 0)
JC/JNCCF=1/0设置进位标志则跳/未设置进位标志则跳
JO/JNOOF=1/0设置溢出标志则跳/未设置溢出标志则跳
JS/JNSSF=1/0设置符号标志则跳/未设置符号标志则跳
JP/JNPPF=1/0设置奇偶标志则跳(偶)/未设置奇偶标志则跳(基)
无符号模式有符号模式跳转条件描述信息
JAJG(left > right)大于则跳转
JAEJGE(left >= right)大于或等于则跳转
JBJL(left < right)小于则跳转
JBEJLE(left <= right)小于或等于则跳转

JZ/JE通用跳转: 检测到ZF=1也就说明表达式返回了0,则程序跳转,否则不跳转.

01031001 | B8 00010000        | mov eax,64                      | eax=100
01031006 | BB 00010000        | mov ebx,64                      | ebx=100
0103100B | 39D8               | cmp eax,ebx                     | eax-ebx 
0103100D | 0F84 EDFF3CFF      | je 401000                       | jump
01031013 | 0F84 E7FF3CFF      | jz 401000                       | jump

JNZ/JNE通用跳转: 检测到ZF=0也就说明表达式返回了1,则程序跳转,否则不跳转.

01031001 | B8 00010000        | mov eax,65                      | eax=101
01031006 | BB 00010000        | mov ebx,64                      | ebx=100
0103100B | 39D8               | cmp eax,ebx                     | eax-ebx 
0103100D | 0F84 EDFF3CFF      | jne 401000                      | not jump
01031013 | 0F84 E7FF3CFF      | jnz 401000                      | not jump

JA/JB无符号跳转: 基于无符号数的跳转指令,JA大于则跳转JB小于则跳转.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB C8000000        | mov ebx,C8                      | ebx=200
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F87 EDFF3CFF      | ja 401000                       | ebx>eax jump

0103100F | B8 64000000        | mov eax,64                      | eax=100
01031014 | BB 32000000        | mov ebx,32                      | ebx=50
01031019 | 3BD8               | cmp ebx,eax                     | ebx-eax
0103101B | 0F82 DFFF3CFF      | jb 401000                       | ebx<eax jump

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BC3               | cmp eax,ebx                     | eax-ebx
0103100D | 0F87 EDFF3CFF      | ja 401000                       | eax=ebx not jump
01031013 | 0F82 E7FF3CFF      | jb 401000                       | eax=ebx not jump

JAE/JBE无符号跳转: 基于无符号数的跳转指令,JAE大于等于则跳转JBE小于等于则跳转.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
01031010 | 3BC3               | cmp eax,ebx                     | eax-ebx
01031012 | 0F83 E8FF3CFF      | jae 401000                      | eax>=ebx jump

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB C8000000        | mov ebx,C8                      | ebx=200
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F83 EDFF3CFF      | jae 401000                      | ebx>=eax jump

01031001 | B8 C8000000        | mov eax,C8                      | eax=200
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F86 EDFF3CFF      | jbe 401000                      | ebx<=eax jump

JG/JL有符号跳转: 基于有符号数的跳转指令,JG大于则跳转JL小于则跳转.

01031001 | B0 7F              | mov al,7F                       | al=0x7F(+127)
01031003 | B3 80              | mov bl,80                       | bl=0x80(-128)
01031005 | 3AC3               | cmp al,bl                       | (+128)-(-127)
01031007 | 0F87 F3FF3CFF      | ja 401000                       | 不跳转,因为7Fh不大于80h
0103100D | 0F8F EDFF3CFF      | jg 401000                       | 跳转,因为(+128)大于(-127)

01031001 | B0 9C              | mov al,9C                       | al=(-100)
01031003 | B3 32              | mov bl,32                       | bl=(50)
01031005 | 3AC3               | cmp al,bl                       | (-100)-(50)
01031007 | 0F82 F3FF3CFF      | jb 401000                       | 不跳转,因为9ch不小于32h
0103100D | 0F8C EDFF3CFF      | jl 401000                       | 跳转,因为(-100)小于(32)

JGE/JLE有符号跳转: 基于有符号数的跳转指令,JGE大于等于则跳转JLE小于等于则跳转.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BC3               | cmp eax,ebx                     | eax-ebx
0103100D | 0F83 EDFF3CFF      | jae 401000                      | 跳转,无符号100=100
01031013 | 0F8D E7FF3CFF      | jge 401000                      | 跳转,有符号100=100

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 9CFFFFFF        | mov ebx,FFFFFF9C                | ebx=(-100)
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F8E EDFF3CFF      | jle 401000                      | 跳转,有符号数(-100)<(100)

JCXZ/JECXZ跳转指令: 检测ECX寄存器的值,如果等于零则执行跳转,否则跳过执行.

01031001 | B9 01000000        | mov ecx,1                       | ecx=1
01031006 | E3 F8              | jecxz <a.EntryPoint>            | not jump

0103100A | B9 00000000        | mov ecx,0                       | ecx=0
0103100F | E3 EF              | jecxz <a.EntryPoint>            | jump

移位相关指令

每种汇编语言都有进行操作数移位的指令,移位和循环移位指令在控制硬件设备,加密数据,以及实现高速图形运算时特别有用,移位指令也是汇编语言中最具特征的指令集,移位(Shifting)的含义是在操作数内向左或向右移动数据位,Intel处理器提供了多种移位指令,具体如下表所示:

指令集含义指令集含义
SHL逻辑左移(无符号数)SHR逻辑右移(无符号数)
SAL算数左移(有符号数)SAR算数右移(有符号数)
ROL循环左移(无符号数)ROR循环右移(无符号数)
RCL循环左移(带进位的)RCR循环右移(带进位的)
SHLD双精度左移(无符号)SHRD双精度右移(无符号)

SHL/SHR 逻辑移位

SHL指令: 对目标操作数执行逻辑左移(针对无符号数)操作,其左移后最低位0填充,而移动出去的最高位则会送入CF(进位标志)中,原来的进位标志位中的值将被覆盖.

Intel处理器中定义,执行移位的源操作数的范围必须在0-255之间,在任何处理器上都可以使用CL寄存器存放移位位数,例如在下面的指令中,AL寄存器被左移一位,最高位被复制到了进位标志中,最低位被清零:

01251006 | B3 8F                | mov al,10001111b                            | AL = 10001111b
01251008 | D0E3                 | shl al,1                                    | CF = 1,AL = 00011110b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 02              | shl al,2                                    | CF = 0,AL = 00000000b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 01              | shl al,1                                    | CF = 1,AL = 00000000b

01251006 | B0 01                | mov al,10100000b                            | AL = 10100000b
01251008 | C0E0 03              | shl al,2                                    | CF = 0,AL = 10000000b

另外使用SHL指令还可以进行2的次幂的高速乘法运算,任何操作数左移动N位,就相当于乘以2的N次方,如下例子:

01311002 | B0 05                | mov al,5                                    | AL 左移动1位
01311004 | D0E0                 | shl al,1                                    | al*2=10

01311007 | B0 05                | mov al,5                                    | AL左移2位
01311009 | C0E0 02              | shl al,2                                    | al*4=20

01311007 | B0 05                | mov al,5                                    | AL左移3位
01311009 | C0E0 03              | shl al,3                                    | al*8=40

SHR指令: 对目标操作数执行逻辑右移(针对无符号数)操作,移出的数据位用0代替,最低位被复制到CF进位标志中,原来的进位标志位丢失.

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,1                                    | CF = 1,AL = 01000111b

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,2                                    | CF = 1,AL = 00100011b

另外任何无符号操作数逻辑右移N位,就相当于该操作数除以2的N次方,如下例子:

01311012 | B2 20                | mov dl,20                                   | DL 右移1位 
01311014 | D0EA                 | shr dl,1                                    | dl/2 = 10

01311012 | B2 20                | mov dl,20                                   | DL 右移2位 
01311014 | D0EA                 | shr dl,2                                    | dl/4 = 5

乘法/除法指令

MUL和IMUL指令分别进行有符号整数和无符号整数的乘法操作,MUL(无符号乘法)指令有三种格式.

8位乘法: 计算AL寄存器BL寄存器相乘,积数默认放在AX寄存器中,进位标志CF清零,因为AH高位等于零.

00111002  | B0 05                    | mov al,5                                 | al = 5
00111004  | B3 10                    | mov bl,10                                | bl = 10
00111006  | F6E3                     | mul bl                                   | AX=50,CF=0

16位乘法: 将16操作数2000h和100h相乘,乘积高位在DX中,低位在AX中.CF=1因为乘机高半部分DX=0

0008100F  | 66:B8 0020               | mov ax,2000                              | ax=2000
00081013  | 66:BB 0001               | mov bx,100                               | bx=100
00081017  | 66:F7E3                  | mul bx                                   | ax*bx

32位乘法: 将32操作数12345h和1000h相乘,得到64位乘积,其高位在EDX中,低位在EAX中.

0008101B  | B8 45230100              | mov eax,12345                            |
00081020  | BB 00100000              | mov ebx,1000                             |
00081025  | F7E3                     | mul ebx                                  |

字串操作指令

移动串指令: MOVSB、MOVSW、MOVSD ;从 ESI -> EDI; 执行后, ESI 与 EDI 的地址移动相应的单位
比较串指令: CMPSB、CMPSW、CMPSD ;比较 ESI、EDI; 执行后, ESI 与 EDI 的地址移动相应的单位
扫描串指令: SCASB、SCASW、SCASD ;依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化
储存串指令: STOSB、STOSW、STOSD ;将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化
载入串指令: LODSB、LODSW、LODSD ;将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化
其中的 B、W、D 分别指 Byte、Word、DWord, 表示每次操作的数据的大小单位.

上述指令可以有重复前缀:
REP ECX > 0 时
REPE (或 REPZ) ECX > 0 且 ZF=1 时
REPNE(或 REPNZ) ECX > 0 且 ZF=0 时
;重复前缀可以自动按单位(1、2、4)递减 ECX

字符串复制(movsb):

.data
	string1 db "hello lyshark",0      ; 原始字符串
	str_len equ $ - string1 -1        ; 计算出原始字符串长度
	string2 db str_len dup(?),0       ; 目标内存地址

.code
	main PROC
		cld                       ; 清除方向标志
		mov esi,offset string1    ; 取源字符串内存地址
		mov edi,offset string2    ; 取目标字符串内存地址
		mov ecx,str_len           ; 指定循环次数,为原字符串长度
		rep movsb                 ; 逐字节复制,直到ecx=0为止
		ret
	main ENDP
END main

另一种字串复制(movsb): 不使用rep重复前缀的方式完成字串复制.

.data
	string1 db "hello lyshark",0      ; 原始字符串
	str_len equ $ - string1 -1        ; 计算出原始字符串长度
	string2 db str_len dup(?),0       ; 目标内存地址

.code
	main PROC
		lea esi,string1               ; 取string1的地址
		lea edi,string2               ; 取string2的地址
		mov ecx,str_len               ; 取字符串长度,用于循环
		cld                       ; 方向->正向
	@@:	movsb                     ; 每次复制一个字节BYTE
		dec ecx                   ; 每次ecx减一
		jnz @B                    ; 如果ecx不为0则循环
		ret
	main ENDP
END main

(movsd)四字节复制字串:

.data
	ddSource DWORD 10h,20h,30h               ; 定义三个四字节数据
	ddDest   DWORD lengthof ddSource dup(?)  ; 得到目标地址

.code
	main PROC
		lea esi,ddSource
		lea edi,ddDest
		mov ecx,lengthof ddSource
		cld
		rep movsd
		ret
	main ENDP
END main

CMPSB:

.data
	Text1 db "hello lyshark",0
	Text2 db "hello lyshar1",0
.code
	main PROC
		lea esi,Text1
		lea edi,Text2
		mov ecx,lengthof Text1
		cld
		repe cmpsb
		je L1
		xor eax,eax            ; 字串不同则清空eax
		jmp L2
	L1:	xor ebx,ebx            ; 字串相同则清空ebx
	L2:	ret
	main ENDP
END main

CMPSD: 比对两个双字数据是否相等.

.data
	var1 DWORD 1234h
	var2 DWORD 5678h
.code
	main PROC
		lea esi,var1
		lea edi,var2
		cmpsd
		je L1
		xor eax,eax      ; 两数如果相等则清空eax
		jmp L2
	L1:	xor ebx,ebx      ; 两数不相等则清空ebx
	L2:	ret
	main ENDP
END main

CMPSW:

.data
	Array1 WORD 1,2,3,4,5      ; 必须全部相等才会清空ebx
	Array2 WORD 1,3,5,7,9
.code
	main PROC
		lea esi,Array1
		lea edi,Array2
		mov ecx,lengthof Array1
		
		cld
		repe cmpsw
		je L1
		xor eax,eax        ; 两数不相等则清空eax
		jmp L2
	L1:	xor ebx,ebx        ; 两数相等则清空ebx
	L2:	ret
	main ENDP
END main

SCASB 扫描字串: 依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化

.data
	szText BYTE "ABCDEFGHIJK",0
.code
	main PROC
	
		lea edi,szText
		mov al,"F"
		mov ecx,lengthof szText -1
		cld
		repne scasb                 ; 如果不相等则重复
		je L1
		xor eax,eax                 ; 如果没找到F则清空eax
		jmp L2
	L1:	sub ecx,lengthof szText -1
		neg ecx           ; 如果找得到, 这里显示是第几个字符; 本例结果是 6
	L2:	ret
	main ENDP
END main

STOSB 存储字串: 将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化

.data
	len = 10
	szText db len dup(0),0
.code
	main PROC
		lea edi,szText                   ; EDI指向字符串
		mov al,"W"                       ; 定义查找字母为W
		mov ecx,len                      ; 设置查找计数器
		cld                              ; 方向=正向
		rep stosb                        ; ecx>0则执行
		ret
	main ENDP
END main

LODSW 载入指令: 将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化,如下是累加求和

.data
	Array WORD 1,2,3,4,5,6,7,8,9,10
.code
	main PROC
		lea esi,Array
		mov ecx,lengthof Array
		xor edx,edx
		xor eax,eax
	@@:	lodsw
		add edx,eax
		loop @B
		
		mov eax,edx           ; 最后将相加结果放入eax

	main ENDP
END main

初始化内存: 把String字符串中的,每一个字节均填充初始化为0FFh

.data
	Count = 100                 ; 申请空间为100
	String BYTE Count DUP(?)    ; 初始化为BYTE
.code
	main PROC
		mov al,0FFh             ; 指定要填充的数据为0FFh
		mov edi,offset String   ; EDI寄存器指向目标内存
		mov ecx,Count           ; 循环计数
		cld                     ; 初始化:方向=前方
		rep stosb               ; 以AL中的值进行填充
		ret
	main ENDP
END main

数组的乘法: 把双字数组中的每一个元素分别乘以一个常量,程序中使用了(LODSD加载),(STOSD保存).

.data
	Array DWORD 1,2,3,4,5
	Multi DWORD 10
.code
	main PROC
		mov esi,offset Array     ; 源指针
		mov edi,esi              ; 目的指针
		
		cld                      ; 方向=向前
		mov ecx,lengthof Array   ; 循环计数器
	L1:	lodsd                    ; 加载[esi]至EAX
		mul Multi                ; 将EAX乘以10
		stosd                    ; 将结果从EAX存储至[EDI]
		loop L1
		ret
	main ENDP
END main

计算字符串长度: 以下代码用于计算字符串的长度,并将结果保存在EAX寄存器中.

.data
	String BYTE "hello world",0      ; 带计算字符串
.code
	main PROC
		mov edi,offset String    ; 取出字符串的基地址
		xor eax,eax              ; 清空eax用作计数器
		
	L1:	cmp byte ptr [edi],0     ; 分别那[edi]的值和0作比较
		je L2                    ; 上一步为零则跳转得到ret
		inc edi                  ; 否则继续执行
		inc eax
		jmp L1
	L2:	ret
	
	main ENDP
END main

小写字串转大写: 将MyString变量中的小写字符串,依次转换为大写字符串.

.data
	MyString db "hello lyshark",0      ; 定义MyString字符串

.code
	main PROC
		mov esi,offset MyString        ; 取出字符串的偏移地址
	L1:	cmp byte ptr [esi],0           ; 分别拿出每一个字节,与0比较
		je L2                          ; 如果相等则跳转到L2
		and byte ptr [esi],11011111b   ; 执行按位与操作
		inc esi                        ; 每次esi指针递增1
		jmp L1                         ; 重复循环
	L2:	ret
	main ENDP
END main

定义二维数组: 定义一个二维数组Table,并取出第一行中的第2偏移地址的元素.

.data
	Table WORD 10h,20h,30h,40h,50h    ; 定义一个数组
	Row = ($ - Table)                 ; 取出数组每行的字节数
	      WORD 60h,70h,80h,90h,0Ah    ; 继续定义数组
.code
	main PROC
		row_index = 1                 ; 表的偏移地址
		column_index = 2              ; 行的偏移地址
		
		mov ebx,offset Table          ; 取偏移地址给ebx
		add ebx,Row*row_index         ; 每行元素*偏移
		
		mov esi,column_index          ; 将行偏移赋值给esi
		mov ax,[ebx+esi*TYPE Table]   ; Table比例因子只能是2,4,8
	main ENDP
END main

位操作指令

符号扩展(CBW/CWDE):

.code
	main PROC
		mov al,7fh
		cbw                 ; 将 AL 扩展为 AX
		PrintHex ax ;007F
		mov al,80h
		cbw
		PrintHex ax ;FF80
		
		mov ax,7fffh
		cwde                 ; 将 AX 扩展为 EAX
		PrintHex eax
	main ENDP
END main

符号扩展(CDQ/CWD):

.code
	main PROC
		mov eax,7FFFFFFFh
		cdq                      ; 将 EAX 扩展为 64 位数 EDX:EAX
		PrintHex edx ;00000000
		PrintHex eax ;7FFFFFFF
		
		mov ax, 7FFFh
		cwd                       ; 将 AX 扩展为 DX:AX
		PrintHex dx ;0000
		PrintHex ax ;7FFF
		
	main ENDP
END main

BT、BTS、BTR、BTC: 位测试指令

;BT(Bit Test): 位测试
;BTS(Bit Test and Set): 位测试并置位
;BTR(Bit Test and Reset): 位测试并复位
;BTC(Bit Test and Complement): 位测试并取反

;它们的结果影响 CF
;它们的指令格式相同:
BT r16/r32/m16/m32, r16/r32/m16/m32
BT r16/r32/m16/m32, i8

.code
main proc
    ;BT 把 10000001b 的第七位复制到 CF, 得知是 1
    mov dx, 10000001b
    bt  dx, 7
    lahf
    PrintHex ah ;47 - 01000111b (CF=1)
    ;BT 把 10000001b 的第六位复制到 CF, 得知是 0
    bt  dx, 6
    lahf
    PrintHex ah ;86 - 10000110b (CF=0)
    
    ;BTS 在执行 BT 命令的同时, 把操作数的指定位置为 1
    mov dx, 10000001b
    bts dx, 6
    PrintHex dl ;C1 - 11000001b
    
    ;BTR 在执行 BT 命令的同时, 把操作数的指定位置为 0
    mov dx, 10000001b
    btr dx, 7
    PrintHex dl ;01 - 00000001b
    
    ;BTC 在执行 BT 命令的同时, 把操作数的指定位取反
    mov dx, 10000001b
    btc dx, 0
    PrintHex dl ;80 - 10000000b
    btc dx, 0
    PrintHex dl ;81 - 10000001b
    ret
main endp
end main

BSF、BSR: 位扫描指令

;BSF(Bit Scan Forward): 位扫描, 低 -> 高
;BSR(Bit Scan Reverse): 位扫描, 高 -> 低

;它们的结果影响 ZF

;扫描的是参数二, 找到是 1 的位后, 把位置数给参数一并置 ZF=0
;找不到(也就是参数二是 0)时, 置 ZF=1

;它们的指令格式相同:
BSF r16/r32, r16/r32/m16/m32

.code
main proc
    ;扫描到时
    mov dx, 0000111100001100b
    bsf cx, dx
    PrintDec cx ;2  - 也就是左数第 3 位
    
    bsr cx, dx
    PrintDec cx ;11 - 也就是左数第 12 位
    
    ;扫描不到时
    mov cx, 0FFFFh
    mov dx, 0
    bsf cx, dx
    lahf
    PrintHex ah ;C6 - 11000110 (ZF=1)
    PrintHex cx ;FFFF - 找不到时不会影响到目的值
    ret
main endp
end main
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构与算法_C++[MP4] 猎豹网校VC++ MFC 经典教程 基础篇[MP4] 猎豹网校Java 零基础入门[MP4] 猎豹网校 游戏编程快速入门[MP4] 猎豹网校 网店赢家淘宝网新手开店[MP4] 猎豹网校 透测测试 系统安全测试[MP4] 猎豹网校 淘宝卖家网店美工[MP4] 猎豹网校 搜索引擎搜索技巧[MP4] 猎豹网校 数据结构与算法_Java语言[MP4] 猎豹网校 数据结构与算法_C语言[MP4] 猎豹网校 软件测试快速入门[MP4] 猎豹网校 猎豹网校数据结构与算法_C#语言[MP4] 猎豹网校 快学快用 Excel 应用大全 中级教程[MP4] 猎豹网校 快学快用 Excel 应用大全 高级教程[MP4] 猎豹网校 快学快用 Excel 应用大全 初级教程[MP4] 猎豹网校 快速掌握_会声会影_标准[MP4] 猎豹网校 快速掌握 QTP 测试工具[MP4] 猎豹网校 快速掌握 Python 学习手册[MP4] 猎豹网校 快速掌握 Python 系统管理[MP4] 猎豹网校 快速掌握 Python 基础教程[MP4] 猎豹网校 快速掌握 Python Django 1.5 网页开发[MP4] 猎豹网校 快速掌握 MATLAB 经典教程[MP4] 猎豹网校 快速掌握 LoadRunner 测试工具[MP4] 猎豹网校 快速掌握 C# 学习指南[MP4] 猎豹网校 快速掌握 Access VBA[MP4] 猎豹网校 快速学会_Windows 命令行批处理脚本[MP4] 猎豹网校 快速学会_Shell 脚本经典教程[MP4] 猎豹网校 快速学会 VB.NET 经典教程[MP4] 猎豹网校 快速学会 TclTk 开发指南[MP4] 猎豹网校 快速学会 Perl 实用教程[MP4] 猎豹网校 快速学会 Perl 入门经典[MP4] 猎豹网校 快速通过_计算机二级_公共基础知识[MP4] 猎豹网校 快速通过_计算机二级_VFP[MP4] 猎豹网校 快速通过_计算机二级_VB[MP4] 猎豹网校 快速通过_计算机二级_Java[MP4] 猎豹网校 快速通过_计算机二级_C语言[MP4] 猎豹网校 快速通过_计算机二级_C++[MP4] 猎豹网校 黑客攻防 网站攻防修炼[MP4] 猎豹网校 黑客攻防 木马揭秘[MP4] 猎豹网校 黑客攻防 蜜罐[MP4] 猎豹网校 高级软件测试分析师[MP4] 猎豹网校 大家可以学的C语言[MP4] 猎豹网校 Windows程序设计 初级课程(二)[MP4] 攻防[MP4] 北风网 C++反汇编基础Win32平台(逆向实例、动态调试实例)[MP4] 安卓 Android 游戏编程入门(上)[MP4] Word排版高级技巧[MP4] Word 零基础入门[MP4] Windows程序设计 初级课程(一)[MP4] Windows Api学习指南[MP4] VisualSVN Subversion[MP4] Visual C# 入门经典[MP4] VC++ MFC快速提高[MP4] VC++ MFC快速入门[MP4] VC++ MFC快速进阶[MP4] Unity 3D 游戏开发经典教程[MP4] Unity 3D 游戏高级教程[MP4] SQL Server 数据库[MP4] Silverlight 经典教程[MP4] Qt 中级课程 (上)[MP4] Python系统管理[MP4] PS抠图秘技[MP4] ppyygg PowerPoint(PPT) 达人进阶[MP4] PHP基础教程[MP4] PHP和MySQL Web开发 高级教程[MP4] PHP和MySQL Web开发 初级教程[MP4] PHP 程序设计[MP4] PHP Zend 开发实战[MP4] PHP Ajax 经典教程[MP4] Oracle 数据库[MP4] MySQL 数据库[MP4] Lua 经典教程[MP4] Linux 轻松入门[MP4] Linux C 基础教程[MP4] JSP 实用教程[MP4] jQuery 快速入门[MP4] Java设计模式[MP4] JavaScript 经典教程[MP4] Java 编程思想 中级教程[MP4] Java 编程思想 高级教程[MP4] Java 编程思想 初级教程[MP4] HTML网页设计[MP4] Flash CS5 经典教程[MP4] Flash ActionScript 经典教程MP4] Flash ActionScript 基础教程MP4] Excel 零基础
梁肇新,豪杰超级解霸的作者 “我就是程序,程序就是我。” 梁肇新 《编程高手箴言》这本书就是梁肇新自己十余年来编程经验的集结。在名人出书成为热潮的这几年中,拥有丰富经历和感悟的梁肇新却没有跟风,这本《箴言》是他的第一本著作,可谓厚积薄发。全书通篇没有时髦的IT新名词或新思想,而是踏踏实实地对很多知识进行了深刻的剖析,这有助于为编程打下坚实的根基。只有这样,才能使开发者在飞速变化的软件领域里免于雾里看花,才能更快更深地认识许多新问题、新知识,也才能更从容地应对未来之挑战。相信这本书对广大程序员大有裨益,无论是从技术角度还是从职业生涯指导角度。 本书是作者十余年编程生涯中的技术和经验的总结。内容涵盖了从认识CPU、Windows运行机理、编程语言的运行机理,到代码的规范和风格、分析方法、调试方法和内核优化,内有作者对许多问题的认知过程和透彻的分析,以及优秀和精彩的编程经验. 目录: 第1章 程序点滴 1 1.1 程序≠软件 1 1.1.1 商业软件门槛的形成 2 1.1.2 认清自己的发展 4 1.2 高手是怎样练成的 5 1.2.1 高手成长的六个阶段 5 1.2.2 初级程序员和高级程序员的区别 7 1.2.3 程序员是吃青春饭的吗 9 1.3 正确的入门方法 11 1.3.1 规范的格式是入门基础 13 1.3.2 调试的重要性 17 1.4 开放性思维 18 1.4.1 动态库的重要性 19 1.4.2 程序设计流程 20 1.4.3 保证程序可预测性 21 第2章 认识CPU 23 2.1 8位微处理器回顾 23 2.2 16位微处理器 24 2.2.1 组成结构 24 2.2.2 8086寄存器组成 25 2.2.3 内存的寻址 26 2.2.4 中断处理 27 2.3 32位微处理器 29 2.3.1 寄存器组成 29 2.3.2 保护模式 32 2.3.3 80386的寻址方式 32 2.4 【实例】:在DOS实模式下读取4GB内存代码分析 36 2.4.1 程序的意义 37 2.4.2 程序代码 37 2.4.3 程序原理 41 2.4.4 程序中的一些解释 42 第3章 Windows运行机理 44 3.1 内核分析 44 3.1.1 运行机理 44 3.1.2 LE文件的格式 53 3.1.3 VxD的设计实现 59 3.1.4 【实例】:CPU降温程序代码分析 65 3.2 消息的运行方式 82 3.2.1 认识消息 82 3.2.2 Windows系统中消息的运作方式 84 3.2.3 消息处理过程实例 87 3.3 GDI的结构和组成 89 3.3.1 GDI的组成 89 3.3.2 GDI和DirectDraw的关系 91 3.4 线程的机制 93 3.4.1 线程的工作方式 93 3.4.2 线程与GDI的冲突:死机的主要原因 94 3.4.3 线程的内存泄漏的主要原因 96 3.4.4 进程管理 98 3.4.5 同步机制 100 3.5 PE结构分析 103 3.5.1 PE头标 103 3.5.2 表节 113 3.5.3 PE文件引入 119 3.5.4 PE文件引出 125 3.5.5 PE文件资源 129 第4章 编程语言的运行机理 133 4.1 汇编的原理 133 4.1.1 指令系统 133 4.1.2 汇编与Win API的接口方法 141 4.1.3 【实例】:自定义程序的入口点 145 4.2 高级语言的原理 151 4.2.1 C/C++的原理 151 4.2.2 解释语言的原理 165 4.2.3 【实例】:用C实现简单的BASIC语言环境 165 4.3 C、C++的学习方式 187 4.3.1 从BASIC到C 187 4.3.2 C、汇编、API的关系 187 4.3.3 接口的建立方法 190 4.4 挂钩技术 201 4.4.1 Windows上C的挂钩 201 4.4.2 C++的挂钩技术 213 第5章 代码的规范和风格 220 5.1 环境的设置 220 5.1.1 集成环境的设置 220 5.1.2 TAB值的设置 221 5.1.3 编译环境的设置 222 5.1.4 设置herosoft.dsm宏 224 5.2 变量定义的规范 227 5.2.1 变量的命名规则 227 5.2.2 变量定义的地方规定 228 5.2.3 变量的对齐规定 229 5.3 代码对齐方式、分块、换行的规范 230 5.4 快速的代码整理方法 232 5.5 注释的规范 233 5.6 头文件的规范 236 5.7 建议采用的一些规则 236 5.8 可灵活运用的一些规则 238 5.9 标准化代码示例 239 5.10 成对编码规则 243 5.10.1 成对编码的实现方法 243 5.10.2 成对编码中的几点问题 248 5.11 正确的成对编码的工程编程方法 251 5.11.1 编码前的工作 252 5.11.2 成对编码的工程方法 255 5.11.3 两个问题的解释 260 第6章 分析方法 266 6.1 分析概要 266 6.1.1 分析案例一:软件硬盘阵列 268 6.1.2 分析案例之二:游戏内存修改工具 274 6.2 接口的提炼 286 6.2.1 分离接口 286 6.2.2 参数分析 287 6.3 主干和分支 290 6.3.1 主干和分支分析举例 291 6.3.2 程序检?? 300 6.4 是否对象化 301 6.5 是否DLL化 307 6.5.1 DLL的建立和调用 307 6.5.2 DLL动态与静态加载的比较 322 6.5.3 DLL中函数的定义 322 6.6 COM的结构 324 6.7 几种软件系统的体系结构分析 326 6.7.1 播放器的解码组成分析 326 6.7.2 豪杰大眼睛的体系结构 330 6.7.3 Windows 9x体系结构 331 第7章 调试方法 333 7.1 调试要点 333 7.1.1 调试和编程同步 333 7.1.2 汇编代码确认 334 7.1.3 Win32的Debug实现方法 342 7.2 基本调试实例分析 343 7.3 多线程应用的调试 350 7.4 非固定错误的调试 352 7.4.1 激活调试环境 352 7.4.2 正确区分错误的类型 356 7.4.3 常见的偶然错误 357 第8章 内核优化 358 8.1 数据类型的认识 358 8.2 X86优化编码准则 359 8.2.1 通用的X86优化技术 359 8.2.2 通用的AMD-K6处理器x86代码优化 361 8.2.3 AMD-K6处理器整数x86代码优化 364 8.3 MMX指令的优化 368 8.3.1 MMX的寄存器介绍 368 8.3.2 MMX的工作原理 368 8.3.3 MMX的检测 369 8.3.4 MMX指令的介绍 370 8.4 MMX的实例一:图像的淡入淡出 394 8.4.1 目的 394 8.4.2 解决方法 394 8.4.3 分析 394 8.4.4 初步实现 395 8.4.5 MMX的优化实现 401 8.5 MMX的实例二:MMX类的实现方法 407 8.5.1 实现方法分析 407 8.5.2 实现步骤 407 8.5.3 检测过程 410 8.5.4 总结 416
第一部分 破解入门: X86上的Linux 第二部分 多种平台上的破解:Windows, Solaris, and Tru64 第三部分 漏洞发现 第四部分 高级内容 很多人问如何入门如何入门,我却不知道要问的是入什么门。很少把某些好文章耐心从头看完,我这次就深有体会。比如袁哥的sniffer原理,一直以为自己对sniffer原理很清楚的,所以也就不曾仔细看过袁哥的这篇。后来有天晚上和袁哥讨论,如何通过端口读写直接获取mac地址,为什么 antisniff可以获得真正的mac地址,而不受更改mac地址技术的影响,如何在linux下获得真正的mac地址。我一直对linux下的端口读写心存疑虑,总觉得在保护模式下的端口都做了内存映象等等。结果袁哥问了我一句,你仔细看我写的文章没有,我愣,最近因为要印刷月刊,我整理以前的很多文档,被迫认真过滤它们,才发现袁哥的文章让我又有新认识。再后来整理到tt的几篇缓冲区溢出的,尤其是上面的关于Solaris可装载内核模块,那就更觉得惭愧了。以前说书非借不能读,现在是文章留在硬盘上却不读。其实本版已经很多经典文章了,也推荐了不少经典书籍了,有几个好好看过呢。 W.Richard.Stevens的UNP我算是认真看过加了不少旁注,APUE就没有那么认真了,而卷II的一半认真看过,写过读书笔记,卷III就没有看一页。道格拉斯的卷I、卷III是认真看过几遍,卷II就只断续看过。而很多技术文章,如果搞到手了就懒得再看,却不知道这浪费了多少资源,忽略了多少资源。BBS是真正能学到东西的地方吗?rain说不是的,我说也不是的。不过这里能开阔人的视野,能得到对大方向的指引,足够了。我一直都希望大家从这里学到的不是技术本身,而是学习方法和一种不再狂热的淡然。很多技术,明天就会过时,如果你掌握的是学习方法,那你还有下一个机会,如果你掌握的仅仅是这个技术本身,你就没有机会了。其实我对系统安全是真不懂,因为我一直都喜欢看程序写程序却不喜欢也没有能力攻击谁谁的主机/站点。我所能在这里做的是,为大家提供一个方向,一种让你的狂热归于淡然的说教。如果你连看都没有看过,却要写个什么隐藏自己的木马,搞笑。如果你看都不看汇编语言,偏要问exploit code的原理,那我无法回答也不想回答你。总有人责问,要讨个说法云云,说什么提问却没有回答。不回答已经是正确的处理方式了,至少没有回你一句,看书去,对不对,至少没有扰乱版面让你生闷气。Unix的man手册你要都看完了,想不会Unix都不行了。微软的MSDN、Platform SDK DOC你要看完了,你想把Win编程想象得稍微困难点都找不到理由。还是那句话,一个程序员做到W.Richard.Stevens那个份上,做到逝世后还能叫全世界的顶级hacker们专门著文怀念,但生前却不曾著文攻击,想想看,那是一种什么样的境界,那是一份什么样的淡然。我们可以大肆讨论技术问题,可以就技术问题进行激烈的卓有成效的讨论,却无意进行基础知识、资源信息的版面重复。我刚在前面贴了一堆isbase的文章,开头就是主页标识,却在后面立刻问什么主页在哪里?前面刚刚讨论过如何修改mac地址,后面马上又来一个,前后相差不过3篇文章。选择沉默已经是很多朋友忍耐力的优异表现了。很多东西都是可以举一反三的。vertex的lids,被 packetstorm天天追踪更新,你要是看了THC的那三篇,觉得理解一个就理解了一堆,都是内核模块上的手脚。你不看你怎么知道。我不想在这里陷入具体技术问题的讨论中去,你要是觉得该做点什么了,就自己去看自己去找。没有什么人摆什么架子,也没有什么人生来就是干这个的。你自己问自己,尽力了吗?
phtotoshop技巧 chm Premiere6手册 chm psql中文手册 chm RemotelyAnywhere-使用全攻略 chm Sniff网络基础原理和软件实现技巧详解 SQL Server精华 CHM chm SQL Server 2000菜鸟入门 chm SQL参考手册 chm UltraEdit教程 chm VB NET面向对象的实现 chm vbapi函数手册 chm vbscript语言参考 chm vbscript速查手册 chm VBSCRIP语言参考 CHM VC++ 编程指南 chm vc新手学堂 chm VMware Workstation 帮助 chm WebCompiler2000与电子书制作教程 chm WIN2000 SERVER做服务器的安全配置 CHM Win2000 驱动程序设计 chm Win2000维护和管理 chm Win2000网络服务功能 chm Win2000网络高级服务 chm WIN2K下配置ASP+CGI+PHP+MYSQL全攻略 CHM win2k教程 chm win2k服务器设置全攻略 chm win32汇编语言教程 chm Windows server 2003使用指南 CHM Windows XP 全功略 chm WINDOWS 非法作详解 CHM windowsapi函数介绍 chm WindowsAPI大全 chm windows技巧全集 chm Windows程序设计中文版 chm Windows程式设计 chm Windows系统文件名详解 chm WinXPghost chm winzip教程 chm 代理服务知识大全 chm 单机架站全攻略allserver chm 博雅电子杂志 电脑版 合订本 chm 安装程序制作简明教程 chm 电脑报2003年增刊 chm 程序员大本营 net版 chm 程序员超级开发宝典 CHM 编程技术 chm">phtotoshop技巧 chm Premiere6手册 chm psql中文手册 chm RemotelyAnywhere-使用全攻略 chm Sniff网络基础原理和软件实现技巧详解 SQL Server精华 CHM chm SQL Server 2000菜鸟入门 chm SQL参考手册 chm UltraEdit教程 chm VB NET面向对象的实现 chm vbapi函数手册 [更多]
GDI+是Windows操作系统中的一个图形处理API,它提供了一系列用于绘制图形和处理图像的函数和类。双缓冲是一种绘图技术,用于解决绘图过程中的闪烁问题。在使用GDI+进行绘图时,可以通过双缓冲技术来减少或消除绘图过程中的闪烁现象。 下面是一个使用GDI+双缓冲的示例代码: ```c++ #include <windows.h> #include <gdiplus.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("DoubleBuffer"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppName, TEXT("Double Buffering Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cxClient, cyClient; HDC hdc; PAINTSTRUCT ps; Gdiplus::Graphics* graphics; Gdiplus::Bitmap* bitmap; Gdiplus::Graphics* memGraphics; Gdiplus::Bitmap* memBitmap; switch (message) { case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); // 创建一个与窗口大小相同的内存位图 bitmap = new Gdiplus::Bitmap(cxClient, cyClient); graphics = Gdiplus::Graphics::FromImage(bitmap); // 创建一个与内存位图相同大小的内存绘图对象 memBitmap = new Gdiplus::Bitmap(cxClient, cyClient); memGraphics = Gdiplus::Graphics::FromImage(memBitmap); // 在内存绘图对象上进行绘制操作 // ... // 将内存绘图对象的内容绘制到窗口的设备上下文中 graphics->DrawImage(memBitmap, 0, 0); // 释放资源 delete graphics; delete bitmap; delete memGraphics; delete memBitmap; EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } ``` 在上面的示例代码中,我们创建了一个窗口,并在窗口的`WM_PAINT`消息处理函数中使用了双缓冲技术。具体来说,我们创建了一个与窗口大小相同的内存位图,并在内存位图上进行绘制操作。然后,将内存位图的内容绘制到窗口的设备上下文中,从而实现了双缓冲绘图。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值