Generate Aligned Access Code
Table of Contents
用途
当使用C中的bit field时,编译器在编译时会生成按byte,字长等来访问的指令,对于普通的内存空间,这个都是可以正常使用的。
但是对于ARM平台中的寄存器,由于总线的限制,访问时必须按照32bit来访问, 当使用ldrb访问时,系统会发生异常.
例子
源代码
#include <stdio.h>
#include <stdint.h>
struct device_status {
union {
struct {
uint32_t length : 12;
uint32_t is_valid : 4;
uint32_t idx: 8;
};
uint32_t value;
};
} __attribute__((packed, aligned(4)));
volatile struct device_status g_status = {
.value = 0x1238,
};
void bitfield_test()
{
printf("status.value = 0x%08x\n", g_status.value);
g_status.idx++;
printf("status.value = 0x%08x\n", g_status.value);
}
int main(int argc, char *argv[])
{
bitfield_test();
return 0;
}
先看一下数据结构
/* offset | size */ type = volatile struct device_status {
/* 0 | 4 */ union {
/* 4 */ struct {
/* 0: 0 | 4 */ uint32_t length : 12;
/* 1: 4 | 4 */ uint32_t is_valid : 4;
/* 2: 0 | 4 */ uint32_t idx : 8;
/* XXX 1-byte padding */
/* total size (bytes): 4 */
};
/* 4 */ uint32_t value;
/* total size (bytes): 4 */
};
/* total size (bytes): 4 */
}
Normal Compile
在编译时通过指定 -mno-strict-align -fno-strict-volatile-bitfields 的编译参数,来防止编译器版本的不同而进行自动优化。
Dump of assembler code for function bitfield_test:
./test.c:
20 {
21 printf("status.value = 0x%08x\n", g_status.value);
0x00000000004005b4 <+0>: fd 7b bd a9 stp x29, x30, [sp, #-48]!
0x00000000004005b8 <+4>: fd 03 00 91 mov x29, sp
0x00000000004005bc <+8>: f3 53 01 a9 stp x19, x20, [sp, #16]
0x00000000004005c0 <+12>: 94 00 00 b0 adrp x20, 0x411000 <__libc_start_main@got.plt>
0x00000000004005c4 <+16>: 13 00 00 90 adrp x19, 0x400000
0x00000000004005c8 <+20>: 81 32 40 b9 ldr w1, [x20, #48]
0x00000000004005cc <+24>: 73 02 1b 91 add x19, x19, #0x6c0
20 {
0x00000000004005d0 <+28>: f5 13 00 f9 str x21, [sp, #32]
21 printf("status.value = 0x%08x\n", g_status.value);
0x00000000004005d4 <+32>: 95 c2 00 91 add x21, x20, #0x30
0x00000000004005d8 <+36>: e0 03 13 aa mov x0, x19
0x00000000004005dc <+40>: ad ff ff 97 bl 0x400490 <printf@plt>
22 g_status.idx++;
0x00000000004005e0 <+44>: a1 0a 40 39 ldrb w1, [x21, #2]
23 printf("status.value = 0x%08x\n", g_status.value);
0x00000000004005e4 <+48>: e0 03 13 aa mov x0, x19
22 g_status.idx++;
0x00000000004005e8 <+52>: 21 1c 00 12 and w1, w1, #0xff
0x00000000004005ec <+56>: 21 04 00 11 add w1, w1, #0x1
0x00000000004005f0 <+60>: 21 1c 00 12 and w1, w1, #0xff
0x00000000004005f4 <+64>: a1 0a 00 39 strb w1, [x21, #2]
23 printf("status.value = 0x%08x\n", g_status.value);
0x00000000004005f8 <+68>: f5 13 40 f9 ldr x21, [sp, #32]
0x00000000004005fc <+72>: 81 32 40 b9 ldr w1, [x20, #48]
24 }
0x0000000000400600 <+76>: f3 53 41 a9 ldp x19, x20, [sp, #16]
0x0000000000400604 <+80>: fd 7b c3 a8 ldp x29, x30, [sp], #48
23 printf("status.value = 0x%08x\n", g_status.value);
0x0000000000400608 <+84>: a2 ff ff 17 b 0x400490 <printf@plt>
End of assembler dump.
从上边的汇编代码可以看到,在生成的代码中,系统调用了 ldrb,strb指令,这个指令是不带对齐的.
上面这个片段就是从内存中读取g_status.idx 并对其加1操作,最后存入内存中.
上述代码片段中使用了ldrb的指令,在访问AHB接口寄存器的时候,会出现问题。
Compile with aligned access
下面是通过 -mstrict-align -fstrict-volatile-bitfields 参数进行编译的结果:
Dump of assembler code for function bitfield_test:
./test.c:
20 {
21 printf("status.value = 0x%08x\n", g_status.value);
0x00000000004005b4 <+0>: fd 7b be a9 stp x29, x30, [sp, #-32]!
0x00000000004005b8 <+4>: fd 03 00 91 mov x29, sp
0x00000000004005bc <+8>: f3 53 01 a9 stp x19, x20, [sp, #16]
0x00000000004005c0 <+12>: 93 00 00 b0 adrp x19, 0x411000 <__libc_start_main@got.plt>
0x00000000004005c4 <+16>: 14 00 00 90 adrp x20, 0x400000
0x00000000004005c8 <+20>: 61 32 40 b9 ldr w1, [x19, #48]
0x00000000004005cc <+24>: 94 02 1b 91 add x20, x20, #0x6c0
0x00000000004005d0 <+28>: e0 03 14 aa mov x0, x20
0x00000000004005d4 <+32>: af ff ff 97 bl 0x400490 <printf@plt>
22 g_status.idx++;
0x00000000004005d8 <+36>: 61 32 40 b9 ldr w1, [x19, #48]
0x00000000004005dc <+40>: 60 32 40 b9 ldr w0, [x19, #48]
0x00000000004005e0 <+44>: 21 5c 50 d3 ubfx x1, x1, #16, #8
0x00000000004005e4 <+48>: 21 04 00 11 add w1, w1, #0x1
0x00000000004005e8 <+52>: 20 1c 10 33 bfi w0, w1, #16, #8
0x00000000004005ec <+56>: 60 32 00 b9 str w0, [x19, #48]
23 printf("status.value = 0x%08x\n", g_status.value);
0x00000000004005f0 <+60>: e0 03 14 aa mov x0, x20
0x00000000004005f4 <+64>: 61 32 40 b9 ldr w1, [x19, #48]
24 }
0x00000000004005f8 <+68>: f3 53 41 a9 ldp x19, x20, [sp, #16]
0x00000000004005fc <+72>: fd 7b c2 a8 ldp x29, x30, [sp], #32
23 printf("status.value = 0x%08x\n", g_status.value);
0x0000000000400600 <+76>: a4 ff ff 17 b 0x400490 <printf@plt>
End of assembler dump.
同样的操作,g_status.idx++;在这里就变成了使用ldr指令的对齐的访问: