不对称的push&pop引发的bug

之前已经完成了最基本的从实模式-》保护模式,跳出了512字节限制,开始走向了kernel的跳转之路。但是,问题也出来了,先贴出一个最最基础的kernel原型:

; DD's kernel. @2013-04-29
       ;以下常量定义部分。内核的大部分内容都应当固定 
         core_code_seg_sel     equ  0x38    ;内核代码段选择子
         core_data_seg_sel     equ  0x30    ;内核数据段选择子 
         sys_routine_seg_sel   equ  0x28    ;系统公共例程代码段的选择子 
         video_ram_seg_sel     equ  0x20    ;视频显示缓冲区的段选择子
         core_stack_seg_sel    equ  0x18    ;内核堆栈段选择子
         mem_0_4_gb_seg_sel    equ  0x08    ;整个0-4GB内存的段的选择子

         ;以下是系统核心的头部,用于加载核心程序 
         core_length      dd core_end       ;核心程序总长度#00

         sys_routine_seg  dd section.sys_routine.start
                                            ;系统公用例程段位置#04

         core_data_seg    dd section.core_data.start
                                            ;核心数据段位置#08

         core_code_seg    dd section.core_code.start
                                            ;核心代码段位置#0c


         core_entry       dd start          	;核心代码段入口点(段内偏移)	#10
                          dw core_code_seg_sel	;段选择子,用于确定cs寄存

[bits 32]
SECTION sys_routine vstart=0                ;系统公共例程代码段
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;function:在保护模式下,打印一串字符串					;
;@1 input: 要打印的字符串的物理地址, edx-段内偏移			;
;@2 input: 打印的字符长度: ecx						;
;output: null 								;
;desc: es:指向0xb800,字符显示缓冲区的选择子,   ds:核心数据段的选择子	;
;history: 增加换行功能,每次打印完,自动换行				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
printf:
	push ax
	push bx
	push cx
	push dx
	push es
	push ds
	
	;1. first of all, trans the phy addres into logic address
	;mov bx, 16
	;div bx

        ;0x30 - 核心数据段 段选择子
	mov ebx, 0x30
	mov ds, ebx
	;0x20 - 字符显示缓冲区的选择子
	mov ebx, 0x20
	mov es, ebx

	;首先针对字符长度,计算占用的行数
	;接下去要修改dx的值,所以先push ax 以及dx
	push eax
	push edx
	mov ax, cx  ;ax是字符串长度
	xor edx, edx
	mov bx, 0x50
	div bx    ;除以一行长度80, ax保存了行数,dx保存了余数
	cmp dx, 0
	;如果没有余数,那么无需+1
	je go
	;否则需要+1
	inc ax
go:
;	push ds
;	mov si, 0
;	mov ds, si
	mov [ds:CUR_ROW], ax
;	pop ds

	;最后pop dx以及ax
	pop dx
	pop ax	

	;cx为字符串的长度,循环次数是字符串的1/2倍
	mov ax, cx
	mov bl, 2
	div bl
	cmp ah, 0x00 ;比较是否存在余数,如果存在则需要将商(al)+1
	je no ;如果不存在余数
	;如果存在余数
	inc al
no:
	xor cx, cx
	mov cl, al
	mov bx, dx
	;当前的id从TOTAL_ROW中获取
;	push ds
;	mov si, 0
;	mov ds, si
	mov ax, [TOTAL_ROW]
	mov si, 0xA0 ;bug-fix, 这里的offset要包含两个字符之间的控制字符串,所以是0x50 * 2 = 0xA0  
	mul si
	mov di, ax
;	pop ds
lp:	
	;一次性取出2个字节,提高读取效率
	mov ax, [bx] ;直接用dx来寻址把内存赋值到ax还不行~~ 得用bx,神囧啊
	mov byte [es:di], al
	inc di
	mov byte [es:di], 00000100B
	inc di

	mov byte [es:di], ah
	inc di
	mov byte [es:di], 00000100B
	inc di

	add bx, 0x02
	loop lp
	;loop完成后,要把当前的di写回到CUR_INDEX中去,下次重新调用的时候,不会覆盖之前打印的信息
;	inc di  ;bug-fix: 这个不需要再加了,因为循环最后一次的时候,已经多加一次,然后退出的!!!

;	push ds
;	mov si, 0
;	mov ds, si
	;获取打印前占位的row index
	mov si, [TOTAL_ROW]
	;把当前的row index加上去
	mov di, [CUR_ROW]
	add si, di
	mov [TOTAL_ROW], si
;	pop ds

	pop ds
	pop es
	pop dx
	pop cx
	pop bx
	pop ax

	retf


SECTION core_data vstart=0                  ;系统核心的数据段

;保存当前显存区间,当前开始的ROW id,从0开始
TOTAL_ROW: dw 0x0000
CUR_ROW: dw 0x0000

TEST_STRING db "hello, here's the kernel" ;24

SECTION core_code vstart=0		    ;系统代码段
start:
	mov ecx, 24
	mov ebx, 0x30
	mov ds, bx
	mov edx, TEST_STRING
	call  sys_routine_seg_sel:printf
	hlt

;===============================================================================
SECTION core_trail
;-------------------------------------------------------------------------------
core_end:

print函数是之前在实模式下就写好了的,现在只是稍微修改了下代码以更好的适配保护模式。但是把代码一跑,烦心的问题出来了:“check_cs(0x0018): not a valid code segment! ”。

<bochs:1> 
省略
00017824978i[BIOS ] Booting from 0000:7c00
00017826445e[CPU0 ] check_cs(0x0018): not a valid code segment !
00017826445e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00017826445e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00017826445i[CPU0 ] CPU is in protected mode (active)
00017826445i[CPU0 ] CS.mode = 32 bit
00017826445i[CPU0 ] SS.mode = 32 bit
00017826445i[CPU0 ] EFER   = 0x00000000
00017826445i[CPU0 ] | EAX=00000018  EBX=00000004  ECX=00000000  EDX=00000000
00017826445i[CPU0 ] | ESP=fffffff4  EBP=00000000  ESI=000e0001  EDI=00000001
00017826445i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af pf cf
00017826445i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00017826445i[CPU0 ] |  CS:0028( 0005| 0|  0) 00040018 000c00a9 0 1
00017826445i[CPU0 ] |  DS:0004( 0000| 1|  0) f053f000 0000ff53 0 0
00017826445i[CPU0 ] |  SS:0018( 0003| 0|  0) 00007c00 f1000fff 1 1
00017826445i[CPU0 ] |  ES:0030( 0006| 0|  0) 000400c0 000c001d 0 1
00017826445i[CPU0 ] |  FS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00017826445i[CPU0 ] |  GS:0000( 0005| 0|  0) 00000000 0000ffff 0 0
00017826445i[CPU0 ] | EIP=000000a5 (000000a5)
00017826445i[CPU0 ] | CR0=0x60000011 CR2=0x00000000
00017826445i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
(0).[17826445] [0x00000000000400bd] 0028:00000000000000a5 (unk. ctxt): retf
00017826445e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status
00017826445i[SYS  ] bx_pc_system_c::Reset(HARDWARE) called
00017826445i[CPU0 ] cpu hardware reset
00017826445i[APIC0] allocate APIC id=0 (MMIO enabled) to 0x00000000fee00000
00017826445i[CPU0 ] CPUID[0x00000000]: 00000002 756e6547 6c65746e 49656e69
00017826445i[CPU0 ] CPUID[0x00000001]: 00000633 00010800 00002008 1fcbfbff
00017826445i[CPU0 ] CPUID[0x00000002]: 00410601 00000000 00000000 00000000
00017826445i[CPU0 ] CPUID[0x80000000]: 80000008 00000000 00000000 00000000
00017826445i[CPU0 ] CPUID[0x80000001]: 00000000 00000000 00000101 2a100000
00017826445i[CPU0 ] CPUID[0x80000002]: 20202020 20202020 20202020 6e492020
00017826445i[CPU0 ] CPUID[0x80000003]: 286c6574 50202952 69746e65 52286d75
00017826445i[CPU0 ] CPUID[0x80000004]: 20342029 20555043 20202020 00202020
00017826445i[CPU0 ] CPUID[0x80000005]: 01ff01ff 01ff01ff 40020140 40020140
00017826445i[CPU0 ] CPUID[0x80000006]: 00000000 42004200 02008140 00000000
00017826445i[CPU0 ] CPUID[0x80000007]: 00000000 00000000 00000000 00000000
00017826445i[CPU0 ] CPUID[0x80000008]: 00003028 00000000 00000000 00000000
00017826445i[     ] reset of 'pci' plugin device by virtual method
00017826445i[     ] reset of 'pci2isa' plugin device by virtual method
00017826445i[     ] reset of 'cmos' plugin device by virtual method
00017826445i[     ] reset of 'dma' plugin device by virtual method
00017826445i[     ] reset of 'pic' plugin device by virtual method
00017826445i[     ] reset of 'pit' plugin device by virtual method
00017826445i[     ] reset of 'floppy' plugin device by virtual method
00017826445i[     ] reset of 'vga' plugin device by virtual method
00017826445i[     ] reset of 'acpi' plugin device by virtual method
00017826445i[     ] reset of 'ioapic' plugin device by virtual method
00017826445i[     ] reset of 'keyboard' plugin device by virtual method
00017826445i[     ] reset of 'harddrv' plugin device by virtual method
00017826445i[     ] reset of 'pci_ide' plugin device by virtual method
00017826445i[     ] reset of 'unmapped' plugin device by virtual method
00017826445i[     ] reset of 'biosdev' plugin device by virtual method
00017826445i[     ] reset of 'speaker' plugin device by virtual method
00017826445i[     ] reset of 'extfpuirq' plugin device by virtual method
00017826445i[     ] reset of 'parallel' plugin device by virtual method
00017826445i[     ] reset of 'serial' plugin device by virtual method
00017826445i[     ] reset of 'gameport' plugin device by virtual method
00017826445i[     ] reset of 'iodebug' plugin device by virtual method
Next at t=17826446
(0) [0x00000000fffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b         ; ea5be000f0
<bochs:2>

快速的走读一遍,没有发现什么大的问题,无奈只能单步进去看看到底程序跑到哪里去了,导致cs不合法,之所以提示不合法,那是因为bochs检测出cs的值为0x18,看看源代码,你就会发现段选择子 = 0x18是 "core_stack_seg_sel    equ  0x18    ;内核堆栈段选择"。那么,毫无疑问,堆栈段被当成cs,那肯定是非法的。


火速单步:发现代码跑到retf后,提示了错误。

<bochs:25> u /10
000400b3: (                    ): pop ds                    ; 1f
000400b4: (                    ): pop es                    ; 07
000400b5: (                    ): pop dx                    ; 665a
000400b7: (                    ): pop cx                    ; 6659
000400b9: (                    ): pop bx                    ; 665b
000400bb: (                    ): pop ax                    ; 6658
000400bd: (                    ): retf                      ; cb
000400be: (                    ): add byte ptr ds:[eax], al ; 0000
000400c0: (                    ): add dword ptr ds:[eax], eax ; 0100
000400c2: (                    ): add dword ptr ds:[eax], eax ; 0100
<bochs:26>


也就是在调用完了printf函数,等待返回的时候,提示了cs不合法的错误:

SECTION core_code vstart=0		    ;系统代码段
start:
	mov ecx, 24
	mov ebx, 0x30
	mov ds, bx
	mov edx, TEST_STRING
	call  sys_routine_seg_sel:printf
	hl

但是检查了下调用前以及调用printf,跳转到该代码段的cs,都是正常的,但是为啥retf后,cs寄存器不恢复成之前的0x38,而变成了0x18?这个非常的令人匪夷所思。百思不得其解啊,混蛋。


这种情况下,最好的办法就是再检查下the fucking code:

;接下去要修改dx的值,所以先push ax 以及dx
push eax
push edx

        ........

;最后pop dx以及ax
pop dx
pop ax

很明显,push和pop没有对称,这是一个严重的问题,为何说严重呢? 原因很简单:会破坏原来的栈,带来的后果要么就是自己push的东东没有清理干净,导致后来push的人需要为此买单,要么就是多pop了,把别人的push的东西给破坏了。总之一句话,非常严重!


初步定位完毕,现在需要具体的分析,在这种错误的代码下,为何会引起cs异常:

这里需要一点背景:call aaa:bbb, 这个是典型的跨段调用;那么call之前会做啥事情呢?很简单,call意味着往别的代码段跳转,而这个跳转不是一去不返的,必须得返回,其实就是函数必须要返回,不论是否存在返回值,或者是void的。那么,跳转过去是简单的,有aaa:bbb指引,问题在于跳转过去后,cs选择子肯定被改写成了当前的aaa,ip也被写成了bbb,那么cpu怎么知道在执行完整个call后,如何返回? 那么ss指引的栈就是发挥作用了,它负责为华丽的call后擦屁股,怎么擦?那么必须要在call前,做两件事情:1、把call前的cs寄存器值push到ss指引的栈中 2、把call执行完后紧接着的指令地址push到栈中(注意,是紧接着的ip哦~~)。这样子,call的最后部分,肯定会有一个ret或者retf(跨段),返回后,原本的那两个push值被pop到cs以及ip寄存器中。(这个动作应该是ret触发的)。那么程序又可以正常执行下去了。


元归正传:根据代码,我们先dump call之前的栈内存:

<bochs:3> u /2
000400ed: (                    ): call far 0028:00000000    ; 9a000000002800
000400f4: (                    ): hlt                       ; f4

<bochs:56> xp /310bx 0x7ad0
[bochs]:
0x0000000000007ad0 <bogus+       0>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007ad8 <bogus+       8>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007ae0 <bogus+      16>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007ae8 <bogus+      24>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007af0 <bogus+      32>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007af8 <bogus+      40>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b00 <bogus+      48>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b08 <bogus+      56>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b10 <bogus+      64>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b18 <bogus+      72>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b20 <bogus+      80>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b28 <bogus+      88>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b30 <bogus+      96>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b38 <bogus+     104>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b40 <bogus+     112>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b48 <bogus+     120>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b50 <bogus+     128>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b58 <bogus+     136>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b60 <bogus+     144>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b68 <bogus+     152>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b70 <bogus+     160>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b78 <bogus+     168>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b80 <bogus+     176>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b88 <bogus+     184>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b90 <bogus+     192>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007b98 <bogus+     200>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007ba0 <bogus+     208>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007ba8 <bogus+     216>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bb0 <bogus+     224>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bb8 <bogus+     232>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bc0 <bogus+     240>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bc8 <bogus+     248>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bd0 <bogus+     256>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bd8 <bogus+     264>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007be0 <bogus+     272>:    0x04    0x00    0x00    0x00    0x04    0x00    0x00    0x00
0x0000000000007be8 <bogus+     280>:    0x30    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x0000000000007bf0 <bogus+     288>:    0x00    0x80    0x09    0x00    0x00    0x00    0x04    0x00
0x0000000000007bf8 <bogus+     296>:    0x18    0x00    0x00    0x00    0x4c    0x01    0x00    0x00
0x0000000000007c00 <bogus+     304>:    0x31    0xc0    0x8e    0xd8    0x31    0xc0

单步执行,进入call后的代码,下面是函数printf内,所有push完成后的stack memory dump:


为了方便突出问题的来龙去脉,忽略中间部分处理的代码,只突出pop的代码:

	;最后pop dx以及ax
	pop dx
	pop ax

	pop ds
	pop es
	pop dx
	pop cx
	pop bx
	pop ax

好了,现在终于可以看看这个stack memory怎么被破坏了~


上图 注意:p-ax表示pop ax,由于pop的时候,eax变成ax,长度缩减了一半,所以当pop完ax,需要retf的时候,pop的ip变成了0x00040030,而pop的cs变成了0x00000018。这也就印证了最开始bochs提示的cs异常,说白了,起始就是由于eax edx缩水成为ax,dx,导致整个栈内存pop都前移了4个字节,cs变成了原来的ip,因此cs被置成原ip的0x18的时候就异常了。

00017826445e[CPU0 ] check_cs(0x0018): not a valid code segment !

另外,这里需要注意,为什么ip是0x18? 回忆一下之前说的,ret/retf后,ip应该是call前的执行的指令的下一条指令的地址,由于指令是顺序执行的,反汇编一把指令:

<bochs:81> u/2
000400ed: (                    ): call far 0028:00000000    ; 9a000000002800
000400f4: (                    ): hlt                       ; f4
<bochs:82>

从上面的反汇编结果来看,call与hlt指令首地址之间相差7个字节(其实就是call aaa:bbb指令的长度),执行call前的ip是0x11,所以call的下一条指令(hlt)的指令地址就是0x11+0x07 = 0x18

<bochs:82> reg
rax: 0x00000000_00000004 rcx: 0x00000000_00000018
rdx: 0x00000000_00000004 rbx: 0x00000000_00000030
rsp: 0x00000000_00000000 rbp: 0x00000000_00000000
rsi: 0x00000000_000e0001 rdi: 0x00000000_000000dc
r8 : 0x00000000_00000000 r9 : 0x00000000_00000000
r10: 0x00000000_00000000 r11: 0x00000000_00000000
r12: 0x00000000_00000000 r13: 0x00000000_00000000
r14: 0x00000000_00000000 r15: 0x00000000_00000000
rip: 0x00000000_00000011
eflags 0x00000006: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af PF cf
<bochs:83>


定位原因到此结束,把pop改成edx和eax,实际运行结果:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值