16位匯編第三講 分段存儲管理思想
內存分段
一丶分段(匯編指令分段)
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位匯編第三講 分段存儲管理思想