汇编语言学习笔记-2 0x00 包含多个段的程序 问几个问题:
计算0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H的和,并保存在ax中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 assume cs:code code segment ; dw表示一个字,这些数据存储在CS:0, CS:2, CS:4,..., CS:E中 dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H mov cx, 8 mov bx, 0 mov ax, 0 loop1: add ax, cs:[bx] add bx, 2 loop loop1 mov ax, 4c00H int 21H code ends end
但是这样编译运行会出现问题,因为程序一开始的CS:IP
指向了数据,但是程序却认为这是汇编指令。 因此这样改:
1 2 3 4 5 6 7 8 9 assume cs:code code segment ... start: ... loop1: ... code ends end start ; end的作用:(1)通知编译器程序结束。(2)通知编译器程序的入口位置
利用栈,将上述8个数据进行逆序存放(以字为单位)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 assume cs:code code segment dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 start: mov ax, cs mov ss, ax mov sp, 22H mov bx, 0 ; 变量i mov cx, 8 ; 循环次数 ext: push cs:[bx] add bx, 2 loop ext mov bx, 0 mov cx, 8 put: pop cs:[bx] add bx, 2 loop put mov ax, 4c00H int 21H code ends end start
将数据、栈、代码放到不同的段 如上所示,放到同一个段显得混乱。那么我们就放到不同的段,如下所示(就是将上一节的程序改了改 ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 assume cs:code, 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 stack ends ; 代码段 code segment start: mov ax, stack ; 不是mov ax, cs了。段地址都要有中间变量来承接。 mov ss, ax mov sp, 22H mov ax, data mov ds, ax mov bx, 0 mov cx, 8 ext: push [bx] add bx, 2 loop ext mov bx, 0 mov cx, 8 put: pop [bx] add bx, 2 loop put mov ax, 4c00H int 21H code ends end start
CPU到底如何处理我们定义的段中的内容,当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的 。
0x01 更灵活的定位内存的方法 and
与or
指令不再多说。
也可以用字符来代替16进制数,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 assume cs:code, ds:data data segment db 'unIX' db 'foRK' data ends code segment start: mov al, 'a' mov bl, 'b' mov ax, 4c00H int 21H code ends end start
题目:将第一个字符串(BaSiC
)转化为大写,将第二个字符串(iNfOrMaTiOn
)转化为小写。其中,大写字母+20H=小写字母
,但是,我们还不知道如何区分大写字母与小写字母,下面给出一种方法,观察A=01000001H,a=01100001H
,可以看到只有第5位不同。第5位为1,那么为小写,第5位为0,那么为大写。 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 assume cs:code, ds:data data segment db 'BaSiC' db 'iNfOrMaTiOn' data ends code segment start: mov ax, data mov ds, ax mov cx, 5 mov bx, 0 s: mov al, [bx] and al, 11011111B mov [bx], al inc bx loop s mov cx, 11 mov bx, 5 s0: mov al, [bx] or al, 00100000B mov [bx], al inc bx loop s0 mov ax, 4c00H int 21H code ends end start
[bx+idata] 前面我们用[bx]
的方式来指明内存单元,还可以用[bx+idata]
表示一个内存单元,其中idata
为常数。例如,mov ax, [bx+200]
代表(ax)=((ds)*16+(bx)+200)
。
Q:
A:
AX=00BEH,BX=1000H,CX=0606H
下面使用[bx+idata]的方式改变大写转小写的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 assume cs:code, ds:data data segment db 'Basic' db 'MinIx' data ends code segment start: mov ax, data mov ds, ax mov bx, 0 mov cx, 5 s: mov al, [bx] and al, 11011111B mov [bx], al mov al, [bx+5] or al, 00100000B mov [bx], al inc bx loop s mov ax, 4c00H int 21H code ends end start
SI与DI SI与DI是与BX功能相近的寄存器,SI与DI不能分成两个8位寄存器来使用。
[bx+si]与[bx+di] 例如,mov ax, [bx+si]
代表(ax)=((ds)*16+(bx)+(si))
,也可以写成mov ax, [bx][si]
总结
Q&A
注:每个字符串16个字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 assume cs:code, ds:data data segment db '1. file ' db '2. edit ' db '3. search ' db '4. view ' db '5. options ' db '6. help ' data ends code segment start: mov ax, data mov ds, ax mov cx, 6 mov bx, 0 s: mov al, ds:[bx+3] or al, 00100000B mov ds:[bx+3], al add bx, 16 loop s code ends end
注:每个字符串16个字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 assume cs:code, ds:data data segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' data ends code segment start: mov ax, data mov ds, ax mov si, 0 mov cx, 4 s0: mov dx, cx ; 外层循环把cx保存起来,保存到dx中 mov bx, 0 mov cx, 3 s: mov al, ds:[bx+si] and al, 11011111B mov ds:[bx+si], al inc bx loop s add si, 16 mov cx, dx ; 恢复cx loop s0 code ends end start
上述程序是用寄存器dx保存cx,为了减少寄存器的占用,下面我们使用栈来进行保存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 assume cs:code, ds:data, ss:stack data segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' data ends stack segment dw 0,0,0,0,0,0,0,0 stack ends code segment start: mov ax, data mov ds, ax mov si, 0 mov cx, 4 mov ax, stack mov ss, ax mov sp, 16 s0: push cx mov bx, 0 mov cx, 3 s: mov al, ds:[bx+si] or al, 00100000B mov ds:[bx+si], al inc bx loop s add si, 16 pop cx loop s0 code ends end start
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 assume cs:code, ds:data, ss:stack data segment db '1. display ' db '2. brows ' db '3. replace ' db '4. modify ' data ends stack segment dw 0,0,0,0,0,0,0,0 ;不是16字节会报错 stack ends code segment start: mov ax, stack mov ss, ax mov sp, 16 mov ax, data mov ds, ax mov cx, 4 mov si, 0 s: push cx mov bx, 0 mov cx, 4 s0: mov al, ds:[3+si+bx] and al, 11011111B mov ds:[3+si+bx], al inc bx loop s0 pop cx add si, 16 loop s mov ax, 4c00H int 21H code ends end start
0x02 数据处理的2个基本问题 寄存器reg:ax,bx,cx,dx,sp,bp,si,di,段寄存器sreg:ds,ss,cs,es。
在8086CPU中,只有bx,bp,si,di可以用在[]中来进行内存单元的寻址。(非常重要!!)
在[]中,bx,bp,si,di要么单个出现,要么以bx+si,bx+di,bp+si,bp+di出现:
只要在[]中使用bp,且指令没有显性给出段地址,则段地址默认为ss,而不是ds!
指令要处理的数据有多长? 1. 通过寄存器名指定,例如`mov al, 1`
1. **使用操作符`X ptr`指明内存单元的长度,X可以为word或byte,例如`mov word ptr ds:[0], 1`,`inc byte ptr [bx]`。但是`push [1000H]`只进行字操作!!!**
div除法指令
举例说明:
div byte ptr ds:[0]
:其中ds:[0]指向的字节为除数,al
保存(ax)/((ds)*16+0)的商
,al
保存(ax)/((ds)*16+0)的余数
div word ptr es:[0]
:其中es:[0]指向的字为除数,ax保存[(dx)*10000H+(ax)]/((es)*16+0)的商
,dx保存[(dx)*10000H+(ax)]/((es)*16+0)的余数
。
利用除法指令计算100001/100 1 2 3 4 5 6 7 8 9 10 11 assume cs:code code segment mov dx, 1H mov ax, 86A1H mov cx, 100 div cx mov ax, 4c00H int 21H code ends end
伪指令dd db是字节数据,dw是字型数据,dd是双字型数据。
Q&A
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 assume cs:code, ds:data data segment dd 100001 dw 100 dw 0 data ends code segment start: mov ax, data mov ds, ax mov dx, ds:[0] mov ax, ds:[2] div word ptr ds:[4] mov ds:[6], ax mov ax, 4c00H int 21H code ends end start
dup指令 编译器识别的指令,与db,dw,dd搭配使用,来进行数据重复。例如:
db 3 dup(0)
相当于db 0,0,0
。
db 3 dup(0,1,2)
相当于db 0,1,2,0,1,2,0,1,2
ndb 3 dup ('abc','ABC')
相当于db 'abcABCabcABCabcABC'
Time: 2023-2-21
0x03 转移指令原理 jmp ax
只改变ip=ax
,而jmp 1000:0
改变cs=1000
且ip=0
。
短转移的ip修改范围为-128~127
,近转移的ip修改范围为-32768~32767
。
offset操作符 编译器处理的符号,功能是取得标号的偏移地址 。如下举例:
Q&A
1 2 mov ax, cs:[si] mov cs:[di], ax
jmp指令
jmp short 标号
,实现段内短转移,IP的修改范围为-128~127
。
jmp跳转原理 考虑如下问题,给出两个程序,分别如下:
两个程序的不同仅仅是第2个程序加入了mov bx, 0
。我们比较两个程序在debug中的汇编与机器码:
可以看到,第一个程序jmp short s
编译成了jmp 0008
,第二个程序编译成了jmp 000b
,但是机器码都是EB03
,这是为啥??
我们首先按照第2个程序分析jmp short s
的执行流程:
cs=0BBD
,ip=0006
,将机器码EB03
读入指令缓冲器。
ip变为0008
。
CPU执行EB03
。
执行后ip变为000B
(因为EB03
中03的缘故,所以000B=0008+3=000B
)。
那么不用多说,问题得到了解答。如下图所示:
段内近转移 jmp near ptr 标号
,实现了段内近位移,范围为-32768~32767
。
段间转移(远转移) jmp far ptr 标号
,cs是标号所在段的段地址,ip为标号在段中的偏移地址。
转移地址在内存中 jmp word ptr 内存单元地址
(段内转移),内存单元开始的一个字,修改ip为这个字。
jmp dword ptr 内存单元地址
(段间转移),内存单元开始的2个字,高地址的字为段地址,低地址的字为偏移地址。
Q&A
1 2 mov [bx], bx mov [bx+2], cs
jcxz指令 条件转移指令,所有的条件转移指令都是短转移,也就是-128~127
。
jcxz 标号
,如果cx=0,则转移到标号处执行。
Q&A
1 2 3 4 mov cx, 0 mov cl, ds:[bx] jcxz ok inc bx
loop指令 loop不详细说,循环指令也是短转移。
Q&A