學習彙編

第一講 學習彙編前你應該知道的知識
1 彙編需要什麼工具和程序,到哪裏下載?
  目前階段,彙編程序僅需要兩個程序就夠了。 masm.exe,link.exe。二者可由http://www.20cn.org/~unique/Download/Tool/masm.rar下載,前者是編譯程序,後者是鏈接程序。
  另外,爲了驗證和調試程序,還需要一個程序debug.exe,該程序由windows本身就提供,所以就不提供下載地址了。
  將二者下載後,放到某一個目錄中(任意目錄都可以),考慮到很多命令需要通過鍵盤敲入,所以建議你不要把文件放入到長文件名目錄、中文目錄或很深的目錄中。比如你可以建一個“D:/Masm”目錄,並建議此後的程序都放這個目錄,此後稱這個目錄爲彙編目錄。


2 學習彙編需要有哪些編程方面的知識。
  沒有任何編程方面的知識,學習此語言等於緣木求魚,所以請放棄學習的想法。一般來說至少要知道如下幾點:
  *)程序的運行邏輯結構有順序(按語句依次執行)、分支結構(IF...THEN...ELSE...),循環結構(FOR...NEXT)三種結構。
  *)知道什麼是子程序,什麼是調用。
  *)彙編程序員的視角。不同編程視角編程要求是不一樣的。比如刪除文件,
      >>用戶的視角是找到“刪除”按鈕或菜單,然後單擊一下即可。
      >>高級程序員的視角是知道刪除的文件,併發出刪除命令。這些通過API實現。
      >>彙編程員的視角是得到要刪除的文件名,找到該文件所在位置,通過調用刪除“中斷命令”進行刪除。
      >>操作系統開發人員的視角則是接到刪除命令後,先找到系統根目錄區,由根目錄區的鏈接依次找到子目錄區,直到找到要刪除的文件,然後按照操作系統刪除文件的規則對該文件名進行修改。比如DOS,只把第一個字符改成"?"。

  按程序語句等價的角度看,一行VB的打印語句,用匯編實現大約需要一百二十多行。知道彙編語言的視角後就要知道,前面的道路是坎坷的,沒有耐心是不行的。想通過幾分鐘幾行程序就完成很複雜的操作不是件容易的事。

3 學彙編有什麼用?
  彙編產生於DOS時代或更早,而現在是Windows時代,所以可能遺憾地說:儘管還有批牛人在用匯編開發核心級程序,但我們幾乎沒什麼用,除了必要時間能拿來分析一兩個程序的部分代碼之外,別的也就沒幹什麼用了。並且並不是所有的彙編命令都能在windows下使用。而泛泛地追求“時髦”而學本語言,最後的結果是損了夫人又折兵。所以學之前你要考慮好。我勸那些爲了當“黑客”而學彙編的人就此止步。
第零講 預備知識

1 一個彙編程序的編譯過程是怎麼樣的。
  1)首先你需要找一個編輯器,編輯器用任何“純文本”編輯器都可以。比如記事本。編好以後保存到彙編目錄中。擴展名爲asm,比如myfirst.asm。但這裏建議你找一個能顯示出當前行的編譯器。這樣出錯後排錯很容易。
  2)然後在DOS下進入D:/Masm目錄中,輸入“masm myfirst.asm",如果有錯系統會提示出錯的行位置和出錯原因。
  3)然後再輸入“link myfirst.obj”,即可看到當前目錄下有一個myfirst.exe程序。

2 宏彙編和彙編有什麼區別嗎?
  二者的區別在於前者提供宏,後者不提供。後者已找不到了,所以你可以認爲二者沒有區別。

3 機器語言、彙編語言、高級語言的關係
  最早的計算機採用機器語言,這種語言直接用二進制數表示,通過直接輸入二進制數,插拔電路板等實現,這種“編程”很容易出錯,每個命令都是通過查命令表實現,既然是通過“查表”實現的,那當然也可以讓計算機來代替人查表實現了。於是就產生了彙編語言,所以不管別人怎麼定義機、匯語言,我就認爲,二者是等價。後來人們發現,用匯編語言編某一功能的時候,連續一段代碼都是相同或相似,於是就考慮用一句語言來代替這一段彙編語言,於是就產生了高級語言。因此,所有高級語言都能轉化成彙編語言,而所以彙編語言又可轉化成機器語言。反之,所有機器語言可以轉成彙編語言(因爲二者等價)。但並不是所以彙編語言都能轉成高級語言。

4 計算機的組成
  通常都把計算機定義成五部分:運算器、控制器、存儲器、輸入系統、輸出系統。
  爲了簡單其間,我們如此理解:運算器+控制器=CPU。存儲器=內存(暫不包括外存,永不包括CACHE)。輸入系統=鍵盤(不包括鼠標),輸入系統=顯示器(不包括打印機,繪圖儀)。

5 寄存器和內存的區別
  寄存器在CPU中。內存在內存條中。前者的速度比後者快100倍左右。後面的程序要求每條指定要麼沒有內存數據,要麼在有一個寄存器的參與下有一個內存數據。(也就是說,不存在只訪問內存的指令)。

6 彙編語言的計數
  與生活中的計數不一樣,彙編中的計數是從0開始的。比如16個計數,則是從0~15,而不是生活中的1~16。這一點看起來簡單,真運算起來就不是件容易的事了,不信等着瞧。

7 進制問題
  又與生活中不一樣的地方是進制。切記下面的常識:
  *)計算機內部存儲都用二進制。
  *)我們的彙編源程序默認都用十進制。(除非你指明類型)
  *)我們用的調試程序debug默認的都是十六進制。(無法指明其他類型)
  其中十六進制的十六個個位數依次是:0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。

8 進制轉換
  一個比較簡單的方法是查表法。
  十進制 十六進制 二進制
      0      0      0000
      1      1      0001
      2      2      0010
      3      3      0011
      4      4      0100
      5      5      0101
      6      6      0110
      7      7      0111
      8      8      1000
      9      9      1001
      10    A      1010
      11    B      1011
      12    C      1100
      13    D      1101
      14    E      1110
      15    F      1111
 
  好了,結合6,7,8三條。大家來算一個“題”。某一組數據顯示時,每個數據佔了四個位置,
每行共十六個。問:十六進制的13位置在哪裏(第幾行,第幾列)。
  格式如下:m m m m n n n n o o o o p p p p '注:之所以沒用ABC是怕與上面十六進制弄混。
            r r r r s s s s t t t t u u u u
第一講 基礎知識

1 訪問內存
  程序在內存中,訪問內存是幾乎每一程序都要進行的操作,計算機對內存編址是線性的,也就是說是一維的,比如256M的內存,地址就應該是從0~(256M-1),這個地址稱爲物理地址或絕對地址。
1.1 地址表示
  但從彙編程序員的角度看,內存卻是二維的,要說明一個地址,需要給出兩個值,就象你在平面上指定一點需要說出(X,Y)座標一樣,彙編程序員的內存視角也需要兩個“座標”,前一個稱爲段地址(Segment),後一個稱爲偏移地址(Offset),該地址稱爲邏輯地址。
  比如“1234:3DF5”就是一個地址。“1F3F:”不是一個地址,因爲他只有段地址,沒有編移地址。注意此後的地址都用十六進制表示。
1.2 地址計算
  前面提到,計算機編址是一維的,彙編程序員是二維的,那麼二者怎麼換算呢?由後者到前者的換算方法是,“段地址串”後面加個“0”,然後再加上偏移地址。
  比如“1234:3DF5”(十六進制的加減運算參見相關資料)
12340 ‘串後加了一個0
3DF5
-----
16135  ’注意此串仍然是十六進制。
    所以,彙編程序員眼中的地址“1234:3DF5”就是物理地址(計算機編址):16135。
    知道了由後者向前者的轉換,那麼由前者向後者的轉換呢?
  “不知道”,爲什麼不知道,繼續往下看。
1.3 到底哪個地址對。
    知道了1.2的地址算法後,我又發現一個問題:
    “1000:6135”的物理地址是多少呢? 10000+6135=16135。
    “1001:6125”的物理地址呢? 10010+6125=16135。
    ......
    那麼到底哪個對呢?問題的回答是這樣的:假設我現在讓你按一下“L”鍵,我可以告訴你如下幾種方法中的一種或幾種。1 請按一下“L”鍵; 2請按一下鍵盤上第四行第十個鍵;3 請按一下第十列中的第四個鍵;4 請按一下“K”右邊的鍵;5 按標準指法單擊一下右手無名指。
    舉上面的例子也就是說,同一個地址有很多種表示方式,具體用哪一種,要看實際使用時的情況。但無論用哪種方式,只要能達到目的即可。(實際中該問題一般不會受此問題困擾,但初學時突然想不通)。
1.4 有多少內存可以訪問
  無論是段地址還是偏移地址都是四位十六進制(如果不夠四位,前面補0)。也就是說:總共可以訪問的地址說是:0000:0000~FFFF:FFFF。 總共FFFF0+FFFF+1=10FFF0個地址。也就是不到1M的空間。
  記住如下結論:
  *)不管你實際內存有多少,目前我們只能訪問不到1M的空間。
  *)而實際上連這1M也用不完。其中上端的384K的址只能讀不能寫,只能讀,一般稱爲ROM。
  *)低端的640K可以讀寫。但這640K的低端100多K也不能隨便寫,因此DOS系統使用該區。
  *)原來1024M的內存,彙編程序只能使用其中400多K。這段內存的容易相當於一個普通文檔的大小。不過這就足夠了。

2 DEBUG的使用
先記住以下兩個命令:D命令和Q命令。前者是顯示內存內容,後者是退出DEBUG命令。
-------------以下爲抄別的人內容---------------
DEBUG.EXE程序是專門爲分析、研製和開發彙編語言程序而設計的一種調試工具,具有跟蹤程序執行、觀察中間運行結果、顯示和修改寄存器或存儲單元內容等多種功能。它能使程序設計人員或用戶觸及到機器內部,因此可以說它是80X86CPU的心靈窗口,也是我們學習彙編語言必須掌握的調試工具。

    1)DEBUG程序使用

在DOS提示符下鍵入命令:

    C>DEBUG [盤符:][路徑][文件名.EXE][參數1][參數2]

  這時屏幕上出現DEBUG的提示符“-”,表示系統在DEBUG管理之下,此時可以用DEBUG進行程序調試。若所有選項省略,僅把DEBUG裝入內存,可對當前內存中的內容進行調試,或者再用N和L命令,從指定盤上裝入要調試的程序;若命令行中有文件名,則DOS把DEBUG程序調入內存後,再由DEBUG將指定的文件名裝入內存。
2)DEBUG的常用命令
(1)退出命令 Q
    格式:Q
    功能:退出DEBUG,返回到操作系統。
(2)顯示存儲單元命令 D
格式1:D[起始地址]
    格式2:D[起始地址][結束地址|字節數]
    功能:格式1從起始地址開始按十六進制顯示80H個單元的內容,每行16個單元,共8行,每行右邊顯示16個單元的ASCII碼,不可顯示的ASCII碼則顯示“·”。格式2顯示指定範圍內存儲單元的內容,其他顯示方式與格式1一樣。如果缺省起始地址或地址範圍,則從當前的地址開始按格式1顯示。
例如:    -D 200          ;表示從DS:0200H開始顯示128個單元內容
                -D 100 120    ;表示顯示DS:0100-DS:0120單元的內容
    說明:在DEBUG中,地址表示方式有如下形式:
    段寄存器名:相對地址,如:DS:100
段基值:偏移地址(相對地址),如:23A0:1500

--------------------------小抄結束--------------------------------

3 驗證第一節裏的內容
運行“開始/程序/附件/MS-DOS命令提示符”(這是win2000,win98下自己找吧)
在“_”下輸入D,顯示
-d
1398:0100  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0110  00 00 00 00 00 00 00 00-00 00 00 00 34 00 87 13  ............4...
1398:0120  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0130  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0140  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0150  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0160  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0170  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
-
我們記下:1398:011C的值是個34。1389:011C的物理地址應該是:13A9C。
那麼1000:3A9C的物理地址也應該是13A9C,他的內存也應該是34,(因爲本來就是一個地址嗎,就象第三行第十列和第十列第三行當然應該是同一個位置)。
-d 1000:3A9C
1000:3A90                                      34 00 87 13              4...
1000:3AA0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AB0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AC0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AD0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AE0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AF0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3B00  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3B10  00 00 00 00 00 00 00 00-00 00 00 00              ............
-
果然如此,同樣你可以驗證:13A9:000C也肯定是指這一個地址,不信試試。
4 DEBUG命令
-------------------繼續小抄----------------
前面已學過:顯示存儲單元命令 D
再學一個命令
(1)修改存儲單元命令 E

格式1:E[起始地址] [內容表]

格式2:E[地址]

功能:格式1按內容表的內容修改從起始地址開始的多個存儲單元內容,即用內容表指定的內容來代替存儲單元當前內容。

例如:—E DS:0100 'VAR' 12 34

表示從DS:0100 爲起始單元的連續五個字節單元內容依次被修改爲

'V'、'A'、'R'、12H、34H。

格式2是逐個修改指定地址單元的當前內容。

如:—E DS:0010

156F:0010 41.5F

其中156F:0010單元原來的值是41H,5FH爲輸入的修改值。若只修改一個單元的內容,這時按回車鍵即可;若還想繼續修改下一個單元內容,此時應按空格鍵,就顯示下一個單元的內容,需修改就鍵入新的內容,不修改再按空格跳過,如此重複直到修改完畢,按回車鍵返回DEBUG“-”提示符。如果在修改過程中,將空格鍵換成按“-”鍵,則表示可以修改前一個單元的內容。

-------------------小抄結束----------------

5 使用DOS時,彙編用戶可以從DOS操作系統中得到什麼?
  現在編程,通常很多功能都是通過調用系統API。很多高級語言都直接把這些API包裝起來,以系統接口或函數的方式提供給用戶,那麼彙編函數都能得到什麼呢?
  首先,彙編用戶有很多東西可以調用。他們主要是:
  5.1 BIOS提供的接口。現在硬件與軟件的區分已越來越不明顯,很多硬件不僅僅是電路,而還要提供一些固化寫入硬件的一部分“程序”,這些程序以ROM的方式出現,彙編用戶最大的好處就是可以直接使用這些“程序”,這些使用不僅功能強大,而且效率非常高。
  5.2 DOS功能調用,作爲操作系統也象BIOS一樣向用戶提供了相應的“程序”。這些程序在很大程序上擴充了BIOS。與BIOS不同的是,這部分程序放在內存中,它可以被修改。而BIOS中不能再修改。
  ==========================================================
  以上兩種接口都通過一種相同的格式調用,這些程序統稱爲“中斷”,現在先不要理解中斷的本意,你現在可以認爲是系統提供給你的函數。
  ============================================================
  5.3 系統共享數據區。編過程序的人都知道全局變量的好處,全局變量方便之外在於任何函數、過程都可以調用、讀取、修改。全局變量不足之處是危險性,有一個過程改了這個變量值,其它的也得跟着改變了。DOS操作系統同樣也提供了這樣的共享數據區,該區是整個系統的共享區,任何程序都可以查找、修改。當然,修改某處必然會對其它程序造成影響。

6 再談中斷
  前面5.2已提到中斷了,現在問題是不同硬件不一樣,即使相同硬件的ROM,不同版本,各個BIOS中斷程序所處的位置也不一樣,DOS中斷也一樣,不同版本、不同配置,在內存位置也不一樣。那麼你使用某一箇中斷,系統怎麼知道你使用的那個中斷程序在哪呢?
  爲了解決這一問題,DOS會在啓動的時候,把所有這些(BIOS和DOS)中斷的首地址保存到一個地址。這個地址很容易記,這段地址是內存的絕對零地址(0000:0000)。前面已講過,每個地址在彙編程序員角度來看是二維的,也就是分爲段地址和偏移地址。每個地址各佔兩個字節,所以要表示這個二維地址需要4個字節。所以每個中斷首地址由4個字節表示。一共256箇中斷,佔用了1024個字節的位置。
  另外需要注意的是,這4個表示地址的字節,數據是由低向高的。比如12 34 56 78所表示的地址是:7856:3412。
一般用INT M表示中斷M,如果M是十六進制,則在後面加上一個H。比如19號中斷,十六進制應該是13H。所以該中斷就是INT 13H。

7 再談系統共享數據區
  該共享數據區在絕對地址:0040:0000開始。

8 驗證我上面說的內容
  8.1 找中斷
  運行DEBUG後。輸入D 0000:0000。顯示絕對零地址的內容。
  C:/>debug
-d 0:0
0000:0000  68 10 A7 00 8B 01 70 00-16 00 9B 03 8B 01 70 00  h.....p.......p.
0000:0010  8B 01 70 00 B9 06 0E 02-40 07 0E 02 FF 03 0E 02  ..p.....@.......
0000:0020  46 07 0E 02 0A 04 0E 02-3A 00 9B 03 54 00 9B 03  F.......:...T...
0000:0030  6E 00 9B 03 88 00 9B 03-A2 00 9B 03 FF 03 0E 02  n...............
0000:0040  A9 08 0E 02 99 09 0E 02-9F 09 0E 02 5D 04 0E 02  ............]...
0000:0050  A5 09 0E 02 0D 02 DC 02-B8 09 0E 02 8B 05 0E 02  ................
0000:0060  02 0C 0E 02 08 0C 0E 02-13 0C 0E 02 AD 06 0E 02  ................
0000:0070  AD 06 0E 02 A4 F0 00 F0-37 05 0E 02 71 84 00 C0  ........7...q...
-u 0070:018B
0070:018B 1E            PUSH    DS
0070:018C 50            PUSH    AX
0070:018D B84000        MOV    AX,0040
0070:0190 8ED8          MOV    DS,AX
0070:0192 F70614030024  TEST    WORD PTR [0314],2400
0070:0198 754F          JNZ    01E9
0070:019A 55            PUSH    BP
0070:019B 8BEC          MOV    BP,SP
0070:019D 8B460A        MOV    AX,[BP+0A]
0070:01A0 5D            POP    BP
0070:01A1 A90001        TEST    AX,0100
0070:01A4 7543          JNZ    01E9
0070:01A6 A90002        TEST    AX,0200
0070:01A9 7422          JZ      01CD
首先,D命令把中斷首地址顯示出來。每4個表示一個地址。其中INT 0的中斷首地址爲:00A7:1068,INT 1的中斷地址爲:0070:018B.......0070:018B是中斷3的首地址。後面那個U命令就表示顯示該地址的“中斷程序”的內存。
    你們可以試着找找INT 13H的位置在哪。
  8.2 驗證系統共享數據區
    系統共享數據區內容極爲豐富,我實在記不住哪麼多了。我曾記在一個本上,可惜那個本早在N年前(3<N<6)就丟了。兄弟們誰找到這個地址的內容,一定要貼上來,這裏有東西可以讓大家眼界大開。
    前幾年,我用的286計算機是黑白顯示器(555555~~~~~~~~~,別嫌我老、舊、慢呀),可當時有個遊戲非要彩顯,不是彩顯不讓運行。我就是改了這個區的某一個位,讓哪遊戲“以爲”我用的是彩顯,於是遊戲能用了。雖然不好看,但總能用。
    在DOS下,你每按一個鍵,系統都會記下來,下面我們一起找找這個鍵盤緩衝區的地址。知道這個地址,你就可以作一個“虛擬”鍵盤,通過發命令來模擬某個人在按鍵。這個地址位於:0040:001E。 其中每個鍵有兩個字節,一個字節是ASCII碼,一個是掃描碼。共16個。
C:/>debug
-d 40:0
0040:0000  F8 03 F8 02 E8 03 E8 02-BC 03 78 03 78 02 80 9F  ..........x.x...
0040:0010  22 C8 00 80 02 28 00 00-00 00 2A 00 2A 00 20 39  "....(....*.*. 9
0040:0020  34 05 30 0B 3A 27 30 0B-0D 1C 64 20 20 39 34 05  4.0.:'0...d  94.
0040:0030  30 0B 3A 27 30 0B 0D 1C-71 10 0D 1C 64 20 00 00  0.:'0...q...d ..
0040:0040  A2 00 C3 00 A2 AF 09 E1-C8 03 50 00 00 10 00 00  ..........P.....
0040:0050  00 18 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0040:0060  0F 0C 00 D4 03 29 30 7F-03 00 C0 00 A1 B7 11 00  .....)0.........
0040:0070  00 00 00 00 00 00 00 00-14 14 14 00 01 01 01 01  ................
-d 0040:0000
0040:0000  F8 03 F8 02 E8 03 E8 02-BC 03 78 03 78 02 80 9F  ..........x.x...
0040:0010  22 C8 00 80 02 28 00 00-00 00 2A 00 2A 00 3A 27  "....(....*.*.:'
0040:0020  30 0B 30 0B 30 0B 30 0B-0D 1C 64 20 20 39 30 0B  0.0.0.0...d  90.
0040:0030  30 0B 30 0B 30 0B 08 0E-08 0E 34 05 30 0B 00 00  0.0.0.....4.0...
0040:0040  1F 00 C3 00 A2 AF 09 E1-C8 03 50 00 00 10 00 00  ..........P.....
0040:0050  00 18 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0040:0060  0F 0C 00 D4 03 29 30 7F-03 00 C0 00 24 B8 11 00  .....)0.....$...
0040:0070  00 00 00 00 00 00 00 00-14 14 14 00 01 01 01 01  ................
-
既然是鍵盤緩衝區,每個輸入的鍵都會顯示在該區中,第一次我只輸入了“d 40:0”,所以你可以在此後顯示數據右邊字符中找到這些字符,注意是間隔開的。
第二次我輸入“d 0040:0000”,則右邊顯示的是“d 0040:0000”的內容。你可以找找。
第二講 內存映象

  之所以把這個內存單獨放一章,是爲了說明它的重要性,後面的幾乎很多程序都需要你對這一章的理解。這裏的內存映象就是指當你把一個可執行文件(EXE或COM文件)放到內存後,整個內存“看”起來是什麼樣子的。
  前面講過,這裏彙編程序只能訪問1M的內存空間,所以下面就以1M內存爲例。並且以DOS操作系統作爲講解對象,所以所編出來的程序也僅是DOS程序。事實上,通過winasm可以訪問遠遠超過1M的空間,並且可以編出FOR windows的程序。但那是另外的話題。我們暫且不說那些。

2.1 內存映象
  首先,這1M內存如果我們不再以二維的方式看,而是一維的,線性地看(二維和一維的轉化方式參見前面章節)。但描述還是以二維的方式描述,從最底端到最高端依次是:
  1 中斷向量區:該區由0000:0000~0000:03FF。這裏存着系統的所有中斷的中斷向量表,對於中斷向量表,你現在先理解爲一些程序的首地址。由這個地址你就能找到該程序。
  2 系統數據區:該區由0040:0000~0040:XXXX(不好意思,忘了),這裏存着整個系統中,DOS操作系統要用的數據,由於這個區的數據對用戶是開放的,所以用戶當然也可以從這裏讀出來用。
  3 DOS操作系統區:操作系統常駐內存,你向計算機發的每個命令其實都是操作系統執行的。這個區的大小主要是由操作系統的版本和用戶的配置大小決定,如果是驅動程序配置,就放到根目錄下的config.sys裏,如果是程序,就放到autoexec.bat裏。這裏設置在現在的windows 95/98/nt/me/2000/xp/2003中仍然有,所以我就不多說了。
  4 用戶程序,這個當然就是你執行的程序了,這種程序分兩種,一種是擴展名爲com文件,一種是exe文件,從程序內部看,前者程序的四個段重合(後面要講這四個段),所以最大長度只等於一個段,用前面段地址的理解就是com文件最大隻能是64K,所以com文件只適合小的程序。而exe,四個段可任何分配,並可擴充段,而且每個段的段地址可以任何改動,因此exe的訪問內存能力大多了。這種格式訪問能力只受地址結構的限制了。
    用戶程序所佔的內存大小完全由程序本身決定,但最大,只能到640K。這一點,怪不得別人,只能怪當前計算機軟硬件設置高手高手高高手們(包括比爾蓋茨)們的失誤了,60年代的超級計算機只有36K的內存,所以他們就在80年代得到一個結論:640K的內存足夠了。
    如果用戶程序大於由操作系統所佔內存的頂底到640K之間的內存量,就會顯示:內存不夠,因而程序不能執行。這種現象對於一開始就用windows的人來說,幾乎沒見過,但對於一開始用DOS並打漢字的人來說,再正常不過。如果小於這段內存,多餘部分就空着。
  5 從640K到1M-64K,這段內存就很難說清了。這段內存中有一部分被硬件佔有,有一部分是顯示緩衝區點有,還有一部分是系統ROM佔有。
  6 從1M-64K到1M之間的這段64K的內存叫作HMA。這段內存是小孩沒娘,說來話長,我們先不說他。
   
2.2 驗證上面的理論

2.2.1 中斷向量表
    中斷向量表就是所有中斷向量首地址表,這裏保存着每個中斷程序的首地址,幾乎所有的彙編書都把中斷後面後面的章節中,並且對中斷的解釋也僅從字面意思解釋,所以導致大學對中斷的不重要和誤解。沒耐心的沒到這個章節就不學彙編了,有耐心的到這裏才豁然開朗。我現在不講中斷的原意。我直接告訴你,你把中斷當成API也許更合適。也就是說,別人把很多已作好的功能放到了內存中。並且把調用這一功能的號告訴了你,你只要調用這些功能號,系統就自動從這個中斷向量表中找到對應的中斷,然後執行你的功能。
    首先讓你感受一下中斷的魅力一下吧。比如中斷21H的2A功能調用是讀取系統的日期,這個調用的規則是,調用前AH寄存器置爲2A。調用後年在CX中,月在DH中,DL在日中,星期在AL中。
-a
139D:0100 mov ah,2a
139D:0102 int 21
139D:0104 int 3
139D:0105
-g=100

AX=2A05  BX=0000  CX=07D4  DX=0C18  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=139D  ES=139D  SS=139D  CS=139D  IP=0104  NV UP EI PL NZ NA PO NC
139D:0104 CC            INT    3
-
可能上面的程序你目前還看不懂。不過沒關係,“mov ah,2a”表示調用功能號是2a號。“int 21”表示調用十六進制21號中斷,“int 3”表示3號中斷,表示程序運行到這一句時停一下。“g=100”表示從“139D:0100 ”開始執行。
AX=2A05  BX=0000  CX=07D4  DX=0C18  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=139D  ES=139D  SS=139D  CS=139D  IP=0104  NV UP EI PL NZ NA PO NC
表示執行的結果。其中CX是年,這個年是由CX中存。07D4十進制就是2004年。DH+DL=DX,所以DH=0C,DL=18。二者轉化爲十進制就是DH=12,DL=24,也就是今天了。AX=AH+AL=2A05,所以AL=05。那就是今天是星期五。
上面可能你們現在還看不懂,不過通過解說你應該可以知道,僅僅兩行命令,就讀到了現在的值。現在需要作的就是把這些值提取出來用作他用了。

    從中斷的作來與中斷向量表又有什麼關係呢?原來你在彙編裏運行int 21時,系統就在上面的中斷向量表中找到int 21的中斷地址,該中斷的地址應該位於:0000:0084~0000:0087,具體算法前面已說明了。
-d 0000:0084 0087
0000:0080              7C 10 A7 00                              |...
-
找到內容是:00A7:107C。然後系統就轉到這個地址執行int 21。

2.2.2 系統數據區前面都已說明過。不再多說。系統區,很多DOS中斷程序實現部分就在這個區。程序運行區依不同的程序而不用。

2.2.3 640K~1M之間,這期間有些地方是ROM,有些地方是硬件的BIOS區。我僅以兩個例子說明這一區。
  ROM區:ROM區就是隻讀內存,也就是說這個區的數據只能讀不能寫。比如F000:0000開始的內存是ROM。我們來寫一下,然後再看看效果。
-d f000:0000 0005 '顯示由F000:0000到F000:0005的六個字節值。
F000:0000  04 E8 A2 FF F9 C3                                ......
-e f000:0000    '修改命令
F000:0000  04.00  E8.00  A2.00  FF.00  F9.00  C3.00'注意,.後面的是我改的,把這幾個值都改成0了。
-d f000:0000 0005 '再次顯示這個區的數據。
F000:0000  04 E8 A2 FF F9 C3                                ......
-
通過上面測試,發現該區數據仍然未改變。但你要是試別的RAM區的,肯定會變。如果想試你自己試試吧。

  顯示緩衝區:在文本方式下,B800:0000開始的地址保存着屏幕上每個字符位置的值。在文本方式下,屏幕被分爲80 X 25。每個位置有兩個值,一個值是ASCII字符,一個值是該ASCII的屬性值(主要是顏色)。所以一個屏幕共有80X25X2=400個字符。
我們來改:
-d b800:0000 0010 '顯示屏幕緩衝區的內容,注意此時本行最左邊的“-”是屏幕左上角。
B800:0000  2D 07 64 07 20 07 62 07-38 07 30 07 30 07 3A 07  -.d. .b.8.0.0.:.
B800:0010  30                                                0
-
看上面的命令,屏幕最上邊一行是“-d b800:0000 0010”,所以他的內容就是“2D 07 64 07 20 07 62 07-38 07 30 07 30 07 3A 07”其中,2D是“-”的ASCII值,07是“-”的屬性值。64是“d”的ASCII值,07是“d”的屬性值。。。。。
現在修改這些值。我把左上角的字改成黃顏色的“-”,那當然是改b800:0001的屬性值了。
-e b800:0001  0e
是不是左上角的顏色變成黃色了嗎?
好了,把第二個字符變成綠色的“-”吧?
-e b800:0002 2d 0b
變了嗎?

可執行文件內存映象
DOS下可執行文件有兩種(BAT是批處理文件,他只是簡單調用DOS內部命令或其它程序,所以此處不認爲它是可執行文件),一種是COM文件,一種是EXE文件,前面提到,COM文件一般小於64K。EXE文件則可以任意大。爲什麼呢?
說到這裏,還要提到段。每個段64K。段的作用就是數據組織單位。段的類型有三種:代碼段(Code Segment,簡稱CS)、數據段(Data Segment,簡稱DS)、棧段(Stack Segment,簡稱SS),另外還有一個附加數據段(Extra Segment,簡稱ES),它的用與數據段DS可以認爲完全一樣,當數據段的64K不夠用,或你就需要把數據放到兩個段中以便移動、複製、比較時,纔用到附加數據段ES。(當然,移動、複製、比較操作在一個段中也可以完成)。
1 段的作用。
1.1 代碼段(CS),程序裝入內存中,DOS怎麼知道是從哪裏執行呢?答案就是系統自動從代碼段指定位置開始執行,並且始終在代碼段中執行。內此代碼段CS的作用就是保存所有的指令。這裏所說的代碼也就是彙編指令了。所以編寫彙編程序也就主要是編寫代碼段中的代碼。
1.2 數據段(DS)、附加段(ES),顧名思義,數據段中存的就是數據,這些數據供代碼段的程序調用。附加段就是附加數據段。作用與數據段相同。
1.3 棧段(SS),這個段非常重要,但實際上,你在使用中,似乎用不着這個段,但實際上,這是黑客編程中最重要的一部分,而且系統會不停地“偷偷地”使用這個段,正是這個偷偷地用,使得系統的很多動作被記錄到這個段中。還有兩點,你必須記住:一是如果你使用了這個棧,比如你把數據存到這個棧中,則必須有相應的出棧命令,並且入幾個數據,就得出幾個數據,多一個或少一個,你的程序就可能導致死機或異常;二是你要把握操作時機,比如你不能在系統使用棧的前後使用棧,比如你在調用子程序之前入棧,而在子程序中出棧,而在系統調用子程序時,系統也要使用棧,這種也將導致出錯。
棧就是一種先入後出(也有稱爲後入先出)的結構,有地址由小到大的增加棧,有地址由大到小的逆向減棧。
2 段重疊
從上面,我們可以看到,CS,DS,SS三者作用各不相同,內存就是象錄音磁帶,錄新歌,則舊歌被刪,帶子上存的始終是最後錄的那段音樂。因此,如果重疊則必然相互衝突。那還能重疊嗎?
這裏所說的重疊不是指內容重疊,而是指概念上的重疊,即數據相互放到一個段中,但相互可以區分開。比如某一段既有數據也有代碼,則代碼在每要執行到數據之前加一個跳轉指令跳過這段代碼。這個跳轉指令要求用戶在編程的時候加上。
而棧段呢?棧段有自己的特殊性,特殊就在於系統也會自動地使用,而用戶則又在不知道系統在使用的情況下使用。避免這種衝突的方法就是採用逆向的棧段。
2 COM文件內存映象。
  COM文件被讀到內存中後,該文件的前100H個字節被操作系統使用,操作系統使用這256個字節保存一些系統要使用的數據,彙編語言編程者不能在這裏存自己的數據,但在知道這此數據的作用後可以使用其中的數據。從100H開始,就是程序的開始了。COM文件之所以最大隻能有64K,其原因是COM文件的四個段是相互重疊的。也就是說,CS,DS,SS,ES四個段的地址都指向這個COM文件的100H處。程序代碼、數據、棧都在由100H到64K的區域內。如何把三者分開呢?棧段採用逆向棧,這個棧由64K開始,隨着數據入棧,則地址就減小。這樣作的好處是,棧段由高端向低端進展,可以詳細與數據、代碼分開;壞處也不言而喻,假如一個COM程序大量用到棧(比如是個遞歸程序)因此棧就不停地降低,而程序代碼本身也很多,甚至不停地申請新空間,這樣數據和棧就會在中間碰頭,導致程序被破壞。
  區分開數據代碼段與棧段後,下面討論把數據段和代碼段也分開。這個簡單的多,只要邏輯上分開就可以。不過一般的方法就是:在100H處放一個跳轉指令,隨後放數據,然後再放置其它的代碼。而100H處的跳轉指令就跳到這裏。
因此,COM文件內存映象就是:
CS:0000  (由於COM的CS,DS,SS,ES三段重疊,因此此行前CS,寫成DS,SS,ES都一樣)。 
CS:0100    一個跳轉到YYYY地址的跳轉指令。
CS:0101    本程序所需要用到的數據
CS:XXXX    數據結束處。
CS:YYYY    程序代碼保存處。
CS:ZZZZ    程序代碼結束處。
CS:FFFF    棧段開始處(注意棧是地址越來越小,所以這裏是開始而不是結束處),也是程序的結束處。另外,此處FFFF與前面XXXX,YYYY,ZZZZ不一樣,這裏是十六進制的64K。

3. EXE文件
  比起COM文件,EXE文件要複雜一些,他的複雜就在於COM文件前面規定了100H個字節用於系統使用,而EXE文件則有個文件頭,文件頭的大小看具體內容多少。文件頭的內容使得EXE看起來複雜了,但也更靈活了。更重要的是,對於病毒設計者,這個文件頭使他們如魚得水。因爲文件頭處
EXE文件的內存映象爲:
XXXX:0000 文件頭
XXXX:YYYY 文件頭結束處。

CS:0000 代碼段開始處
CS:ZZZZ 代碼段結束處

DS:0000 數據碼段開始處
DS:WWWW 數據碼段結束處

SS:0000 棧段開始處
SS:UUUU 棧段結束處

ES:0000 附加段開始處
ES:VVVV 附加段結束處

說明:
1 上述ES可以沒有,要看實際需要
2 CS,DS,ES,SS的順序也是看編程者是怎麼安排的,好在用戶不必關心他的具體位置。
3 由上可見,CS,DS,ES,SS的段地址肯定保存到了文件頭中。
4 由上可見,實際執行的只是CS,因此DS,ES,SS的首地址,CS肯定要想辦法知道。:)
第三章 彙編指令
3.1 什麼是機器語言
前面提到“最早的計算機採用機器語言,這種語言直接用二進制數表示,通過直接輸入二進制數,插拔電路板等實現,這種“編程”很容易出錯,每個命令都是通過查命令表實現”。
比如要執行21號中斷,需要查表,得到21號中斷的指令就是CD 21。這樣不管你通過什麼方式,在內存指令位置,寫入兩個字節,一個是CD(這可不是音樂光盤,而是二進制數,轉成十進制就是205),另一個是21(同樣是十六進制,十進制是33)。
上面就是機器語言。

3.2 什麼是彙編語言
前面也提到“既然是通過“查表”實現的,那當然也可以讓計算機來代替人查表實現了。於是就產生了彙編語言”,彙編語言產生的重要目的就是用容易記的符號來代替容易出錯的二進制數(或十六進制數)。
比如前面的21號中斷,機器語言是CD 21。而彙編語言就規定中斷用int表示(interrupt的前三個字母),21號中斷就成了int 21h。其中21後面的h表示是表示這個21是十六進制。由於大小寫不敏感,所以int 21h寫成下列方式都等價:
int 33
Int 21h
INT 21H

3.3 彙編指令集
一、數據傳輸指令
───────────────────────────────────────
它們在存貯器和寄存器、寄存器和輸入輸出端口之間傳送數據.
1. 通用數據傳送指令.
MOV 傳送字或字節.
MOVSX 先符號擴展,再傳送.
MOVZX 先零擴展,再傳送.
PUSH 把字壓入堆棧.
POP 把字彈出堆棧.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次壓入堆棧.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次彈出堆棧.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次壓入堆棧.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次彈出堆棧.
BSWAP 交換32位寄存器裏字節的順序
XCHG 交換字或字節.( 至少有一個操作數爲寄存器,段寄存器不可作爲操作數)
CMPXCHG 比較並交換操作數.( 第二個操作數必須爲累加器AL/AX/EAX )
XADD 先交換再累加.( 結果在第一個操作數裏 )
XLAT 字節查錶轉換.
── BX 指向一張 256 字節的表的起點, AL 爲表的索引值 (0-255,即
0-FFH); 返回 AL 爲查表結果. ( [BX+AL]->AL )
2. 輸入輸出端口傳送指令.
IN I/O端口輸入. ( 語法: IN 累加器, {端口號│DX} )
OUT I/O端口輸出. ( 語法: OUT {端口號│DX},累加器 )
輸入輸出端口由立即方式指定時, 其範圍是 0-255; 由寄存器 DX 指定時,
其範圍是 0-65535.
3. 目的地址傳送指令.
LEA 裝入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 傳送目標指針,把指針內容裝入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 傳送目標指針,把指針內容裝入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 傳送目標指針,把指針內容裝入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 傳送目標指針,把指針內容裝入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 傳送目標指針,把指針內容裝入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.
4. 標誌傳送指令.
LAHF 標誌寄存器傳送,把標誌裝入AH.
SAHF 標誌寄存器傳送,把AH內容裝入標誌寄存器.
PUSHF 標誌入棧.
POPF 標誌出棧.
PUSHD 32位標誌入棧.
POPD 32位標誌出棧.

二、算術運算指令
───────────────────────────────────────
ADD 加法.
ADC 帶進位加法.
INC 加 1.
AAA 加法的ASCII碼調整.
DAA 加法的十進制調整.
SUB 減法.
SBB 帶借位減法.
DEC 減 1.
NEC 求反(以 0 減之).
CMP 比較.(兩操作數作減法,僅修改標誌位,不回送結果).
AAS 減法的ASCII碼調整.
DAS 減法的十進制調整.
MUL 無符號乘法.
IMUL 整數乘法.
以上兩條,結果回送AH和AL(字節運算),或DX和AX(字運算),
AAM 乘法的ASCII碼調整.
DIV 無符號除法.
IDIV 整數除法.
以上兩條,結果回送:
商回送AL,餘數回送AH, (字節運算);
或 商回送AX,餘數回送DX, (字運算).
AAD 除法的ASCII碼調整.
CBW 字節轉換爲字. (把AL中字節的符號擴展到AH中去)
CWD 字轉換爲雙字. (把AX中的字的符號擴展到DX中去)
CWDE 字轉換爲雙字. (把AX中的字符號擴展到EAX中去)
CDQ 雙字擴展. (把EAX中的字的符號擴展到EDX中去)

三、邏輯運算指令
───────────────────────────────────────
AND 與運算.
OR 或運算.
XOR 異或運算.
NOT 取反.
TEST 測試.(兩操作數作與運算,僅修改標誌位,不回送結果).
SHL 邏輯左移.
SAL 算術左移.(=SHL)
SHR 邏輯右移.
SAR 算術右移.(=SHR)
ROL 循環左移.
ROR 循環右移.
RCL 通過進位的循環左移.
RCR 通過進位的循環右移.
以上八種移位指令,其移位次數可達255次.
移位一次時, 可直接用操作碼. 如 SHL AX,1.
移位>1次時, 則由寄存器CL給出移位次數.
如 MOV CL,04
SHL AX,CL

四、串指令
───────────────────────────────────────
DS:SI 源串段寄存器 :源串變址.
ES:DI 目標串段寄存器:目標串變址.
CX 重複次數計數器.
AL/AX 掃描值.
D標誌 0表示重複操作中SI和DI應自動增量; 1表示應自動減量.
Z標誌 用來控制掃描或比較操作的結束.
MOVS 串傳送.
( MOVSB 傳送字符. MOVSW 傳送字. MOVSD 傳送雙字. )
CMPS 串比較.
( CMPSB 比較字符. CMPSW 比較字. )
SCAS 串掃描.
把AL或AX的內容與目標串作比較,比較結果反映在標誌位.
LODS 裝入串.
把源串中的元素(字或字節)逐一裝入AL或AX中.
( LODSB 傳送字符. LODSW 傳送字. LODSD 傳送雙字. )
STOS 保存串.
是LODS的逆過程.
REP 當CX/ECX<>0時重複.
REPE/REPZ 當ZF=1或比較結果相等,且CX/ECX<>0時重複.
REPNE/REPNZ 當ZF=0或比較結果不相等,且CX/ECX<>0時重複.
REPC 當CF=1且CX/ECX<>0時重複.
REPNC 當CF=0且CX/ECX<>0時重複.

五、程序轉移指令
───────────────────────────────────────
1>無條件轉移指令 (長轉移)
JMP 無條件轉移指令
CALL 過程調用
RET/RETF過程返回.
2>條件轉移指令 (短轉移,-128到+127的距離內)
( 當且僅當(SF XOR OF)=1時,OP1<OP2 )
JA/JNBE 不小於或不等於時轉移.
JAE/JNB 大於或等於轉移.
JB/JNAE 小於轉移.
JBE/JNA 小於或等於轉移.
以上四條,測試無符號整數運算的結果(標誌C和Z).
JG/JNLE 大於轉移.
JGE/JNL 大於或等於轉移.
JL/JNGE 小於轉移.
JLE/JNG 小於或等於轉移.
以上四條,測試帶符號整數運算的結果(標誌S,O和Z).
JE/JZ 等於轉移.
JNE/JNZ 不等於時轉移.
JC 有進位時轉移.
JNC 無進位時轉移.
JNO 不溢出時轉移.
JNP/JPO 奇偶性爲奇數時轉移.
JNS 符號位爲 "0" 時轉移.
JO 溢出轉移.
JP/JPE 奇偶性爲偶數時轉移.
JS 符號位爲 "1" 時轉移.
3>循環控制指令(短轉移)
LOOP CX不爲零時循環.
LOOPE/LOOPZ CX不爲零且標誌Z=1時循環.
LOOPNE/LOOPNZ CX不爲零且標誌Z=0時循環.
JCXZ CX爲零時轉移.
JECXZ ECX爲零時轉移.
4>中斷指令
INT 中斷指令
INTO 溢出中斷
IRET 中斷返回
5>處理器控制指令
HLT 處理器暫停, 直到出現中斷或復位信號才繼續.
WAIT 當芯片引線TEST爲高電平時使CPU進入等待狀態.
ESC 轉換到外處理器.
LOCK 封鎖總線.
NOP 空操作.
STC 置進位標誌位.
CLC 清進位標誌位.
CMC 進位標誌取反.
STD 置方向標誌位.
CLD 清方向標誌位.
STI 置中斷允許位.
CLI 清中斷允許位.

六、僞指令
───────────────────────────────────────
DW 定義字(2字節).
PROC 定義過程.
ENDP 過程結束.
SEGMENT 定義段.
ASSUME 建立段寄存器尋址.
ENDS 段結束.
END 程序結束.
3.4 再談寄存器和內存的區別
  第零講說到“寄存器在CPU中。內存在內存條中。前者的速度比後者快100倍左右。後面的程序要求每條指定要麼沒有內存數據,要麼在有一個寄存器的參與下有一個內存數據。(也就是說,不存在只訪問內存的指令)。”
  寄存器是在CPU中的存儲器,而內存是在內存條中的存儲器。CPU訪問寄存器,只需要通過微指令直接就可以訪問,而訪問內存則要先經過總線,再由總線到達內存控制器,讀到某單元的內存數據後放上總線,再傳到CPU中,CPU才能使用。
  8086系列計算機的寄存器,共有14個,每個都是十六位的。
AX,BX,CX,DX,SP,BP,SI,DI,CS,DS,SS,ES,IP,FLAGS。
其中前四位,每個可以單位再分成兩個,AX=AH+AL,BX=BH+BL,CX=CH+CL,DX=DH+DL。這些分開的每個都是8位的。
  這個分開不要理解成平時語言中的分開,你可以理解爲AX是由AH和AL組合成的,你給AL付值,就意味着同時給AX的低半部付值。你給AX付值,就意味着同時改變AH和AL。這樣作的好處是你可以更靈活地控制這個寄存器。


3.5 指令說明
看了3.3的指令集和3.4的寄存器,是不是已經暈了,或者了迷糊?不要急,上面的東西雖然多,我也沒讓你一下學會,(其實有些永遠也不會似乎也不是什麼大不了的事)。爲了應付看的懂我後面所說的,我把其中的指令挑幾個重點的,你必須要記住,其它的慢慢學吧。

1數據傳輸指令。
mov A,B
注意不是move,這個指令是把B中的數據複製給A,(B中仍保存原狀)。這裏的A和B可以是寄存器,可以是內存。但可以同時是寄存器,不能同時是內存。比如
mov ax,100 ;這是對的,注意100在這裏叫立即數,但這個數在編譯系統編譯成exe的時候保存在內存中。如果學過別的高級語言,你就可以理解爲這就是賦值語句 Let ax=100/ax:=100;/ax=100。
2 僞指令
僞指令就是不是真的指令,但他同時又是指令。之所以說這樣矛盾的話,是因爲僞指令不是機器語言的一部分,而是彙編語言的一部分,是你告訴彙編的編譯系統如何去作。
string DB '這是我的第一個彙編語言程序$'
上面一行指令中,DB就是僞指令,他的作用就是告訴編譯程序,把後面一些數據或字符串放到內存中。當然對於exe來說,已在內存中了,就不用“告訴”了。(這就是爲什麼叫僞指令)。string是你給這段內存起的名字,如果你不需要這段內存,不起名字也可以,但如果後面要用,當然要加上這個名字。'這是我的第一個彙編語言程序$'這個就是要處理的數據,當然你也可以換成別的內容,但需要注意的是,要以'$'結尾,這是彙編的約寫,即:只是到了$,就認爲字符串結束,否則就一直向下找,直到找到一個$爲止。所以這就要求你的字符串中不能有'$',如果必須有,再換別的處理方式,後面再說。
3 地址傳送指令
Lea A,string
前面已經定義了string,後面要把地址找到,就要用到lea指令。lea是把字符串的地址給A這個寄存器中,A當然可以上前面提到的任意寄存器。注意地址和內容的區別。如果是內容就是把string的字符串給A了。(當然這也不成立,一個字符串有很多字節,而一個寄存器只有兩個字節)。
那麼從上面也看到了,string代表一個地址,lea把這個地址給了A,那這個地址到底在哪裏呢?事實上這不重要,就象你要把某書店買書,這個書店在哪並不是最重要的,有沒有你要的書纔是最重要的。所以你前面標出string,後面引用就行了,至於這個地址到底在哪是編譯程序的事,不是你的事。

4 運算指令
ADD A,N
這個很容易理解吧,寄存器A加上N,把和仍存在A中。類似於高級語言中的let a=a+n/a:=a+n/a+=n。

5 串操作指令
記住串操作指令表面很複雜,其實很簡單。
因爲他就象一個複雜的數學公式一樣簡單,你所要記住的就是公式的格式,使用時具體套用即可。
從一個地址到另一個地址的複製需要注意的是:
*把源串段地址給DS。
*把源串編址給SI。
*把目的串段址給ES。
*把目的串偏址給DI。
*把要複製的個數給CX,這裏可不考慮$了。
*把FLAG中的方向標誌標誌你要的方向,一個是順向,另一個是逆向。
*發送loop movs,scans等命令。


6 轉移指令
  記住:無條件轉移指令 jmp。等於轉 jz,不等於時轉jnz

7 中斷指令
  int 中斷號,注意進制,默認是十進制,所以十六進制就加h。

好了,上面的指令變成七八個了,這你不能嫌多了吧,如果再嫌多就不要繼續向下看了。
第四講 彙編程序

4.1 彙編程序框架

data SEGMENT '數據段,編程者可以把數據都放到這個段裏
....數據部分
'數據格式是: 標識符 db/dw 數據。
data ENDS'數據段結束處。

edata SEGMENT '附加數據段,編程者可以把數據都放到這個段裏
....附加數據部分
edata ENDS'附加數據段結束處。

code SEGMENT'代碼段,實際的程序都是放這個段裏。
      ASSUME CS:code,DS:data,ES:edata '告訴編譯程序,data段是數據段DS,code段是代碼段CS
start:MOV AX,data '前面的start表示一個標識位,後面用到該位,如果用不到,就可以不加
      MOV DS,AX '這一句與上一行共同組成把data賦值給DS。段寄存器.
      MOV AX,edata
      MOV ES,AX '與前一句共同組成edata->ES
      .......程序部分
      MOV AX,4C00h'程序退出,該句內存由下一行決定。退出時,要求ah必須是4c。
      INT 21h
code ENDS'代碼段結束。
END start'整個程序結束,並且程序執行時由start那個位置開始執行。


上面就是一個程序的框架結構。在這個結構中,有三個段,DS,ES,CS。這三個段分別存數據,附加數據,代碼段。

4.2 編寫我們的Hello,world思路。
開始編寫我們的第一個程序。
程序要求:顯示一個“Hello,Mr.286.”怎麼樣?
思路:
1 要顯示一個字符串,根據前面我讓你們記的七八個指令夠嗎?答案是:不僅夠,而且還用不完。
首先定義一下總可以吧。

hellostr db 'Hello,Mr.286.$'
最後的$不要忘了。

2 首先要考慮的問題就是找中斷,找到合適的中斷,該中斷就能幫我們完成這個顯示任務。我找到(在哪找到的,怎麼找到的,別問我,到網上或書上都能找到):
-------------------------------------------
中斷INT 21H功能09H

功能描述: 輸出一個字符串到標準輸出設備上。如果輸出操作被重定向,那麼,將無法判斷磁盤已滿
入口參數: AH=09H
DS:DX=待輸出字符的地址
說明:待顯示的字符串以’$’作爲其結束標誌
出口參數: 無
-------------------------------------------
由上面看到,我們所需要作的就是把DS指向數據段,DX指向字符串的地址,AH等於9H,調用21h中斷。
mov ds,數據段地址
lea dx,hellostr 'hellostr已在前面1中定義了。
mov ah,9h
int 21h。
由於只要在調用int 21h之前把準備的東西準備齊就行了,所以int 21h前面三行的順序並不重要。

3 退出程序,運行完總要退出呀。再查中斷手冊
--------------------------------------------
中斷INT 21H功能4CH

功能描述: 終止程序的執行,並可返回一個代碼
入口參數: AH=4CH
AL=返回的代碼
出口參數: 無

--------------------------------------------
mov ah,4Ch
mov al,0
int 21h

mov ax,4c00h
int 21h
這裏需要說明的是返回代碼有什麼用,返回給誰?返回給操作系統,因爲是操作系統DOS調用的這個程序,這個返回值可以通過批處理中的errorlevel得到,這裏不多說明,實際上操作系統很少處理這一值,因此al你隨便寫什麼值影響都不大。

4.3 程序實現
data SEGMENT
msg DB 'Hello, Mr.286.$'
data ENDS

code SEGMENT
      ASSUME CS:code,DS:data
start:MOV AX,data
      MOV DS,AX
      lea dx,msg
      mov ah,9h
      int 21h
      MOV AX,4C00h
      INT 21h
code ENDS
END start

4.4 編譯運行。
把上面程序保存成hello286.asm後,就可以編譯運行了。進入DOS,進入彙編目錄,如果還沒下載,到前面找下載地址。

=================================================
E:/Download/Masm>masm hello286.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.

Object filename [hello286.OBJ]:
Source listing  [NUL.LST]:
Cross-reference [NUL.CRF]:

  50408 + 415320 Bytes symbol space free

      0 Warning Errors
      0 Severe  Errors
說明:上面連續三個回車,表示我要的都是默認值。下面是零個警告,零個嚴重錯誤,(當然了,我的程序還敢錯嗎?)

E:/Download/Masm>link hello286

Microsoft (R) Overlay Linker  Version 3.60
Copyright (C) Microsoft Corp 1983-1987.  All rights reserved.

Run File [HELLO286.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK : warning L4021: no stack segment

說明:三個回車仍要默認,後面有個警告,沒有棧段,這個沒關係,沒有的話系統會自動給一個。

E:/Download/Masm>hello286
Hello, Mr.286.
說明:運行成功。
E:/Download/Masm>
4.4 深度思考
4.4.1 是不是數據必須放數據段,代碼必段放代碼段呢?
答,代碼必段放代碼段,否則你怎麼執行呀?但數據也可以放到代碼段,只是程序要作修改。
code SEGMENT
      ASSUME CS:code,DS:data
      msg DB 'Hello, Mr.286.$'
start:MOV AX,data
      MOV DS,AX
      lea dx,msg
      mov ah,9h
      int 21h
      MOV AX,4C00h
      INT 21h
code ENDS
END start
編譯後仍然可以。
4.4.2 我編的程序在內存中是什麼樣子的呢?
------------------------------------------------------------------------
E:/Download/Masm>debug hello286.exe
-u
1420:0000 B81F14        MOV    AX,141F
1420:0003 8ED8          MOV    DS,AX
1420:0005 8D160000      LEA    DX,[0000]
1420:0009 B409          MOV    AH,09
1420:000B CD21          INT    21
1420:000D B8004C        MOV    AX,4C00
1420:0010 CD21          INT    21
1420:0012 FF362421      PUSH    [2124]
1420:0016 E87763        CALL    6390
1420:0019 83C406        ADD    SP,+06
1420:001C FF362421      PUSH    [2124]
-d 141f:0000 L20
141F:0000  48 65 6C 6C 6F 2C 20 4D-72 2E 32 38 36 2E 24 00  Hello, Mr.286.$.
141F:0010  B8 1F 14 8E D8 8D 16 00-00 B4 09 CD 21 B8 00 4C  ............!..L
-q

E:/Download/Masm>
------------------------------------------------------------------------------
上面是什麼呀?還記得前面說的嗎?
1420:0000 B81F14        MOV    AX,141F
  |  |      |          |          |
段址:偏址 機器語言      mov指令 把段地址的地址(141f)賦值給AX寄存器。

1420:0012後面的是垃圾數據,不用管它,把上面程序與源程序作一個比較,看有什麼不用,差別在於把標號語言轉成實際地址了。
程序前兩行一執行,數據段地址就變成了141f,而那個字符串偏移地址在0000,由(LEA    DX,[0000]看出),所以我用-d 141f:0000 L20(後面L20表示只顯示20個字節),就能把段地址顯示出來了。
所以剛纔的程序在內存中就變成了:
141f:0000 Hello, Mr.286.$  ----->這是段地址裏的內存
1420:0000 B81F14        MOV    AX,141F  ------>這是代碼段裏的內存。data變成了實際地址
1420:0003 8ED8          MOV    DS,AX
1420:0005 8D160000      LEA    DX,[0000] ------>偏址變成了0000,因爲實際上msg也就是從頭開始的。當然是0了。
1420:0009 B409          MOV    AH,09    ------->注意Debug裏,默認的是十六進制
1420:000B CD21          INT    21
1420:000D B8004C        MOV    AX,4C00
1420:0010 CD21          INT    21

  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章