操作方法与实验步骤

(2) 开始⽤gdb调试内核。

a) 在⼀个终端⾥切换到⽬录“~/os/linux-0.11-lab“,然后运⾏脚本rungdb,以启动 bochs虚拟机并等待gdb连接。

b) 在另⼀个终端⾥切换到⽬录“~/os/linux-0.11-lab/cur/linux“,然后运⾏命令 “gdb tools/system”,以启动gdb并读⼊符号信息。

c) 在gdb中执⾏如下命令连接到bochs虚拟机:

target remote localhost:1234

d) 跟踪到main函数⼊⼝。

 

 

(3) 跟踪分析时钟中断。

a) 在函数do_timer(由时钟中断的处理函数timer_interrupt调⽤)处设置断点。

b) 跟踪到该断点第22次出现。 (22次c)

 

此时虚拟机打印出:

 

c) 跟踪到do_timer函数执⾏完毕返回。

反编译后观察到现在程序运行到do_timer 的0x000073dd:

finish追踪到do_Mmer函数执⾏完毕返回timer_interrupt 的 0x00007893;

观察到在0x0000788e确实调用了do_timer

 

d) 跟踪到timer_interrupt函数(⽤汇编语⾔写的)执⾏完毕、返回之前的iret指令。执⾏该iret指令(⽤命令si),返回到恢复点。分析断点和恢复点,记录bochs机的输出画⾯。

 

此时运行到0x00007893,下面通过反复执行si指令使timer_interrupt函数执⾏完毕。中间通过disassemble指令查看当前运行汇编指令的位置。

 

 

断点应为设置断点后continue程序后停下来时PC寄存器所指指令的前一条指令地址,即0x0000788e。

恢复点为中断时PC所指的地址,即0x00007893。

 

发现iret,此处能返回到恢复点,因此我们设置断点跳到那里。

 

 

恢复点和断点都是0x000079aa,是一个循环,运行si指令仍然在循环。

通过查看寄存器,发现寄存器ecx每次循环减一。

 

记录bochs机的输出画⾯:

 

(4) 跟踪分析“int 0x81”指令引发的异常。

a) 退出调试(使⽤quit命令),重新开始⽤gdb调试内核。

b) 在函数task1处设置断点。

c) 跟踪到该断点22次出现。

 

d) 通过反汇编定位到task1函数中的第⼀个“int 0x81”指令,并跟踪到该指令执⾏之前。

 

e) 查看寄存器的当前状态。

 

f) 单步执⾏⼀条汇编指令。

g) 再次查看寄存器的当前状态,并与之前的状态对⽐,并分析栈和态的变化。

 

  1. CS寄存器最低两位由11变为00,即由用户态变为核心态;
  2. EIP寄存器里存储的是CPU下次要执行的指令的地址,此时由task1里面的0x79af变为display_interrupt里面的0x7970;
  3. ESP寄存器里存储的是始终指向栈顶,在调用函数display_interrupt之后栈的栈顶完成了由用户栈到内核栈的切换过程,同理,作为代码段寄存器的CS和堆栈段寄存器的SS也是如此转换。
  4. 综上所述,我们清楚了由用户态切换到内核态的步骤主要包括:

[1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。

[2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。

[3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。

 

h) 查看当前栈(核⼼栈)中保存的中断现场,分析恢复点和⽤户栈的位置。

(gdb) x/5wx $esp

0x1ceac <init_task+4076>: 0x000079b1 0x0000000f 0x00000246 0x00022bcc

0x1cebc <init_task+4092>: 0x00000017

 

0x00000017对应ss;

0x000079b1对应eip+2,即为恢复点;

0x0000000f对应cs;

0x00000246对应eflags;

0x00022bcc对应esp;

发生异常后,核心栈把现场的ss:eip, eflags, cs:esp 保存下来,恢复点就是栈顶所在的地址0x79b1。用户栈为0x22bcc。

 

中断描述符IDT表示一个系统表,它与中断或异常向量相联系。每一个中断或异常向量在这个系统表中有对应的中断或异常处理程序入口地址。中断描述符的每一项对应一个中断或异常向量,每个向量由8个字节组成。

  1. 跟踪到0x81号异常处理程序返回之前的iret指令。

 

j) 查看寄存器的当前状态。

 

k) 单步执⾏⼀条汇编指令。

l) 再次查看寄存器的当前状态,并与之前的状态对⽐,并分析栈和态的变化。

 

esp由原来init_task的0x1ceac变为user_stack的0x22bcc,由核心栈转为用户栈;

eip由调用的函数display_interrupt的0x7986变更为task0的0x798f,说明结束了函数display_interrupt,跳到了原来的函数task0;

eflags由原来的CF SF 变为现在的 PF ZF;

CF(bit 0) [Carry flag]   若算术操作产生的结果在最高有效位(most-significant bit)发生进位或借位则将其置1,反之清零。这个标志指示无符号整型运算的溢出状态。

SF(bit 7) [Sign flag]   该标志被设置为有符号整型的最高有效位。(0指示结果为正,反之则为负)

PF(bit 2) [Parity flag]   如果结果的最低有效字节(least-significant byte)包含偶数个1位则该位置1,否则清零。

ZF(bit 6) [Zero flag]   若结果为0则将其置1,反之清零。

CS寄存器最低两位由00变为11,即由核心态变为用户态;

作为代码段寄存器的CS和堆栈段寄存器的SS完成了由内核栈到用户栈的切换过程。

(5) 调试分析执⾏过的系统调⽤。

a) 退出调试(使⽤quit命令),重新开始⽤gdb调试内核。

b) 在函数 system_call 处设置断点,跟踪该断点,在断点每次出现时分析所要求的信息,直到屏幕上第2次出现字符’1’。

 

第一个1出现:

 

第二个1出现

 

输出“1”的时候,系统调⽤号$eax都是27,而且当前进程号current->pid为1。

 

把原来的断点system_call 这个disable掉,然后添加新条件断点system_call if $eax==27(若把断点设置为system_call if $eax!=29效果一样):

 

 

发现每次continue都会输出字符直到输出字符“1”。实验说明:

系统调⽤的ID(eax)

实现函数

(对应表)

进程号(current->pid)

动作

2

__NR_fork

0

复制创建一个进程(以输出1)

29

__NR_pause

0

输出“0”

0

__NR_setup

1

启动内核

48

__NR_signal

1

信号处理

27

__NR_alarm

1

输出“1”

 

  1. 修改内核源码,使得每次时钟中断发⽣时,都在屏幕上输出字符’t’。提示:字符输出可以使⽤int 0x81。

在timer_interrupt中添加

movb $116,%al

int $0x81

 

保存文件后重新编译并运行,虚拟机效果如图:

 

(7) 修改内核源码,使得每次产⽣系统调⽤时,都在屏幕上输出系统调⽤号和当前进程号。(选做)

提示:

a) 在内核态输出信息可以使⽤printk函数,其⽤法类似于printf。

b) 在汇编程序中可以调⽤C函数(⽤call指令),参数通过栈传递。也就是说,先将参数压到栈中,然后执⾏call指令来调⽤C函数。

c) 在输出系统调⽤号时,可以忽略掉0号进程的连续pause系统调⽤,只保留每组第1个。

把system_call改写成这样:

system_call:

cmpl $nr_system_calls-1,%eax

ja bad_sys_call

push %ds

push %es

push %fs

pushl %edx

pushl %ecx # push %ebx,%ecx,%edx as parameters

pushl %ebx # to the system call

movl $0x10,%edx # set up ds,es to kernel space

mov %dx,%ds

mov %dx,%es

movl $0x17,%edx # fs points to local data space

mov %dx,%fs

 

#如果系统调用号是29,

#也就是pause系统调⽤,

#就直接跳过不打印信息,进入函数no_print执行原来的代码;

cmpl $29,%eax

je no_print

 

#平衡栈

push %eax

 

#压入系统调用号

push %eax

 

#取出进程号压到栈里,

#进程号在以current为指针偏移556的地方,

#556是gdb调试发现的

movl current,%eax

addl $556,%eax

movl (%eax),%eax

push %eax

 

#把格式压进栈

push $mess

#调用printk,输出

call printk

 

#平衡栈

addl $0xc,%esp

pop %eax

 

no_print:

call sys_call_table(,%eax,4)

pushl %eax

movl current,%eax

cmpl $0,state(%eax) # state

jne reschedule

cmpl $0,counter(%eax) # counter

je reschedule

 

#格式化字符串内容:

mess:

.ascii "The num of process is %d,The num of sys_call  is %d\n\0"

 

 

  1. 修改版本0内核,使得每发⽣100次时钟中断,就在屏幕上输出⼀个字符’t’。(选做)

 

先定义:
count:

.long 0

 

chr:

.ascii "%c\0"

 

然后把timer_interrupt改写成:

push %ds # save ds,es and put kernel data space

push %es # into them. %fs is used by _system_call

push %fs

pushl %edx # we save %eax,%ecx,%edx as gcc doesn't

pushl %ecx # save those across function calls. %ebx

pushl %ebx # is saved as we use that in ret_sys_call

pushl %eax

 

#每次时钟中断count+1

addl $1,count

movl count,%eax

#累加器count没满足100,则不输出,执行原来代码

cmpl $100,%eax

jne no_print

#把字母t和输出格式压栈

pushl $0x74

pushl $chr

#调用printk输出

call printk

#结束这个进程前调整栈顶

addl $0x8,%esp

#count归零

movl $0,count

 

no_print:

movl $0x10,%eax

mov %ax,%ds

mov %ax,%es

movl $0x17,%eax

mov %ax,%fs

incl jiffies

movb $0x20,%al # EOI to interrupt controller #1

outb %al,$0x20

movl CS(%esp),%eax

andl $3,%eax # %eax is CPL (0 or 3, 0=supervisor)

pushl %eax

call do_timer # 'do_timer(long CPL)' does everything from

addl $4,%esp # task switching to accounting ...

jmp ret_from_sys_call

效果图:

 

  1. 修改版本0内核,使得每次产⽣系统调⽤时,都在屏幕上输出系统调⽤号和当前进程号。(选做)

把system_call改写成这样:

system_call:

cmpl $nr_system_calls-1,%eax

ja bad_sys_call

push %ds

push %es

push %fs

pushl %edx

pushl %ecx # push %ebx,%ecx,%edx as parameters

pushl %ebx # to the system call

movl $0x10,%edx # set up ds,es to kernel space

mov %dx,%ds

mov %dx,%es

movl $0x17,%edx # fs points to local data space

mov %dx,%fs

 

cmpl $29,%eax

je no_print

push %eax

push %eax

 

movl current,%eax

addl $556,%eax

movl (%eax),%eax

push %eax

 

push $mess

call printk

addl $0xc,%esp

pop %eax

 

no_print:

call sys_call_table(,%eax,4)

pushl %eax

movl current,%eax

cmpl $0,state(%eax) # state

jne reschedule

cmpl $0,counter(%eax) # counter

je reschedule

 

末尾填上输出:

mess:

.ascii "The num of process is %d,The num of sys_call  is %d\n\0"

 

 

 

 

 

Logo

更多推荐