Ricky Zhang
rickycheung@21cn.com
2005-2-14
1、实验目的:
了解Minix如何封装中断调用,并如本身内核消息传递机制融合。
2、实验目标:
动态上跟踪:在Bochs上,对系统的键盘中断调用代码/kernel/Mpx386.s的hwint_master01加断点,续步跟踪了解。
静态上跟踪:与系统中断有关源代码有,/kernel/Mpx386.s,/kernel/proc.c
3、实验步骤:
动态跟踪笔记:
一、把Minix的内核映像(/minix/XXX)上传到windows上,用任何反编译器反汇编(Debug也可以,但我用了W32Dasm)。
用sti为关键字找到/kernel/Mpx386.s的hwint_master01代码段,源码中在中断汇编代码使用了宏
----------------/kernel/Mpx386.s的hwint_master01代码段-------------------------------
!*===========================================================================*
!* hwint00 - 07 *
!*===========================================================================*
! Note this is a macro, it looks like a subroutine.
#define hwint_master(irq) /
call save /* save interrupted process state */;/
inb INT_CTLMASK ;/
orb al, [1<<irq] ;/
outb INT_CTLMASK /* disable the irq */;/
movb al, ENABLE ;/
outb INT_CTL /* reenable master 8259 */;/
sti /* enable interrupts */;/
push irq /* irq */;/
call (_irq_table + 4*irq) /* eax = (*irq_table[irq])(irq) */;/
pop ecx ;/
cli /* disable interrupts */;/
test eax, eax /* need to reenable irq? */;/
jz 0f ;/
inb INT_CTLMASK ;/
andb al, ~[1<<irq] ;/
outb INT_CTLMASK /* enable the irq */;/
0: ret /* restart (another) process */
! Each of these entry points is an expansion of the hwint_master macro
.align 16
_hwint00: ! Interrupt routine for irq 0 (the clock).
hwint_master(0)
.align 16
_hwint01: ! Interrupt routine for irq 1 (keyboard)
hwint_master(1)
--------------------------------------------------------------------------------------------------
------------------------------------对应/kernel/Mpx386.s的hwint_master01的内核映像反汇编的结果
:000002F1 E8CB020000 call 000005C1
:000002F6 E421 in al, 21
:000002F8 0C02 or al, 02
:000002FA E621 out 21, al
:000002FC B020 mov al, 20
:000002FE E620 out 20, al
:00000300 FB sti
:00000301 6A01 push 00000001
:00000303 FF15C86C0000 call dword ptr [00006CC8]
:00000309 59 pop ecx
:0000030A FA cli
:0000030B 85C0 test eax, eax
:0000030D 7406 je 00000315
:0000030F E421 in al, 21
:00000311 24FD and al, FD
:00000313 E621 out 21, al
:00000315 C3 ret
:00000316 00000000000000000000 BYTE 10 DUP(0)
:00000320 00 BYTE 0
---------------------------------------------------------------------------------------------------------------------------------
2、从笔记一中提到,由Secondary Boot把minix的映像文件(在/minix下),装载到内存物理0x00800地址上(源码在/boot/boot.h下定义'#define MINIXPOS 0x00800L /* Minix is loaded here (rounded up towards)')
3、用bochs的debug功能bochsdbg,添加一个物理地址的断点
因为minix映象在内存的物理开始地址是0x00800,而hwint_master01的偏移是0x000002F1 (里面有200h的a.out.h的头,因些要减去200h),所以在内存中hwint01的物理开始地址是0x00800+0x002F1-200h-1h=0x8f0
在bochs的后台,按Ctrl+C,停机
<bochs:32> pb 0x08f0 //物理地址断点
<bochs:33> c // continue
4、回到minix,随意按一外键
5、Bochs自动进入debug
(0) Breakpoint 4, 0x8f0 in ?? ()
Next at t=2340001600
(0) [0x000008f0] 0030:000000f0 (unk. ctxt): call 0x3c0 ; e8cb0200
00
<bochs:46>
6、开始进入Hwint01的中断陷入部分.
7、查看进入中断例程的寄存器装态
(0) [0x000008f0] 0030:000000f0 (unk. ctxt): call 0x3c0 ; e8cb0200
00
<bochs:16> info r
eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0
esp 0x735c 0x735c
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0xf0 0xf0
eflags 0x2 2
cs 0x30 48
ss 0x18 24
ds 0xf 15
es 0xf 15
fs 0xf 15
gs 0xf 15
寄存器表1
<bochs:17> s
Next at t=468489601
(0) [0x00000bc0] 0030:000003c0 (unk. ctxt): cld ; fs
<bochs:18> info r
eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0
esp 0x7358 0x7358
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x3c0 0x3c0
eflags 0x2 2
cs 0x30 48
ss 0x18 24
ds 0xf 15
es 0xf 15
fs 0xf 15
gs 0xf 15
寄存器表2
注解:call指令,使esp自减了=0x735c-0x7358=4(byte)
<bochs:33> u /40 0xbc0
00000bc0: ( ): cld ; fc
00000bc1: ( ): pushad ; 60
00000bc2: ( ): opsize push ds ; 661e
00000bc4: ( ): opsize push es ; 6606
00000bc6: ( ): opsize push fs ; 660fa0
00000bc9: ( ): opsize push gs ; 660fa8
00000bcc: ( ): mov dx, ss ; 8cd2
00000bce: ( ): mov ds, dx ; 8eda
00000bd0: ( ): mov es, dx ; 8ec2
00000bd2: ( ): mov eax, esp ; 89e0
00000bd4: ( ): inc byte ptr [ds:0x6c58] ; fe05586c0000
00000bda: ( ): jnz 0xbec ; 7510
00000bdc: ( ): mov esp, 0x2ff8 ; bcf82f0000
00000be1: ( ): push 0x433 ; 6833040000
00000be6: ( ): xor ebp, ebp ; 31ed
00000be8: ( ): jmp dword ptr ds:[eax+0x28] ; ff6028
字节对齐(4的整倍数 .align 4)
00000bec: ( ): push 0x455 ; 6855040000
00000bf1: ( ): jmp dword ptr ds:[eax+0x28] ; ff6028
注解:pushad指令等价于
Temp ← (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);
把ds,es,fs,gs,入栈。后四个共2*4=8byte,pushad的入栈大小为8*4=32byte。因此total入栈为0x28
(40D),故jmp dword ptr ds:[eax+0x28]的地址为调用call save时所保存的CS:IP.
与源码/kernel/Mpx386.s的save对比
!*===========================================================================*
!* save *
!*===========================================================================*
! Save for protected mode.
! This is much simpler than for 8086 mode, because the stack already points
! into the process table, or has already been switched to the kernel stack.
.align 16
save:
cld ! set direction flag to a known value
pushad ! save "general" registers
o16 push ds ! save ds
o16 push es ! save es
o16 push fs ! save fs
o16 push gs ! save gs
mov dx, ss ! ss is kernel data segment
mov ds, dx ! load rest of kernel segments
mov es, dx ! kernel does not use fs, gs
mov eax, esp ! prepare to return
incb (_k_reenter) ! from -1 if not reentering
jnz set_restart1 ! stack is already kernel stack
mov esp, k_stktop
push _restart ! build return address for int handler
xor ebp, ebp ! for stacktrace
jmp RETADR-P_STACKBASE(eax)
.align 4
set_restart1:
push restart1
jmp RETADR-P_STACKBASE(eax)
从代码得出,保存恢复工作分两种,一种是有当前是还没有被嵌套的中断,另一种是已经被嵌套的中断。
前者要首先恢复使用kernel栈,然后把_restart入栈待中断完成后做恢复工作且调用/proc.c中的un_hold,
激活被挂起的其它的中断例程。
后者则只把restart1入栈,待中断完成后做恢复工作。
最后两者都利用jmp RETADR-P_STACKBASE(eax),返回save之后的代码。
以下是动态调试结果:
单步执行到0xbd4
(0) [0x00000bd4] 0030:000003d4 (unk. ctxt): inc byte ptr [ds:0x6c58] ; fe05586c
0000
<bochs:55> x /b 0x18:0x6c58
[bochs]:
0x00017558 <bogus+ 0>: 0xff
注解:incb (_k_reenter),_k_reenter记录中断嵌套次数。
如果_k_reenter是-1(0xff为-1的补码,执行inc指令后为0),则当前没有嵌套且栈并非为kernel的栈,
把esp指回kernel栈顶,使用kernel栈
然后把恢复寄存器工作交由_restart的地址入kernel栈,待中断工作完成后,重调用_restart
mov esp, k_stktop
push _restart ! build return address for int handler
以下是_restart的程序
!*===========================================================================*
!* restart *
!*===========================================================================*
_restart:
! Flush any held-up interrupts.
! This reenables interrupts, so the current interrupt handler may reenter.
! This does not matter, because the current handler is about to exit and no
! other handlers can reenter since flushing is only done when k_reenter == 0.
cmp (_held_head), 0 ! do fast test to usually avoid function call
jz over_call_unhold
call _unhold ! this is rare so overhead acceptable
over_call_unhold:
mov esp, (_proc_ptr) ! will assume P_STACKBASE == 0
lldt P_LDT_SEL(esp) ! enable segment descriptors for task
lea eax, P_STACKTOP(esp) ! arrange for next interrupt
mov (_tss+TSS3_S_SP0), eax ! to save state in process table
restart1:
decb (_k_reenter)
o16 pop gs
o16 pop fs
o16 pop es
o16 pop ds
popad
add esp, 4 ! skip return adr
iretd ! continue process
注解:当k_reenter==0即嵌套重数为0时,调用proc.c的unhold例程,激活被挂起的中断例程。
否则,当前栈为kernel栈,直接跳转至set_restart1,恢复寄存器工作交给restart1的地址push至栈中,待中断完成后,执行。
restart1:
decb (_k_reenter)
o16 pop gs
o16 pop fs
o16 pop es
o16 pop ds
popad
add esp, 4 ! skip return adr
iretd ! continue process
继续动态调试,单步执行到返回
00000be8: ( ): jmp dword ptr ds:[eax+0x28] ; ff6028
<bochs:61> s
Next at t=468489616
(0) [0x00000be8] 0030:000003e8 (unk. ctxt): jmp dword ptr ds:[eax+0x28] ; ff6028
<bochs:62> info r
eax 0x7330 29488
ecx 0x0 0
edx 0x18 24
ebx 0x0 0
esp 0x2ff4 0x2ff4
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x3e8 0x3e8
eflags 0x42 66
cs 0x30 48
ss 0x18 24
ds 0x18 24
es 0x18 24
fs 0xf 15
gs 0xf 15
<bochs:63> x 0x18:0x7358
0x00017c58 <bogus+ 0>: 0x000000f5
注解:所以返回的IP为0xf5,近jmp所以CS为0x30,跳回0x30:0xf5(当前为保护模式)
<bochs:84> calc 0x30:0xf5
0x8f5 2293
跳回的物理地址0x8f5
-------------------------------hwint01的内存反汇编结果
<bochs:67> u /25 0x8f0
000008f0: ( ): call 0xbc0 ; e8cb020000
000008f5: ( ): in al, 0x21 ; e421
000008f7: ( ): or al, 0x2 ; 0c02
000008f9: ( ): out 0x21, al ; e621
000008fb: ( ): mov al, 0x20 ; b020
000008fd: ( ): out 0x20, al ; e620
000008ff: ( ): sti ; fb
00000900: ( ): push 0x1 ; 6a01
00000902: ( ): call dword ptr [ds:0x6cc8] ; ff15c86c0000
//调用keyboard的中断例程
00000908: ( ): pop ecx ; 59
00000909: ( ): cli ; fa
0000090a: ( ): test eax, eax ; 85c0
0000090c: ( ): jz 0x914 ; 7406
0000090e: ( ): in al, 0x21 ; e421
00000910: ( ): and al, 0xfd ; 24fd
00000912: ( ): out 0x21, al ; e621
00000914: ( ): retn ; c3
---------------------------------------------------------------------------------
(待扩展)