从实模式到保护模式笔记02相同的功能不同的实现
从实模式到保护模式笔记02相同的功能不同的实现
0x00 代码
jmp near start
mytext db 'l',7,'a',7,'b',7,'e',7,'l',7,' ',7,'o',7,'f',7,'f',7,'s',7,'e',7,'t',7,':',7
number db 0,0,0,0,0
start:
mov ax,0x7c0;将0x0000:0x7c00转化成0x07c0:0x0000,修改数据段基地址
mov ds,ax
mov ax,0xb800
mov es,ax
cld;si di递增
mov si,mytext
mov di,0
mov cx,(number-mytext)/2;因为用的movsw一次传一个字,所以/2
rep movsw
mov ax,number
mov bx,ax
mov cx,5
digit:
xor dx,dx
div si
mov [bx],dl
inc bx
loop digit
mov bx,number
mov si,4
show:
mov al,[bx+si]
add al,0x30
mov ah,0x04
mov [es:di],ax
add di,2
dec si
jns show
mov word [es:di],0x0744;写入字符'D'并设置颜色
jmp near $
times 510-($-$$) db 0
db 0x55,0xaa
0x01 代码详解
程序开头使用了jmp near start,目的是为了跳过数据区,在上一章的代码中,我们是将每一个要显示的字符的ascii写在指令里,本章的代码则是定义一个数据区,然后再从数据区里取数据
db用于定义一个字节的数据 dw 一个字 dd 双字 times n db x,x,x可以将(x,x,x)重复定义n次
段地址的初始化 汇编语言源程序的编译假定:编译后的代码将从某个内存段中,偏移地址位0的地方开始加载,这样,指令的汇编地址将和在段内的偏移地址相等,直接使用标号也不会发生问题。
但是实际上,程序被加载时,有可能不是从段内偏移地址为0的地方开始的,比如0x0000:0x7c00,这样如果标号的汇编地址是0x05,那它被加载后的实际段内偏移地址是0x7c05,但是所有访问该标号的指令仍然访问的是0x05,因为这是在编译时就决定了的,所以在上一章中,我们使用了:
mov [0x7c00+number+0x00],dl
这样古怪的写法
而好在,我们可以用逻辑地址0x07c0:0x0000这样的分段方式,将程序从段内偏移地址为0的地方开始加载,这样标号的汇编地址和段内偏移地址相等,就不会出问题了。
批量数据传送 movsb 一次传送一个字节 movsw一次传送一个字,rep mov sb rep movsw可以批量传送, 寄存器cx保存传送次数,DS:SI是源位置,ES:DI是目标位置,传送的方向由flag寄存器里的DF位(direction flag)决定 DF为0则SI,DI递增(如果是movsb+1 是movsw+2) 反之DF位为1则SI,DI递减。可以通过CLD置零DF位,STD置1DF位
计算机中的数 数据都是二进制,同样的一个二进制数,你可以将它看成无符号数,也可以看成有符号数。对于有符号数的负数使用的是反码表示(对应正数的原码取反,再+1),也就是比如-5,对应正数5 ->00000101->取反->11111010->+1->11111011 可以发现 有符号数的正数负数相加也恰好为0(不看最终进位)
neg指令 所以有符号数负数可以用0-正数得到 同样正数也可以用0-负数得到,mov al,00000001 neg al al->11111111 neg指令就是无符号数取相反数的,也就是实现x=0-x的
处理器视角的数据类型 上面说过,一个数是有符号数和无符号数取决于你自己的看法,对于处理器,大部分指令,处理数据时,不论你将数据看成有符号数还是无符号数,结果都是对的,比如以下:
mov ah,al
mov ah,0xf0
inc ah
mov ax,0x8c03
add ax,0x05
add ax,-5
sub ax,-13
几乎所有的处理器指令既能操作有符号数,又能操作无符号数,但是例如 mul div乘法除法指令除外
mov al,255;无符号数255,有符号数-1
mul 10;结果2550 按照16位来看 不论有符号数还是无符号数都是2550
除法div也有着同样的问题,8086设计了 imul 和 idiv 指令来解决有符号乘法和数除法的问题
对于idiv:
mov ax,0xf0c0
mov bl,0x10
idiv bl
这样是错的,因为ax/bl的商在al,余数在ah,但是上面的代码商会溢出,所以我们就要用32位/16位,需要注意的是,对于有符号数来说,正数扩展位数是在前面添上足够的0,而负数是在前面添上足够的1
mov ax,0xf0c0
mov dx,0xffff;由于ax是负数,要组合成dx ax 一个32位数,就要让dx全1
mov bl,0x10
mov bh,0x00
idiv bx
8086提供 cbw cwd 指令来扩展位数,意思分别是 convert byte to word convert word to double word,注意该指令只针对al或者ax中的数据
mov ax,0xf0c0
cwd
mov bl,0x10
idiv
代码中的 jns show 也就是jmp not sign,也就是SF位为0时跳转 SF位受之前能改变SF位的指令影响,如果产生负数,也就是最高位为1,就将SF位置1,一般的运算指令的结果都能改标志位 ZF (zero flag)SF (sign flag)PF (奇偶)CF (cross无符号数进位)OF(overflow 有符号数溢出127+1=-128),但是mov inc push pop不会
条件转移指令 不一一列举了 有好多,分为有符号数和无符号数的
在nasm中 $ 和 \(分别表示 当前行的汇编地址 和 当前程序段的起始汇编地址(也就是恒位0)。前面代码用times 510-($-\)) db 0,就是利用这个计算前面部分指令的长度,再用510(留下2个字节写0x55 0xaa),减去就得到要填充的0的个数。
0x02 进一步调试
命令 n 用于跳出循环(loop或者rep movsb/w) 或者call的子程序
命令 u/n 物理地址 反汇编指令,查看物理地址处开始的n条指令
命令 info eflags 用于查看标志位的值,如果某个标志位是大写字母,则表明该标志位为1,小写则为0:
已经被执行的指令是xor dx,dx 下一条将被执行的指令是div ax,si。xor dx,dx的结果是0导致ZF PF置1(pf是结果二进制表示中1的个数是否为偶数,偶数即置1),所以ZF PF大写,其余小写
0x03 检测题目
第一题太基础不做,第二题很明显嘛cx先减一再判断是不是为0,所以一共执行2的16次方6566次