linux 64位与0比较好,调试在64位Debian上编译好的Linux 0.11(一),debian0.11

调试在64位Debian上编译好的Linux 0.11(一),debian0.11

@qqiseeu2013-11-27 20:44

调试在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

1.调试前的准备

1.制作内核引导Image

为了把编译好的内核镜像放到bochs中运行,首先制作内核引导Image,具体为(参考Linux内核完全剖析):

#也可以用下述命令替换顶层Makefile中的disk目标下的命令

ddif=Imageof=bootimage-fda.img

dd bs=1024if=/dev/zero of=bootimage-fda.img seek=256count=1184

其中Image是之前编译出的内核镜像。上述操作得到一个大小为1.44MB的Image文件bootimage-fda.img,将作为在bochs中使用的引导盘。注意此时我们仍未创建根文件系统,而在Makefile中设置了ROOT_DEV=FLOPPY,因此正常情况下在fork出进程1、进程1调用setup((void*)&drive_info)安装根文件系统时,会提示插入含有根文件系统的软盘,不过这暂时不用担心。

2.设置bochs

接下来设置bochs,仍参考Linux内核完全剖析,但由于书中使用的bochs版本较老,有些选项的写法需要修改:

./bochsrc-fda.bxrc

romimage: file=$BXSHARE/BIOS-bochs-latest

megs: 16

vgaromimage: file=$BXSHARE/VGABIOS-elpin-2.40

floppya: 1_44="bootimage-fda.img", status=inserted

floppyb: 1_44=diskb.img, status=inserted

ata0-master: type=disk, path="hdc-0.11-new.img", mode=flat, cylinders=121, heads=16, spt=63

boot: a

log: bochs.out

parport1: enabled=0

vga: update_freq=5

keyboard: paste_delay=100000, serial_delay=200

cpu: count=1, ips=4000000

mouse: enabled=0

注意其中用到的diskb.img与hdc-0.11-new.img是这个包提供的。其中也包含了做好的根文件系统镜像,可以直接用。然后就可以直接运行系统了!

bochs-f bochsrc-fda.bxrc

3.设置添加了gdb-stub功能的bochs

调试C程序代码时很多时候还是用gdb更为方便,因此可以专门编译出一份开启了gdb-stub功能的bochs,并将其重命名为bochs-gdb以与原来的bochs区分。若要换用开启了“gdb-stub”功能的bochs进行调试,则给所有Makefile文件中的CFLAGS加上 -g 选项,去除所有LDFLAGS中的 -s 选项。此时system模块由于附加了大量调试信息,大小超过了顶层Makefile中设定的限制SYSSIZE

= 0x3000,因此可将顶层Makefile中的tools/system目标修改如下:

tools/system: boot/head.o init/main.o \

$(ARCHIVES) $(DRIVERS) $(MATH) $(LIBS)

$(LD) $(LDFLAGS) boot/head.o init/main.o \

$(ARCHIVES) \

$(DRIVERS) \

$(MATH) \

$(LIBS) \

-o tools/system > System.map

objcopy --only-keep-debug tools/system tools/system.dbg

objcopy --add-gnu-debuglink=tools/system.dbg tools/system

objcopy -g tools/system

将调试信息抽取出来专门存于一个文件system.dbg中,gdb执行时将会利用该调试信息文件。

2.问题

1.无法正确载入system模块

直接运行内核发现bochs的虚拟终端上不断刷新如下信息

...

ata0 master: Generic 1234 ATA-6 Hard-Disk ( 121 MBytes)

Press F12 for boot menu.

Booting from Floppy...

Loading system ...

初步猜测进入了某个死循环。使用打开了调试功能的bochs单步调试发现,程序执行到setup.s中第193行之前都正常

boot/setup.s

193 jmpi 0, 8 ! jmp offset 0 of segment 8(cs)

这一行的功能是跳转到cs段偏移0处(此时保护模式已打开),实际上就是物理地址0x0处。该地址此时应为system模块的起始处(因在进入保护模式前,setup.s中113-126行的代码已将system模块从线性地址0x10000移至0x0000,而当时线性地址0x0与物理地址0x0是重合的)。然而此时查看物理地址0x0处的内容发现,从该处开始有很长一段(事实上足足有3KB)的值是全零,因此system模块必然出了问题。问题只能出在三个地方:

bootsect.s中109行,将system模块从磁盘上读入内存0x10000处时

setup.s中113-126行,将system模块从0x10000复制到0x00000处时

build.c中157-167行,将system模块组装到Image中时(话说这个我一开始还真没想到)

通过bochs调试前两处可能出现错误的地方,没有发现任何问题,因此问题只可能出在system模块本身。根据bootsect.s中读入system模块的代码,发现其是从第5个磁盘块(每个磁盘块大小为1KB)开始读入system模块的,因此system模块的起始地址应为Image中第2.5KB处,即地址0x0a00,然而使用hexdump查看Image文件发现,system模块实际上是从0x1600字节处开始的,等于说存在一个3KB大小的空洞!build.c中组装system模块的代码如下:

tools/build.c

157 if ((id=open(argv[3],O_RDONLY,0))<0)

158 die("Unable to open 'system'");

159 if (read(id,buf,GCC_HEADER) != GCC_HEADER)

160 die("Unable to read header of 'system'");

161 if (((long *) buf)[6] != 0)

162 die("Non-GCC header of 'system'");

163 for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )

164 if (write(1,buf,c)!=c)

165 die("Write call failed");

166 close(id);

可以看出,build程序先读取system模块的GCC_HEADER(实际上就是ELF头)判断文件格式的合法性,然后抛弃该文件头,把剩下的内容添加到Image文件中。这里有一个隐含的假设:system模块的ELF头大小就是1KB!然而实际上,这个用现代版本gcc编译出来的目标文件,其ELF头的大小是4KB(这也可以通过hexdump看出)。因此build程序实际上应该丢弃system模块前4KB的内容。在163行前加上下述代码即可:

/* The header size is 4*GCC_HEADER (4KB) on my machine*/

for(i=0;i<3;i++)

if(read(id,buf,GCC_HEADER)!=GCC_HEADER)

die("Unable to read header of 'system'");

2.在显示出“Loading system”信息后就停止运行

按正常情况,应该是在mount_root()函数显示出

Insert root floppy and press ENTER

信息之后系统再暂停运行,然而这里的情况表明程序根本没有执行到这一步。在main.c中的init()调用setup()之前插一句printk("entering

init");再重新运行一遍程序,发现根本没有执行到这一行,则有如下几种可能:

fork()调用存在问题

进程调度存在问题

首先用bochs调试代码,检查main.c中定义的内联版本的fork()是否被正确展开(其实就是检查main()调用fork()时有没有用call指令。因为这里提到过GCC有时不会把该函数按内联的方式展开)。在我的电脑上编译出来的代码中,这一步是没有问题的,于是接下来深入sys_fork调用去检查。换用bochs-gdb调试代码,结果发现copy_process()中子进程复制父进程task_struct时出现了问题:

*p = *current;

这一步执行完后,并没有把*current复制给*p。查看这两个task_struct结构的周边内存,发现下述情况

...

(gdb) x /16 0x00017140

0x17140 : 0x00017160 0x00000000 0x00000000 0x00000000

0x17150: 0x00000000 0x00000000 0x00000000 0x00000000

0x17160: 0x00000000 0x00000000 0x00000000 0x00000000

0x17170: 0x00000000 0x00000000 0x00000000 0x00000000

(gdb) x /16 0x00ffefe0

0xffefe0: 0x00017160 0x00000000 0x00000000 0x00000000

0xffeff0: 0x00000000 0x00000000 0x00000000 0x00000000

0xfff000: 0x00000000 0x00000000 0x00000000 0x00000000

0xfff010: 0x00000000 0x00000000 0x00000000 0x00000000

...

本次调试时p=0x00fff000,current=0x00017160,注意到位于地址0x17140的值似乎被复制到了地址0xffefe0处,这可能说明复制时是往低地址方向复制的。于是在gdb中使用set

disassemble-next-line on命令查看copy_process()中*p

= *current语句产生的汇编代码得:

...

*p = *current; /* NOTE! this doesn't copy the supervisor stack */

=> 0x00007a8a : mov 0x17140,%esi

0x00007a90 : mov $0xef,%ecx

0x00007a95 : mov %ebx,%edi

0x00007a97 : rep movsl %ds:(%esi),%es:(%edi)

可见此时使用了rep

movsl的方法来复制内存。根据Intel的手册对该指令的描述,其复制方向受EFLAGS寄存器中DF位控制:DF=0时往高地址方向复制(这也是默认情况),DF=1时往低地址方向复制。用info

registers查看EFLAGS寄存器的值,果然DF位被置位了。此时编译器认为DF位为0,然而实际上DF=1 。我对于编译原理了解不多,故只能猜测将DF位置位的代码并不是由编译器编译C代码产生的(否则编译器应该会知道自己将DF置位了),因此应是之前某手工编写的汇编代码中存在std指令,且之后没有用cld复位。搜索源文件知

~/Src/LinuxKernel/0.11/linux-0.11-deb$ egrep -nr '\Wstd\W' .

./mm/memory.c:67: __asm__("std ; repne ; scasb\n\t"

./kernel/chr_drv/console.c:189: __asm__("std\n\t"

./kernel/chr_drv/console.c:174: __asm__("std\n\t"

./include/string.h:352: __asm__("std\n\t"

在这四个使用了std指令的内联汇编代码的最后都加上一句cld复位DF位即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值