免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
论坛 操作系统 BSD bios分析
123下一页
最近访问板块 发新帖
查看: 20111 | 回复: 20
打印 上一主题 下一主题

bios分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-11-25 13:24 |只看该作者 |正序浏览
qiuhan
2007.10.13

我们并没有实际去研读物理bios的代码,而是查看了bochs中的bios虚拟实现,主要出于如下考虑:
1 对于物理机的bios需要特殊的硬件进行调试,我们没有
2 对于不同的系统构架,bios很可能不同,我们偏重于功能性的研读bios,对于理解os的实现来说,
  已经足够了;而且,这也会是理解物理bios的一个很好的起点。
另外,我们更多的是关注bios中对PCI和ACPI的实现,对于POST流程,我们只说了一个大概。

下面我们就开始介绍一些基础知识:
the layout and contents of the first Meg of memory[1][2]:
0x0 - 0x3ff: 256个bios中断向量
0x400 - 0x4ff: 255B BDA(BIOS Data Area) 保存bios检测的结果,如:
        0x40E: LPT4 I/O base address 或者 EBDA(Extended Bios Data Area)
                如果存在EBDA,其值为0x9FC0,查找RSDP的一种方法就是从EBDA查找(AcpiTbFindRsdp)
        0x410: Equipment Word
        0x472: Soft reset flag 系统启动时会在该地址写入1234h告诉bios下次跳过内存检测
                参见i386/i386/locore.s
        0x475: Number of hard disk drives(参见boot0)
0x500 - 0x9Fbff: dos, etc
0x9FC00 - 0x9feff: EBDA(Extended Bios Data Area) 768B
0x9ff00 -- 0x9ffff: boot device tables 256B
0xA0000 - 0xAffff: Graphics Video memory (EGA and above)
0xB0000 - 0xBffff: Graphics area for EGA and up
0xC0000 - 0xCffff: additional ROM-BIOS & video memory
0xD0000 - 0xDffff: ROM cartridges
0xE0000 - 0xEffff: ROM cartridges
0xF0000 - 0xFDfff: IBM PC ROM BASIC
0xFE000 - 0xFFFEF: ORIGINAL IBM PC ROM BIOS
        0xfe05b : POST Entry Point
0xFFFF0 - 0xFFFF4: Power-up Entry Point(RESET JUMP)
0xffff5 : ASCII Date ROM was built - 8 characters in MM/DD/YY
0xffffe : System Model ID

bios的作用不仅仅只是POST,在os启动之后还会为os提供支持,做幕后英雄。以BSD为例:
1 pci_routing_table_structure: "$PIR" pci_pir_open调用bios_sigsearch在0xe0000-0x100000之间查找"$PIR"(或者"_PIR")。
2 bios32_structure: "_32_" signature
bios32入口,在BSD启动中bios32_init调用bios_sigsearch在0xe0000-0x100000
之间查找"_32_",得到入口地址bios32_entry_point保存在bios32_SDCI中。
然后以0x49435024("$PCI")为参数调用bios32_SDlookup,实际上会调用bios32_entry_point来得到pcibios的入口地址pcibios_protected,保存在PCIbios中。
pci_pir_biosroute中会通过bios32调用pcibios_protected来为一个特定的设备路由中断。

pci配置寄存器的读写:
每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域. 每个 PCI 域可以占用多达 256 个总线. 每个总线占用 32 个设备, 每个设备可以是一个多功能卡(例如一个声音设备, 带有一个附加的 CD-ROM 驱动)有最多 8 个功能. 因此, 每个功能可在硬件层次被一个 16-位地址或者 key , 标识.
所有的 PCI 设备都有一个至少 256-字节配置寄存器地址空间, 前 64 字节是标准的, 而剩下的是依赖设备的(ldd3:ch12).
第一步是将寄存器的地址写入配置地址寄存器(0xcf8)中,寄存器的地址格式如下(pci22: p52):
31:配置使能位,在进行配置操作时必须将该位设置为1。
30-24:是保留位;
23-16:位是总线号,8位,最大256个总线
15-11位是设备号,5位,最大32个设备
10-8位是功能号,3位,最大8个功能,对于单功能设备,其值为0。
7-2是外部PCI设备的PCI配置空间寄存器偏移量, 6位,最大64个字节???
1-0: 只读,读时必须返回0
注意这里的Bit7-2寄存器偏移量占用6位,只能表示64个,不是有256个字节吗?
这里用到一个技巧,我们接下来会看看到。
第二步是对配置数据寄存器(0xcfc)进行读或写。
我们来回答上面的问题:
假设我们读的是long数据(4字节),那很容易,直接把Bit7-2左移2位
就可以得到8位的寄存器偏移了;
假设我们读的是word数据(2字节)或者就是一个字节,那么末2位从哪里得到呢?
pci把这个偏移给了配置数据寄存器。
我们来看实例:下例是读pci设备d上偏移为addr的配置寄存器:
static uint32_t pci_config_readw(PCIDevice *d, uint32_t addr)
{
    outl(0xcf8, 0x80000000 | (d->bus << 16) | (d->devfn << 8) | (addr & 0xfc));
    return inw(0xcfc + (addr & 2));
}
读的是一个word,那么addr的最后一位肯定为0.我们把addr用0xfc进行mask得到的是addr的Bit7-2.
丢失的是Bit1, Bit0一定为0; 然后我们读配置数据寄存器时,把Bit1传给了它(addr & 2).
我们来看pci是怎么来理解的:
static uint32_t pci_host_data_readw(void* opaque, pci_addr_t addr)
{
    PCIHostState *s = opaque;
    uint32_t val;
    if (!(s->config_reg & (1 << 31)))
        return 0xffff;
    val = pci_data_read(s->bus, s->config_reg | (addr & 3), 2);
#ifdef TARGET_WORDS_BIGENDIAN
    val = bswap16(val);
#endif
    return val;
}
这里参数addr是我们传给inw的值,s->config_reg是我们先前通过
outl(0xcf8, 0x80000000 | (d->bus << 16) | (d->devfn << 8) | (addr & 0xfc));
传给的值,由于传给的值含有0x80000000,所以在if时不会给我们返回0xffff;
另外,通过s->config_reg | (addr & 3)把地址给补齐了。


下面我们开始使用bochs来调试bios(我们讲述的只是post,不涉及setup):
# bochs -f /root/.bochsrc
(0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b         ; ea5be000f0
这里是cpu的第一条指令f000:fff0(对应的物理地址为0xffff0),很简单,就是一个跳转到0xfe05b的指令。
我们来看一下此时cpu寄存器的状态:
<bochs:1> dump_cpu
eax:0x00000000, ebx:0x00000000, ecx:0x00000000, edx:0x00000543
ebp:0x00000000, esp:0x00000000, esi:0x00000000, edi:0x00000000
eip:0x0000fff0, eflags:0x00000002, inhibit_mask:0
cs:s=0xf000, dl=0x0000ffff, dh=0xff009bff, valid=1
ss:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
ds:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
es:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
fs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
gs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
ldtr:s=0x0000, dl=0x0000ffff, dh=0x00008200, valid=1
tr:s=0x0000, dl=0x0000ffff, dh=0x00008300, valid=1
gdtr:base=0x00000000, limit=0xffff
idtr:base=0x00000000, limit=0xffff
dr0:0x00000000, dr1:0x00000000, dr2:0x00000000
dr3:0x00000000, dr6:0xffff0ff0, dr7:0x00000400
cr0:0x00000010, cr1:0x00000000, cr2:0x00000000
cr3:0x00000000, cr4:0x00000000
done
这里很多值都为0,为什么edx的值为0x543呢?我也不知道[FIXME]。
我们比较关心的是eflags,eip, cs和idt.
我们继续看跳转后的代码:
<bochs:2> disassemble 0xfe05b 0xfe060
000fe05b: (                    ): xor ax, ax                ; 31c0
000fe05d: (                    ): out 0x0d, al              ; e60d
000fe05f: (                    ): out 0xda, al              ; e6da
为了查看方便,我们还是使用源码bochs-2.3.5/bios/rombios.c:
.org 0xe05b ; POST Entry Point
bios_table_area_end:
post:

  xor ax, ax

  ;; first reset the DMA controllers
  out 0x0d,al
  out 0xda,al

  ;; then initialize the DMA controllers
  mov al, #0xC0
  out 0xD6, al ; cascade mode of channel 4 enabled
  mov al, #0x00
  out 0xD4, al ; unmask channel 4
这里初始化DMA控制器

  ;; Examine CMOS shutdown status.
  mov AL, #0x0f
  out 0x70, AL
  in  AL, 0x71

  ;; backup status
  mov bl, al

  ;; Reset CMOS shutdown status.
  mov AL, #0x0f
  out 0x70, AL          ; select CMOS register Fh
  mov AL, #0x00
  out 0x71, AL          ; set shutdown action to normal

  ;; Examine CMOS shutdown status.
  mov al, bl

  ;; 0x00, 0x09, 0x0D+ = normal startup
  cmp AL, #0x00
  jz normal_post
  cmp AL, #0x0d
  jae normal_post
  cmp AL, #0x09
  je normal_post
检查CMOS关机状态,这里我们会跳转到normal_post

normal_post:
  ; case 0: normal startup

  cli
  mov  ax, #0xfffe
  mov  sp, ax
  xor  ax, ax
  mov  ds, ax
  mov  ss, ax

  ;; zero out BIOS data area (40:00..40:ff)
  mov  es, ax
  mov  cx, #0x0080 ;; 128 words
  mov  di, #0x0400
  cld
  rep
    stosw
对BDA段(0x400-0x4ff)清0,接下来的post过程会在此段内存写入很多控制和状态信息。

  ;; set all interrupts to default handler
  xor  bx, bx         ;; offset index
  mov  cx, #0x0100    ;; counter (256 interrupts)
  mov  ax, #dummy_iret_handler
  mov  dx, #0xF000

post_default_ints:
  mov  [bx], ax
  inc  bx
  inc  bx
  mov  [bx], dx
  inc  bx
  inc  bx
  loop post_default_ints
在0x0-0x3ff段写入256个缺省的中断向量(0xf000ff53,处理函数为dummy_iret_handler),此时中断是关闭的。

  ;; set vector 0x79 to zero
  ;; this is used by 'gardian angel' protection system
  SET_INT_VECTOR(0x79, #0, #0)
重设0x79号中断

  ;; base memory in K 40:13 (word)
  mov  ax, #BASE_MEM_IN_K
  mov  0x0413, ax
在0x413处写入Memory size in Kb(0x027f,639)

  ;; Manufacturing Test 40:12
  ;;   zerod out above

  ;; Warm Boot Flag 0040:0072
  ;;   value of 1234h = skip memory checks
  ;;   zerod out above
对于0x412和0x472的检测,并没有实现。

  ;; Printer Services vector
  SET_INT_VECTOR(0x17, #0xF000, #int17_handler)

  ;; Bootstrap failure vector
  SET_INT_VECTOR(0x18, #0xF000, #int18_handler)

  ;; Bootstrap Loader vector
  SET_INT_VECTOR(0x19, #0xF000, #int19_handler)

  ;; User Timer Tick vector
  SET_INT_VECTOR(0x1c, #0xF000, #int1c_handler)

  ;; Memory Size Check vector
  SET_INT_VECTOR(0x12, #0xF000, #int12_handler)

  ;; Equipment Configuration Check vector
  SET_INT_VECTOR(0x11, #0xF000, #int11_handler)

  ;; System Services
  SET_INT_VECTOR(0x15, #0xF000, #int15_handler)
设置中断向量,其中int19_handler是启动函数,post的最后阶段就是调用它;
而int18_handler是启动失败的处理函数,设备引导失败时会调用它把控制权重新交给bios

  ;; EBDA setup
  call ebda_post
在0x40E写入0x9FC0,标志EBDA的起始地址。

  ;; PIT setup
  SET_INT_VECTOR(0x08, #0xF000, #int08_handler)
  ;; int 1C already points at dummy_iret_handler (above)
  mov al, #0x34 ; timer0: binary count, 16bit count, mode 2
  out 0x43, al
  mov al, #0x00 ; maximum count of 0000H = 18.2Hz
  out 0x40, al
  out 0x40, al
注册System Timer ISR Entry Point

  xor  ax, ax
  mov  ds, ax
  mov  0x0417, al /* keyboard shift flags, set 1 */
  mov  0x0418, al /* keyboard shift flags, set 2 */
  mov  0x0419, al /* keyboard alt-numpad work area */
  mov  0x0471, al /* keyboard ctrl-break flag */
  mov  0x0497, al /* keyboard status flags 4 */
  mov  al, #0x10
  mov  0x0496, al /* keyboard status flags 3 */


  /* keyboard head of buffer pointer */
  mov  bx, #0x001E
  mov  0x041A, bx

  /* keyboard end of buffer pointer */
  mov  0x041C, bx

  /* keyboard pointer to start of buffer */
  mov  bx, #0x001E
  mov  0x0480, bx

  /* keyboard pointer to end of buffer */
  mov  bx, #0x003E
  mov  0x0482, bx

  /* init the keyboard */
  call _keyboard_init
初始化键盘

  ;; mov CMOS Equipment Byte to BDA Equipment Word
  mov  ax, 0x0410
  mov  al, #0x14
  out  0x70, al
  in   al, 0x71
  mov  0x0410, ax
从cmos中读出并在0x410处写入Equipment Word

  ;; Parallel setup
  SET_INT_VECTOR(0x0F, #0xF000, #dummy_iret_handler)
  xor ax, ax
  mov ds, ax
  xor bx, bx
  mov cl, #0x14 ; timeout value
  mov dx, #0x378 ; Parallel I/O address, port 1
  call detect_parport
  mov dx, #0x278 ; Parallel I/O address, port 2
  call detect_parport
  shl bx, #0x0e
  mov ax, 0x410   ; Equipment word bits 14..15 determing # parallel ports
  and ax, #0x3fff
  or  ax, bx ; set number of parallel ports
  mov 0x410, ax
并口的设置

  ;; Serial setup
  SET_INT_VECTOR(0x0C, #0xF000, #dummy_iret_handler)
  SET_INT_VECTOR(0x14, #0xF000, #int14_handler)
  xor bx, bx
  mov cl, #0x0a ; timeout value
  mov dx, #0x03f8 ; Serial I/O address, port 1
  call detect_serial
  mov dx, #0x02f8 ; Serial I/O address, port 2
  call detect_serial
  mov dx, #0x03e8 ; Serial I/O address, port 3
  call detect_serial
  mov dx, #0x02e8 ; Serial I/O address, port 4
  call detect_serial
  shl bx, #0x09
  mov ax, 0x410   ; Equipment word bits 9..11 determing # serial ports
  and ax, #0xf1ff
  or  ax, bx ; set number of serial port
  mov 0x410, ax
串口的设置

  ;; CMOS RTC
  SET_INT_VECTOR(0x1A, #0xF000, #int1a_handler)
  SET_INT_VECTOR(0x4A, #0xF000, #dummy_iret_handler)
  SET_INT_VECTOR(0x70, #0xF000, #int70_handler)
  ;; BIOS DATA AREA 0x4CE ???
  call timer_tick_post
为CMOS RTC设置中断向量,并检测timer_tick

  ;; PS/2 mouse setup
  SET_INT_VECTOR(0x74, #0xF000, #int74_handler)

  ;; IRQ13 (FPU exception) setup
  SET_INT_VECTOR(0x75, #0xF000, #int75_handler)

  ;; Video setup
  SET_INT_VECTOR(0x10, #0xF000, #int10_handler)

  ;; PIC
  mov al, #0x11 ; send initialisation commands
  out 0x20, al
  out 0xa0, al
  mov al, #0x08
  out 0x21, al
  mov al, #0x70
  out 0xa1, al
  mov al, #0x04
  out 0x21, al
  mov al, #0x02
  out 0xa1, al
  mov al, #0x01
  out 0x21, al
  out 0xa1, al
  mov  al, #0xb8
  out  0x21, AL ;master pic: unmask IRQ 0, 1, 2, 6
#if BX_USE_PS2_MOUSE
  mov  al, #0x8f
#else
  mov  al, #0x9f
#endif
  out  0xa1, AL ;slave  pic: unmask IRQ 12, 13, 14
初始化两个8259A中断控制器

  call rombios32_init
这里是配置pci的关键代码,后面我们会着重介绍它。

  call _init_boot_vectors

  call rom_scan
从0xC0000到0xE0000(包含),以2KB递增,检测是否以0xAA55起始并满足校验,
然后调用ROM initialization entry point,这里的调用使用:
  mov  bp, sp   ;; Call ROM init routine using seg:off on stack
  db   0xff     ;; call_far ss:[bp+0]
  db   0x5e
  db   0
很有意思[FIXME]。

  call _print_bios_banner

  ;;
  ;; Floppy setup
  ;;
  call floppy_drive_post

  ;;
  ;; Hard Drive setup
  ;;
  call hard_drive_post

  ;;
  ;; ATA/ATAPI driver setup
  ;;
  call _ata_init
  call _ata_detect

  sti        ;; enable interrupts
  int  #0x19
使能中断,并调用int19h,完成启动。

至此,我们对bios的大致流程有了一个大概的认识。

下面是我们的重点:rombios32_init。
它即是一段汇编码又是一段c代码:汇编码实现使能A20,设置idt,gdt以及段寄存器,并切换到保护模式,
然后把c代码的rombios32_init拷贝到0x40000,接着调用它;调用完成后把0x40000开始的内存清0,
重设段寄存器,恢复到实模式,重设idt

我们来看c代码的rombios32_init,源码位于:
bochs-2.3.5/bios/rombios32.c
void rombios32_init(void)
{
    BX_INFO("Starting rombios32\n");

    ram_probe();

    cpu_probe();

    smp_probe();

    pci_bios_init();

    if (bios_table_cur_addr != 0) {

        mptable_init();

        if (acpi_enabled)
            acpi_bios_init();

        bios_lock_shadow_ram();
    }
}
ram_probe负责内存容量的检测,cpu_probe负责cpu的检测,smp_probe通过发送SIPI来检测系统中的cpu个数
void smp_probe(void)
{
    uint32_t val, sipi_vector;

    smp_cpus = 1;
    if (cpuid_features & CPUID_APIC) {

        /* enable local APIC */
        val = readl(APIC_BASE + APIC_SVR);
        val |= APIC_ENABLED;
        writel(APIC_BASE + APIC_SVR, val);

        writew((void *)CPU_COUNT_ADDR, 1);
        /* copy AP boot code */
        memcpy((void *)AP_BOOT_ADDR, &smp_ap_boot_code_start,
               &smp_ap_boot_code_end - &smp_ap_boot_code_start);

        /* broadcast SIPI */
        writel(APIC_BASE + APIC_ICR_LOW, 0x000C4500);
        sipi_vector = AP_BOOT_ADDR >> 12;
        writel(APIC_BASE + APIC_ICR_LOW, 0x000C4600 | sipi_vector);

        delay_ms(10);

        smp_cpus = readw((void *)CPU_COUNT_ADDR);
    }
    BX_INFO("Found %d cpu(s)\n", smp_cpus);
}
SVR: Spurious Interrupt Vector Register
AP: Application Processor
BSP: Boot-Strap Processor
IPI: interprocessor interrupts
SIPI: Startup IPI
ICR: Interrupt Command Register
首先向local APIC的SVR写入APIC_ENABLED标志位使能; 在CPU_COUNT_ADDR(0xf000)处写入1标志自己是一个cpu;
然后在AP_BOOT_ADDR(0x10000)处拷贝入ap启动代码,该代码位于bios/rombios32start.S:42
  .code16
smp_ap_boot_code_start:
  xor %ax, %ax
  mov %ax, %ds
  incw CPU_COUNT_ADDR
1:
  hlt
  jmp 1b
smp_ap_boot_code_end:
代码很简单,就是在CPU_COUNT_ADDR处加1,然后hlt;那么ap怎么找到这个入口的呢?
首先向其它的ap(不包括自己)发送一条INIT(Intel v3:p288)中断,初始化(apic_init_ipi)lapic的寄存器;
然后以AP_BOOT_ADDR >> 12为中断向量发送SIPI,然后其它ap会通过apic_startup设置eip为0,cs_base为AP_BOOT_ADDR

论坛徽章:
2
IT运维版块每日发帖之星
日期:2015-10-05 06:20:00操作系统版块每日发帖之星
日期:2015-10-05 06:20:00
21 [报告]
发表于 2009-07-15 10:29 |只看该作者
需要一定的汇编基础

被我挖了个坟

[ 本帖最后由 zero-B 于 2009-7-15 10:32 编辑 ]

论坛徽章:
0
20 [报告]
发表于 2009-07-15 09:17 |只看该作者
不错. 多谢分享

论坛徽章:
0
19 [报告]
发表于 2008-11-19 15:51 |只看该作者
原帖由 qiuhanty 于 2007-11-25 13:24 发表
qiuhan
2007.10.13

我们并没有实际去研读物理bios的代码,而是查看了bochs中的bios虚拟实现,主要出于如下考虑:
1 对于物理机的bios需要特殊的硬件进行调试,我们没有
2 对于不同的系统构架,bios很可能 ...



....连基础带过程,已经很详细了,


不错不错。。。收了!!!

论坛徽章:
0
18 [报告]
发表于 2008-11-18 23:06 |只看该作者
飘一下算了。。天书

论坛徽章:
0
17 [报告]
发表于 2008-11-18 10:04 |只看该作者

回复 #2 qiuhanty 的帖子


很早之前就看到了bios的下一代替代品EFI(Extensible Firmware Interface)
中文名:可扩展固件界面软件,看提示类似一个pe的os,而且使用c编写,似乎注重安全
但一直没见过实际应用,似乎理想离显示还挺远。

论坛徽章:
0
16 [报告]
发表于 2008-11-17 21:58 |只看该作者
好难啊!看不懂

论坛徽章:
0
15 [报告]
发表于 2008-11-17 21:41 |只看该作者
收藏!

论坛徽章:
1
黑曼巴
日期:2020-02-27 22:54:26
14 [报告]
发表于 2008-11-17 21:29 |只看该作者
先顶在看

论坛徽章:
0
13 [报告]
发表于 2007-11-27 12:48 |只看该作者
完全看不懂
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP