uboot 执行printf后异常死机问题定位

环境

硬件: PowerPC T1024,SPI启动
软件: uboot 2015.01
调试软件: codeWarrior
调试工具: codeWarrior TAP

现象

移植uboot,删除Nand 和Nor Flash相关内容。在启动后,死在了某一处打印位置,打印信息不完整。

调试1:定位死机位置

使用仿真器Attach板子,发现系统是死在了0x800(Program)处,LR寄存器指向的是_serial_puts函数中调用_serial_putc打印的位置。这说明系统是在执行_serial_puts函数时死掉的。
结论: 怀疑是打印函数一层层调用,堆栈溢出,或者系统printf函数问题。

调试2:验证系统printf函数

自己在需要打印的文件内部,写了一个裸串口打印函数和dbgputs函数,发现依然会死在裸串口输出函数里面。
结论: 说明和函数调用无关系,是串口打印本身的问题。

调试3:定位串口输出异常

在串口打印函数里面点灯:

	while (*pstr)
	{
		out_be32(&pgpio->gpdat, LED_G_ON|LED_R_ON);
		while(!(readb(CONFIG_SYS_NS16550_COM1 + UART_ULSR) & ULSR_TEMT));
		out_be32(&pgpio->gpdat, LED_G_ON|LED_R_OFF);
		writeb(*pstr, CONFIG_SYS_NS16550_COM1 + UART_DATA);
		out_be32(&pgpio->gpdat, LED_G_OFF|LED_R_ON);
		pstr++;
		isync();
	}

发现死在了“while(!(readb(CONFIG_SYS_NS16550_COM1 + UART_ULSR) & ULSR_TEMT));”里面。怀疑是读寄存器读多了导致异常,于是在while里面加上了delay,也修改readb为指针方式直接读,问题依旧。
结论: 和读寄存器无关系。

调试4:定位中断异常指令

通过观察Program中断异常,发现此事中断向量里面的值全是0x00000000,并未初始化。所以Program并不是真的出现中断异常,它可能是其他异常中断向量未初始化(默认为0)导致的。于是在checkboard函数后面,将中断向量都初始化为“0x48000000”(死循环),观察原始异常是谁。

	for(i = 0x100; i < 0xf00; i+= 0x100)
		*((volatile u32*)i) = 0x48000000;		//指令 b 0,死循环

结论: 系统死机后,程序停在了“0x0a00”(Decrementer)异常处。

调试5:分析Decrementer中断

搜索Decrementer中断,发现它是一个递减器,递减频率和TB寄存器一样,目的是为系统提供一种可编程的软件延时中断。通过观察SRR0寄存器(异常地址),发现它是在执行在执行while(!(readb(CONFIG_SYS_NS16550_COM1 + UART_ULSR) & ULSR_TEMT))时异常。所以之前死机的原因是:串口打印时使用轮询方式,结果轮询时间太长,导致递减器溢出中断,而此时系统还未挂载中断,所以死机。
结论: 系统进入了软延时中断,但没有挂载中断处理函数,导致系统死在了Program中断中。

调试6:分析中断挂载顺序。

发现系统在board_r.c/board_init_r中的函数执行顺序如下:

init_fnc_t init_sequence_r[] = {
724			initr_trap,				//初始化后中断向量
730			board_early_init_r,		
848 		initr_net,				//打印死机的位置
818			interrupt_init,			//使能DEC中断
}

也就是说,开启中断前已经挂载了中断函数。在board_early_init_r中打印0xA00的值,发现里面是有内容的,这与之前仿真器调试时里面没有值的信息不符。还原代码复现问题,死机后再次连仿真器调试,发现虚拟地址里面0xA00值是0,但物理地址里面的值却是真实的值。
结论: 中断向量是配置了的,但是在异常时,虚拟地址中却没有找到中断向量,怀疑是物理地址0没有映射到虚拟地址0当中。

调试7:核对TLB配置

在程序中board_early_init_r、initr_net等处加死循环,用仿真器观察对应地方的MMU配置,看他们是否有改变,是否有其他物理地址映射到虚拟地址0的地方。

/* 写DEC计数器,避免产生Decrementer中断 */
void set_dec (void)					
{
	unsigned long val = 0xffffffff;
	asm volatile ("mtdec %0"::"r" (val));
}
volatile int s_stop = 1;	//volatile 告知编译器s_stop的值随时回改变,避免编译器将while(s_stop)优化成了while(1)
static int initr_net(void)
{
	......
	__asm__ ("nop");		//通过nop的个数,方便汇编中定位死循环的位置
	__asm__ ("nop");
	while(s_stop)
		set_dec();
}

在死机前和死机后,通过仿真器观察MMU中条目有略微的改变,但改变的地方均和物理地址0和虚拟地址0无关。并且条目中只有TLB12是将物理地址0(DDR)映射到虚拟地址0。
结论: 和MMU配置无关。

调试8:分析cache

在调试7过程中,发现虚拟地址中,部分中断向量正常,部分不正常,物理地址中的全都正常。于是使用刷新命令,在读一次,发现虚拟地址中的内容也正常了。怀疑虚拟地址中内容不正常,和MMU配置无关,和cache有关系,是cache不一致导致。

搜索代码中cache的配置,发现cache是开启的。搜索代码,发现代码中有invalidate_icache、icache_enable、icache_disable等函数。而BSP中和cache相关的操作,只有在board_early_init_r 中Flash相关的地方,有如下操作:

	/* Flush d-cache and invalidate i-cache of any FLASH data */
	flush_dcache();				//同步数据cache
	invalidate_icache();		//无效指令cache(和flush_icache一样),同步指令cache

这段代码和Flash操作一起被屏蔽掉了。在代码中还原此操作,异常死机问题就没有了。

结论

uboot 在initr_trap中以写数据的形式修改了中断向量,但是没有同步cache,导致指令cache中的内容未被更新。所以系统在进入DEC中断时,没有了可执行的指令,进而进入Program异常中断而死机。解决的办法是先同步dcache,确保修改的中断向量写入到内存中,再同步icache,将中断向量加载到icache中来。

系统异常中断调试总结

1、观察程序挂起后停止的中断向量位置,查找对应的中断原因。
2、如果死在了Program位置,并且中断向量未初始化,说明这里并不是第一中断现场,通过如下代码初始化中断向量后再观察。

	for(i = 0x100; i < 0xf00; i+= 0x100)
		*((volatile u32*)i) = 0x48000000;	//死循环指令,保存第一中断现场

3、对应内核文档,定位异常的地址、原因等。通常SRR0保存了中断下条指令的位置。MSR寄存器保存了一些状态,它能够指示具体的中断原因。LR寄存器保存了最近堆栈地址等。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值