最近对一段声卡裸板程序进行调试时发现了一个结构体对齐误用的错误。在接有wm8976的s3c2440开发板上调试裸板程序,用来解析并播放NandFlash上的wav二进制数据,使用dma传输数据到iis接口,并在传输完毕后触发中断再次传输。然而结果是既没有声音,dma中断也没有被触发,以为是dma的设置出错,经过反复试验后,将问题定位到了iis的部分代码:
struct i2s_regs
{
unsigned long iiscon; //0x55000000
unsigned long iismod;
unsigned long iispsr;
unsigned long iisfcon;
unsigned long iisfifo;
}__attribute__((packed));
static volatile struct i2s_regs* i2sregs;
void i2s_init(unsigned int bits_per_channel,unsigned int samp_per_second)
{
int i;
int min = 0xffff;
unsigned int tmp_fs;
unsigned int pre = 0;
i2sregs = (struct i2s_regs*)0x55000000;
GPECON &= ~0x3ff;
GPECON |= 0x2aa;
if(bits_per_channel == 16)
{
i2sregs->iismod = (2<<6) | (1<<3) | (1<<2) | (1);
}else{
i2sregs->iismod = (2<<6) | (0<<3) | (1<<2) | (1);
}
for(i = 0;i < 32;i++)
{
tmp_fs = PCLK/384/(i+1);
if(ABS(tmp_fs,samp_per_second) < min)
{
min = ABS(tmp_fs,samp_per_second);
pre = i;
}
}
i2sregs->iispsr = (pre<<5) | pre ;
i2sregs->iisfcon = (1<<15) | (1<<13) ;
i2sregs->iiscon = (1<<5) | (1<<1);
}
将结构体指针指向iis寄存器的首地址,通过该结构体指针来访问设置iis寄存器,去掉attribute__ ((packed)) 的属性描述符后成功运行了程序,那么为什么会出现这种问题呢?
因为attribute__ ((packed)) 的属性描述符的作用是不进行结构体成员的对齐,在内存中按成员的大小依次排列各成员,按理说 这五个成员都是unsigned long 类型,本身就是4字节对齐的,无论加不加上attribute__ ((packed)) 的属性描述符编译出来的结果都应该是一样的,也就是说结构体的所占大小应该都是20,加上打印语句后也是20。那么会不会是指针赋值的时候出了问题呢?i2sregs = (struct i2s_regs*)0x55000000;寄存器的首地址本身也是4字节对齐的,也不会出错,对每个结构体成员进行取址,得到的结果也都和芯片手册上写的一致,那么说明结构体指针映射没有问题,只可能是对它们进行赋值的时候出了问题了,这种情况下也只有查看反汇编的代码来看看究竟了。使用arm-linux-objdump对elf进行反汇编,分别得到加上属性描述符与不加的代码,来进行分析。
这是不加上attribute__ ((packed) 属性描述符的代码,忽略掉值的计算等指令,只寻找最后赋值的指令可以大致发现,对每个结构体成员(寄存器)进行最后的写入时,使用的都是str指令。
33f80860 <i2s_init>:
33f80860: e92d41f0 push {r4, r5, r6, r7, r8, lr}
33f80864: e3a03455 mov r3, #1426063360 ; 0x55000000
33f80868: e59f80d0 ldr r8, [pc, #208] ; 33f80940 <i2s_init+0xe0>
33f8086c: e1a04000 mov r4, r0
33f80870: e1a06001 mov r6, r1
33f80874: e59f00c8 ldr r0, [pc, #200] ; 33f80944 <i2s_init+0xe4>
33f80878: e1a01003 mov r1, r3
33f8087c: e5883000 str r3, [r8]
33f80880: eb000279 bl 33f8126c <printf>
33f80884: e3a01456 mov r1, #1442840576 ; 0x56000000
33f80888: e5913040 ldr r3, [r1, #64]
33f8088c: e3c33fff bic r3, r3, #1020 ; 0x3fc
33f80890: e3c33003 bic r3, r3, #3 ; 0x3
33f80894: e5813040 str r3, [r1, #64]
33f80898: e5912040 ldr r2, [r1, #64]
33f8089c: e3822faa orr r2, r2, #680 ; 0x2a8
33f808a0: e3822002 orr r2, r2, #2 ; 0x2
33f808a4: e3540010 cmp r4, #16 ; 0x10
33f808a8: e5812040 str r2, [r1, #64]
33f808ac: 0a00001f beq 33f80930 <i2s_init+0xd0>
33f808b0: e5982000 ldr r2, [r8]
33f808b4: e3a03085 mov r3, #133 ; 0x85
33f808b8: e5823004 str r3, [r2, #4]
33f808bc: e3a04000 mov r4, #0 ; 0x0
33f808c0: e3a05801 mov r5, #65536 ; 0x10000
33f808c4: e2455001 sub r5, r5, #1 ; 0x1
33f808c8: e1a07004 mov r7, r4
33f808cc: e2844001 add r4, r4, #1 ; 0x1
33f808d0: e3a00b7f mov r0, #130048 ; 0x1fc00
33f808d4: e28000a0 add r0, r0, #160 ; 0xa0
33f808d8: e1a01004 mov r1, r4
33f808dc: eb0001bb bl 33f80fd0 <__aeabi_idiv>
33f808e0: e1500006 cmp r0, r6
33f808e4: 80663000 rsbhi r3, r6, r0
33f808e8: 90603006 rsbls r3, r0, r6
33f808ec: e1530005 cmp r3, r5
33f808f0: 2a000003 bcs 33f80904 <i2s_init+0xa4>
33f808f4: e1500006 cmp r0, r6
33f808f8: e0605006 rsb r5, r0, r6
33f808fc: e2447001 sub r7, r4, #1 ; 0x1
33f80900: 80665000 rsbhi r5, r6, r0
33f80904: e3540020 cmp r4, #32 ; 0x20
33f80908: 1affffef bne 33f808cc <i2s_init+0x6c>
33f8090c: e5981000 ldr r1, [r8]
33f80910: e1873287 orr r3, r7, r7, lsl #5
33f80914: e5813008 str r3, [r1, #8]
33f80918: e3a02a0a mov r2, #40960 ; 0xa000
33f8091c: e3a03022 mov r3, #34 ; 0x22
33f80920: e581200c str r2, [r1, #12]
33f80924: e5813000 str r3, [r1]
33f80928: e8bd41f0 pop {r4, r5, r6, r7, r8, lr}
33f8092c: e12fff1e bx lr
33f80930: e5982000 ldr r2, [r8]
33f80934: e3a0308d mov r3, #141 ; 0x8d
33f80938: e5823004 str r3, [r2, #4]
33f8093c: eaffffde b 33f808bc <i2s_init+0x5c>
33f80940: 33f83248 .word 0x33f83248
33f80944: 33f830f0 .word 0x33f830f0
再看看加上attribute__ ((packed) 属性描述符的代码,同样查看给寄存器赋值的指令,发现使用的却是strb指令。
33f80828 <i2s_init>:
33f80828: e3a0c456 mov ip, #1442840576 ; 0x56000000
33f8082c: e59c3040 ldr r3, [ip, #64]
33f80830: e3c33fff bic r3, r3, #1020 ; 0x3fc
33f80834: e3c33003 bic r3, r3, #3 ; 0x3
33f80838: e58c3040 str r3, [ip, #64]
33f8083c: e59c2040 ldr r2, [ip, #64]
33f80840: e59f3168 ldr r3, [pc, #360] ; 33f809b0 <i2s_init+0x188>
33f80844: e3822faa orr r2, r2, #680 ; 0x2a8
33f80848: e3500010 cmp r0, #16 ; 0x10
33f8084c: e3822002 orr r2, r2, #2 ; 0x2
33f80850: e3a00455 mov r0, #1426063360 ; 0x55000000
33f80854: e92d41f0 push {r4, r5, r6, r7, r8, lr}
33f80858: e58c2040 str r2, [ip, #64]
33f8085c: e5830000 str r0, [r3]
33f80860: e1a06001 mov r6, r1
33f80864: 0a000042 beq 33f80974 <i2s_init+0x14c>
33f80868: e5d03004 ldrb r3, [r0, #4]
33f8086c: e3e0307a mvn r3, #122 ; 0x7a
33f80870: e5c03004 strb r3, [r0, #4]
33f80874: e3a02000 mov r2, #0 ; 0x0
33f80878: e5d03005 ldrb r3, [r0, #5]
33f8087c: e5c02005 strb r2, [r0, #5]
33f80880: e5d03006 ldrb r3, [r0, #6]
33f80884: e5c02006 strb r2, [r0, #6]
33f80888: e3a05801 mov r5, #65536 ; 0x10000
33f8088c: e5d03007 ldrb r3, [r0, #7]
33f80890: e5c02007 strb r2, [r0, #7]
33f80894: e1a04002 mov r4, r2
33f80898: e2455001 sub r5, r5, #1 ; 0x1
33f8089c: e1a07002 mov r7, r2
33f808a0: e2844001 add r4, r4, #1 ; 0x1
33f808a4: e3a00b7f mov r0, #130048 ; 0x1fc00
33f808a8: e28000a0 add r0, r0, #160 ; 0xa0
33f808ac: e1a01004 mov r1, r4
33f808b0: eb0001ce bl 33f80ff0 <__aeabi_idiv>
33f808b4: e1500006 cmp r0, r6
33f808b8: 80663000 rsbhi r3, r6, r0
33f808bc: 90603006 rsbls r3, r0, r6
33f808c0: e1530005 cmp r3, r5
33f808c4: 2a000003 bcs 33f808d8 <i2s_init+0xb0>
33f808c8: e1500006 cmp r0, r6
33f808cc: e0605006 rsb r5, r0, r6
33f808d0: e2447001 sub r7, r4, #1 ; 0x1
33f808d4: 80665000 rsbhi r5, r6, r0
33f808d8: e3540020 cmp r4, #32 ; 0x20
33f808dc: 1affffef bne 33f808a0 <i2s_init+0x78>
33f808e0: e1870287 orr r0, r7, r7, lsl #5
33f808e4: e3a03455 mov r3, #1426063360 ; 0x55000000
33f808e8: e20010ff and r1, r0, #255 ; 0xff
33f808ec: e5d32008 ldrb r2, [r3, #8]
33f808f0: e5c31008 strb r1, [r3, #8]
33f808f4: e1a02420 lsr r2, r0, #8
33f808f8: e20220ff and r2, r2, #255 ; 0xff
33f808fc: e5d31009 ldrb r1, [r3, #9]
33f80900: e5c32009 strb r2, [r3, #9]
33f80904: e1a01820 lsr r1, r0, #16
33f80908: e20110ff and r1, r1, #255 ; 0xff
33f8090c: e5d3200a ldrb r2, [r3, #10]
33f80910: e5c3100a strb r1, [r3, #10]
33f80914: e1a00c20 lsr r0, r0, #24
33f80918: e5d3200b ldrb r2, [r3, #11]
33f8091c: e5c3000b strb r0, [r3, #11]
33f80920: e3a01000 mov r1, #0 ; 0x0
33f80924: e5d3200c ldrb r2, [r3, #12]
33f80928: e5c3100c strb r1, [r3, #12]
33f8092c: e5d3200d ldrb r2, [r3, #13]
33f80930: e3e0205f mvn r2, #95 ; 0x5f
33f80934: e5c3200d strb r2, [r3, #13]
33f80938: e5d3200e ldrb r2, [r3, #14]
33f8093c: e5c3100e strb r1, [r3, #14]
33f80940: e5d3200f ldrb r2, [r3, #15]
33f80944: e5c3100f strb r1, [r3, #15]
33f80948: e5d32000 ldrb r2, [r3]
33f8094c: e3a02022 mov r2, #34 ; 0x22
33f80950: e5c32000 strb r2, [r3]
33f80954: e5d32001 ldrb r2, [r3, #1]
33f80958: e5c31001 strb r1, [r3, #1]
33f8095c: e5d32002 ldrb r2, [r3, #2]
33f80960: e5c31002 strb r1, [r3, #2]
33f80964: e5d32003 ldrb r2, [r3, #3]
33f80968: e5c31003 strb r1, [r3, #3]
33f8096c: e8bd41f0 pop {r4, r5, r6, r7, r8, lr}
33f80970: e12fff1e bx lr
33f80974: e5d03004 ldrb r3, [r0, #4]
33f80978: e3e03072 mvn r3, #114 ; 0x72
33f8097c: e5c03004 strb r3, [r0, #4]
33f80980: e3a02000 mov r2, #0 ; 0x0
33f80984: e5d03005 ldrb r3, [r0, #5]
33f80988: e5c02005 strb r2, [r0, #5]
33f8098c: e5d03006 ldrb r3, [r0, #6]
33f80990: e5c02006 strb r2, [r0, #6]
33f80994: e3a05801 mov r5, #65536 ; 0x10000
33f80998: e5d03007 ldrb r3, [r0, #7]
33f8099c: e1a04002 mov r4, r2
33f809a0: e5c02007 strb r2, [r0, #7]
33f809a4: e2455001 sub r5, r5, #1 ; 0x1
33f809a8: e1a07002 mov r7, r2
33f809ac: eaffffbb b 33f808a0 <i2s_init+0x78>
33f809b0: 33f83254 .word 0x33f83254
那么问题基本就大概明白了,确实是给寄存器赋值的时候出了问题,cpu在访问非4字节对齐的地址时,会访问相邻区域的内容,将读到的值进行丢弃、拼接组合后读回,写入的情况下也会先读取原来的值,修改掉要修改的部分后再写回,在访问内存等区域时会使用ldrb 、strb等指令,但是在操作寄存器时是否可以使用这些指令就不得而知了,为此,修改代码,同时定义加上对齐描述符的结构体和不加的,并让它们的指针都指向iis寄存器的首地址,在原代码中加上这些步骤
利用不加描述符的结构体指针访问iis寄存器,读出修改之前的值
在进行写入前打印下将要写入的值
利用加上描述符的结构体指针访问iis寄存器,读出修改之后的值
利用不加描述符的结构体指针访问iis寄存器,读出修改之后的值
iiscon np :0x00000100
iismod np :0x00000000
iispsr np :0x00000000
iisfcon np :0x00000000
iismod wr :0x0000008d
iispsr wr :0x000000a5
iisfcon wr :0x0000a000
iiscon wr :0x00000022
iismod rd :0x00000000
iispsr rd :0x00000000
iisfcon rd :0x00000000
iiscon rd :0x00000100
iismod np :0x00000000
iispsr np :0x00000000
iisfcon np :0x00000000
iiscon np :0x00000100
可以看到寄存器的值并没有任何改变,说明加上了不对齐的属性描述符后,编译器会进行一些优化,而这些处理恰恰使我们对寄存器的访问失败,导致了错误,因此,在进行寄存器映射时,因为寄存器本身就是4字节对齐的,可不要画蛇添足对 对齐方式进行特殊处理,否则得到的结果无法预料。