• 《汇编语言》(王爽)补充笔记,第 10 ~ 13 章


    第十章 CALL 和 RET 指令

    ● call 与 ret 指令均为转移指令,用于子程序实现

    ● ret 用栈中数据修改 IP 实现段内转移,执行本质:pop IP,或表示成 IP = ((ss) * 16 + (sp)),sp = (sp) + 2(括号表求值),以下代码展示一个从腰开始、到头结束的程序

     1 assume cs:codesg
     2 
     3 stack segment
     4     db 16 dup (0)
     5 stack ends
     6 
     7 codesg segment
     8     mov     ax, 4c00h
     9     int     21h
    10 start:
    11     mov     ax, stack   ; 手动维护一个栈
    12     mov     ss, ax      
    13     mov     sp, 16
    14     mov     ax, 0       ; 把 0 入栈
    15     push    ax
    16     mov     bx, 0       ; 做一些其他事
    17     ret                 ; IP = 0,CS:IP 指向第一条指令
    18 codesg ends
    19 
    20 end start

    ● retf 用栈中数据修改 CS 与 IP 实现段间转移,执行本质:pop IP    pop CS,以下代码类似

     1 assume cs:codesg
     2 
     3 stack segment
     4     db 16 dup (0)
     5 stack ends
     6 
     7 codesg segment
     8     mov     ax, 4c00h
     9     int     21h
    10 start:
    11     mov     ax, stack
    12     mov     ss, ax
    13     mov     sp, 16
    14     mov     ax, 0
    15     push    cs          ; 多一个 cs 入栈
    16     push    ax
    17     mov     bx, 0
    18     retf
    19 codesg ends
    20 
    21 end start

    ● call mark 实现 IP 压栈后段内跳转,执行本质:push IP   jmp near PTR mark

    ● call far mark 实现 CS、IP 压栈后段间跳转,执行本质:push CS    push IP   jmp far PTR mark

    ● call reg16call word PTR addr ,call dword PTR addr 类似实现压栈和跳转

    ● 非溢出的除法,XH 与 XL 表 X 的高 16 位和低 16 位:X / N = XH / N * 65536 + ((X - XH / N * N) * 65536 + XL) / N

    第十一章 标志寄存器(这部分的例子写的都挺好)

    ● 8086 CPU标志寄存器(16 位)中存储的信息称为程序状态字(PSW)。从高位(15)到低位(10)依次为:无,无,无,无,OF(11),DF,IF,TF,SF,ZF(6),无,AF(4),无,PF(2),无,CF(0)

    ● ZF 零标志位,PF 奇偶标志位,SF 符号标志位,CF 进位标志位,OF 溢出标志位,DF 方向标志位,

    ● SF 理解:将数据当有符号数运算时,可通过 SF 得知结果正负,将数据当无符号数运算时,SF 无意义,虽然指令可能影响了其值

    ● CF 理解:无符号运算最高有效位(依寄存器,不依实际数值)向假想的更高位的进位和借位的情况

    ● OF 理解:有符号运算是否溢出(正 / 负)的情况

    ● 好例,说明 CF 和 OF 之间并没有关系:

      mov al, 62h    add al, 63h  ; 执行后 CF = 0(无符号加法 98 + 99 = 197 < 255),OF = 1(有符号加法 98 + 99 = 197 > 127)

      mov al, F0h    add al, 88h  ; 执行后 CF = 1(无符号加法 240 + 136 = 376 > 255),OF = 1(有符号加法 -16 - 120 = -136 < -128)

      mov al, F0h    add al, 78h  ; 执行后 CF = 1(无符号加法 240 + 120 = 360 > 255),OF = 0(有符号加法 -16 + 120 = 104)

    ● 指令 cmp 理解:两个操作数作差,但不输出结果,仅根据结果调整标志寄存器的数值。

    ● 无符号数比较,执行 cmp ax, bx 后,根据 ZF 和 CF 来判断两个操作数的情况

      ZF = 1,说明 ax == bx

      ZF = 0,说明 ax != bx

      CF = 1 或 ZF = 1,说明 ax ≤ bx

      CF = 1,说明 ax < bx

      CF = 0,说明 ax ≥ bx

      CF = 0 且 ZF = 0,说明 ax > bx

    ● 有符号数比较(有溢出的存在,不能仅从 ZF 和 SF 判断,要考虑溢出标志位),执行 cmp ax, bx 后,

      ZF = 1,说明 ax == bx

      ZF = 0,说明 ax != bx

      OF = 0 且 SF = 1,说明 ax < bx

      OF = 1 且 SF = 1,说明 ax > bx。溢出后结果为负,说明是正溢出,正数减负数导致

      OF = 0 且 SF = 0,说明 ax ≥ bx,配合 ZF判断是否取到等号

      OF = 1 且 SF = 0,说明 ax < bx。溢出后结果为正,说明是负溢出,负数减正数导致

    ● 无符号条件转移及其等价伪代码

      je      相等转移        if( ZF ==1 ) jmp

      jne    不等转移        if( ZF ==0 ) jmp

      jb      低于转移        if( CF == 1) jmp

      jnb    不低于转移    if( CF == 0 ) jmp

      ja      高于转移        if( CF == 0 && ZF == 0 ) jmp

      jna    不高于转移    if( CF == 1 || ZF == 1 ) jmp

    ● DF 作用:每次串处理操作后 si,di 按 DF 值进行递增或递减

    ● pushf 与 popf 指令,将标志寄存器压栈或出栈。可用于访问和修改标志寄存器:

    1     pushf
    2     pop ax  ; 标志寄存器的值出栈到 ax 中
    3 
    4     ...     ; 修改 ax 的值
    5 
    6     push ax
    7     popf    ; ax 的值出栈到标志寄存器中

    ● 在 debug 中,标志寄存器直接按各个标志的值来显示,其对照关系如下:

     寄存器    == 1   == 0 

      OF  OV  NV

      DF  DN  UP

      SF  NG  PL

      ZF  ZR  NZ

      PF  PE  PO

      CF  CY  NC

    第十二章 内中断

    ● 8086 CPU 内中断发生条件及中断类型码:除法错误(0),单步执行(1),into 执行(4),int 执行(n,字节型立即数)

    ● 中断向量表时中断处理程序入口地址的列表,在 8086 CPU 机器上占据内存首段 0000:0000 ~ 0000:03FF(1024 Byte),每个入口的段地址和偏移地址各占 1 Word(每入口 4 Byte),最多 256 入口。但是一般情况下 0000:0200 ~ 0000:02FF 都是空着的,操作协同和其他程序也不占用,debug 中查看如下

    ● 8086 CPU 中断过程:

      ① 从中断信息取得中断类型码 N

      ② 保存标志寄存器 pushf

      ③ 置标志寄存器 TF = 0,IF = 0

      ④ 保存当前指令位置 push CS,push IP

      ⑤ 计算中断程序入口 IP = (N * 4),CS = (N * 4 + 2)

      ⑥ 中断程序开始执行,保存要用到的寄存器,处理中断,回复用到的寄存器,用 iret 指令返回(iret 常与硬件自动完成的中断过程配合使用)

    ● 代码,自行改写 0 号中断,在发生除法溢出时执行自定义的程序(输出字符串 “overflow!”)

     1 assume cs:code
     2 
     3 code segment
     4 start:
     5     call div0               ; 向内存中注入 div0 代码(只需要运行一次,再次调用主程序时不用)
     6        
     7     mov     ax, 1000h
     8     mov     bh, 1h
     9     div     bh              ; 计算 1000h / 1h,使得 al 中放不下结果,进入 0 号中断
    10       
    11     mov     ax, 4c00h
    12     int     21h
    13 
    14 div0:
    15     mov     ax, cs                          ; 设置 ds:si 指向中断程序源代码地址
    16     mov     ds, ax
    17     mov     si, offset do0                  
    18     mov     ax, 0                           ; 设置 es:di 指向目标地址 0000:0200
    19     mov     es, ax
    20     mov     di, 200h                        
    21     mov     cx, offset do0end - offset do0  ; 设置 cx 为传输长度,用两个 offset 来计算
    22     cld                                     ; 设置传输方向为正
    23     rep     movsb                           ; 代码注入指定地址
    24                                             
    25     mov     ax, 0                           ; 设置 es:0000 指向中断向量表
    26     mov     es, ax
    27     mov     word PTR es:[0*4], 200h         ; 将第 0 个中断源指向注入的程序地址
    28     mov     word PTR es:[0*4+2], 0h
    29           
    30     mov     ax, 4c00h
    31     int     21h
    32 
    33 do0:    
    34     jmp     short do0start      ; 跳转到真正可执行的代码地址
    35     db      "overflow!"         ; 将输出字符串放到 .code 域而不是 .data 域,否则代码注入时找不到
    36 
    37 do0start:
    38     mov     ax, cs              ; 设置 ds:si 指向字符串
    39     mov     ds, ax
    40     mov     si, 202h      
    41     mov     ax, 0b800h          ; 设置 es:di 指向显存空间的中间位置
    42     mov     es, ax
    43     mov     di, 12*160+36*2
    44     mov     cx, 9               ; 设置 cx 为字符串长度    
    45 
    46 s:                              ; 显示字符串
    47     mov     al, [si]
    48     mov     es:[di], al
    49     inc     si
    50     add     di, 2
    51     loop s
    52 
    53     mov ax, 4c00h
    54     int 21h
    55 
    56 do0end:                         ; 标记程序结束的地址,用于计算代码长度
    57     nop
    58 
    59 code ends
    60 
    61 end start

    ■ 程序输出,注意 0000 : 0000 处入口地址发生了改变,0000 : 0200 处注入了程序,从右边可见 “overflow!” 的字符串

    ● 单步中断:CPU 检测到标志寄存器 TF = 1,则产生单步中断,其处理过程同一般中断过程,只是中断类型码为 1

    ● 有的情况下,即使 CPU 检测到中断信息,也不会发生响应。例如刚执行完 ss 寄存器的传送指令后,CPU 忽略中断,因为 ss : sp 指向栈顶,更改 ss 后可能指向敏感地址,不能调用中断等其他程序。此时应该将调整 sp 的指令紧接着调整 ss 指令存放。

    第十三章 int 指令

    ● int 指令引发内中断,处理过程同一般中断过程

    ● iret 指令,放在需要返回原程序运行地址的中断例程的末端,等价于指令:pop IP,pop CS,popf,ret

    ● BIOS 主要包括几方面的内容:① 硬件系统检测室初始化程序;② 外部中断和内部中断的中断例程;③ 对硬件设备进行 I/O 操作的中断例程;④ 其他硬件系统相关的中断历程

    ● BIOS 安装到内存的过程:

      ① 开机 CPU 加电,初始化 CS = 0FFFFh,IP = 0,从 CS : IP 开始执行。该处有一条跳转指令,跳转执行 BIOS 硬件系统检测和初始化程序

      ② 初始化程序将 BIOS 支持的中断例程入口地址登记到内存的中断向量表中,中断例程本身是固化到 ROM 中的程序,一直在内存中存在

      ③ 硬件系统检测和初始化完成后,调用 int 19h 进行操作系统引导

      ④ DOS 操作系统启动,将其支持的中断例程填入内存

    ● 中断例程可以包含多个子程序,BIOS 和 DOS 的中断例程都使用寄存器 ah 来传递内部子程序编号

    ● 内存地址空间中,B80000h ~ BFFFFh 共 32 kB 空间用于 80 * 25 彩色自负模式的显示缓冲区,默认情况下显示第0页 B0000h ~ B8F9Fh 的内容

    ● 代码,在屏幕上显示文字,以及之前一直使用的程序退出(在这之前有屏幕输出的返利代码都不能正确输出,从这里开始可以用了)

     1 assume cs:code
     2 data segment 
     3  db 'Hello World!','$'  ; 需要输出的字符串用 $ 标记结尾
     4 data ends
     5 
     6 code segment
     7 start:        
     8     mov bh, 0           ; 第 0 页
     9     mov dh, 3           ; 行号
    10     mov dl, 9           ; 列号
    11     mov ah, 2           ; 调用 BIOS 10 号中断例程 2 号子程序指定光标位置
    12     int 10h  
    13         
    14     mov al, 3           ; 字符 ASCII
    15     mov bl, 11001010b   ; 颜色属性,闪烁(BL),背景(RGB),高亮(I),前景(RGB)
    16     mov bh, 0           ; 第 0 页
    17     mov cx, 8           ; 字符重复数
    18     mov ah, 9           ; 调用 BIOS 10 号中断例程 9 号子程序打印字符
    19     int 10h
    20 
    21     mov ah, 2           
    22     mov bh, 0            
    23     mov dh, 5    
    24     mov dl, 9    
    25     int 10h
    26     
    27     mov ax, data        ; ds : dx 指向 data 地址
    28     mov ds, ax
    29     mov dx, 0           
    30     mov ah, 9           ; 调用 BIOS 21 号中断例程 9 号子程序输出字符串
    31     int 21h
    32 
    33     mov ax,4c00h        ; 调用 BIOS 21h 号中断例程 4Ch 号子程序结束程序,指定返回值 00h 
    34     int 21h 
    35 
    36 code ends
    37 
    38 end start               ; 有数据段的时必须指定入口,否则暴死,无数据段时可以没有入口

    ■ 输出结果

  • 相关阅读:
    函数 free 的原型
    malloc 返回值的类型是 void *
    malloc 函数本身并不识别要申请的内存是什么类型
    用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”
    动态内存的申请与释放必须配对,防止内存泄漏
    避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1” 操作
    不要忘记为数组和动态内存赋初值
    用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL
    释放了内存却继续使用它
    忘记了释放内存,造成内存泄露
  • 原文地址:https://www.cnblogs.com/cuancuancuanhao/p/9656436.html
Copyright © 2020-2023  润新知