《汇编语言手册(共12页).docx》由会员分享,可在线阅读,更多相关《汇编语言手册(共12页).docx(12页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精选优质文档-倾情为你奉上寄存器与存储器1. 寄存器功能. 寄存器的一般用途和专用用途. CS:IP 控制程序执行流程. SS:SP 提供堆栈栈顶单元地址. DS:BX(SI,DI) 提供数据段内单元地址. SS:BP 提供堆栈内单元地址. ES:BX(SI,DI) 提供附加段内单元地址. AX,CX,BX和CX寄存器多用于运算和暂存中间计算结果,但又专用于某些指令(查阅 指令表)。. PSW程序状态字寄存器只能通过专用指令(LAHF, SAHF)和堆栈(PUSHF,POPF)进行存取。2. 存储器分段管理. 解决了16位寄存器构成20位地址的问题. 便于程序重定位. 20位物理地址=段地址
2、* 16 + 偏移地址. 程序分段组织: 一般由代码段,堆栈段,数据段和附加段组成,不设置堆栈段时 则使用系统内部的堆栈。3. 堆栈. 堆栈是一种先进后出的数据结构 , 数据的存取在栈顶进行 , 数据入栈使堆栈向地址减小的方向扩展。. 堆栈常用于保存子程序调用和中断响应时的断点以及暂存数据或中间计算结果 。 .堆栈总是以字为单位存取指令系统与寻址方式1. 指令系统. 计算机提供给用户使用的机器指令集称为指令系统,大多数指令为双操作数指令。执行指令后,一般源操作数不变,目的操作数被计算结果替代。. 机器指令由CPU执行,完成某种运算或操作,8086/8088指令系统中的指令分为6类: 数据传送,
3、算术运算,逻辑运算,串操作,控制转移和处理机控制。2. 寻址方式. 寻址方式确定执行指令时获得操作数地址的方法. 分为与数据有关的寻址方式(7种)和与转移地址有关的寻址方式(4)种。. 与数据有关的寻址方式的一般用途:(1) 立即数寻址方式-将常量赋给寄存器或存储单元(2) 直接寻址方式-存取单个变量(3) 寄存器寻址方式-访问寄存器的速度快于访问存储单元的速度(4) 寄存器间接寻址方式-访问数组元素(5) 变址寻址方式(6) 基址变址寻址方式(7) 相对基址变址寻址方式 (5),(6),(7)都便于处理数组元素. 与数据有关的寻址方式中,提供地址的寄存器只能是BX,SI,DI或BP. 与转移
4、地址有关的寻址方式的一般用途:(1) 段内直接寻址-段内直接转移或子程序调用(2) 段内间接寻址-段内间接转移或子程序调用(3) 段间直接寻址-段间直接转移或子程序调用(4) 段间间接寻址-段间间接转移或子程序调用汇编程序和汇编语言1. 汇编程序. 汇编程序是将汇编语言源程序翻译成二进制代码程序的语言处理程序,翻译的过程称为汇编。2. 汇编语言. 汇编语言是用指令助记符,各种标识变量,地址,过程等的标识符书写程序的语言, 汇编语言指令与机器指令一 一对应。. 伪指令,宏指令不是由CPU执行的指令,而是由汇编程序在汇编期间处理的指令。. 伪指令指示汇编程序如何完成数据定义,存储空间分配,组织段等
5、工作。. 宏指令可简化程序并减少程序书写量。. 条件汇编伪指令的功能是确定是否汇编某段源程序,而不是实现程序分支,对未汇编的程序将不产生相应的目标代码。. 结构作为一种数据结构可将一组类型不同但有逻辑关联的数据组织在一起,便于 整体处理数据。. 记录可用于提高存储单元的利用率,将若干不足一个字节或字且有逻辑关联的信 息压缩存放在一个字节或字中。. 指令中的表达式在汇编期间计算,并且只能对常量或地址进行计算。程序设计基础1. 分支程序设计. 程序分支由条件转移指令或无条件转移指令实现. 存放若干目的转移地址或跳转指令的跳转表常用于实现多路分支. 条件转移指令只能实现偏移量为-128至+127字节
6、范围的转移. 无条件转移指令根据寻址方式可实现短转移(偏移量为-128至+127字节),段内转 移,段间转移。2. 循环程序设计. 可由循环控制指令或条件转移指令组织循环结构. 内层循环结构必须完全包含在外层循环结构内,并不能发生从循环结构外向循环 结构内的转移。3. 子程序设计. 子程序中应保护寄存器内容,并正确使用堆栈, 成对执行PUSH和POP指令,保证执行RET指令时堆栈栈顶为返回地址。. 主程序可通过寄存器,参数表,或堆栈传递参数给子程序 4. EXE文件和COM文件. 二者都是可执行文件. COM文件源程序的特点是: 第一条可执行指令的起始存放地址必须是100H,不能分段,不用定义
7、堆栈,所有过程为NEAR类型,直接用INT 20H 指令返回DOS。5. DOS功能调用与BIOS中断调用. 二者都是完成DOS系统提供给用户的输入/输出等常用功能,通过执行软中断指令 完成一次软中断服务。. DOS功能调用的中断服务程序是操作系统的一部分,存于RAM中; 而BIOS中断调用的中断服务程序存放在ROM中。输入/输出与中断系统1. 输入/输出的方式. 程序直接I/O方式: 用IN和OUT指令直接在端口级上进行I/O操作,数据传送方式 分为无条件传送方式和查询传送方式。. 中断传送方式: 由CPU响应中断请求完成中断服务。. DMA传送方式: 直接在存储器与外设之间传送数据。2.
8、有关中断的概念. 中断、中断源、中断请求、中断服务、中断向量、中断向量表、中断响应过程、中断指令、开中断、关中断、内部中断、外部中断、可屏蔽中断、非屏蔽中断。3. 键盘I/O、显示器I/O操作. 键盘的输入操作用BIOS的16H中断调用控制,也可直接访问60H端口(数据端口), 61H端口(状态端口)检测键盘的按键操作。. 对于特殊键(如Shift , Ctrl , Alt , NumLock , ScrollLock等键)的按动情况,可以直接从来40:17H单元取得有关信息。. 显示器的图形显示可以用BIOS的10H中断调用实现,另一种速度更快的方法是直 接读写视频缓冲区。4. 打印机I/O
9、操作由 INT 17H中断调用实现, 串行通讯口操作由 INT 14H中断调用实现。CLD Clear the direction flag (set to forward direction)将方向标志置0,使si和di增量,串处理从低地址向高地址处理8088 汇编速查手册一、数据传输指令它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.1. 通用数据传送指令.MOV传送字或字节.MOVSX先符号扩展,再传送.MOVZX先零扩展,再传送.PUSH把字压入堆栈.POP把字弹出堆栈.PUSHA把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.POPA把DI,SI,BP,SP,B
10、X,DX,CX,AX依次弹出堆栈.PUSHAD把EAX,ECX,EDX, EBX,ESP,EBP,ESI,EDI依次压入堆栈.POPAD把EDI,ESI,EBP,ESP,EBX,EDX, ECX,EAX依次弹出堆栈.BSWAP交换32位寄存器里字节的顺序XCHG交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )XADD先交换再累加.( 结果在第一个操作数里 )XLAT字节查表转换. BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即0-FFH); 返回 AL 为查表
11、结果. ( BX+AL-AL )2. 输入输出端口传送指令.INI/O端口输入. ( 语法: IN 累加器, 端口号DX )OUTI/O端口输出. ( 语法: OUT 端口号DX,累加器 )输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,其范围是 0-65535.3. 目的地址传送指令.LEA装入有效地址. 例: LEA DX,string;把偏移地址存到DX.LDS传送目标指针,把指针内容装入DS.例: LDS SI,string;把段地址:偏移地址存到DS:SI.LES传送目标指针,把指针内容装入ES.例: LES DI,string;把段地址:偏移地址存到
12、ES:DI.LFS传送目标指针,把指针内容装入FS.例: LFS DI,string;把段地址:偏移地址存到FS:DI.LGS传送目标指针,把指针内容装入GS.例: LGS DI,string;把段地址:偏移地址存到GS:DI.LSS传送目标指针,把指针内容装入SS.例: LSS DI,string;把段地址:偏移地址存到SS:DI.4. 标志传送指令.LAHF标志寄存器传送,把标志装入AH.SAHF标志寄存器传送,把 AH内容装入标志寄存器.PUSHF标志入栈.POPF标志出栈.PUSHD32位标志入栈.POPD32位标志出栈.二、算术运算指令ADD 加法.ADC带进位加法.INC加 1.A
13、AA加法的ASCII码调整.DAA加法的十进制调整.SUB减法.SBB带借位减法.DEC减 1.NEC求反(以 0 减之).CMP比较.(两操作数作减法,仅修改标志位,不回送结果).AAS减法的 ASCII码调整.DAS减法的十进制调整.MUL无符号乘法.IMUL整数乘法.以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),AAM乘法的ASCII码调整.DIV无符号除法.IDIV整数除法.以上两条,结果回送:商回送AL,余数回送AH, (字节运算);或商回送AX,余数回送DX, (字运算).AAD除法的ASCII码调整.CBW字节转换为字. (把AL中字节的符号扩展到AH中去)C
14、WD字转换为双字. (把AX中的字的符号扩展到DX中去)CWDE字转换为双字. (把AX中的字符号扩展到EAX中去)CDQ双字扩展.(把EAX中的字的符号扩展到EDX中去)三、逻辑运算指令AND与运算.OR或运算.XOR异或运算.NOT取反.TEST测试.(两操作数作与运算,仅修改标志位,不回送结果).SHL逻辑左移.SAL算术左移.(=SHL)SHR逻辑右移.SAR算术右移.(=SHR)ROL循环左移.ROR循环右移.RCL通过进位的循环左移.RCR通过进位的循环右移.以上八种移位指令,其移位次数可达255次.移位一次时, 可直接用操作码.如 SHL AX,1.移位1次时, 则由寄存器CL给
15、出移位次数.如MOV CL,04SHL AX,CL四、串指令DS:SI源串段寄存器:源串变址.ES:DI目标串段寄存器:目标串变址.CX重复次数计数器.AL/AX扫描值.D标志0表示重复操作中SI和DI应自动增量; 1表示应自动减量.Z标志用来控制扫描或比较操作的结束.MOVS串传送.( MOVSB传送字符.MOVSW传送字.MOVSD传送双字. )CMPS串比较.( CMPSB比较字符.CMPSW比较字. )SCAS串扫描.把AL或AX的内容与目标串作比较,比较结果反映在标志位.LODS装入串.把源串中的元素(字或字节)逐一装入AL或AX中.( LODSB传送字符.LODSW传送字.LODS
16、D传送双字. )STOS保存串. 是LODS的逆过程.REP当CX/ECX0时重复.REPE/REPZ当ZF=1或比较结果相等,且 CX/ECX0时重复.REPNE/REPNZ当ZF=0或比较结果不相等,且 CX/ECX0时重复.REPC当CF=1且CX/ECX0时重复.REPNC当CF=0且CX/ECX0时重复.五、程序转移指令1无条件转移指令 (长转移)JMP无条件转移指令CALL过程调用 RET/RETF过程返回.2条件转移指令 (短转移,-128到+127的距离内)( 当且仅当(SF XOR OF)=1时,OP1循环控制指令(短转移)LOOPCX不为零时循环.LOOPE/LOOPZCX
17、不为零且标志Z=1时循环.LOOPNE/LOOPNZCX 不为零且标志Z=0时循环.JCXZCX为零时转移.JECXZECX 为零时转移.4中断指令INT中断指令INTO 溢出中断IRET中断返回5处理器控制指令 HLT处理器暂停, 直到出现中断或复位信号才继续.WAIT当芯片引线TEST为高电平时使CPU进入等待状态.ESC转换到外处理器.LOCK封锁总线.NOP空操作.STC置进位标志位.CLC清进位标志位.CMC进位标志取反.STD置方向标志位.CLD清方向标志位.STI置中断允许位.CLI清中断允许位.六、伪指令DW定义字(2字节).PROC定义过程.ENDP过程结束.SEGMENT
18、定义段.ASSUME建立段寄存器寻址.ENDS段结束. END程序结束.汇编语言基础知识汇编语言和CPU以及内存,端口等硬件知识是连在一起的. 这也是为什么汇编语言没有通用性的原因. 下面简单讲讲基本知识(针对INTEL x86及其兼容机)=x86汇编语言的指令,其操作对象是CPU上的寄存器,系统内存,或者立即数. 有些指令表面上没有操作数, 或者看上去缺少操作数, 其实该指令有内定的操作对象, 比如push指令, 一定是对SS:ESP指定的内存操作, 而cdq的操作对象一定是eax / edx. 在汇编语言中,寄存器用名字来访问. CPU 寄存器有好几类, 分别有不同的用处:1. 通用寄存器
19、:EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(这个虽然通用,但很少被用做除了堆栈指针外的用途)这些32位可以被用作多种用途,但每一个都有专长. EAX 是累加器(accumulator), 它是很多加法乘法指令的缺省寄存器. EBX 是基地址(base)寄存器, 在内存寻址时存放基地址. ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器. EDX是.(忘了.哈哈)但它总是被用来放整数除法产生的余数. 这4个寄存器的低16位可以被单独访问,分别用AX,BX,CX和DX. AX又可以单独访问低8位(AL)和高8位(AH), BX,CX,D
20、X也类似. 函数的返回值经常被放在EAX中.ESI/EDI分别叫做源/目标索引寄存器(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串. EBP是基址指针(BASE POINTER), 它最经常被用作高级语言函数调用的框架指针(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:push ebp ;保存当前ebpmov ebp,esp ;EBP设为当前堆栈指针sub esp, xxx ;预留xxx字节给函数临时变量.这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的E
21、BP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.ESP 专门用作堆栈指针.2. 段寄存器:CS(Code Segment,代码段) 指定当前执行的代码段. EIP (Instruction pointer, 指令指针)则指向该段中一个具体的指令. CS:EIP指向哪个指令, CPU 就执行它. 一般只能用jmp, ret, jnz, call 等指令来改变程序流程,而不能直接对它们赋值.DS(DATA SEGMENT, 数据段) 指定一个数据段. 注意:在当前的计算机系统中, 代码和数据没有本质差别, 都是一串二进制数,
22、区别只在于你如何用它. 例如, CS 制定的段总是被用作代码, 一般不能通过CS指定的地址去修改该段. 然而,你可以为同一个段申请一个数据段描述符别名而通过DS来访问/修改. 自修改代码的程序常如此做.ES,FS,GS 是辅助的段寄存器, 指定附加的数据段.SS(STACK SEGMENT)指定当前堆栈段. ESP 则指出该段中当前的堆栈顶. 所有push/pop 系列指令都只对SS:ESP指出的地址进行操作.3. 标志寄存器(EFLAGS):该寄存器有32位,组合了各个系统标志. EFLAGS一般不作为整体访问, 而只对单一的标志位感兴趣. 常用的标志有: 进位标志C(CARRY), 在加法
23、产生进位或减法有借位时置1, 否则为0.零标志Z(ZERO), 若运算结果为0则置1, 否则为0符号位S(SIGN), 若运算结果的最高位置1, 则该位也置1. 溢出标志O(OVERFLOW), 若(带符号)运算结果超出可表示范围, 则置1. JXX 系列指令就是根据这些标志来决定是否要跳转, 从而实现条件分枝. 要注意,很多JXX 指令是等价的, 对应相同的机器码. 例如, JE 和JZ 是一样的,都是当Z=1是跳转. 只有JMP 是无条件跳转. JXX 指令分为两组, 分别用于无符号操作和带符号操作. JXX 后面的XX 有如下字母:无符号操作: 带符号操作:A = ABOVE, 表示高于
24、 G = GREATER, 表示大于B = BELOW, 表示低于 L = LESS, 表示小于C = CARRY, 表示进位或借位 O = OVERFLOW, 表示溢出S = SIGN, 表示负通用符号:E = EQUAL 表示等于, 等价于Z (ZERO)N = NOT 表示非, 即标志没有置位. 如JNZ 如果Z没有置位则跳转Z = ZERO, 与E同.如果仔细想一想,就会发现 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, .4. 端口端口是直接和外部设备通讯的地方。外设接入系统后,系统就会把外设的数据接口
25、映射到特定的端口地址空间,这样,从该端口读入数据就是从外设读入数据,而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关,系统总共提供对64K个8位端口的访问,编号0-65535. 相邻的8位端口可以组成成一个16位端口,相邻的16位端口可以组成一个32位端口。端口输入输出由指令IN,OUT,INS和OUTS实现,具体可参考汇编语言书籍。汇编指令的操作数可以是内存中的数据, 如何让程序从内存中正确取得所需要的数据就是对内存的寻址。 INTEL 的CPU 可以工作在两种寻址模式:实模式和保护模式。 前者已经过时,就不讲了, WINDOWS 现在
26、是32位保护模式的系统, PE 文件就基本是运行在一个 32位线性地址空间, 所以这里就只介绍32位线性空间的寻址方式。 其实线性地址的概念是很直观的, 就想象一系列字节排成一长队,第一个字节编号为0, 第二个编号位1, 。 一直到(十六进制FFFFFFFF,这是32位二进制数所能表达的最大值了)。 这已经有4GB的容量! 足够容纳一个程序所有的代码和数据。 当然, 这并不表示你的机器有那么多内存。 物理内存的管理和分配是很复杂的内容, 初学者不必在意, 总之, 从程序本身的角度看, 就好象是在那么大的内存中。 在INTEL系统中, 内存地址总是由段选择符:有效地址的方式给出。段选择符(SEL
27、ECTOR)存放在某一个段寄存器中, 有效地址则可由不同的方式给出。 段选择符通过检索段描述符确定段的起始地址, 长度(又称段限制), 粒度, 存取权限, 访问性质等。 先不用深究这些, 只要知道段选择符可以确定段的性质就行了。 一旦由选择符确定了段, 有效地址相对于段的基地址开始算。 比如由选择符1A7选择的数据段, 其基地址是, 把1A7 装入DS中, 就确定使用该数据段。 DS:0 就指向线性地址。 DS:1F5278 就指向线性地址5E5278。 我们在一般情况下, 看不到也不需要看到段的起始地址, 只需要关心在该段中的有效地址就行了。 在32位系统中, 有效地址也是由32位数字表示,
28、 就是说, 只要有一个段就足以涵盖4GB线性地址空间, 为什么还要有不同的段选择符呢? 正如前面所说的, 这是为了对数据进行不同性质的访问。 非法的访问将产生异常中断, 而这正是保护模式的核心内容, 是构造优先级和多任务系统的基础。 这里有涉及到很多深层的东西, 初学者先可不必理会。 有效地址的计算方式是: 基址+间址*比例因子+偏移量。 这些量都是指段内的相对于段起始地址的量度, 和段的起始地址没有关系。 比如, 基址=, 间址=400, 比例因子=4, 偏移量=20000, 则有效地址为: +400*4+20000=+1000+20000=。 对应的线性地址是+=。 (注意, 都是十六进制
29、数)。 基址可以放在任何32位通用寄存器中, 间址也可以放在除ESP外的任何一个通用寄存器中。 比例因子可以是1, 2, 4 或8。 偏移量是立即数。 如: EBP+EDX*8+200就是一个有效的有效地址表达式。 当然, 多数情况下用不着这么复杂, 间址,比例因子和偏移量不一定要出现。 内存的基本单位是字节(BYTE)。 每个字节是8个二进制位, 所以每个字节能表示的最大的数是, 即十进制的255。 一般来说, 用十六进制比较方便, 因为每4个二进制位刚好等于1个十六进制位, b = 0xFF。 内存中的字节是连续存放的, 两个字节构成一个字(WORD), 两个字构成一个双字(DWORD)。
30、 在INTEL架构中, 采用small endian格式, 即在内存中,高位字节在低位字节后面。 举例说明:十六进制数803E7D0C, 每两位是一个字节, 在内存中的形式是: 0C 7D 3E 80。 在32位寄存器中则是正常形式,如在EAX就是803E7D0C。 当我们的形式地址指向这个数的时候,实际上是指向第一个字节,即0C。 我们可以指定访问长度是字节, 字或者双字。 假设DS:EDX指向第一个字节0C: mov AL, byte ptr DS:EDX ;把字节0C存入AL mov AX, word ptr DS:EDX ;把字7D0C存入AX mov EAX, dword ptr D
31、S:EDX ;把双字803E7D0C存入EAX 在段的属性中,有一个就是缺省访问宽度。如果缺省访问宽度为双字(在32位系统中经常如此),那么要进行字节或字的访问,就必须用byte/word ptr显式地指明。 缺省段选择:如果指令中只有作为段内偏移的有效地址,而没有指明在哪一个段里的时候,有如下规则: 如果用ebp和esp作为基址或间址,则认为是在SS确定的段中; 其他情况,都认为是在DS确定的段中。 如果想打破这个规则,就必须使用段超越前缀。举例如下: mov eax, dword ptr edx ;缺省使用DS,把DS:EDX指向的双字送入eax mov ebx, dword ptr ES
32、:EDX ;使用ES:段超越前缀,把ES:EDX指向的双字送入ebx 堆栈: 堆栈是一种数据结构,严格地应该叫做“栈”。“堆”是另一种类似但不同的结构。SS 和 ESP 是INTEL对栈这种数据结构的硬件支持。push/pop指令是专门针对栈结构的特定操作。SS指定一个段为栈段,ESP则指出当前的栈顶。push xxx 指令作如下操作: 把ESP的值减去4; 把xxx存入SS:ESP指向的内存单元。 这样,esp的值减小了4,并且SS:ESP指向新压入的xxx。 所以栈是“倒着长”的,从高地址向低地址方向扩展。pop yyy 指令做相反的操作,把SS:ESP指向的双字送到yyy指定的寄存器或内
33、存单元,然后把esp的值加上4。这时,认为该值已被弹出,不再在栈上了,因为它虽然还暂时存在在原来的栈顶位置,但下一个push操作就会把它覆盖。因此,在栈段中地址低于esp的内存单元中的数据均被认为是未定义的。 最后,有一个要注意的事实是,汇编语言是面向机器的,指令和机器码基本上是一一对应的,所以它们的实现取决于硬件。有些看似合理的指令实际上是不存在的,比如: mov DS:edx, ds:ecx ;内存单元之间不能直接传送 mov DS, 1A7 ;段寄存器不能直接由立即数赋值 mov EIP, 3D4E7 ;不能对指令指针直接操作。高级语言程序的汇编解析 在高级语言中,如C和PASCAL等等
34、,我们不再直接对硬件资源进行操作,而是面向于问题的解决,这主要体现在数据抽象化和程序的结构化。例如我们用变量名来存取数据,而不再关心这个数据究竟在内存的什么地方。这样,对硬件资源的使用方式完全交给了编译器去处理。不过,一些基本的规则还是存在的,而且大多数编译器都遵循一些规范,这使得我们在阅读反汇编代码的时候日子好过一点。这里主要讲讲汇编代码中一些和高级语言对应的地方。 1. 普通变量。通常声明的变量是存放在内存中的。编译器把变量名和一个内存地址联系起来(这里要注意的是,所谓的“确定的地址”是对编译器而言在编译阶段算出的一个临时的地址。在连接成可执行文件并加载到内存中执行的时候要进行重定位等一系
35、列调整,才生成一个实时的内存地址,不过这并不影响程序的逻辑,所以先不必太在意这些细节,只要知道所有的函数名字和变量名字都对应一个内存的地址就行了),所以变量名在汇编代码中就表现为一个有效地址,就是放在方括号中的操作数。例如,在C文件中声明: int my_age; 这个整型的变量就存在一个特定的内存位置。语句 my_age= 32; 在反汇编代码中可能表现为: mov word ptr 007E85DA, 20 所以在方括号中的有效地址对应的是变量名。又如: char my_name11 = lianzi2000; 这样的说明也确定了一个地址,对应于my_name. 假设地址是007E85DC
36、,则内存中007E85DC=l,007E85DD=i, etc. 对my_name的访问也就是对这地址处的数据访问。 指针变量其本身也同样对应一个地址,因为它本身也是一个变量。如: char *your_name; 这时也确定变量your_name对应一个内存地址,假设为007E85F0. 语句your_name=my_name;很可能表现为: mov 007E85F0, 007E85DC ;your_name的内容是my_name的地址。 2. 寄存器变量 在C和C+中允许说明寄存器变量。register int i; 指明i是寄存器存放的整型变量。通常,编译器都把寄存器变量放在esi和ed
37、i中。寄存器是在cpu内部的结构,对它的访问要比内存快得多,所以把频繁使用的变量放在寄存器中可以提高程序执行速度。 3. 数组 不管是多少维的数组,在内存中总是把所有的元素都连续存放,所以在内存中总是一维的。例如,int i_array23; 在内存确定了一个地址,从该地址开始的12个字节用来存贮该数组的元素。所以变量名i_array对应着该数组的起始地址,也即是指向数组的第一个元素。存放的顺序一般是i_array00,01,02,10,11,12 即最右边的下标变化最快。当需要访问某个元素时,程序就会从多维索引值换算成一维索引,如访问i_array11,换算成内存中的一维索引值就是1*3+1
38、=4.这种换算可能在编译的时候就可以确定,也可能要到运行时才可以确定。无论如何,如果我们把i_array对应的地址装入一个通用寄存器作为基址,则对数组元素的访问就是一个计算有效地址的问题: ; i_array11=0x16 lea ebx,xxxxxxxx ;i_array 对应的地址装入ebx mov edx,04 ;访问i_array11,编译时就已经确定 mov word ptr ebx+edx*2, 16 ; 当然,取决于不同的编译器和程序上下文,具体实现可能不同,但这种基本的形式是确定的。从这里也可以看到比例因子的作用(还记得比例因子的取值为1,2,4或8吗?),因为在目前的系统中简
39、单变量总是占据1,2,4或者8个字节的长度,所以比例因子的存在为在内存中的查表操作提供了极大方便。 4. 结构和对象 结构和对象的成员在内存中也都连续存放,但有时为了在字边界或双字边界对齐,可能有些微调整,所以要确定对象的大小应该用sizeof操作符而不应该把成员的大小相加来计算。当我们声明一个结构变量或初始化一个对象时,这个结构变量和对象的名字也对应一个内存地址。举例说明: struct tag_info_struct int age; int sex; float height; float weight; marry; 变量marry就对应一个内存地址。在这个地址开始,有足够多的字节(s
40、izeof(marry)容纳所有的成员。每一个成员则对应一个相对于这个地址的偏移量。这里假设此结构中所有的成员都连续存放,则age的相对地址为0,sex为2, height 为4,weight为8。 ; marry.sex=0; lea ebx,xxxxxxxx ;marry 对应的内存地址 mov word ptr ebx+2, 0 . 对象的情况基本相同。注意成员函数具体的实现在代码段中,在对象中存放的是一个指向该函数的指针。 5. 函数调用 一个函数在被定义时,也确定一个内存地址对应于函数名字。如: long comb(int m, int n) long temp; . return
41、temp; 这样,函数comb就对应一个内存地址。对它的调用表现为: CALL xxxxxxxx ;comb对应的地址。这个函数需要两个整型参数,就通过堆栈来传递: ;lresult=comb(2,3); push 3 push 2 call xxxxxxxx mov dword ptr yyyyyyyy, eax ;yyyyyyyy是长整型变量lresult的地址 这里请注意两点。第一,在C语言中,参数的压栈顺序是和参数顺序相反的,即后面的参数先压栈,所以先执行push 3. 第二,在我们讨论的32位系统中,如果不指明参数类型,缺省的情况就是压入32位双字。因此,两个push指令总共压入了两
42、个双字,即8个字节的数据。然后执行call指令。call 指令又把返回地址,即下一条指令(mov dword ptr.)的32位地址压入,然后跳转到xxxxxxxx去执行。在comb子程序入口处(xxxxxxxx),堆栈的状态是这样的: (请回忆small endian 格式) yyyyyyyy -ESP 指向返回地址 前面讲过,子程序的标准起始代码是这样的: push ebp ;保存原先的ebp mov ebp, esp;建立框架指针 sub esp, XXX;给临时变量预留空间 . 执行push ebp之后,堆栈如下: yyyyyyyy old ebp - esp 指向原来的ebp 执行mov ebp,esp之后,ebp 和esp 都指向原来的ebp. 然后sub esp, xxx 给临时变量留空间。这里,只有一个临时变量temp,是一个长整数,需要4个字节,所以xxx=4。这样就建立了这个子程序的框架: yyyyyyyy old ebp - 当前ebp指向这里 temp 专心-专注-专业