王爽 16 位汇编语言学习记录
以下為匯編學習記錄,內容全部出自王爽的16位《匯編語言》,如有錯誤,可直接去查看原書。
匯編語言
??機器語言是機器指令集的集合,機器指令是一列二進制數字,計算機將其翻譯成高低電平,從而使器件收到驅動。而程序員很難看懂!例如:8086 CPU 完成運算 s = 768 + 12288 – 1280,對應的機器碼是:
10110000000000000000011 00000101000000000110000 00101101000000000000101??匯編語言的主體是匯編指令。匯編指令是機器指令便于記憶的書寫格式。其最終由編譯器將他們處理成對應的機器語言,由機器執行。
匯編語言的組成:
- 匯編指令:機器碼的助記符,有對應的機器碼 每條指令語句在匯編時都產生一個供CPU執行的機器目標代碼。
- 偽指令:由匯編器執行,沒有對應的機器碼 它所指示的操作是由匯編程序在匯編源程序時完成的,在匯編時,它不產生目標代碼,在將源程序匯編成目標程序后,它就不復存在。
- 宏指令:
- 其他符號:如:+、-、*、等,由匯編器執行,沒有對應的機器碼
??指令和數據存放在存儲器中,也就是平常說的內存中。在內存或硬盤上存儲的數據和指令沒有區別,都是二進制信息。例如:內存中有 1000100111011000,作為數據看是 89D8 H,作為指令看是 mov ax, bx。存儲器被劃分為若干單元,單元從 0 開始編號,最小信息單位為 bit(位),8 個 bit 組成一個 byte(字節),微機存儲器以字節為最小單位來計算。
CPU 對存儲器的讀寫
CPU 從 3 號單元中讀取數據過程:
地址總線
??CPU 通過地址總線選定存儲單元。一個 CPU 有 N 根地址總線,則可以說這個 CPU 的地址總線寬度是 N,其最多可以尋找 2N 個內存單元。地址總線決定了其尋址能力。
注意高低地址。上圖實際選定的內存單元是 0000001011(即:11號單元)
數據總線
??CPU 和內存或其他器件的數據傳送是通過數據總線進行的。
對于不能一次傳送的數據,將先傳送低字節,后傳送高字節。例如:對于 89D82 H,將先傳送 9D82,后傳送 8。
控制總線
??CPU 對外部器件的控制是通過控制總線進行的。CPU 有多少控制總線,對外部器件就有多少種控制。控制總線決定了 CPU 對外部器件的控制能力。
??每個物理存儲器在邏輯存儲器中占據一定的地址空間,CPU 在相應的地址空間中寫入數據,實際上就是向該物理存儲器中寫數據。例如:在 8086PC 中,
- 0 ~ 7fffH 的 32KB 空間為主存儲器的地址空間
- 8000H~9fffH 的 8K 空間為顯存的地址空間
- A000H~ffffH 的 24K 空間為各個 ROM 的地址空間
那么,向8001H里面寫入數據,實際就是把數據寫入到顯存中
不同的計算機內存地址空間的分配是不同的
寄存器
??8086 CPU 共有 14 個寄存器,AX、BX、CX、DX,SI、DI,SP、BP、IP,CS、DS、SS、ES,PSW。
通用寄存器
??8086 CPU 中,AX、BX、CX、DX通常用來存放一般性數據,稱為通用寄存器。他們均為16位。并且都可以分為兩個8位的寄存器(高8位和低為位)使用
- AX 可分為 AH 和 AL
- BX 可分為 BH 和 BL
- CX 可分為 CH 和 CL
- DX 可分為 DH 和 DL
??出于兼容性,8086 CPU 可以一次處理兩種尺寸的數據:字節型和字型(雙字節。高地址存放高字節,低地址存放低地址)。在進行數據傳送或運算時,指令操作對象的位數應該一致,例如:不能在字與字節類型之間傳送數據:
mov ax, bl (錯誤的指令)
mov bh, ax (錯誤的指令)
物理地址
8086 CPU 是 16 位結構:
- 運算器一次最多處理 16 位數據
- 寄存器最大寬度是16 位
- 寄存器和運算器之間的通路是16位
8086 CPU 有 20 根地址線,然而其又是 16 位結構,所以 8086 CPU 內部用兩個 16 位地址合成一個 20 位地址
地址加法器采用 物理地址 = 段地址 x 16 + 偏移地址 的方法合成 20 位的物理地址。
段的概念
??段的劃分源自于 8086 的尋址方式,實際上,內存并不會分段。我們把連續的一段內存用段加以描述,從而方便 8086 的尋址。由計算式可知,段的起始地址一定是 16 的倍數,偏移地址為 16 位,16 位的尋址能力為 64K,則一個段的最大長度為 64K。
段寄存器
??8086 CPU 中,用CS、DS、SS、ES 四個段寄存器來存放內存單元的段地址。CS 和 IP 是 8086 CPU 中兩個關鍵的寄存器,他指出了 CPU當前要讀取的指令地址。CS 稱為代碼段寄存器,IP 稱為指令指針寄存器 。任意時刻,8086 CPU 將 CS: IP 指向的內容當做指令執行。
??8086 CPU 工作過程:
修改 cs 和 ip 的值
jmp 指令
格式: jmp 段地址:偏移地址 ;執行后, cs = 段地址, IP = 偏移地址
例如: jmp 2AE3H:3 ;執行后,CS =2AE3 , IP = 3
格式: jmp 寄存器 ;執行后 , ip = 寄存器的值
例如: jmp ax ;若執行前,ax = 1000H cs = 2000H ip = 0003H ;則執行后, ax = 1000H cs = 2000H ip = 1000H
注意:mov指令不能修改CS和IP的值
實驗一 debug的使用
注意在 Debug 中,數據都是用十六進制表示,且不用加 H
- 顯示所有寄存器和標志位狀態
- 顯示當前 CS:IP指向的指令。
2. 用 D 命令查看內存內容
- 直接輸入d,查看當前 cs:ip 指向的內存內容,注意:如果繼續輸入d,則可繼續查看后面的內存的內容
- 輸入d 段地址 : 偏移地址,查看指定的內存地址的內容。如果繼續輸入d,則可繼續查看后面的內存的內容
- 輸入d 段地址: 偏移地址 結束地址,查看指定地址內存內容。如果繼續輸入d,則可繼續查看后面的內存的內容
- 輸入 e 段地址: 偏移地址 數據1 數據2 數據3 …,修改指定地址內存中的內容
- 輸入 e段地址: 偏移地址,可以逐個字節進行修改,注意:不修改直接輸空格,輸完數后,按空格輸入下一個,回車直接結束
- 使用 e 命令可以輸入字符(單引號標識)或字符串(雙引號標識),都是存儲的ASCII
- 用 e 命令向內存中寫入機器碼,用U命令查看內存中機器碼的含義,用T命令執行內存中的機器碼
- 使用 e 命令可以輸入字符(單引號標識)或字符串(雙引號標識),都是存儲的ASCII
- 直接輸入a,在當前內存(CS:ip指向的內存)中輸入匯編指令
- 輸入a 段地址: 偏移地址,在指定的內存中輸入匯編指令
- 輸入 a 偏移地址,向 cs:偏移地址 指向的內存中的寫入匯編指令
用 u 命令查看內存中機器碼對應的匯編指令
- 直接輸入u,查看當前內存(CS:ip指向的內存)中機器碼對應的匯編指令
- 輸入u 段地址: 偏移地址,查看指定的內存中的機器碼對應的匯編指令
使用T命令執行內存中的匯編代碼
- 直接輸入t,執行當前指令
使用G命令將程序執行到指定地址處
- 輸入g 偏移地址,表示將指令執行到當前偏移地址處
使用 P 命令可以一次性執行完循環,且int 21H指令必須用P命令執行
- 當遇到循環時,輸入p,即可直接執行完循環
用 DEBUG 跟蹤程序
- 輸入debug 要跟蹤的程序全名,debug 將程序加載進內存
注意:
加載進內存后,cx中存放的是程序的長度(占用機器碼的字節數),上圖說明3-1.exe占的機器碼是22個字節(十六進制表示為16H)
debug 中,對于最后的 int 21 指令,需要用 p 命令執行
說明: 當加載進內存后,CS變被賦予SA+10H,IP被賦值0
數據段寄存器DS
mov 指令
mov 寄存器, 立即數 ; 將數據直接送入寄存器 例:mov ax,2 mov 寄存器, 寄存器 ; 將一個寄存器中的值送入另一個寄存器中 mov ax,bx mov 寄存器, 內存單元 ; 將一個內存單元中的數據送入寄存器 mov ax , [0] mov 內存單元, 寄存器 ; 將一個寄存器中的數據送入指定的內存單元 mov [1], bx mov 段寄存器, 寄存器 ; 將一個寄存器的值送入段寄存器 mov ds, ax mov 寄存器, 段寄存器 ; 將一個段寄存器中的值送入一般寄存器 mov ax, ds mov 段寄存器, 內存單元注意:
CPU 提供的棧機制
push(進棧)和pop(出棧)都是以字為單位進行的。POP 和 PUSH 指令:
push 寄存器 ;將一個寄存器中的數據入棧 pop 寄存器 ;用一個寄存器接受出棧的棧頂元素 push 段寄存器 ;將一個段寄存器中的數據入棧 pop 段寄存器 ;用一個段寄存器接受出棧的棧頂元素 push 內存單元 ;將一個內存字單元處的數據入棧 pop 內存單元 ;用一個內存字單元接受出棧的棧頂元素注意:
例如: mov ax, 1000H
mov ds, ax ; 存放數據段的段地址
push [0] ; 將1000:0內存字單元中的數據進棧
pop [2] ; 將出棧的數據放到1000:2內存字單元中
??8086 CPU 提供 SS 和 SP 兩個寄存器來標識棧。SS 存放棧頂段地址,SP 存放偏移地址。任意時刻,SS:SP 指向棧頂。push 和pop 指令執行時,自動從 SS:SP 指向處取得棧頂地址
- PUSH 指令的執行過程
注意:
- POP指令的執行過程
注意:出站后,SS:SP 指向新棧頂,pop 執行前的棧頂元素仍然存在(如上圖的 2266H),只是它已不再棧中(棧頂已改變),再一次使用 push 指令時,將覆蓋原有數據。
8086 CPU不保證對棧的操作不會越界
- PUSH入棧越界
- POP出棧越界
和上圖基本相似
編程實例
要求:
(1)將10000H~1000FH作為棧空間,初始狀態棧空
(2)設置 ax = 001AH,bx = 001BH
(3)將ax和bx的值入棧
(4)然后將ax和bx清零
(5)最后從棧中恢復ax和bx的值
程序:
段的綜述
??我們可以將一段內存定義為一個段,用一個段地址指示段,用偏移地址訪問段內的單元,這完全是我們自己的安排。
- 可以用一個段存放數據,將它定義為“數據段”;
- 可以用一個段存放代碼,將它定義為“代碼段”;
- 可以用一個段當做棧,將它定義為”棧段“;
我們可以這樣安排,但是若要 CPU 按照這種安排來訪問這些段,就要:
- 對于數據段,將它的段地址存放在DS中,用mov、add、sub等訪問內存單元的指令時,CPU就將我們定義的數據段中的內容當作數據來訪問。
- 對于代碼段,將它的段地址存放在CS中,將段中第一條指令的偏移地址存放在IP中,這樣cpu就將執行我們定義的代碼段中的指令。
- 對于棧段,將它的段地址放在SS中,將棧頂單元的偏移地址存放在SP中,這樣cpu在需要進行棧的操作時,如執行push, pop指令等,就將我們定義的棧當作棧空間來用。
??可見,不管我們如何安排,CPU將內存中的某段內容當作代碼,是因為CS:IP指向了那里;CPU將某段內存當作棧,是因為SS:SP指向了那里;我們一定要清楚,什么是我們的安排,以及如何讓CPU按我們的安排行事。要非常清楚CPU的工作機理,才能在控制CPU按照我們的安排運行的時候做到游刃有余。
??一段內存,可以既是代碼段的存儲空間,又是數據的存儲空間,還可以是棧空間,也可以什么都不是。關鍵在于CPU中寄存器的設置,即 CS、IP,SS、DS 的指向。
段前綴
??在匯編程序中,可以顯示給出段地址,這些顯示的段地址稱為段前綴。例如:ds:[bx] 、ds: [0]、 ss: sp 、cs: sp 、cs: ip 等。顯示給出段前綴時,將使用給出的寄存器作為段地址,而不是使用默認段寄存器
第一個匯編程序
基本格式(包含多個段):
assume cs: code, ds: data, ss: stack ; 偽指令,將寄存器和各段聯系起來 data segment ; 數據段 ; 偽指令,格式:段名 segment,表示一個段的開始,段名表示一個地址,被編譯時翻譯成地址dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data ends ; 偽指令,和他上面的段名 segment成對存在,格式:段名 ends,表示一個段的結束 stack segment ; 棧段 ; 可同時定義多個段(代碼段、數據段、棧段)dw 0,0,0,0,0,0,0,0 stack ends code segment ; 代碼段 ; 可以不定義數據段和棧段,但代碼段不可少,否則程序根本沒意義 start: ; 標號 ; 標號代表一個地址,這個標號在編譯時被翻譯成地址 mov ax, stack ; 段名表示一個地址,被編譯時翻譯成地址mov ss, ax mov sp, 16 ; 初始情況,棧底與棧底相同,高地址表示棧底mov ax, data ; 段名表示一個地址,被編譯時翻譯成地址mov ds, axpush ds: [0] push ds: [2] pop ds: [2] pop ds: [0] mov ax,4c00h int 21h ; 這兩條語句為一組,表示程序的返回 code ends end start ; 偽指令,end標志著一個匯編程序的結束,編譯器遇到end就結束對程序的編譯,同時指出了程序的入口為start處注意:在多個段中,各段空間相互獨立,地址都是從 0 到段大小。 例如上例:數據段空間 0 ~ 15(字節空間),棧空間 15 ~ 0(字節空間),代碼段從 start 開始
基本格式(只有一個段)
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 start: mov ax, codesg ;或mov ax, cs mov ss, ax mov sp, 24h ;上三條指令設置棧頂指針,使指向了codesg:24H ;或mov sp, 36 ; 36是十進制對應的16進制是24Hmov ax,0 mov ds, ax mov bx,0 ; 這三條指令設置數據指針,使其指向了0: 0,即本段數據的開始處mov cx,8 ; 循環次數s: push [bx] ; 將0123H入棧,默認數據段寄存器ds,此處是 ds: [bx]。注意:push和pop指令一次操作一個字(兩個字節)pop cs: [bx] ; 或 pop ss: [bx] add bx, 2 ; 注意:push和pop指令一次操作一個字(兩個字節)loop s mov ax,4c00h int 21h codesg ends end start注意:只有一個段時,各種代碼公用一段空間。例如上例:數據從 dodesg 開始占(0 ~ 15),棧則跟在后面,從 16~31 ,start 處的指令實際是從 32 開始
[bx]和 loop 指令
[bx] 使用:
mov ax, [bx] ; 其中,[bx]表示內存單元,段地址默認ds中,該指令表示將ds:[bx]字單元的內容送入寄存器ax mov [bx], ax ; 其中,[bx]表示內存單元,段地址默認ds中,該指令表示將寄存器ax的內容送入ds:[bx]字單元例:指令執行后的內存情況
loop指令:
例:用 loop 指令計算 211
assume cs:code code segment strat:mov ax, 2mov cx, 11s: add ax, axloop s mov ax, 4c00H int 21H code ends end startDEBUG與匯編編譯器對指令的不同處理
實例:計算 ffff:0~ffff:b 中的數據之和,結果存在 dx 中
分析:
解決方法:用一個 16 位寄存器做中介,先將數據存入ax中,用 ax 和 dx 做加法,加過存在 dx 中。程序:
assume cs:code code segment start: mov ax, 0ffffH ; 注意,匯編中數據不能以字母開頭,因此要在前面加0 mov ds, ax mov bx, 0 ; 初始化,使ds: bx指向ffff: 0 mov dx, 0 ; 初始化累加器 dx = 0 mov cx, 12 ; 循環次數 s: mov ah, 0 mov al, ds: [bx] ; ax作為中間寄存器 add dx, ax inc bxloop smov ax, 4c00H int 21H code ends end start更靈活的內存定位方法
and 指令: 按位與運算,通過該指令可以將操作對象的相應位設為0,其他位不變
例如:
or 指令: 按位或運算,通過該指令可以 操作對象的相應位設為1,其他位不變
mov al, 01100011B or al, 00111011B在匯編中,我們可以使用 英文單引號(’’) 來指明數據是字符。例如:db ’asm‘ ,編譯器將他們轉換為對應的ASCII碼。
大小寫轉換問題
就 ASCII 碼的二進制來看,除第五位外,大小寫字母的其他位都相同。大寫字母的第五位是 0,而小寫的第五位是 1。
[bx+idata] 的尋址方式
SI 和 DI 兩個寄存器,功能和 BX 相近,但 SI 和 DI 不能被分成兩個 8 位寄存器使用
- 實例分析:用 SI 和DI 將字符串 ’Welcome to masm!’ 復制到他后面的內存空間中。
- 實例分析:編程,將datasg段中每個單詞的前4個字母改為大寫字母。
數據處理的兩個基本問題
??8086 CPU中,只有 si、di、bx、bp 四個寄存器可以在 [ ] 中使用。四個寄存器可以單獨使用,也可以以以下組合出現:bx 和 si、bx 和 di、bp 和 si、bp 和 di。下面的指令都是正確的:
注意:
8086 CPU 尋址方式:
問題二:指令要處理的數據有多長
1. 通過寄存器指明處理數據的長度。在指令所使用的寄存器是多長,數據就是多長。例如: mov ax, [0] ax為16位的,所以處理的是兩個字節的內容,即偏移地址[0]和[1]兩個字節
2. 在沒有寄存器名存在的情況下,用操作符 X ptr 指明操作數的長度,X 可以是 Byte 和 Word。例如:
某些指令默認長度,如 pop 和 push 指令默認是對字單元操作
-
div 除法指令。格式: div 除數
div 寄存器 div 內存單元div 使用默認寄存器
數被除數位數被除數默認存放的寄存器商余數 16位 ax al ah 32位 (高位)dx + ax(低位) ax dx 只能出現以上兩種組合對應 ,不足時要不足位數。例如:
div byte ptr ds:[0] ; al = ax / (ds*16+0) 的商; ah = ax / (ds*16+0) 的余數 div word ptr es:[0] ; ax = (dx*10000H+ax) / (es*16+0)的商; dx = (dx*10000H+ax) / (es*16+0)的余數實例:編程計算100001/100
assume cs: code code segment start:mov dx, 1mov ax, 86A1H ; 注意:100001轉換為十六進制為186A1H,高位1給dx,低位86A1H給axmov bx, 100div bx code ends end start
分析:100001>65535,所以不能用ax存放,只能用dx和ax存放,被除數是32位的,因此除數必須是16位的(盡管100<255)
程序:
實驗7 尋址方式在結構化數據訪問中的應用
assume cs: code, ds: data, es: tabledata segment db '1975','1976','1977','1978','1979','1980','1981','1982','1983' db '1984','1985','1986','1987','1988','1989','1990','1991','1992' db '1993','1994','1995' ;以上是表示21年的字符串 4 * 21 = 84dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514 dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000 ;以上是表示21年每年公司總收入的dword型數據 4 * 21 = 84dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226 dw 11542,14430,15257,17800 ;以上是表示21年每年公司雇員人數的21個word型數據 2 * 21 = 42 data endstable segment db 21 dup ('year summ ne ?? ') ; 'year summ ne ?? ' 剛好16個字節 table endscode segment start: mov ax, data mov ds, ax mov ax, table mov es, ax mov bx,0 ;ds:[bx]在data中數據定位(和idata結合,用于年份和收入) mov si,0 ;es:[si]在table中定位(和idata給合用于定位存放數據的相對位置) mov di,0 ;ds:[di]在data中用于得到員工數 mov cx,21 ;cx循環次數 s:;將年從data 到 table 分為高16位和低16位mov ax, [bx]mov es:[si], ax ; 高16位mov ax, [bx+2]mov es:[si+2], ax ; 低16位;table 增加空格mov byte ptr es:[si+4],20h ; 0~3的四個字節是年份,第4個字節是空格;將雇員數從data 到 tablemov ax, [di + 168]mov es:[si + 10], ax;table 增加空格mov byte ptr es:[si+12],20h ; 10~11的四個字節是雇員數,第12個字節是空格;將收入從data 到 table 分為高16位和低16位mov ax, [bx+84]mov es:[si+5], ax ; 高16位mov dx, [bx+86]mov es:[si+7], dx ; 低16位;table 增加空格mov byte ptr es:[si+ 9],20h ; 5~8的四個字節是收入,第9個字節是空格;計算工資;取ds處工資,32位;mov ax,[bx + 84] ;mov dx,[bx + 86];計算人均收入, 注意:上面在將收入放到table中時剛好將數據放到了dx和ax中,因此不用再重新設置被除數div word ptr ds:[di + 168] ; ax = (dx*10000H+ax)/ ds:[di + 168]的商mov es:[si+13],ax ;將結果存入table處;table 增加空格mov byte ptr es:[si + 0fh],20h ; 13~14的四個字節是人均收入,第15個字節是空格(15的十六進制是f);改變三個寄存器值add si,16 ; table的下一行add di,2add bx,4loop smov ax,4c00h int 21h code ends end start轉移指令的原理
可以修改 IP 或同時修改 cs 和 ip 的指令稱為轉移指令。
-
offset 運算符:可以取得標號相對與所在段開始的偏移地址
-
jmp 指令:jmp 是無條件轉移指令,可以修改 ip 的值,也可以同時修改 cs 和 ip 的值
-
根據位移進行轉移的jmp指令。格式:jmp short 標號 ; 轉到標號處執行指令
注意: - 實現段內短轉移,對ip的修改范圍是 -128 ~ 127
- cpu 在執行 jmp 指令時并不需要轉移的目的地址
- 在 jmp short 標號指令多對應的機器碼中并不包含轉移的目的地址。而包含的是轉移的位移。這個位移是編譯器根據標號出來的。轉移位移的計算方法:
段內近轉移:jmp near ptr標號
注意:以上兩條指令的機器碼中,并不包含轉移的目的地址,而是包含轉移的位移 -
轉移的目的地址在 jmp 指令中
格式: jmp far ptr 標號 ;實現段間轉移,又稱遠轉移。轉到標號處執行指令
功能:cs = 標號所在段的段地址,ip = 標號所在段的偏移地址 -
轉移的目的地址在寄存器中的 jmp 指令
格式:jmp 16位寄存器
功能: ip = 16位寄存器的值 -
轉移的目的地址在內存中的 jmp 指令
mov ax, 1234Hmov ds:[0], axjmp word ptr ds:[0] ;執行后,IP = 1234H
格式 1:jmp word ptr 內存單元 ; 段內轉移
功能:ip = 該字型內存單元的值(2個字節)
例如:格式 2:jmp dword ptr 內存單元 ; 段間轉移
功能:cs = 該內存單元+2(高16位) ip = 該內存單元(低16位) -
jcxz 指令:jcxz 為有條件轉移指令,所有的有條件轉移指令都是短轉移,對應的機器碼中包含轉移的位移,而不是目的地址。范圍是 -128~127
格式:jcxz 標號 ; 如果cx = 0,則轉到標號處執行
操作:若 cx = 0 則 ip = ip + 8 位位移(補碼表示);cx != 0 ,繼續向下執行 -
loop 指令:循環指令都是短轉移,對應的機器碼中包含轉移的位移,而不是目的地址。范圍是-128-127
格式:loop 標號
操作: cx = cx – 1 若cx != 0 ,則ip = ip + 8位位移,轉到標號處;cx = 0 ,繼續向下執行
注意:編譯器會對轉移位移的越界進行檢查實驗 8
; 考察段內轉移時jmp指令的機器碼中,包含的是轉移的位移,而不是目的地址 assume cs:codesg codesg segmentmov ax,4c00h ;該指令占3個字節,機器碼是B84c00int 21h ; 該指令占2個字節, start: mov ax,0 ;ax=0,該指令占3個字節,機器碼B80000s: nop ;占一字節,機器碼90nop ;占一字節,機器碼90mov di, offset s ;(di)=s偏移地址,該指令占3個字節mov si,offset s2 ;(si)=s2偏移地址,該指令占3個字節mov ax,cs:[si] ;(ax) = jmp short s1指令對應的機器碼EBF6,該指令占3個字節mov cs:[di],ax ;jmp short s1覆蓋s處指令2條nop指令,jmp short s1占兩個字節,其機器碼為跳轉位移-8s0: jmp short s ;從此向下未執行,直接跳到2,然后就跳到mov ax,4c00h了s1: mov ax,0int 21hmov ax,0s2: jmp short s1 ;注意:對于jmp short s1指令,機器碼中存放的是偏移位移,而不是目的地址,此句的偏移位移為-8(十六進制是F8)nop codesg endsend start實驗 9
背景知識
??80 X 25 彩色字符模式顯示緩沖區的結構:內存地址空間中,B8000H~BFFFFH 共 32KB 的空間,為 80 X25 彩色字符模式的顯示緩沖區。想這個地址中寫入數據,寫入的數據將立即顯示在顯示器上。
??在 80 X 25 彩色模式下,顯示器可以顯示 25 行,每行 80 個字符(160 個字節),每個字符可以有256種屬性(背景色、前景色、閃爍、高亮等組合信息)。這樣,一個字符在顯示緩沖區中就要占兩個字節(一個字空間),低字節存放ASCII碼,高字節存放字符的屬性。在顯示緩沖區中,偶地址存放字符,奇地址存放字符的顏色屬性。在 80 X 25 彩色模式下,一屏幕的內容在顯示緩沖區中共占 4000 個字節。
??顯示緩沖區分為 8 頁,每頁 4KB(約為 4000B),顯示器可以顯示任意一頁的內容。一般情況下,顯示第0頁的內容
??一個在屏幕上現實的字符,具有前景(字符色)和背景(底色)兩種顏色。字符還可以以高亮度和閃爍的方式顯示。各屬性被記錄在字節位中。1 表示有效,0 表示無效。屬性字節格式
注:
- R表示紅色;G表示綠色;B表示藍色
- 可以使用任意字節位的屬性進行搭配
- 紅底綠字: 01000010B
- 閃爍紅底綠字:11000010B
- 黑底白字:00000111B ; RGB混合
-
ret 指令:用棧中的數據修改 IP
執行過程: - IP = SS X 16 + SP
- SP = SP+2
-
retf 指令:用棧中的數據同時修改CS和IP
執行過程: - IP = SS X 16 + SP ; 低字節
- SP = SP + 2
- CS = SS X 16 + SP ; 高字節
- SP = SP + 2
-
call 指令: 不能實現短轉移,至少實現近轉移
執行過程: - 將當前的IP或先CS和后IP壓棧
- 跳轉
例如:
程序
;編程:在屏幕中間分三行顯示綠色、綠底紅色、白底藍色的字符串 welcome to masm! ;黑框為80*25的屏幕,每行的字節數為80*2=160. ;要求顯示在屏幕中間,先計算行和列的偏移 ;行偏移:(25(總行數)- 3(字符竄占3行))/2 = 11.所以顯示在第11,12,13行。偏移值分別為1760,1920,2080。計算方法為 行數*160(每行的字節數) 。 ;列偏移:由于要顯示的字符數為16個,所以開始顯示的列偏移為(80-16)/2*2=64。 ;綠色最終位置為 11*160+64 = 1824,轉換為16進制為720H;綠底紅色最終位置為12*160+64 = 1984,轉換為16進制為7c0H;白底藍色最終位置為13*160+64 = 2144轉換為16進制為860H assume cs:code,ds:datadata segmentdb 'welcome to masm!' data endscode segment start:mov ax,datamov ds,axmov bx,0 ;ds:bx指向data字符串mov ax,0b800hmov es,axmov si,0 ;es:si指向顯存mov cx,16 ; 字符串長度為16個字節(0~15) s: mov al,[bx] ;字符賦值al,默認使用ds作為段寄存器mov ah,02h ;綠色; 兩個字節為一組,高位為字符屬性,低位放字符mov es:[si+720h],ax ;寫入第11行64列mov ah,14h ;綠底紅色mov es:[si+7c0h],ax ;寫入第12行64列mov ah,71h ;白底藍色mov es:[si+860h],ax ;寫入第13行64列inc bx ;指向下一字符add si,2 ;指向下一顯存單元loop smov ax,4c00hint 21hcode ends end start相當于POP IP
相當于 POP IP + POP CS
依據位移進行轉移的 call 指令 ---- 機器碼中包含轉移的位移,不包含目的地址。
格式:call 標號 ; 段內近轉移
操作:
SS X 16 + SP = IP ; ip進棧
注意:
相當于:push ip + jmp near ptr 標號
實例:下面的程序執行后,ax中的數值為多少?
內存地址 機器碼 匯編指令 執行后情況1000:0 b8 00 00 mov ax,0 ax=0 ip指向1000:31000:3 e8 01 00 call s 注意此處,CPU先將call s 讀到指令緩沖區中,使得ip增加,實際進棧的IP為call指令之后第一個地址。此處是6進棧1000:6 40 inc ax1000:7 58 s:pop ax ax=6轉移的目的地址在指令中的 call 指令
格式:call far ptr 標號 ; 段間轉移
操作:
SS X 16 + SP = CS ; cs進棧
SP = SP – 2
SS X 16 + SP = IP ; ip進棧
IP = 標號相對于所在段的偏移地址 ;完成跳轉
相當于:push cs + push ip + jmp far ptr 標號
實例:下面的程序執行后,ax中的數值為多少?
內存地址 機器碼 匯編指令 執行后情況 1000:0 b8 00 00 mov ax,0 ax=0,ip指向1000:3 1000:3 9a 09 00 00 10 call far ptr s 注意此處,CPU先將call far ptr s 讀到指令緩沖區中,使得ip增加,實際進棧的IP為call指令之后第一個地址。此處是cs = 1000先進棧,然后ip = 8進棧 1000:8 40 inc ax 1000:9 58 s:pop ax ax=8h,從上面的執行可看出add ax,ax ax=10h pop bx bx=1000h add ax,bx ax=1010h轉移地址在寄存器中的call指令
格式:call 16位寄存器
操作:
SS X 16 + SP = IP ; ip進棧
相當于:push ip + jmp 16位寄存器
實例:下面的程序執行后,ax中的數值為多少?
內存地址 機器碼 匯編指令 執行后情況 1000:0 b8 06 00 mov ax,6 ax=6,ip指向1000:3 1000:3 ff d0 call ax 此處同上,進棧的仍是call指令后第一個字節的地址5 1000:5 40 inc ax 1000:6 58 mov bp,sp bp=sp=fffehadd ax,[bp] ax=[6+ds:(fffeh)]=6+5=0bh轉移地址在內存中的call指令
格式 1:call word ptr 內存單元 ; 段內近轉移
功能:ip = 該字型內存單元的值(2個字節)
操作:
SS X 16 + SP = IP ; ip進棧
相當于:push IP + jmp word ptr 內存單元
例如:
格式 2:call dword ptr 內存單元 ; 段間轉移
功能:cs = 該內存單元+2(高16位) ip = 該內存單元(低16位)
操作:
SS X 16 + SP = CS ; cs進棧
SP = SP – 2
SS X 16 + SP = IP ; ip進棧
ip = 該內存單元(低16位) ;完成跳轉
相當于: push cs + push IP + jmp word ptr 內存單元
實例:下面的程序執行后,ax和bx中的數值為多少?
利用 call 和 ret 來實現子程序的機制
格式:
……code segmentmain:……call sub1 ; call指令將其后第一個字節地址壓棧后,跳轉……mov ax, 4c00Hint 21Hsub1:子程序用到的寄存器入棧 ; 主要是為了防止子程序用的寄存器和主程序沖突…call sub2…子程序用到的寄存器出棧 ret ; ret指令恢復之前call壓棧的值,注意:此處要保證子程序中沒有修改棧中的數據,否則將不能返回sub2:子程序用到的寄存器入棧 ……子程序用到的寄存器出棧 Retcode ends end main- mul 乘法指令
格式:div 寄存器 或 div 內存單元
mul 使用默認寄存器
只能出現以上兩種組合。例如:mul byte ptr ds:[0] ; ax = ah * (ds*16+0) 的積 mul word ptr es:[0] ; ax = ax * (es*16+0)的低8位; dx = ax * (es*16+0)的高8位
參數和結果傳遞問題
實例:設計一個子程序,計算data段中第一組數的3次方,保存在后面的一組dword單元中
程序: assume cs: code, ds: datadata segmentdw 1, 2, 3, 4, 5, 6, 7, 8dd 0, 0, 0, 0, 0, 0, 0, 0data endscode segmentstart:mov ax, datamov ds, axmov si, 0 ; ds:[si]讀取dw數據mov di, 0 ; ds:[di]將數據保存到dd數據中mov cx, 8s:mov bx, [si] ; 用bx傳遞參數call cubemov ds:[di], ax ; ax是低位,注意dd是雙字(占四個字節)mov ds:[di+2], dx ; 高位add si, 2add di, 4loop smov ax, 4c00hint 21h ; 說明:計算n的3次方 ; 參數:bx = n ; 返回值:dx = 結果高位 ; ax = 結果低位 cube:mov ax, bx ; 注意給出的數據時16位的mul bx ; ax中數* bx中數,結果的高位自動放入dx,mul bxret code ends end start
實例:設計一個子程序,計算data段中中的字符串轉化為大寫
程序: assume cs: code, ds: datadata segmentdb ’convensation’data endscode segmentstart:mov ax, datamov ds, axmov si, 0 ; ds:[si]z=指向字符串mov cx,12call touppermov ax, 4c00hint 21htoupper:and byte ptr ds:[si], 11011111Binc siloop toupperretcode endsend start
略
寄存器沖突問題
解決方法:在程序的開始將所用的寄存器中的內容保存起來,子程序返回前在恢復,可以用棧保存寄存器中的數據
未完待續…
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的王爽 16 位汇编语言学习记录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ARM 之十一__weak 和 __at
- 下一篇: 华大 MCU 之一 HC32F460 替