OS-Lab1
问题答案
1、8086的五类寄存器,分别举例说明其作用
Intel 8086 CPU 有五种类型的寄存器,分别是通用寄存器、指针寄存器、段寄存器、标志寄存器和指令指针寄存器。
通用寄存器:通用寄存器用于存储数据和计算,共有 8 个,分别是 AX、BX、CX、DX、BP、SP、SI 和 DI。其中 AX、BX、CX 和 DX 是 16 位寄存器,可以存储 16 位的数据。BP、SP、SI 和 DI 是 16 位的指针寄存器,用于存储内存地址。
指针寄存器:指针寄存器用于存储内存地址,共有 4 个,分别是 BP、SP、SI 和 DI。BP 和 SP 分别用于存储堆栈底部和栈顶的地址,SI 和 DI 用于存储数据在数组中的地址。
段寄存器:段寄存器用于存储内存中的段地址,共有 4 个,分别是 CS、DS、ES 和 SS。CS 用于存储代码段的地址,DS 用于存储数据段的地址,ES 用于存储附加数据段的地址,SS 用于存储堆栈段的地址。
标志寄存器:标志寄存器用于存储 CPU 的状态标志,共有 16 位,可以分为多个标志位。其中常用的标志位包括 CF(进位标志)、ZF(零标志)、SF(符号标志)、OF(溢出标志)等。
指令指针寄存器:指令指针寄存器用于存储下一条要执行的指令的地址,共有 IP 和 CS 两个寄存器。IP 存储当前指令在代码段中的偏移地址,CS 存储当前代码段的地址。
举例说明:
AX 寄存器:可以用来存储 16 位整数,也可以拆分成两个 8 位寄存器 AH 和 AL,分别用于存储高 8 位和低 8 位数据。
BP 寄存器:用于存储当前堆栈底部的地址,通常在函数调用时使用。
DS 寄存器:用于存储数据段的地址,可以通过修改 DS 寄存器来访问不同的数据段。
CF 标志位:用于存储进位标志,当进行加法运算时,如果结果超出了寄存器的位数,CF 标志位就会被设置为 1。
IP 寄存器:用于存储下一条要执行的指令的偏移地址,可以通过修改 IP 寄存器来实现跳转。
2、段寄存器?作用?
NASM 支持以下四个段寄存器:
CS:存储代码段的段地址。
DS:存储数据段的段地址。
SS:存储堆栈段的段地址。
ES:存储附加数据段的段地址。
在实模式下,每个段寄存器可以指向 64 KB 的内存空间。通过组合段寄存器和通用寄存器,可以访问内存中的任意数据。
段寄存器的作用是将内存地址分成段和偏移两个部分来进行寻址。在实模式下,每个内存地址由一个 20 位的段地址和一个 16 位的偏移地址组成,总共可以寻址 1MB 的内存空间。通过不同的段寄存器来存储不同的段地址,程序可以访问不同的内存区域。
3、什么是寻址?8086寻址方式
寻址是指 CPU 访问内存中某个数据的过程。在计算机中,每个内存单元都有一个唯一的地址,CPU 可以通过地址来访问内存中的数据。寻址方式是指 CPU 访问内存时使用的地址计算方式。
Intel 8086 CPU 支持多种寻址方式,包括以下几种:
立即寻址(Immediate Addressing):直接将常数或变量的地址作为操作数,例如 mov ax, 1234h。
直接寻址: MOV AX [1234H] 直接给出了地址1234H,用[]符号取数
寄存器寻址(Register Addressing):将寄存器中的值作为操作数,例如 mov ax, bx。
寄存器间接寻址(Register Indirect Addressing):使用寄存器中存储的地址作为操作数,例如 mov ax, [bx]。
寄存器相对寻址: MOV AX [SI+3]。
基址加变址寻址(Base plus Index Addressing):将基址寄存器、变址寄存器和偏移量相加得到操作数的地址,例如 mov ax, [bx+si+10].
相对基址加变址 :MOV AX [BX+DI+3]
4、主程序和子程序如何传参?
在 NASM(Netwide Assembler)汇编语言中,主程序和子程序之间可以使用寄存器来传递参数。以下是一些常用的寄存器和它们对应的参数传递方式:
利用约定的地址传递参数
利用寄存器传递参数
• 缺点:能传递的参数有限,因为寄存器有限
AX、BX、CX、DX 寄存器:可以用于传递 16 位的整数参数。主程序将参数存储在寄存器中,然后调用子程序,并在子程序中使用相应的寄存器来获取参数。
SI、DI 寄存器:可以用于传递指针类型的参数。主程序将指针存储在寄存器中,然后调用子程序,并在子程序中使用相应的寄存器来获取指针。
BP 寄存器:可以用于传递参数和局部变量。在子程序中,BP 寄存器通常用作栈帧指针,指向当前子程序的栈帧底部。主程序可以将参数存储在栈中,并将 BP 寄存器指向参数所在的栈位置,然后调用子程序,在子程序中使用 BP 寄存器来获取参数。
利用堆栈传递参数(常用)
Stack:可以用于传递多个参数和大型数据结构。主程序将参数依次压入栈中,并在调用子程序时将栈顶指针传递给子程序。在子程序中,可以使用栈指针来获取参数。
5、boot.asm中 org 07c00h作用,如果去掉,程序应该怎么修改?
org 07c00h 指令的作用是告诉编译器程序将会被加载到内存的哪个地址,即告诉编译器该程序的起始地址是 07c00h。通过这个指令,编译器将会生成相应的二进制文件,以便在程序被加载到内存中时正确地执行。
如果去掉 org 07c00h 指令,程序需要做出以下修改:
修改程序的入口地址。由于没有指定起始地址,编译器将会使用默认的起始地址。因此,程序需要将入口地址调整为默认的起始地址,通常是 0000h。
修改程序中的跳转地址。由于程序的起始地址发生了变化,程序中的跳转地址也需要做相应的修改,以便程序能够正确地执行。
修改(PPT):
修改boot1.asm
mov ax,cs
mov ds,ax
mov es,ax
call DispStr
jmp $
DispStr:
mov ax,BootMessage + 07c00h
mov bp,ax
mov cx,16
mov ax,01301h
mov bx,000ch
mov dl,0
int 10h
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0
dw 0xaa55
6、int 10h功能
在 NASM 中,int 10h 是一种 BIOS 中断,用于控制和管理屏幕的显示。int 10h 可以通过 AH 寄存器来指定要执行的功能号,以及通过其他寄存器来传递参数。以下是一些常用的功能号:
0x00:设置显示模式
0x02:设置光标位置
0x03:获取光标位置
0x05:清屏
0x06:滚屏
0x0e:在光标位置打印字符
0x0f:获取当前显示页面
以下是一些常用的寄存器和它们对应的参数:
AH:指定要执行的功能号
AL:传递字符或颜色信息
BH:传递页面号
CH、CL:传递光标位置的行号和列号
DH、DL:传递光标位置的行号和列号
7、boot.asm中times 510-( − - −$) db 0作用
在 Bootloader 的汇编代码中,times 510-($-$$) db 0 指令的作用是填充二进制文件的末尾,使得整个文件大小达到 512 字节。
在 x86 架构中,Bootloader 通常被放置在硬盘的第一个扇区,即第 0 磁头、第 0 磁道、第 1 扇区。这个扇区的大小是 512 字节。当计算机启动时,BIOS 会将这个扇区加载到内存的 0x7c00 地址处,并将控制权交给这个扇区的代码。
因此,在编写 Bootloader 时,必须确保生成的二进制文件大小为 512 字节。如果生成的二进制文件大小小于 512 字节,那么 BIOS 加载这个文件时可能会发生错误。因此,如果代码大小不足 512 字节,需要在文件末尾填充一些字节,使得文件大小达到 512 字节。
$:表示当前行的地址或偏移量。也就是说,在代码中使用$时,它会被替换为当前行的地址或偏移量。
$$:表示当前模块的起始地址或偏移量。也就是说,在代码中使用$$时,它会被替换为当前模块的起始地址或偏移量。
times 510-($-$$) db 0 指令的作用是在当前位置填充 0,使得当前位置到文件末尾的空间大小为 510 字节。这个指令使用了一个特殊符号 $,表示当前位置的地址,以及一个特殊符号 $$,表示当前段的起始地址。因此,$-$$ 表示当前位置距离段的起始地址的偏移量。510-($-$$) 表示需要填充的字节数,即文件末尾距离当前位置的偏移量。最后,db 0 表示要填充的字节内容为 0。
PPT上的解释
当BIOS自检结束后会根据启动选项设置去选择启动设备,即检测软盘的第0磁头第0磁道第1扇区,是否以数值0x55和0xaa两字节作为结尾。如果是,那么BIOS就认为这个扇区是一个引导扇区,进而把此扇区的数据复制到物理内存地址0x7c00处,随后将处理器的执行权移交给这段程序(跳转至0x7c00地址处执行)
8、bochsrc中各参数含义
megs:32
display_library: sdl2
floppya: 1_44=a.img, status=inserted
boot: floppy
* display_library:Bochs使用的GUI库
* megs:虚拟机内存大小 (MB)
* floppya:虚拟机外设,软盘为a.img文件
* boot:虚拟机启动方式,从软盘启动
* bochsrc与a.img以及boot.bin放在同一目录下
其中status是虚拟软盘镜像的状态,inserted的意思是已将虚拟软盘插入到虚拟软盘驱动器中。
9、boot.bin应该放在软盘的哪一个扇区?为什么
在将 Bootloader 代码写入软盘时,通常将生成的二进制文件 boot.bin 放在软盘的第一个扇区,即磁头 0、磁道 0、扇区 1 的位置。这是因为在 x86 架构的计算机中,BIOS 会在初始化硬件后,将软盘的第一个扇区加载到内存地址 0x7c00 处,并将控制权交给这个扇区的代码。因此,如果 Bootloader 的代码不在软盘的第一个扇区,BIOS 将无法正确加载并执行 Bootloader。
需要注意的是,软盘的第一个扇区除了 Bootloader 代码外,还需要存储一些引导扇区标识和分区表等信息。其中,引导扇区标识是 16 位的固定值 0xaa55,用于标识这个扇区是一个引导扇区。分区表则是用来记录硬盘的分区信息的。因此,在将 Bootloader 写入软盘的第一个扇区时,需要确保代码大小不超过 510 字节,以便为引导扇区标识和分区表留出 2 个字节的空间。
10、为什么不让boot程序直接加载内核,而是先加载Loader再加载内核?
处理器的实模式和保护模式切换问题:在操作系统启动过程中,处理器需要从实模式切换到保护模式。这个过程需要进行一些复杂的设置和初始化,而 Bootloader 通常只能以实模式运行。为了避免在 Bootloader 中处理这些复杂的操作,通常会编写一个 Loader 程序来完成这个任务。Loader 程序通常能够在实模式和保护模式之间进行平滑的切换,并负责加载内核。
内核的大小和复杂性:操作系统的内核通常比 Bootloader 更大和复杂,因此需要更复杂的加载和初始化过程。为了简化操作系统启动流程,通常会将内核划分为多个部分,每个部分都由 Loader 加载和初始化。
启动参数的传递:在启动操作系统时,通常需要将一些参数传递给内核,例如系统配置信息、启动选项、硬件信息等。为了实现这个功能,Bootloader 需要将这些参数传递给 Loader,然后 Loader 再将这些参数传递给内核。
因此,为了简化操作系统启动过程,通常会采用 Bootloader-Loader-Kernel 的架构。在这个架构中,Bootloader 负责加载 Loader,Loader 负责处理保护模式的初始化和加载内核的任务,内核则负责完成操作系统的启动和运行。这种架构能够有效地分离操作系统启动过程中的不同任务,并且使得整个启动过程更加简单和可维护。
书上的答案:
一个操作系统从开机到开始运行,大致经历“引导→加载内核入内存→跳入保护模式→开始执行内核”这样一个过程。
也就是说,在内核开始执行之前不但要加载内核,而且还有准备保护模式等一系列工作,如果全都交给引导扇区来做,512字节很可能是不够用的,所以,不妨把这个过程交给另外的模块来完成,我们把这个模块叫做Loader。
引导扇区负责把 Loader加载入内存并且把控制权交给它,其他工作放心地交给Loader来做,因为它没有512字节的限制,将会灵活得多。
11、Loader作用?
启动流程:
boot.bin --> loader.bin ---> loader.bin --> kernel.bin -->kernel
跳⼊保护模式
启动内存分⻚
从kernel.bin中读取内核,并放⼊内存,然后跳转到内核所在 的开始地址,运⾏内核
跟boot类似,使⽤汇编直接在软盘下搜索kernel.bin
但是,不能把整个kernel.bin放在内存,⽽是要以ELF⽂件的格式读取并提取代码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hSSdGecC-1683622650620)(./img/image-20230406143738643.png)]
12、Kernel作用?
在计算机操作系统中,Kernel(内核)是操作系统的核心部分,负责管理计算机的硬件和软件资源,并提供各种系统服务和功能。Kernel 的主要作用如下:
硬件管理:Kernel 负责管理计算机的硬件资源,包括 CPU、内存、磁盘、网络设备等。Kernel 通过硬件驱动程序与硬件交互,控制硬件的访问和使用,保证各个设备的协调工作,提供稳定和高效的系统性能。
进程管理:Kernel 负责管理计算机中运行的进程,包括进程的创建、销毁、调度、通信等。Kernel 通过进程调度算法,决定哪些进程可以获得 CPU 的使用权,保证系统资源的合理分配和利用。
内存管理:Kernel 负责管理计算机中的内存资源,包括内存的分配、释放、保护、映射等。Kernel 通过虚拟内存管理机制,将物理内存划分为若干个虚拟内存页,实现进程之间的隔离和内存共享,提高系统的稳定性和安全性。
文件系统管理:Kernel 负责管理计算机中的文件系统,包括文件的创建、读取、写入、删除等。Kernel 通过文件系统驱动程序,实现对不同文件系统类型的支持,提供统一的文件系统接口和访问权限管理。
系统服务和功能:Kernel 提供各种系统服务和功能,包括网络通信、安全管理、设备驱动程序、系统调用等。Kernel 通过这些服务和功能,为用户和应用程序提供便捷、安全和高效的系统环境。
总之,Kernel 是操作系统的核心部分,负责管理计算机的各种资源和功能,为用户和应用程序提供稳定、高效、安全和便捷的系统环境。
Lab1-1
安装Bochs虚拟机
sudo apt-get install bochs
装载操作系统的存储设备:软盘
bximage
1
fd:软盘映像
1.44M:映像大小 1.44MB
a.img:映像名称(在往映像中写入操作系统时以及配置计算机时会用到)
bximage工具不仅可以创建虚拟磁盘镜像文件,还可以查看虚拟磁盘镜像文件的硬件配置信息,如镜像类型、磁盘容量、磁头数、磁道数以及扇区数等。
安装NASM汇编
sudo apt-get install nasm
创建boot.asm
org 07c00h ; 告诉编译器程序加载到7c00处
mov ax, cs
mov ds, ax
mov es, ax
call DispStr ; 调用显示字符串例程
jmp $ ; 无限循环
DispStr:
mov ax, BootMessage
mov bp, ax ; ES:BP = 串地址
mov cx, 16 ; CX = 串长度
mov ax, 01301h ; AH = 13, AL = 01h
mov bx, 000ch ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
mov dl, 0
int 10h ; 10h 号中断
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好
为512字节
dw 0xaa55 ; 结束标志
使用NASM来汇编boot.asm生成“操作系统”(boot.bin)的二进制代码
nasm boot.asm –o boot.bin
- -o output
- 编译结束后,便可将生成的二进制程序文件写入到虚拟软盘镜像文件内 请注意,此处说的是写 入到虚拟软盘镜像文件内,而不是复制到虚拟软盘镜像文件内
使用dd命令将boot.bin写入软盘中
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
- if:代表输入文件
- of:代表输出设备
- bs:代表一个扇区大小 512字节
- count:代表扇区数
- conv:代表不作其它处理 规定在写入数据后不截断(改变)输出文件的尺寸大小
- 使用dd命令把引导程序强制写入到虚拟软盘的固定扇区中,这种强制写入固定扇区的方法能够跳过文件系统的管理与控制,转而直接操作磁盘扇区
Bochs的配置文件
vi bochsrc
megs:32
display_library: sdl2
floppya: 1_44=a.img, status=inserted
boot: floppy
- display_library:Bochs使用的GUI库
- megs:虚拟机内存大小 (MB)
- floppya:虚拟机外设,软盘为a.img文件
- boot:虚拟机启动方式,从软盘启动
- bochsrc与a.img以及boot.bin放在同一目录下
启动虚拟机
bochs -f bochsrc
-
-f指定配置文件
-
如果提示没有sdl,则需要用命令行下载sdl
sudo apt-get install nasm bochs bochs-sdl
-
默认第6开始模拟
-
只要在终端命令行输入字符串 c/cont/continue 中的任意一种,即可使虚拟机运行
-
显示出hello OS world
org 07c00h的作用
org是伪指令,伪指令是指,不生成对应的二进制指令,只是汇编器使用的。也就是说, boot.bin文件里面,没有07c00h这个东西,BIOS不是因为这条指令 才把代码放在07c00h的。 • mov这种指令,就会生成二进制代码,可以直接告诉CPU该做什么
告诉汇编器,当前这段代码会放在 07c00h处。所以,如果之后遇到需要绝对寻址的指令,那么绝 对地址就是07c00h加上相对地址
在第一行加上org 07c00h只是让编译器从相对地址07c00h处 开始编译第一条指令,相对地址被编译加载后就正好和绝对地址吻合
如果需要反汇编
ndisasm boot1.bin -o 0
指令集合
nasm boot.asm -o boot.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
bochs -f bochsrc
c
Lab1.2
Loader
一个操作系统从开机到开始运行,大致经历“引导→加载内核入内存→跳入保护模式→开始执行内核”这样一个过程。
也就是说,在内核开始执行之前不但要加载内核,而且还有准备保护模式等一系列工作,如果全都交给引导扇区来做,512字节很可能是不够用的,所以,不妨把这个过程交给另外的模块来完成,我们把这个模块叫做Loader。
引导扇区负责把 Loader加载入内存并且把控制权交给它,其他工作放心地交给Loader来做,因为它没有512字节的限制,将会灵活得多。
FAT12
- 扇区(Sector):磁盘上的最小数据单元。
- 簇(Cluster):一个或多个扇区。
- 分区(Partition):通常指整个文件系统。
簇
扇区结构
- 引导扇区:扇区号0,BPB(BIOS parameter Block),以BPB_开头的域属于BPB,以BS_开头的域不属于BPB,只是引导扇区一部分
- 根目录区:根目录区位于第二个FAT表之后,开始的扇区号为19,它由若干个目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt个。由于根目录区的大小是依赖于BPB_RootEntCnt的,所以长度不固定。根目录区中的每一个条目占用32字节,它的格式如表所示。
最后剩下最重要的信息DIR_FstClus,即文件开始簇号,它告诉我们文件存放在磁盘的什么位置,从而让我们可以找到它。由于一簇只包含一个扇区,所以简化了计算过程,而且下文中说到簇的地方,你也可以将它替换成“扇区”。需要注意的是,数据区的第一个簇的簇号是2,而不是0或者1。
FAT2可看做是FAT1的备份
指令集合
nasm boot.asm -o boot.bin
nasm loader.asm -o loader.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
sudo mount ./a.img /media/ -t vfat -o loop
sudo cp loader.bin /media/
sync
sudo umount /media/
bochs -f bochsrc
c
指令解释
- ‘-t vfat’ 表示要使用的文件系统类型为 VFAT
- ‘-o loop’ 表示使用回环设备挂载,即将文件作为一个磁盘镜像挂载。
- ‘cp’ 命令用于复制文件,即将文件复制到 “/media/” 目录中。
- ‘sync’ 是一个 Linux/Unix 操作系统中的命令,用于将缓存中的数据写入磁盘中,以保证数据的持久化,并且刷新文件缓存和磁盘缓存中的数据,以确保数据的一致性。在执行文件系统操作前,可以使用 ‘sync’ 命令来确保数据已经被写入磁盘,防止因为掉电等异常情况导致数据的丢失或损坏。通常,在进行磁盘备份和还原操作时,也会使用 ‘sync’ 指令来刷写缓存,确保数据的完整性。
- umount是解挂载
linux指令补充
-
‘mount’ 命令用于将文件系统挂载到指定的目录中,从而使其在该目录下可见并访问。它的工作原理如下:
-
- 检查要挂载的设备或文件系统是否已经格式化并准备好被挂载。
-
- 指定挂载的目录,即创建一个目录作为挂载点或者使用一个已经存在的目录作为挂载点。
-
- 使用 ‘mount’ 命令将设备或文件系统挂载到指定的目录之下,并且可以指定一些可选的选项来定义挂载的行为。
-
- 操作完成后,可以使用 ‘umount’ 命令将设备或文件系统从挂载点卸载。
-
- 挂载点上的数据可以被其他程序或用户所访问和操作。
-
例如,执行以下命令可以将文件系统 “/dev/sdb1” 挂载到目录 “/mnt” 中:
sudo mount /dev/sdb1 /mnt
通过执行该命令,就可以访问 “/dev/sdb1” 中的数据,而这些数据在 “/mnt” 目录下可见和可访问。 -
-
循环挂载(loop mount)是将一个普通文件作为块设备挂载为文件系统的方法,通过这种方式将文件作为虚拟磁盘来使用。 循环挂载常常用于安装操作系统镜像、读取磁盘映像、挂载加密文件系统等场景。这种方式不需要实际物理设备,只需要一个文件,可以避免使用磁盘空间,提供了更为灵活的数据存储方式。 执行循环挂载的命令格式如下:
sudo mount /path/to/file.iso /mnt -o loop
其中,‘/path/to/file.iso’ 为要挂载的文件的路径,‘/mnt’ 为挂载目标目录的路径,‘-o loop’ 选项表示使用循环挂载方式。 执行上述命令后,文件系统将被挂载到 ‘/mnt’ 目录下,用户即可通过该目录进行访问和操作,这种方式可以视为将磁盘镜像或者其他文件作为 “虚拟磁盘” 来使用,访问的文件数据源也就是磁盘镜像或者其他文件。
LAB-1.2
加载loader入内存
要加载一个文件入内存的话,免不了要读软盘,这时候就用到BIOS中断int 13h。
BIOS 中断int 13h的用法
中断号 | 寄存器 | 作用 |
---|---|---|
13h | ah=00h dl-驱动器号(0表示A盘) | 复位软驱 |
ah=02h al=要读扇区数 ch=柱面(磁道)号 cl=起始扇区号 dh=磁头号 d1=驱动器号(0表示A盘) es :bx→数据缓冲区 | 从磁盘将数据读入es :bx指向的缓冲区中 |
扇区号/每磁道扇区数(18)---->商Q \ 余数R→起始扇区号=R+1
商Q---->柱面号=Q>>1 磁头号=Q&1
boot.asm 64bitOS
org 0x7c00
BaseOfStack equ 0x7c00
;这段程序中的代码BaseOfLoader equ 0x1000和offsetOfLoader equ 0x00组合成了Loader程序的起始物理地址,
;这个组合必须经过实模式的地址变换公式才能生成物理地址,即BaseOfLoader<<4 + offsetOfLoader = 0x10000。
BaseOfLoader equ 0x1000
OffsetOfLoader equ 0x00
RootDirSectors equ 14
SectorNumOfRootDirStart equ 19
SectorNumOfFAT1Start equ 1
SectorBalance equ 17
jmp short Label_Start
nop
BS_OEMName db 'MINEboot'
BPB_BytesPerSec dw 512
BPB_SecPerClus db 1
BPB_RsvdSecCnt dw 1
BPB_NumFATs db 2
BPB_RootEntCnt dw 224
BPB_TotSec16 dw 2880
BPB_Media db 0xf0
BPB_FATSz16 dw 9
BPB_SecPerTrk dw 18
BPB_NumHeads dw 2
BPB_HiddSec dd 0
BPB_TotSec32 dd 0
BS_DrvNum db 0
BS_Reserved1 db 0
BS_BootSig db 0x29
BS_VolID dd 0
BS_VolLab db 'boot loader'
BS_FileSysType db 'FAT12 '
Label_Start:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
;======= clear screen
mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 0184fh
int 10h
;======= set focus
mov ax, 0200h
mov bx, 0000h
mov dx, 0000h
int 10h
;======= display on screen : Start Booting......
mov ax, 1301h
mov bx, 0002h ;黑底绿字
mov dx, 0402h ;空4⾏2列
mov cx, 9
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartBootMessage
int 10h
;======= reset floppy
xor ah, ah
xor dl, dl
int 13h
;======= search loader.bin
;通过这段代码能够从根目录中搜索出引导加载程序(文件名为loader.bin )。
;一旦发现完全匹配的字符串,则跳转到Label_Filelame_Found处执行
;如果没有找到,那么就执行其后的Label_No_LoaderBin模块
;进而在屏幕上显示提示信息,通知用户引导加载程序不存在。
mov word [SectorNo], SectorNumOfRootDirStart
Lable_Search_In_Root_Dir_Begin:
;在程序执行初期,程序会先保存根目录的起始扇区号,并依据根目录占用磁盘扇区数来确定需要搜索的扇区数,并从根目录中读入一个扇区的数据到缓冲区
;接下来,遍历读入缓冲区中的每个目录项,寻找与目标文件名字符串("LOADER BIN",0)相匹配的目录项,
;其中DX寄存器记录着每个扇区可容纳的目录项个数(512/32=16=Ox10 ),
;CX寄存器记录着目录项的文件名长度(文件名长度为11B,包括文件名和扩展名,但不包含分隔符“”)。
cmp word [RootDirSizeForLoop], 0
jz Label_No_LoaderBin
dec word [RootDirSizeForLoop]
mov ax, 00h
mov es, ax
mov bx, 8000h
mov ax, [SectorNo]
mov cl, 1
call Func_ReadOneSector
mov si, LoaderFileName
mov di, 8000h
cld
mov dx, 10h
Label_Search_For_LoaderBin:
cmp dx, 0
jz Label_Goto_Next_Sector_In_Root_Dir
dec dx
mov cx, 11
Label_Cmp_FileName:
;在比对每个目录项文件名的过程中,使用了汇编指令LODSB,该命令的加载方向与DF标志位有关,
;因此在使用此命令时需用CLD指令清DF标志位。
cmp cx, 0
jz Label_FileName_Found
dec cx
lodsb
cmp al, byte [es:di]
jz Label_Go_On
jmp Label_Different
Label_Go_On:
inc di
jmp Label_Cmp_FileName
Label_Different:
and di, 0ffe0h
add di, 20h
mov si, LoaderFileName
jmp Label_Search_For_LoaderBin
Label_Goto_Next_Sector_In_Root_Dir:
add word [SectorNo], 1
jmp Lable_Search_In_Root_Dir_Begin
;======= display on screen : ERROR:No LOADER Found
Label_No_LoaderBin:
mov ax, 1301h
mov bx, 008ch
mov dx, 0100h
mov cx, 21
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, NoLoaderMessage
int 10h
jmp $
;======= found loader.bin name in root director struct
;在Label_FileName_Found模块中,程序会先取得目录项DIR_FstClus字段的数值,
;并通过配置ES寄存器和BX寄存器来指定loader.bin程序在内存中的起始地址,
Label_FileName_Found:
mov ax, RootDirSectors
and di, 0ffe0h
add di, 01ah
mov cx, word [es:di]
push cx
add cx, ax
add cx, SectorBalance
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, cx
;再根据loader.bin程序的起始簇号计算出其对应的扇区号。
;接着,每读入一个扇区的数据就通过Func_GetFATEntry模块取得下一个FAT表项
;并跳转至Label_Go_On_Loading_File处继续读入下一个簇的数据,
;循环,直至Func_GetFATEntry模块返回的FAT表项值是offfh为止。
Label_Go_On_Loading_File:
push ax
push bx
mov ah, 0eh
mov al, '.'
mov bl, 0fh
int 10h
pop bx
pop ax
mov cl, 1
call Func_ReadOneSector
pop ax
call Func_GetFATEntry
cmp ax, 0fffh
jz Label_File_Loaded
push ax
mov dx, RootDirSectors
add ax, dx
add ax, SectorBalance
add bx, [BPB_BytesPerSec]
jmp Label_Go_On_Loading_File
;当loader.bin文件的数据全部读取到内存后,跳转至Label_File_Loaded处准备执行loader.bin程序。
Label_File_Loaded:
jmp BaseOfLoader:OffsetOfLoader
;======= read one sector from floppy
;代码中的Func_ReadOnesector模块负责实现软盘读取功能,
;它借助BIOS中断服务程序INT 13h的主功能号AH=02h实现软盘扇区的读取操作,该中断服务程序的各寄存器参数说明如下。
;INT 13h,AH=02h功能:读取磁盘扇区。
;AL=读入的扇区数(必须非0 );
;CH=磁道号(柱面号)的低8位;
;CL=扇区号1~63 ( bit 0~5),磁道号(柱面号)的高2位( bit 6~7,只对硬盘有效);DH=磁头号;
;DL=驱动器号(如果操作的是硬盘驱动器,bit 7必须被置位);ES:BX=>数据缓冲区。
Func_ReadOneSector:
push bp
mov bp, sp
sub esp, 2
mov byte [bp - 2], cl ;模块Func_ReadOneSector在读取软盘之前,会先保存栈帧寄存器和栈寄存器的数值,
push bx ;从栈中开辟两个字节的存储空间(将栈指针向下移动两个字节)
mov bl, [BPB_SecPerTrk] ;由于此时代码bp - 2与ESP寄存器均指向同一内存地址,所以CL寄存器的值就保存在刚开辟的栈空间里。
div bl ;而后,使用AX寄存器(待读取的磁盘起始扇区号)除以BL寄存器(每磁道扇区数),
inc ah ;计算出目标磁道号(商:AL寄存器)和目标磁道内的起始扇区号(余数:AH寄存器),考虑到磁道内的起始扇区号从1开始计数,故此将余数值加1,即inc ah。
mov cl, ah ;紧接着,再按照公式(3-1)计算出磁道号(也叫柱面号)与磁头号,将计算结果保存在对应寄存器内。
mov dh, al
shr al, 1
mov ch, al
and dh, 1
pop bx
mov dl, [BS_DrvNum]
Label_Go_On_Reading:
mov ah, 2
mov al, byte [bp - 2]
int 13h ;最后,执行INT 13h中断服务程序从软盘扇区读取数据到内存中,当数据读取成功(CF标志位被复位)后恢复调用现场。
jc Label_Go_On_Reading
add esp, 2
pop bp
ret
;======= get FAT Entry
;这段程序首先会保存FAT表项号,并将奇偶标志变量(变量[odd] )置0。
;因为每个FAT表项占1.5 B,所以将FAT表项乘以3除以2(扩大1.5倍)来判读余数的奇偶性并保存在[odd]中(奇数为1,偶数为0 ),
;再将计算结果除以每扇区字节数,商值为FAT表项的偏移扇区号,余数值为FAT表项在扇区中的偏移位置。
Func_GetFATEntry:
push es
push bx
push ax
mov ax, 00
mov es, ax
pop ax
mov byte [Odd], 0
mov bx, 3
mul bx
mov bx, 2
div bx
cmp dx, 0
jz Label_Even
mov byte [Odd], 1
;接着,通过Func_ReadOnesector模块连续读入两个扇区的数据,此举的目的是为了解决FAT表项横跨两个扇区的问题。
;最后,根据奇偶标志变量进一步处理奇偶项错位问题,即奇数项向右移动4位。
Label_Even:
xor dx, dx
mov bx, [BPB_BytesPerSec]
div bx
push dx
mov bx, 8000h
add ax, SectorNumOfFAT1Start
mov cl, 2
call Func_ReadOneSector
pop dx
add bx, dx
mov ax, [es:bx]
cmp byte [Odd], 1
jnz Label_Even_2
shr ax, 4
Label_Even_2:
and ax, 0fffh
pop bx
pop es
ret
;======= tmp variable
RootDirSizeForLoop dw RootDirSectors
SectorNo dw 0
Odd db 0
;======= display messages
StartBootMessage: db "Hello OS!"
NoLoaderMessage: db "ERROR:No LOADER Found"
LoaderFileName: db "LOADER BIN",0
;======= fill zero until whole sector
times 510 - ($ - $$) db 0
dw 0xaa55
loader.asm 64bitOS
;这段代码主要的功能是将堆栈指针(SP)的初始值设置为0x7c00,这是BIOS加载引导扇区的位置,以便接下来的代码可以顺利地运行。此外,它还将数据和附加段寄存器的值设置为代码段寄存器的值,以便程序可以访问和修改内存中的数据。
org 10000h
mov ax, cs
mov ds, ax
mov es, ax
mov ax, 0x00
mov ss, ax
mov sp, 0x7c00
;======= display on screen : Start Loader......
mov ax, 1301h
mov bx, 004fh ;红底白字
mov dx, 0810h ;row 8,col 16
mov cx, 13
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartLoaderMessage
int 10h
jmp $
;======= display messages
StartLoaderMessage: db "Hello Loader!"
Lab-1.3
指令集合
nasm -felf32 -g main.asm -o main.o
ld -g -melf_i386 main.o -o main
./main
gdb main
指令解释
[section .data] ; 数据段
msg1 db '请输入第一个数:' ; 提示信息1
msg2 db '请输入第二个数:' ; 提示信息2
msg3 db '商为:' ; 提示信息3
msg4 db '余数为:' ; 提示信息4
msg5 db '长度为:' ;`
zero db '0'
ediis db ' edi is:'
yushuBigger db 'yushuBigger '
chushuBigger db 'chushuBigger '
equalmesg db 'equal '
invalid db "invalid"
invalid_len equ $-invalid ;由当前位置标记$减去output所在位置计算而得
[section .bss] ; 未初始化数据段 .bss 是指 block started by symbol 的缩写,是一种专门存放未初始化数据的数据段。在该段中,所有变量都被初始化为0或空。
num1 resb 105 ; 存储第一个数 resb 指令用于为变量分配指定长度的连续空间,但不会将该空间中的任何位置初始化为具体的值。
num2 resb 105 ; 存储第二个数
ans resb 105 ; 存储商
num1_len resd 1 ; 第一个数的长度
num2_len resd 1 ; 第二个数的长度
pointer resd 1 ; edi value
weishu resd 1 ;judge whether num1 = num2
ans_len resd 1
[section .text] ; 代码段
global _start
_start:
; 第一个数输入
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg1 ; 指向要输出的信息的指针
mov edx, 22 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
; 读取第一个数并存储
mov eax, 3 ; 系统调用号
mov ebx, 0 ; 输入从标准输入的文件句柄(stdin)
mov ecx, num1 ; 存储输入的缓冲区
mov edx, 105 ; 存储缓冲区的大小
int 0x80 ; 调用系统中断,读取输入的数
; 第二个数输入
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg2 ; 指向要输出的信息的指针
mov edx, 22 ; 要输出的字符数
int 0x80 ; 调用系统中断,输出信息
; 读取第二个数并存储
mov eax, 3 ; 系统调用号
mov ebx, 0 ; 输入从标准输入的文件句柄(stdin)
mov ecx, num2 ; 存储输入的缓冲区
mov edx, 105 ; 存储缓冲区的大小
int 0x80 ; 调用系统中断,读取输入的数
; 确定第一个数和第二个数的长度
mov eax, num1 ;
call strlen
mov dword[num1_len], eax ; 将eax的值移入num_len1变量中
mov eax, num2 ;
call strlen
mov dword[num2_len], eax ; 将eax的值移入num_len2变量中
;计算商的位数
mov ebx, dword[num1_len]
cmp ebx, dword[num2_len] ;len1 < len2
jb div.Bbigger
sub ebx, dword[num2_len]
inc ebx
mov dword[ans_len], ebx
call _initialize
; 两个数相除处理
div:
cmp dword[num1_len], 0 ; 检查输入是否合法(num1的长度为0则为非法)
je .print_invalid
cmp dword[num2_len], 0 ; 检查输入是否合法(num2的长度为0则为非法)
je .print_invalid
cmp byte[num2], '0' ; 检查输入是否合法(num2的第一个字符不能为0)
je .print_invalid ; 如果不合法则输出错误信息并退出程序
mov ebx, dword[num1_len] ; bl = len1
cmp dword[num2_len], ebx ;if (len1 < len2) -> compare destination source ;无符号大于 len2 > len1
ja .Bbigger
jmp .Abigger
mov cl, 0 ;cl = 0 loop->i
.Abigger:
call _CompareBit ;;按位比较被除数和除数的大小
cmp esi, 1 ;num1 >= num2
ja .Bbigger ;num1_len = num2_len && num1 < num2
;;num1 > num2
;;for loop sub
mov edi, 0 ;edi = 0 ---> i
.loopsub:
;;循环减法ans_len - 1次 ebx为i
;;edi中存放着这是第几次外循环,总计ans_len次循环, 0-ans_len - 1
inc edi ;edi++ start from 1
cmp edi, dword[ans_len] ;
ja .printAns ;edi > ans_len
call _subBig ;sub
jmp .loopsub ;最外层的for
.Bbigger:
; 商为0
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg3 ; 指向要输出的信息的指针
mov edx, 7 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, zero ; 指向要输出的信息的指针
mov edx, 1 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
;余数为被除数
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg4 ; 指向要输出的信息的指针
mov edx, 10 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, num1 ; 指向要输出的信息的指针
mov edx, 105 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
jmp .exit
.printAns:
; 商为ans
mov eax, 4
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg3
mov edx, 7
int 0x80
;remove 0
mov edi, dword[ans_len]
mov esi, ans
cmp edi, 0
je .print0
.loopremove:
cmp byte[esi], '0'
jne .printnowans
cmp edi, 1
je .printnowans
inc esi
dec edi
jmp .loopremove
.printnowans:
mov eax, 4
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, esi
mov edx, edi
int 0x80
;余数为被除数
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg4 ; 指向要输出的信息的指针
mov edx, 10 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
;remove 0
mov edi, dword[num1_len]
mov esi, num1
.loopremoveyushu:
cmp byte[esi], '0'
jne .printnowyushu
cmp edi, 1
je .printnowyushu
inc esi
dec edi
jmp .loopremoveyushu
.printnowyushu:
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, esi ; 指向要输出的信息的指针
mov edx, edi ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
jmp .exit
.print0:
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, zero ; 指向要输出的信息的指针
mov edx, 1 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
.exit:
mov eax, 1
mov ebx, 0
int 80h
mov edx, 1
mov ecx, [0ah]
mov ebx, 1
mov eax, 4
int 80h
; invalid input
.print_invalid:
mov eax, 4
mov ebx, 1
mov ecx, invalid
mov edx, invalid_len
int 80h
jmp .exit
_printtest:
push edx
push ecx
push ebx
push eax
; 商为ans
mov eax, 4
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg3
mov edx, 7
int 0x80
mov eax, 4
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, ans
mov edx, dword[ans_len]
int 0x80
;被除数
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, msg4 ; 指向要输出的信息的指针
mov edx, 10 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, num1 ; 指向要输出的信息的指针
mov edx, 105 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
;edi
add edi, '0'
mov dword[pointer], edi
sub edi, '0'
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, ediis ; 指向要输出的信息的指针
mov edx, 8 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
mov eax, 4 ; 系统调用号
mov ebx, 1 ; 输出到标准输出的文件句柄(stdout)
mov ecx, pointer ; 指向要输出的信息的指针
mov edx, 1 ; 要输出的字符数 UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
int 0x80 ; 调用系统中断,输出信息
.End:
pop eax
pop ebx
pop ecx
pop edx
ret
strlen:
push ebx
mov ebx, eax
nextchar:
cmp byte [eax], 0
jz finished
inc eax
jmp nextchar
finished:
sub eax, ebx ; eax = eax - ebx(origin eax)
sub eax, 1
pop ebx
ret
_initialize:
push edx
push ecx
push ebx
push eax
mov eax, dword[ans_len]
mov ebx, ans
mov ecx, 0
.initAns:
mov byte[ebx], '0';ans[i] = '0'
inc ebx ;ebx++ --> i++
inc ecx ;ecx++
cmp ecx, eax ;ecx != ans_len
jne .initAns ;
.End:
pop eax
pop ebx
pop ecx
pop edx
ret
_CompareBit:
;按位比较num1和num2的大小,num1已经确定和num2同样长度
push edx
push ecx
push ebx
push eax
mov ebx, dword[num2_len] ; ebx = len2
cmp dword[num1_len], ebx ;if (len1 > len2) -> set
ja .set1bigger
mov eax, num1 ;eax = &num1
mov ebx, num2 ;ebx = &num2
mov ecx, 0 ;ecx = 0 --> i
mov esi, 0
.Compareloop:
inc ecx
cmp ecx, dword[num2_len]
jnb .set1bigger ; ecx >= num1_len
mov dl, byte[ebx] ;bl = num2[i]
cmp byte[eax], dl
jb .set2bigger ;num1[i] < num2[i]
inc eax
inc ebx
jmp .Compareloop
.set2bigger:
mov esi, 2
jmp .CompareBitEnd
.set1bigger:
mov esi, 1
jmp .CompareBitEnd
.CompareBitEnd:
pop eax
pop ebx
pop ecx
pop edx
ret
_subBig:
;;只负sub,call则默认被除数比除数大,一定可以sub,并且直接操作被除数num1
;;edi中存放着这是第几次外循环,总计ans_len次循环, 0-ans_len - 1
;;外call for edi用于计算位数,右移借位即是10*上一位+这一位,edi同时指示rem中位数
;;;call后loop负责将这一次可减的减到不可减为止
push edx
push ecx
push ebx
push eax
push edi
mov eax, num1 ;eax = &num1
mov ebx, num2 ;ebx = &num2
mov ecx, 0 ;ecx用于比较
mov edx, 0 ;edx = 0 edx---->j
;esi为标志位flag
;如果函数compareInSub发现当前无法再sub就置flag = 1
;esi == 2表示不需要借位, esi == 3表示需要借位
mov esi, 0
add eax,edi ;eax = &num1[i]
dec eax ;eax = &num1[i - 1]
.subLoop:
call _compareInSub ;比较能否sub,是否被除数需要左移借位
cmp esi, 2
;sub
jna .subLoopBig ;num1从edi(i)开始的前len2位比num2的大
ja .subLoopSmallBorrow ;num1从edi(i)开始的前len2位比num2的xiao
.subLoopBig:
call _compareInSub
cmp esi, 2 ;3需要借位
ja .subLoopSmallBorrow
call _sub
jmp .subLoopBig
.subLoopSmallBorrow:
cmp edi, 1
je .subEnd
mov ebx, num1
add ebx, edi
dec ebx ;&num1[i] = ebx
sub ebx, 1 ;ebx = &num1[i - 1]
cmp byte[ebx], '0'
jna .subEnd ;num1[i - 1] <= '0'
mov al, byte[ebx] ;al = num1[i - 1] + '0'
sub al, 48 ; borrow all from num1[i - 1]
mov byte[ebx], 48 ;num1[i - 1] = 0
mov ecx, 10 ;ecx = 10
inc ebx ;ebx = &num1[i]
.mulloop:
add byte[ebx], al ;num1[i] += al
dec ecx ;ecx--
cmp ecx, 0 ;ecx != 0
je _subBig.subLoopBig ;call not need borrowed
jmp .mulloop
.subEnd:
jmp .End
.End:
pop edi
pop eax
pop ebx
pop ecx
pop edx
ret
_sub:
push edx
push ecx
push ebx
push eax
push edi
mov eax, num1 ;eax = &num1
mov ebx, num2 ;ebx = &num2
add eax, edi
dec eax ;eax = &num1[i - 1] ebx = &num2[0]
add eax, dword[num2_len];eax = &num1[i - 1 + num2_len]
sub eax, 1 ;eax = &num1[i - 1 + num2_len - 1]
add ebx, dword[num2_len];ebx = &num2[num2_len]
sub ebx, 1 ;ebx = &num2[num2_len -1]
mov ecx, 0 ;count
.subByteLoop:
mov dl, byte[eax] ;dl = num1[i]
cmp dl, byte[ebx]
jb .subborrow ;num1[i] < num2[j]
add dl, '0' ;dl(num1[i]) -> char-> dl 保存的时候也需要以ASCII码
sub dl, byte[ebx] ;dl(num1[i]) -= num2[j]
mov byte[eax], dl ;num1[i] = dl
inc ecx ;count++
dec eax ;eax = &num1[i--]
dec ebx ;ebx = &num2[j--]
cmp ecx, dword[num2_len]
jnb .End ;ecx = count = num2_len
jmp .subByteLoop
.subborrow:
add dl, 10 ;borrow 10
sub dl, byte[ebx] ;dl(num1[i]) -= num2[j]
add dl, '0'
mov byte[eax], dl ;num1[i] = dl
inc ecx ;count++
dec eax ;eax = &num1[i--]
dec byte[eax] ;num1[i--]-- :borrowed
dec ebx ;ebx = &num2[j--]
cmp ecx, dword[num2_len]
jnb .End ;ecx = count = num2_len
jmp .subByteLoop
.End:
mov dl, 00h ;dl = 0
mov edx, ans ;edx = &ans[0]
add edx, edi ;edx = &ans[edi]
sub edx, 1 ;edx = &ans[edi - 1]
inc byte[edx] ;ans[edi - 1]++ '0'++
pop edi
pop eax
pop ebx
pop ecx
pop edx
ret
_compareInSub:
push edx
push ecx
push ebx
push eax
push edi
mov eax, num1 ;eax = &num1
mov ebx, num2 ;ebx = &num2
mov ecx, 0 ;ecx用于比较
mov edx, 0 ;edx = 0 weishu---->j
;esi为标志位flag
;如果函数compareInSub发现当前无法再sub就置flag = 1
;esi == 2表示不需要借位, esi == 3表示需要借位
mov esi, 0
add eax, edi ;eax = &num1[i]
dec eax ;eax = &num1[i- 1]
mov byte[weishu], 0 ;weishu = 0
.Loop:
mov cl, byte[num2_len] ;cl = len2
cmp byte[weishu], cl ;weishu is for num1 == num2 ->outloop
je .equal
mov cl, byte[ebx] ;cl = ebx = num2[j]
cmp byte[eax], cl
ja .num1Bigger ;num1[i + j] > num2[j]
jb .num2Bigger ;num1[i + j] < num2[j]
inc eax ;i++
inc ebx ;j++
inc byte[weishu] ;weishu++
jmp .Loop
.num1Bigger:
mov esi, 2
jmp .End
.num2Bigger:
;需要借位
mov esi, 3
jmp .End
.equal:
mov esi, 1
jmp .End
.End:
pop edi
pop eax
pop ebx
pop ecx
pop edx
ret
= 0
mov edx, ans ;edx = &ans[0]
add edx, edi ;edx = &ans[edi]
sub edx, 1 ;edx = &ans[edi - 1]
inc byte[edx] ;ans[edi - 1]++ ‘0’++
pop edi
pop eax
pop ebx
pop ecx
pop edx
ret
_compareInSub:
push edx
push ecx
push ebx
push eax
push edi
mov eax, num1 ;eax = &num1
mov ebx, num2 ;ebx = &num2
mov ecx, 0 ;ecx用于比较
mov edx, 0 ;edx = 0 weishu---->j
;esi为标志位flag
;如果函数compareInSub发现当前无法再sub就置flag = 1
;esi == 2表示不需要借位, esi == 3表示需要借位
mov esi, 0
add eax, edi ;eax = &num1[i]
dec eax ;eax = &num1[i- 1]
mov byte[weishu], 0 ;weishu = 0
.Loop:
mov cl, byte[num2_len] ;cl = len2
cmp byte[weishu], cl ;weishu is for num1 == num2 ->outloop
je .equal
mov cl, byte[ebx] ;cl = ebx = num2[j]
cmp byte[eax], cl
ja .num1Bigger ;num1[i + j] > num2[j]
jb .num2Bigger ;num1[i + j] < num2[j]
inc eax ;i++
inc ebx ;j++
inc byte[weishu] ;weishu++
jmp .Loop
.num1Bigger:
mov esi, 2
jmp .End
.num2Bigger:
;需要借位
mov esi, 3
jmp .End
.equal:
mov esi, 1
jmp .End
.End:
pop edi
pop eax
pop ebx
pop ecx
pop edx
ret