0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文了解多個段的相關(guān)程序

程序員cxuan ? 來源:程序員cxuan ? 2023-03-08 14:28 ? 次閱讀

這是 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 :

d546cf78-ba6a-11ed-bfe3-dac502259ad0.png

??????等下,這個 and ax,[bx+di] 是什么東西?再看下 cs:ip 的地址,是初始地址沒錯啊,為啥沒看到程序中的指令呢?我們再用 debug -u 看看

d554b6ec-ba6a-11ed-bfe3-dac502259ad0.png

這前幾條指令都是什么東西?怎么 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 處呢?

d563a828-ba6a-11ed-bfe3-dac502259ad0.png

我們編譯鏈接執(zhí)行程序看一下。

d5806d8c-ba6a-11ed-bfe3-dac502259ad0.png

我們通過 -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í)行后的棧段如下圖所示

d58b941e-ba6a-11ed-bfe3-dac502259ad0.png

這是程序被載入后的內(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ù)的逆序存放。

d5982828-ba6a-11ed-bfe3-dac502259ad0.png

可見,我們定義這些數(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

最終程序的功能和上面的一模一樣。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(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)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    全面了解linux相關(guān)知識

    今天浩道跟大家分享linux實(shí)用場景相關(guān)的實(shí)例,助你全面了解linux相關(guān)知識。
    發(fā)表于 01-29 11:09 ?469次閱讀

    了解STM32啟動過程

    款芯片的啟動文件都值得去研究,因為它可是你的程序跑的最初一段路,不可以不知道。通過了解啟動文件,我們可以體會到處理器的架構(gòu)、指令集、中斷向量安排等內(nèi)容,是非常值得玩味的。
    發(fā)表于 05-08 09:44 ?3184次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>了解</b>STM32啟動過程

    詳解PLC子程序與子程序指令

    在編程時經(jīng)常會遇到相同的程序需要多次執(zhí)行的情況,如圖6-39所示,程序A要執(zhí)行兩次,編程時要寫兩相同的
    的頭像 發(fā)表于 12-14 13:33 ?7633次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b>詳解PLC子<b class='flag-5'>程序</b>與子<b class='flag-5'>程序</b>指令

    帶你了解步進(jìn)電機(jī)的相關(guān)知識

    帶你了解步進(jìn)電機(jī)的相關(guān)知識:相、線、極性和步進(jìn)方式2017-09-07 16:45這里不說步進(jìn)電機(jī)的 “細(xì)分” 實(shí)驗,只說下有關(guān)步進(jìn)電
    發(fā)表于 07-08 06:48

    了解BLDC與PMSM的區(qū)別

    參考文件:了解BLDC與PMSM的區(qū)別? ?????BLDC和PMSM電機(jī)區(qū)別???? ? STM32 FOC BLDC與PMSM的區(qū)別PS:總結(jié)語句用紅色標(biāo)出,看紅色字體即可?,F(xiàn)代電機(jī)與控制
    發(fā)表于 08-30 08:38

    了解LVGL的學(xué)習(xí)路線

    “本文大部分內(nèi)容來自LVGL官方文檔,手翻版,如有錯誤歡迎指正。”系列文章目錄、LVGL系列(了解LVGL的學(xué)習(xí)路線輕松
    發(fā)表于 12-07 12:55

    了解通信技術(shù)的常用名詞解釋

    了解通信技術(shù)的常用名詞解釋
    的頭像 發(fā)表于 06-19 17:55 ?5908次閱讀

    了解氣象觀測站是什么?

    了解氣象觀測站是什么?
    的頭像 發(fā)表于 09-12 13:17 ?1311次閱讀

    了解pcb電路板加急打樣流程

    了解pcb電路板加急打樣流程
    的頭像 發(fā)表于 11-08 14:21 ?6242次閱讀

    了解 PCB 的有效導(dǎo)熱系數(shù)

    了解 PCB 的有效導(dǎo)熱系數(shù)
    的頭像 發(fā)表于 11-24 15:48 ?1807次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>了解</b> PCB 的有效導(dǎo)熱系數(shù)

    了解剛?cè)峤Y(jié)合制造過程

    了解剛?cè)峤Y(jié)合制造過程
    的頭像 發(fā)表于 12-04 16:22 ?691次閱讀

    了解單向晶閘管的結(jié)構(gòu)及導(dǎo)電特性

    了解單向晶閘管的結(jié)構(gòu)及導(dǎo)電特性
    的頭像 發(fā)表于 12-05 15:52 ?1145次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>了解</b>單向晶閘管的結(jié)構(gòu)及導(dǎo)電特性

    了解相控陣天線中的真時延

    了解相控陣天線中的真時延
    的頭像 發(fā)表于 12-06 18:09 ?1796次閱讀

    帶你了解 DAC

    了解 DAC
    的頭像 發(fā)表于 12-07 15:10 ?8446次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b>帶你<b class='flag-5'>了解</b> DAC

    pcb應(yīng)變測試有多重要?了解!

    pcb應(yīng)變測試有多重要?了解!
    的頭像 發(fā)表于 02-24 16:26 ?1000次閱讀