大佬们,有个奇怪的现象帮忙分析下哇,调试uboot时发现了一个奇怪的指令异常,我看上去代码没什么问题,
当我选择关闭Thumb指令编译uboot,这个位置是没有错的,难道说thumb指令还有什么特殊的地方么?
还是说其他东西有坑?
U-Boot SPL 2018.11 (Apr 24 2020 - 23:50:16 +0800)
DRAM: 64 MiB
Trying to boot from FEL
U-Boot 2018.11 (Apr 24 2020 - 23:50:16 +0800) Allwinner Technology
CPU: Allwinner F Series (SUNIV)
Model: f1c200s
DRAM: 64 MiB
MMC: SUNXI SD/MMC: 0
Setting up a 480x272 lcd console (overscan 0x0)
In: serial@1c25400
Out: serial@1c25400
Err: serial@1c25400
Hit any key to stop autoboot: 0
Reading 4194304 byte(s) (2048 page(s)) at offset 0x001c0000
undefined instruction
pc : [<82fa9c64>] lr : [<82fa9c51>]
reloc pc : [<81716c64>] lr : [<81716c51>]
sp : 82e709f0 ip : fffdccb0 fp : 00000000
r10: deadbeef r9 : 82e72ec8 r8 : 82f932a0
r7 : 00000000 r6 : 00000000 r5 : 00000002 r4 : 82e749a0
r3 : 00000000 r2 : 00000002 r1 : 82e753a0 r0 : 82e753a0
Flags: Nzcv IRQs off FIQs off Mode SVC_32
Code: 29104019 2920d008 2900d00b 0038d106 (bc04b024)
Resetting CPU ...
问题位置反汇编
memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
81716c4a: 18c9 adds r1, r1, r3
81716c4c: f00c febc bl 817239c8 <__memcpy_from_thumb>
81716c50: e7c8 b.n 81716be4 <spinand_read_page+0x120>
switch (status & STATUS_ECC_MASK) {
81716c52: 2330 movs r3, #48 ; 0x30
81716c54: 4019 ands r1, r3
81716c56: 2910 cmp r1, #16
81716c58: d008 beq.n 81716c6c <spinand_read_page+0x1a8>
81716c5a: 2920 cmp r1, #32
81716c5c: d00b beq.n 81716c76 <spinand_read_page+0x1b2>
81716c5e: 2900 cmp r1, #0
81716c60: d106 bne.n 81716c70 <spinand_read_page+0x1ac>
}
81716c62: 0038 movs r0, r7
81716c64: b024 add sp, #144 ; 0x90 <--------------------------这个位置
81716c66: bc04 pop {r2}
81716c68: 4691 mov r9, r2
81716c6a: bdf0 pop {r4, r5, r6, r7, pc}
return nand->eccreq.strength;
81716c6c: 6a67 ldr r7, [r4, #36] ; 0x24
81716c6e: e7f8 b.n 81716c62 <spinand_read_page+0x19e>
return -EINVAL;
81716c70: 2716 movs r7, #22
return -EBADMSG;
81716c72: 427f negs r7, r7
81716c74: e7f5 b.n 81716c62 <spinand_read_page+0x19e>
81716c76: 274a movs r7, #74 ; 0x4a
81716c78: e7fb b.n 81716c72 <spinand_read_page+0x1ae>
81716c7a: 46c0 nop ; (mov r8, r8)
81716c7c: 00001301 .word 0x00001301
/81716c64
离线
看了一下U-Boot关于异常处理部分的代码:interrupts.c,感觉在异常发生处时处于ARM状态而非Thumb状态(Thumb状态会有(T)的提示),所以感觉可能是你的代码从其它地方跳转到此处时没有正确切换状态所致。可以检查一下到附近位置的跳转指令。
我大概梳理了一下,还是有几个迷惑点:
1. uboot代码未使用到Thumb和Arm的指令混编,不应该出现ARM状态
2. 该代码处是一函数调用,但由于优化并未产生调用,附近跳转也没有异常
3. 从code位置二进制上看,代码二进制并未被修改,即不应该是内存越界问题
4. 大概去看了下ThumbV1和ThumbV2没有发现在ADD上有什么差异,不知道是不是理解问题
离线
我觉得不是Thumb的问题,指令都是正确的,并且异常附近的Thumb指令都是从ARMv4T就支持的。
ARM和Thumb的状态切换是需要显式调用bx或blx指令,之后通过识别pc寄存器的最后一位来判断跳转后进入何种模式(0是ARM,1是Thumb)。所以问题可能是因为在其它地方调用bx或blx指令时错误地进入到了Thumb模式(汇编没写好时有这个可能性),但是在执行上面的指令时可能还是正常的(指令虽然乱套了但并没有引发异常),直到出现未定义指令异常。
U-Boot应该有开关可以控制是否使用Thumb模式(SYS_THUMB_BUILD),不过我觉得这个开关应该是正确打开的,否则不应该执行到这里才报错。更有可能的情况还是上面那种。如果不介意的话,可以把编译好后的文件发上来看看。
当然不介意,能解决这奇怪的问题最好,还没有一点思路。我又重新生成了一个确认打开SYS_THUMB_BUILD必出。
U-Boot SPL 2018.11 (Apr 25 2020 - 11:09:43 +0800)
DRAM: 64 MiB
Trying to boot from FEL
U-Boot 2018.11 (Apr 25 2020 - 11:09:43 +0800) Allwinner Technology
CPU: Allwinner F Series (SUNIV)
Model: f1c200s
DRAM: 64 MiB
MMC: SUNXI SD/MMC: 0
Setting up a 480x272 lcd console (overscan 0x0)
In: serial@1c25400
Out: serial@1c25400
Err: serial@1c25400
Hit any key to stop autoboot: 0
Reading 6291456 byte(s) (3072 page(s)) at offset 0x001c0000
undefined instruction
pc : [<82fa9c64>] lr : [<82fa9c51>]
reloc pc : [<81716c64>] lr : [<81716c51>]
sp : 82e709f0 ip : fffdccb0 fp : 00000000
r10: deadbeef r9 : 82e72ec8 r8 : 82f932a0
r7 : 00000000 r6 : 00000000 r5 : 00000002 r4 : 82e748d0
r3 : 00000000 r2 : 00000002 r1 : 82e752e0 r0 : 82e752e0
Flags: Nzcv IRQs off FIQs off Mode SVC_32
Code: 29104019 2920d008 2900d00b 0038d106 (bc04b024)
Resetting CPU ...
resetting ...
离线
看了异常附近的指令,函数内bx/blx指令只有一条,结合代码来看,应该是在core.c中的spinand_read_page函数返回处,而函数中blx指令对应的应该是同一文件中spinand_check_ecc_status函数使用的get_status函数指针。我检查了一下函数指针的地址,应该没有问题。
重新整理一下问题,我觉得是这样的:
首先,异常内容是未定义指令,但当前出错的指令实际上是ARMv5T兼容的,因此和架构没有关系。
其次,看U-Boot的提示,当前应该处在ARM状态而非Thumb状态,这与预期不符(可以通过前面说的(T)提示和Code的指令长度发现CPU将这些指令当成了32位宽的ARM指令),这应该是问题的原因。
不过,就目前的已有信息,还是很难看出是哪个地方引发了错误的状态切换,毕竟代码能够跑到这里来,侧面说明了这个错误的状态切换很可能是在异常附近发生的。事实上,前面几条指令在ARM状态下也能执行(非未定义指令),只是执行结果是错误的而已。
要想快速排查出错误原因,应该还是要上调试器比较靠谱,在异常附近加个断点,看看在异常附近的状态切换是否符合要求。
另外补充一下,除了bl/blx指令之外,pop指令也有改变执行状态的能力,前提是pop指令的操作对象包括pc。
谢谢大神的分析指点,我又仔细回顾了一下当前的现象:
1. CPU的状态目前看起来确实是不正确的,但是是不是我觉得不能确定,不是很熟悉uboot的那段汇编,但是CPRS应该不能直接访问把。
2. 从现象上来看,问题现场不好确定是不是这个位置,有可能是其他代码块引发,然后飞到这个位置上来的,但从lr的寄存器上看,返回地址又是正确的
3. 尝试了下加入故障点,发现问题发生在spinand_check_ecc_status函数返回,mrs指令在thumb下不可用,求大神支个招。
4. 手上没有调速器,这个坑爹问题验证不了,害....
static inline unsigned __get_cpsr(void)
{
unsigned long retval;
asm volatile (" mrs %0, cpsr" : "=r" (retval) : );
return retval;
}
static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
if (spinand->eccinfo.get_status)
return spinand->eccinfo.get_status(spinand, status);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
printf("cpsr -> 0x%x\n", __get_cpsr());
return 0;
case STATUS_ECC_HAS_BITFLIPS:
/*
* We have no way to know exactly how many bitflips have been
* fixed, so let's return the maximum possible value so that
* wear-leveling layers move the data immediately.
*/
return nand->eccreq.strength;
case STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
default:
break;
}
return -EINVAL;
}
离线
确实有可能是其它代码块引发的问题,不过U-Boot已经执行到这个地方了,我觉得还是先在附近查找问题比较好。
CPSR(准确说是SPSR,因为是表示异常触发时记录的CPSR寄存器内容)是在vectors.S的get_bad_stack宏中存储的,这边的逻辑应该没有错,反映的就是异常发生时(而非进入异常后)的CPSR寄存器状态。
在Thumb状态中,确实没有什么比较好的办法来获取CPSR寄存器的内容,不过我觉得可以写个函数尝试输出函数内lr寄存器(记录返回地址)的内容,如果该函数正确执行的话,输出的lr寄存器的最低位应当是1(表示Thumb状态)。为了保存T状态位,这个函数必须使用blx调用,而blx接受的参数是寄存器而非常量offset,我认为通过函数指针访问函数的方式应该会编译成带blx跳转的形式(只要没被编译器优化),可以试试。
情况越来越不明了了,我现在越来越怀疑是不是存在越界或者其他什么问题了。添加了如下测试点
static inline unsigned __get_cpsr(void)
{
unsigned long retval;
asm volatile (" mrs %0, cpsr" : "=r" (retval) : );
return retval;
}
static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
if (spinand->eccinfo.get_status)
return spinand->eccinfo.get_status(spinand, status);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
printf("cpsr -> 0x%x\n", __get_cpsr());
return 0;
case STATUS_ECC_HAS_BITFLIPS:
/*
* We have no way to know exactly how many bitflips have been
* fixed, so let's return the maximum possible value so that
* wear-leveling layers move the data immediately.
*/
return nand->eccreq.strength;
case STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
default:
break;
}
return -EINVAL;
}
验证在spinand_init函数下添加TEST点,返回地址确实最低位为1,测试函数代码段OK。
noinline static void TEST(void)
{
unsigned int tfunc = (unsigned int) __builtin_return_address(0);
if (gd->flags & GD_FLG_RELOC)
tfunc -= gd->reloc_off;
printf("ret <- 0x%x\n", tfunc);
}
static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
TEST();
if (spinand->eccinfo.get_status)
return spinand->eccinfo.get_status(spinand, status);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
return 0;
case STATUS_ECC_HAS_BITFLIPS:
/*
* We have no way to know exactly how many bitflips have been
* fixed, so let's return the maximum possible value so that
* wear-leveling layers move the data immediately.
*/
return nand->eccreq.strength;
case STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
default:
break;
}
return -EINVAL;
}
当添加到前面的测试点位置时,迷惑的事情又出现了,打印并未出现。确认TEST点(81716c1a)在故障(81716c7c)位置之前。
奇奇怪怪的事情真奇妙,lr的地址显示他都在thumb模式下。而TEST点打印未出现,我怀疑堆栈可能已经GG,但是uboot下并未有
栈回溯功能,还是我不知道?
U-Boot 2018.11 (Apr 26 2020 - 18:24:48 +0800) Allwinner Technology
CPU: Allwinner F Series (SUNIV)
Model: f1c200s
DRAM: 64 MiB
MMC: SUNXI SD/MMC: 0
Setting up a 480x272 lcd console (overscan 0x0)
In: serial@1c25400
Out: serial@1c25400
Err: serial@1c25400
Hit any key to stop autoboot: 0
ret <- 0x817172c9
Reading 6291456 byte(s) (3072 page(s)) at offset 0x001c0000
undefined instruction
pc : [<82fa9c90>] lr : [<82fa9c7d>]
reloc pc : [<81716c90>] lr : [<81716c7d>]
sp : 82e709f0 ip : fffdcc80 fp : 00000000
r10: deadbeef r9 : 82e72ec8 r8 : 82f932a0
r7 : 00000000 r6 : 00000000 r5 : 00000002 r4 : 82e748d0
r3 : 00000000 r2 : 00000002 r1 : 82e752e0 r0 : 82e752e0
Flags: Nzcv IRQs off FIQs off Mode SVC_32
Code: 29104019 2920d008 2900d00b 0038d106 (bc04b024)
Resetting CPU ...
resetting ...
感觉在迷茫的路上越来越迷茫了,也可能是我并不能让编译器生成跳转为blx的调用,大神还有什么招数没有哦?
U-Boot 2018.11 (Apr 26 2020 - 18:25:27 +0800) Allwinner Technology
CPU: Allwinner F Series (SUNIV)
Model: f1c200s
DRAM: 64 MiB
MMC: SUNXI SD/MMC: 0
Setting up a 480x272 lcd console (overscan 0x0)
In: serial@1c25400
Out: serial@1c25400
Err: serial@1c25400
Hit any key to stop autoboot: 0
Reading 6291456 byte(s) (3072 page(s)) at offset 0x001c0000
undefined instruction
pc : [<82fa9c7c>] lr : [<82fa9c7f>]
reloc pc : [<81716c7c>] lr : [<81716c7f>]
sp : 82e709f0 ip : fffdcc88 fp : 00000000
r10: deadbeef r9 : 82e72ec8 r8 : 82f932a0
r7 : 00000000 r6 : 00000000 r5 : 00000002 r4 : 82e748d0
r3 : 00000000 r2 : 00000002 r1 : 82e752e0 r0 : 82e752e0
Flags: nZCv IRQs off FIQs off Mode SVC_32
Code: 18129309 9210414b e7af9311 f00c18c9 (e7c5feb9)
Resetting CPU ...
resetting ...
81716c70: 414b adcs r3, r1
81716c72: 9210 str r2, [sp, #64] ; 0x40
81716c74: 9311 str r3, [sp, #68] ; 0x44
81716c76: e7af b.n 81716bd8 <spinand_read_page+0xec>
memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
81716c78: 18c9 adds r1, r1, r3
81716c7a: f00c feb9 bl 817239f0 <__memcpy_from_thumb>
81716c7e: e7c5 b.n 81716c0c <spinand_read_page+0x120> <-----------------------lr地址
switch (status & STATUS_ECC_MASK) {
81716c80: 2130 movs r1, #48 ; 0x30
81716c82: 4029 ands r1, r5
81716c84: 2910 cmp r1, #16
81716c86: d008 beq.n 81716c9a <spinand_read_page+0x1
81716c02: 000a movs r2, r1
81716c04: 0001 movs r1, r0
81716c06: 9806 ldr r0, [sp, #24]
81716c08: f7fe f840 bl 81714c8c <mtd_ooblayout_get_databytes>
if (!ecc_enabled)
81716c0c: 9b07 ldr r3, [sp, #28]
81716c0e: 2b00 cmp r3, #0
81716c10: d03e beq.n 81716c90 <spinand_read_page+0x1a4>
return spinand_check_ecc_status(spinand, status);
81716c12: aa08 add r2, sp, #32
81716c14: 230d movs r3, #13
81716c16: 189b adds r3, r3, r2
81716c18: 781d ldrb r5, [r3, #0]
TEST();
81716c1a: f7ff fdff bl 8171681c <TEST> <-----------------------------------------
if (spinand->eccinfo.get_status)
81716c1e: 6e23 ldr r3, [r4, #96] ; 0x60
81716c20: 2b00 cmp r3, #0
81716c22: d02d beq.n 81716c80 <spinand_read_page+0x194>
return spinand->eccinfo.get_status(spinand, status);
81716c24: 0029 movs r1, r5
81716c26: 0020 movs r0, r4
81716c28: 4798 blx r3
81716c2a: 0007 movs r7, r0
81716c2c: e030 b.n 81716c90 <spinand_read_page+0x1a4>
void *buf = NULL;
81716c2e: 9703 str r7, [sp, #12]
unsigned int nbytes = 0;
81716c30: 003e movs r6, r7
81716c32: e7ac b.n 81716b8e <spinand_read_page+0xa2>
离线
看来没什么好办法了只有先放放了
离线