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指令的对齐的访问:

Refs

Contact me via :)
虚怀乃若谷,水深则流缓。