精华内容
下载资源
问答
  • 2019-09-27 09:24:49

    删除线格式

    INT 10H 是由 BIOS 对屏幕及显示器所提供的服务程序,而后倚天公司针对倚天中文提供了许多服务程序,这些服务程序也加挂在 INT 10H 内。使用 INT 10H 中断服务程序时,先指定 AH 寄存器为下表编号其中之一,该编号表示欲调用的功用,而其他寄存器的详细说明,参考表后文字,当一切设定好之后再调用 INT 10H。底下是它们的说明:

    AH功 能调用参数返回参数 / 注释
    1置光标类型(CH) 0―3 = 光标开始行
    (CL)0―3 = 光标结束行
    2置光标位置BH = 页号
    DH = 行DL = 列
    3读光标位置BH = 页号CH = 光标开始行
    CL = 光标结束行
    DH = 行
    DL = 列
    4置显示页AL = 显示页号
    5屏幕初始化或上卷
    6屏幕初始化或上卷AL = 上卷行数
    AL =0全屏幕为空白
    BH = 卷入行属性
    CH = 左上角行号
    CL = 左上角列号
    DH = 右下角行号
    DL = 右下角列号
    7屏幕初始化或下卷AL = 下卷行数
    AL = 0全屏幕为空白
    BH = 卷入行属性
    CH = 左上角行号
    CL = 左上角列号
    DH = 右下角行号
    DL = 右下角列号
    8读光标位置的属性和字符BH = 显示页
    AH = 属性
    AL = 字符
    9在光标位置显示字符及其属性BH = 显示页
    AL = 字符
    BL = 属性
    CX = 字符重复次数
    A在光标位置只显示字符BH = 显示页
    AL = 字符
    CX = 字符重复次数
    E显示字符(光标前移)AL = 字符
    BL = 前景色
    光标跟随字符移动
    13显示字符串ES:BP = 串地址
    CX = 串长度
    DH, DL = 起始行列
    BH = 页号
    AL = 0,BL = 属性
    串:Char,char,……,char
    AL = 1,BL = 属性
    串:Char,char,……,char
    AL = 2
    串:Char,attr,……,char,attr
    AL = 3
    串:Char,attr,……,char,attr
    光标返回起始位置
    光标跟随移动
    光标返回起始位置
    光标跟随串移动

    AH=00H
    AH=00/INT 10H 是用来设定显示模式的服务程序,AL 寄存器表示欲设定的模式:

    AL文字/图形分辨率颜色
    00文字40*252
    01文字40*2516
    02文字80*252
    03文字80*2516
    04图形320*2002
    05图形320*2004
    06图形640*2002

    AH=01H
    您可以把光标想成一个小的矩形,平时这个矩形扁平位于某字底部,但藉由此功能可以改变其大小与位置。光标起始处与终止处分别由 CL 与 CH 的 0 到 4 位表示,参考下图:

    而 CH 的第 7 位必须是 0,第 5、6 位表示光标属性:

    位 6位 5属性
    00正常
    01隐形
    10
    11闪烁缓慢

    AH=02H
    此功能是设定光标位置,位置用 DH、DL 表示,DH 表示列号,DL 表示行号。由左至右称之为『列』,屏幕最上面一列为第零列,紧靠第零列的下一列称为第一列……;由上而下称之为『行』,屏幕最左边一行称之为第零行,紧靠第零行右边的一行为第一行。故最左边,最上面的位置为 DH=0 且 DL=0;最左边第二列,DH=1,DL=0。如果是文字模式时,BH 为欲改变光标位置的显示页,如果是图形模式,BH 要设为 0。

    以行列来说明 DH、DL 之意义,小木偶常常搞混,底下以座标方式解释。在文字模式下,字符的位置类似数学直角座标系的座标,但是 Y 轴方向相反,Y 轴是以屏幕最上面为零,越下面越大,直到 24 为止,存于 DH 内。X 轴和直角座标系相同,越右边越大,存于 DL 内,其最大值视显示模式而变。

    AH=03H
    AH=03H/INT 10H 这个中断服务程序返回时,会在 DX 里面有光标的行列位置,CX 内有光标的大小,DX、CX 之数值所代表的意义和 AH=02H/INT 10H、AH=01H/INT 10H 相同。

    AH=04H
    此功能是探测光笔之位置,似乎只有 CGA 卡有接上光笔??

    AH=05H
    这个功能是把指定的显示页显示于屏幕上,欲显示的显示页于 AL 寄存器中指定。此功能只能在文字模式下才能发生作用。

    AH=06H/07H
    这个服务程序的功用是把某一个设定好的矩形区域内的文字向上或向下移动。先说明向上移动,即调用 AH=06H/INT 10H。当此服务程序工作时,会使矩形区域的文字向上移动,而矩形区域底端移进空格列。向上移动的列数存入 AL 中 ( 如果 AL 为零,表示使矩形区域的所有列均向上移 ),底端移入空格列的属性存于 BH,矩形区域是藉由 CX、DX 来设定左上角与右上角的座标,左上角的行与列分别由 CL、CH 设定,右下角的行与列由 DL、DH 设定。

    AH=07H/INT 10H 和 AH=06H/INT 10H 相似,只是卷动方像不同而已。

    AH=08H
    这个服务程序是用来取得光标所在位置的字符及属性,调用前,BH 表示欲读取之显示页,返回时,AL 为该位置之 ASCII 字符,AH 为其属性。有关属性的说明,请参考注一。

    AH=09H
    这个功能是在光标位置显示字符,所要显示字符的 ASCII 码存于 AL 寄存器,字符重复次数存于 CX 寄存器,显示页存于 BH 寄存器,属性存于 BL 寄存器,其属性使用与 AH=08/INT 10H 一样。

    AH=0AH
    这个功能和 AH=09H/INT 10H 一样,差别在 AH=0AH 只能写入一个字符,而且不能改变字符属性。

    AH=0BH
    这个服务程序是选择调色盘。显示模式 5 是 320*200 的图形模式,最多可以显示 4 种颜色,这四种颜色的意思是最多可以『同时』显示一种背景色及三种前景色,而这三种前景色有两种方式可供选择,因此事实上,在显示模式 5 有两种调色盘可供选择。就好像您去买 12 种颜色的水彩,但可在调色盘上以任意比例搭配出许多种颜色。

    调色盘 0 的三色是绿、红、黄;调色盘 1 的三色是青、紫红、白。背景色有 16 六种可供选择,这 16 种就是注一的 16 色。调用此中断时,先决定要设定背景色抑或调色盘,

    要设定背景色时,则使 BH 为 0,再使 BL 之数值为 0 到 0fh 之间表示注一的 16 色之一。
    要设定调色盘时,则使 BH 为 1。再设定 BL 为零或一表示选择那一种调色盘。
    背景色只有在前景色为 0 时才会显现出来。

    AH=0CH
    AH=0Ch/INT 10H 是在绘图模式中显示一点 ( 也就是写入点像,write graphics pixel ),而 AH=0DH/INT 10H 则是读取点像 ( read graphics pixel )。

    写入时,要写入位置 X 座标存于 CX 寄存器,Y 座标存于 DX 寄存器,颜色存于 AL 寄存器。和文字模式相同,萤光幕上的 Y 座标是最上面一列为零,越下面越大,X 座标则和数学的定义相同。CX、DX、AL 值之范围与显示模式有关:

    显示模式X 座标Y 座标颜色
    40~3190~1990、1
    50~3190~1990~3
    60~6390~1990、1

    AH=0DH/INT 10H 则是读取某一位置之点像,您必须指定 CX、DX,而 INT 10H 会传回该位置点像之颜色。

    AH=0EH
    这个子程序是使显示器像打字机一样的显示字符来,在前面用 AH=09H/INT 10H 和 AH=0AH/INT 10H 都可以在萤光幕上显示字符,但是这两奘方式显示字符之后,光标位置并不移动,而 AH=0EH/INT 10H 则会使光标位置移动,每显示一个字符,光标会往右移一格,假如已经到最右边了,则光标会移到最左边并移到下一列,假如已经移到最下面一列的最右边,则屏幕会向上卷动。

    AL 寄存器存要显示的字符,BH 为目前的显示页,如果是在图形模式,则 BH 须设为 0,假如是在图形模式下,也可以设定 BL 来表示文字的颜色,文字模式下的 BL 则无功能。

    AH=0FH
    这个服务程序是得到目前的显示模式,调用前只需使 AH 设为 0fh,当由 INT 10H 返回时,显示模式存于 AL 寄存器 ( 参考 AH=00H/INT 10H 的显示模式表 ),目前的显示页存于 BH 寄存器,总字符行数存于 AH 寄存器。

    注一: 所谓属性是指字符的颜色、背景颜色、是否闪烁、有没有底线等性质。在彩色显示卡 ( CGA/EGA/VGA 等 ) 的文字模式中,颜色是用 4 个位表示,故可以表现出 16 种颜色,如下表:

    二进制数颜色例子二进制数颜色例子
    0000黑色black1000灰色gray
    0001蓝色blue1001淡蓝色light blue
    0010绿色green1010淡绿色light green
    0011青色cyan1000淡青色light cyan
    0100红色red1100淡红色light red
    0101紫红色magenta1101淡紫红色light magenta
    0110棕色brown1110黄色yellow
    0111银色light gray1111白色white

    在彩色显示器里,如 CGA、EGA、VGA 等,常用一个字节 ( 8 个位 ) 来表示文字颜色和背景颜色,通常以第 0~3 位表示文字本身颜色;第 4~6 位表示背景颜色,背景颜色只有上表左栏的 8 种而已;第 7 个位,表示是否闪烁,0 表示不闪烁,1 表示闪烁。

    但是在单色显示器里,如 MDA 和 Hercules 卡中,这些颜色表并无意义,所以属性解释方式不同,请看下表:

    数值属性
    00H空格,不显示任何数据
    77H显示白色方块
    07H正常的黑底白字
    70H反白的白底黑字
    01H加底线
    更多相关内容
  • 0x00文章中我们提到了引导扇区的一个重要的细节:主引导签名。 而在这篇文章中,我们先不要急着开始动手写,先了解一下计算机刚刚启动之后,我们能使用的内存空间,以及内存的分布情况。 刚刚启动的时候,计算机会...

    在0x00文章中我们提到了引导扇区的一个重要的细节:主引导签名。

    而在这篇文章中,我们先不要急着开始动手写,先了解一下计算机刚刚启动之后,我们能使用的内存空间,以及内存的分布情况。

    刚刚启动的时候,计算机会进入实模式,在这个模式下,我们能够访问的内存空间只有1M大小(且只能执行16位程序),如果我们想实现32位系统,能够访问4GB内存的话,就必须进入保护模式(64位系统则要进一步进入长模式)。

    实模式下,内存分布的情况如下:

    1. 0xffff0-0xfffff BIOS入口地址。CPU初始化之后,会设置CS:IP指向0xffff0,直接跳转到这个入口位置,并且执行储存在此处的指令0xea5be000f0,即jmp 0xf000:0xe05b,跳转到物理地址(0xf000<<4)+0xe05b=0xfe05b处,此处为自检程序。
    2. 0xf0000-0xfffef BIOS程序范围请注意! 0xffff0-0xfffff也属于BIOS程序范围。所以实际上BIOS程序范围在0xf0000-0xfffff。
    3. 0xc8000-0xeffff 映射硬件适配器的ROM
    4. 0xc0000-0xc7fff 显示适配器BIOS
    5. 0xb8000-0xbffff 文本模式显示适配器空间
    6. 0xb0000-0xb7fff 黑白显示适配器空间
    7. 0xa0000-0xaffff 彩色适配器空间。之后我们会通过设置vga 13h模式来使用这个空间,直接对这个空间进行8位写操作,相应的显示屏像素就会被修改,我们就是通过这个来实现最基本的显示的。当然上面两个显示适配器空间也是可以用来实现显示的,不过我没有深究,如果感兴趣的话可以了解。
    8. 0x9fc00-0x9ffff 扩展BIOS数据区
    9. 0x07e00-0x9fbff 可用区域(大概608kb)。进入bootloader后我们会把主引导程序(512字节)整体搬到0x90000处,再将之后编写的加载程序载入到0x90200处来把操作系统从存储设备取出来载入内存,操作系统主要部分也会被载入这部分空间(0x10000~0x8ffff)
    10. 0x07c00-0x07dff 引导程序。本文接下来讲述的bootsect.s编译出来的引导扇区将会被载入到这段空间运行。
    11. 0x00500-0x07bff 可用区域(约30kb)
    12. 0x00400-0x004ff BIOS数据区
    13. 0x00000-0x003ff 中断向量表。中断向量表是个非常重要的结构,这个部分存储的数据直接与int指令挂钩。表中存储了跳转的地址,刚刚进入系统的时候,我们需要BIOS提供的一些基础功能来帮助初始化系统和载入系统,这时候我们会用到int指令,例如int $0x10,这表示我们调用BIOS的video service,这时候CPU会从中断向量表中找0x10号中断,取出地址并跳转,转入BIOS提供的那段程序中执行,执行完毕后返回。这个向量表会在进入保护模式之后被我们的操作系统覆盖,但是之后我们仍然需要自己构造中断向量表。

    下面开始动手写bootsect.s,如果初次接触AT&T汇编,也不用怕,下面的程序会逐条进行解释。

    先是开头声明部分

    .code16
    

    这句的意思是告诉汇编器将这个代码编译成16位程序。后面在编写head.s时,会用到32位程序,那么开头就写.code32。

    .global bootstart
    

    定义了全局symbol,全局symbol是外部程序可见的,所以链接器在链接的时候如果在其他程序中发现了一个未定义symbol,但是查到在此处有同名的global的symbol,那么就会进行链接。

    .global databeg,dataend,bssbeg,bssend,textbeg,textend
    # 此处也是定义全局symbol
    

    下面是分段部分

    .text
    textbeg:
    .data
    databeg:
    .bss
    bssbeg:
    

    .text, .data, .bss分别表示代码段,数据段,未初始化数据段,三个段定义在同一个地址范围,意味着这个程序实际上是不分段的。

    接下来是常量定义部分

    .text # text段从这里开始
    
    .equ BOOTSEG, 0x07c0
    

    .equ用于定义常量,这里定义BOOTSEG为0x07c0,后续我们会通过ljmp指令将CS寄存器设置为该值。有人会问为什么不是0x07c00,这个就得说到i386的寻址方式:

    i386在实模式(16位)下,寻址是通过CS:IP来指向实际物理地址的,那么CS=0x07c0,IP=0x0000的时候,指向的实际物理地址就是:

    (0x07c0<<4)+0x0000=0x07c00,就是主引导在一开始被载入的地址。

    .equ INITSEG, 0x9000
    # 这是我们后面要把这个主引导程序整体搬去的地方,CS:IP=0x90000
    
    .equ SETUPSEG,0x9020
    # setup.s程序会被载入到CS:IP=0x90200处,
    # 也就是主引导搬迁之后0x901ff的后面一个字节开始处
    
    .equ SYSSEG, 0x1000
    # 操作系统主要程序被加载到0x10000处,当然
    # 这个时候我们是没有写操作系统主程序的,这个只是提前写一下
    # 毕竟我们要在写之前先规划好空间
    
    .equ SYSEND, 0x8000
    # 这是我们操作系统程序装载的结尾地址
    

    下面是bootsect.s的主体程序:

    ljmp $BOOTSEG,$bootstart
    # ljmp指令会同时对CS:IP赋值并且直接跳转到CS:IP指定的位置,
    # 这里我们就直接跳转到了0x07c0:bootstart即0x07c00+bootstart位置,
    # bootstart就是下面这个汇编代码的起始symbol,
    # 执行该指令之后,bootloader正式进入bootstart开始执行指令
    

    在接触主程序之前,先提一句,at&t风格的汇编,例如mov %ax,%bx,这个操作是从ax寄存器取出数据,存到bx寄存器中,也就是说前面的是源,后面的是目(目的/目的地),这和intel风格的汇编是反过来的。

    bootstart:
    	mov $BOOTSEG,%ax
    	mov %ax,%ds
    	mov $INITSEG,%ax
    	mov %ax,%es
    	xor %di,%di
    	xor %si,%si
    	mov $0x100,%cx
    	rep
    	movsw
    	ljmp $INITSEG,$stackset
    

    bootstart这段代码就已经开始有点整人了(对于零基础的人来说)。这段代码的目的是,将0x07c00~0x07dff处这512个字节,整体搬到0x90000处。那么这是怎么做到的呢?

    首先看到我们对ds和es寄存器进行了赋值。ds和es不能直接通过mov $数字,%ds这种形式来直接赋值,只能间接通过其他寄存器来赋值,所以我们使用了ax寄存器。那么ds寄存器存入了0x07c0,es寄存器存入了0x9000。

    接下来两个xor指令,是对di和si寄存器进行清零操作的,xor运算可以快速置0,如果这一点不太清楚,可以搜索异或真值表,不难发现异或只有在两个输入不相同的时候才会输出1,而这里xor %di,%di是寄存器对自身进行按位异或,那结果必然是0。

    接着我们将0x100存入cx寄存器,0x100即256。那么为什么我们要给cx赋值256呢

    下面就是重点了,rep指令是根据cx寄存器的值进行操作的,也就意味着cx是多少,rep指令以及后面这个指令(movsw)就执行多少次。那么movsw就要执行256次。movsw又是啥呢

    这个指令是对数据进行“搬运”,它的执行是根据ds:dies:si来进行的,这个指令会从ds:di处取数据,存放到es:si处,并且在执行之后,自动对di和si加一。再加上movsw这个w后缀意味着一次搬运一个word,即2字节,所以循环256次之后,我们一共搬运了512个字节。

    所以这下我们就清楚为什么前面要给ds和es赋值,并且置零di和si了,ds:di指向了0x07c00,es:si指向了0x90000,所以这段代码直接把0x07c00后面的512个字节整体移动到了0x90000处!

    接着ljmp,跳转到了0x90000+stackset偏移量处,执行下面的这段代码。

    stackset:
    	mov %cs,%ax     # ax=INITSEG
    	mov %ax,%ds     # ds=ax
    	mov %ax,%es     # es=ax
    	mov %ax,%ss     # ss=ax
    	mov $0xff00,%sp # sp=0xff00
    

    刚刚跳转到0x9000:stackset处,不难得到CS的值是0x9000即INITSEG,IP的值是stackset的偏移量。这时候我们初始化所有的段寄存器ds,es,ss(也可以包括其他的段寄存器),让他们一起赋值为0x9000。并且设置sp(stack pointer)为0xff00,这时候栈的基址就被设置在了es:sp=0x9000:0xff00=0x9ff00处。

    start:
    	mov $0x03,%ah   # read cursor position
    	xor %bh,%bh     # set page 0
    	int $0x10       # BIOS video service
    
    	mov $INITSEG,%ax
    	mov %ax,%es     # es:bp points to the string
    
    	mov $sysmsg,%bp # set string address
    	mov $0x1301,%ax # write string,move cursor
    	mov $0x0007,%bx # page 0,black background/white characters
    	mov $28,%cx     # length of string
    	int $0x10       # BIOS video service
    

    这段代码提到了另外一个重点:BIOS中断调用。看到int $0x10了没,这就是前文中提到的中断调用,0x10就是中断号,在执行了这个指令之后,CPU会跳转到中断指定的位置执行对应的程序,然后返回到此处。由于我们现在还没有能够完全掌控各个设备,所以需要借助BIOS里面已经写好的程序,这时我们就是在调用BIOS内部给我们的程序。

    int调用类似于call,但是它执行的是从中断向量表(前文提及到过)中对应中断号位置存储的函数地址,而不是非常直白的call,它有一个搜寻->跳转的过程。

    那么既然调用的是已经写好的函数,那么函数必然会需要一些参数啊,BIOS中断调用使用的参数并不是像使用C调用函数的那种方法push到栈中的,而是由你自己设置一些需要用到的寄存器,来实现传参的。

    比如第一个调用,0x10是BIOS显示服务,将ah(ax的高8位)设置为0x03,意思就是我们选择的是读取光标位置这个服务。然后我们将bh(bx高8位)设0,表示页号为0,然后调用0x10中断。0x10中断执行结束后,会反馈一些数据回来:
    0x10中断0x03服务
    这个反馈回来的ch cl dh dl值在接下来的0x10中断0x13号服务中会被部分使用到:
    0x10中断0x13服务
    那么根据这个传参要求,我们要设置es为0x9000,bp为字符串的起始地址,cx字符串长度,dh,dl起始行列(在0x03号服务中已经获得),al=1光标跟随移动,bl 0x07黑底白字(就是图中的属性),bh 0x00页号0。这就是这段代码的下半段所做的事情。sysmsg是字符串地址,在后面会提到,字符串长度28,调用一下0x10中断,此时屏幕上就会输出一行字:
    成功了

    die:
    	hlt
    	jmp die # infinite loop
    

    这是个死循环,我们的最简操作系统最后会执行到这里无限循环(操作系统本质上包含一个可以跳出的死循环)。hlt指令是在你的输入设备(键盘/鼠标)没有进行任何操作的时候,暂停CPU的运行,让CPU停下来“歇一歇”,不用一直执行jmp die操作。

    sysmsg:
    	.ascii "Starting Balloon System..."
    	.byte 13,10
    

    这里就是存放sysmsg字符串的位置,.ascii后面可以写一串字符串,后面跟着的.byte 13,10表示13号和10号字符,13号字符即回车,光标返回最前面,10号字符即换行,光标移到下一行。

    .=510
    signature:
    	.word 0xaa55
    

    这里是程序的点睛之笔,如果缺少这一段,那么程序是无法被识别为主引导的。.=510意思就是一直跳到偏移量为510处,跳过的部分默认填0,这时候留下两个字节的位置,让我们来设置主引导签名0x55,0xaa,这里之所以写成0xaa55,是因为i386在取数的时候为小端序,低地址存放的是数的高位,所以在写入文件的时候,0x55会被放置到前面。

    .text
    textend:
    .data
    dataend:
    .bss
    bssend:
    
    

    代码段结束。

    要想让这段代码顺利跑起来,我们需要qemu-system-i386环境,并且需要一个链接脚本来保证链接器能生成正常的那512字节,最后还要将这512字节的bootsect文件装入一个虚拟软盘,这时候我们需要用到dd指令。链接脚本的代码如下:

    OUTPUT_FORMAT(elf32-i386)
    OUTPUT_ARCH(i386)
    
    SECTIONS {
    	.text 0x0000:{
    		*(.text)
    	}
    	/DISCARD/ : {
    
    	}
    }
    

    开头两句表示输出格式为elf32-i386,架构为i386
    下面是想要的链接结果,将.text段放置到0x0000起点,我们的bootsect.s本来就只有.text段,所以整个代码会直接从0x0000处开始,保证了进入引导的时候能正确执行。保存为文件名ld_boot.ld。

    接下来写一下Makefile

    All: Image
    # 执行make All的时候,会直接生成Image文件
    
    .PHONY=clean run-qemu
    # .PHONY表示伪目标,这里写的东西都不代表文件名,
    # 我们用的clean和run-qemu都仅仅是操作
    
    bootsect:bootsect.s ld_boot.ld
    	- @as --32 bootsect.s -o bootsect.o
    	- @ld -T ld_boot.ld bootsect.o -o bootsect
    	- @objcopy -O binary -j .text bootsect
    # 冒号后面跟着的是生成bootsect所需的基础文件,
    # 这里需要bootsect.s和ld_boot.ld
    # 生成bootsect文件,第一句是用汇编器把.s文件汇编到.o文件
    # 第二句是用链接脚本将.o文件转化到elf可执行文件
    # 第三句是用objcopy提取bootsect中.text段的内容,再覆盖bootsect文件
    # 这样bootsect就是标准的可以被识别和运行的引导程序了
    
    Image:bootsect
    	- @dd if=bootsect of=Image bs=512 count=1
    	- @echo "Image built done"
    # dd可以制作一个镜像,if表示输入文件,of表示输出到文件,bs表示一块的大小
    # bs=512表示一块有512字节大小,count=1表示生成1块
    
    clean:
    	- @rm -f *.o bootsect Image
    # 清理文件
    
    run-qemu:Image
    	- @qemu-system-i386 -boot a -fda Image
    # -fda表示把Image当做虚拟软盘载入qemu虚拟机
    
    

    然后在控制台输入make run-qemu,就可以运行啦!最终结果就是输出一个字符串,然后进入die死循环,这就是最小的操作系统。

    下一篇文章我们会对bootsect.s文件进行扩展,尝试通过0x13 BIOS存储器服务,来从软盘中读取我们需要的程序段。

    展开全文
  • 例:DH = 0x01,DL = 0x0 读取第二磁头,读取第一块存储介质 调用中断读取硬盘数据 记得上一篇中,我们给出的 boot sector 在内存中的位置,我们将选取 0x8000 作为加载我们磁盘数据的内存地址。它在我们的 boot ...

    目录

    回顾

    上一篇文章,我们讨论了以下内容:

    • Boot Sector 被 BIOS 加载到 0x7c00 的内存位置
    • 用程序证实了 0x7c00 物理内存位置上,确实是我们的 Boot Sector 程序
    • 寄存器分为通用寄存器,指针寄存器,段寄存器以及控制寄存器,我们分别列出了各个寄存器的名称及基本功能
    • 用段寄存器替代 org 指令来完成寻找 Boot Sector 前两个字节内容的任务
    • 一个段中的内存寻址可以通过 [段寄存器:内存偏移量] 来完成
    • 必要的汇编指令,如 jmp,cmp,times,pusha,popa等

    今日目标

    今天的目标,我们要告别 16-bit Real Mode,进入 32-bit Protected Mode。意味着我们离内核只有一步之遥了。

    这篇文章中,我们即将要学习

    • 如何读取磁盘上的数据,为读取内核代码做准备
    • 什么是 GDT
    • 如何在汇编中定义 GDT
    • 如何切换到 32-bit Protected Mode

    我将前两篇文章及今天这篇文章中涉及的代码,整理了一下,列在 这里

    我们开始吧。

    BIOS 读取硬盘数据

    硬盘数据的读取,需要在寄存器设置一系列的参数。有一些参数,有关硬盘的工作方式。所以,我们先来简单了解一下硬盘的必要知识。

    硬盘

    硬盘由盘片,和读写磁头组成。为了扩大容量,几张盘片,重叠在一起,由磁头来读写数据。由于盘片是有两面的,因此,一张盘片就有两个磁头,分别负责读取/写入该磁盘面上的数据。

    引用书上的图片为例。

    这是典型的机械硬盘的内部构造。

    在这里插入图片描述

    硬盘的盘片,是可以磁化的,一个比特的数据,磁化即为 1,非磁化即为 0。数据在盘片高速旋转时,由读写头读取和写入。

    推荐大家看一下希捷的关于硬盘的视频。里面提到了 1个 bit 的实际物理大小,是 84 纳米(nanometer,也称毫微米)。

    硬盘的物理构造,由专门的名词来描述。盘片叠加在一起,盘片上的每一圈,我们称之为磁道(Track),因为硬盘由多个盘片叠加组成,这些磁道,构成如一个圆柱体,我们称之为柱面(Cylinder)。读写装置,我们称之为磁头(Head)。每个盘面,被逻辑分成多个扇区(Sector),每个扇区通常是 512 个字节。

    那么,在这样的物理构造下,我们要读取特定一个位置上的数据,就需要 3 个参数来确定。哪一个柱面(磁道),哪一个磁头,哪一个扇区。

    这个 3D 坐标被称为 Cylinder-Head-Sector (CHS)地址。更多关于 CHS 的信息,可以阅读这篇 Wiki

    • Cylinder,即柱面,描述的是我们需要读取数据的在第几个磁道。
    • Head,即磁头,描述的是我们需要的数据具体在哪一个盘面。
    • Sector,即扇区,描述的是我们需要的数据在第几个扇区。

    应用书上的图片作为例子。将 CHS 地址视觉化。

    在这里插入图片描述

    读取硬盘数据的参数

    如同之前的文章中,我们要调用显示设备,在屏幕上输出字符,就要在 ah 中写入 0x0e,并触发中断。硬盘读写,也需要我们将相应的指令写入到寄存器,来告诉 BIOS 我们要读取的数据的位置和长度。

    硬盘与与 CPU 有多种不同的连接总线,如 ATA/IDE,SATA,SCSI,USB。BIOS 为这些常见的设备提供了统一的指令。

    我们将这些指令(包括寄存器中的参数和中断)列举在下面:

    • AH 0x02 ; BIOS 读取磁盘扇区的模式(原书中写成了 al,有误)
    • AL 0x5 ; 读取的扇区数(1 - 128)
    • CH 0x3 ; 磁道/柱面 (0 - 1023)
    • CL 0x4 ; 扇区(1-63)
    • DH 0x1 ; 磁头(0 - 255)
    • DL 0x0 ; 存储介质 (0 => 1 号软驱;1 => 2 号软驱;0x80 => 第 1 块硬盘;0x81 => 第 2 块硬盘)
    • ES:BX ; 磁盘数据将被读取并写入到这个内存地址
    • INT 0x13 ; 触发中断读取指定位置上指定长度的数据,并写入到内存的指定位置

    读取操作完成之后,CPU 会设置几个返回值到寄存器,说明读取操作是否成功,我们可以做错误处理:

    更多关于每个参数的索引起始,以及索引范围的信息,可以阅读这篇 Wiki


    硬盘参数小结:

    • AX 寄存器中,高位 AH 部分存储读取模式,低位 AL 部分存储要读取的扇区数。例:AH = 0x02,AL = 0x05,读取模式,读取 5 个扇区
    • BX 寄存器中,存储的是读取到的数据要被加载到内存地址的内存地址偏移量。例:BX = 0x8000,若 ES 为 0x0,读取的数据就被加载到 0x8000 的内存地址上
    • CX 寄存器中,高位 CH 部分存储柱面信息,低位 CL 部分存储要读取第几扇区的数据。例:CH = 0x03,CL = 0x02,读取第 3 柱面,第 2 扇区
    • DX 寄存器中,高位 DH 部分存储磁头信息,低位 DL 部分存储要读取第几块软驱或者硬盘的数据。例:DH = 0x01,DL = 0x0 读取第二号磁头,读取第一块存储介质

    调用中断读取硬盘数据

    记得上一篇中,我们给出的 boot sector 在内存中的位置,我们将选取 0x8000 作为加载我们磁盘数据的内存地址。它在我们的 boot sector 之后的空闲空间里。

    在这里插入图片描述

    我们将读取两个扇区的测试数据。

    来看代码,磁盘读取的参数设置,在 read_from_disk.asm 中。

    read_from_disk.asm

    read_from_disk:
        pusha
    
    checking
        push bx ; 之后打印出测试数据被加载到内存的位置
        push dx
    
        ; 各个参数
        mov ah, 0x02 ; 读取模式
        mov al, dh ; 读取两个扇区的数据 (dead...beef...)
        mov ch, 0x00 ; 从第 1 柱面开始读
        mov cl, 0x02 ; 从第 2 扇区开始读 (第 1 个扇区是 boot sector)
        mov dh, 0x00 ; 从第 1 磁头开始读
        ;mov dl, 0x00 ; 从第1 块存储介质开始读
    
        int 0x013 ; 触发中断
    
        mov bx, READ_START
        call print
        call print_nl
    
        jc op_error ; 如果操作失败, CF(Carry Bit) 寄存器会被设置为 1, 如果 CF 寄存器被设置为 1,jc 就会跳转
        
        pop dx
        cmp al, dh
        jne read_error
    
        mov bx, READ_COMPLETE
        call print 
    
        ; 打印出测试数据被加载到哪里
        pop bx
        mov dx, bx
        call print_hex
        call print_nl
    
        popa
        ret
    
    op_error:
        ; 错误信息
        mov bx, OP_ERROR
        call print
        call print_nl
    
        ; 如果有错误发生,我们打印出错误码信息
        mov dh, ah
        call print_hex
        ; just hang the cpu on error
        jmp disk_loop
    
    read_error:
        mov bx, READ_ERROR
        call print
        jmp disk_loop
    
    disk_loop:
        jmp $
    
    READ_START: db "Reading start...", 0
    READ_COMPLETE: db "Reading complete, data loaded to ", 0
    OP_ERROR: db "Disk read error...", 0
    READ_ERROR: db "Incorrect number of sectors read...", 0
    

    print_hex.asm

    print_hex:
        pusha
    
        mov cx, 0 ; our indbx variable
    
    ; Strategy: get the last char of 'dx', then convert to ASCII
    ; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
    ; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
    ; Then, move the ASCII byte to the correct position on the resulting string
    hex_loop:
        cmp cx, 4 ; loop 4 times
        je end
        
        ; 1. convert last char of 'dx' to ascii
        mov ax, dx ; we will use 'ax' as our working register
        and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
        add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
        cmp al, 0x39 ; if > 9, add extra 7 to represent 'A' to 'F'
        jle step2
        add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
    
    step2:
        ; 2. get the correct position of the string to place our ASCII char
        ; bx <- base address + string length - indbx of char
        mov bx, HEX_OUT + 5 ; base + length, starts last last char of HEX_OUT
        sub bx, cx  ; our indbx variable
        mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
        ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
    
        ; increment indbx and loop
        add cx, 1
        jmp hex_loop
    
    end:
        ; prepare the parameter and call the function
        ; remember that print receives parameters in 'bx'
        mov bx, HEX_OUT
        call print
    
        popa
        ret
    
    HEX_OUT:
        db '0x0000',0 ; reserve memory for our new string
    

    print.asm

    print:
        pusha
    
    start:
        mov al, [bx] ; 'bx' is the base address for the string
        cmp al, 0 
        je done
    
        mov ah, 0x0e
        int 0x10 ; 'al' already contains the char
    
        add bx, 1 ; print next char
        jmp start
    
    done:
        popa
        ret
    
    ; print new line
    print_nl:
        pusha
        
        mov ah, 0x0e
        mov al, 0x0a ; newline char
        int 0x10
        mov al, 0x0d ; carriage return
        int 0x10
        
        popa
        ret
    

    boot_sect_main.asm

    [org 0x7c00]
    
    ; 我们将利用栈保存一些寄存器的值,所以将栈的内存位置设置在空闲区域
    mov ax, 0x8000
    mov bp, ax
    mov sp, bp
    ; 测试数据会被加载到 [ES:BX] => 0x8000
    mov ax, 0x0
    mov es, ax
    mov bx, 0x9000
    
    ; 读取两个扇区的数据,dh 这里用来传递 0x2 这个数据
    mov dh, 0x2
    
    ; 开始读取
    call read_from_disk
    
    ; 打印第 2 扇区第一个字  => 0xdead
    mov dx, [es:bx]
    call print_hex
    ; 打印第 3 个扇区第一个字  => 0xbeef
    mov dx, [es:bx + 512]
    call print_hex
    
    jmp $
    
    %include "print.asm"
    %include "print_hex.asm"
    %include "read_from_disk.asm"
    
    times 510 - ($ - $$) db 0x0
    dw 0xaa55
    
    ; 写入 512 个字节到第 2 扇区(第 1 扇区是 boot sector)
    times 256 dw 0xdead
    ; 写入 512 个字节到第 3 扇区
    times 256 dw 0xbeef
    

    在 boot_sect_main.asm 中,我们分别读取了第二和第三个扇区的前两个字节,可以看到结果如下。

    在这里插入图片描述

    我们可以用 od 命令查看 bin 文件中的内容,看到紧接着我们的 boot sector,写入了我们的测试数据。

    在这里插入图片描述

    大家可以尝试修改代码,加载测试数据到不同的内存地址,观察程序的变化。

    现在,我们已经具备加载内核的能力。下面,我们告别 16-bit Real Mode,开启 32-bit Protected Mode。

    32-Bit Protected Mode

    16-bit Real Mode 以下称 16 位模式, 32-Bit Protected Mode 以下称 32 位模式。

    首先,经过前两篇文章的学习,我们已经很熟悉 16 位模式了。现在,我们要思考一下为什么还需要切换到 32 位模式,它和 16 位模式有什么区别。

    接着,我们要学习 32 位模式中最重要的概念,全局描述符(Global Descriptor Table)。

    最后,我们学习怎么在汇编中定义 GDT,并切换到 32 位模式。

    关于 32-Bit Protected Mode

    在切换操作之前,我们必须先了解一下 32 位模式。

    什么是 32-bit Protected Mode?

    Protected Mode,保护模式,是自 80286 以来的现代 CPU 的主要工作模式。

    32 位模式加入了虚拟内存的概念,并且加强了内存读写保护,提供了通过 Rings 限制可用指令的能力。

    总而言之,32 位模式向着更加高级,更加安全的方向发展,为现代操作系统提供了一个更好的运行环境。

    为什么我们需要 32-bit Protected Mode?

    我们从 16 位模式切换到 32 位模式,有两个最主要的目的。

    • 第一,为了完全释放 CPU 的能力
    • 第二,为了更好地理解硬件的内存保护机制

    我们不能容忍那可怜的 1MB 内存,不能容忍我们程序的内存毫无保护,所以,32 位模式势在必行。

    32-bit Protected Mode vs 16-bit Real Mode

    到了 32 位模式之后所发生的变化总结如下:

    • 寄存器扩展到了 32-bit,之后,寄存器的使用都要加上 e,意思是 extended,例如:mov eax, 0x80808080
    • 通用寄存器增加了两个,FS 和 GS
    • 内存分段的技术更加高效,同时也更加复杂
      • 我们可以防止一个段中的代码被执行
      • CPU 支持虚拟内存和分页,用户程序将会以分页的形式在磁盘和内存之间进行切换(swapping)
      • 中断的处理也更加的高级

    32 位模式下的字符打印

    在继续下面的内容之前,我们必须先做一点代码上的调整。能够打印字符对于程序的调试是很重要的,所以,我们现在要将 16 位模式下的打印字符的代码,调整到 32 位可用。在调整代码之前,我们需要先对 32 位模式下的底层调用有所了解,才能顺利在 32 位模式下打印字符。

    告别 BIOS

    BIOS 下的中断和系统调用,是专门为 16 位模式设计的,因此,在 32 位模式下不可用。书中提到,有办法可以暂时切换回 16 位模式去使用 BIOS 的系统调用,但是这没有意义,十分复杂,也违背我们要切换到 32 位的初衷。

    那么,我们必须丢弃 BIOS,重新调整我们的思路去适应 32 位模式。

    32 位模式下的显示设备调用

    这里要说明的是 32 位模式下,关于显示设备调用需要理解的一些概念。

    Memory-Mapped Device(Memory-Mapped I/O)

    计算机的外围设备,分为 Memory-Mapped(内存映射) 和 Port-Mapped(端口映射)两种。我们这里讨论的显示设备,是 Memory-Mapped Device(暂译为内存映射设备)的一种。

    计算机外围设备,都以某种方式连接至 CPU,他们都与 CPU 有输入输出的操作。因此,外围设备的输入输出操作被统称为 Memory/Port-Mapped I/O

    内存映射设备使用同一内存空间来记录数据内存地址与设备内存地址。这是我总结的,原文是 “Memory-mapped I/O uses the same address space to address both memory and I/O devices.”。可以这样理解, CPU 访问设备内存上的数据时,其实就是在访问设备本身。有这样特征的设备,就被称为 Memory-Mapped Device。

    例如显示设备,我们只需要往设备内存中写入数据,就可以在屏幕上展示这些数据。

    所以,接下来要讲到的在 32 位模式下打印字符,我们只需要 CPU 去访问特定的显示设备内存(Video Memory),即可完成显示设备调用,打印字符到屏幕。

    VGA 模式(Video Graphics Array)

    显示设备有两种模式可以设置:

    • 文本模式(text mode)
    • 图形模式(graphics mode)

    在计算机启动的时候,无论计算机上有多么高级的显示设备(RTX 2080Ti 😄),都必须从 Video Graphics Array(VGA)标准文本模式开始。书中讨论的是,VGA 标准文本模式的一种,它的特点是:

    • 以行列模式显示字符
    • 有效像素为 720x400
    • 可以显示 80x25 个字符
    • 每个字符 9x16 像素大小

    在 VGA 模式下,我们不需要对每个像素进行操控,因为就像上面所说,字符的信息(9x16像素)已经存储在显示设备的内存中。我们称每个字符信息为一个字符单元(character cell)

    在内存中,每个字符单元用 2 个字节 表示。第一个字节代表字符的 ASCII 码,第二个字节代表 该字符显示时的属性,如背景色,前景色,或者是否该闪烁。

    VGA 模式内存

    前文我们说到了显示设备是内存映射设备,因此,我们要显示字符,就必须在设备内存的位置上,写入相应的值

    这个设备的内存地址比较固定,通常都在 0xb8000

    另外,前文说到 VGA 模式是行列模式(80x25),但是内存是线性的,因此,我们还有一个公式,用于计算行列上的字符的内存地址。

    0xb8000 + 2 * (row * 80 + col)
    

    例如第 2 行第 3 列的内存地址应该是 0xb8000 + 2 * (2 * 80 + 3) => 0xb8206

    这个公式以后会用到。暂做记录。

    32-bit Protected Mode 输出字符

    学习了这么多,是时候写段代码了。

    下面的代码会在屏幕的左上角打印出 HelloWorld

    pm_print.asm

    ; 常量定义
    VIDEO_MEMORY equ 0xb8000 ; VGA 设备内存地址
    WHITE_ON_BLACK equ 0x0f ; 显示属性,白色前景,黑色背景
    
    ; 打印 EDX 中的字符串
    print_string_pm:
        pusha
        mov edx, VIDEO_MEMORY ; Set edx to the start of vid mem.
    
    print_string_pm_loop:
        mov al, [ebx] ; Store the char at EBX in AL
        mov ah, WHITE_ON_BLACK ; Store the attributes in AH
        cmp al, 0 ; if (al == 0) , 字符串结束
        je print_string_pm_done
    
        mov [edx], ax ; 将字符和属性写入 EDX 指向的内存地址
        add ebx, 1 ; 指向下一个字符
        add edx, 2 ; 指向设备内存的下一个字符单元(1 个字符单元 2 个字节)
        jmp print_string_pm_loop ; 循环打印
    
    print_string_pm_done:
        popa
        ret
    

    pm_main.asm

    org 0x7c00
    
    mov ebx, HELLO_WORLD
    call print_string_pm
    
    jmp $
    
    %include "pm_print.asm"
    
    HELLO_WORLD: db "HelloWorld", 0
    
    times 510 - ($ - $$) db 0
    dw 0xaa55
    

    注意,在测试打印代码的时候,不要任何文件中放置 bits 32 指令,我们还没有做切换操作,否则会没有任何显示。

    测试结果如下,屏幕左上角打印出字符串 HelloWorld

    在这里插入图片描述

    通过测试代码,在 print_string_pm_loop 方法中,我们往 edx 所在的显示设备内存写入任意字符(mov [edx], ax),屏幕上就会显示出相应的字符,这就是内存映射设备的工作方式。

    全局描述符(GDT)

    现在,我们已经知道了 32 位模式下打印字符的原理。我们接着了解最后一个概念,全局描述符(Global Descriptor Table)。

    什么是 GDT 及 GDT 的作用

    全局描述符,Global Descriptor Table(GDT)让 CPU 在 32 位模式下实现像 16 位模式一样的内存寻址。它是各类描述符的统一集中。它不仅包括段描述,还有 Task State Segment 描述符Local Descriptor TableCall State Structures等

    关于 GDT 的更多更详细的信息,大家可以参考 Wiki

    我们这里着重要讨论的,是段描述符(Segement Descriptor)。

    回忆在 16 位模式中,内存寻址的公式是:

    段选择符 * 10H + 内存偏移量
    

    在我们切换到 32 位模式之后,虽然这样的 段地址 + 偏移量 的逻辑没有变,但是实现方式完全不同。

    之前的 段选择符(Segment Selector),现在指向的是前面提到的 段描述符(Segment Descriptor)

    与段内存相关的段内存描述符,是段内存的元信息,包含了段内存空间的基址,大小,访问权限等。这个 8 个字节的数据描述了 32 位模式下的段内存的如下几个属性:

    • 32 位的段内存基址(Base Address),定义了该段内存的起始物理内存地址
    • 20 位的段空间(Segment Limit),定义了该段内存的空间大小
    • 另外的一些标识位(Flags),定义了该段内存的其他属性,如运行权限,是否只读等

    我们引用书上的图片来看一下 GDT 的结构。

    在之后用代码定义完 GDT 之后,我会将 GDT 的各个位拿出来梳理。

    下图中,红色是 GDT 的前 4 个字节,绿色是后 4 个字节。我们最关注的两个属性,基址和段空间大小可以很清楚看到分布在 GDT 的结构中。只是位置很奇怪。OS-From-Scratch 的作者并不清楚为什么如此分配 bit 给这两个属性,东一点,西一点,可能跟 CPU 的设计有关。

    在这里插入图片描述

    以下是书上对于每个属性的简单解释:

    Base: 0x0
    在这里插入图片描述

    Basic Flat Model

    虽然定义很复杂,但是 Intel 给出了一个最简单的 GDT 模型,叫做 Basic Flat Model。这个模型只定义两个相叠加的段内存(段内存叠加我们在下一篇中讲解),共同覆盖 4GB 的物理内存空间。这两个段内存,一个是 代码段(Code Segment),一个是是 数据段(Data Segment)

    在这个简单的模型下,两个段内存间仍然是没有内存保护机制的,同时,也无法使用虚拟内存的分页特性。使用这样简单的模型,只为了让我们过渡到 32 位模式,以便我们能用高级语言(C)在后期更高效地修改 GDT。

    定义 GDT

    要注意的是,CPU 要求 GDT 的最开始是一个 null descriptor,是一个 8 个字节的 0,硬性规定,我们只能照做。

    另外,CPU 必须知道我们的 GDT 的长度。不过我们不会将 GDT 的起始地址直接交给 CPU,而会将一个更加简单的数据交给 CPU,这个数据结构叫做 GDT Descriptor(相当于 GDT 的元数据,用来描述 GDT)。

    GDTD 是一个 6 字节结构,包含:

    • GDT 的长度(16 位)
    • GDT 的基址(32 位)

    现在,我们会用 dbdwdd 指令在段描述符中写入必要的数据。

    代码中使用 10001100b 这样的形式,来直接写入二进制;使用 dw 来写入 2 个字节;使用 dd 来写入 4 个字节(double word)。

    pm_32bit_gdt.asm

    ; GDT
    gdt_start :
    	gdt_null: ; 定义 gdt 起始的 null descriptor,8 个字节
    	dd 0x0
    	dd 0x0
    
    gdt_code: ; 定义 gdt code 段
    	dw 0xffff ; Limit ( bits 0 -15)
    	dw 0x0 ; Base ( bits 0 -15)
    	db 0x0 ; Base ( bits 16 -23)
    	db 10011010b ; 1st flags , type flags
    	db 11001111b ; 2nd flags , Limit ( bits 16 -19) 
    	db 0x0 ; Base ( bits 24 -31)
    
    gdt_data: ; 定义 gdt data 段
    	dw 0xffff ; Limit ( bits 0 -15)
    	dw 0x0 ; Base ( bits 0 -15)
    	db 0x0 ; Base ( bits 16 -23)
    	db 10010010b ; 1st flags , type flags
    	db 11001111b ; 2nd flags , Limit ( bits 16 -19)
    	db 0x0 ; Base ( bits 24 -31)
    
    gdt_end: ; 这个 label 用于计算 GDT 的长度,交给接下来的 GDTD 使用
    
    ; GDT descriptior
    gdt_descriptor :
    	dw gdt_end - gdt_start - 1 ;  GDT 的长度,总是实际代码长度 - 1(因为不能定义长度为 0 的 GDT)
    	dd gdt_start ; GDT 的基址
    
    ; 定义两个常量,分别代表代码段和数据段的内存地址偏移量
    CODE_SEG equ gdt_code - gdt_start
    DATA_SEG equ gdt_data - gdt_start
    

    我们具体看一下两个 GDT 的具体数据:

    GDT Code:

    0000 0000   1100 1111   1001 1010   0000 0000   0000 0000 0000 0000   1111 1111 1111 1111
      【1】        【2】        【3】        【4】            【5】                  【6】
    

    【1】基址最高位 8 位,全部为 0
    【2】4 个第二标识位 1100,4 个 Limits 最高位 1111
    【2-1】4 个第二标识位为:|G|D/B|L|AVL
    【2-1-1】G:Granularity 设置为1,就会将我们的 limit 左移 12 位(十进制 * 4096,16 进制 * 1000),来完成 4GB 的寻址;这里就是为什么我们说 32 位模式的寻址逻辑,和 16 位模式一样,只是实现方式不同;这里设置为 1
    【2-1-2】DB:Default Operation Size,0 代表 16-bit Segment,1 代表 32-bit Segment,这里设置为 1
    【2-1-3】L:是否是 64-bit Code Segment,1 代表是,0 代表不是,我们是 32-bit,这里设置为 0
    【2-1-4】AVL:是否可被系统程序使用,这里设置为 0
    【2-2】:4 个 Limits 最高位,这里设置为 1111
    【3】4 个第一标识位 1001,Type 标识位 1010
    【3-1】4 个第一标识位为 |P|DPL|S,DPL 占 2 个 bit
    【3-1-1】P:Segemnt Present,设置为 0,则段不可用,因此,这里设置为 1
    【3-1-2/3】DPL:Descriptor Privilege Level,本文在 什么是 32-bit Protected Mode? 中提到的 Ring 就是这里的权限等级,0 为最高级,这里设置为 00
    【3-1-4】S:Descriptor Type,这个描述符的种类,0 为系统,1 为代码或者数据,这里设置为 1
    【3-2】Type 类型,4 个 bit
    【3-2-1】类型位:0 为数据,1 为代码,这里设置为 1
    【3-2-2】Conforming:如果设置为 0,其他低权限段内存中的代码无法调用本段中的代码,这是内存保护的原型,这里设置为 0
    【3-2-3】Readable/Writable:Readable 针对代码段,Writable 针对数据段;代码段不可写,数据段不可读;这里,1 为可读,0 为只可执行,设置为 1 允许我们读取定义在代码中的常量,这里设置为 1
    【3-2-4】Accessed:虚拟内存以及 debugging 使用,这里设置为 0
    【4】【5】基地址低 24 位,全部为 0,因此基地址为 32 个 0
    【6】Limit 最低 16 位,全部位 1,因此 Limit 为 1111 1111 1111 1111 1111(20 位)

    GDT Data 只是将上述 【3-2-1】设置位 0,代表数据,其余位全部一样。

    GDT 更多的内容,大家可以看 这篇文章

    这就是 GDT 的全部内容。

    接下来,我们将在最后一步,切换到 32 位模式的操作中,使用定义好的 GDT。

    切换到 32-Bit Protected Mode

    切换的操作很直观,我们来看一下切换之前的几个必要步骤:

    • cli 禁用中断,如果还有中断被触发,将被 CPU 全部忽略(直到中断被手动恢复)
    • lgdt [gdt_descriptor] 将 GDTD 交给 CPU(前文说过,我们不会将 GDT 直接交给 CPU,而是将 GDTD 交给 CPU)
    • 设置 cr0 寄存器的第一个 bit 为 1
    • 做一次 far jump,jmp <段地址>:<内存偏移量>,让 CPU pipline 中的指令全部清空(将不同阶段的指令全部执行完)

    一旦切换到 32 位模式,首先要做几件事情,来确保在 32 位模式下的内存寻址和指令执行不会出错:

    • 使用 bits 32 指令来告诉 assembler 现在开始所有的指令要以 32 位模式来编译
    • 设置其他段寄存器的地址,指向 GDT 中定义的新的数据段
    • 更新栈的地址

    Instruction Pipelining

    我们简单介绍一下 Instruction Pipelining(以下简称 IP) 以及 Pipeline Flushing(以下简称 PF)。这是在单 CPU 下实现指令级并发的技术。CPU 是由控制单元,逻辑控制,寄存器等器件构成,pipelining 技术将合理安排这些 CPU 内部器件的工作,降低闲置时间,实现指令的并行执行,将效率最大化。

    x86 CPU 的指令执行步骤如下:

    1. 读取指令
    2. 指令解码及寄存器访问
    3. 执行
    4. 访问内存
    5. 结果写回寄存器

    每一个步骤,被称为一个 stage(阶段)

    IP 技术使得指令的执行并行,也就产生不同指令在不同阶段的情况。比如指令 A 已经到了解码阶段,指令 B 在读取阶段,而指令 C 已经到执行阶段。

    记得前几年英特尔 CPU 的 漏洞 跟 IP 技术是有关的。

    现在的情况是,如果我们切换到了 CPU,但是 CPU 的 pipeline 中还有未执行完的某个阶段的指令,如果该指令是需要在 16 位模式下执行,而我们已经切换到了 32 位模式,那么 CPU 就会崩溃。

    因此,我们在切换到 32 位模式下,一定要做一个操作将 pipeline 中的指令全部清空(Pipeline Flushing 让 CPU 在切换之前将未执行完的指令全部执行完)。

    我没有找到太多有关 PF 的资料。大家可以看一下 Intel Developer Manual 上对于模式切换的说明

    在这里插入图片描述

    模式切换

    进入 32 位模式之后,我们的栈空间会被设置在这个空余内存空间内。

    在这里插入图片描述

    pm_32bit_switch.asm

    [bits 16]
    switch_to_pm:
        cli ; 禁用中断
        lgdt [gdt_descriptor] ; 将 GDTD 内容交给 CPU
        mov eax, cr0
        or eax, 0x1
        mov cr0, eax ; 这 3 步,设置 cr0 寄存器的第一个 bit,由于不能直接写入,借助 eax 传递
        jmp CODE_SEG:init_pm ; far jump,清空 CPU Pipeline,接下来就是 32 位模式
    
    [bits 32] ; 进入 32 位模式
    init_pm:
        mov ax, DATA_SEG ; 更新其他段寄存器,指向我们的数据段
        mov ds, ax
        mov ss, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
    
        mov ebp, 0x8000; 更新栈地址
        mov esp, ebp
    
        call BEGIN_PM ; 打印信息
    

    pm_32bit_switch_main.asm

    [org 0x7c00]
    
    mov bp, 0x800 ; 指向上述代码中的新的栈(bp * 10H + 0x0 = 0x8000)
    mov sp, bp
    
    mov bx, MSG_REAL_MODE
    call print ; 输出在在 BIOS 信息之后
    
    call switch_to_pm ; 开始切换模式
    
    %include "print_16bit.asm"
    %include "pm_32bit_gdt.asm"
    %include "pm_32bit_print.asm"
    %include "pm_32bit_switch.asm"
    
    [bits 32]
    BEGIN_PM: ; after the switch we will get here
        mov ebx, MSG_PROT_MODE
        call print_string_pm ; 打印在屏幕左上角
        jmp $
    
    MSG_REAL_MODE db "Started in 16-bit real mode", 0
    MSG_PROT_MODE db "Loaded 32-bit protected mode", 0
    
    ; bootsector
    times 510 - ($ - $$) db 0
    dw 0xaa55
    

    切换及打印信息所需所有代码,在 32Bit Protected Mode Switch

    运行结果如下,成功切换到 32 位模式。

    在这里插入图片描述

    总结

    • 读取硬盘所需的参数设置,硬盘数据的地址由 CHS 提供,我们需要将柱面,磁头,扇区信息写入相应的寄存器
    • 读取硬盘的测试数据并打印
    • 32 位模式提供虚拟内存,分页等更加灵活高效的内存管理模式,同时增加了内存寻址的空间,寄存器也从 16 位扩展到了 32 位
    • 无论计算机的显示设备多么高级,在计算机启动时,都处于 VGA 模式
    • VGA 文本模式的一种,是 80x25 的行列模式,每个字符的像素大小是 9x16
    • VGA 模式下,一个字符的在内存中的位置被称为字符单元
    • 显示设备是内存映射设备,我们在显示设备的内存地址写入信息,就可以显示在屏幕上
    • 全局描述符是 32 位模式下内存寻址重要信息
    • 在 32 位模式下,段寄存器指向的不是段内存的基址,而是 GDT 中的段描述符
    • 段描述符包含了段内存的基址,大小,权限等信息
    • 切换到 32 位之前,我们必须定义 GDT 和 GDTD(GDT Descriptor)
    • 我们还需要禁用中断,将 GDTD 交给 CPU,设置 cr0 寄存器的第一个 bit,做一个 far jump 清空 CPU pipeline
    • 切换到 32 位之后,要在代码中使用 bits 32 来让 assembler 以 32 位模式编译指令,另外,我们需要将其他段寄存器指向我们新的段内存,并更新栈的地址到空闲内存地址

    这是一个需要耐心的漫长的旅程。我们终于可以在下一章,拉开内核的序幕,并转向高级语言 C。


    推荐阅读(参考链接):

    • https://wiki.osdev.org/Protected_Mode#:~:text=On%2080386s%20and%20later%2C%20the,available%20instruction%20set%20via%20Rings.
    • https://wiki.osdev.org/Security#Rings
    • https://en.wikipedia.org/wiki/INT_13H
    • https://stanislavs.org/helppc/int_13-2.html
    • https://stanislavs.org/helppc/int_13-1.html
    • https://wiki.osdev.org/BIOS
    • https://en.wikipedia.org/wiki/Cylinder-head-sector
    • https://en.wikipedia.org/wiki/Virtual_memory
    • https://en.wikipedia.org/wiki/Paging
    • https://en.wikipedia.org/wiki/Memory-mapped_I/O
    • https://en.wikipedia.org/wiki/Video_Graphics_Array#Standard_text_modes
    • https://wiki.osdev.org/Printing_To_Screen
    • http://3zanders.co.uk/2017/10/16/writing-a-bootloader2/
    • https://en.wikipedia.org/wiki/Global_Descriptor_Table
    • https://wiki.osdev.org/Global_Descriptor_Table
    • https://wiki.osdev.org/GDT_Tutorial#What_should_I_put_in_my_GDT.3F
    • https://en.wikibooks.org/wiki/X86_Assembly/Global_Descriptor_Table
    • https://www.cs.umd.edu/~meesh/cmsc411/website/saltz/cs412/lect3.html
    • https://en.wikipedia.org/wiki/Protection_ring
    • http://tuttlem.github.io/2014/07/11/a-gdt-primer.html#:~:text=gdt_tab%20starts%20with%20the%20length,that%20has%20a%20zero%20length.
    • https://en.wikipedia.org/wiki/Instruction_pipelining
    • https://en.wikipedia.org/wiki/Central_processing_unit
    • https://electronics.stackexchange.com/questions/153735/what-is-pipeline-flushing-in-microprocessors
    • https://www.computerhope.com/jargon/p/pipeline-flush.htm
    • https://en.wikipedia.org/wiki/Meltdown_(security_vulnerability)
    • http://faydoc.tripod.com/cpu/jmp.htm
    • https://en.wikipedia.org/wiki/Pipeline_stall
    • http://qcd.phys.cmu.edu/QCDcluster/intel/vtune/reference/vc145.htm#:~:text=Far%20jump%2D%2DA%20jump,to%20as%20an%20intersegment%20jump.&text=When%20executing%20a%20near%20jump,specified%20with%20the%20target%20operand.
    • https://stackoverflow.com/questions/29430762/what-is-the-difference-if-any-between-long-and-far-jumps-in-assembly
    • https://stackoverflow.com/questions/5757866/what-does-short-jump-mean-in-assembly-language#:~:text=Short%20jumps%20(and%20near%20calls,from%20the%20address%20of%20the
    • https://software.intel.com/content/dam/develop/public/us/en/documents/325462-sdm-vol-1-2abcd-3abcd.pdf
    展开全文
  • 2.通过功能号0x09探究色彩控制 上面的显示为什么是红色呢?我们可以通过实验来看一下颜色控制。 ------------------------------------------------------------------------ INT 0x10 功能 0x09 --------...

    本文 转载自 https://blog.csdn.net/guzhou_diaoke/article/details/8397658


    使用BIOS 显示服务(Video Service)--INT 10H,下面主要探究字符显示模式。

    BIOS中断在保护模式下是不能用的,故不能在Linux中测试,所以写了个简单的boot loader,并在虚拟机中运行程序。


    1.以电传的方式写入字符串(AH=0x13)

    
     
    1. ------------------------------------------------------------------
    2. INT 0x10功能 0x13
    3. --------------------------------------------------------------
    4. 描述:
    5. 以电传打字机的方式显示字符串
    6. 接受参数:
    7. AH 0x13
    8. AL 显示模式
    9. BH 视频页
    10. BL 属性值(如果AL= 0x000x01
    11. CX 字符串的长度
    12. DH,DL 屏幕上显示起始位置的行、列值
    13. ES:BP 字符串的段:偏移地址
    14. 返回值:
    15. 显示模式(AL):
    16. 0x00:字符串只包含字符码,显示之后不更新光标位置,属性值在BL中
    17. 0x01:字符串只包含字符码,显示之后更新光标位置,属性值在BL中
    18. 0x02:字符串包含字符码及属性值,显示之后不更新光标位置
    19. 0x03:字符串包含字符码及属性值,显示之后更新光标位置
    20. -------------------------------------------------------------------

    示例:
    
     
    1. # A bootsect, which print a string by BIOS interrupt video services(int 0x10)
    2. .section .text
    3. .global _start
    4. .code16
    5. _start:
    6. movw %cx, %ax
    7. movw %ax, %ds
    8. movw %ax, %es
    9. movw $msgstr,%bp
    10. movw len, %cx
    11. movb $0x05, %dh
    12. movb $0x08, %dl
    13. movb $0x01, %al
    14. movb $0x13, %ah
    15. movb $0x01, %bl
    16. movb $0x00, %bh
    17. int $0x10
    18. 1:
    19. jmp 1b
    20. msgstr:
    21. .asciz "Hello babyos(print by BIOS int 0x10:0x13, mode 0x01)!"
    22. len:
    23. .int . - msgstr
    24. .org 0x1fe, 0x90
    25. .word 0xaa55
    26. makefile:
    27. all: boot.img
    28. boot.o: boot.s
    29. as -o $@ $<
    30. boot: boot.o
    31. ld --oformat binary -N -Ttext 0x7c00 -o $@ $<
    32. boot.img: boot
    33. dd if=boot of=boot.img bs= 512 count= 1
    34. clean:
    35. rm ./boot ./boot.img ./boot.o

    运行:


    2.通过功能号0x09探究色彩控制

    上面的显示为什么是红色呢?我们可以通过实验来看一下颜色控制。

    
     
    1. ------------------------------------------------------------------------
    2. INT 0x10功能 0x09
    3. -------------------------------------------------------------------
    4. 描述:
    5. 显示字符并设置其属性
    6. 接受参数:
    7. AH 0x09
    8. AL 字符的ASCII码
    9. BH 视频页
    10. BL 属性值
    11. CX 重复次数
    12. 返回值:
    13. 注意:
    14. 在显示字符之后并不前进光标。在文本和图形模式下均可调用该功能
    15. 显示完字符后,如果还要继续显示字符,必须调用INT 0x10功能 0x02前进光标
    16. -------------------------------------------------------------------------
    17. ------------------------------------------------------------------------
    18. INT 0x10功能 0x02
    19. -------------------------------------------------------------------
    20. 描述:
    21. 把光标定位在选定视频页的特定行列位置
    22. 接受参数:
    23. AH 0x02
    24. DH,DL 行、列值
    25. BH 视频页
    26. 返回值:
    27. 注意:
    28. 80x25模式下,DH范围 024,DL范围 079
    29. -------------------------------------------------------------------------

    示例:

    实现一个从第4行~7行,第8列~71列,显示‘A’~‘Z’的程序,共显示256个字符,使用BL(0~255)

    C 伪代码:
    
     
    1. cx = 0x04;
    2. bh = 0x00;
    3. dh = 0x04;
    4. dl = 0x08;
    5. al = 'A';
    6. for (bl = 0; bl < 256; bl++)
    7. {
    8. print_char();
    9. al++;
    10. if (al == 'Z')
    11. al = 'A';
    12. dl++;
    13. if (dl == 72)
    14. {
    15. dh++;
    16. dl = 4;
    17. }
    18. set_cursor();
    19. }
    汇编代码:
    
     
    1. # A bootsect, which print a colorful chars by BIOS INT 0x10, 0x09
    2. .section .text
    3. .global _start
    4. .code16
    5. _start:
    6. movw %cx, %ax
    7. movw %ax, %ds
    8. movw %ax, %es
    9. movw $ 0x01, %cx # 字符显示重复次数
    10. movb $ 0x00, %bh # 视频页
    11. movb $ 0x04, %dh # 显示起始行
    12. movb $ 0x08, %dl # 显示起始列
    13. movb $ 'A', %al # 显示字符
    14. movb $ 0x00, %bl # 属性值
    15. 1:
    16. call print_char
    17. incb %al
    18. cmpb $ 'Z', %al
    19. jne 2f
    20. movb $ 'A', %al
    21. 2:
    22. incb %dl
    23. cmpb $ 72, %dl
    24. jne 3f
    25. movb $ 8, %dl
    26. incb %dh
    27. 3:
    28. call set_cursor
    29. incb %bl
    30. cmp $ 256, %bl
    31. jne 1b
    32. jmp 1f
    33. print_char:
    34. movb $ 0x09, %ah
    35. int $ 0x10
    36. ret
    37. set_cursor:
    38. movb $ 0x02, %ah
    39. int $ 0x10
    40. ret
    41. 1:
    42. jmp 1b
    43. .org 0x1fe, 0x90
    44. .word 0xaa55

    结果:


    可以显式地验证色彩控制BL:
    
     
    1. 7 6 5 4 3 2 1 0
    2. I R G B I R G B
    3. 闪烁 R G B I R G B

    如上图所示,7~4位为背景色,I表示高亮,RGB表示红绿蓝,若显卡支持闪烁,则位7表示是否闪烁。
    色彩混合:
    
     
    1. -----------------------------------------------------------
    2. 混合的三种基色 不开启亮度位 开启亮度位
    3. -------------------------------------------------------
    4. 红+绿+蓝 浅灰 白色
    5. 绿+蓝 青色 浅青
    6. 红+蓝 洋红 浅洋红
    7. 红+绿 棕色 黄色
    8. 无色彩 黑色 暗灰
    9. -----------------------------------------------------------

    3.清屏

    可以发现屏幕上有许多Bochs的打印信息,看着不爽,想办法去掉它们。

    利用0x06号功能,上卷全部行,则可清屏。

    -----------------------------------------------------------
    INT 0x10 功能0x06
    ------------------------------------------------------
    描述:
    上卷窗口
    参数:
    AH 6
    AL 上卷的行数(0表示全部)
    BH 空白区域的视频属性
    CH,CL 窗口左上角的行列位置
    DH,DL 窗口右下角的行列位置
    返回值:

    ------------------------------------------------------------
    示例:

    
     
    1. # A bootsect, which print a colorful chars by BIOS INT 0x10, 0x09
    2. .section .text
    3. .global _start
    4. .code16
    5. _start:
    6. movw %cx, %ax
    7. movw %ax, %ds
    8. movw %ax, %es
    9. call clear_screen # 清屏
    10. movw $ 0x01, %cx # 字符显示重复次数
    11. movb $ 0x00, %bh # 视频页
    12. movb $ 0x04, %dh # 显示起始行
    13. movb $ 0x08, %dl # 显示起始列
    14. movb $ 'A', %al # 显示字符
    15. movb $ 0x00, %bl # 属性值
    16. 1:
    17. call print_char # 打印字符
    18. incb %al # 下一个字符
    19. cmpb $ 'Z', %al # 是否该重新从‘A’开始
    20. jne 2f
    21. movb $ 'A', %al
    22. 2:
    23. incb %dl # 下一个位置
    24. cmpb $ 72, %dl # 是否到下一行
    25. jne 3f
    26. movb $ 8, %dl
    27. incb %dh
    28. 3:
    29. call set_cursor # 设置光标位置
    30. incb %bl # 下一种属性
    31. cmp $ 0, %bl # 是否 256种属性用完
    32. jne 1b
    33. jmp 1f # 结束
    34. clear_screen: # 清屏函数
    35. movb $ 0x06, %ah # 功能号 0x06
    36. movb $ 0, %al # 上卷全部行,即清屏
    37. movb $ 0, %ch # 左上角行
    38. movb $ 0, %ch # 左上角列
    39. movb $ 24, %dh # 右下角行
    40. movb $ 79, %dl # 右下角列
    41. movb $ 0x07, %bh # 空白区域属性
    42. int $ 0x10
    43. ret
    44. print_char:
    45. movb $ 0x09, %ah # 功能号 0x09
    46. int $ 0x10
    47. ret
    48. set_cursor:
    49. movb $ 0x02, %ah # 功能号 0x02
    50. int $ 0x10
    51. ret
    52. 1:
    53. jmp 1b
    54. .org 0x1fe, 0x90
    55. .word 0xaa55

    结果:


    4.直接写显存绘制字符串:

    示例:
    
     
    1. #---------------------------------------------------------------
    2. # 直接写显存显示一些文字函数:
    3. # 显示计算机当前工作的显示模式
    4. draw_some_text:
    5. # 设置ES,DS的值
    6. movw $VIDEO_SEG_TEXT,%ax
    7. movw %ax, %es
    8. xorw %ax, %ax
    9. movw %ax, %ds
    10. # 计算字符显示位置的显存地址(目标地址)
    11. movw $(( 80*TEXT_ROW+TEXT_COL) * 2), %di
    12. # 源字符串地址
    13. leaw msgstr, %si
    14. movb $TEXT_COLOR, %al # 属性值(颜色)
    15. movw len, %cx # 显示的字符个数
    16. draw_a_char:
    17. movsb
    18. stosb
    19. loop draw_a_char
    20. ret


    展开全文
  • 0x01 , %al # 读取扇区数 movb $ 0x00 , %ch # 柱面 movb $ 0x02 , %cl # 扇区 movb $ 0x00 , %dh # 磁头 movb $ 0x00 , %dl # 驱动器 re_read: # 若调用失败则重新调用 int $ 0x13 jc ...
  • uboot中烧录kernel 通过SD卡将kernel烧录到nand中: fatload mmc 0:1 0x21000000 uImage.xz nand erase.part KERNEL nand write.e 0x21000000 KERNEL ${filesize} nand erase.part RECOVERY nand write.e 0x...
  • 用户需填写主机的ip地址、树莓派的用户名和密码以及端口,点击快速连接即可建立树莓派和电脑的文件传输的桥梁。如下图所示,左下角部分的文件夹是电脑内的文件夹,右下角的文件夹是树莓派内的文件夹,拖动,即可...
  • 0x01 baby_web hint:想想初始页面是哪个 打开发现在首页在1.php 按照提示去index.php,抓包。 getflag 0x02 Training-WWW-Robots 按题意直接访问robots.txt getFlag 0x03 Web_python_template_injection31 ...
  • IBM服务器感叹亮黄灯 无法启动

    千次阅读 2021-07-31 07:59:35
    1. I ---- 9/14/2016:2:56:19 --N --0x4000000e00000000 --Remote Login Successful. Login ID: USERID from Web at IP address 192.168.70.772. I ---- 9/14/2016:2:46:54 --N --0x800301081381ffff ...
  • 参考书籍:  1.《IBM-PC汇编语言程序设计》  2.... ... ◆ 设置显示方式: ... 功能:AH = 00H ... 调用参数:AL = 00H 40 × 25 黑白文本,16级灰度 ... AL = 01H 40 × 25 16文本  AL...
  • RGB 256颜色表代码表

    千次阅读 2020-05-12 21:05:07
    24bpp 256 数组 由上链接中拷贝下来的 0x000000 ,0x800000 ,0x008000 ,0x808000 ,0x000080 ,0x800080 ,0x008080 ,0xc0c0c0 ,0x808080 ,0xff0000 ,0x00ff00 ,0xffff00 ,0x0000ff ,0xff00ff ,0x00ffff ,0xffffff ,...
  • 注:以下程序为原创,若发现任何BUG,欢迎指正;若有问题,欢迎交流;权利归原作者所有,若转载,请注明出处;若能有益于一二访客,幸甚。 昨天学习了VGA显示的一些东西,今天准备... movb $0x06, %ah # 功能号0x06...
  • 0x01 (右上角)先戳 Star,再戳 Fork 0x02 打开 _config.yml 文件 点击右上角的笔 根据提示修改内容 完成后点击下方绿色的 Comment Changes 0x03 打开 使用 GitHub 账号登录 0x04 点击绿色 New Site From Git 选择 ...
  • Linux驱动开发: FrameBuffe(LCD)驱动开发

    千次阅读 多人点赞 2021-09-07 20:09:35
    通常,使用如下方式(前面的数字表示次设备): (次设备)0 = /dev/fb0 第一个fb设备 (次设备)1 = /dev/fb1 第二个fb 设备。 fb 也是一种普通的内存设备,可以读写其内容。 例如,屏幕抓屏:cp /dev/fb0 ...
  • //写入要读的寄存器 return LCD_RD_DATA(); } 这两个函数函数十分简单,LCD_WriteReg 用于向 LCD 指定寄存器写入指定数据,而 LCD_ReadReg 则用于读取指定寄存器的数据,这两个函数,都只带一个参数/返回值,所以...
  • 当 FDD 模式的 u 盘(有 BPB,没有分区表)被 BIOS 分配驱动器 0x80 时,映射为 0x00, 同时屏蔽驱动器 0x80(避免通过 BIOS 读 u 盘,有可能死机)。 这一更改也避免了 Issue 162 读 64 位逻辑扇区的问题。...
  • 我的OS | 确认操作系统的执行状态

    千次阅读 2020-05-22 17:38:47
     设置AL为0x01 mov al, 0x01 ; 十进制的13,设置字符串的长度为13B mov cx, 13 ; 设置在第零行 mov dh, 0x0 ; 设置在第零列 mov dl, 0x0 ; 设置字符串的内存地址(段地址为DS(Data Segment, 数据段), 偏移...
  • 0x04-HackTheBox-Pathfinder

    2020-04-23 15:02:53
    我在 0x00 开篇中有说过,在慢慢学习,和记录的过程当中,一个渗透测试学习者应该形成自己的学习思路,和测试工作流程。 这是第五篇博文,我个人的学习思路初步已经形成,那就是 nmap 一下 HackTheBox 的目标机器,...
  • 字符串转GB2321编码后,若码值小于0X80,则表示此字符串是有两个部分拼接而成。 网上得到字符串对应的编码都是直接转unsigned char,若是使用UNICODE,则需先将字符串转char,然后强转得到高地位,相加后可得对应的...
  • babyos (一)——利用BIOS 中断INT 0x10显示字符和字符串 http://blog.csdn.net/guzhou_diaoke/article/details/8397658 注:以下程序系原创,使用AT&T格式汇编来调用BIOS 0x10中断,如有错误,...
  • 文章目录前言一、VB-01模块介绍二、通信串口选择1.通信串口介绍2.UART03.打印信息说明三 、ESP-C3-12F端与VB-01的数据交互1.读取指令并解析控制2.控制VB-01播放提示音四、总结 前言      ...
  • 0x01,0xFF,0xF7,0xC0,0x07,0xF0,0x03,0xC0,0x01,0xFF,0xE0,0x00,0x07,0xF0,0x07,0xE0, 0x00,0xF7,0xE0,0x00,0x07,0xF0,0x0F,0xF0,0x00,0x07,0xE0,0x00,0x07,0xF0,0x1F,0xF8, 0x00,0x0F,0xC7,0xFF,0xFF,0xFF,0xFF,0xF...
  • 本文实例讲述了Python编程实现控制cmd命令行显示颜色的方法。分享给大家供大家参考,具体如下: 基于win7 + python3.4 运行效果: ...FOREGROUND_BLUE = 0x01 # 蓝 FOREGROUND_GREEN = 0x02 # 绿 FOREGROU
  • 基于STM32设计的校园一卡通项目

    千次阅读 2021-12-26 13:06:18
    } /* 函数功能:初始化RC522的IO口 */ void RC522_IO_Init(void) { RCC->APB2ENR |= 0x01 ; AFIO->MAPR |= 0x01 ; RCC->APB2ENR |= 0x01 ; //PA时钟使能 //#define RC522_CS PAout(10) //#define RC522_SCLK ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,146
精华内容 3,658
热门标签
关键字:

色号0x01