Linux内核完全注释 阅读笔记:3.1、as86汇编器
By: Ailson Jack
Date: 2018.09.05
个人博客: http://www.only2fire.com/
本文在我博客的地址是:http://www.only2fire.com/archives/77.html,排版更好,便于学习,也可以去我博客逛逛,兴许有你想要的内容呢。
欢迎大家访问我博客中的文章,对文章的修改、更新,以及相关主题文章的发布,都会比我的****博客要快些 ^_^。
这里是这篇文章涉及到的一些代码和工具,各位请自行下载吧。文件下载地址:**:。
在Linux 0.1.x系统中使用了两种汇编器(Assembler)。一种是能产生16位代码的as86汇编器,使用配套的ld86链接器;另一种是GNU的汇编器as,使用GNU ld链接器来链接产生的目标文件。
as86和ld86是由MINIX-386的主要开发者之一Bruce Evans编写的Intel 8086、80386汇编编译程序和链接程序。Linux系统仅用它来创建16位的启动引导扇区程序boot/bootsect.s和实模式下初始设置程序boot/setup.s的二进制执行代码。
由于Linux系统仅使用as86和ld86编译和链接上面提到的两个16位汇编程序bootsect.s和setup.s,因此这里仅介绍这两个程序中用到的一些汇编程序语法和汇编命令的作用和用途。
1、as86汇编语言语法
汇编器专门用来把低级汇编语言程序编译成含机器码的二进制程序或目标文件。汇编器会把输入的一个汇编语言程序(例如srcfile)编译成目标文件(objfile)。汇编的命令行基本格式是:
as86 [选项] –o objfile srcfile
其中选项用来控制编译过程以产生指定格式和设置的目标文件。输入的汇编语言程序srcfile是一个文本文件,该文件内容必须是由换行字符结尾的一系列文本行组成。
一条汇编语句通常由标号(可选)、指令助记符(指令名)和操作数三个字段组成。汇编器编译产生的目标文件objfile通常起码包含3个段(section),即正文段(.text)、数据段(.data)和未初始化数据段(.bss)。
2、汇编语言程序
下面我们以一个简单的框架示例程序boot.s来说明as86汇编程序的结构以及程序中语句的语法,然后给出编译链接和运行方法,最后再分别列出as86和ld86的使用方法和编制选项。示例程序如下所示,这个示例是bootsect.s的一个框架程序,能编译生成引导扇区代码。其中为了演示说明某些语句的使用方法,故意加入了无意义的第20行语句。
!
! boot.s --- bootsect.s的框架程序. 用代码0x07替换msg1中1字符,然后在屏幕第1行上显示.
!
.global begtext, begdata, begbss, endtext, enddata, endbss !全局标识符,供ld86链接使用;
.text !正文段;
begtext:
.data !数据段;
begdata:
.bss !未初始化数据段;
begbss:
.text !正文段;
BOOTSEG = 0x07c0 !BIOS加载bootsect代码的原始段地址;
entry start !告知链接程序,程序从start标号处开始执行.
start:
jmpi go, BOOTSEG !段间跳转.INITSEG指出跳转段地址,标号go是偏移地址.
go: mov ax, cs !段寄存器cs的值--->ax,用于初始化数据段寄存器ds和es.
mov ds, ax
mov es, ax
mov [msg1+17], ah !0x07--->替换字符串中1个点符号,喇叭将会响一声.
mov cx, #20 !共显示20个字符,包括回车换行符.
mov dx, #0x1104 !字符串将显示在屏幕第18行、第5列处.上半字节表示行,0x11:第18行 0x04:第5列;行/列值均加1,才表示人们熟悉的1开始的起始方式.
mov bx, #0x000c !字符显示属性(红色).
mov bp, #msg1 !指向要显示的字符串(中断调用要求).
mov ax, #0x1301 !写字符串并移动光标到串结尾处.
int 0x10 !BIOS中断调用0x10,功能0x13,子功能01.
loop1: jmp loop1 !死循环.
msg1: .ascii "Loading system ..." !调用BIOS中断显示的信息.共20个ASCII码字符.
.byte 13, 10
.org 510 !表示以后语句从地址510(0x1fe)开始存放.
.word 0xaa55 !有效引导扇区标志,供BIOS加载引导扇区使用.
.text
endtext:
.data
enddata:
.bss
endbss:
!必须以空行结尾,否则可能编译不过
该程序是一个简单的引导扇区启动程序,编译链接产生的执行程序可以放入软盘的第1个扇区直接用来引导计算机启动。启动后会在屏幕的第17行,第5列处显示出红色字符串”Loading system …”,并且光标下移一行,然后程序就在第27行上死循环。
在as86汇编语言程序中,凡是以感叹号’!’或分号’;’开始的语句其后面均为注释文字。注释语句可以放在任何语句的后面,也可以从一个新行开始。
‘.global‘是汇编指示符(或称为汇编伪指令、伪操作符),用于定义随后的标号标识符是外部的或全局的,并且即使不使用也强制引入。汇编指示符均以一个字符’.’开始,并且不会在编译时产生任何代码。汇编指示符由1个伪操作码,后跟0个或多个操作数组成。’global’是一个伪操作码,而其后面的标号’begtext, begdata, begbss’等就是它的操作数。标号是后面带冒号的标识符,例如第6行上的’begtext:’。但在引用一个标号时无须带冒号。
第5行到第11行上除定义了3个标号外,还定义了3个伪操作符:’.text’、’.data’、’.bss’。它们分别对应汇编程序编译产生目标文件中的3个段,即正文段、数据段和未初始化数据段。’.text’用于标识正文段的开始位置,并切换到text段;’.data’用于标识数据段的开始位置,并把当前段切换到数据段;’.bss’用于标识未初始化数据段的开始,并把当前段改变成bss段。因此行5-11用于在每个段中定义一个标号,最后再切换到text段开始编写随后的代码。这里把三个段都定义在同一重叠地址范围中,因此本示例程序实际上不分段。
第12行定义了一个赋值语句”BOOTSEG = 0x07c0”。等号’=’(或符号’EQU’)用于定义标识符BOOTSEG所代表的值,因此这个标识符可称为符号常量。这个值与C语言中的写法一样,可以使用十进制、八进制和十六进制。
标识符’entry’是保留关键字,用于迫使链接器ld86在生成的可执行文件中包括进其后指定的标号’start’。
entry start告诉编译器程序入口是start,我们这段是引导代码,引导代码在引导盘的指定位置处,BIOS会读取引导盘的第一个扇区到内存0x7c00:0x00处。
注意:程序运行在实模式下面,物理地址=代码段cs*16+偏移,要想在0x7c00处开始执行,要设BOOTSEG=0x07c0,而不是0x7c00。
jmpi go,BOOTSEG是跳转到0x7c00处执行,在《2.3主存储器、BIOS和CMOS存储器》的第2小节”基本输入/输出程序BIOS”,我们说到BIOS会加载引导程序到内存的0x7c00处,并且跳转到这个地方继续执行。而jmpi 这条语句使得引导程序从0x7c00开始执行,也就是说,开始执行我们当前的引导区程序。
在as86中,寻址方式有以下这些:
(1)、直接寄存器寻址
跳转到bx值指定的地址处,即把bx的值拷贝到IP中。
mov bx,ax
jmp bx
(2)、间接寄存器寻址
bx值指定内存位置处的内容作为跳转的地址。
mov [bx],ax
jmp [bx]
(3)、立即寻址
把立即数1234放到ax中,把msg1地址值放到ax中。立即数前一定要加’#’号,否则将作为内存地址使用而使语句变成绝对寻址语句。另外,把一个标号(例如msg1)的地址值放入寄存器中时也一定要在前面加’#’ 号,否则会变成把msg1地址处的内容放到寄存器中。
mov ax,#1234
mov ax,#msg1
(4)、绝对寻址
把内存地址1234(msg1)处的内容放入ax中。
mov ax,1234
mov ax,msg1
mov ax,[msg1]
(5)、索引寻址
把第2个操作数所指内存位置处的值放入ax中。
mov ax,msg1[bx]
mov ax,msg1[bx*4+si]
第26行是BIOS屏幕显示中断调用:int 0x10。这里使用其功能19、子功能1。该中断的作用是把一字符串(msg1)写到屏幕指定位置处。寄存器ax的AH存储的是功能号,AL存储的是子功能号。寄存器cx中是字符串长度值,dx中是显示位置值,bx中是显示使用的字符属性,es:bp指向字符串。关于BIOS中断和int 10H的介绍,可以参考我整理的文章:《BIOS 中断向量表》、《BIOS int 10H中断介绍》。
第27行是一个跳转语句跳转到当前指令处,因此这是一个死循环语句。这里采用死循环语句是为了让显示的内容能够停留在屏幕上而不被删除。死循环语句是调试汇编程序时常有的方法。
第28-29行定义了字符串msg1。定义字符串需要使用伪操作符’.ascii’,并且需要使用双引号括住字符串。伪操作符’.asciiz’还会自动的在字符串后添加一个NULL(0)字符。另外,第29行上定义了回车和换行(13,10)两个字符。定义字符需要使用伪操作符’.byte’,并且需要使用单引号把字符括住,当然也可以像程序中一样直接写出字符的ASCII码。
第30行上的伪操作符语句’.org’定义了当前汇编的位置。这条语句会把汇编器编译过程中当前段的位置计数器值调整为该伪操作符语句上给出的值。对于本示例程序,该语句把位置计数器值设置为510,并在此处(第31行)放置了有效引导扇区标志字0XAA55。伪操作符’.word’用于在当前位置定义一个双字节内存对象(变量),其后可以是一个数字或者是一个表达式。由于后面没有代码或数据了,因此我们可以据此确定boot.s编译出来的可执行程序应该正好为512字节。
3、as86汇编语言程序的编译和链接
对上面的程序编写好之后,接下来就可以编译、链接上面的示例程序,依次执行下面的命令(我是在Ubuntu14.04中进行编译的):
# as86 -0 -a -o boot.o boot.s //编译,生成与as部分兼容的目标文件
# ld86 -0 -s -o boot boot.o //链接,去掉符号信息
# dd bs=32 if=boot of=boot.img skip=1 //写入软盘或Image盘文件中
第1条命令利用as86汇编器对boot.s程序进行编译,生成boot.o目标文件。第2条命令使用链接器ld86对目标文件执行链接操作,最后生成可执行文件boot。其中选项’-0’用于生成8086的16位目标程序;’-a’用于指定生成与GNU as和ld部分兼容的代码;’-s’用于告诉链接器要去除最后生成的可执行文件中的符号信息。’-o’指定生成的可执行文件名称。
从上面ls命令列出的文件信息可以看出,最后生成的boot程序并不是前面所说的正好512字节,而是多了32字节,这32字节是文件头信息。为了能够使用这个程序引导启动机器,需要人工去掉这32字节。这里我们通过dd命令去除boot中的前32字节,并且新生成的文件名为boot.img。
在我提供的文件中,有一个bochs的安装包,大家安装一下吧。接着将得到的boot.img和” bochsrc.bxrc”文件放在同一个目录下面,然后双击” bochsrc.bxrc”运行程序,得到的结果如下图所示:
4、as86和ld86使用方法和选项
(1)、as86的使用方法和选项
as86 [-03agjuw] [-b [bin]] [-lm [list]] [-n name] [-o objfile] [-s sym] srcfile
默认设置(除了以下默认值以外,其他选项默认为关闭或无;若没有明确说明a标志,则不会有输出):
-3 使用80386的32位输出
list 在标准输出上显示
name 源文件的基本名称(即不包括’.’后的扩展名)
各选项含义:
-0 使用16bit代码段
-3 使用32bit代码段
-a 开启与GNU as、ld的部分兼容性选项
-b 产生二进制文件,后面可以跟文件名
-g 在目标文件中仅存入全局符号
-j 使所有跳转语句均为长跳转
-l 产生列表文件,后面可以跟随列表文件名
-m 在列表中扩展宏定义
-n 后面跟随模块名称(取代源文件名称放入目标文件中)
-o 产生目标文件,后跟目标文件名(objfile)
-s 产生符号文件,后跟符号文件名
-u 将未定义符号作为输入的未指定段的符号
-w 不显示警告信息
(2)、ld链接器的使用方法和选项
ld86 [-03Mimrs[-]] [-T textaddr] [-llib_extension] [-o outfile] infile…
默认设置(除了以下默认值以外,其他选项默认为关闭或无):
-3 32位输出
outfile a.out格式输出
各选项含义:
-0 产生具有16bit魔数的头结构,并且对-lx选项使用i86子目录
-3 产生具有32bit魔数的头结构,并且对-lx选项使用i386子目录
-M 在标准输出设备上显示已链接的符号
-T 后面跟随正文基地址(使用适合于strtoul的格式)
-i 分离的指令与数据段(I&D)输出
-lx 将库/local/lib/subdir/libx.a加入链接的文件列表中
-m 在标准输出上显示已链接的模块
-o 指定输出文件名,后跟输出文件名
-r 产生适合于进一步重定位的输出
-s 在目标文件中删除所有符号
排版更好的内容见我博客的地址:http://www.only2fire.com/archives/77.html
注:转载请注明出处,谢谢!^_^