从入门到精通汇编语言 第五章(流程转移与子程序)

startup_gd32f10x_md.s中汇编语法 下面把这个启动文件里常见的汇编符号,按“它像 C 里什么”来归类成一个速查表。这样你以后读启动文件就不会只看一大堆符号名了。相当于:uint32_t __Vectors[] = { ... };它不是“执行指令”,而是“存一份地址值”。while(1) { }或者一个死循环。全局函数或全局变量,允许别的文件引用。在数组中放一个 32 位常量值。全局符号定义 / 对外暴露。它是启动代码里最重要的函数。HardFault入口。不让异常时跑到随机地址。宏定义 / 常量定义。“定义一个段/区块” 阅读详情

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

一、“转移”概述

1、转移的概念

(1)般情况下指令是顺序地逐条执行的,而在实际中,常需要改变程序的执行流程,这就需要使用到转移指令。

(2)转移指令可以控制CPU执行内存中某处代码的指令,它通过修改IP寄存器,或同时修改CS寄存器和IP寄存器实现。

2、转移的分类

(1)按转移行为分类:

①段内转移:只修改IP寄存器,如“JMP AX”。

②段间转移:同时修改CS寄存器和IP寄存器,如“JMP 1000:0”。

(2)根据指令对IP寄存器修改的范围(即在原内容基础上的修改幅度)不同分类(非全):

①段内短转移:IP修改范围为-128~127。

②段内近转移:IP修改范围为-32768~32767。

(3)按转移指令分类:

①无条件转移指令。

②条件转移指令。

③循环指令。

④中断。

⑤过程调用。

3、取得标号的偏移地址及段地址

(1)使用操作符offset可取得标号对应内存在其段中的偏移地址,使用操作符seg可取得标号对应内存在其段的段地址。

(2)举例:程序在运行中将s处的一条指令复制到s0处。

assume cs:codesg

codesg segment

        s:     mov ax, bx

                mov si, offset s  ;将标号s对应内存在其段中的偏移地址存入SI

                mov di, offset s0  ;将标号s0对应内存在其段中的偏移地址存入DI

                mov ax, cs:[si]   ;将s处的指令(占两个字节)内容送入AX

                mov cs:[di], ax  ;将AX中的内容送入s0对应的内存

        s0:   nop     ;空语句,占一个字节

                nop     ;空语句,占一个字节

codesg ends

end start

二、JMP指令

1、概述

(1)JMP指令可实现无条件转移,可以只修改IP寄存器,也可以同时修改CS寄存器和IP寄存器。

(2)JMP指令要给出两种信息的其中一个:

①转移的距离(转移有向前和向后之分,转移距离分别对应正数和负数,用补码表示)。

②转移的目的地址。

2、依据位移进行转移

(1)依据位移进行转移可分为段内短转移和段内近转移。

①段内短转移:IP修改范围为-128~127,指令格式为“JMP short <标号>”。

②段内近转移:IP修改范围为-32768~32767,指令格式为“JMP near ptr <标号>”。

(2)jmp short的机器指令中,包含的是跳转到指令的相对位置,而不是转移的目标地址(简而言之,就是转移距离,范围为-128~127,超出范围会报错),如下所示。

assume cs:codesg

codesg segment

        start:  mov ax, 0

                  jmp short s   ;程序员写入标号即可,8位转移距离由编译器计算

                  add ax, 1

        s:       nop     ;空语句,占一个字节

codesg ends

end start

①当程序运行至“jmp short s”指令时,(IP) = 0003H,CS:IP指向EB 05(“jmp short s”指令的机器码)。

②CPU读取指令码EB 05进入指令缓冲器。

③(IP) = (IP) + 2 = 0005H(2为指令码EB 05的长度),此时CS:IP指向指令“add ax, 1”。

④CPU执行指令缓冲器中的指令EB 05。

⑤指令EB 05执行后,(IP) = (IP) + 05 = 000AH,CS:IP指向“nop”。

(3)jmp near ptr的机器指令中,包含的也是跳转到指令的相对位置,而不是转移的目标地址(简而言之,就是转移距离,范围为-32768~32767,超出范围会报错),如下所示。

assume cs:codesg

codesg segment

        start:  mov ax, 0

                  jmp near ptr s   ;程序员写入标号即可,16位转移距离由编译器计算

                  db 128 dup (0)

        s:       nop     ;空语句,占一个字节

codesg ends

end start

①当程序运行至“jmp near ptr s”指令时,(IP) = 0003H,CS:IP指向E9 00 01(“jmp near ptr s”指令的机器码)。

②CPU读取指令码E9 00 01进入指令缓冲器。

③(IP) = (IP) + 3 = 0006H(3为指令码E9 00 01的长度),此时CS:IP指向128个字节数据的第一个字节。

④CPU执行指令缓冲器中的指令E9 00 01。

⑤指令E9 00 01执行后,(IP) = (IP) + 100H = 0106H,CS:IP指向“nop”。

3、依据目的地址进行转移

(1)远转移的指令格式为“JMP far ptr <标号>”,far ptr指明了跳转到的目的地址,即包含了标号的段地址CS和偏移地址IP。

(2)举例:

assume cs:codesg

codesg segment

        start:  mov ax, 0

                  mov bx, 0

                  jmp far ptr s   ;程序员写入标号即可,目的地址由编译器计算

                  db 128 dup (0)

        s:       nop     ;空语句,占一个字节

codesg ends

end start

①当程序运行至“jmp far ptr s”指令时,(IP) = 0006H,CS:IP指向EA 0B 01 6A 07(“jmp far ptr s”指令的机器码)。

②CPU读取指令码EA 0B 01 6A 07进入指令缓冲器。

③(IP) = (IP) + 5 = 000BH(5为指令码E9 00 01的长度),此时CS:IP指向128个字节数据的第一个字节。

④CPU执行指令缓冲器中的指令EA 0B 01 6A 07。

⑤指令EA 0B 01 6A 07执行后,(IP) = 010B,(CS) = 076A。

4、转移地址在寄存器或内存中的JMP指令

(1)转移地址在寄存器中的JMP指令:

①格式:JMP <16位寄存器>。(如“jmp ax”)

②作用:寄存器中存放着一个字,它是转移的目的偏移地址。

(2)转移地址在内存中的JMP指令:

①段内转移:

[1]格式:JMP word ptr <内存单元地址>或JMP word ptr <段寄存器>:<内存单元地址>。

[2]作用:从内存单元地址处开始存放着一个字,它是转移的目的偏移地址。

[3]举例:

​

②段间转移:

[1]格式:JMP dword ptr <内存单元地址>或JMP dword ptr <段寄存器>:<内存单元地址>。

[2]作用:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。

[3]举例:

​

三、一些其它的转移指令

1、JCXZ指令

(1)指令格式:JCXZ <标号>。

(2)指令功能:如果(cx)=0,执行此条指令后将程序转移至标号处,否则执行此条指令后程序按顺序继续执行指令。

(3)此条指令改变的是IP寄存器中的值,修改范围为-128~127,底层原理同“JMP short <标号>”,此处不再赘述。

2、LOOP指令

(1)指令格式:LOOP <标号>。

(2)指令功能:如果(cx)=0,执行此条指令后程序按顺序继续执行指令,否则执行此条指令后将程序转移至标号处。

(3)此条指令改变的是IP寄存器中的值,修改范围为-128~127,底层原理同“JMP short <标号>”,此处不再赘述。

四、CALL指令和RET指令

1、CALL指令

(1)根据字面意思,CALL指令用于调用子程序,其本质是流程转移,底层原理和JMP指令类似。

(2)指令格式:CALL <标号> 或CALL far ptr <标号> 或CALL <寄存器>。

①CPU执行“CALL <标号> ”指令时,会进行两步操作:

[1]将当前的IP压入栈中,即(sp) = (sp) - 2、((ss) * 16 + (sp)) = (IP)。

[2]转移到标号处执行指令,即(IP) = (IP) + 16位位移(16位位移 = <标号>处的地址 - CALL指令后的第一个字节的地址,范围为-32768~32767,用补码表示,它由编译程序在编译时算出)。

②CPU执行“CALL far ptr <标号> ”指令时,会进行两步操作:

[1]将当前的CS和IP依次压入栈中,即(sp) = (sp) - 2、((ss) * 16 + (sp)) = (CS)、(sp) = (sp) - 2、((ss) * 16 + (sp)) = (IP)。

[2]转移到标号处执行指令,(CS) = 标号所在的段地址、(IP) = 标号所在的偏移地址。

③CPU执行“CALL <16位寄存器> ”指令时,会进行两步操作:

[1]将当前的IP压入栈中,即(sp) = (sp) - 2、((ss) * 16 + (sp)) = (IP)。

[2]将IP寄存器中的值置为<16位寄存器>中的值。

(3)除了上面三种指令格式以外,转移地址还可通过访问特定内存地址给出。

①CALL word ptr <内存单元地址>:<内存单元地址>指向一个字数据,该指令会将IP寄存器中的值改为<内存单元地址>中存放的一个字数据。

​

②CALL dword ptr <内存单元地址>:<内存单元地址>指向两个字数据,该指令会将IP寄存器中的值改为<内存单元地址>指向内存中低地址存放的字数据,将CS寄存器中的值改为<内存单元地址>指向内存中高地址存放的字数据。

​

2、RET指令和RETF指令

(1)RET指令可以没有操作数,它相当于指令“POP IP”,用栈中的数据修改IP的内容,从而实现近转移;RETF指令同样也可以没有操作数,它相当于指令“POP IP”与“POP CS”,用栈中的数据修改IP和CS的内容,从而实现远转移。

​

(2)RET指令和RETF指令通常配合CALL指令使用,从而实现子程序的调用,后续主要使用RET指令进行介绍。

五、MUL指令

1、MUL指令的功能和用法

(1)MUL是乘法指令,使用MUL做乘法的时候,被乘数默认放在AL或AX中,乘数放在其它寄存器或内存单元中,积存放在AX或AX、DX中,具体如何放置见下表:

被乘数的长度为8位

被乘数的长度为16位

被乘数存放的位置

AL

AX

乘数存放的位置

8位长度的内存单元或寄存器

16位长度的内存单元或寄存器

积存放的位置

AX

DX(积高16位)和AX(积低16位)

(2)MUL指令格式:MUL <寄存器或内存单元地址>。

(3)使用MUL指令时,切记提前在默认的寄存器中设置好被乘数,且默认寄存器不作别的用处。

2、MUL指令使用举例

(1)计算10×100:

assume cs:code

code segment

        main: mov al, 100   ;被乘数(8位)

                  mov bl, 10   ;乘数(8位)

                  mul bl

                  mov ax, 4c00h

                  int 21h

code ends

end main

(2)计算100×10000:

assume cs:code

code segment

        main: mov ax, 100   ;被乘数(16位)

                  mov bx, 1000   ;乘数(16位)

                  mul bl

                  mov ax, 4c00h

                  int 21h

code ends

end main

六、汇编语言的模块化程序设计

1、CALL指令和RET指令的配合使用

(1)CALL指令和RET指令配合使用,可以实现具有子程序的源程序,其中子程序可根据提供的参数处理一定的事务,处理后将结果(返回值)提供给调用者。

(2)具有子程序的源程序的框架:

assume cs:code, ss:stack

stack segment

                  db 16 dup (0)   ;为call和ret指令设置栈

stack ends

code segment

        main: ...

                  call sub1    ;调用子程序sub1

                  ...

                  mov ax, 4c00h

                  int 21h

        sub1: ...     ;子程序sub1开始

                  call sub2    ;调用子程序sub2

                  ...

                  ret     ;子程序返回

        sub2: ...     ;子程序sub2开始

                  ...

                  ret     ;子程序返回

code ends

end main

(3)举例:将AX中的值乘以2(AX的初始值为1000D),结果存放在AX寄存器中。

assume cs:code, ss:stack

stack segment

                  db 16 dup (0)   ;为call和ret指令设置栈

stack ends

code segment

        main: mov ax, stack

                  mov ss, ax   ;栈段地址初始化

                  mov sp, 16   ;栈顶指针初始化

                  mov ax, 1000   ;AX初始化

                  call s    ;调用子程序

                  mov ax, 4c00h

                  int 21h

        s:       add ax, ax   ;AX中的值乘以2,结果存放在AX中

                  ret     ;子程序返回

code ends

end main

2、参数和结果传递的问题

(1)例1:根据提供的N,计算N的3次方,要求将计算过程封装为一个子程序(或者说子函数)。

①解决方案——用寄存器与子函数完成参数和结果的传递:

[1]函数参数放到BX中,即(bx) = N。

[2]子程序中用多个MUL指令计算N^3。

[3]将结果放到DX和AX中。

②汇编程序:

assume cs:code, ds:data

data segment

                  dw 1,2,3,4,5,6,7,8  ;提供的N

                  dd 0,0,0,0,0,0,0,0  ;N的3次方计算结果保存在此处

data ends

code segment

        main: mov ax, data

                  mov ds, ax   ;数据段地址初始化

                  mov si, 0    ;DS:SI指向数据段第一个字

                  mov di, 16   ;DS:DI指向数据段保存计算结果区域的第一个双字

                  mov cx, 8

        s:       mov bx, [si]   ;将N通过BX传递给子程序

                  call cube    ;调用子程序进行计算

                  mov [di], ax   ;积的低16位存到计算结果区域的低地址字

                  mov [di].2, dx   ;积的高16位存到计算结果区域的高地址字

                  add si, 2    ;DS:SI指向数据段下一个字

                  add di, 4    ;DS:DI指向数据段保存计算结果区域的下一个双字

                  loop s

                  mov ax, 4c00h

                  int 21h

        cube: mov ax, bx

                  mul bx

                  mul bx

                  ret     ;子程序返回

code ends

end main

③缺陷:计算结果可能大于32位,甚至更大,如果全部借助寄存器进行传递,显然是不现实的。

(2)例2:将data段中的字符串转化为大写。

①解决方案——用内存单元与子函数完成参数和结果的传递:

[1]将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。

[2]对于具有批量数据的返回结果,也可用同样的方法。

②汇编程序:

assume cs:code, ds:data

data segment

                  db ‘conversation’

data ends

code segment

        main: mov ax, data

                  mov ds, ax    ;数据段地址初始化

                  mov si, 0     ;DS:SI指向数据段第一个字

                  mov cx, 12    ;总共需要转换12个字母

                  call capital    ;调用子程序进行大写字母转换

                  mov ax, 4c00h

                  int 21h

        capital: and byte ptr [si], 11011111b ;将DS:SI指向的字母转换为大写

                  inc si     ;(si) = (si) + 1

                  loop capital

                  ret      ;子程序返回

code ends

end main

(2)例3:计算(a-b)的3次方,假设a = 3、b = 1。

①解决方案——用栈与子函数完成参数的传递:由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。

②知识补充:“RET idata”指令的含义为“pop ip”、“add sp, n”,栈顶指针sp下移后,虽然栈中的数据没有被删除,但它们在逻辑上相当于被删除了,不会影响栈的正常运作;“RETF idata”同理,这里不再赘述。

③汇编程序:

assume cs:code

code segment

        main: mov ax, 1

                  push ax     ;参数b压入栈中

                  mov ax, 3

                  push ax     ;参数a压入栈中

                  call difcube    ;调用子程序进行算式计算

                  mov ax, 4c00h

                  int 21h

        difcube: push bp  ;要借用BP寄存器,首先需要保护其原本的内容,将其入栈

                  mov bp, sp ;获取栈顶指针,存在BP寄存器中

                  mov ax, [bp+4] ;获取参数a,将其存入AX中

                  sub ax, [bp+6]  ;获取参数b,计算a-b,将其存入AX中

                  mov bp, ax

                  mul bp

                  mul bp

                  pop bp  ;BP寄存器借用完成,将其原本的内容从栈顶还原回去

                  ret 4   ;子程序返回,并逻辑删除形式参数a和b

code ends

end main

④程序的执行过程中栈的变化:

​

七、寄存器冲突问题

1、可能引起冲突的情况

(1)在编写子程序时,子程序使用了主程序正在使用的寄存器,且寄存器中有主程序后续需要使用到的数据,这种情况下子程序很可能会影响主程序的正常运作。

(2)举例:将data段中的字符串转化为大写。

assume cs:code, ds:data

data segment

                  db ‘word’, 0    ;’\0’是字符串的结束标志

                  db ‘byte’, 0    ;’\0’是字符串的结束标志

                  db ‘uint’, 0    ;’\0’是字符串的结束标志

                  db ‘char’, 0    ;’\0’是字符串的结束标志

data ends

code segment

        main: mov ax, data

                  mov ds, ax    ;数据段地址初始化

                  mov bx, 0

                  mov cx, 4     ;需要转换4个单词

        s:       mov si, bx    ;DS:SI指向字符串首地址

                  call capital    ;调用子程序对一个单词进行大写字母转换

                  add bx, 5     ;使DS:SI指向下一个字符串首地址

                  loop s

                  mov ax, 4c00h

                  int 21h

        capital: mov cl, [si]    ;主程序中的循环结构正在用CX寄存器

                  mov ch, 0     ;子程序直接占用CX寄存器显然是有问题的

                  jcxz ok     ;判断DS:SI指向的是否是’\0’,是则结束

                  and byte ptr [si], 11011111b ;将DS:SI指向的字母转换为大写

                  inc si     ;DS:SI指向下一个字母

                  jmp short capital

        ok:     ret      ;子程序返回

code ends

end main

2、冲突问题的解决

(1)冲突问题有两种解决方案,如下所示,最为常用的是第二种,使用第二种方案时,编写调用了程序的程序的时候不必关心子程序到底使用了哪些寄存器,编写子程序的时候也不必关心调用者使用了哪些寄存器。

①在编写调用子程序的程序时,程序员需要注意子程序中有没有用到会产生冲突的寄存器,如果有,使用别的寄存器,不要使用会产生冲突的寄存器。

②在子程序的开始将要用到的所有寄存器中的内容都保存起来,在子程序返回前再恢复。

(2)子程序标准框架:

子程序开始: 子程序中使用的寄存器入栈

                    子程序内容

                    子程序使用的寄存器出栈

                    返回(ret、retf)

(3)寄存器冲突问题的解决示例:将data段中的字符串转化为大写。

assume cs:code, ds:data

data segment

                  db ‘word’, 0    ;’\0’是字符串的结束标志

                  db ‘byte’, 0    ;’\0’是字符串的结束标志

                  db ‘uint’, 0    ;’\0’是字符串的结束标志

                  db ‘char’, 0    ;’\0’是字符串的结束标志

data ends

code segment

        main: mov ax, data

mov ds, ax    ;数据段地址初始化

mov bx, 0

mov cx, 4     ;需要转换4个单词

s:  mov si, bx    ;DS:SI指向字符串首地址

call capital    ;调用子程序对一个单词进行大写字母转换

add bx, 5     ;使DS:SI指向下一个字符串首地址

loop s

mov ax, 4c00h

int 21h

capital: push cx

push si

change: mov cl, [si]    ;将DS:SI指向的字符送入CL

mov ch, 0     ;CH置0

jcxz ok     ;判断DS:SI指向的是否是’\0’,是则结束

and byte ptr [si], 11011111b ;将DS:SI指向的字母转换为大写

inc si     ;DS:SI指向下一个字母

jmp short capital

ok:  pop si

pop cx

ret      ;子程序返回

code ends

end main

八、标志寄存器

1、标志寄存器介绍

(1)标志寄存器的简写为PSW或FLAGS,又称程序状态字。

(2)标志寄存器的结构:

①FLAGS寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。

②8086CPU中没有使用FLAGS的1、3、5、12、13、14、15位,这些位不具有任何含义。

(3)标志寄存器的作用:

①用来存储相关指令的某些执行结果。

②用来为CPU执行相关指令提供行为依据。

③用来控制CPU的相关工作方式。

(4)直接访问标志寄存器的方法:

①pushf指令:将标志寄存器的值压栈。

②popf指令:从栈中弹出数据,送入标志寄存器中。

(5)在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如add、sub、mul、div、inc、or、and等,它们大都是运算指令,进行逻辑或算术运算;有的指令的执行对标志寄存器没有影响,比如mov、push、pop等,它们大都是传送指令。

(6)使用一条指令的时候,要注意这条指令的全部功能,其中包括执行结果对标记寄存器的哪些标志位造成影响。

2、ZF-零标志

(1)ZF标记相关指令的计算结果是否为0。

①ZF=1,表示“结果是0”,1表示“逻辑真”

②ZF=0,表示“结果不是0”,0表示“逻辑假”。

(2)举例:

;and指令一共两个操作数,它对两个操作数执行按位与操作,结果存在操作数1中

mov ax, 1

and ax, 0   ;ZF = 1,表示“结果是0”

;or指令一共两个操作数,它对两个操作数执行按位或操作,结果存在操作数1中

mov ax, 1

or ax, 0   ;ZF = 0,表示“结果不是0”

3、PF-奇偶标志

(1)一条指令执行后,结果的所有二进制位中,若1的个数为偶数,PF = 1,否则若1的个数为奇数,PF = 0。

(2)举例:

mov al, 1

add al, 10   ;计算结果为0000 1011B,其中有3个1,PF = 0

mov al, 1

or al, 2   ;计算结果为0000 0011B,其中有2个1,PF = 1

4、SF-符号标志

(1)一条指令执行后,将结果视为有符号数,若结果为负,SF = 1,否则若结果为非负,SF = 0。

(2)举例:

mov al, 10000001b

add al, 1   ;计算结果为1000 0010B,为负数,SF = 1

sub ax, ax  ;计算结果为0000 0000 0000 0000B,为正数,SF = 0

(3)SF标志是CPU对有符号数运算结果的一种记录,将数据当作有符号数来运算的时候,通过SF可知结果的正负,将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。

5、CF-进位标志

(1)对于位数为N的无符号数来说,其对应的二进制信息的最高位即第N-1位,是最高有效位,假想存在的第N位,就是相对最高有效位的更高位。

(2)在进行无符号数运算的时候,CF记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。一条指令执行后,若过程中有进位或借位,CF=1,否则若过程中无进位或借位,CF=0。

(3)举例:

mov al, 98h

add al, al   ;计算过程产生进位,(al) = 30H,CF = 1

add al, al   ;计算过程未产生进位或借位,(al) = 60H,CF = 0

sub al, 98h  ;计算过程产生借位,(al) = C8H,CF = 1

6、OF-溢出标志

(1)在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。有符号数操作指令执行后,若有溢出,OF = 1,否则若无溢出,OF = 0。

(2)举例:

mov al, 98

add al, 99   ;98 + 99 = 197,超出了8位有符号数的表示范围,OF = 1

mov al, 0f0h

add al, 88h  ;(-16) + (-120) = -136,超出了8位有符号数的表示范围,OF = 1

九、带进(借)位的加(减)法

1、ADC指令

(1)ADC是带进位加法指令,它利用了CF位上记录的进位值,其功能与ADD指令类似,不同的是,它在将两个操作数相加的同时还会加上CF标志的值。

(2)指令格式:ADC <操作数1>, <操作数2>。

(3)举例:

①目的:编写一个子程序,对两个128位数据(低地址存放数的低位)进行相加,运算结果存储在第一个数的存储空间中。

②汇编程序:

assume cs:code, ds:data

data segment

                  dw 0A452H,0A8F5H,78E6H,0A8EH,8B7AH,54F6H,0F04H,671EH

                  dw 0E71EH,0EF04H,54F6H,8B7AH,0A8EH,78E6H,58F5H,0452H

data ends

code segment

        main: mov ax, data

                  mov ds, ax    ;数据段地址初始化

                  mov si, 0     ;DS:SI指向第一个操作数首地址

                  mov di, 16    ;DS:DI指向第二个操作数首地址

                  mov cx, 8     ;需要进行8次加法

                  call add128    ;调用子程序进行加法

                  mov ax, 4c00h

                  int 21h

        add128: push ax

                  push cx

                  push si

                  push di

                  sub ax, ax    ;进位标志清零

        s:       mov ax, [si]

                  adc ax, [di]    ;带进位加法

                  mov [si], ax    ;将结果保存在第一个数的存储空间中

                  inc si     ;DS:SI指向操作数高位

                  inc si     ;此处不可用add指令,会影响CF

                  inc di     ;DS:SI指向操作数高位

                  inc di     ;此处不可用add指令,会影响CF

                  loop s

                  pop di

                  pop si

                  pop cx

                  pop ax

                  ret      ;子程序返回

code ends

end main

2、SBB指令

(1)SBB是带借位减法指令,它利用了CF位上记录的减位值,其功能与SUB指令类似,不同的是,它在将两个操作数相减的同时还会减去CF标志的值。

(2)指令格式:SBB <操作数1>, <操作数2>。

(3)举例:

①目的:计算003E1000H-00202000H,结果放在AX和BX中。

②汇编程序:

assume cs:code

code segment

        main: mov bx, 1000h   ;被减数低16位存入BX

                  mov ax, 003eh    ;被减数高16位存入AX

                  sub bx, 2000h    ;低16位先做减法

                  sbb ax, 0020h    ;高16位再做减法,并带上借位

                  mov ax, 4c00h

                  int 21h

code ends

end main

十、CMP指令与条件转移指令

1、CMP指令

(1)指令格式:CMP <操作数1>, <操作数2>。

(2)CMP指令会计算操作数1减去操作数2的结果,但它并不保存结果,也不改变两个操作数,仅仅影响标志寄存器,其它相关指令(通常是条件转移指令)通过识别这些被影响的标志寄存器位可以得知两个操作数的比较结果。

(3)无符号数比较与标志位取值(以指令“CMP AX, BX”为例):

(4)有符号数比较与标志位取值(以指令“CMP AX, BX”为例):

2、条件转移指令

(1)几个英文字母的含义:

(2)根据单个标志位转移的指令:

(3)根据无符号数比较结果进行转移的指令:

(4)根据有符号数比较结果进行转移的指令:

3、CMP指令与条件转移指令的配合使用举例

(1)目标:子程序If_else实现逻辑“如果(ah)=(bh),则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)”。

(2)子程序实现:

If_else: cmp ah, bh ;进行两数比较

                je s   ;如果两数相等,跳转至标号s,否则顺序执行

                add ah, bh ;不满足条件执行的语句

                jmp short ok ;跳转至后面,避免同时执行了两个分支的操作

         s:    add ah, ah ;满足条件执行的语句

         ok:  ret

十一、串传送

1、DF-方向标志

(1)该标志位用来控制CPU的相关工作方式,具体表现如下:

①DF = 0:每次操作后SI寄存器、DI寄存器后,SI寄存器、DI寄存器的值自增。

②DF = 1:每次操作后SI寄存器、DI寄存器后,SI寄存器、DI寄存器的值自减。

(2)对DF位进行设置的指令:

①CLD指令:将标志寄存器的DF位设为0(clear)。

②STD指令:将标志寄存器的DF位设为1(setup)。

2、串传送指令

(1)在前面举过将字符串复制到另一存储空间中的例子,当时的解决方法是程序员人为地通过寄存器将字符一位一位地进行传送,实际上要想实现这种串传送的功能,还可以借助串传送指令。

(2)执行一次以字节为单位传送的串传送指令——MOVSB会发生的事情:

①((es) * 16 + (di)) = ((ds) * 16 + (si))。

②若DF = 0,则(si) = (si) + 1、(di) = (di) + 1;若DF = 1,则(si) = (si) - 1、(di) = (di) - 1。

(3)执行一次以字为单位传送的串传送指令——MOVSW会发生的事情:

①((es) * 16 + (di)) = ((ds) * 16 + (si))。

②若DF = 0,则(si) = (si) + 2、(di) = (di) + 2;若DF = 1,则(si) = (si) - 2、(di) = (di) - 2。

3、REP指令

(1)REP指令常与串传送指令搭配使用。

(2)REP指令无操作数,它会根据CX中的值,重复执行它后面的指令(也能认为这个指令是REP指令的操作数)。

4、串传送应用举例

(1)目的:用串传送指令,将F000H段中的最后16个字符复制到data段中。

(2)汇编程序:

assume cs:code, ds:data

data segment

                  db 16 dup (0)

data ends

code segment

        main: mov ax, 0f000h

                  mov ds, ax

                  mov si, 0ffffh    ;F000:FFFFH是F000H段的最后一字节地址

                  mov ax, data    ;获取数据段地址

                  mov es, ax    ;数据段地址送入ES中

                  mov di, 15    ;ES:DI指向data段最后一字节

                  mov cx, 16    ;需要进行16次串传送

                  std      ;将DF位设为1,操作(si)和(di)时它们会递减

                  rep movsb    ;进行16次串传送

                  mov ax, 4c00h

                  int 21h

code ends

end main

汇编 I/O 与中断:端口读写、中断向量表与 ISR 本文详解x86架构下I/O端口与中断机制,涵盖端口映射、IN/OUT指令、中断向量表(IVT)结构及中断处理流程。通过实模式下的键盘、定时器等硬件示例,揭示操作系统如何通过中断实现硬件管理,并演示自定义中断服务程序的安装与正确调用方式,强调保护现场、读取扫描码、避免重入等关键细节,为理解操作系统底层交互提供坚实基础。 阅读详情

相关推荐

C 与汇编混合编程:符号链接、调用约定与参数传递

本文详解汇编与C语言混合编程的实现原理,涵盖PUBLIC与EXTERN指令的使用、32位调用约定(参数从右往左压栈、调用者清栈)、栈帧布局及寄存器保护规则。通过实例展示C调用汇编函数与汇编调用C库(如printf)的写法,并对比32位与64位(Windows x64)在参数传递、返回值和栈对齐上的差异。强调边界清晰的重要性,确保跨模块调用稳定可靠,适用于性能优化、硬件操作及学习底层机制场景。

软件工程师文艺 315

CTF从入门到提高 ——汇编语言学习,从零基础到精通,收藏这篇就够了!

CTF从入门到提高 ——汇编语言学习,从零基础到精通,收藏这篇就够了!

Cairo_A的博客 951

王爽《汇编语言》实战指南:8086实模式与配套资源全解析

计算机程序最终都要转化为机器指令才能被CPU执行,而汇编语言正是理解这一底层过程的最佳入口。以Intel 8086的16位实模式为基础,寄存器、内存分段、栈操作与中断机制共同构成了指令执行的核心原理。掌握这些底层概念,不仅能解释CS:IP如何取指、SP如何平衡栈帧,更能为后续学习32位保护模式或ARM体系铺平道路。在工程实践中,使用DOSBox搭建汇编环境、借助DEBUG单步跟踪指令流,是验证理论、排查错误的有效方式。本文结合王爽《汇编语言》的配套PPT、源代码与SWF演示资源,系统梳理了从环境搭建到程序调

weixin_29219539的博客 155

爆破经验谈

其次是VC,BC,ASM,比较难的是VB,要有一定的经验才可搞定一两个。哈哈,有兴趣了吧,有信心了吧,好了好了朋友赶紧学汇编吧。如果一个软件采用明码比较,不管它有没有加壳,都可用OllyDbg之类的动态调试工具在很短的时间内获得,并做成内存注册机,速度之快有时令人惊讶,我曾五分钟之内找到注册码。还有,Win32DASM里的提示也不是完全对,它的解释很清楚,“Possible",“可能是”。我在爆破亿虎软件时,我上面的方法都用了,看上都象正式的了,什么注册提示都没了,但用起来还是有30的限制。

biyusr的专栏 440

汇编语言习题答案深度解析:从寄存器寻址到标志位实战

从最基础的指令执行逻辑出发,理解寄存器与内存地址的关系是掌握汇编程序设计的核心。七种寻址方式看似繁琐,实则统一于“数据在哪、如何获取”这一本质问题。而标志位的隐式变化,往往决定条件转移与程序流程的正确性。掌握这些底层原理,不仅能应对新版钱晓捷教材中的典型习题,更能为分析反汇编代码、调试C/C++程序、学习ARM汇编打下坚实基础。文章通过一道键盘输入转十六进制输出的综合题,完整演示了从ASCII转换到数值合并再到显示输出的汇编实现路径,并剖析了段寄存器初始化、LOOP边界、DOS功能调用等常见陷阱,帮助读者从

weixin_34214500的博客 388

郭天祥单片机完整教程与实战项目合集

51单片机的内部外设(如定时器、串口、中断控制器)均由一组特殊功能寄存器(SFR)控制。这些寄存器位于地址空间0x80~0xFF,映射到物理模块的配置与状态字段。C51通过sfr关键字实现对SFR的直接访问。// 自定义SFR(通常已在reg52.h中定义)// 将P1端口地址定义为0x90// 定时器模式寄存器// 定时器0低字节// 定时器0高字节// 设置定时器0为模式1(16位定时)TL0 = 0xFC;// 初值低位TH0 = 0x18;

weixin_28988985的博客 551

C++内联函数效率

关键字inline是C++相对于C语言的又一个扩充,在函数的声明或定义、函数的返回类型前加上关键字inline,即可把函数指定为内联函数从而提升程序运行的效率。但使用inline是要付出代价的,正如茨威格在《断头王后》中那样写道:“ 那时候她还太年轻,不知道所有命运馈赠的礼物,早已在暗中标好了价格。” 那么inline的优势和它为此要付出的代价是什么呢?让我们来慢慢揭晓!定义和声明分离的后果是什么?f(10);return 0;运行结果:为什么普通函数可以将定义和声明分离,而内联函数不行呢?

Code Writers 432

【OpenCV】复杂场景下的高精度圆检测实战(附完整代码)

本文针对OpenCV霍夫圆检测在复杂场景下易失效的问题,提出了一套基于轮廓分析的高精度圆检测实战方案。通过图像预处理、轮廓发现及多轮几何筛选(面积、周长、椭圆拟合、多边形逼近),有效排除噪声和类圆干扰物,显著提升复杂场景下的检测鲁棒性,并附完整可调参的Python代码实现。

weixin_29095587的博客 199

自建简单计算机CPU——软硬兼施

教材的一个独特之处是它的广泛性。无论您对学生构建的SAP(simple as possible)简单微处理器、6502、6800/6808、8080/8085/Z80、8086/8088感兴趣,这本教科书都能满足您的需求。关于微处理器的教科书有时很难理解。《Digital Computer Electronics (1993)》以易于理解和有趣的方式介绍微处理器的各个方面。使用这本教科书的唯一先决条件是了解二极管和晶体管。

zashizhi3299的博客 537

UART全功能测试指南:环路/DMA/RS485/硬件流控实战

UART(通用异步收发器)是嵌入式系统中最基础的串行通信接口,其核心原理基于起始位、数据位、校验位与停止位的异步帧结构,依赖精确的波特率时钟同步与采样重建机制。在工业与物联网场景中,UART不仅需保障基本通信可靠性,更需支持DMA卸载、RS485半双工自动方向控制、RTS/CTS硬件流控等进阶能力,以提升实时性、抗干扰性与系统资源效率。典型应用涵盖调试日志输出、传感器数据采集、Modbus RTU总线通信及固件空中升级。本指南聚焦UART外设测试实践,覆盖环路自检、DMA吞吐验证、RS485时序对齐、硬件流

weixin_42599908的博客 133

AJ-Report开源数据大屏存在远程命令执行漏洞

该平台可以通过post方式在validationRules参数对应值中进行命令执行,可以获得服务器权限,登陆管理后台接管大屏。如果被攻击者利用,危害后果十分严重。

swordheart的博客 387

哈工大计算机系统ICS2024大作业-hello程序的一生

本论文旨在通过运用在计算机系统课程中所学知识,在复习与回顾的基础上,逐步分析hello.c程序在预处理,编译,汇编,链接,加载到执行到回收等过程。通过使用Linux系统下的工具,来分析hello程序的一生,并借此了解c语言程序的执行流程和执行时各硬件的工作情况,对计算机系统编译源文件、运行进程等机制进行较深入的分析和介绍。

m0_74049435的博客 748

如何解决Windows系统中缺失oem*.inf文件导致的软件运行问题

本文详细解析了Windows系统中因oem*.inf文件缺失导致软件无法运行的常见问题。文章阐述了oem*.inf文件作为安装信息文件的重要作用,并提供了从精准定位文件路径、安全获取与替换文件的手动修复步骤,到利用专业工具进行一站式修复的完整解决方案。最后,还给出了从软件安装来源、卸载习惯到系统维护等方面的预防措施,帮助用户彻底避免此类问题。

water的专栏 546

10个完整汇编实例:排序、字符串与递归算法

本文提供10个16位实模式下可运行的汇编程序,涵盖冒泡排序、字符串反转、进制转换、阶乘与斐波那契计算等经典算法,附逐行解析。重点讲解寄存器使用、循环控制、栈操作、除法清零、符号比较、字符处理等核心技巧,揭示如PUSH/POP CX保全计数器、DIV前清DX、ASCII转换、溢出处理等关键细节。通过实践掌握底层逻辑,提升汇编编程能力。

软件工程师文艺 206

使用gsplat进行3D高斯泼溅的方案

gsplat 不是一个软件,而是一个深度优化CUDA的开源Python库。它的作者是 Nerfstudio 团队,这个团队最出名的项目是 Nerfstudio,是对 NeRFs 方法支持最好的一套框架。据其所述,出于对新一代技术的好奇,他们构建了 gsplat 这个库。它支持 Windows/Linux 两个系统,使用前必须准备好 Nvidia 显卡。跑这个项目真是特别费劲,官方的文档支持不够完善,以至于遇到问题,开发者们都只能在issue区里抱团取暖。

兴趣使然的创作者 2103

当AI写小说——一个被严重低估的工程化战场

摘要 AI小说创作远非简单的prompt生成,而是一项需要系统化工程管理的复杂任务。本文通过真实案例分析,揭示了当前AI写小说存在的五大核心难题:设定一致性难以维持、文风控制缺乏个性、爽感节奏难以把握、长篇记忆能力有限以及平台规则适配困难。研究显示,90%的失败案例源于错误的创作方法,主要表现为对话框式随机生成和模板式同质化创作。要实现高质量的AI小说创作,必须建立包括设定治理、记忆管理、文风校准等在内的全链路工程化体系,而非依赖单一prompt指令。只有采用系统化的人机协同方法,才能突破当前AI创作的质量

sellyogurt的博客 680

汇编调试实战:DEBUG、Turbo Debugger、GDB 与 IDA

本文系统讲解汇编调试技巧,涵盖 DEBUG.exe 基础使用(寄存器查看、单步执行、内存操作)、Turbo Debugger 图形化调试,以及 GDB(32位Linux)和逆向工具(IDA/Ghidra)的高级应用。重点解析寄存器管理、断点设置、栈平衡检查与常见错误排查,如死循环、内存越界、栈崩溃等,强调静态分析识别函数边界、动态分析验证逻辑流程。掌握这些方法可高效定位底层缺陷,提升汇编开发能力。

软件工程师文艺 210

SAP 成本分摊逻辑与案例(含具体数据)

摘要: SAP成本分摊通过**分配(Allocation)与分摊(Assessment)**两种方式,将间接成本中心费用按预设规则(如人数、工时、比例等)结转至直接成本对象。分配保留原始成本要素(如电费、工资),适用于需明细追踪的场景;分摊则打包为次级成本要素(如管理费用),简化核算。实操案例涵盖行政费用分配(按人数)、水电车间分摊(按工时)、机修作业分摊(作业成本法)及内部订单结算,系统通过KSV1/KSU1配置循环规则,KSV5/KSU5执行结转。关键点在于选择匹配业务的分摊基础,合理配置次级成本要素,

金牌架构师 550

ARM 汇编调试入门:MOV、LDR 和 STR

掌握,最重要的是看清数据移动的方向。

ccddvvv的博客 96

基于Simulink的自适应反步法(Adaptive Backstepping)控制​

本文针对非线性系统控制中未知参数和强非线性耦合的挑战,提出基于Simulink的自适应反步法解决方案。通过融合参数在线估计与反步递归设计,该方法显著提升了永磁同步电机(PMSM)速度控制的鲁棒性,使跟踪误差从20%降至3%,参数估计收敛时间小于0.5秒。文章详细阐述了从算法推导到Simulink实现的完整流程,包括系统架构设计、核心算法推导(以PMSM为例)、模块化建模方法、参数优化策略以及工程部署建议。提供的自适应控制器MATLAB代码和关键参数表可直接应用于实际工程,为电机驱动、机械臂等非线性系统控制提

xiaoheshang_123的博客 101

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

汇编子程序设计:参数传递、局部变量与调用约定

汇编代码模块化核心在于子程序设计。通过PROC/ENDP定义函数,结合栈帧管理(PUSH BP/MOV BP, SP)实现局部变量隔离与递归支持。参数传递可选寄存器(高效)或栈(多参适用),配合调用约定(如C、Pascal)规范参数入栈顺序与栈清理责任。掌握这些模式,便能编写结构清晰、可复用的汇编代码,迈向工程化开发。

软件工程师文艺 224

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

四级 Linux 应用与开发工程师笔记:8086 汇编、存储器、接口芯片和操作系统计算题整理

计算机四级 Linux 应用与开发工程师备考笔记:这门考的不是 Linux 命令,而是操作系统原理和计算机组成与接口两门课。组成与接口部分重点整理 8086 寻址方式、常考汇编指令和读程序题、存储器位扩展和字扩展、流水线时空图,接口芯片和中断按做题方法整理;操作系统部分整理高响应比优先、Clock 置换、两级页表、生产者消费者等考点,配真题讲解。

qq_39121450的博客 221

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Linux入门第二部分(ARM裸机篇) 第三章 汇编LED驱动实验

本文基于正点原子教程,详细讲解了I.MX6ULL裸机开发中LED驱动的实现。通过配置时钟、引脚复用(MUX)、电气属性(PAD)及GPIO方向与数据寄存器,使用汇编语言完成GPIO1_IO03引脚的初始化,实现低电平点亮LED0。程序经交叉编译、链接生成.bin文件,并借助imxdownload工具添加IVT/DCD头部,烧写至SD卡后,开发板上电启动即可点亮LED,完整演示了裸机程序从编写到烧写的全流程。

Zevalin的博客 498

汇编分支与循环:JMP/LOOP/条件跳转的结构化编程

本文详解如何用汇编的JMP与条件跳转实现if/else、for、while等高级控制流。通过循环计数、嵌套循环及复杂判断的典型模式,展示底层指令如何构建逻辑结构。掌握LOOP指令与跳转组合,即可在无高级语法环境下实现高效、精准的程序控制,体现汇编语言的简洁与性能优势。

软件工程师文艺 245

直接操作 VGA 显存:用汇编控制文本显示

本文详解VGA文本模式下直接操作显存的原理与实践:通过段寄存器B800h访问显存,每字符占2字节(ASCII+属性),利用偏移公式(n×80+m)×2定位输出。属性字节控制颜色与闪烁,如1Ch为蓝底亮红字。使用MOV ES:[DI], AX结合REP STOSW可高效清屏,配合INT 10h实现光标重置与滚动窗口。深入理解显存映射,是掌握图形底层逻辑的关键。

软件工程师文艺 192
上一篇: 从入门到精通汇编语言 第四章(内存寻址方式)
下一篇: 从入门到精通汇编语言 第六章(中断及外部设备操作)
Zevalin爱灰灰
Zevalin爱灰灰 优质创作者: 嵌入式与硬件开发技术领域 优质创作者: 嵌入式与硬件开发技术领域
博客等级 码龄3年 7137粉丝 553原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

Zevalin爱灰灰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值