win32 汇编基础概念整理

2023-05-16

一、关于寄存器

寄存器有EAX,EBX,ECX,EDX,EDI,ESI,ESP,EBP等,似乎IP也是寄存器,但只有在CALL/RET在中会默认使用它,其它情况很少使用到,暂时可以不用理会。
EAX是WIN32 API 默认的返回值存放处。
ECX是LOOP指令自动减一的寄存器。
ESP是堆栈指针。
EBP经常用来在堆栈中寻址。
ESI好像常常用在指针寻址中,EDI不大清楚。

二、关于内存寻址

WIN32中内存是平坦的,对于每个程序来说都可以使用2G范围的地址,但各个程序之间并不会干扰,这是因为各个程序所使用到的物理内存被Windows自行安排,不会互相覆盖,而且一个程序不会随意地访问到另一个程序的地址空间。

三、关于堆栈

Windows为每个程序安排了堆栈段,它是从高地址向低地址延伸的,之所以采用这种方式,是因为这样可以使堆栈指针始终指向最近入栈的元素的起始地址,这样的话,为访问这个元素提供了非常便利的方式。

ESP作为堆栈指针始终指向栈顶,如果看一下PUSH和POP的操作就可以明白这句话:
PUSH: ESP <-- ESP-4 (ESP+3,ESP) <-- 入栈元素
POP: 出栈元素 <-- (ESP+3,ESP) ESP <-- ESP+4

因为PUSH和POP自动修改了ESP的值,使它始终指向栈顶了。当然也可以自己来修改ESP的值,例如我们可以:
sub esp,4 ;这样就把栈顶指针向下移动了。
这种操作常常用在局部变量的分配中,在子程序中使用到局部变量时,就在堆栈中为它们提供空间,这样可以使子程序退出时收回局部变量占用的空间,有利于子程序的模块化。

我们可以用ESP来寻址堆栈中的元素,比如ESP指向当前栈顶元素的起始地址,ESP-4指向前一个元素的起始地址,不过因为ESP常常在变化,这样用ESP在堆栈中寻址的话不方便,所以我们就用EBP来代替ESP寻址,首先把EBP入栈保存,然后把ESP赋值给EBP,这样就可以用EBP来寻址堆栈中的数据了。我用一个例子来说明堆栈的变化。

push 0x00000001 ;1
push ebp ;2
mov ebp,esp ;3
push 0x12345678 ;4
mov eax,dword ptr[ebp+4] ;5
mov ebx,dword ptr[ebp-4] ;6
mov ax,word ptr[ebp-2] ;7
mov al,byte ptr[ebp-1] ;8
mov al,byte ptr[ebp-3] ;9
mov ax,word ptr[ebp-3] ;10

5 eax=0x00000001
6 ebx=0x12345678
7 ax=0x1234
8 al=0x12
9 al=0x56
10 ax=0x3456


堆栈使用在子程序的实现中,当调用子程序时,首先把参数入栈,然后把返回IP入栈,然后转移到子程序处,如果有局部变量,则下移ESP,然后初始化该局部变量,这样用到EBP来寻址局部变量,参数的寻址同样要用到EBP。


四、简单的几个关键字

ptr 显式指定后面的数据的类型
offset 全局变量的地址
addr 局部变量的地址,也可以用在全局变量上
local 定义局部变量
proc 定义子程序
proto 声明子程序

五、例子

Hello.asm文件的内容如下:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 第一部分:模式和源程序格式的定义语句
.386 ; 指令集
.model flat,stdcall ; 工作模式
option casemap:none ; 格式
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
szCaption db 'A MessageBox !',0
szText db 'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start ; 指定程序的入口

1. 第一部分模式和源程序格式的定义语句
第一行 指定使用的指令集(编译器使用)
Win32环境工作在80386及以上的处理器中,所以必须定义.386。如果程序(VxD等驱动程序)中要用到特权指令,那么必须定义.386p。
第二行 定义程序工作的模式(包括内存模式、语言模式、其它模式)
对Win32程序来说,只有一种内存模式,即flat(平坦)模式。
Win32 API调用使用的是stdcall格式,所以Win32汇编中必须在.model中加上stdcall参数。
第三行 option语句
由于Win32 API中的API名称区分大小写,所以必须定义option casemap:none,来表明程序中的变量和子程序名对大小写敏感。

2. 包含全部段的源程序结构:
.386
.model flat,stdcall
option casemap:none
<一些include语句>
.stack [堆栈段的大小]
.data
<一些初始化过的变量定义>
.data?
<一些没有初始化过的变量定义>
.const
<一些常量定义>
.code
<代码>
<开始标记>
<其他语句>
end 开始标记

3. 段的定义
数据段
.data
已初始化数据段,可读可写的已定义变量;
当程序装入完成时,这些值就已经在内存中;
数据定义在.data段中会增加可执行文件的大小;
.data段一般存放在可执行文件的_DATA节区(Section)内;
.data?
未初始化数据段,可读可写的未定义变量,在可执行文件中不占空间;
这些变量一般作为缓冲区或者在程序执行后才开始使用。
数据定义在.data?数据段中不会增加可执行文件的大小;
.data?段一般存放在可执行文件的_BSS节区内;
.const
常量,可读不可写的变量;

代码段
.code
所有的指令都必须写在代码段中;
Win32中,数据段是不可执行的,只有代码段有可执行的属性;
对于运行在特权级3的应用程序,.code段不可写。除非把可执行文件PE头部中的属性位改成可写;
对于运行在特权级0的程序,所有的段都有读写权限,包括代码段;
.code代码段一般存放在可执行文件的_TEXT节区内;

堆栈段
.stack
与DOS汇编不同,Win32汇编不必考虑堆栈。系统会自动分配堆栈空间;
堆栈段的内存属性是可读写并且可执行;
靠动态修改代码的反跟踪模块可以拷贝到堆栈中去边修改边执行;
缓冲区溢出技术也会用到这个特性;

4. 调用操作系统功能的方法:
DOS下
操作系统的功能通过各种软中断来实现。
应用程序调用操作系统功能将经历如下三个过程:
把相应的参数放在各个寄存器中再调用相应的中断;
程序控制权转到中断中去执行;
完成以后通过iret中断返回指令回到应用程序中;
DOS下调用系统功能方法的缺点:
所有的功能号定义是难以记忆的数字;
80x86系列处理器能处理的中断最多只能有256个;
通过寄存器来传递参数,对于参数较多的函数很不方便;
Win32下
系统功能模块放在Windows的动态链接库(DLL)中
作为Win32 API核心的3个DLL:
KERNEL32.DLL 系统服务功能。
GDI32.DLL 图形设备接口。
USER32.DLL 用户接口服务。

常用API的参数和函数声明,查看文档《Microsoft Win32 Programmer's Reference》

5. Win32 API的函数原型声明
函数原型声明的汇编格式如下:
函数名 proto [距离] [语言] [参数1]:数据类型, [参数2]:数据类型,......
proto是函数声明的伪指令
距离可以设置为NEAR、FAR、NEAR16、NEAR32、FAR16或FAR32,由于Win32中只有一个平坦的段,无所谓距离,所以在定义时可以忽略距离。
语言类型可是使用.model所定义的默认值。

以消息对话框函数MessageBox为例
C格式如下:
int MessageBox(
HWND hWnd, // Handle to owner window
LPCTSTR lpText, // text in message box
LPCTSTR lpCaption, // message box title
UINT uType // message box style
);

汇编格式如下:
MessageBox Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
或者写为
MessageBox Proto :dword,:dword,:dword,:dword
编译器只对参数的数量和类型感兴趣,参数的名称只是增加可读性,所以可以省略。
对于汇编语言来说,Win32环境中的参数实际上只有一种类型,就是一个32位的整数(dword,double word),双字,四字节。


6. 调用Win32 API
调用API有如下两种方法:
1) invoke
invoke是MASM提供的伪指令;
invoke伪指令的好处就是能够提高代码的可读性,减少错误;
invoke做了下面三件事:
在编译的时候,由编译器把invoke伪指令展开成相应的push指令和call指令;
进行参数数量的检查工作;
如果带的参数数量和声明时的数量不符,编译器会报错;
2) push和call的组合
80386处理器的指令

invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
也可写为
push NULL
push offset szText
push offset szCaption
push MB_OK
call MessageBox

7. Win32 API函数返回值的处理方法
对于汇编语言来说,Win32 API函数返回值的类型只有dword一种类型,它永远放在eax中。
如果要返回的内容在一个eax中放不下,Win32 API采用如下方法来解决:
a) 一般是eax中返回一个指向返回数据的指针;
b) 在调用参数中提供一个缓冲区地址,数据直接返回到这个缓冲区中去。类似变参的概念;

8. 与字符串相关Win32 API的分类
在Win32环境中,根据两个不同的字符集(ANSI字符集和Unicode字符集),可以把和字符串相关的API分成两类:
a) 处理ANSI字符集的Win32 API函数
函数名称的尾部带一个“A”字符;
ANSI字符串是以NULL结尾的一串字符数组,每一个ANSI字符占一个字节的宽度;
例如:MessageBoxA Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
b) 处理Unicode字符集的Win32 API函数
函数名称的尾部带一个“W”字符;
每一个Unicode字符占两个字节的宽度,所以可以同时定义65536个不同的字符;
例如:MessageBoxW Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword

Windows 9x系列不支持Unicode版本的API,绝大多数的API只有ANSI版本。
只有Windows NT系列才完全支持Unicode版本的API。
为了编写在几个平台中都能通用的程序,一般应用程序都使用ANSI版本的API函数集。

提高程序可移植性的一个方法:
一般在源程序中不直接指明使用Unicode还是ANSI版本,而是使用宏汇编中的条件汇编功能来统一替换。
比如,在头文件中做如下定义:
if UNICODE
MessageBox equ <MessageBoxW>
else
MessageBox equ <MessageBoxA>
endif
然后在源程序的头部指定UNICODE=1或UNICODE=0,重新编译后就能产生不同的版本。

9. include语句
include语句的语法是:
include 文件名

include <文件名>
用“<>”将文件名括起来,可以避免党文件名和MASM的关键字同名时引起编译器混淆。

include语句的作用:
解决了所用到的Win32 API函数都必须预先声明的麻烦。
把所有用到的Win32 API函数声明预先放在一个头文件中,然后用include语句包含进源程序。

编译器对include语句的处理方法,仅是简单地用指定的文件内容把这行include语句替换掉而已。

和C语言中的#include作用类似。

10. includelib语句
includelib语句的语法是:
includelib 库文件名

includelib <库文件名>
用“<>”将文件名括起来,同样可以避免当文件名和MASM的关键字同名时引发编译器混淆。

includelib语句的作用是:
告诉链接器使用哪些导入库。

导入库
WIN32中,API函数的实现代码放在DLL中,导入库中只留有API函数的定位信息和参数数目等简单信息。

DOS下的函数库是静态库
C语言的函数库是典型的静态库
静态库的好处是节省大量的开发时间。
静态库的缺点是每个可执行文件中都包含了要用到的相同函数的代码,即占用了大量的磁盘空间,执行的时候,这些代码也会重复占用内存。

includelib语句和include语句的处理不同,includelib不会把.lib文件的内容插入到源程序中,它只是告诉链接器在链接的时候到指定的库文件中去找Win32 API函数的位置信息而已。


11. MASM中标号和变量的命名规范
MASM中标号和变量的命名规范是相同的,如下:
1) 可以用字母、数字、下划线及符号@、$和?。
2) 第一个符号不能是数字。
3) 长度不能超过240个字符。
4) 不能使用指令名等关键字。
5) 在作用域内必须是唯一的。

12. 标号
标号有如下两种定义方法:
标号名: 目的指令 ;方法1

标号名:: 目的指令 ;方法2
方法1和方法2是不同的
方法1
标号名的后面跟一个冒号,表示标号的作用域是当前的子程序。
在单个子程序中的标号不能同名,不能从一个子程序中用跳转指令跳到另一个子程序中。
方法2
标号名的后面跟两个冒号,表示标号的作用域是整个程序。
对任何其它子程序都是可见的。

在低版本MASM中,默认标号的作用域是整个程序。
在高版本MASM中,默认标号的作用域是当前的子程序。

高版本MASM中的@@标号
当用@@做标号时,可以用@F和@B来引用;
@F表示本条指令后的第一个@@标号;
@B表示本条指令前的第一个@@标号;

不要在间隔太远的代码中使用@@标号,源程序中@@标号和跳转指令之间的距离最好限制在编辑器能够显示的同一屏幕的范围内。

13. 全局变量
全局变量的作用域是整个程序
Win32汇编的全部变量定义在.data或.data?段内,这两个段都是可写的。可以同时定义变量的类型和长度。

全局变量的定义格式如下:
变量名 类型 初始值1,初始值2,......
变量名 类型 重复数量 dup (初始值1,初始值2,......)

MASM支持的变量类型如下表:

名称 表示方式 缩写 长度(字节) 
字节 Byte db 1 
字 word dw 2 
双字(double word) dword dd 4 
三字(far word) fword df 6 
四字(quad word) qword dq 8 
10字节BCD码(ten byte) tbyte dt 10 
有符号字节(sign byte) sbyte 1 
有符号字(sign word) sword 2 
有符号双字(sign dword) sdword 4 
单精度浮点数 Real4 4 
双精度浮点数 Real8 8 
10字节浮点数 Real10 10


注意:只有定义全局变量的时候,类型才可以用缩写。

在byte类型变量的定义中,可以用引号定义字符串和数值定义的方法混用。
例如:szText db ‘Hello,world!’,0dh,0ah,’Hello again’,0dh,0ah,0

全局变量的初始化:
全局变量在定义中既可以指定初值,也可以只用问号预留空间。
全局变量定义在.data?段中时,只能用问号预留空间,因为.data?段不能指定初始值。
定义时用问号指定的全局变量的初始值是0。

14. 局部变量
局部变量的好处是使程序的模块结构更加分明。
局部变量的缺点是因为空间是临时分配的,所以无法定义含有初始化值的变量,对局部变量的初始化一般在子程序中由指令完成。
局部变量的作用域是单个子程序。
局部变量定义在堆栈中。

局部变量的定义格式如下:
local 变量名1[[重复数量]][:类型],变量名2[[重复数量]][:类型] ......
local是MASM提供的伪指令,用于支持局部变量的定义。有了local伪指令降低不少难度。

定义局部变量需注意以下几点:
a) local伪指令必须紧接在子程序定义的伪指令proc后、其它指令开始之前,因为局部变量的数目必须在子程序开始的时候就确定下来;
b) 定义局部变量时数据类型不能用缩写。如果要定义数据结构,可以用数据结构的名称当作类型;
c) Win32汇编中,参数的默认类型是dword,如果定义dword类型的局部变量,类型可以省略;
d) 当定义数组类型的局部变量时,重复数量可以用“[]”括起来,不能使用定义全局变量的dup伪指令。
e) 局部变量不能和已定义的全局变量同名。
f) 局部变量的作用域是当前的子程序,所以在不同的子程序中可以有同名的局部变量。

局部变量的初始化:
局部变量无法在定义的时候指定初始化值,因为local伪指令只是为局部变量留出空间。
局部变量的初始值是随机的,所以,对局部变量的值一定要初始化。
一般在子程序中使用指令来初始化局部变量。

RtlZeroMemory这个Win32 API函数实现将整个数据结构填0的功能,类似C语言的memset。

在原来的DOS环境下,低版本的MASM中,所有的变量都相当于现在所说的全局变量,都定义在数据段里面。
用汇编语言在堆栈中定义局部变量非常麻烦,需要作一张表,表上的内容是局部变量名和 ebp指针的位置关系。

15. 使用局部变量的一个典型例子与反汇编得到指令的比较:
TestProc proc
local @loc1:dword,@loc2:word
local @loc3:byte

mov eax,@loc1
mov ax,@loc2
mov al,@loc3

TestProc endp

反编译后得到以下指令:
:00401000 55 push ebp
:00401001 8BEC mov ebp,esp
:00401003 83C4F8 add esp,FFFFFFF8
:00401006 8B45FC mov eax,dword ptr [ebp-04]
:00401009 668B45FA mov ax,word ptr [ebp-06]
:0040100D 8A45F9 mov al,byte ptr [ebp-07]
:00401010 C9 leave
:00401011 C3 ret

其中的
push ebp ; 把原来ebp寄存器的值保存起来;
mov ebp,esp ; 把esp寄存器的值复制到ebp寄存器中,供存取局部变量时做指针用;
add esp,FFFFFFF8 ; 在堆栈中预留出空间(即重新设置堆栈指针),由于堆栈是向下增长,所以要把esp加上一个负值。
三条指令用于局部变量的准备工作。

在堆栈中预留出空间时,把esp加上(-8),而不是加上(-7),是因为在80386处理器中,以dword为界对齐时存取内存的速度最快。以空间换时间。

leave是80386指令集中的一条指令,用于局部变量的扫尾工作。
一条leave指令就实现了mov esp,ebp和pop ebp两条指令的功能。
mov esp,ebp ; ebp寄存器中保存了正确的初始esp值,所以把正确的esp设置回去后,ret指令就能从堆栈中取出正确的地址返回。
pop ebp ; 执行这条语句之后,堆栈就是正确的。

由于esp寄存器在程序的执行过程中可能随时会被用到,所以不可能用esp寄存器做指针来存取堆栈中的局部变量。
ebp寄存器也是以堆栈段为默认数据段的,所以可以用ebp做指针来存取堆栈中的局部变量。

局部变量在堆栈中排列的顺序如下表:

ebp偏移 内容 
ebp+4 由call指令推入的返回地址。 
ebp push ebp指令推入的原ebp值,然后新的ebp就等于当前的esp寄存器的值。 
ebp-4 第一个局部变量@loc1:dword (4个字节) 
ebp-6 第二个局部变量@loc2:word (2个字节) 
ebp-7 第三个局部变量@loc3:byte (1个字节)




使用局部变量时的注意点:
a) ebp寄存器是关键,它起到保存原始esp寄存器值的作用;
b) 另外,ebp寄存器随时用做存取局部变量的指针基址,所以绝不能把ebp寄存器用于别的用途;
c) ebp寄存器的值绝对不能被改变,把ebp寄存器的值改掉,程序就玩完;


16. 数据结构
数据结构相当于一种自定义的数据类型,类似C语言中的struct定义。
汇编中,数据结构的定义方法如下:
结构名 struct
字段1 类型 ?
字段2 类型 ?
......
结构名 ends

定义数据结构并不会在某个段中产生数据,只有使用数据结构在数据段中定义数据后,才会产生数据。

使用数据结构在数据段中定义数据的两种方法如下:
第一种定义方法是未初始化的定义方法:
.data?
stWndClass WNDCLASS <>
......

第二种定义方法是定义的同时指定结构中个字段的初始值:
.data
stWndClass WNDCLASS <1,1,1,1,1,1,1,1,1,1>
......


汇编中,对数据结构变量的几种引用方法如下:
a) 最直接的方法:
mov eax,stWndClass.lpfnWndProc
如果stWndClass结构变量在内存中的起始地址是403000h,那么这句指令会被编译成mov eax,[403004h]
b) 在实际使用中,常有使用指针存取数据结构变量的情况:
如果使用esi寄存器做指针寻址
mov esi,offset stWndClass
mov eax,[esi + WNDCLASS.lpfnWndProc]
第二句指令将被编译成mov eax,[esi+4]
c) 使用assume伪指令把寄存器预先定义为结构指针,在进行操作:
mov esi,offset stWndClass
assume esi:ptr WNDCLASS
mov eax,[esi].lpfnWndClass
......
assume esi:nothing
编译后产生同样的代码,不过程序的可读性比较好。
注意:在不使用esi寄存器做指针的时候要用assume esi:nothing取消定义。

结构的嵌套定义如下:
NEW_WNDCLASS struct
dwOption dword ?
oldWndClass WNDCLASS <>
NEW_WNDCLASS ends

引用嵌套的oldWndClass结构变量的lpfnWndProc字段的方法:
assume esi:ptr NEW_WNDCLASS
mov eax,[esi].oldWndClass.lpfnWndProc
......
assume esi:nothing

windows.inc文件定义了大部分Win32 API所涉及的常量和数据结构。

17. 以不同的类型访问变量
MASM中以不同的类型访问不会对变量造成影响。而C语言中的数据类型强制转换过程中,数据的内容已经发生变化。
MASM中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样,编译器忽略语法上的长度校验,仅使用变量的地址。
访问变量是显式地指出要访问长度的方法是:
类型 ptr 变量名
例如:
mov ax,word ptr szBuffer
mov eax,dword ptr szBuffer
类型可以设置为byte、word、dword、fword、qword、real8和real10。
类型必须和操作的寄存器长度匹配,否则无法通过编译。

需要注意的是:
指定类型的访问变量并不会去检测长度是否溢出。

80386的字节序是:
低位数据在低地址,高位数据在高地址
举例:
下面这段代码存在长度溢出的问题。长度溢出即越界存取到相邻的其它变量。
.data
bTest1 db 12h
wTest2 dw 1234h
dwTest3 dd 12345678h
......
.code
......
mov al,bTest1
mov ax,word ptr bTest1
mov eax,dword ptr bTest1
......
通过反汇编后的内容如下:
; .data段中的变量
:00403000 12 ; 从这里开始的1个字节是变量bTest1
:00403001 34 ; 从这里开始的2个字节是变量wTest2
:00403002 12
:00403003 78 ; 从这里开始的4个字节是变量dwTest3
:00403004 56
:00403005 34
:00403006 12

; .code段中的代码
:00401000 A000304000 mov al,byte ptr [00403000]
:00401005 66A100304000 mov ax,word ptr [00403000]
:0040100B A100304000 mov eax,dword ptr [00403000]

运行结果:
al等于12h
ax等于 3412h
eax 等于 78123412h

从例子可以看出,汇编中用ptr强制覆盖变量长度的时候,实质上只用了变量的地址,编译器并不会考虑定界的问题。

movzx指令用于数据长度的扩展
movzx指令是80386处理器提供的扩展指令,该指令总是将扩展的数据位用0代替。
movzx指令是安全的强制类型转换方式。
能够像C语言的强制类型转换一样,把一个字节扩展到一个字或一个双字再放到ax或eax中,高位保持0而不是越界存取到其它的变量中。

movsx指令可以完成带符号位的扩展
movsx指令是80386处理器提供的扩展指令;
当被扩展数据的最高位为0时,效果和movzx指令相同;当最高位为1时,则扩展部分的数据位全部用1填充。

18. 变量的尺寸和数量
sizeof伪操作符可以取得变量、数据类型或数据结构以字节为单位的长度(尺寸)。
格式:
sizeof 变量、数据类型或数据结构名

lengthof伪操作符可以取得变量、数据类型或数据结构中数据的项数(数量)
格式:
length 变量、数据类型或数据结构名

对字符串使用sizeof伪操作符,取得的长度包括结束符0。

需要注意的是:
sizeof伪操作符和length伪操作符取得的数值是编译期产生的,由编译器直接替换到指令中去。所以,在反汇编得到的代码中没有sizeof或lengthof,而只有它们取得的数值。

取得字符串长度的一种特殊情况:
如果szHello的定义分成两行:
szHello db ‘Hello’,0dh,0ah
db ‘World’,0
sizeof szHello得到的数值是7而不是13。
这种定义方式实质为越界使用字符串变量。
MASM中的变量定义只认一行,后一行db ‘World’,0实际上是另一个没有名称的数据定义。
要取得这种字符串的长度时,千万不能用sizeof伪指令,最好是在程序中用lstrlen函数去计算。

19. 获取变量地址
获取全局变量地址和获取局部变量地址的操作是不同的。
因为全局变量定义在数据段中,而局部变量在堆栈中。全局变量的地址可以在编译期确定,而局部变量的地址只能在运行期确定。

全局变量的地址在编译期已经由编译器确定了。
获取全局变量的地址使用offset伪操作符,这个操作在编译期而不是运行期完成。
mov 寄存器,offset 变量名

不可能用offset伪操作符来获取局部变量地址的原因是:
局部变量是用ebp来做指针访问的,由于ebp的值随着程序的执行环境不同可能是不同的,所以局部变量的地址值在编译期也是不确定的。

获取局部变量的地址使用lea指令
lea指令是80386处理器指令集中的一条指令。
lea eax,[ebp-4]

在invoke伪指令的参数中用到某个局部变量的地址,使用MASM提供的伪操作符addr。
格式为:
addr 局部变量名和全局变量名
addr伪操作符即可用于局部变量,也可用于全局变量

使用addr伪操作符需要注意以下几点:
a) 对局部变量取地址的时候,addr伪操作符只能用在invoke的参数中,不能用在如下的mov指令中。
mov eax,addr 局部变量名 ;这是错误的用法
因为在这句mov指令中,编译器无法把addr伪操作符替换成lea指令。
b) 当在invoke中使用addr伪操作符时,在addr伪操作符的左边不能使用eax寄存器,否则eax寄存器的值会被覆盖掉,当然eax寄存器用在addr伪操作符的右边的参数中是可以的。
MASM对于这种情况会报编译期错误。


20. 使用子程序
Win32汇编中的子程序也采用堆栈来传递参数,所以可以用invoke伪指令来调用子程序,并进行语法检查工作。

子程序的定义方式如下:
子程序名 proc [距离] [语言类型] [可视区域] [USES 寄存器列表] [,参数:类型]...[VARARG]
local 局部变量列表

指令

子程序名 endp
proc伪指令和endp伪指令用于定义子程序开始和结束的位置。
子程序有如下属性:
a) 距离 Win32中只有一个平坦的段,无所谓距离,所以对距离的定义往往忽略。
b) 语言类型 表示参数的使用方式和堆栈平衡的方式,如果忽略,则使用程序头部.model定义的值。
c) 可视区域 可以设为PRIVATE、PUBLIC和EXPORT,默认是PUBLIC。
PRIVATE表示子程序只对本模块可见;
PUBLIC表示子程序对所有模块可见(在最后编译链接完成的可执行文件中);
EXPORT表示子程序是DLL的导出函数;
d) USES寄存器列表 表示由编译器在子程序指令开始前自动安排push这些寄存器的指令,并且在ret前自动安排pop指令,用于保护执行环境。
一种更方便的做法是,在子程序的开头和结尾用pushad指令和popad指令一次保存和恢复所有寄存器。
e) 参数和类型 参数指参数的名称,在定义参数名的时候不能跟全局变量和子程序中的局部变量重名。
对于类型,由于Win32中的参数类型只有32位(dword)一种类型,所以可以省略。在参数定义的最后还可以跟VARARG,表示在已确定的参数后还可以跟多个数量不确定的参数。

在写源程序的时候有意识地把子程序的位置提到invoke语句的前面,省略掉proto语句,可以简化程序和避免出错。

参数传递和堆栈平衡
在调用子程序时,参数的传递是通过堆栈进行的。
调用者把传递给子程序的参数压入堆栈,子程序从堆栈中取出相应的值来使用。

调用约定,约定了参数入栈的顺序和由谁(调用者或子程序)来平衡堆栈。
由于各种语言默认的调用约定是不同的,所以在proc以及proto语句的语言属性中确定语言类型后,编译器才能将invoke为指令翻译成正确的样子。

不同语言调用方式的差别如下表:

语言类型 最先入栈参数 平衡堆栈者 允许使用VARARG 
C 右 调用者 是 
SysCall 右 子程序 是 
StdCall 右 子程序 是 
BASIC 左 子程序 否 
FORTRAN 左 子程序 否 
PASCAL 左 子程序 否

注:VARARG表示参数的个数可以是不确定的,如wsprinitf,StdCall的堆栈清除平时是由子程序完成的,但使用VARARG时是由调用者清除的。
从上表可以看出只有C语言是调用者平衡堆栈,其他语言类型都是被调用者来平衡堆栈。

因为Win32约定的类型是StdCall,所以在程序中调用子程序或系统API后,不必自己来平衡堆栈,免去了很多麻烦。
存取参数和局部变量都是通过堆栈来实现的,和存取局部变量类似,参数的存取也是通过ebp做指针来完成的。
所有对局部变量使用的限制几乎都可以适用于参数。

21. 条件测试语句
MASM的条件测试的语法和C语言相同。
同样,对于不含比较符的单个变量或寄存器,MASM也是将所有非零值认为是“真”,零值认为是“假”。
与C语言的条件测试相同,MASM的条件测试伪操作符并不会改变被测试的变量或寄存器的值。
MASM的条件测试伪操作符经过编译器编译会翻译成类似cmp或test之类的比较或位测试的指令。

MASM条件测试的基本表达式如下:
寄存器或变量 操作符 操作数
两个以上的表达式可以用逻辑运算符连接:
(表达式1)逻辑运算符(表达式2)逻辑运算符(表达式3)...

条件测试中的操作符和逻辑运算符如下表

操作符和逻辑运算符 操作 用途 
== 等于 变量和操作数之间的比较 
!= 不等于 变量和操作数之间的比较 
> 大于 变量和操作数之间的比较 
>= 大于等于 变量和操作数之间的比较 
< 小于 变量和操作数之间的比较 
<= 小于等于 变量和操作数之间的比较 
& 位测试 将变量和操作数做“与”操作 
! 逻辑取反 对变量取反或对表达式的结果取反 
&& 逻辑与 对两个表达式的结果进行逻辑“与”操作 
|| 逻辑或 对两个表达式的结果进行逻辑“或”操作



MASM的条件测试语句有如下几点限制:
a) 表达式的左边只能是变量或寄存器,不能为常数;
b) 表达式的两边不能同时为变量,但可以同时为寄存器;
这些限制来自于80x86的指令。

以下一些系统标志寄存器中的各种标志位的状态指示,本身相当于一个表达式:
CARRY? 表示Carry位是否置位
OVERFLOW? 表示Overflow位是否置位
PARITY? 表示Parity位是否置位
SIGN? 表示Sign位是否置位
ZERO? 表示Zero位是否置位

22. 分支语句
MASM中的分支伪指令的语法如下:
.if 条件表达式1
表达式1为“真”时执行的指令
[.elseif 条件表达式2]
表达式2为“真”时执行的指令
[.elseif 条件表达式3]
表达式3为“真”时执行的指令
...
[.else]
所有表达式为“否”时执行的指令
.endif

注意:
关键字if/elseif/else/endif的前面有个小数点,如果不加小数点,就变成宏汇编中的条件汇编伪操作。功能完全不一样。
if/else/endif是宏汇编中条件汇编宏操作的伪操作指令,作用是根据条件决定在最后的可执行文件中包不包括某一段代码。

由.if/.elseif/.else/.endif条件分支伪指令构成的分支结构只能有一个条件被满足。
如果需要构成的分支结构对于所有的表达式为“真”都要执行相应的代码,可以利用多个.if/endif来完成,如下:
.if 表达式1
表达式1为“真”要执行的指令
.endif
.if 表达式2
表达式2为“真”要执行的指令
.endif

23. 循环语句
循环语句的语法如下:
.while 条件测试表达式
指令
[.break[.if 退出条件]] ;如果.break伪指令后面跟一个.if测试伪指令的话,那么当退出条件为“真”时才执行.break伪指令。
[.continue]
.endw

.repeat
指令
[.break[.if 退出条件]] ;如果.break伪指令后面跟一个.if测试伪指令的话,那么当退出条件为“真”时才执行.break伪指令。
[.continue]
.until 条件测试表达式(或.untilcxz [条件测试表达式])

其中,.while/.break/.continue/.endw/.repeat/.until/.untilcxz都是伪指令。

循环体中可以使用.break伪指令强制退出循环。
循环体中可以使用.continue伪指令忽略以后的指令。

.while/.endw和.repeat/.until的区别如下:
a) 前者可能一次也不会执行循环体内的指令,而后者至少会执行一次循环体内的指令。
b) 前者当判断条件为FALSE时退出循环,而后者当判断条件为TRUE时退出循环。

MASM的条件测试总是把操作数当作无符号数看待。
这就是说,在分支和循环的伪指令反汇编后可以发现,在使用>,>=,<和<=比较符时,MASM的伪指令总是将比较以后的跳转指令使用为jb和jnb等无符号数比较跳转的指令。
所以,如果程序中需要构造有符号数的比较分支或循环结构,那么必须另外用jl和jg等有符号数比较跳转的指令来完成,使用条件测试配合分支或循环伪指令可能会得到错误的结果

Win32汇编 寄存器

1、数据寄存器
数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。32位CPU有4个32位的 通用寄存器EAX、EBX、ECX和EDX。对低16位数据的存取,不会影响高16位的数据。这些低16位寄存器分别命名为:AX、BX、CX和DX,它 和先前的CPU中的寄存器相一致。

4个16位寄存器又可分割成8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有 自己的名称,可独立存取。程序员可利用数据寄存器的这种”可分可合”的特性,灵活地处理字/字节的信息。
寄存器AX和AL通常称为累加器 (Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、 除、输入/输出等操作,它们的使用频率很高; 寄存器BX称为基地址寄存器(Base Register)。它可作为存储器指针来使用; 寄存器CX称为计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数;在位操作 中,当移多位时,要用CL来指明移位的位数;
寄存器DX称为数据寄 存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也 可用于存放I/O的端口地址。在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,但在32位CPU中,其32位寄 存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性。
2、 变址寄存器
32位CPU有2个32位通用寄存器ESI和EDI。其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响高16位 的数据。
寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。变址 寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。它们可作一般的存储器指针使用。在字符串操作指令的执行过程中, 对它们有特定的要求,而且还具有特殊的功能。
3、指针寄存器
32位CPU有2个32位通用寄存器EBP和ESP。其低16位对应先前 CPU中的BP和SP,对低16位数据的存取,不影响高16位的数据。
寄存器EBP、ESP、BP和SP称为指针寄存器(Pointer Register),主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。指针寄存 器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。
它们主要用于访问堆栈内的存储单元,并且规定:
BP 为基指针(Base Pointer)寄存器,用它可直接存取堆栈中的数据;
SP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。
4、段寄存器
段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值 和一个偏移量组合而成
的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。
CPU内部的段寄存器:
CS--代 码段寄存器(Code Segment Register),其值为代码段的段值;
DS--数据段寄存器(Data Segment Register),其值为数据段的段值;
ES--附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
SS-- 堆栈段寄存器(Stack Segment Register),其值为堆栈段的段值;
FS--附加段寄存器(Extra Segment Register),其值为附加数据段的段值;
GS--附加段寄存器(Extra Segment Register),其值为附加数据段的段值。

在16位CPU系统中,它只有4个段寄存器,所以,程序在任何时刻至多有4个正在使用的段 可直接访问;在32位微机系统中,它有6个段寄存器,所以,在此环境下开发的程序最多可同时访问6个段。32位CPU有两个不同的工作方式:实方式和保护 方式。在每种方式下,段寄存器的作用是不同的。有关规定简单描述如下:
实方式: 前4个段寄存器CS、DS、ES和SS与先前CPU中的所对应的段寄存器的含义完全一致,内存单元的逻辑地址仍为”段值:偏移量”的形式。为访问某内存段 内的数据,必须使用该段寄存器和存储单元的偏移量。
保护方式: 在此方式下,情况要复杂得多,装入段寄存器的不再是段值,而是称为”选择子”(Selector)的某个值。
5、指令指针寄存器
32位 CPU把指令指针扩展到32位,并记作EIP,EIP的低16位与先前CPU中的IP作用相同。
指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情 况。所以,在理解它们的功能时,不考虑存在指令队列的情况。
在实方式下,由于每个段的最大范围为64K,所以,EIP中的高16位肯定都为0,此 时,相当于只用其低16位的IP来反映程序中指令的执行次序。
6、标志寄存器
一、运算结果标志位
1、进位标志CF(Carry Flag)
进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。使用该 标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等。
2、奇偶标志 PF(Parity Flag)
奇偶标志PF用于反映运算结果中”1″的个数的奇偶性。如果”1″的个数为偶数,则PF的值为1,否则其值为0。
利 用PF可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。
3、辅助进 位标志AF(Auxiliary Carry Flag)
在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:
(1)、在字 操作时,发生低字节向高字节进位或借位时;
(2)、在字节操作时,发生低4位向高4位进位或借位时。
对以上6个运算结果标志位,在一般编 程情况下,标志位CF、ZF、SF和OF的使用频率较高,而标志位PF和AF的使用频率较低。
4、零标志ZF(Zero Flag)
零标 志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
5、符号标志 SF(Sign Flag)
符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所 以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。
6、溢出标志OF(Overflow Flag)
溢 出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清 为0。”溢出”和”进位”是两个不同含义的概念,不要混淆。如果不太清楚的话,请查阅《计算机组成原理》课程中的有关章节。
二、状态控制标志位
状 态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。
1、追踪标志TF(Trap Flag)
当追踪标志TF被 置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。指令系统中没有专门的指令来改变标志位TF的 值,但程序员可用其它办法来改变其值。
2、中断允许标志IF(Interrupt-enable Flag)
中断允许标志IF是用来决定 CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部 产生的中断请求。具体规定如下:
(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;
(2)、当IF=0 时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
CPU的指令系统中也有专门的指令来改变标志位IF的值。
3、方向标志 DF(Direction Flag)
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节--字 符串操作指令--中给出。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值。
三、32位标志寄存器增加的标志位
1、I/O特 权标志IOPL(I/O Privilege Level)
I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行 I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。
2、嵌套任务标志 NT(Nested Task)
嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:
(1)、当NT=0,用堆栈中保存 的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;
(2)、当NT=1,通过任务转换实现中断返回。
3、重启动标志 RF(Restart Flag)
重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示”接受”调试故障,否则拒绝之。在成功执行完 一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。
4、虚拟8086方式标志VM(Virtual 8086 Mode)
如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

win32 汇编基础概念整理 的相关文章

  • 【C#】VS2019 未能在命名空间“Microsoft.Win32”中找到类型名“RegistryKey” 的解决办法

    文章目录 前言解决方案 结语 前言 今天在写 C 实验的时候遇到了 未能在命名空间 Microsoft Win32 中找到类型名 RegistryKey 此类型已转发到程序集 Microsoft Win32 Registry Version
  • Win32控制台应用程序点击关闭按钮后如何等待当前任务处理完成后再结束应用

    在开发控制台应用程序时经常会遇到一种情况 xff0c 就是当点击关闭按钮时程序仍在处理一些任务 xff0c 此时如果强制退出会导致任务处理异常 因此 xff0c 程序需要响应点击关闭按钮的事件并等待当前任务处理完成 具体方法如下 xff0c
  • 常见 win32.com 操作

    import win32com client from win32api import RGB xlapp 61 win32com client gencache EnsureDispatch 34 Ket Application 34 x
  • Win32窗口

    Win32窗口 span class token comment windows 开发所需头文件 包含Windows开发所需要的宏 类 函数 结构体等结构的定义 span span class token macro property sp
  • Windows下的Win32串口编程

    在工业控制中 xff0c 工控机 xff08 一般都基于Windows平台 xff09 经常需要与智能仪表通过串口进行通信 串口通信方便易行 xff0c 应用广泛 一般情况下 xff0c 工控机和各智能仪表通过RS485总线进行通信 RS4
  • VS2015项目属性配置中的变量设置

    关于VS的项目属性配置 很长时间都没有去细究关于各种定义的意义 比如说 outDir这样的东西以前没有细细追究过原因 这次都一并做个记录 1 常规页签 是对各个变量的解释输出目录 OutDir 中间目录 IntDir 目标文件名 Targe
  • mciSendString的介绍

    转载至 http blog sina com cn s blog 149e9d2ec0102wzcn html 使用MCI API 源文件中需要包含头文件Mmsystem h 在Project gt Settings gt Link gt
  • 通过文件夹文件获取文件夹大小

    思路就是便利文件夹下的每个文件 碰到子文件夹递归进去继续找文件 所有的文件大小累加起来 int GetFolderSize LPCTSTR szPath TCHAR szFileFilter 512 TCHAR szFilePath 512
  • C++ win32编程 02 常见消息

    02 常见消息 1 打印消息相关信息 1 1 将消息内容转化为字符串 第一步 定义字符串变量 用来保存转化后的消息 wchar t szInfo 300 定义消息内容变量 第二步 用宽字符格式化函数转化消息内容 wsprintf szInf
  • 【mfc】学生信息管理,实现List控件节点的增删改查

    之前在mfc这个专栏里面 写了很多关于win32的程序 其实也没什么的 win32是mfc的基础 mfc只是win32的扩展 系统自带的扩展 新建一个mfc如同新建一个win32程序 不过这个win32程序一开始就带了很多空函数框架 一 基
  • 桌面下雪小程序 WIN32

    想起以前还没有上大学的时候 过圣诞节 有同学发了一个桌面下雪的小程序 当看到效果的 哇 当时觉得好高端 就想什么时候我也能写出这么一个程序 学了计算机之后 发现这完全可以实现 于是就准备写一个 当卡壳的时候在网上找资料 结果发现在网上找到的
  • 让CPU画出图形(其实很简单的)

    本例子是当初微软的一个题目 希望windows任务管理器的CPU的占有率 是一个正旋曲线 如果是你 你会如何解决这个问题呢 先上图吧 由于cpu要处理其他电脑程序 只能画出来大概的模样 其实我当时想这个问题时候 是不是考虑对cpu进行操作
  • windows 各种消息

    win32 消息
  • 20_删除某个文件夹RemoveDirectory()

    删除某个文件夹RemoveDirectory 思想是RemoveDirectory 只能删除一个空的目录 如果目录中有子目录或者是子文件的话就会删除失败的 解决的方法是递归的思想 将子文件删除 之后就可以调用这个函数删除一个空的文件夹了 删
  • WIN32 代码测试(Control)

    include
  • WIN32 资源

    首先解释一下句柄 win32中的句柄在数值上表示一个32位的数 用来标识应用程序 进程中不同对象以及同类对象中的不同实例 而所谓实例就是指被实例化的对象 实例化的过程就是通过类创建对象的过程 实例化对象的目地是为对象开辟内存空间 所以句柄是
  • 有关cocos2d创建c++项目,并把win32项目打包成apk文件

    首先进入cocos2d资源包文件夹例如 cd D Cocos Cocos2d x cocos2d x 3 10 再输入cocos new try项目名 p org cocos2dx 包名 l cpp d codes 创建cocos2d 3
  • win32读取注册表

    直接代码 bool bIsIE6 false HKEY hKey NULL DWORD dwType DWORD dwSize LONG lReg RegOpenKey HKEY CLASSES ROOT HTTP shell open c
  • ShellExecuteEx中与被调进程同步

    在实际的开发中会遇到这样的情况 A进程在运行时 需要调起B进程完成某些工作 例如取回关键文件 且必须等待该进程完成工作结束后才能往下继续 那么这时候 就可以采用ShellExecuteEx和WaitForSingleObject的结合对被调
  • Win32 文件分割合并

    CFileSplitUtils h pragma once include

随机推荐

  • MFC中画直线和曲线的几种方法

    一 画直线 要想在MFC中画出有颜色的线条 xff0c 首先就要设置DC的画笔 xff0c 我们可以按如下方法来设置画笔 xff1a 第一步 xff1a 在View类中添加一个COLORREF类型的数据成员m Color xff08 用来保
  • C++函数的传入参数是指针的指针(**)的详解

    要修改变量的值 xff0c 需要使用变量类型的指针作为参数或者变量的引用 如果变量是一般类型的变量 xff0c 例如int xff0c 则需要使用int 类型的指针类型int 作为参数或者int的引用类型int amp 但是如果变量类型是指
  • 零基础学习WinCE开发

    在接触WinCE时候的基础 xff1a 软件语言基础 xff1a C C 43 43 C 我不是计算机专业的 xff0c 所以这些语言基础也是比较业余的 经验不丰富 xff0c 遇到问题就是查MSDN或者到网上查找相关解决方案即可 xff0
  • SD-WAN为什么备受欢迎?

    SD WAN即软件定义广域网 xff0c 通过集中控制器 xff0c 将广阔地理范围内的计算机 云服务 数据中心集中起来统一管理 比较多地用于企业组网场景 xff0c 那么为什SD WAN如此受欢迎呢 xff1f 近年来 xff0c 企业的
  • C#实现缩放和剪裁图片的方法示例

    C 实现缩放和剪裁图片的方法 分享给大家供大家参考 xff0c 具体如下 xff1a 1 2 3 4 5 6 7 8 9 10 11
  • 虚拟内存解疑

    虚拟内存别称 虚拟存储器 xff08 Virtual Memory xff09 电脑 中所运行的 程序均需经由 内存执行 xff0c 若执行的程序占用内存很大或很多 xff0c 则会导致内存消耗殆尽 为解决该问题 xff0c Windows
  • windows内存结构概述

    13 1 Windows的虚拟地址空间安排 13 1 1虚拟地址空间的分区 xff08 即虚拟地址空间布局 xff09 进程的地址空间划分 分区 x86 32位 Windows 3GB用户模式下的x86 32位Windows X64 64位
  • 变量名和内存地址及符号表

    1 变量名是给编译器看的 xff0c 编译器根据变量是局部还是全局分配内存地址或栈空间 xff0c 所谓的变量名在内存中不存在 xff0c 操作时转换成地址数存放在寄存器中了 其实可以理解为是符号表起到了连接作用 2 符号表 xff08 此
  • C/C++编译和链接过程详解 概述 (重定向表,导出符号表,未解决符号表)

    详解link 有 些人写C C 43 43 以下假定为C 43 43 程序 xff0c 对unresolved external link或者duplicated external simbol的错误信息不知所措 xff08 因为这样的错误
  • 编译器构造概述(详细)

    一 编译器简介 前面谈到静态链接器构造的基本流程 xff0c 最后提到所构造的链接器若要能正常工作的前提是需要构造一个能生成符合链接器输入文件格式的编译器 xff0c 本文构造一个符合这种具体格式要求编译器 但是编译器的直接编译的结果一般是
  • nm命令中符号类型详解

    nm命令介绍的很多 xff0c 但大多不介绍其函数符号标志的含义 最近在调试动态库时常用到 xff0c 其中用的最多的用法 nm A grep aaa c 43 43 filt A 为了显示文件 xff0c c 43 43 filt转换为可
  • 电脑怎样执行编程语言的?

    链接 xff1a https www zhihu com question 29227521 answer 154819061 来源 xff1a 知乎 著作权归作者所有 商业转载请联系作者获得授权 xff0c 非商业转载请注明出处 这个问题
  • 汇编中的标号概念

    地址标号和数据标号 地址标号 1 assume cs code 2 code segment 3 a db 1 2 3 4 5 6 7 8 4 b dw 0 5 start mov si offset a 6 mov bx offset b
  • 汇编语言不带冒号标号的用法

    汇编语言中 xff0c 有一种编程方法 xff1a 直接定址表 这种方法和高级语言 xff0c C语言中的数组有类似的思想 xff0c 就是将参数编排在一起 xff0c 然后通过数组名的调用 xff0c 得到需要的数值 汇编语言中 xff0
  • 实战录 | 基于openflow协议的抓包分析

    实战录 导语 云端卫士 实战录 栏目定期会向粉丝朋友们分享一些在开发运维中的经验和技巧 xff0c 希望对于关注我们的朋友有所裨益 本期分享人为云端卫士安全SDN工程师宋飞虎 xff0c 将带来基于openflow协议的抓包分析 一 什么是
  • 汇编中的标号

    当程序中要跳转到另一位置时 xff0c 需要有一个标识来指示新的位置 xff0c 这就是标号 xff0c 通过在目标地址的前面放上一个标号 xff0c 可以在指令中使用标号来代替直接使用地址 使用变量是任何编程语言都要遇到的工作 变量是计算
  • win32常用的汇编指令和寄存器

    通用寄存器 EAX 累加 Accumulator 寄存器 AX AH AL 常用于乘 除法和函数返回值 EBX 基址 Base 寄存器 BX BH BL 常做内存数据的指针 或者说常以它为基址来访问内存 ECX 计数器 Counter 寄存
  • win32汇编寄存器汇总

    32位CPU所含有的寄存器有 xff1a 4个数据寄存器 EAX EBX ECX和EDX 2个变址和指针寄存器 ESI和EDI 2个指针寄存器 ESP和EBP 6个段寄存器 ES CS SS DS FS和GS 1个指令指针寄存器 EIP 1
  • 汇编指令ebp与esp的关系与作用

    可以看到 xff0c 初始情况下 xff0c ebp此时值为0012FEDC xff0c 也就是栈帧的地址 xff0c 而栈顶地址esp值为0012FDFC 可以看到两个值有一定的关系 而 帧指针 的地址较高 然后我们让它执行前两句 xff
  • win32 汇编基础概念整理

    一 关于寄存器 寄存器有EAX EBX ECX EDX EDI ESI ESP EBP等 xff0c 似乎IP也是寄存器 xff0c 但只有在CALL RET在中会默认使用它 xff0c 其它情况很少使用到 xff0c 暂时可以不用理会 E