1. 程式人生 > >16位匯編第三講 分段存儲管理思想

16位匯編第三講 分段存儲管理思想

結束 位置 main函數 操作數 fin 筆記 概念 小端 nds

      內存分段

一丶分段(匯編指令分段)

1.為什麽分段?

  因為分段是為了更好的管理數據和代碼,就好比C語言為什麽會有內存4區一樣,否則匯編代碼都寫在一起了,執行的話雖然能執行,但是代碼多了,數據多了,搞不清什麽是代碼

什麽是數據了.

匯編分段代碼

 1 e 1000:0 "Hello$" 首先給1000:0的物理地址寫入Hello字符串
 2 d 1000:0  顯示一下是否顯示成功
 3 
 4 mov ax,1000        給ax賦值數據,下面要分段了,所以需要給ax賦值
 5 mov ds,ax          開始分段(分配數據段),把ax的值給段寄存器ds,可能有人會說,ds也是段寄存器,為什麽不直接寫 mov ds,1000, 這裏因為是cs ds ss es等段寄存器是後面出來的,數據線沒有連接他們,所以通過地址加法器先給ax賦值,再給ds...賦值
 6 mov es,ax          (分配附加段)ax的值也給es賦值(ds和es一般都是相同段地址)
 7 mov ax,2000        給ax賦值2000
 8 mov ss,ax          給ss段寄存器賦值2000 (分配棧段)
 9 mov dx,0           給dx賦值字符串的偏移 (因為在指令字典中,dx是字符串的首地址的偏移,但是他是和ds數據段連用的,所以ds已經改為了1000,而1000*16 + 0偏移就是字符串的首地址,所以直接給即可)
10 mov ah,9           調用顯示hello,給參數9
11 int 21             系統調用(調用API)
12 mov ax,4c00        退出指令,給ax
13 int 21             系統調用(調用API)
14 ret                返回

指令圖片,變為100偏移處了

技術分享

註意一點,我們給mov dx,100的時候,其實是把100的偏移給dx,這樣 ds內容的段地址是1000,dx是100, 他會聯合起來去尋址,利用昨天的尋址公式找到物理地址, 1000 * 16 + 100 = 10100(實際物理地址)而實際物理地址就是字符串的首地址

所以下面調用可以正常顯示hello了

但是我們如果寫成 mov dx,[100] 那麽就相當於對當前的物理地址取內容給dx, 變成了從100的偏移中取得內容給dx,dx的值就變味6548了,因為小端模式,所以65先讀,又因為dx是16位寄存器,所以只能讀取2個word,

那麽這樣尋址就會錯誤了,等價於他去尋找字符串的首地址變成了 1000 * 16 + 6548 = ???反正結果是不對了,就會出現各種各樣的BUG

退出指令

mov ax,4c00 這個是操作系統提供的,用於退出匯編程序

如果不退出,ip的偏移就會出現錯誤,那麽就可能隨機的吧ip和cs聯合尋找的物理地址當做代碼段去執行,就會出現錯誤.所以直接退出.

int 21就是系統調用(也就是調用API)

二丶多個匯編程序變為一個匯編程序執行

想想以前,如果不能多人開發,那麽就不會出現各種遊戲和高級軟件了.

怎麽解決匯編程序多人開發

  上面說了,我們為了有效的區分代碼,數據.我們分段了,但是多人開發,每個代碼段怎麽辦,難道要規定好?

所以以前如果合並匯編程序,那麽要修改代碼段,然後修改偏移,最後讓兩個匯編程序執行到一起.

但是這樣是有規律的,所以後來就出現了連接器 link(連接成Obj)link的作用就是專門修復段,還有修復段偏移的,達到兩個程序就可以在一起都執行了

當然OBJ網上有開源的文件格式可以研究一下.

這樣方法,叫做重定向,obj首次發明了出來,那麽這個時候就有了連接的概念了,

obj最簡單的文件格式

代碼段 代碼段長度

數據段 數據段長度

附加段 附加段長度

等等,當然可能更加詳細.但是這樣通過把另一個程序的段還有數據長度,都修改一下,就完成了兩個匯編語言合並到一起就可以都執行了.

三丶編譯器的出現

上面說的debug只是一個調試器,或者叫做翻譯器

現在出現了一個編譯器,編譯器就規定了語法了,然後那個時候我們可以把我們的程序,按照編譯器的語法,編譯成匯編代碼

比如分段

1.代碼段

MyCode segment

  ....你的匯編代碼

MyCode ends

那麽這樣就把代碼段分好了(專門執行代碼)(但是這樣雖然分好了,但是永遠不會執行)
因為CS和IP是確定代碼執行的位置,顯然我們這只是把段分好了,但是CS和Ip還沒有修改,也不能修改,因為一開始就是默認的,怎麽辦,
所以現在在編譯器中我們可以寫成這樣

MyCode segment
START: 在這裏首次提出了標號的概念,就相當於C語言的Goto語句,可以定義標號
  ...你的匯編代碼
MyCode segment
end START          這裏有個end,代表了匯編程序結束, START代表跳轉到START來執行我們的代碼

2.分數據段

MyData segment

  db "helloworld$"  ;分號在編譯器裏面已經認為了是匯編代碼的註視了,這裏的db相當於是 #define byte,就是按字節定義,也可以寫為 db 100 就是分配數據區為100

  ;dd        代表 #define Dword (4個字節)

  ;dw            代碼 #define Word  定義兩個字節的意思

MyData ends

3.分棧段

MyStack segment stack 這裏後面要加個關鍵字,因為上面的地址是數據段,當我們壓棧的時候,棧的方向是向上增長的(也就是壓棧,然後數據不斷的累積,壓一個,那麽數據就會向上增長,向低地址增長,那麽就會把數據段給覆蓋了,所以給個關鍵字,轉換過來)

  ;db 100 dup(?) 這裏我寫的註釋,意思就是 分配 100個字節, dup的意思就是是否初始化,給? 就是這個棧不初始化,(一般來說不會初始化的)

  ;db 100 dup(0) 這裏就是分配了100個字節,都初始化為0

 org 64      這個意思就是當前的斷寄存器分配64k,如果分配64k,那麽在1MB的空間中,最多只能分配16個這樣的段 org是貴求64k段

MyStack ends;     ends是結束

四丶編譯器

編譯器用微軟獨立開發的是 6.15版本,最後的版本,可以區第一課的連接中下載編譯器

文件夾

技術分享

其中 ml.exe是編譯器

link.exe 是連接器,連接obj文件

edit 是微軟以前的編輯器 (ALT+ F操作菜單,那時候沒有鍵盤,TAB切換各個選項)

1.編譯器的使用

  1.改名

    我們要使用編譯器,第一步就是給編譯器改個名字,為了不可vc++6.0自帶的沖突,所以隨便改一個

    這裏我改成ml16.exe

  2.配置環境變量, 計算機 - > 屬性- > 高級 - > 環境變量

技術分享

打開屬性

技術分享

選擇高級,然後選擇環境變量

技術分享

這裏分為三步,第一步,復制ml編譯器所在的文件夾路徑,第二部點擊環境變量的path,然後在最後面輸入 ; 文件夾路徑, 分號是結束上一個環境變量的語句,然後自己添加新的

第三不就是 ;文件夾路徑即可. 確定 確定 確定.....

輸入自己編譯器的名字測試是否完成

技術分享

顯示版本號完成

編譯我們的匯編程序,編譯我們的匯編程序,就要按照編譯器的規範去寫了.匯編文件的後綴名字是.asm

五丶第一個.asm程序 利用編譯器分段,執行一個Hello

 1 MyData segment
 2 g_szHello db "HelloWorld$"   //這些是分數據段 還有個g_szHello標號,下面偏移的時候細說
 3 MyData ends
 4 
 5 MyStack segment stack
 6     org 64           //這些是分棧段
 7 MyStack ends
 8 
 9 MyCode segment
10 START:             //設置標號
11     mov ax,1234h
12     mov bx,1234h
13     ;因為分好段了,所以現在開始設置段寄存器
14     mov ax,MyData
15     mov ds,ax        //匯編代碼分段,例如給ds分數據段,則可以直接給 MyData了,給棧分段,則直接可以給MyStack(當然這些段的名字都是自己定義的,自己隨便定義主要是後面的關鍵字不要變即可)
16     mov es,ax
17     mov ax,MyStack
18     mov ss,ax
19     mov dx,offset g_szHello  //我們利用匯編分段的時候說過,以前是 mov dx,0 (代表了從 ds * 16 + 0的物理地址得出字符串的地址)現在有個標號的概念,我們可以利用關鍵字直接給標號了,這樣就不用自己手寫給地址了,大大的提升了開發的效率
20     mov ah,9h
21     int 21h
22     mov ax,4c00h        //退出匯編程序需要給的值
23     int 21h            //調用int 21h會看ax的值是否是4c00是就退出
24     ret
25 MyCode ends
26 end START

編譯出來是一個匯編寫的可執行文件,也就是EXE這個可執行文件裏面記錄了各種段的信息,以及IP指令執行的位置(這也就是為什麽通過exe文件格式,設計出來的入口函數,如果用Debug,你是沒辦法修改的)

EXE文件格式後面細講,主要現在有個概念,就是EXE記錄了段信息,各種寄存器的信息即可.

還需要註意,這裏我們是按照編譯器的規範寫的第一個ASM程序,我們的數據都加上了h這種結束符號,因為從編譯器開始就認為你給16進制就要給h了

比如mov ah,9 在debug裏面就認為參數是9h, 而編譯器認為雖然也是9,但是是10進制的9, 而且在編譯器中,還可以寫成二進制,八進制,10進制

比如 mov ah,9(debug的) ,在編譯器可以寫成 mov ah,1001b 在調用int 21一樣調用

編譯程序步驟

ml16 /c 文件名.asm

link 文件名.obj

(這裏回車回車回車即可)

執行

技術分享

三步走,第一步就是編譯

第二步就是連接,連接的時候,我畫了一個框框,因為光標會在這4個地方等待,直接回車 回車...即可.

第三步就是執行了

六丶段超越

但是分段只是邏輯上的分段,比如你在代碼段裏面放數據,是一樣可以執行的

比如上面的asm代碼可以改成下面這樣

 1 MyData segment
 2 g_szHello db "HelloWorld$"
 3 MyData ends                  //和上面一樣分段
 4 
 5 MyStack segment stack
 6     org 64                   //給棧分配
 7 MyStack ends
 8 
 9 MyCode segment                //代碼段
10 g_szNumber db "HelloWorldsssss$"     //我們要在START上面放數據,不然匯編程序會把數據當做代碼執行,現在我們給了 一段數據
11 START:
12     mov ax,1234h
13     mov bx,1234h
14     ;因為分好段了,所以現在開始設置段寄存器
15     mov ax,MyCode              // 這個ax給的是代碼段的段地址
16     mov ds,ax                //那麽把ds數據段設置為代碼段的位置,那麽下面調用數據段的內容會從這裏開始當做段基地址 * 16 + 偏移,找到數據內容 也就是 Helloworld sssss
17     mov es,ax
18     mov ax,MyStack
19     mov ss,ax
20     mov ax,cs:[0h]
21     mov dx, offset g_szNumber      //這裏的dx 會把ds當做基地址,然後尋址找到Hellossss....... 所以說分段只是邏輯上的分段,現在數據段和邏輯段都重疊了
22     mov ah,9h
23     int 21h
24     mov ax,4c00h
25     int 21h 
26     ret
27 MyCode ends
28 end START

為什麽要再舉一個這樣的例子,其實說以前主要是為了藏代碼執行,就比如說你寫個C語言程序,如果就是main函數對吧,(其實真正的入口點不是這個,不做簡介,自己百度)

然後利用上面的手段,你會發現,我在main函數裏面就寫個return 0,但是程序一打開就是有很牛逼的界面,你說厲害不,其實最主要的就是,這種方法病毒程序都使用這種方法.

所以其實段只是邏輯的概念,比如C語言的內存4區,就是基於匯編的分段,C語言也可以在全局變量區執行代碼,執行函數,有的是方法.只不過分段了只是為了更好的開發而已

真正底層這些都不會是問題的.

執行結果:

 技術分享

段超越:

  什麽是段超越,上面我們分段了,但是其實分段只是邏輯中的分段

  比如我們 mov dx,0 那麽基地址就是 ds數據段,dx存的就是0偏移,然後通過尋址方法,找到物理地址所在的內存

  那麽現在我們改成這樣 dx的值不從ds數據段獲取了

  改為 mov dx,CS:[0H] 代表了我們要從 CS代碼段裏面的0偏移處,取出的內容賦值給DX

比如

  CS的段基地址為 1000 :0 存放的數據為 1 2 3 4 5 6 7

  那麽 mov dx,CS:[0H] 相當於 從CS數據段中的0偏移取出內容 給 dx,因為dx寄存器是16位,所以取出的內容是3412 dx的偏移就是3412

  我們也可以指定讀取, mov dx,word ptr[0h]這個不是段超越,段超越是指定段讀取,這個是默認從DS數據段中取出在0H位置處的兩個字節的長度,給DX

  註意只要是從DS(數據段)取出的內容,都不是段超越

除了DS都是,默認的 mov dx,[0h] 則是在ds中取出數據,等價於 mov dx,DS:[0H]

七丶,8086的機器碼尋址方式

這個比較著重要了,就是通過機器代碼反匯編出來匯編代碼
主要常用的有三種尋址方式
1.立即數尋址方式
2.寄存器尋址方式
3.存儲器尋址方式
先介紹第一種,(第二種第三種,第四講細講)
第一種
比如我們寫了一段匯編代碼,反匯編的時候可以看出機器碼
有的時候要通過機器碼反匯編出來匯編代碼
比如下面我寫好了一個程序

1 <span style="font-size: 15px"><span style="font-size: 18pt"><img src="http://images2017.cnblogs.com/blog/1197364/201708/1197364-20170830005926796-1616123290.png" alt=""></span></span>

前邊我們說過,每一條匯編指令對應一條機器碼

上面從B83412去看

其中立即數尋址方式就是 ax後面的1234會按照小尾方式當做機器碼存儲

那麽現在看的 B83412 其中3412就是操作數

B8是什麽

B代表的是MOV指令

8轉換成二進制是 1000B 我們推測可能是代表那個寄存器,最起碼後邊三位要代表寄存器

我們換一條指令,mov bx,1234看看有什麽改變

技術分享

我們發現變成了BB3412 前邊知道了第一個B是mov指令的意思,3412是立即數

那麽現在又多了一個B,我們變成二進制查看一下

B 1011B 發現侯三給變成11了

那麽我們利用e 指令,給指定位置寫入二進制,看看能出來一個匯編指令嗎 (e 地址 回車,然後輸入第一個,空格則可以輸入第二個地址,依次類推)

技術分享

我們發現,我們寫了一段二進制代碼變成匯編代碼成了 MOV CX,1234

9的二進制代碼是 1001 代表的是CX

那麽由此可以看出

8代表的是AX寄存器

9代表的是CX寄存器

B 代表的 BX寄存器

作業:

  求出 八位通用寄存器分別所代表的值, 包括低八位和高八位各個寄存器的值

  (AX BX CX DX SI DI SP BP ah,al , bh,bl......)

筆記代碼連接:

鏈接:http://pan.baidu.com/s/1c2xVEBQ 密碼:66cw

16位匯編第三講 分段存儲管理思想