zoukankan      html  css  js  c++  java
  • [转]汇编语言的准备知识给初次接触汇编者 2

    汇编指令的操作数可以是内存中的数据, 如何让程序从内存中正确取得所需要的数据就是
    对内存的寻址。
    INTEL 的CPU 可以工作在两种寻址模式:实模式和保护模式。前者已经过时,就不讲
    了, WINDOWS 现在是32 位保护模式的系统, PE 文件就基本是运行在一个32 位线性
    地址空间, 所以这里就只介绍32 位线性空间的寻址方式。
    其实线性地址的概念是很直观的, 就想象一系列字节排成一长队,第一个字节编号为
    0, 第二个编号位1, 。。。。一直到4294967295(十六进制FFFFFFFF,这是32 位二
    进制数所能表达的最大值了)。这已经有4GB 的容量! 足够容纳一个程序所有的代码和数
    据。当然, 这并不表示你的机器有那么多内存。物理内存的管理和分配是很复杂的内
    容, 初学者不必在意, 总之, 从程序本身的角度看, 就好象是在那么大的内存中。
    在INTEL 系统中, 内存地址总是由"段选择符:有效地址"的方式给出。段选择符
    (SELECTOR)存放在某一个段寄存器中, 有效地址则可由不同的方式给出。段选择符通
    过检索段描述符确定段的起始地址, 长度(又称段限制), 粒度, 存取权限, 访问性质
    等。先不用深究这些, 只要知道段选择符可以确定段的性质就行了。一旦由选择符确定
    了段, 有效地址相对于段的基地址开始算。比如由选择符1A7 选择的数据段, 其基地址
    是400000, 把 1A7 装入DS 中, 就确定使用该数据段。 DS:0 就指向线性地址
    400000。 DS:1F5278 就指向线性地址5E5278。我们在一般情况下, 看不到也不需要看
    到段的起始地址, 只需要关心在该段中的有效地址就行了。在 32 位系统中, 有效地址也
    是由32 位数字表示, 就是说, 只要有一个段就足以涵盖4GB 线性地址空间, 为什么还
    要有不同的段选择符呢? 正如前面所说的, 这是为了对数据进行不同性质的访问。非法的
    访问将产生异常中断, 而这正是保护模式的核心内容, 是构造优先级和多任务系统的基
    础。这里有涉及到很多深层的东西, 初学者先可不必理会。
    有效地址的计算方式是: 基址间址*比例因子偏移量。这些量都是指段内的相对于段
    起始地址的量度, 和段的起始地址没有关系。比如, 基址=100000, 间址=400, 比例
    因子=4, 偏移量=20000, 则有效地址为:
    100000 400*4 20000=100000 1000 20000=121000。对应的线性地址是400000
    121000=521000。 (注意, 都是十六进制数)。
    基址可以放在任何32 位通用寄存器中, 间址也可以放在除ESP 外的任何一个通用寄
    存器中。比例因子可以是1, 2, 4 或8。偏移量是立即数。如: [EBP EDX*8 200]就是
    一个有效的有效地址表达式。当然, 多数情况下用不着这么复杂, 间址,比例因子和偏
    移量不一定要出现。
    内存的基本单位是字节(BYTE)。每个字节是8 个二进制位, 所以每个字节能表示的
    最大的数是11111111, 即十进制的255。一般来说, 用十六进制比较方便, 因为每4
    个二进制位刚好等于1 个十六进制位, 11111111b = 0xFF。内存中的字节是连续存放
    的, 两个字节构成一个字(WORD), 两个字构成一个双字(DWORD)。在 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 DS:[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:[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 指定的寄存器或内存单元,然后把esp 的值加上4。这时,认为该值已被弹出,不再
    在栈上了,因为它虽然还暂时存在在原来的栈顶位置,但下一个push 操作就会把它覆
    盖。因此,在栈段中地址低于esp 的内存单元中的数据均被认为是未定义的。
    最后,有一个要注意的事实是,汇编语言是面向机器的,指令和机器码基本上是一一
    对应的,所以它们的实现取决于硬件。有些看似合理的指令实际上是不存在的,比如:
    mov DS:[edx], ds:[ecx] ;内存单元之间不能直接传送
    mov DS, 1A7 ;段寄存器不能直接由立即数赋值
    mov EIP, 3D4E7 ;不能对指令指针直接操作。

    备注:忘记是在哪里收集的,没能注明原出处,若读者知道还请指出,谢谢

  • 相关阅读:
    Jenkins 集成Sonar scanner的使用案例
    Sonarqube 安装 及与Jenkins sonar scanner插件集成部署
    shell if 判断匹配1位数字
    使用tcpdump抓包
    golang + snap7 对西门子plc进行读写
    python基础练习题(题目 求输入数字的平方,如果平方运算后小于 50 则退出)
    python基础练习题(题目 统计 1 到 100 之和)
    python基础练习题(题目 使用lambda来创建匿名函数。)
    微服务状态之python巡查脚本开发
    python基础练习题(题目 计算两个矩阵相加)
  • 原文地址:https://www.cnblogs.com/yahue/p/2575376.html
Copyright © 2011-2022 走看看