汇编与调试第5周复习笔记(含老师样例代码)
1. 本周学习目标
- 掌握基于 CF 的二进制输出两种写法(分支法与 adc 法)
- 掌握十进制输出的经典套路:除 10 取余 + 压栈逆序
- 理解 push/pop 的执行本质和小端存储
- 理解 段地址:偏移地址 与 byte/word/dword ptr
2. 知识点整理
2.1 CF 与字符输出
- shl 会把移出的最高位送入 CF
- 可以用 jc/jnc 分支输出 0/1
- 也可以用 adc 直接把 CF 转成字符('0' 或 '1')
2.2 十进制转换套路
- 每轮 div 10 得到一位余数(低位)
- 余数先压栈
- 最后出栈打印,得到高位到低位顺序
2.3 栈与小端
- push 先减 SP 再写内存
- pop 先读内存再加 SP
- 多字节按小端顺序存放:低字节在低地址
2.4 段地址与偏移地址
- 逻辑地址可写为 ds:[bx]
- 宽度修饰决定读取字节数:byte/word/dword ptr
- 给 DS 赋值常用两步:mov ax, imm + mov ds, ax
3. 老师样例代码(第5周)
3.1 1.asm(shl + jc 输出二进制)
asm
;把32位非符号整数转化成二进制输出
.386
code segment use16
assume cs:code
main:
mov eax, 12345678h
mov cx, 32
again:
shl eax, 1
mov ebx, eax ; mov指令不影响FL中的标志位
jc is_1 ; jc表示当CF==1时则跳,而jnc则表示CF==0时则跳
is_0:
mov dl, '0'
jmp output
is_1:
mov dl, '1'
output:
mov ah, 2
int 21h
mov eax, ebx
sub cx, 1
jnz again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main3.2 2.asm(shl + adc 无分支输出二进制)
asm
;把32位非符号整数转化成二进制输出
.386
code segment use16
assume cs:code
main:
mov eax, 12345678h
mov cx, 32
again:
shl eax, 1
mov ebx, eax ; mov指令不影响FL中的标志位
mov dl, 0
adc dl, '0' ; dl = dl + '0' + CF
; adc表示add with carry flag
output:
mov ah, 2
int 21h
mov eax, ebx
sub cx, 1
jnz again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main3.3 3.asm(无符号十进制输出)
asm
; 把eax中的非符号数转化成十进制格式输出
.386
code segment use16
assume cs:code
main:
mov eax, 2147483647
mov ebx, 10 ; 除数
mov ecx, 0 ; 统计除法的次数即十进制位数
div_again:
mov edx, 0
div ebx ; edx、eax / ebx = eax..edx
add edx, '0'
push edx ; 把edx压入堆栈
add ecx, 1
cmp eax, 0
jnz div_again
pop_again:
pop edx
mov ah, 2
int 21h
sub ecx, 1
jnz pop_again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main3.4 pushpop.asm(push/pop 原理讲解)
text
push、pop的语法:
push 一个16位的寄存器或16位的变量
push 一个32位的寄存器或32位的变量
pop 一个16位的寄存器或16位的变量
pop 一个32位的寄存器或32位的变量
push、pop的原理:
设堆栈指针sp=0x1000
mov ax, 1234h
mov bx, 5678h
push ax ; CPU在执行此指令时会做以下步骤:
; sp = sp - 2 = 0x0FFE
; *sp = ax
+0FFE 34h ;1234h必须按小端规则保存到堆栈中
+0FFF 12h ;小端规则(little-endian)是指宽度大于8位的值保存
+1000 ;到内存中时必须把低8位保存到低地址处,
;高8位保存到高地址处
push bx ; CPU在执行此指令时会做以下步骤:
; sp = sp - 2 = 0x0FFC
; *sp = bx
+0FFC 78h
+0FFD 56h
+0FFE 34h ;1234h必须按小端规则保存到堆栈中
+0FFF 12h ;小端规则(little-endian)是指宽度大于8位的值保存
+1000 ;到内存中时必须把低8位保存到低地址处,
;高8位保存到高地址处
设堆栈指针sp=0x1000
mov eax, 12345678h
mov ebx, 89ABCDEFh
push eax ; ①sp=sp-4=0FFCh ②*sp=eax
+0FFC 78h ;12345678h必须按小端规则保存到堆栈中
+0FFD 56h ;小端规则(little-endian)是指宽度大于8位的值保存
+0FFE 34h ;到内存中时必须把低8位保存到低地址处,
+0FFF 12h ;高8位保存到高地址处
+1000
push ebx ; ①sp=sp-4=0FF8h ②*sp=ebx
+0FF8 0EFh ;89ABCDEFh必须按小端规则保存到堆栈中
+0FF9 0CDh ;小端规则(little-endian)是指宽度大于8位的值保存
+0FFA 0ABh ;到内存中时必须把低8位保存到低地址处,
+0FFB 89h ;高8位保存到高地址处
+0FFC 78h
+0FFD 56h
+0FFE 34h
+0FFF 12h
+1000
pop ebx ; ①ebx = *sp; ②sp=sp+4=0FFCh
+0FF8 0EFh ; pop ebx前sp=0FF8h
+0FF9 0CDh
+0FFA 0ABh
+0FFB 89h
+0FFC 78h ; pop ebx后sp=0FFCh
+0FFD 56h
+0FFE 34h
+0FFF 12h
+1000
pop eax ; ①eax=*sp; sp=sp+4=1000h
+0FFC 78h ; pop eax前sp=0FFCh
+0FFD 56h
+0FFE 34h
+0FFF 12h
+1000 ; pop eax后sp=1000h3.5 2.c(与 2.asm 对照)
c
//把32位非符号整数转化成二进制输出
#include <stdio.h>
main()
{
int a = 0x87654321, mask = 0x80000000;
int i;
for(i=0; i<32; i++)
{
/*
if((a & mask) != 0) // if(a & mask)
putchar('1');
else
putchar('0');
*/
// putchar(((a & mask) != 0) + '0');
// putchar(!!(a & mask) + '0');
putchar((a<0) + '0');
a = a << 1; // a <<= 1;
}
}3.6 p.c(指针与下标等价)
c
#include <stdio.h>
main()
{
char a[]="ABCD", *p;
p = a;
putchar(p[0]);
putchar(0[p]); // 0[p] ==> *(0+p)
putchar(*p);
}3.7 段地址偏移地址.txt(已按正常中文还原)
text
1. 段地址、偏移地址
char c;
char *p = (char *)0x12345;
c = *p; // c = 0x56;
物理地址 值
10000h 12h ; 10000h是段起始地址, 其十六进制的个位必须=0
...
12344h 34h ; 12344h的偏移地址=2344h,段地址=1000h
12345h 56h ; 12345h的16位偏移地址=12345h-10000h=2345h
... ; 12345h的16位段地址=段首地址去掉个位=1000h
1FFFFh 78h ; 1FFFFh是段的末地址, 段长度=
; 1FFFFh-10000h+1 = 10000h字节 = 64K
; 64*2的10次方 = 2的6次方*2的10次方
; = 2的16次方即10000h
如何把物理地址12345h处的字节56h保存到寄存器al中?
mov ax, 1000h
mov ds, ax ; ds是数据段寄存器(data segment简称)
; 注意ds不能接受常数给它赋值, 它只能接受
; 一个16位的寄存器或16位的变量给它赋值;
; 故不能写成mov ds, 1000h
mov bx, 2345h ; bx=偏移地址=2345h
mov al, ds:[bx]; 相当于C语言的 al = *(ds:bx);
; 其中ds:bx是逻辑地址, 它起到了C语言的指针的作用
; al = 56h
2. 汇编语言中变量的一般表示形式:
宽度修饰 用段寄存器表示的段地址:[偏移地址]
其中偏移地址既可以用常数表示,也可以用寄存器+常数表示
宽度修饰包括以下几种:
(1) byte ptr 表示变量的宽度是8位
(2) word ptr 表示变量的宽度是16位
(3) dword ptr 表示变量的宽度是32位
10000h 12h ; 10000h是段起始地址, 其十六进制的个位必须=0
...
12344h 34h ; 12344h的偏移地址=2344h,段地址=1000h
12345h 56h ; 12345h的16位偏移地址=12345h-10000h=2345h
; 12345h的16位段地址=段首地址去掉个位=1000h
12346h 78h
12347h 9Ah
...
1FFFFh 78h ; 1FFFFh是段的末地址, 段长度=
; 1FFFFh-10000h+1 = 10000h字节 = 64K
; 64*2的10次方 = 2的6次方*2的10次方
; = 2的16次方即10000h
设ds=1000h, bx=2344h, 现在要把12344h处的字节赋值给al
mov al, byte ptr ds:[bx] ; al = *(char *)(ds:bx)
; byte=char, ptr=第2颗*号
或
mov al, ds:[bx] ; 跟上面的语句等价, 因为根据al的宽度为8位
; 编译器可以推断ds:[bx]也是8位,故byte ptr可以省略
; al = 34h
设ds=1000h, bx=2344h, 现在要把12344h处的short int赋值给ax
mov ax, word ptr ds:[bx]; word=short int
; ax=5634h
设ds=1000h, bx=2344h, 现在要把12344h处的long int赋值给eax
mov eax, dword ptr ds:[bx]; dword=long int
; eax=9A785634h4. 本周易错点清单
- shl 后 CF 只反映“移出的位”,不是任意位
- adc 用法不熟,忘记先把 dl 清零
- 十进制输出忘记入栈,导致输出反序
- push/pop 宽度与寄存器不匹配
- 给 DS 直接赋立即数
- 忽略小端顺序,word/dword 读取结果判断错
5. 复习建议
- 把 1.asm 和 2.asm 对照着背,理解有分支与无分支两种写法。
- 手写 3.asm,先写除法循环,再写出栈循环。
- 对照 段地址偏移地址 一节,自己画一次内存地址与字节分布图。