从实模式到保护模式笔记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 指令来解决有符号乘法和数除法的问题

QQ截图20200628180647

​ 对于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的子程序

QQ截图20200628210539

命令 u/n 物理地址 反汇编指令,查看物理地址处开始的n条指令

QQ截图20200628210637

命令 info eflags 用于查看标志位的值,如果某个标志位是大写字母,则表明该标志位为1,小写则为0:

QQ截图20200628210843

​ 已经被执行的指令是xor dx,dx 下一条将被执行的指令是div ax,si。xor dx,dx的结果是0导致ZF PF置1(pf是结果二进制表示中1的个数是否为偶数,偶数即置1),所以ZF PF大写,其余小写

0x03 检测题目

QQ截图20200628211316

​ 第一题太基础不做,第二题很明显嘛cx先减一再判断是不是为0,所以一共执行2的16次方6566次