我们知道, 一个程序要运行, 首先要将程序加载进入内存中, 然后运行其中的代码, 同时, 我们的程序也会在运行过程中, 保存一部分数据到内存中.
我们知道, 可以运行的代码, 例如求解一个方程组, 都是放在代码段中的
数据段中则会存放一些初始化数据, 例如我们的已经初始化的全局变量
而栈段则会存放运行中的堆栈信息, 临时变量等.
当然还会有堆区等其他区段, 这里暂且忽略.
对于计算机来讲, 所有的代码和数据都可以2进制形式存放在内存中, 那么对于某一个特定的内存地址, 我们如何辨别这个数据是代码还是普通数据?
总所周知, cpu 内部有若干寄存器, 有些寄存器是通用寄存器, 可以存放普通的数据, 例如用来存放加法指令的结果.
而还有些则有自己特定的功能.
对于代码段来说, cpu 需要知道当前的代码在内存中的哪个位置, 这一点, 就是靠的CS, IP 寄存器(8086 CPU, 下同).
例如, 当CS 为 0x1000, IP 为 0x0001, 那么下一句代码的位置就在 CS*0x10+IP = 0x100001 这个地址. CS 就是代码段地址, 而IP 则是代码段偏移量, 可以看到, 当前有一个代码段位于 0x10000 处.
当cpu 需要从内存中读取某个全局变量时, 则会用到另外一个寄存器, DS, 例如我们有一个全局变量位置在 0x20005 处. 我们如果要读取可以使用类似下面的指令
mov ds, 0x2000; 给ds 寄存器赋值, 相当于 ds=0x2000,实际上没有这个指令, 并不能直接给ds 赋值, 需要使用其他寄存器中转, 类似于 ax=0x2000; ds=ax 这样. 这里为了方便理解, 就这样写了.
mov ax, [5]; 读取制定内存位置的数据到寄存器中. [5]表示偏移量, 偏移量是相对于ds 来说, 这里就相当于 ax=*((int*)(ds+5))
也就是说, DS 寄存器标识了一个段, 我们这里存在一个段在 0x2000 处.
同理, 栈也有寄存器, SP 和 SS, SS 表示栈顶, SP 标识偏移量, SS 就标识了一个栈段.
这里就存在一个问题, 起始我们是可以任意修改这些寄存器的, 也就是说, 何处是代码段, 何处是数据段是我们自己规定的, CPU 并不知道, 它只知道CS:IP 这个地方的数据需要读取出来运行, 而 SS:SP 这个位置, 可以读取一个栈的数据.
所有的一切, 都只是我们的一厢情愿. 你如果错误的将CS:IP 指向了一个数据段的地址, 那么CPU 运行时可能就直接异常退出了, 同理, 当尝试从代码段读取数据时, 也只会读到一段莫名其妙的数据.CPU 并不会帮你处理这些异常的行为, 一切都在你自己手里.
还有一个问题, 就是这些寄存器都只有一个起始位置, 并没有一个能表示长度的寄存器, 特别是对于栈段.例如, 我们规定0x30000-0x3000F 保存的是栈中的数据, 0x30010-0x3001F 保存的是代码, 那么如果我们在栈区多次pop, 就有可能跑道代码区里面去了, 就有可能会错误修改代码区数据, 导致程序无法正常运行.
网友评论