精华内容
下载资源
问答
  • 段寄存器,段描述符和段选择子

    千次阅读 2021-02-09 23:18:08
    寄存器: ES CS SS DS FS GS LDTR TR共8个 读一个寄存器只读16位,写一个寄存器写96位 Struct SegMent{ WORD Selector;//16位selector WORD Attribute;//16位的Attribute WORD Base;//32位的Base DWORD ...

    前奏

    X86 CPU的3个模式:实模式,保护模式和虚拟8086模式

    保护模式有什么特点?
    段的机制
    页的机制

    学习保护模式有什么用?
    真正理解内核是如何运作的

    段寄存器

    段寄存器:
    ES CS SS DS FS GS LDTR TR共8个

    读一个段寄存器只读16位,写一个段寄存器写96位

    Struct SegMent{
    WORD	Selector段选择子;//16位selector
    WORD	Attribute;//16位的Attribute
    WORD	Base;//32位的Base
    DWORD	Limit	//32位的Limit
    }

    在这里插入图片描述
    XP环境下:

    段寄存器SelectorAttributeBaseLimit
    ES0023可读,可写00xFFFFFFFF
    CS001B可读,可执行00xFFFFFFFF
    SS0023可读,可写00xFFFFFFFF
    DS0023可读,可写00xFFFFFFFF
    FS003B可读,可写0x7FFDE0000xFFF
    GS----

    可执行:当前这个段修饰的地址可以赋值给eip
    注意:
    加粗数据在不同环境下,数值可能不一样。

    验证环节:

    探测Attribute:

    __asm {
    
    		mov ax,ss	//ss段寄存器可写,cs段寄存器不可写
    		mov ds,ax
    		mov dword ptr ds:[var],eax
    	}
    	printf("你死了");
    

    在这里插入图片描述

    __asm {
    
    		mov ax,cs	//ss段寄存器可写,cs段寄存器不可写
    		mov ds,ax
    		mov dword ptr ds:[var],eax
    	}
    	printf("你死了");
    

    在这里插入图片描述
    验证Attribute的确存在

    探测Base:

    __asm {
    
    		mov ax, ds	
    		mov gs, ax
    		mov eax,gs:[0]	
    	}
    

    在这里插入图片描述

    	__asm {
    
    		mov ax, ds	
    		mov gs, ax
    		mov dword ptr ds : [var] , eax
    	}
    

    在这里插入图片描述
    验证Base的确存在

    探测Limit:

    __asm{
    		mov ax,fs
    		mov gs,ax
    	    mov eax ,gs:[0x1000]	//0x7FFDF000+0x1000	fs的limit的0xFFF
    	    mov dword ptr ds:[var],eax
    	}
    

    在这里插入图片描述

    	__asm{
    		mov ax,fs
    		mov gs,ax
    		mov	eax, dword ptr cs:[0x7FFDF000 + 0x1000]
    	    mov dword ptr ds:[var],eax
    	}
    

    在这里插入图片描述
    验证Limit的确存在

    可以通过MOV指令对寄存器进行读写(LDTR和TR除外)

    段描述符和段选择子

    GDT(全部描述符表) LDT(局部描述符表)
    Windows里面并没有使用LDT,使用的都是GDT

    GDTR(48位寄存器,表中存放了GDT表 的起始地址(32位),外加存放GDT表的大小(16位))
    当我们执行类似MOV DS,AX指令时,CPU会查表,根据AX的值来决定查找GDT还是LDT,查找表的什么位置,查出多少数据。

    r gdtr
    

    利用winDbg可以查看GDT 的地址

    r gdtl
    

    利用winDbg可以查看GDT 表的大小

    dd 地址
    

    查看此地址开始以double word 为一组的数据

    段描述符(8个字节为一组)
    在这里插入图片描述
    GDT表里面存储的元素称为段描述符,每个段描述符是8个字节

    dq 地址
    

    查看此地址开始以8个字节为一组的数据

    段选择子
    在这里插入图片描述
    RPL:请求特权级别

    TI:
    TI=0 查GDT表
    TI=1 查LDT表

    Index:(索引值)
    MOV DS,AX指令时,假如AX=1B,即001B
    拆分为0000 0000 0001 1011

    去掉TI和RPL值,后剩下Index值为11,即3,则查GDT表索引值为3 (GDT表中索引值从0开始,索引值为0处的段描述符为0)的段描述符

    加载段描述符至寄存器(dword 4个字节;fword 6个字节;qword 8个字节;):
    除了MOV指令,我们还可以使用LES,LSS,LDS,LFS,LGS指令修改寄存器

    CS不能通过上述的指令进行修改,CS为代码段,CS的改变会导致EIP的改变,要改CS,
    必须要保证CS与EIP一起改

    char buffer[6];
    __asm{
    les ecx,fword ptr ds:[buffer]//高2字节给es,低4个字节给ecx
    }
    

    注意:RPL<=DPL(在数值上)段权限检查

    P位(高四字节第15位)

    p=1短描述符有效

    p=0短描述符无效

    段描述符与段寄存器的对应关系

    在这里插入图片描述
    Attribute //16位 对应段描述符(高四字节) 第8位~第23位

    Base //32位 (高四字节)第24位 ~ 第31位 + (高四字节)第0位 ~ 第7位+(低四字节)第16位 ~ 第31位

    Limit //32位 (高四字节)第16位 ~ 第19位 +(低四字节)第0位 ~ 第15位 总共20位 最大值也就是FFFFF,此时分情况
    1.如果G位为0,那么Limit单位是字节,此时高位填0,即最大值也就是0x000fffff
    2.如果G位为1,那么Limit单位是4KB,4x1024=4096,4096代表有多少个,但是地址计算都是从0开始的,那么需要减1,即上限为4096-1=4095,刚好转为0xfff,如果此时Limit此时界限为1的话,那么此时为0x1FFF,则最大可以为0xffffffff

    总结:
    如果G为0的话,那么Limit为0x000FFFFF
    如果G为1的话,那么Limit为0xFFFFFFFF

    注意:
    FS对应的短描述符比较特殊,FS是与线程相关,查分后的值与段寄存器中的值不符合。
    在这里插入图片描述

    展开全文
  • GRUB引导程序之第一阶段stage1.S分析

    千次阅读 2021-11-25 13:43:30
    初识BIOS BIOS是最基础的输入、输出系统,是固化在计算机主板上ROM芯片中的程序,可以这么理解,当计算机主板加电之后,CPU先加电,... 被读取的512字节就是MBR,在GRUB引导程序中,就是stage1,对应了一汇编代...

    初识BIOS

        BIOS是最基础的输入、输出系统,是固化在计算机主板上ROM芯片中的程序,可以这么理解,当计算机主板加电之后,CPU先加电,然后加载到CPU中运行的第一个计算机程序就是BIOS。  BIOS会执行POST(Power-on Self Test)硬件自检功能,自检完成之后,就会检索启动设备,并将启动设备的第一个柱面,第一个磁道的第一个扇区,共计512字节的内容读取到物理地址0x7c00处。

        被读取的512字节就是MBR,在GRUB引导程序中,就是stage1,对应了一段汇编代码stage1.S。该代码编译之后的大小为446字节,随后是64字节的分区表,最后是两个字节的MBR标识0x55AA,一共是512字节。

        现在的计算机一般使用的是UEFI代替传统的BIOS来引导系统启动,相应的分区方式也经常搭配使用了GPT而非传统的MBR分区。GPT分区的主分区里面,也就是第一部分依然会有一个PMBR分区,对于linux等系统,一般不管使用了MBR分区还是GPT分区,都可以以传统的BIOS来引导启动系统,在这个过程都会用到GRUB第一阶段的代码。本文的分析的也主要根据传统的BIOS启动来的。

    GRUB第一阶段

        参考的代码为GRUB legacy (0.97)版本的代码,其中第一阶段的文件为stage1.S,该文件为汇编文件,其详细注释标注在了源码中。

    #include <stage1.h>
    
    //代码开始部分放入物理地址0x7c00处
    #define ABS(x) (x-_start+0x7c00)
    
    //将X在物理内存中的地址赋值给si寄存器
    #define MSG(x)	movw $ABS(x), %si; call message
    
    #define	MOV_MEM_TO_AL(x)	.byte 0xa0;  .word x
    	
    	.file	"stage1.S"
    
    	.text
    	.code16
    
    //_start标号处的地址,也就是grub启动第一阶段加载在内存中的地址0x7c00
    .globl _start; _start:
    //跳转指令, 跳转到after_BPB标号的位置
    	jmp	after_BPB
    //空指令,一般占位用
    	nop
    
    //占位填充的长度,_start+4距离_start起始位置的长度是4字节,
    //既一共需要填充0满之4字节。前面的指令占用了3个字节,再填充1个字节的0即可。
    	. = _start + 4
    	
    mode:
    	.byte	0
    disk_address_packet:	
    sectors:
    	.long	0
    heads:
    	.long	0
    cylinders:
    	.word	0
    sector_start:
    	.byte	0
    head_start:
    	.byte	0
    cylinder_start:
    	.word	0
    
    	. = _start + STAGE1_BPBEND
    
    stage1_version:	
    	.byte	COMPAT_VERSION_MAJOR, COMPAT_VERSION_MINOR
    boot_drive:	
    	.byte	GRUB_INVALID_DRIVE
    force_lba:
    	.byte	0
    stage2_address:
    	.word	0x8000
    stage2_sector:
    	.long	1
    stage2_segment:
    	.word	0x800
    
    //start之后跳转到的位置
    after_BPB:
    
    //现在的地址环境没有经过检测,因此需要先关闭bios中断确保安全后再开启
    	cli
    boot_drive_check:
    //jmp为跳转执行,跳转到下面的1标号的地方。
    //在BIOS加载完成启动代码之后,会将启动盘号设置到DL寄存器中,也就是说此时DL寄存器存储的值为0x80
    //软盘号的取值范围为:0x00~0x7F  磁盘号的取值范围为:0x80~0xFF。 
    //这里的代码直接跳转到下面的1标号位置了
    //数字标号具有局部性,f表示forward向前的,既执行地址之后的标号,b为backward向后的,既执行地址之前的标号。
    	jmp	1f
    	testb	$0x80, %dl
    	jnz	1f
    	movb	$0x80, %dl
    	
    //上述1标号的地方
    1:	
    
    	/*
    	 * ljmp to the next instruction because some bogus BIOSes
    	 * jump to 07C0:0000 instead of 0000:7C00.
    	 */
    //长跳转指令,"ljmp 段选择子 段内偏移", 此处内存就一个段,所以段选择子为0
    //此处就是跳转到了(real_start - start) + 0x7c00物理地址处(也就是接着real_start执行),有矫正地址的作用
    //部分伪造的BIOS程序会跳转到0x07C0:0x0000地址处,而正确的地址应该是0x0000:0x7C00
    	ljmp	$0, $ABS(real_start)
    
    real_start:	
    //AX寄存器通过异或清零
    	xorw	%ax, %ax
    //将DS寄存器和SS寄存器的值清零
    	movw	%ax, %ds
    	movw	%ax, %ss
    //将0x2000立即数赋值给SP寄存器,既当前的内存堆栈栈顶为0x2000,此时就开辟出了一个堆栈可以进行压栈出栈操作
    	movw	$STAGE1_STACKSEG, %sp
    //地址设置完成,开启bios中断
    	sti
    
    //直接寻址,将boot_drive标号处的物理地址指向的值0xFF赋值给AX寄存器的低8位
    	MOV_MEM_TO_AL(ABS(boot_drive))
    //比较指令,值相等的话ZF零标志位寄存器为1,此处两个值相等,ZF=1
    //cmp指令可以理解为源操作数与目的操作数做算术减法运算,结果为0则设置ZF为1
    	cmpb	$GRUB_INVALID_DRIVE, %al
    //条件转移指令,ZF为1时向前跳转到标号1的位置,也就是下面的标号1的位置。
    	je	1f
    	movb	%al, %dl
    1:
    //将DX寄存器的值压入堆栈,DX中存储的是启动盘号,也就是0x80或者0x81......0xFF
    	pushw	%dx
    
    //在屏幕上打印GRUB字样
    	MSG(notification_string)
    
    //判断DL寄存器的值与0x80逻辑与操作是否为0,为0的话说明不是硬盘,则ZF=1
    //test指令为逻辑与操作,操作结果为0则设置ZF寄存器为1
    	testb	$STAGE1_BIOS_HD_FLAG, %dl
    //条件转移指令,等同于je,可理解为如果ZF设置为1了(不是硬盘,也就不支持LBA模式)则跳转到chs_mode搞事情
    //非硬盘,或者硬盘但是不具有LBA扩展功能的,均要进入CHS模式进行寻址读取内容。
    	jz	chs_mode
    			
    //如果是硬盘的话,通过BIOS的0x13号中断,0x41号的功能来判断是否执行LBA模式。
    //AH功能集:
    //检查扩展是否存在: AH=0x41 BX=0x55AA DL=磁盘号
    //扩展读:AH=0x42
    //扩展写:AH=0x43
    //校验扇区:AH=0x44
    //扩展定位:AH=0x47
    //取得驱动器参数:AH=0x48
    	movb	$0x41, %ah
    	movw	$0x55aa, %bx
    	int	$0x13
    //在上述的0x13中断返回时,会对CF进行赋值,CF=0表示硬盘支持扩展访问,CF=1表示不支持扩展访问。
    //当支持硬盘扩展访问时,BX的值为被赋值为0xAA55。
    
    //在BIOS1.04等版本中,经过0x13中断的0x41号功能检查了扩展之后,DX寄存器会被污染。
    //所以要出栈获取到之前的压栈保留的磁盘号,然后再压栈,保证SP寄存器数值一致,既堆栈平衡。
    	popw	%dx
    	pushw	%dx
    	
    //jc是条件转移指令,CF为1时,表示不支持LBA,则跳转到CHS模式处理中。
    //然后还需要比较BX寄存器中的值是不是0xAA55,如果不一致说明也不支持LBA模式,则跳转到CHS模式。
    	jc	chs_mode
    	cmpw	$0xaa55, %bx
    	jne	chs_mode
    
    //这里的force_lba标号地址对应的值为0,将0复制到AL寄存器中
    //testb为比较指令,与AL寄存器的值做与运算,也就是两个0值逻辑与运算之后等于0,则ZF=1。
    //jnz为条件转移指令,当ZF没有设置时,跳转到lba_mode标号处,直接进入LBA模式处理。
    //此处由于force_lba为0,既继续进行判断。force_lba可以理解为强制进入LBA模式,配置之后可以减少一些LBA的判断。
    	MOV_MEM_TO_AL(ABS(force_lba))
    	testb	%al, %al
    	jnz	lba_mode
    	
    //继续进行CX寄存器的判断,CX寄存器的值与立即数1进行与操作,支持LBA时,CX的第0位被设置位1,与1进行与操作结果是1,则ZF=0。
    //CX寄存器表示位描述符,其中每位的表示:
    //0位: 磁盘扩展访问功能
    //1位: 支持可移动驱动控制器功能
    //2位: 增强的磁盘驱动控制器功能
    	andw	$1, %cx
    //ZF=1时,既表示不支持LBA,则跳转到CHS模式
    	jz	chs_mode
    	
    //接下来进行LBA模式处理函数
    lba_mode:
    	movl	0x10(%si), %ecx
    
    //将disk_address_packet处的地址赋值到SI寄存器中
    	movw	$ABS(disk_address_packet), %si
    
    //将disk_address_packet地址减1处的值赋值为1,既将此处的mode设置为1,表示进行LBA扩展模式。mode取值描述:
    //1:表示执行LBA扩展模式
    //0:表示执行CHS寻址模式
    	movb	$1, -1(%si)
    	
    //直接寻址将stage2_sector代表的地址,赋值给EBX寄存器,该地址的值为1
    	movl	ABS(stage2_sector), %ebx
    
    /*
    disk_address_packet地址处的数据与磁盘参数的对应关系:
    	struct dap {
    	u8 len; 一般长度取值为0x10,表示dap结构长度为16字节
    	u8 zero; 默认必须为0
    	u16 nsector: 实际上是8位有效,表示读取的扇区数,一般取值从1~127
    	u16 addr: 内存地址addr
    	u16 segment: 段选择子的值
    	u32 sectorLo: 表示LBA扇区号的低4字节
    	u32 sectorHi:表示LBA扇区号的高4字节 
    }
    */
    //将0x0010值赋值到disk_address_packet地址处,既si[0]=0x10,si[1]=0x00。
    //表示要传输的dap大小为0x10,
    	movw	$0x0010, (%si)
    
    //将立即数1赋值到disk_address_packet地址,既si[2]=0x1
    //表示要传输的扇区数为1个扇区
    	movw	$1, 2(%si)
    	
    //将EBX寄存器指向的地址处的值,也就是1赋值给si[8]=0x1。
    //既要读取的起始扇区号为1,其实就是从第二个扇区开始读取,一共读取1个扇区。
    //该编号就是LBA的扇区编号。
    	movl	%ebx, 8(%si)
    
    //将0x7000的值赋值给si[6]和si[7],既si[6]=0x00,si[7]=0x70
    	movw	$STAGE1_BUFFERSEG, 6(%si)
    
    //将EAX寄存器清零,然后设置si[4]=0,si[5]=0
    //既数据缓存地址为0x7000:0x0000
    //后续通过BIOS中断读取的1个扇区的内容,就读取到0x7000:0x0000地址对应的内存中。
    	xorl	%eax, %eax
    	movw	%ax, 4(%si)
    //设置si[12]~si[15] = 0x0
    	movl	%eax, 12(%si)
    
     //AH寄存器设置位0x42,调用BIOS0x13号中断,进行扩展读操作。
    	movb	$0x42, %ah
    	int	$0x13
    
    //进位标志位寄存器CF=0时,表示读取成功,意味着支持扩展读。
    //中断执行失败,将CF设置为1,表示不支持扩展读。
    //jc为有条件转移执行,当CF设置位1时,重新进入CHS模式进行操作。
    	jc	chs_mode
    
    //将立即数0x7000赋值给BX寄存器,该值会在copy_buffer中使用。
    //在copy_buffer中会将0x7000:0x0000出的内容拷贝到0x0800:0x0000处
    //经过地址换算,最后第二扇区的代码会拷贝到0x8000地址处(0x0800*16+0x0000)
    	movw	$STAGE1_BUFFERSEG, %bx
    //跳转到copy_buffer标号处进行拷贝。
    	jmp	copy_buffer
    	
    //进入CHS模式
    chs_mode:	
    //CHS模式下,0x13中断,0x08功能可以做磁盘参数检测
    //DL:软盘驱动器的个数
    //DH:磁头数,取值范围为0~255
    //CH:磁道柱面数的一部分,总共用10位表示,CH全部8位加上CL的高2位,柱面数取值范围0~1023。
    //CL: 低6位表示每个磁道柱面的扇区数,取值范围为0~63。
    	movb	$8, %ah
    	int	$0x13
    //上述参数检测执行成功的话,CF会设置位0,否则位会设置位1
    //当CF=0(执行成功时)会跳转到final_init标号处执行具体的CHS寻址读取操作。
    	jnc	final_init
    
    //再次检查一下是否是磁盘,如果是不是磁盘的话跳转到floppy_probe标号处,进行软盘处理
    	testb	$STAGE1_BIOS_HD_FLAG, %dl
    	jz	floppy_probe
    
    //确定是磁盘的话,而且前面做磁盘参数检测也失败了
    //只能跳转到hd_probe_error标号处,打印“Hard Disk”,然后陷入死循环,game over ^_^。
    	jmp	hd_probe_error
    
    //检查完磁盘参数之后继续执行具体的CHS寻址读取操作
    final_init:
    //将sectors处的地址值赋值给SI寄存器
    	movw	$ABS(sectors), %si
    //sectors地址减1的位置赋值为0,既将mode赋值为0,采用CHS寻址模式
    	movb	$0, -1(%si)
    
    //填充Disk Address Packet(DAP)结构,该结构在LBA扩展模式下有说明
    //清空EAX寄存器,将DH寄存器(参数检查时获得的磁头数)赋值给AL寄存器
    //将AX寄存器加1,变为磁头数的数量
    //将EAX寄存器的值(磁头数)赋值给si[4],si[5],si[6]和si[7]
    	xorl	%eax, %eax
    	movb	%dh, %al
    	incw	%ax
    	movl	%eax, 4(%si)
    
    //清空DX寄存器
    //将CL寄存器的值(保存着之前获取的扇区数)赋值给DL寄存器
    //将DX的值左移两位,将原有的DL高2位代表的柱面数放到DH的低两位中,此时DL中的扇区数扩大了4倍
    //将CH寄存器中的柱面数的值赋值给AL中
    //将DH寄存器中的柱面数的值放入到AH中。此时AX寄存器中存储的就是柱面数。
    	xorw	%dx, %dx
    	movb	%cl, %dl
    	shlw	$2, %dx
    	movb	%ch, %al
    	movb	%dh, %ah
    
    //AX寄存器的值加1,此时可得到真实的柱面数量
    //将柱面数赋值给si[8],si[9]
    	incw	%ax
    	movw	%ax, 8(%si)
    
    //清空AX寄存器
    //将DL赋值给AL寄存器,同时让AL右移两位之后得到原来的扇区数
    	xorw	%ax, %ax
    	movb	%dl, %al
    	shrb	$2, %al
    
    //将扇区数赋值给si[0],si[1],si[2]和si[3]
    	movl	%eax, (%si)
    
    setup_sectors:
    //将stage2_sector标号代表的地址指向的值1赋值给EAX寄存器,该值为1,代表stage2开始的扇区号
    	movl	ABS(stage2_sector), %eax
    
    //清空EDX寄存器
    	xorl	%edx, %edx
    //16位被除数放在AX寄存器,8位除数为源操作数,8位的商,存储在AL中,8位余数存储在AH中
    //32位被除数放在DX,AX中。其中DX为高位,16位除数为源操作数,16位的商,存储在AX中,16位余数在DX中
    //64位被除数在EDX,EAX中,其中EDX为高位,32位除数为源操作数,32位的商,存储在EAX中,32位余数在EDX中
    //此处的被除数位1,除数位为扇区数,用stage2开始的扇区号除以每个磁道包含的扇区数
    	divl	(%si)
    
    //将DL寄存器中存放的余数赋值给si[10],余数既stage2开始的扇区号,除数为磁道号
    	movb	%dl, 10(%si)
    
    //清空EDX寄存器
    //然后用被除数AX中的值(上一步的商),除以si[4]对应地址存放的单柱面最大磁头数
    //其中商为stage2所在的柱面号,余数为stage2开始的磁道号。
    	xorl	%edx, %edx
    	divl	4(%si)
    
    //将DL寄存器中的值(stage2开始的磁道号)赋值给si[11]
    	movb	%dl, 11(%si)
    
    //将AX寄存器中的值(stage2所在的柱面号)赋值给si[12]
    	movw	%ax, 12(%si)
    
    //比较si[8]所代表地址指向的数与AX寄存器的值
    //其中si[8]指向的值为柱面数,而ax代表上面div操作的商。
    //当柱面号超过了最大值时跳转到geometry_error,打印“Geom Error”并死循环,然后 Game Over ^_^
    //stage2所在柱面数合法,则继续向下执行
    	cmpw	8(%si), %ax
    	jge	geometry_error
    
    //将si[13]指向的柱面号的高位的值赋值给DL寄存器
    	movb	13(%si), %dl
    
    //将DL寄存器的值左移6位,然后将si[10]指向的stage2的扇区号赋值给CL寄存器
    	shlb	$6, %dl	
    	movb	10(%si), %cl
    
    //CL寄存器加1,得到stage2的真实的扇区号表示
    //然后通过orb或运算命令,将CL寄存器的高两位存储着柱面号,低6位存储着扇区号
    	incb	%cl
    	orb	%dl, %cl
    //将si[12]指向的值(既柱面号)赋值给CH寄存器。
    	movb	12(%si), %ch
    
    //将DX寄存器出栈,原栈中存储的DX寄存器的低8位为磁盘号。
    	popw	%dx
    	
    //将si[11]指向的磁道号赋值给DH寄存器中。
    	movb	11(%si), %dh
    
     //将0x7000赋值给BX寄存器,将BX寄存器的值赋值给ES寄存器
    	movw	$STAGE1_BUFFERSEG, %bx
    	movw	%bx, %es	/* load %es segment with disk buffer */
    
    //清空BX寄存器,将0x0201赋值给AX寄存器,既AH=0x02 AL=0x01
    	xorw	%bx, %bx	/* %bx = 0, put it at 0 in the segment */
    	movw	$0x0201, %ax	/* function 2 */
    	int	$0x13
    //以上设置的参数对照功能:
    //AH:0x02
    //AL:需要读取的扇区数
    //CH:起始的柱面号的值
    //CL:低6位为需要的扇区号,高2位为起始的柱面号的值
    //DH:起始的磁头号的值
    //DL:对应的磁盘号
    //ES:BX   segment:offset,读取的缓存地址
    
    //中断执行失败,CF=1,执行成功,CF=0。当执行失败是打印“Read Error”,然后执行死循环
    	jc	read_error
    //读取数据成功,将ES寄存器中的0x7000赋值给BX寄存器,然后执行后续的copy_buffer
    	movw	%es, %bx
    	
    copy_buffer:
    //将0x800赋值给ES寄存器
    	movw	ABS(stage2_segment), %es
    
    //将通用寄存器全部压入堆栈
    //将DS寄存器的值压入堆栈,后续会将该寄存器污染
    	pusha
    	pushw	%ds
    	
    //将0x100赋值给CX寄存器,CX为计数器寄存器,该值表示后续双字节拷贝的具体次数
    //将BX寄存器的值0x7000赋值给DS寄存器
    //清空SI寄存器和DI寄存器
    //使用cld将方向标志位DF复位,既设置DF=0,其相反的指令为std
    //DF=0表示向高地址增加,DF=1表示向低地址减少。cld复位DF之后,将向高地址增加。
    	movw	$0x100, %cx
    	movw	%bx, %ds
    	xorw	%si, %si
    	xorw	%di, %di
    	cld
    	
    //rep重复执行后面的movsw,rep受ECX寄存器控制,每执行依次,ECX寄存器依次减1,当ECX寄存器为0时不再执行。
    	rep
    //movsw每次传输一个word(双字)宽度的数据。
    //movsw或者movsb用来将DS:SI指向的存储单元中的数据装入ES:DI指向的存储单元中。
    //此处也就是将(0x7000:0x0000,从磁盘中读取的第二扇区的数据)装入到(0x0800:0x0000)地址处,依次装入双字节
    //由于CX寄存器中的值为0x100,则将拷贝0x100次,每次2个字节,一共拷贝了512字节,也就是刚好一个扇区的数量。
    	movsw
    
    //将DS寄存器的值出栈
    //然后将所有通用寄存器的值出栈
    	popw	%ds
    	popa
    
    //跳转到0x8000地址处,执行下一阶段命令,此时就开启了GRUB引导的后续阶段
    //实模式下的地址转换:address = es * 16 + di => (0x0800:0x0000 转化为 0x800* 16 + 0x0000 = 0x8000)
    	jmp	*(stage2_address)
    //这里就是大结局了
    
    geometry_error:
    	MSG(geometry_error_string)
    	jmp	general_error
    
    hd_probe_error:
    	MSG(hd_probe_error_string)
    	jmp	general_error
    
    read_error:
    	MSG(read_error_string)
    
    general_error:
    	MSG(general_error_string)
    
    stop:	jmp	stop
    
    notification_string:	.string "GRUB "
    geometry_error_string:	.string "Geom"
    hd_probe_error_string:	.string "Hard Disk"
    read_error_string:	.string "Read"
    general_error_string:	.string " Error"
    
    //在1标号的位置,将0x0001赋值给BX寄存器
    //将xe赋值给AX寄存器的高8位。
    //然后执行中断,中断号为16,既屏幕显示I/O
    //功能OE为在Teletype模式下显示字符,AL=字符 BH=页码 BL=模型模式下的前景色
    //我们可以发现这次每次取出来一个字节同时调用bios中断显示出来。
    1:
    	movw	$0x0001, %bx
    	movb	$0xe, %ah
    	int	$0x10		/* display a byte */
    //是通过call message来调用的
    //在MSG(x)中,将x对应的物理地址赋值到si寄存器
    message:
    //lodsb取si寄存器地址对应的一个字节byte到AX寄存器的低8位中。
    	lodsb
    //cmpb是比较指令,比较AL寄存器中的值是否是立即数0。
    //不相等的话,零标志位ZF寄存器为0,相等的话ZF的值为1。
    //当字符串到达尾部时,取出的字节才会是0值。
    	cmpb	$0, %al
    //条件转移指令,jne是用来比较ZF寄存器是否为0,为0的话跳转到后面的标号处。
    //此处为1b,既向后(也就是之前的代码)到标号1出,也就是上面的1标号位置。
    	jne	1b	/* if not end of string, jmp to display */
    //通过lodsb依次提取byte至AL中,当到字符串末尾时,执行ret返回
    	ret
    
    	. = _start + STAGE1_WINDOWS_NT_MAGIC
    nt_magic:	
    	.long 0
    	.word 0
    
    part_start:	
    	. = _start + STAGE1_PARTSTART
    
    probe_values:
    	.byte	36, 18, 15, 9, 0
    
    floppy_probe:
    //将probe_values标号对应地址前一个地址赋值给SI寄存器
    	movw	$ABS(probe_values-1), %si
    
    //此处会有一个循环,分别对软盘的扇区进行尝试
    probe_loop:
    //将AH设置位0,通过BIOS的13号中断,可以重置软盘驱动器。
    	xorw	%ax, %ax
    	int	$0x13
    
    //将SI寄存器加1,既SI寄存器其实指向了存储36整数处的地址,既probe_values后面一个字节位置。
    //寄存器寻址,将SI寄存器指向的地址处的数据赋值给CX寄存器的低8位。
    //36、18、15、9、0等数字代表扇区数。
    	incw	%si
    	movb	(%si), %cl
    
    //判断扇区数是否位0,不为0的话,ZF=0,跳转到下面的标号1位置,进行扇区有效性检测
    //为0的话,也就是扇区可用值已经都尝试过一遍了,没有合适的,于是ZF设置为1,打印"Floppy Error", Game Over ^_^。
    	cmpb	$0, %cl
    	jne	1f
    
    	MSG(fd_probe_error_string)
    	jmp	general_error
    
    fd_probe_error_string:	.string "Floppy"
    
    //进行扇区有效性检测
    1:
    //将0x7000赋值给BX寄存器,将0x201赋值给AX寄存器,将CH设置为0,DH设置为0
    //调用BIOS的0x13号中断
    //其中各项寄存器的参数说明:
    //AL:要读取的扇区数
    //AH:功能号
    //CH:起始的柱面数
    //CL:起始的柱面数,用到了高两位。低6位表示扇区号
    //DL:磁盘号
    //ES:BX: 内存位置,segment:offset
    //此处AX赋值为0x201,既AH=0x02执行0x02号读功能,AL=0x01表示读取一个扇区
    //起始柱面号为0x0,磁盘号为0x00,表示第一块软盘。
    //CL表示扇区号依次从36、18、15、9中取值。
    	movw	$STAGE1_BUFFERSEG, %bx
    	movw	$0x201, %ax
    	movb	$0, %ch
    	movb	$0, %dh
    	int	$0x13
    
    //当读取成功时,进位标志位寄存器CF=0,表示该扇区有效。
    //当读取失败时,进位标志位寄存器CF=1,跳转到probe_loop选择其他扇区数再做尝试。
    	jc	probe_loop
    
    //将DH寄存器设置1,磁头取值为0~1,1就说明有两个磁头
    //将CH寄存器设置为79,柱面取值范围为0~79,79表示一共有80个柱面
    	movb	$1, %dh
    	movb	$79, %ch
    
    //然后跳转到final_init设置磁头、柱面和扇区的对应值,使用CHS传统的寻址方式来读取grub的后续阶段
    //从这里可以看出,针对CHS模式,软盘其实多做了一步扇区检测,而硬盘却不需要,因为后续阶段就是从第二个扇区开始的。
    //始于floppy_probe,终于final_init
    	jmp	final_init
    
    	. = _start + STAGE1_PARTEND
    
    	.word	STAGE1_SIGNATURE
    

    展开全文
  • CSS元素选择父元素

    2021-06-11 08:55:54
    CSS子元素选择父元素发布时间:2018-10-09 23:21,浏览次数:6139, 标签:CSS通常一个CSS选择器都是从上往下选择的,通过父元素选择子元素,那么能不能通过子元素选择父元素呢? 1 2 如果我想选择包含 a.active 的 li...

    CSS子元素选择父元素

    发布时间:2018-10-09 23:21,

    浏览次数:6139

    , 标签:

    CSS

    通常一个CSS选择器都是从上往下选择的,通过父元素选择子元素,那么能不能通过子元素选择父元素呢?

    如果我想选择包含 a.active 的 li 该怎么实现呢? 目前我们学到的CSS好像是没有办法的,不过今天要将的一个CSS伪类 :has()

    就有这个功能,虽然还处于草案阶段,但是还是可以提前了解一下。

    li:has(> a.active){ color:red; }

    除了表示包含,:has 还可以表示兄弟跟随关系

    div:has(+ p){ color:red; }

    表示选择

    标签,前提是这个div标签必须是被一个

    紧跟着的。此外还可以与:not 一起使用

    article:not(:has(a)){ color:red; }

    表示不包含 的 标签。注意这里 :not 和 :has 的先后顺序,不同顺序代表不同的意思

    article:has(:not(a)){ color:red; }

    表示包含非 的 标签

    其实我们前面讲过的 :focus-within 也是一个通过子元素选择父元素的伪类,只不过条件只能是子元素是否获取焦点, 而 :has 则更灵活和强大。

    form:focus-within{ background-color:black; }

    如果通过 :has 实现的话, 可以这样写

    form:has(:focus){ background-color:black; }

    展开全文
  • 通常一个CSS选择器都是从上往下选择的,通过父元素选择子元素,那么能不能通过子元素选择父元素呢?12如果我想选择包含 a.active 的 li 该怎么实现呢? 目前我们学到的CSS好像是没有办法的,不过今天要将的一个CSS伪...

    通常一个CSS选择器都是从上往下选择的,通过父元素选择子元素,那么能不能通过子元素选择父元素呢?

    如果我想选择包含 a.active 的 li 该怎么实现呢? 目前我们学到的CSS好像是没有办法的,不过今天要将的一个CSS伪类 :has() 就有这个功能,虽然还处于草案阶段,但是还是可以提前了解一下。

    li:has(> a.active){

    color:red;

    }

    除了表示包含,:has 还可以表示兄弟跟随关系

    div:has(+ p){

    color:red;

    }

    表示选择

    标签,前提是这个div标签必须是被一个

    紧跟着的。此外还可以与:not 一起使用

    article:not(:has(a)){

    color:red;

    }

    表示不包含 的 标签。注意这里 :not 和 :has 的先后顺序,不同顺序代表不同的意思

    article:has(:not(a)){

    color:red;

    }

    表示包含非 的 标签

    其实我们前面讲过的 :focus-within 也是一个通过子元素选择父元素的伪类,只不过条件只能是子元素是否获取焦点, 而 :has 则更灵活和强大。

    form:focus-within{

    background-color:black;

    }

    如果通过 :has 实现的话, 可以这样写

    form:has(:focus){

    background-color:black;

    }

    到此这篇关于CSS子元素选择父元素的实现的文章就介绍到这了,更多相关CSS子元素选择父元素内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!

    展开全文
  • 旁站,域名,C的含义

    千次阅读 2021-03-01 22:26:12
    旁站入侵就是旁边的站点,所以叫它旁站,比如我们在入侵目标网站的时候,我们没有发现什么漏洞无法拿下的时候,我们会选择旁站入侵,旁站入侵就是找和目标网站同服务器下的某一个网站,从那里突破拿到旁
  • 在MySQL查询中选择多个列/字段

    千次阅读 2021-02-02 07:33:30
    是的,你可以这样做。您需要的诀窍是有两种方法可以从表服务器中获取表。一种方法是......FROM TABLE A另一种方式是FROM (SELECT col as name1, col2 as name 2 FROM ...) B请注意,select子句及其周围的括号是一个...
  • 寄存器

    千次阅读 2021-01-15 12:46:20
    一、段寄存器有哪些 ? 段寄存器有ES、CS、SS、DS、FS、GS、LDTR、TR共8个。 通常我们用汇编读写某一个地址时,如下: Mov dword ptr ds:[0x... 段选择子(Select) 段属性(Attributes) .
  • 先来HTML代码 <div class="box"> <div>1</div> <div>2</div> <div>...选择box里面第一个div添加样式: .box > div:first-child { color: #7F7F7F;
  • 组织特异性启动的筛选方法

    千次阅读 2021-01-12 05:06:41
    组成型启动(如CMV,EF1A,UBC等)在大部分细胞中都能维持较稳定的表达活性,但其应用主要局限于细胞层面。相比而言,组织特异性启动可调控外源基因在某些特定来源的细胞或组织部位中表达,因此更加适用于在体研究。...
  • 全国青少年软件编程(Scratch)等级考试试卷(三级)(2020-09) 分数:100.00 题数:38 一、单选题(共25题,每题2分,共50分) ... ...角色隐藏之后就不会在舞台上显示,虽然有克隆,但是并没有克隆体启动程序,...
  • 什幺是程序?*在计算机科学中,程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部份代码,由一个或多个语句块组成。它负责完成某项特定任务,...
  • 3.描述符(Segment Descriptor)

    千次阅读 2021-09-19 15:45:16
    段选择子与段描述符关系 <1>.通过段选择子查找对应段描述符 3.段描述符属性探测 <1.>P位 <2>.G位 <3>.S位 4.TYPE域 数据段测试 <2>.代码段测试 <5>.DB位 <1.>CS段影响 <2>.SS段影响 <3>.向下扩展数据段 0.全局描述符...
  • C语言创建进程

    千次阅读 2020-12-29 20:12:16
    程序运行的时候,可以创建与自己关联的进程,创建了这个子进程之后,可以选择等待这个子进程执行完毕,也可以让进程与自己并行执行,还可以终止自己转而执行进程。这些操作都是通过一系列相似而又有细微区别的...
  • js 下获取元素的方法

    千次阅读 2020-12-19 12:50:13
    这个是一个小模型: 123 在上面这代码中,如果使用以下js代码: var oDiv=document.getElementByTagName("div")[0]; alert(oDiv.firstChild.nodeName) 死活都得不出结果,后来查了才知道,原来:在现代浏览器下,...
  • 展开全部中断程序是在...因为中断是由系统调用的,不知道什么时候中断,所以你可以选择允许中断或者不允许中断,这就好比你正在干工作时,电话铃响了,你可以选择接电话也可以选择不接电话,中断就好比电话铃,你不...
  • 按照材料分类有聚氨酯灌封胶、有机硅灌封胶和环氧树脂灌封胶,对于选择软胶还是硬胶,其时两种都可以灌封、防水绝缘,如果要求耐高温导热那么建议使用有机硅软胶;如果要求耐低温、那么建议使用有聚氨酯软胶;如果...
  • 宏与程序的区别

    千次阅读 2021-02-05 11:49:32
    宏和程序都是为了简化源程序的编写,提高程序的可维护性,但是它们二者之间存在...而程序代码在目标程序中只出现一次,调用程序是执行同一程序,因此,目标程序也得到相应的简化;3 、宏引用时,参数是通过...
  • 2021 年全国大学生电子设计竞赛实施过程说明

    千次阅读 多人点赞 2021-07-22 09:39:35
    高职高专学生原则上选择“高职高专组”题目,但也可选择“本科组”题目,并严格按“本科组”题目的标准进行评审。只要参赛队中有本科生(含已专升本的学生),该队只能选择“本科组”题目。每支报名参赛队必须在赛区...
  • 载波间隔

    千次阅读 2021-06-23 09:44:37
    载波是通信系统里的频域资源概念,载波可以认为是可独立调制的一小频域资源,一般来说载波>载波。一个信道有一个或者多个载波,载波就是一个个载波,比如100MHz带宽里,假设15KHz是一个子载波,这...
  • 这里我介绍几个原生js获取元素节点的方法:一、通过标签的属性值获取后代节点以getElementBy开头的方法,可以根据具体的属性获取元素的后代节点。这些方法不只会获取节点,他也会获取到所...
  • CSS选择器之伪类选择器(元素)

    千次阅读 2021-01-16 18:38:44
    :last-child 选择某个元素的最后一个元素 :first-of-type [CSS3]选择一个上级元素下的第一个同类元素 :last-of-type [CSS3]选择一个上级元素的最后一个同类元素 :only-child [CSS3]如果某个元素是父元素中...
  • }, 引用小姐姐的一话 但是,(又敲黑板!!!)我们给后台传过去了父节点,如果有反显的情况下(如:修改,查看功能),一旦有父节点,节点又将会全部勾选!!这种情况下又该怎么办呢? 思路如下: 1.循环遍历出...
  • Scratch一级A卷真题解析(2020.8) 一、单选题(共25题,每题2分,共50分) 1、在一个Scratch项目中,让角色拥有了“思想”的是() A.造型 B....考点分析:考查各个功能区的功能,项目最主要的核心就是脚本也叫...
  • 关注公众号:麒麟随笔,走着瞧) 这篇文章其实是我即将发布的文章《Cocos Creator 3.x后期效果框架源码剖析》的序。 但这个序太长了,甚至写着写着就跑题了,面对这思如泉涌的结晶又舍不得删。 怎么办呢?于是开了...
  • windows-sys15:windows11 Android 系统安装和apk应用安装

    千次阅读 热门讨论 2021-10-23 19:04:10
    1 安装条件 必须是windows11系统,系统版本号为 22000.282 如果你的电脑满足windows11的硬件...2 安装Android 系统 2.1 Microsoft Store下载安装 下载地址:https://www.microsoft.com/store/productId/9P3395VX91NR
  • 汇编课设-电子闹钟

    千次阅读 2021-02-22 23:13:13
    设计一个电子闹钟,用汇编语言编写电子闹钟程序,在唐都实验箱上实现硬件接线,通过对计数器8254,可编程并行接口芯片8255以及中断控制芯片8259的应用,实现调用显示程序、调用闹钟程序、调用整点报时程序、...
  • 基于51单片机的电子时钟

    千次阅读 2021-04-24 11:39:36
    1.3 任务目标 使用DS1302芯片作为计时设备,用6个7LED数码管或者LCD162作为显示设备,实现时钟功能; 功能要求: (1)可以分别设定小时、分钟和秒,复位后时间为00:00:00; (2)秒钟复位功能,秒复位键按下后...
  • 前言: 因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods ... } 组件 这里已下拉框为例 template代码 <template> <div> <div class="combox"> 请选择角色"> ...
  • 均衡器的设置和参数

    千次阅读 2021-01-14 16:09:38
    本帖最后由 GTXarrow 于 2015-2-2 ...10均衡器表示有10个可调节节点。节点越多,便可以调节出更精确的曲线,同时难度更大。从左到右的顺序是从低频到高频[100Hz, 200Hz, 400Hz, 600Hz, 1KHz, 3KHz, 6KHz, 12KHz, ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 675,474
精华内容 270,189
关键字:

段选择子