操作系统之旅(007)—— 启动内核

执行命令~/Applications/bochs-2.6.8/bin/bochs -f linux.bxrc,通过bochs启动linux。

内核未能正常启动,且由于编译完成前从未调试过,所以选择从头开始调试。

首先查看bootsect.bin运行情况。

执行命令~/Applications/bochs-2.6.8/bin/bochs -f linux.bxrc启动bochs,然后按下一次回车,出现提示“<bochs:1>”,输入b 0x7c00(bootsect.bin程序入口地址,在此打上断点)回车,然后再输入c回车,程序在断点处停住,如下所示:

(0) Breakpoint 1, 0x00007c00 in ?? ()
Next at t=14040247
(0) [0x000000007c00] 0000:7c00 (unk. ctxt): mov ax, 0x07c0            ; b8c007
<bochs:4>

再输入n回车,一直回车直到程序不再返回。

程序停在read_track中死循环了,看了下代码,是以nasm语法修改bootsect.s时候忘记对track,sread,head三个变量的引用加上[ ]的原因,全部加上。

单步执行太慢,这里直接找到bootsect.s最后一条指令,jmp SETUPSEG:0,及jmp 0x9020:0,需要知道这条指令的地址,所以执行命令:objdump -D -b binary -m i8086 build/bootsect.bin > build/bootsect.disasm,再bootsect.disasm文件中搜索9020,找到该指令 93: ea 00 00 20 90        ljmp   $0x9020,$0x0。

重新执行bochs,通过命令:b 0x90093,为该跳转指令打上断点,继续执行bochs,在断点处停住,到此为止bootsect.s程序结束。继续执行一条指令(bochs指令n),bochs显示:

(0) [0x000000090200] 9020:0000 (unk. ctxt): mov ax, 0x9000            ; b80090

正好对应setup.s第一条指令,由此程序在setup.s中运行。


同上方法,找到setup.s最后一条指令: jmp 8:0  ; jmp offset 0 of segment 8 (cs),打上断点,尝试运行到此断点,尝试成功,继续执行一条指令,输入bochs命令disasm/10,反汇编10条指令,回显如下:

<bochs:99> disasm/10
00000000: (                    ): jnle .+69                 ; 7f45
00000002: (                    ): dec esp                   ; 4c
00000003: (                    ): inc esi                   ; 46
00000004: (                    ): add dword ptr ds:[ecx], eax ; 0101
00000006: (                    ): add dword ptr ds:[eax], eax ; 0100
00000008: (                    ): add byte ptr ds:[eax], al ; 0000
0000000a: (                    ): add byte ptr ds:[eax], al ; 0000
0000000c: (                    ): add byte ptr ds:[eax], al ; 0000
0000000e: (                    ): add byte ptr ds:[eax], al ; 0000
00000010: (                    ): add al, byte ptr ds:[eax] ; 0200

与head.s文件开始命令不符合,调查发现原因是system.bin文件不是纯二进制文件,而是elf格式,解决方案如下:

build/system.bin :   boot/head.o init/main.o $( ARCHIVES ) $( DRIVERS ) $( MATH ) $( LIBS )
     $( LD ) $( LDFLAGS ) boot/head.o init/main.o $( ARCHIVES ) $( DRIVERS ) $( MATH ) $( LIBS ) -o build/system.bin > System.map

修改为

build/system.bin :   boot/head.o init/main.o $( ARCHIVES ) $( DRIVERS ) $( MATH ) $( LIBS )
     $( LD ) $( LDFLAGS ) boot/head.o init/main.o $( ARCHIVES ) $( DRIVERS ) $( MATH ) $( LIBS ) -o build/system.elf > build/system.map
    objdump -D build/system.elf > build/system.disasm
    objcopy -O binary -R .note -R .comment -S build/system.elf build/system.bin

重新编译运行,程序成功进入到head.s中。


自此可以结合gdb进行内核调试,步骤如下:

1、拷贝一份linux.bxrc:cp linux.bxrc linux_gdb.bxrc

2、在linux_gdb.bxrc开头添加:gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0

3、修改所有Makefile,为gcc和as编译标志加上-g选项,删除所有LDFLAGS的-s -x标志,重新编译

4、在终端下执行命令:/home/reborn/Applications/bochs-gdb/bin/bochs -f linux_gdb.bxrc,bohcs下直接回车,显示Waiting for gdb connection on port 1234

5、再开一个终端执行命令:gdb build/system.elf,进入gdb,输入命令:target remote localhost:1234,之后就可以正常的gdb调试了


接上述步骤,在gdb下为main打断点:b main,执行c命令,调试程序终止,内核程序有问题,打开system.disasm查看,发现符号链接的地址不是从0开始,找到原因,修改如下:

head.s中为startup_32加上global属性:.globl startup_32,idt,gdt,pg_dir,tmp_floppy_area

根目录Makefile的LDFLAGS修改为:LDFLAGS =-m elf_i386 -Ttext 0 -e startup_32 -M

重新编译调试,程序成功停止在main处。


接上输入c继续执行程序,程序进入死循环,bochs窗口如下图:


在gdb终端下按下ctrl+c组合键,回到gdb命令行,输入bt指令,打印调用栈,如下所示:

(gdb) bt
#0  panic (s=0x1a2b8 "copy_page_tables called with wrong alignment") at panic.c:23
#1  0x0000af71 in copy_page_tables (from=4135, to=67108864, size=655360) at memory.c:159
#2  0x00008749 in copy_mem (nr=1, p=0xfff000) at fork.c:56
#3  0x00008a27 in copy_process (nr=1, ebp=167656, edi=4092, esi=917504, gs=23, none=31003, ebx=139264, ecx=21992, edx=139264, fs=23, es=23, ds=23, eip=25805, cs=15, eflags=1542, esp=167628, ss=23) at fork.c:115
#4  0x00007a30 in sys_fork () at system_call.s:217
#5  0x00000001 in startup_32 () at boot/head.s:18


按调用栈提示从上向下扫描代码,copy_mem中调用的get_base问题较大,看过get_base的代码后,打开system.disasm文件,搜索copy_mem,找到get_base宏代码的展开处,代码如下:

    8613: 8a 30                 mov    (%eax),%dh
    8615: 8a 11                 mov    (%ecx),%dl
    8617: c1 e2 10              shl    $0x10,%edx
    861a: 66 8b 12              mov    (%edx),%dx
发现edx寄存器又做输出又作输入,这里需要它只做输出,股修改如下:

sched.h中的_get_base宏

:"=d" (__base)        =>        :"=&d" (__base)

重新编译调试运行,在schedule()中的while(1)处死循环,调试发现,当前共有两个进程,进程0(idle进程)和进程1(init进程),通过gdb查看进程1的任务数据(task_struct)不正确,最后定位到copy_process函数中的*p = *current一行,调试执行到下一行代码,对比结构体数据,发现数据并没有正确拷贝过来,打开system.disasm文件,搜索current全局变量的地址为0x23220,再搜索到copy_process的函数实现处,最终定位到汇编代码:

87dd: f3 a5                 rep movsl %ds:(%esi),%es:(%edi)

结构体复制最终是通过该行进行,该条指令的复制和eflags标志寄存器的df标志位有关,查看该标志位,gdb指令i r eflags,发现该标志位被置位,而这里需要的是该标志位复位,所以做出如下修改:

在<*p = *current;>一行前加上<__asm__ __volatile__ ("cld\n\t");>代码,这么加可能会导致其他问题,暂时不理。


重新编译调试,本次从main入口单步执行到fork时发现fork是函数调用,并没有按老版编译器进行内联处理,而这里的fork必须内联,因为要保证复制进程时用户栈不被使用,所以不能有函数调用,这里进行如下修改:

     int n_pid = - 1 ;
     __asm__ __volatile__ ( "int $0x80" \
        : "=a" (n_pid) \
        : "0" (__NR_fork));
     if ( ! n_pid) {        /* we count on this going ok */
         init ();
    }
/*
*   NOTE!!   For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
     while ( 1 )
         __asm__ __volatile__ ( "int $0x80" \
            : \
            : "a" (__NR_pause));

main.c中其他fork,pause,sync,setup处做类似处理。


重新编译调试,如下所示:


执行到挂载文件系统了,现在需要一个文件系统(下载地址),下载linux-0.11-devel-060625.zip后解压,hdc-0.11-new.img是这里要用到的文件系统,把该文件拷贝到程序根目录,修改linux.bxrc,在最后加入:

ata0: enabled=1,ioaddr1=0x1f0,ioaddr2=0x3f0,irq=14
ata0-master: type=disk, path="hdc-0.11-new.img", mode=flat, cylinders=410, heads=16, spt=38

修改bootsect.s,把ROOT_DEV equ 0x306修改位ROOT_DEV equ 0x301。


重新编译运行,linux0.11至此成功启动,效果如下图:


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值