我們知道, 一個程序要運行, 首先要將程序加載進入內(nèi)存中, 然后運行其中的代碼, 同時, 我們的程序也會在運行過程中, 保存一部分?jǐn)?shù)據(jù)到內(nèi)存中.
我們知道, 可以運行的代碼, 例如求解一個方程組, 都是放在代碼段中的
數(shù)據(jù)段中則會存放一些初始化數(shù)據(jù), 例如我們的已經(jīng)初始化的全局變量
而棧段則會存放運行中的堆棧信息, 臨時變量等.
當(dāng)然還會有堆區(qū)等其他區(qū)段, 這里暫且忽略.
對于計算機來講, 所有的代碼和數(shù)據(jù)都可以2進制形式存放在內(nèi)存中, 那么對于某一個特定的內(nèi)存地址, 我們?nèi)绾伪鎰e這個數(shù)據(jù)是代碼還是普通數(shù)據(jù)?
總所周知, cpu 內(nèi)部有若干寄存器, 有些寄存器是通用寄存器, 可以存放普通的數(shù)據(jù), 例如用來存放加法指令的結(jié)果.
而還有些則有自己特定的功能.
對于代碼段來說, cpu 需要知道當(dāng)前的代碼在內(nèi)存中的哪個位置, 這一點, 就是靠的CS, IP 寄存器(8086 CPU, 下同).
例如, 當(dāng)CS 為 0x1000, IP 為 0x0001, 那么下一句代碼的位置就在 CS*0x10+IP = 0x100001 這個地址. CS 就是代碼段地址, 而IP 則是代碼段偏移量, 可以看到, 當(dāng)前有一個代碼段位于 0x10000 處.
當(dāng)cpu 需要從內(nèi)存中讀取某個全局變量時, 則會用到另外一個寄存器, DS, 例如我們有一個全局變量位置在 0x20005 處. 我們?nèi)绻x取可以使用類似下面的指令
mov ds, 0x2000; 給ds 寄存器賦值, 相當(dāng)于 ds=0x2000,實際上沒有這個指令, 并不能直接給ds 賦值, 需要使用其他寄存器中轉(zhuǎn), 類似于 ax=0x2000; ds=ax 這樣. 這里為了方便理解, 就這樣寫了.
mov ax, [5]; 讀取制定內(nèi)存位置的數(shù)據(jù)到寄存器中. [5]表示偏移量, 偏移量是相對于ds 來說, 這里就相當(dāng)于 ax=*((int*)(ds+5))
也就是說, DS 寄存器標(biāo)識了一個段, 我們這里存在一個段在 0x2000 處.
同理, 棧也有寄存器, SP 和 SS, SS 表示棧頂, SP 標(biāo)識偏移量, SS 就標(biāo)識了一個棧段.
這里就存在一個問題, 起始我們是可以任意修改這些寄存器的, 也就是說, 何處是代碼段, 何處是數(shù)據(jù)段是我們自己規(guī)定的, CPU 并不知道, 它只知道CS:IP 這個地方的數(shù)據(jù)需要讀取出來運行, 而 SS:SP 這個位置, 可以讀取一個棧的數(shù)據(jù).
所有的一切, 都只是我們的一廂情愿. 你如果錯誤的將CS:IP 指向了一個數(shù)據(jù)段的地址, 那么CPU 運行時可能就直接異常退出了, 同理, 當(dāng)嘗試從代碼段讀取數(shù)據(jù)時, 也只會讀到一段莫名其妙的數(shù)據(jù).CPU 并不會幫你處理這些異常的行為, 一切都在你自己手里.
還有一個問題, 就是這些寄存器都只有一個起始位置, 并沒有一個能表示長度的寄存器, 特別是對于棧段.例如, 我們規(guī)定0x30000-0x3000F 保存的是棧中的數(shù)據(jù), 0x30010-0x3001F 保存的是代碼, 那么如果我們在棧區(qū)多次pop, 就有可能跑道代碼區(qū)里面去了, 就有可能會錯誤修改代碼區(qū)數(shù)據(jù), 導(dǎo)致程序無法正常運行.