码迷,mamicode.com
首页 > 其他好文 > 详细

实验1-----bootloader运行

时间:2019-02-12 09:13:25      阅读:298      评论:0      收藏:0      [点我收藏+]

标签:跳转   incr   虚拟   flag   tor   参数   开始   read   模式   

1.bootloader启动代码分析

1.1寄存器初始化为0(实模式)

技术图片

    其中“-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

 

1.2使能A20(实模式)

  

    # 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

 

1.3初始化gdt表和gdtr寄存器(实模式)

    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。

  技术图片

 

1.4保护模式初始化段寄存器(保护模式)

    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。

1.5设定bootloader运行时的栈(保护模式)

    # 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下,然后即可调用函数

 技术图片

 

实验1-----bootloader运行

标签:跳转   incr   虚拟   flag   tor   参数   开始   read   模式   

原文地址:https://www.cnblogs.com/stankangyong/p/10363768.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!