這是 x86 匯編連載系列第七篇文章,前六篇文章見文末。
上回我們簡單認(rèn)識了一下什么是段,段前綴和一段安全的段空間是哪里,但是程序中不會僅有一個段,復(fù)雜程序必然是包含多個段的,這篇文章我們就來了解下多個段的相關(guān)程序。
內(nèi)存地址空間是由操作系統(tǒng)直接管理和分配的,一般操作系統(tǒng)分配空間有兩種方式:
程序由磁盤載入內(nèi)存中時,會由操作系統(tǒng)直接為程序分配其運(yùn)行所需要的內(nèi)存空間。
程序在運(yùn)行時可以動態(tài)向操作系統(tǒng)申請分配內(nèi)存空間。
如果想要在程序被載入時獲得內(nèi)存空間分配,我們就需要在源程序中對其進(jìn)行聲明,通過定義多個段的方式來申請內(nèi)存空間。這樣的好處是能夠保證段內(nèi)的數(shù)據(jù)連續(xù),而且對于我們來說也能夠清晰明白的看懂程序邏輯,所以我們一般采用定義多個段的方式編寫程序。
在代碼段 cs 中使用數(shù)據(jù)
現(xiàn)在考慮這樣一個問題,如何累加下面這幾個數(shù)據(jù)的和,并把它放在一個 ax 寄存器中呢?
0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
我們當(dāng)然可以通過一個數(shù)據(jù)接一個數(shù)據(jù)這樣進(jìn)行累加,并把每次累加的結(jié)果都用 ax 進(jìn)行存儲,簡單一點(diǎn)的方式是通過循環(huán)的方式累加,一共需要累加 8 次數(shù)據(jù),用 ax 存儲。但是現(xiàn)在就有一個問題,這 8 個數(shù)據(jù)應(yīng)該放在哪呢?我們之前學(xué)的累加做法都是把他們放在一組連續(xù)的內(nèi)存單元,這樣我們就可以累加內(nèi)存中的數(shù)據(jù)來把它們進(jìn)行累加了,但是如何把這些數(shù)據(jù)放在一個連續(xù)的內(nèi)容單元中呢?這段內(nèi)存單元又是從哪找呢?
我們可以不用自己找內(nèi)存單元,直接讓操作系統(tǒng)分配,我們只需要定義這些數(shù)據(jù)并把它們放在一個連續(xù)的內(nèi)存單元即可,具體該怎么做呢?請看下面代碼
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h mov ax,0 mov bx,0 mov cx,8 s: add ax,cs:[bx] add bx,2 loop s mov ax,4c00h int 21h code ends end
上面匯編代碼中出現(xiàn)了之前我們沒有學(xué)過的 dw,dw 的含義是定義字型數(shù)據(jù),dw 的全稱就是 define word,上面代碼使用 dw 定義了 8 個字型數(shù)據(jù),它們所占用的空間為 16 個字節(jié)。
定義了數(shù)據(jù)之后,我們就需要對這 8 個數(shù)據(jù)進(jìn)行累加,我們該如何找到這 8 個數(shù)據(jù)呢?
仔細(xì)觀察上面代碼,我們可以知道這 8 個數(shù)據(jù)在代碼段 cs 中,所以我們可以通過 cs 來找到這幾個數(shù)據(jù),所以 cs 是段地址,而偏移地址是用 ip 表示的,也就是說這 8 個數(shù)據(jù)的偏移地址分別是 cs:0 cs:2 cs:4 cs:6 cs:8 cs:a cs:c cs:e 。
我們將上面這段代碼編寫、編譯和鏈接后,對其 exe 文件進(jìn)行 debug :
??????等下,這個 and ax,[bx+di] 是什么東西?再看下 cs:ip 的地址,是初始地址沒錯啊,為啥沒看到程序中的指令呢?我們再用 debug -u 看看
這前幾條指令都是什么東西?怎么 mov bx,0000 在偏移地址 0013 處?
從上圖中我們可以看到,程序被加載入內(nèi)存之后,所占內(nèi)存空間的前 16 個單元存放在源程序中用 dw 定義的數(shù)據(jù),后面的單元存放源程序中匯編指令所對應(yīng)的機(jī)器指令。
那么如何執(zhí)行匯編指令所定義的機(jī)器指令呢?你可以直接 -t 慢慢的執(zhí)行到 mov bx,0 處,也可以改變 ip 寄存器的值,也就是 ip = 10h,從而使 cs:ip 指向程序的第一條指令。
這兩種方式看起來哪個都有一定的局限性,那么還有沒有更簡潔一點(diǎn)的方式呢?
看下面這段代碼
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h start: mov ax,0 mov bx,0 mov cx,8 s: add ax,cs:[bx] add bx,2 loop s mov ax,4c00h int 21h code ends end start
仔細(xì)看這段代碼,和上面那段代碼有什么區(qū)別?
只有兩個區(qū)別,一是在 mov ax,0 前面加了一個 start: 標(biāo)志,并且在 end 后加了一個 start 標(biāo)志,這兩個標(biāo)志分別指向程序的開始處和結(jié)束處。
那么問題來了,為什么你加一個 start: 標(biāo)志就說這是程序的開始處,我隨便加個比如 begin: ,能不能成為程序的開始處呢?
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h begin: ...... code ends end begin
經(jīng)過我的實(shí)驗驗證是可以的,為什么呢?
關(guān)鍵點(diǎn)并不在于你定義的是什么標(biāo)志,而在于最后的 end,end 除了能夠告訴編譯器程序結(jié)束的位置外,還能夠告知編譯器程序是從哪里開始的,在上面代碼中我們用 start: 和 end start 告訴程序從 start 處開始,并執(zhí)行到 end start 處結(jié)束,而 mov ax,0 是程序的第一條指令。
程序開始標(biāo)志 start: 和 IP
我們知道,CS 和 IP 這兩個寄存器能夠指明程序的開始處和程序執(zhí)行的偏移地址,而且 start: 標(biāo)號指明了程序開始的地方,那么 start: 標(biāo)號所指向的偏移地址是不是我們之前討論的 10h 處呢?
我們編譯鏈接執(zhí)行程序看一下。
我們通過 -u 和 -r 分別執(zhí)行了一下,可以看到,程序的起始地址都是 076A:0010 ,這也就是說,除了我們 dw 定義的幾條數(shù)據(jù)外,IP 指向的偏移地址 0010 就是程序的開始處。
所以有了這種方法,我們就可以這樣安排程序框架:
assume cs:code code segment ...數(shù)據(jù) start: ... 代碼 code ends end start
在代碼段 cs 中使用棧
除了在 cs 代碼段中定義數(shù)據(jù),還可以在 cs 代碼段中定義棧,比如下面這個需求:
利用棧將程序定義的數(shù)據(jù)逆序存放,源代碼如下
assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ...... codesg ends end
如何實(shí)現(xiàn)將數(shù)據(jù)逆序存放呢?可以這么思考:
先定義一段和數(shù)據(jù)相同大小的??臻g,然后把數(shù)據(jù)入棧,然后再依次出棧就能夠?qū)崿F(xiàn)逆序存放了,因為棧是后入先出的數(shù)據(jù)結(jié)構(gòu),最開始 push 進(jìn)去的最后 pop 。
代碼如下:
assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 start:mov ax,cs mov ss,ax mov sp,30h mov bx,0 mov cx,8 s0:push cs:[bx] add bx,2 loop s mov bx,0 mov cx,8 s1:pop cs:[bx] add bx,2 loop s1 mov ax,4c00h int 21h codesg ends end start
解釋下上面這段代碼:
首先先定義了 16 個值為 0 的字型數(shù)據(jù),程序被載入內(nèi)存后,操作系統(tǒng)會為程序分配 16 個字型數(shù)據(jù)空間,用于存放 16 個數(shù)據(jù),把這段空間當(dāng)做棧來使用。
start 標(biāo)號處開始程序,首先先要設(shè)置一段空間為棧段,由于我們只有一個 cs 段,所以程序是從 cs 段開始的,剛開始 dw 的 8 個數(shù)據(jù)所需的地址空間為 cs:0 ~ cs:f,后面 dw 定義的 16 個字所需的地址空間是 cs:10 ~ cs:2f ,由于 ss:sp 這兩個寄存器始終指向棧頂,目前還沒有數(shù)據(jù)入棧,所以棧頂必須為 2f + 1 ,也就是 30h 才可。
編譯鏈接執(zhí)行后的棧段如下圖所示
這是程序被載入后的內(nèi)存分配情況,可以看到,cs:0 ~ cs:f 存儲的是 8 個字型數(shù)據(jù),cs:10 ~ cs:2f 存儲的是 16 個字型 0 數(shù)據(jù)。
程序執(zhí)行完成后的內(nèi)存分配圖如下,可以看到已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)的逆序存放。
可見,我們定義這些數(shù)據(jù)的最終目的,是通過它們?nèi)〉靡欢ㄈ萘康膬?nèi)存空間,所以我們在描述 dw 的作用時,可以說用它來定義數(shù)據(jù),也可以說用它來開辟一段內(nèi)存空間。比如上面的 dw 0123h ... 0987h ,可以說定義了 8 個字型數(shù)據(jù),也可以說開辟了 8 個內(nèi)存空間,它們的效果是一樣的。
多個段的使用
上面討論的內(nèi)容都是將數(shù)據(jù)和棧放入一個段中,這樣做雖然比較省事,但是程序邏輯不夠清晰,像個大雜燴一樣,而且都放入一個段中,這個段的內(nèi)存有可能不夠用(8086 CPU 中一個段最大不能超過 64 KB)。
為了能夠清晰說明程序邏輯,并且能夠容納數(shù)據(jù)和棧,我們一般使用多個段的方式分別將數(shù)據(jù)、代碼和棧分別放入各自的段中。
比如我們通過多個段的方式將上面實(shí)現(xiàn)逆序存放的程序進(jìn)行改寫,代碼如下
assume cs:codesg,ds:data,ss:stack data segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data ends stack segment dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 stack ends code segment start: mov ax,stack mov ss,ax mov sp,20h mov ax,data mov ds,ax mov bx,0 mov cx,8 s0: push [bx] add bx,2 loop s0 mov bx,0 mov cx,8 s1: pop [bx] add bx,2 loop s1 mov ax,4c00h int 21h code ends end start
如上代碼所示,在 assume 后面定義了三個段,這些都是偽指令,只是為了方便說明段的類型。然后分別在 data segment 和 stack segment 定義了相關(guān)數(shù)據(jù),最后再 code segment 編寫的程序代碼。
基本上代碼和在一個段中的定義是一樣的,值得說明是,同一個段中的 ss:sp 指向的是 ss:30 ,而在這段代碼中的 ss:sp 指向的是 ss:20 ,這個大家知道是怎么回事吧?由于數(shù)據(jù)會直接定義在 data segment 中,所以棧段的 16 個字型數(shù)據(jù)占用的空間就是 ss:0 ~ ss:1f,所以 ss:sp 指向 20h 處。
其實(shí),代碼段、數(shù)據(jù)段、棧段完全是我們自己定義的,但是并不是我們分別定義了 cs:code,ds:data,ss,stack 之后,程序就會把它們分別當(dāng)做程序段、數(shù)據(jù)段和棧段的。要知道這些和 assume 一樣都是偽指令,它們是由編譯器執(zhí)行的,CPU 并不知道這些指令的存在,所以我們必須要在程序中告訴 CPU 哪個是棧段、哪個是數(shù)據(jù)段。
該如何告訴 CPU 呢?
我們看下棧段的設(shè)置指令
mov ax,stack mov ss,ax mov sp,20h
這樣就會將 ss 指向 stack ,ss:sp 用來指向 stack 棧段的棧頂?shù)刂罚挥性谶@個指令執(zhí)行后,CPU 才會把 stack 段當(dāng)做棧段來使用。
相同的,CPU 如果想訪問 data 段中的數(shù)據(jù),則可用 ds 指向 data 段,用其他寄存器比如 bx 來存放 data 段中的偏移地址即可。
所以,我們完全可以按照下面這種方式來定義程序
assume cs:a,ds:b,ss:c b segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h b ends c segment dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 c ends a segment d: mov ax,c mov ss,ax mov sp,20h mov ax,b mov ds,ax mov bx,0 mov cx,8 s0: push [bx] add bx,2 loop s0 mov bx,0 mov cx,8 s1: pop [bx] add bx,2 loop s1 mov ax,4c00h int 21h code a end d
最終程序的功能和上面的一模一樣。
-
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
6817瀏覽量
88743 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
2976瀏覽量
73815 -
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
6686瀏覽量
123140 -
程序
+關(guān)注
關(guān)注
116文章
3762瀏覽量
80754 -
代碼
+關(guān)注
關(guān)注
30文章
4723瀏覽量
68236
原文標(biāo)題:多個段的程序
文章出處:【微信號:cxuangoodjob,微信公眾號:程序員cxuan】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論