1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        多個段的程序

        共 7359字,需瀏覽 15分鐘

         ·

        2023-03-06 00:19

        點擊藍(lán)色“ 程序員cxuan ”即可關(guān)注作者

        加個“ 星標(biāo) ”,及時接收最新文章

        63252f6b316be72c19c6fd50705427c5.webp

        這是 x86 匯編連載系列第七篇文章,前六篇文章見文末。

        上回我們簡單認(rèn)識了一下什么是段,段前綴和一段安全的段空間是哪里,但是程序中不會僅有一個段,復(fù)雜程序必然是包含多個段的,這篇文章我們就來了解下多個段的相關(guān)程序。

        內(nèi)存地址空間是由操作系統(tǒng)直接管理和分配的,一般操作系統(tǒng)分配空間有兩種方式:

        1. 程序由磁盤載入內(nèi)存中時,會由操作系統(tǒng)直接為程序分配其運行所需要的內(nèi)存空間。
        2. 程序在運行時可以動態(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)行存儲,簡單一點的方式是通過循環(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 :

        d814f971e8ef5bcf3904b13402bf2ac0.webp

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

        1a897fd088cb8a9f54ff3bb74090f31d.webp

        這前幾條指令都是什么東西?怎么 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 指向程序的第一條指令。

        這兩種方式看起來哪個都有一定的局限性,那么還有沒有更簡潔一點的方式呢?

        看下面這段代碼

              
                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)過我的實驗驗證是可以的,為什么呢?

        關(guā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 處呢?

        856420fbd3ea3476356922b579d3ef49.webp

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

        2e367effc4f237bfb114c981bc0a1b8f.webp

        我們通過 -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

        如何實現(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í)行后的棧段如下圖所示

        142190885be5bf29b93ab5d97073c27e.webp

        這是程序被載入后的內(nèi)存分配情況,可以看到,cs:0 ~ cs:f 存儲的是 8 個字型數(shù)據(jù),cs:10 ~ cs:2f 存儲的是 16 個字型 0 數(shù)據(jù)。

        程序執(zhí)行完成后的內(nèi)存分配圖如下,可以看到已經(jīng)實現(xiàn)了數(shù)據(jù)的逆序存放。

        92c26e54d5c243e598594a82f832f622.webp

        可見,我們定義這些數(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ù)、代碼和棧分別放入各自的段中。

        比如我們通過多個段的方式將上面實現(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ù)據(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

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


        bba81a32664e749883e89ec54935e86d.webp


        ?往期推薦?

        愛了愛了,這篇寄存器講的有點意思

        沒錯!cxuan 對匯編下手了

        手把手教你匯編 Debug

        手撕匯編。。。

        來看三段程序

        簡單聊聊什么是段

        瀏覽 49
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            激情三级视频 | 丁香五月天婷国产 | 精品 黄色 | 一级视频在线观看免费 | 香蕉狠狠爱视频 | 成人高清在线观看 | 国产精品久久久久久久久久直播 | 夜夜夜夜夜操 | 青青欧美 | 一级特黄特色的免费大片视频 |