问题现象
调试sp02
LLVM编译的可执行程序在Linux模拟器上运行,无法执行结束。在Windows模拟器可以正常执行(有乱码)。
Linux模拟器运行结果
*** TEST 2 ***
INIT - xxos_task_wake_after - yielding processor
PREEMPT - xxos_task_delete - deleting selfINIT - suspending TA2 while middle task on a ready chainTA1 - xxos_task_wake_after - sleep 1 secondTA2 - xxos_task_wake_after - sleep 1 minuteTA3 - xxos_task_wake_after - sleep 5 secondsTA1 - xxos_task_ident - tid of TA2 (0x.8x)
TA1 - xxos_get_classic_name - id -> name of TA2 OK
TA1 - xxos_task_ident - tid of TA3 (0x.8x)
Windows模拟器运行结果
*** TEST 2 ***
INIT - xxos_task_wake_after - yielding processor
PREEMPT - xxos_task_delete - deleting selfINIT - suspending TA2 while middle task on a ready chainTA1 - xxos_task_wake_after - sleep 1 secondTA2 - xxos_task_wake_after - sleep 1 minuteTA3 - xxos_task_wake_after - sleep 5 secondsTA1 - xxos_task_ident - tid of TA2 (0x.8x)
TA1 - xxos_get_classic_name - id -> name of TA2 籓K
TA1 - xxos_task_ident - tid of TA3 (0x.8x)
TA1 - xxos_task_set_priority - set TA3's priority to 2TA1 - xxos_task_suspend - suspend TA2TA1 - xxos_task_delete - delete TA2TA1 - xxos_task_wake_after - sleep for 5 secondsTA3 - xxos_task_delete - delete self*** END OF TEST 2 ***
调试sp03
调试前
Linux模拟器
*** TEST 3 ***
Windows模拟器
*** TEST 3 ***
狻籃0
程序卡在vprintk()中, sp04也是
尝试解决
- printk打印加上换行
- 修改
print_time()
和put_name()
函数:分别在他们调用的printk打印结尾加上换行。
修改之后Linux模拟器多打印了几行乱码。看来也不是因为缓冲没及时flush的原因。
问题锁定:printk加参数就崩
既然知道是printk()
和vprintk()
的问题(程序卡死在vprintk()
中),那么就把问题出现的条件简单化,在程序中以从简单到复杂的形式来调用printk()
,以逐步排查问题。首先尝试直接打印一个常量字符串,没问题。接下来尝试打印一个局部整形变量,出问题了。多试几次之后发现,只要printk()
以加参数打印的形式就会崩。
int a = 10;
printk("a=%d\n", a);
打印结果竟然是
a=8411292
打印的看起来是一个地址。改vprintk还是编译器?????
问题剖析
看vprintk汇编代码
- U0是第一个参数format字符串的地址,因为是字符串常量,该地址在数据段。U1存的是第二个参数va_list的地址,在栈中。
printk的汇编有问题
_printk:
0b000480: XR39=SER || YR39=U9
0b000482: [U8+=-2,-1]=XYR39
0b000484: U9=U8
0b000485: U8=U8+-2
0b000486: XR0=[U9+3]
0b000487: [U9+0]=XR0
0b000488: U0=U9+0
0b000489: XR1=U0
0b00048a: XR1=R1+1 //这里把第二个参数的位置指错了
0b00048b: U0=XR0
0b00048c: U1=XR1
0b00048d: CALL 0x0b001400
0b00048f: U8=U9
0b000490: XYR39=[U8+1,1] || U8=U8+2
0b000493: U9=XR39 || YSER=R39
0b000495: RET
printk源码:
/*
* printk
*
* Kernel printf function requiring minimal infrastrure.
*/
void printk(const char *fmt, ...)
{
va_list ap; /* points to each unnamed argument in turn */
va_start(ap, fmt); /* make ap point to 1st unnamed arg */
vprintk(fmt, ap);
va_end(ap); /* clean up when done */
}
printk()
中va_start(ap, fmt);
会把ap
指向fmt
的下一个地址,以前的编译器编译的结果不复制参数,所以没问题。这个编译器会复制参数,但是对于这种可变参数的,又只复制了第一个参数也就是fmt
,之后把ap
指向复制后的fmt
的下一个地址,就错了。如图1所示。
问题实质
因此错误就是printk()
处理参数错误,对可变参数列表没有进行复制,而在使用时又是按照已复制的形式来使用,因此使用了错误的参数,打印的结果自然也是错的,之前打印的是SER寄存器入栈之前的值。再深一层的原因就需要分析编译器的代码了,我的猜想可能是这个编译器处理可变参数列表的情况就会出错,当然只是瞎猜,有机会可以测试一下。
解决问题
为了解决这个问题,这里我们调整va_start(ap, fmt)
的实现。使得ap的位置指向复制后参数的往后 4 个地址,也即参数被复制之前的地址。
目前这种方法看起来是可行的,但是绝对不符合逻辑,编译器对我们来说不透明了,我们不应该知道编译器是怎么传参的。