标签:跳转 incr 虚拟 flag tor 参数 开始 read 模式
其中“-e start”指出了bootblock的入口地址为start,而“-Ttext 0x7C00”指出了代码段的起始地址为0x7c00。也就导致start位置的虚拟地址为0x7c00
bootloader程序被bios从引导扇区载入,构建程序时指定了代码段初始地址为0x7c00,并且起始地址从start开始。bios加载bootloader后cs:ip为0x00007c00的物理内存地址开始执行。由于此时处于实模式,并且链接程序时指定了程序代码段的初始偏移地址为0x7c00,所以此处即为bootloader的代码(CS段寄存器位0)
# 链接程序时会指定程序代码段地址为0x7c00,所以相应代码在内存中以0x7c00作为起始地址 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader .globl start start: .code16 # Assemble for 16-bit mode,即8086模式,其为16位,所以此代码后面的一个word为16位2字节, cli # Disable interrupts,关闭终端 cld # String operations increment, # Set up the important data segment registers (DS, ES, SS). xorw %ax, %ax # Segment number zero,把各个寄存器都初始化为0 movw %ax, %ds # -> Data Segment movw %ax, %es # -> Extra Segment movw %ax, %ss # -> Stack Segment
# Enable A20: # For backwards compatibility with the earliest PCs, physical # address line 20 is tied low, so that addresses higher than # 1MB wrap around to zero by default. This code undoes this. seta20.1: inb $0x64, %al # Wait for not busy testb $0x2, %al jnz seta20.1 movb $0xd1, %al # 0xd1 -> port 0x64 outb %al, $0x64 seta20.2: inb $0x64, %al # Wait for not busy testb $0x2, %al jnz seta20.2 movb $0xdf, %al # 0xdf -> port 0x60 outb %al, $0x60
lgdt gdtdesc #初始化gdtr寄存器,gdtr寄存器的值为gdtdesc地址的值。正好是48个字节,采用小端模式
gdtr寄存器大小为48位,即6个字节大小,lgdt指令会从gdtdesc代表的地址处读取6个字节大小的数据装入gdtr寄存器,所以其gdtdesc处地址英存放4字节的gdt表地址和2字节gdt表的大小
.data # Bootstrap GDT .p2align 2 # force 4 byte alignment gdt: # gdt表的内容,可以看到包含了三个部分 SEG_NULLASM # null seg SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel gdtdesc: # gdtr寄存器会载入这个地址的值,48位共6个字节 .word 0x17 # sizeof(gdt) - 1 表示gdt表的大小为24个字节,由于前面有.code16,所以其word大小为一个字2字节,即为00000000 00010111 .long gdt # address gdt 表示gdt的表的地址,其为两个字
需要注意,x86采用的小端模式,即低位为低地址,高位高地址。而程序的可执行文件的内存地址随着代码的行数增加向下不断增大,所以gdtr寄存器的高位为gdt的地址(4字节),低位为0x0017(2字节),表示gdt表大小为24字节。
SEG_NULLASM和SEG_ASM为两个宏,其具体分析如下:
/* Normal segment */ #define SEG_NULLASM \ .word 0, 0; .byte 0, 0, 0, 0
.word
就地生成一个字长度(此处2字节,因为前面伪代码指定.code16)的数, .byte
就地生成一个字节的数。上述代码生成两个字(每个字2字节)长度的数0,接着生成4个字节的数0。
#define SEG_ASM(type,base,lim) \ .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); .byte (((base) >> 16) & 0xff), (0x90 | (type)), (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) /* Application segment type bits */ #define STA_X 0x8 // Executable segment #define STA_E 0x4 // Expand down (non-executable segments) #define STA_C 0x4 // Conforming code segment (executable only) #define STA_W 0x2 // Writeable (non-executable segments) #define STA_R 0x2 // Readable (executable segments) #define STA_A 0x1 // Accessed
>>表示将lim参数右移12位,高位补0,然后和0xFFFF进行与。以SEG_ASM(STA_X|STA_R, 0x0, 0xFFFFFFFF) 为例, 首先对于上面的两个字word,0xFFFFFFFF右移12位变为0x000FFFFF,然后与0x0000FFFF项and,则其最后结果为0xFFFF(一个字即2个字节长度)。后面一个字为0x0000。然后对于后续的4个byte,第一个为0x00,依次类推
gdt:
.word 0, 0;
.byte 0, 0, 0, 0;
.word 0xffff, 0;
.byte 0, 0x9a, 0xcf, 0
.word 0xffff, 0;
.byte 0, 0x92, 0xcf, 0
已知一个段描述符的大小为8字节,即64位,其构造如下,所以根据下图,我们发现对于一个8字节的代码段描述符,0xffff对应段描述符0-15位,0字对应16-31位,然后4个byte对应高位。所以最后得到的Base基地址就是0x00000000。
ljmp $PROT_MODE_CSEG, $protcseg # 会把CS寄存器的值变为PROT_MODE_CSEG 变量,即为 0000000000001000,其中偏移量为0000000000001,所以偏移数值为1*8=8 # EIP指令寄存器的数值变为 $protcseg代码段的值 .code32 # Assemble for 32-bit mode,因为此时已经处于保护模式下 protcseg: # Set up the protected-mode data segment registers movw $PROT_MODE_DSEG, %ax # Our data segment selector movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %fs # -> FS movw %ax, %gs # -> GS movw %ax, %ss # -> SS: Stack Segment
ljmp长跳转重新初始化了代码段寄存器CS的值,其中CS的前12位为0x001,将其乘以8为0x008作为gdt表的偏移值来选择段描述符,所以其选择即为CS段描述符,其Base为0,偏移地址即为protcseg的地址。需要注意由于我们bootloader程序代码段在实模式加载到内存时其从0x00007C00物理地址向高位内存加载,而当我们在分段模式下设定Base地址为0时,偏移地址为protcseg地址时,在保护模式下正好能运行bootloader中protcseg段代码。
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector,该变量为代码段选择子,0000000000001000 .set PROT_MODE_DSEG, 0x10 # kernel data segment selector,该变量为数据段选择子,0000000000010000 .set CR0_PE_ON, 0x1 # protected mode enable flag,该变量为 00000001,使能开关
之后设定相应的寄存器,其选择的相应段描述符Base地址都为0。
# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) movl $0x0, %ebp movl $start, %esp call bootmain # 调用c编写的bootmain函数
由于段地址向下递减,所以我们设定初始栈顶指针起始位置在bootloader下,然后即可调用函数
标签:跳转 incr 虚拟 flag tor 参数 开始 read 模式
原文地址:https://www.cnblogs.com/stankangyong/p/10363768.html