LLVM调试记录 - 20180409

问题现象

调试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汇编代码

  1. 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所示。

图 1 vprintk()函数调用栈

图1 printk()函数调用栈

问题实质

因此错误就是printk()处理参数错误,对可变参数列表没有进行复制,而在使用时又是按照已复制的形式来使用,因此使用了错误的参数,打印的结果自然也是错的,之前打印的是SER寄存器入栈之前的值。再深一层的原因就需要分析编译器的代码了,我的猜想可能是这个编译器处理可变参数列表的情况就会出错,当然只是瞎猜,有机会可以测试一下。

解决问题

为了解决这个问题,这里我们调整va_start(ap, fmt)的实现。使得ap的位置指向复制后参数的往后 4 个地址,也即参数被复制之前的地址。
目前这种方法看起来是可行的,但是绝对不符合逻辑,编译器对我们来说不透明了,我们不应该知道编译器是怎么传参的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值