II. 数据的表示与运算

数据的表示

二进制数据的组织

名字宽度英文定义方式C 语言中等价类型
字节8 bitbytedb(unsigned) char;
16 bitworddw(unsigned) short int;
双字32 bitdouble worddd(unsigned) long int; float;
四字64 bitquadruple word (qword)dq(unsigned) long long; double;
十字节80 bitten byte (tbyte)dtlong double;

在 data 段中可以定义变量和数组,对应语法如下:

data segment
  s db "Hello, World" ; 字符串 char[13]
  a dd 1, 2, 3, 4, 5  ; 数组 int[5]
  b dw 5 dup(0)       ; 数组 short int[5],初始值全为0
  c db 0FFh;          ; 值为0FFh的字符 char
  f dd 3.14           ; 值为3.14的浮点数 float
data ends
  • dup(数) 来表示数组,用 ? 表示无初始值或初始值任意。
  • 在定义字符串时,可以通过 0Dh0Ah 来表示换行。

二进制浮点数的表示

略。

数据的运算

零扩充和符号扩充

  • 当把低位无符号数赋给高位无符号数时,必须采用零扩充规则,即在左边补 0。
  • 当把低位有符号数赋给高位有符号数时,必须采用符号扩充规则,即在左边补上原数的最高位(符号位)。

cbwcwd 符号扩充

  • cbw,即 convert byte to word,可将 AL 中的值符号扩充到 AX 中
  • cwd,即 convert word to double word,可将 AX 中的值符号扩充到 DX:AX 中

movzx 零扩充

movzx dest, src 把 src 零扩充到寄存器 dest 中。指令格式:

  • movzx reg16, reg8|reg8
  • movzx reg32, reg8|mem8|reg16|mem16

movsx 符号扩充

movsx dest, src 功能与格式与 movzx 类似,区别在于 movsx 是符号扩充。

整数运算

加法 add / inc / adc

  • add dest, srcdest += src
  • adc dest, src(带进位加):dest += src + CF
  • inc opop++

add / adc 指令若发生进位会将 CF 寄存器置 1,否则将 CF 寄存器置 0。inc 指令不会影响 CF 寄存器

减法 sub / sbb / dec / neg

  • sub dest, srcdest -= src
  • sbb dest, src(带错位减):dest -= src + CFdest = dest - src - CF
  • dec opop--
  • neg op(取相反数):op = -op

乘法 mul / imul

mul 进行无符号整数乘法。

  • 当 mul 后跟一个 8 位寄存器或 8 位变量作为乘数时,被乘数一定是 AL,乘积一定是 AX。
  • 当 mul 后跟一个 16 位寄存器或 16 位变量作为乘数时,被乘数一定是 AX,乘积一定是 DX:AX。
  • 当 mul 后跟一个 32 位寄存器或 32 位变量作为乘数时,被乘数一定是 EAX,乘积一定是 EDX:EAX。

imul 进行有符号整数乘法,其第一类用法和 mul 的上述用法相同。第二类用法可以包含两个或三个操作数,其中前两个操作数可以是寄存器或变量,第三个操作数只能是常数:

  • imul eax, ebx:EAX = EAX × EBX
  • imul eax, ebx, 3:EAX = EAX × EBX × 3

除法 div / idiv

div 进行无符号整数除法。

  • 当 div 后跟一个 8 位寄存器或 8 位变量作为除数时,被除数一定是 AX,商是 AL,余数是 AH
  • 当 div 后跟一个 16 位寄存器或 16 位变量作为除数时,被除数一定是 DX:AX,商是 AX,余数是 DX
  • 当 div 后跟一个 32 位寄存器或 32 位变量作为除数时,被除数一定是 EDX:EAX,商是 EAX,余数是 EDX

除法溢出有来两种情形:

  • 除数是 0。
  • 被除数除以除数的商无法保存到相应寄存器中。

当发生除法溢出时,会在 div 指令上方插入并调用一条 int 00h 中断指令(默认行为:显示 divide overflow 并强行结束程序运行)。如果修改 0:0 处的中断向量,则可以在发生中断时转到我们自己的中断函数,对除法溢出的问题进行处理。iret 后会重新执行除法指令。

idiv 进行有符号整数除法,用法参考 div

浮点数运算

Pasted image 20240109190524

  • fld 压入小数类型的变量。
  • fild 把整数类型的变量转化为小数类型并压入。
  • fst 把小数寄存器 st(0) 保存到变量中。
  • fstp 把小数寄存器 st(0) 保存到变量中并弹出 st(0)。
  • fadd / fsub / fmul / fdiv 进行浮点数的四则运算;如用 fmul st, st(1) 进行乘法计算。

CPU 内部一共有 8 个小数寄存器,分别叫做 st(0)、st(1)、…、st(7)。其中 st(0)简称 st 这 8 个寄存器的宽度均达到 80 位,相当于 C 语言中的 long double 类型。

位运算

逻辑运算

指令用法含义C 语言等价运算符言
andadd ax, bx&
oror ax, bx|
xorxor ax, ax异或^
notnot ax取反~

逻辑移位 shlshr

适用于无符号数的移位运算,空位补 0,最后一个被移动的位会被放到 CF 标志中。

算术移位 salsar

适用于有符号数的移位运算。salshl 完全相同,sar 会在被操作数是负数时补 1,否则行为和 shr 相同。同样的,最后一个被移动的位会被放到 CF 标志中。

循环移位 rolror

对应变量进行循环移位操作,即被移出的值会从另一侧填补到数中。注意最后一个被移动的位会在被放到另一端的同时放到 CF 标志中。

带进位循环移位 rclrcr

同样是循环移位操作,但会把 CF 标志看作其中一位参与运算(如被操作数宽度为 16 位,可看作一个长度为 17 的环)。

字符串操作与运算

Fun Fact:C 语言中的三个关于字符串的库函数
  • strcpy(target, source); 永远按正方向复制

  • memcpy(target, source); 永远按正方向复制

  • memmove(target, source); 会自行选择方向保证部分重叠时正确

字符串指令的方向会被 DF 标志控制,当 DF=0 时会按照正方向(低地址到高地址)运行,当 DF=1 时会按照反方向(高地址到低地址)运行。可通过执行 cldstd 来控制。

字符串复制指令 rep movsb/w/d

rep movsb,即 move string in byte,会以字节byte)为单位复制字符串。如果将 movsb 换成 movswmovsd,则分别会以 word、double word 为单位复制字符串。

在执行 repo movsb 执行前需要做以下准备工作:

  1. DS:SI 指向源字符串(SI 就是 source index)
  2. ES:DI 指向目标字符串(DI 就是 destination index)
  3. CX 寄存器存储移动次数
  4. DF 标志表示方向(如果)

rep movsb 的行为的伪代码如下:

again:
	if (cx == 0) goto done
	asm mov byte ptr es:[di], byte ptr es:[do]  // movsw/d的话这里就换为word/dword
	df == 0 ? (si++, di++) : (si--, di--)      // movsw/d的话这里就需要增减2/4
	cx--
	goto again
done:

字符串比较指令 repe/ne cmpsb/w/d

单条 cmpsb 指令的行为如下:

asm cmp byte ptr ds:[si], byte ptr es:[di]
df == 0 ? (si++, di++) : (si--, di--)

reperepne 则控制了 cmp 比较结果的行为,如 repe 的行为如下(若本次比较相等则继续比较下一个):

again:
	if (cx == 0) goto done
	asm cmp byte ptr ds:[si], byte ptr es:[di]
	df == 0 ? (si++, di++) : (si--, di--)
	cx--
	if (zf == 1) goto again    // repne的话这里就要求ZF=0
done:

字符串搜索指令 repe/ne sacsb/w/d

这条字符串指令就只和 ES:DI (注意不是 DS:SI 哦)所指向的字符串有关,CPU 将在其中搜索 AL/AX/EAX 的值(取决于是 sacsb 还是 sacswsacsd)。

repe sacsb 的行为如下:

again:
	if (cx == 0) goto done
	asm cmp al, byte ptr es:[di]  // sacsw/d的话这里还要换成AX/EAX
	df == 0 ? (di) : (di--)
	cx--
	if (zf == 1) goto again       // repne的话这里就要求ZF=0
done:

写入字符串指令 rep stosb/w/d

把 AL/AX/EAX 的值写入 ES:DI 指向的目标字符串中。

读取字符串指令 lodsb/w/d

从 DS:SI 所指向的源字符串中读取一个 byte/word/double word 保存到 AL/AX/EAX。

例:用 lodsbstosb 指令过滤掉字符串中的空格并存储到另一字符串中
data segment
s db "Ada Lovelace is often regarded as "
  db "the first computer programmer."
slen = $ - offset s
t db slen dup('T')
data ends

code segment
assume cs:code, ds:data
main:
   mov ax, data
   mov ds, ax
   mov es, ax
   mov si, offset s; ds:si->s[0]
   mov di, offset t; es:di->t[0]
   mov cx, slen    ; CX = length of s
   cld             ; DF = 0
   jcxz done       ; jump if cx is zero
again:
   lodsb           ; AL=ds:[si], si++
   cmp al, ' '
   je skip
   stosb           ; es:[di]=AL, di++
skip:
   loop again      ; dec cx
                   ; jnz again
done:
   xor al, al      ; AL = 00h
   stosb           ; es:[di]=AL, di++
   mov ah, 4Ch
   int 21h
code ends
end main

Comments