一,概述
在MIPS 体系结构中,正常的加载和存储必须对齐,半字必须从2个字节的边界加载,字必须从4个字节的边界取出,加载一个非对齐的地址会导致CPU 陷入异常。例如,lh 读取一个半字时,存储器的地址必须是2 的整数倍,lw读取一个字时,存储器的地址必须是4的整数倍,sd 写入一个双字时,存储器的地址必须是8 的整数倍。
Mips 提供非对齐访问的伪指令, uld, 在非对齐的地址上加载64位的数据。
Ulh 在非对齐的地址上加载半字。ulhu, ulw, usd, ush, usw。 一条伪指令至少有两条指令合成。
我们的系统安装了字节对齐异常的处理函数来仿效理想的加载操作,使得地址对齐的操作对用户程序是透明的。
mips 对齐问题以及32数据加载到64 寄存器高位数据扩展的问题,是编程时要特别注意。 最近碰到一个的问题,平台是龙芯2k+vxWorks6.8,寄存器宽度64位。简单示例如下,实验发现,未对齐的数据加载符号位并不能如设想的那样扩展。
//本示循环处理网络数据,一个网络数据可能包含2个tlv数据
数据示例(两个TLV数据相同):
02-02-07-00-11-22-33-44-55-66-77-02-02-07-00-11-22-33-44-55-66-77
struct tlv
{
unsigned short type;
unsigned short length;
unsigned char value[0];
};
void dq_test(struct tlv *pstMsg )
{
unsigned short msgtype = pstMsg->type;
printf("%s 0x%x, msgtype 0x%x,pstMsg->type 0x%x\n",__FUNCTION__, pstMsg,msgtype, pstMsg->type);
if (pstMsg->type < 0x300)
{
printf("test ok\n");
}
else
{
printf("test error !!!!!!!!!\n");
}
return ;
}
函数执行的打印如下,除了两条TLV消息的偏移地址不一样外,其它信息打印的都一样,但是第二条TLV数据(地址为0xc38baf7f)非2字节对齐,pstMsg->type < 0x300 判断为假。
dq_test 0xc38baf74, msgtype 0x202,pstMsg->type 0x202
test ok
dq_test 0xc38baf7f, msgtype 0x202,pstMsg->type 0x202
test error !!!!!!!!!
函数dq_test 的汇编如下:
c0467754 <dq_test>:
c0467754: 27bdffd0 addiu sp,sp,-48
c0467758: ffbf0028 sd ra,40(sp)
c046775c: ffbe0020 sd s8,32(sp)
c0467760: 03a0f02d move s8,sp
c0467764: afc40010 sw a0,16(s8)
c0467768: 8fc20010 lw v0,16(s8)
c046776c: 94420000 lhu v0,0(v0)
c0467770: a7c20000 sh v0,0(s8)
c0467774: 97c30000 lhu v1,0(s8)
c0467778: 8fc20010 lw v0,16(s8)
c046777c: 94420000 lhu v0,0(v0)
c0467780: 0040402d move a4,v0
c0467784: 3c02c06b lui v0,0xc06b
c0467788: 2444cb48 addiu a0,v0,-13496
c046778c: 3c02c06b lui v0,0xc06b
c0467790: 2445cb40 addiu a1,v0,-13504
c0467794: 8fc60010 lw a2,16(s8)
c0467798: 0060382d move a3,v1
c046779c: 0c109534 jal c04254d0 <printf>
c04677a0: 00000000 nop
if (pstMsg->type < 0x300)
c04677a4: 8fc20010 lw v0,16(s8)
c04677a8: 94420000 lhu v0,0(v0) // v0 中存储pstMsg->type 的值,
非对齐访问,触发对齐异常,异常处理中进行字节加载和移位。
c04677ac: 2c420300 sltiu v0,v0,768 // v0 = ((unsigned)v0 < (unsigned)0x300) ? 1:0
c04677b0: 10400007 beqz v0,c04677d0 //如果v0为零则跳到函数快结束的地方
c04677b4: 00000000 nop
c04677b8: 3c02c06b lui v0,0xc06b
c04677bc: 2444cb78 addiu a0,v0,-13448
c04677c0: 0c109534 jal c04254d0 <printf>
c04677c4: 00000000 nop
c04677c8: 08119df8 j c04677e0 <$L30>
c04677cc: 00000000 nop
c04677d0: 3c02c06b lui v0,0xc06b
c04677d4: 2444cb88 addiu a0,v0,-13432
c04677d8: 0c109534 jal c04254d0 <printf>
c04677dc: 00000000 nop
c04677e0: 03c0e82d move sp,s8
c04677e4: dfbf0028 ld ra,40(sp)
c04677e8: dfbe0020 ld s8,32(sp)
c04677ec: 27bd0030 addiu sp,sp,48
c04677f0: 03e00008 jr ra
c04677f4: 00000000 nop
用ejtag 调试程序,给比较指令打断点,程序进行比较时,运行停住,此时查看寄存器的值,发现v0 寄存器高32 位数值不对。
cpu0 -source configs/config.ls2k
b 0x98000000c04677ac
cpu0 -set
zero:0x0 at:0xfffffffffffffffe v0:0xffffffff00000202 v1:0x0
a0:0x3400ffa1 a1:0x0 a2:0x10 a3:0x103
t0:0x1 t1:0x3400ffa0 t2:0x0 t3:0x3400ffa1
t4:0x8c t5:0xa t6:0x4fdd t7:0x10
s0:0x3400ffa1 s1:0xffffffffc38bbba0 s2:0xffffffffc0790000 s3:0x0
s4:0x0 s5:0x0 s6:0x0 s7:0x0
t8:0x0 t9:0x0 k0:0xffffffffc04677c0 k1:0x3400ffa3
gp:0xffffffffc077f010 sp:0xffffffffc38baea0 s8:0xffffffffc38baea0 ra:0xffffffffc
04677b8
status:0x3400ffa1 lo:0x666666666666666a hi:0xe badvaddr:0xa
cause:0x40008410 pc:0xffffffffc04677ac epc:0xffffffffc04677c0
cpu0 –
怀疑是字节对齐异常处理的纠正有问题。
二,异常处理简介
mips异常处理向量0x180 源代码在 vectors.S 异常原因存储在 CP0_CAUSE 处理函数为 excStub 。vxworks 6.8 的版本,每个任务都有独立的异常栈,发生异常后都在发生异常时任务的异常栈中执行。首先保存被被中断任务的寄存器数值,执行结束后,再恢复中断任务的寄存器。
excStub
callExcHandler
异常钩子函数
excBsrTbl 异常处理函数表
VOIDFUNCPTR excBsrTbl[] =
{
excIntHandle, /* 0 - interrupt exception */
excExcHandle, /* 1 - tlb mod exception */
excExcHandle, /* 2 - tlb load exception */
excExcHandle, /* 3 - tlb store exception */
excExcHandle, /* 4 - address load exception */
excExcHandle, /* 5 - address store exception */
excExcHandle, /* 6 - instr. bus error exception*/
excExcHandle, /* 7 - data bus error exception */
excExcHandle, /* 8 - system call exception */
excExcHandle, /* 9 - break point exception */
excExcHandle, /* 10 - rsvd instruction exception*/
excExcHandle, /* 11 - coprocessor unusable excptn*/
excExcHandle, /* 12 - overflow exception */
excExcHandle, /* 13 - trap exception */
excExcHandle, /* 14 - VCEI exception */
excExcHandle, /* 15 - FPE exception */
excExcHandle, /* 16 - reserved entry */
excExcHandle, /* 17 - reserved entry */
excExcHandle, /* 18 - reserved entry */
excExcHandle, /* 19 - reserved entry */
excExcHandle, /* 20 - reserved entry */
excExcHandle, /* 21 - reserved entry */
excExcHandle, /* 22 - reserved entry */
excExcHandle, /* 23 - watch exception */
excExcHandle, /* 24 - reserved entry */
excExcHandle, /* 25 - reserved entry */
excExcHandle, /* 26 - reserved entry */
excExcHandle, /* 27 - reserved entry */
excExcHandle, /* 28 - reserved entry */
excExcHandle, /* 29 - reserved entry */
excExcHandle, /* 30 - reserved entry */
excExcHandle, /* 31 - VCED exception */
excIntHandle, /* 32 - software trap 0 */
excIntHandle, /* 33 - software trap 1 */
excIntHandle, /* 34 - autovec VME irq7 interrupt */
excIntHandle, /* 35 - autovec VME irq6 interrupt */
excIntHandle, /* 36 - autovec VME irq5 interrupt */
excIntHandle, /* 37 - autovec VME irq4 interrupt */
excIntHandle, /* 38 - autovec VME irq3 interrupt */
excIntHandle, /* 39 - autovec VME irq2 interrupt */
excIntHandle, /* 40 - autovec VME irq1 interrupt */
excIntHandle, /* 41 - spare interrupt */
excIntHandle, /* 42 - uart 0 interrupt */
excIntHandle, /* 43 - uart 1 interrupt */
excIntHandle, /* 44 - msg pending interrupt */
excIntHandle, /* 45 - spare interrupt */
excIntHandle, /* 46 - spare interrupt */
excIntHandle, /* 47 - hw bp interrupt */
excIntHandle, /* 48 - spare interrupt */
excIntHandle, /* 49 - spare interrupt */
excIntHandle, /* 50 - timer 0 interrupt */
excIntHandle, /* 51 - timer 1 interrupt */
excIntHandle, /* 52 - spare exception */
excIntHandle, /* 53 - spare exception */
excExcHandle, /* 54 - unimplemented FPA oper */
excExcHandle, /* 55 - invalid FPA operation */
excExcHandle, /* 56 - FPA div by zero */
excExcHandle, /* 57 - FPA overflow exception */
excExcHandle, /* 58 - FPA underflow exception */
excExcHandle, /* 59 - FPA inexact operation */
excIntHandle, /* 60 - bus error interrupt */
excIntHandle,
异常钩子安转
intVecSet ();
三,分析
查看lhu指令的非对齐纠正处理操作函数如下
字节对齐的中断处理
/*store the opcode to t0, RT to t5, RS to t6, offset to t7*/
and t5,t5,0x03e00000
srl t5, t5, 21
and t6, t6, 0x001f0000
srl t6, t6, 16
and t7, t7, 0x0000ffff
and t0, t0, 0xfc000000
srl t0, t0, 26
/*-----------------if opcode == op_lhu-----------------*/
op_lhu:
//机器码组成 37(指令码)+rb(r5)+rt(r6)+offset(r7)
//汇编指令 lhu v0,0(v0) v0 寄存器索引->r5 v0索引->r6 0->r7
li t0, _RTypeSize
multu t0, t5 //乘法单元的结果寄存器hi/lo hilo = (unsigned)t0*(unsigned)t5
mflo t0
addi t0, t0, E_STK_GREG_BASE
lw a0, FRAMEA1(lwIntr)(sp) /* pEsf to a0 */
addu a0, a0, t0
lw t4, 0(a0) /* load the RS address to t4 */
addu t4, t4, t7
lbu t2, 1(t4) //源地址单字节加载
lbu t3, 0(t4) //源地址单字节加载
sll t2, t2, 0x8 //左移8位
or t2, t2, t3 //或
li t0, _RTypeSize
multu t0, t6
mflo t0
addi t0, t0, E_STK_GREG_BASE //获取目标寄存器的偏移,在本例中就是V0
lw a0, FRAMEA1(lwIntr)(sp)
addu a0, a0, t0
/*sw t2, 0(a0)*/
sd t2, 0(a0) //--> 对于汇编 lhu v0,0(v0) 就是给目标寄存器v0 赋值
b process_end
nop
把 sw t2, 0(a0) 指令更换成sd t2, 0(a0) 问题解决。