从入门到精通汇编语言 第三章(汇编语言程序)

参考教程:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili

一、用汇编语言写的源程序

1、用汇编语言编写程序的工作过程

(1)大致流程:程序员编写汇编程序,汇编程序通过编译器转换为计算机能识别的机器码。

(2)汇编程序可认为是包含汇编指令和伪指令的文本。

伪指令没有对应的机器码的指令,最终不被CPU所执行,它是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作

汇编指令有机器码对应的指令,可以被编译为机器指令,最终被CPU执行

汇编程序最后往往有两条固定语句,它们的作用是程序返回,也就是在程序结束运行后,将CPU的控制权交还给使它得以运行的程序

2、程序中的三种伪指令

(1)段定义:

个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用

②一个有意义的汇编程序中至少要有一个段,这个段用来存放代码(也就是代码段)。

每个段都需要有段名定义程序中的段可使用以下伪指令

<段名> segment  ;段的开始

                ;段的内容

<段名> ends   ;段的结束

(2)end(并非ends)是汇编程序的结束标记,若程序结尾处不加end,编译器在编译程序时,无法知道程序在何处结束。

(3)assume的中文释义是假设,含义是假设某一段寄存器和程序中的某一个用“segment ... ends”定义的段相关联,例如“assume cs:codesg”指的是CS寄存器与codesg关联,将定义的codesg当作程序的代码段使用。

3、源程序变为机器码的过程

4、汇编程序的结构

(1)一般来讲,汇编程序都是单独编写成源文件后再编译为可执行文件的程序,这个流程适用于编写大型程序。

①汇编程序需要包括汇编指令,还要有指导编译器工作的伪指令。

②源程序由一些段构成,这些段存放代码、数据,或将某个段当作栈空间。

③程序中可用“;”进行注释的添加,使用方法与C语言中的双斜杠一样。

(2)汇编程序的编写举例:写一个程序求出2的3次方。

①定义一个段(代码段)adc。

②实现处理任务(求解2的3次方)。

③指出程序在何处结束。

④将段abc与段寄存器CS关联。

⑤加上程序返回的代码。

5、程序中可能的错误

(1)语法错误:程序在编译时被编译器发现的错误,容易发现。

(2)逻辑错误:程序在编译时不能表现出来的、在运行时发生的错误(往往体现为得到非预期的结果),较难发现。

二、由源程序到程序运行(实践环节须知)

1、由写出源程序到执行可执行文件的过程

2、编辑源程序

        编辑源程序可使用的工具非常多,比如记事本、Visual C++等等,初学者推荐使用EditPlus进行编辑。

3、编译

(1)首先将编辑好的源程序文件.asm移动至工作目录,然后打开DOSBox,按照惯例完成工作目录挂接,接着使用masm命令,输入“masm <源程序文件名>”(后面加上“;”可简化操作过程)即可编译源程序。

(2)在编译过程中会产生3个过程文件:

①目标文件(.OBJ)是对一个源程序进行编译要得到的最终结果。

②列表文件(.LST)是编译器将源程序编译为目标文件的过程中产生的中间结果。

③交叉引用文件(.CRF)同列表文件一样,是编译器将源程序编译为目标文件过程中产生的中间结果。

(3)对源程序的编译结束后,编译器输出的最后两行会告诉程序员,这个源程序有没有警告错误(非必须改正,视情况而定)以及必须要改正的错误。

4、连接

(1)在源程序编译好后,再使用link命令,输入“link <源程序文件名>”(后面加上“;”可简化操作过程)即可生成可执行文件。

(2)在连接过程中会产生3个过程文件:

①可执行文件(.EXE)是对一个程序进行连接要得到的最终结果。

②映像文件(.MAP)是连接程序将目标文件连接为可执行文件过程中产生的中间结果。

③库文件(.LIB)里包含了一些可以调用的子程序,如果程序中调用了某一个库文件中的子程序,就需要在连接的时候,将这个库文件和目标文件连接到一起,生成可执行文件。

5、执行可执行程序

(1)在完成连接后,直接输入“<可执行程序文件名>”,即可执行可执行程序,不过屏幕上可能并不会有任何信息显示出来。

(2)DOSBox启动后,计算机由“命令解释器”(程序command.com)控制,运行可执行程序时,command将程序加载入内存,设置CPU的CS:IP指向程序的第一条指令(即程序的入口),使程序得以运行,程序运行结束后,返回到“命令解释器”,CPU继续运行command。

三、用Debug跟踪程序的执行(实践环节须知)

1、用Debug装载程序

(1)首先将编辑好的源程序文件.asm移动至工作目录,然后打开DOSBox,按照惯例完成工作目录挂接,接着输入“debug <可执行程序文件名>”即可完成装载。

(2)举例:如下所示。

①程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为DS:0,这个内存区的前256个字节存放程序段前缀PSP;DOS用来和程序进行通信,从256字节处向后的空间存放的是程序。

②程序加载后,CS的值为DS+10H,CX中存放代码的长度(字节)。

2、用Debug单步执行程序

(1)在装载程序以后,输入“t”即可执行程序中的1条汇编指令,并获取执行后的寄存器结果。

(2)除了输入“t”以外,输入“p”(继续命令P)也会有相类似的结果,不同的是,P命令遇到子程序、中断等时会直接执行,然后输出结果。

(3)输入“g”(运行命令G),Debug会从指定地址处开始运行程序,直到遇到断点或者程序正常结束(G命令还可以指定执行到的代码地址)。

四、方括号和圆括号

1、方括号

(1)在汇编语言中,“[]”中填写的是内存单元的地址(偏移地址),这个地址可以以立即数的形式给出,也可以将地址值存入寄存器中,以寄存器的形式给出。

(2)举例:

指令

段地址

偏移地址

操作单位

MOV AX, [0]

在DS中

0

MOV AL, [0]

在DS中

0

字节

MOV AX, [BX]

在DS中

在BX中

MOV AL, [BX]

在DS中

在BX中

字节

2、圆括号

(1)在汇编语言中不使用圆括号,它仅仅是方便学习的一种约定,“()”中填写的是内存单元的地址(偏移地址)或者寄存器,整体表示为一个内存单元或寄存器中的内容。

(2)举例:

描述对象

描述方法

AX中的内容为1145H

(ax) = 1145H

2000:1000处的内容为4514H

(21000H) = 4514H

指令MOV AX, [2]的功能

(ax) = ((ds) * 16 + 2)

指令MOV [2], AX的功能

((ds) * 16 + 2) = (ax)

指令PUSH AX的功能

(sp) = (sp) - 2

((ss) * 16 + (sp)) = (ax)

指令POP AX的功能

(ax) = ((ss) * 16 + (sp))

(sp) = (sp) + 2

指令ADD AX, BX的功能

(ax) = (ax) + (bx)

指令ADD AX, 2的功能

(ax) = (ax) + 2

3、符号idata

(1)在汇编语言中不使用idata,它仅仅是方便学习的一种约定,用于表示常量(或者说立即数)。

(2)举例:

①MOV AX, idata:代表MOV AX, 1、MOV AX, 2……

②MOV AX, [idata]:代表MOV AX, [1]、MOV AX, [2]……

五、LOOP指令

1、LOOP指令使用介绍

(1)LOOP指令的功能是实现算法中的循环结构。

(2)LOOP指令的格式:LOOP <标号>

(3)使用LOOP指令时的要求:

CX中要提前存放循环次数,因为(cx)影响着LOOP指令的执行结果。

要定义一个标号,如同C语言中goto语句需要的标号一样。

(4)CPU执行LOOP指令时要进行的操作:

(cx) = (cx) - 1

判断CX中的值,不为零则转至标号处执行程序,否则向下执行

2、LOOP指令使用举例

(1)计算2的12次方,结果存储在AX中。

①算法设计:

②汇编程序设计:

assume cs:code

code segment

        mov ax, 2

        mov cx, 11

    s: add ax, ax  ;add ax, ax为本程序的循环体

        loop s

        mov ax, 4c00h

        int 21h

code ends

end

(2)计算FFFF:0006字节单元中的数乘以3,结果存储在DX中。

①算法设计:

②汇编程序设计:

assume cs:code

code segment

        mov ax, 0ffffh  ;在汇编源程序中数据不能以字母开头,要在ffff前面加0

        mov dx, ax  ;(ds) = (ax) = FFFFH

        mov bx, 6  ;(bx) = 6

        mov al, [bx]  ;(al) = ((ds) * 16 + (bx))

        mov ah, 0   ;(ah) = 0

        mov dx, 0  ;(dx) = 0

        mov cx, 3

    s: add dx, ax  ;add dx, ax为本程序的循环体

        loop s

        mov ax, 4c00h

        int 21h

code ends

end

六、段前缀的使用

1、段前缀的作用

(1)如下程序乍一看可能没什么问题,用Debug执行它可能也能得到预期的结果,但如果将其经过编译、连接得到可执行程序,那么该程序中的一些代码可能会被编译器“过度优化”,这是程序员不希望发生的。

assume cs:code

code segment

        mov ax, 2000h 

        mov dx, ax

        mov al, [0]  ;过度优化为mov al, 0

        mov bl, [1]  ;过度优化为mov bl, 1

        mov cl, [2]  ;过度优化为mov cl, 2

        mov ax, 4c00h

        int 21h

code ends

end

(2)为了解决上述问题,可以在“[idata]”前显式地写上段寄存器,如下所示。

assume cs:code

code segment

        mov ax, 2000h 

        mov dx, ax

        mov al, ds:[0]

        mov bl, ds:[1]

        mov cl, ds:[2]

        mov ax, 4c00h

        int 21h

code ends

end

(3)出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”(数据段)、“ss:”(栈段)或“es:”(附加段),在汇编语言中称为段前缀

2、段前缀的使用举例

(1)目标:将内存FFFF:0000~FFFF:000B中的数据拷贝到0000:0200~0000:020B单元中。

(2)不使用段前缀的方案:

assume cs:code

code segment

        mov bx, 0  ;BX中存放偏移地址

        mov cx, 12  ;统共拷贝12个(字型)数据

    s: mov ax, 0ffffh  ;源数据所在位置段地址送至寄存器AX中

        mov ds, ax  ;将源数据所在位置段地址通过寄存器AX送至寄存器DS中

        mov dl, [bx]  ;(dl) = ((ds) * 16 + bx)

        mov ax,0020h  ;拷贝数据目标位置段地址送至寄存器AX中

        mov ds, ax  ;将拷贝数据目标位置段地址通过寄存器AX送至寄存器DS中

        mov [bx], dl  ;((ds) * 16 + bx) = (dl)

        inc bx   ;(bx) = (bx) + 1

        loop s

        mov ax, 4c00h

        int 21h

code ends

end

(3)使用段前缀及附加段寄存器的方案:

assume cs:code

code segment

        mov ax, 0ffffh  ;源数据所在位置段地址送至寄存器AX中

        mov ds, ax  ;将源数据所在位置段地址通过寄存器AX送至寄存器DS中

        mov ax, 0020h  ;拷贝数据目标位置段地址送至寄存器AX中

        mov es, ax  ;将拷贝数据目标位置段地址通过寄存器AX送至寄存器ES中

        mov bx, 0  ;BX中存放偏移地址

        mov cx, 12  ;统共拷贝12个(字型)数据

    s: mov dl, [bx]  ;(dl) = ((ds) * 16 + bx)

        mov es:[bx], dl ;((es) * 16 + bx) = (dl)

        inc bx   ;(bx) = (bx) + 1

        loop s

        mov ax, 4c00h

        int 21h

code ends

end

七、代码段、数据段、栈段的规划

1、在代码段中使用数据

(1)在大型项目中,在程序中直接写地址是非常危险的,因为内存中存放着丰富的内容,如果程序员通过写地址误修改了内存中一些非常重要的数据,这将引发不可预见的后果,为了解决这个问题,可以在程序的段中存放数据,运行时由操作系统分配空间。

(2)程序的段可分为代码段(必须有)、数据段和栈段,各个段中均可以有数据,但需要注意的是,在代码段中定义数据时要用一个标号声明第一条代码的起始位置,否则编译器默认第一条代码的起始位置就是代码段的开头,然而代码段的开头是数据而非代码(如下图所示),将数据当成代码去执行,同样会引发非预期的结果。

(3)定义数据需使用关键字dw、db或dd,其中dw用于定义字型数据,db用于定义一个字节数据,dd用于定义一个双字节数据

(4)举例:计算8个数据(0123H、0456H、0789H、0abcH、0defH、0fedH、0cbaH、0987H)的和,结果存在AX寄存器中。(第一条代码的起始位置标号为start)

assume cs:code

code segment

        dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H

        start:  mov bx, 0  ;偏移地址存放在BX中(代码段第一条代码)

        mov ax, 0   ;AX中的内容初始化为0

        mov cx, 8   ;统共累加8个双字节数据

    s:  add ax, cs:[bx]  ;(ax) = (ax) + ((cs) * 16 + bx)

        add bx, 2

        loop s

        mov ax, 4c00h

        int 21h

code ends

end start

2、在代码段中使用栈

(1)在代码段中使用栈,首先要为栈预留足够的空间,这可以通过在代码段中定义数据实现(定义一些无用数据,它们可以起到占位的作用),然后明确栈顶指针指向的位置(段地址+偏移地址),将其送入段寄存器SS中,这样就完成了在代码段中定义栈空间的过程。

(2)举例:利用栈结构将上例中的8个数据逆序存放。(第一条代码的起始位置标号为start)

assume cs:code

code segment

        dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H

        dw 0, 0, 0, 0, 0, 0, 0, 0

start:  mov ax, cs

        mov ss, ax  ;代码段的段地址作为栈段的段地址,存放在SS中

        mov sp, 30h  ;栈顶指针的偏移地址存放在SP中

        mov bx, 0  ;偏移地址存放在BX中

        mov cx, 8   ;统共8个数据入栈

    r:  push cs:[bx]  ;(sp) = (sp) - 2    ((ss) * 16 + (sp)) = ((cs) * 16 + (bx))

        add bx, 2

        loop r

        mov bx, 0  ;偏移地址存放在BX中

        mov cx, 8   ;统共8个数据出栈

    c:  pop cs:[bx]  ;((cs) * 16 + (bx)) = ((ss) * 16 + (sp))     (sp) = (sp) + 2

        add bx, 2

        loop c

        mov ax, 4c00h

        int 21h

code ends

end start

3、将数据、代码、栈放入不同段

(1)上例的程序中,数据、栈和代码都在一个段,程序显得混乱,编程和阅读时都要注意何处是数据、何处是栈、何处是代码,为了避免这种麻烦,建议数据、栈和代码各自放在不同的段中,如下图所示。

(2)更改上例程序,在功能不变的前提下将数据、代码、栈放入不同段。

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

stack ends

code segment     ;代码段

start:

        ;初始化各段寄存器(代码段寄存器不需要初始化)

        mov ax, stack

        mov ss, ax  ;栈段的段地址存放在SS中

        mov sp, 20h  ;栈顶指针的偏移地址存放在SP中(这决定了栈的大小)

        mov ax, data

        mov ds, ax  ;数据段的段地址存放在DS中

        ;入栈

        mov bx, 0  ;偏移地址存放在BX中

        mov cx, 8   ;统共8个数据入栈

    r: push [bx]   ;默认为代码段前缀,可不加段前缀

        add bx, 2

        loop r

        ;出栈

        mov bx, 0  ;偏移地址存放在BX中

        mov cx, 8   ;统共8个数据出栈

    c: pop [bx]   ;默认为代码段前缀,可不加段前缀

        add bx, 2

        loop c

        mov ax, 4c00h

        int 21h

code ends

end start

八、处理字符问题

1、字符在汇编程序中的表示

(1)汇编程序中,......的方式指明数据是以字符的形式给出的(一个字符占一个字节的空间),编译器将把它们转化为相对应的ASCII码

(2)以字符的形式定义数据举例:

assume cs:code, ds:data

data segment

        db ‘unIX

        db ‘foRK

data ends

code segment

start:  mov al, ‘a’  ;字符’a’对应的ASCII码送入寄存器AL中

        mov bl, ‘b’  ;字符’b’对应的ASCII码送入寄存器BL中

        mov ax, 4c00h

        int 21h

code ends

end start

2、大小写字母问题

(1)大写的26个字母的ASCII码是紧挨着的,小写的26个字母的ASCII码也是紧挨着的,并且一个字母的大写和小写,其对应的ASCII码存在这样的关系——小写字母的ASCII码值比大写字母的ASCII码值大20H(并且它们的二进制形式只有第6位有区别,大写字母为0,小写字母为1,这为大小写字母转换提供了另一种思路)

大写字母

二进制形式

小写字母

二进制形式

A

01000001

a

01100001

B

01000010

b

01100010

C

01000011

c

01100011

D

01000100

d

01100100

(2)大小写字母转换举例:对数据段定义的两个字符串,将第一个字符串中的小写字母转换为大写字母,将第二个字符串中的大写字母转换为小写字母。

assume cs:codesg, ds:datasg

datasg segment

        db ‘BaSiC

        db ‘iNfOrMaTiOn

datasg ends

codesg segment

start:  mov ax, datasg

        mov ds, ax   ;初始化段寄存器DS,它要存放数据段的段地址

        ;将第一个字符串‘BaSiC’中的小写字母转换为大写字母

        mov bx, 0   ;存放数据段偏移地址

        mov cx, 5

 s1:  mov al, [bx]   ;将字符串中的字符依次送入al,每轮循环送入一个

        and al 11011111b  ;将al中的第6位二进制数置0

        mov [bx], al   ;将大写转换后的字符送回它原来的位置

        inc bx    ;(bx) = (bx) + 1

        loop s1

        ;将第二个字符串‘iNfOrMaTiOn’中的大写字母转换为小写字母

        mov bx, 5   ;存放数据段偏移地址

        mov cx, 11

 s2:  mov al, [bx]   ;将字符串中的字符依次送入al,每轮循环送入一个

        or al 00100000b  ;将al中的第6位二进制数置1

        mov [bx], al   ;将小写转换后的字符送回它原来的位置

        inc bx    ;(bx) = (bx) + 1

        loop s2

        mov ax, 4c00h

        int 21h

codesg ends

end start

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值