调试在64位Debian上编译好的Linux 0.11(二)

@qqiseeu  2013-12-04 18:20

调试在64位Debian上编译好的Linux 0.11(二)

本机环境:

  • SMP Debian 3.11.6-1 (2013-10-27) x86_64 GNU/Linux
  • gcc (Debian 4.8.2-5) 4.8.2
  • GNU assembler (GNU Binutils for Debian) 2.23.90.20131116
  • GNU ld (GNU Binutils for Debian) 2.23.90.20131116
  • Bochs x86 Emulator 2.6

编译时的一些设定:

./Makefile

RAMDISK = -DRAMDISK=512  #设定虚拟盘大小为512KB
ROOT_DEV=FLOPPY
LD  =ld -m elf_i386 -Ttext 0 -e startup_32

2.问题

2.在显示出“Loading system”信息后就停止运行(续上篇文章

类似的,main()drive_info=DRIVE_INFO一行对应的汇编代码也使用了rep,而此时DF置位。在main()函数执行前使DF置位的命令只可能在boot/下的文件中出现。使用egrep寻找知:

   
   
  1. ~/Src/LinuxKernel/0.11/linux-0.11-deb$ egrep -nr '\<std\>' boot/
  2. boot/head.s:209: std

在213行加个cld就好。

3.又出现类似问题2.1的情况

使用gdb单步调试,发现在转入进程1并执行init()->setup()->bread()调用时,触发page fault,且在do_no_page()->get_free_page()时跳转到一个全零的内存区域。检查汇编代码发现get_free_page()的地址并没有错误,但是其代码应该在的地方被清零了。

初步猜测是

  1. bootsect加载system模块时出错;
  2. setup.s移动system模块时出错;
  3. get_free_page()所在的内存区域被违规擦写了。

重新调试程序,刚进入main()函数时立即查看get_free_page()所在内存位置,发现其代码就在该处,因此可以排除前两条猜测。设断点于bread()函数,单步调试发现从bread()->ll_rw_block()->make_request()->add_request()->do_hd_request()->reset_hd()->reset_hd()->hd_out()的整个过程都未出现问题。这时时钟中断被触发(注意这是第一次触发时钟中断),继续step into发现在time_interrupt()->do_timer()的过程中,do_timer()调用了一个next_timer指向的函数,然而next_timer按理来说应该被初始化为NULL。查看其值发现next_timer = 0x113

再次从头开始调试,发现在setup.s中把system模块从0x10000移到0x00000之后,next_timer就已经是非NULL值了。进一步检查发现sched.c中定义的所有全局static变量都被错误初始化了(我不知道为什么,如果有人知道的话请一定留言告诉我,谢谢!)。

修改方法:在sched_init()函数最后将上述全局static变量手动初始化:

/* 
 * all the static variable with global scope defined in this
 * file are initialized incorrectly. I don't know why :-(
 */
memset(timer_list, 0, sizeof(timer_list));
if (next_timer != NULL)
    next_timer = NULL;

for (i=0; i<4; i++) {
    wait_motor[i] = NULL;
    mon_timer[i] = moff_timer[i] = 0;
}

之后可以正确进入mount_root()函数并在屏幕上打印

Insert root floppy and press ENTER

的提示。

4.插入系统盘并回车后,系统假死

mount_root()函数开始调试,仔细检查从

mount_root()->read_super()->check_disk_change()->floppy_change()->floppy_on()->sleep_on()->...

的整个流程,均未发现问题。然而在mount_root()调用iget()以获取第一个空闲inode时,发现inode_table[]也没有被正确地初始化为全零。意识到可能还有更多的全局变量/静态全局变量的初始化存在问题,我检查了内核中定义的所有全局变量/静态全局变量,发现还有last_task_used_mathstartup_timejiffieslast_pid也未正确初始化。手动将它们重置即可,或是干脆写个函数来一次性解决这个问题。(见这里

5.init()函数第一次调用open()时出错,无法打开“/dev/tty0”

修复后可以成功装载根文件系统,接下来执行到open处再次出错。单步调试进入open_namei()->dir_namei()->get_dir()->find_entry()后发现,传给find_entry()的参数name在调用match()后值发生变化,然而match()函数不应该修改name的值:

static int match(int len,const char * name,struct dir_entry * de)
{
    register int same;

    if (!de || !de->inode || len > NAME_LEN)
        return 0;
    if (len < NAME_LEN && de->name[len])
        return 0;
    __asm__("cld\n\t"
        "fs ; repe ; cmpsb\n\t"
        "setz %%al"
        :"=a" (same)
        :"0" (0),"S" ((long) name),"D" ((long) de->name),"c" (len));
    return same;
 }

为name增加一个watch point可注意到,在调用match()前,name被从edx寄存器移到esi寄存器中,而match()函数在未保存esi的情况下使用且改变了esi寄存器的值(通过rep指令),且在match()返回后,find_entry()依然通过esi中的地址来取name指向的字符串,这就造成每次调用match()后,name的值都不一样了。

通过反汇编find_entry()的目标代码发现,其调用match()时没有使用call指令,而是直接把match()的汇编代码嵌入到自己的汇编代码中去了,然后因其指定输入寄存器esi中的内容为name,所以之前有一个把name从edx移入esi的动作。gcc默认用来传递参数的寄存器是eax、edx、ecx,因此gcc会自动考虑是否需要“保存/恢复”这三个寄存器的指令。而match()使用内联汇编时操作的esi、edi作为手动指定的寄存器,需要自己增加保存/恢复的指令。

修改方法:在match()的内联汇编的前后使用push/pop保存并恢复esi寄存器的值。

注意到内核代码中存在大量内联汇编,它们都可能出现类似问题,因此应给所有使用esi/edi作为输入寄存器的内联汇编代码前后加上一对pushl/popl

3.结语

做完上述修改后内核就可以在bochs中运行了。我把所有对内核代码的修改生成了一个patch文件,放在github上。由于水平所限,有一些问题的原理我也不清楚,解决的时候带有一点猜的性质,如果大家发现文中存在什么错误,还请一定指出。


    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    带中文注释可成功编译运行的Linux0.11+Bochs2.62实验环境说明 此注释以网上获得的“linux带中文注释的0.11版本”为基础,对照赵炯博士《Linux内核完全注释(0.11) 》V3.0版(http://oldlinux.org/download/clk011c-3.0.pdf)编辑而成。作为对赵博士感谢,以及对Linux初学者的回馈,特发布在CSDN上。 此注释可以在http://oldlinux.org/Linux.old/bochs/提供的Linux-0.11-devel-XXXXXX实验环境下正确编译成功,使用:"make disk"命令重启Bochs虚拟机后,新编译源码直接生效,便于学习者直接阅读源码,直接进行实验。 注意事项: 1、为了使注释版与实验环境上的Linux0.11内核保持一致,达到对应文件可以互换的目的,与Linux0.11原始版本相比,加入了15个系统调用函数(参见include/Linux/sys.h第78-92行。赵博士原书没有这部分注释,我不敢班门弄斧),其它相关的文件加入了相应的定义。新加入的代码只有函数体定义,没有具体实现,对其它原始代码没有改变、没有影响。 2、键盘定义改成了美式键盘(原始代码中是芬兰键盘,会导致个别键出问题,调试的时候我曾被迷糊了好久,以为自己把程序搞乱了)。 3、把网上VC版的注释统一改成了 “/* */” 格式的注释。经测试,在Linux0.11实验环境中(gcc1.40),只有标准C注释语法可以正常编译。 4、由于《Linux内核完全注释(0.11) 》原书版本更新的原因,注释中提到的图、表可能与V3.0版书中不一致。 5、由于代码中加入注释,代码行号发生变化,注释中提到的代码行号会出现不一致,建议对照3.0版查询对应内容。 6、实验方法:请先安装附带的Bochs2.62版安装包,双击Test.bxrc即可启动实验系统,执行命令:sh t,即可完成对linuxcn的编译。 7、linux目录中是此实验系统中/usr/src/linux提取出来的不含中文注释的linux0.11源码(此版本比原始的0.11版多15个系统调用函数),linuxcn是加入了中文注释的源码。 8、diskb.img是实验系统与Windows环境下进行文件交换的1.44M软盘映像,执行脚本命令"sh t"时会自动从此映像中读取linux.tar、linuxcn.tar包,解包并编译编译结果在:/usr/root/zw/linuxcn目录下。为了方便文件交换,建议使用7zip为压缩/解压缩工具(7zip可以直接生成tar包),用WinImage实现Windows环境与软件映像交换文件。 9、实验系统下 .profile中加入了几个命令,请读者注意。 10、若实验环境的启动盘被破坏,请用压缩包中的bootimage-0.11-hd覆盖对应文件即可。 11、若实验环境的要命文件系统被破坏,请用压缩包中的hdc-0.11-new.img覆盖对应文件即可。 2014-5-4 cyfx2288
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值