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);
   0x00000000004005e4 <+0>: fd 7b bd a9 stp x29, x30, [sp, #-48]!
   0x00000000004005e8 <+4>: fd 03 00 91 mov x29, sp
   0x00000000004005ec <+8>: f3 53 01 a9 stp x19, x20, [sp, #16]
   0x00000000004005f0 <+12>:    94 00 00 b0 adrp    x20, 0x411000 <__libc_start_main@got.plt>
   0x00000000004005f4 <+16>:    13 00 00 90 adrp    x19, 0x400000
   0x00000000004005f8 <+20>:    81 32 40 b9 ldr w1, [x20, #48]
   0x00000000004005fc <+24>:    73 c2 1b 91 add x19, x19, #0x6f0
   0x0000000000400600 <+28>:    f5 13 00 f9 str x21, [sp, #32]
   0x0000000000400604 <+32>:    95 c2 00 91 add x21, x20, #0x30
   0x0000000000400608 <+36>:    e0 03 13 aa mov x0, x19
   0x000000000040060c <+40>:    a1 ff ff 97 bl  0x400490 <printf@plt>

22      g_status.idx++;
   0x0000000000400610 <+44>:    a0 0a 40 39 ldrb    w0, [x21, #2]
   0x0000000000400614 <+48>:    00 1c 00 12 and w0, w0, #0xff
   0x0000000000400618 <+52>:    00 04 00 11 add w0, w0, #0x1
   0x000000000040061c <+56>:    00 1c 00 12 and w0, w0, #0xff
   0x0000000000400620 <+60>:    a0 0a 00 39 strb    w0, [x21, #2]

23      printf("status.value = 0x%08x\n", g_status.value);
   0x0000000000400624 <+64>:    e0 03 13 aa mov x0, x19
   0x0000000000400628 <+68>:    81 32 40 b9 ldr w1, [x20, #48]
   0x000000000040062c <+72>:    f3 53 41 a9 ldp x19, x20, [sp, #16]
   0x0000000000400630 <+76>:    f5 13 40 f9 ldr x21, [sp, #32]
   0x0000000000400634 <+80>:    fd 7b c3 a8 ldp x29, x30, [sp], #48
   0x0000000000400638 <+84>:    96 ff ff 17 b   0x400490 <printf@plt>
End of assembler dump.

从上边的汇编代码可以看到,在生成的代码中,系统调用了 ldur指令,这个指令是不带对齐的.

0x0000000000400610 <+44>:   a0 0a 40 39 ldrb    w0, [x21, #2]
0x0000000000400614 <+48>:   00 1c 00 12 and w0, w0, #0xff
0x0000000000400618 <+52>:   00 04 00 11 add w0, w0, #0x1
0x000000000040061c <+56>:   00 1c 00 12 and w0, w0, #0xff
0x0000000000400620 <+60>:   a0 0a 00 39 strb    w0, [x21, #2]

上面这个片段就是从内存中读取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);
   0x00000000004005e4 <+0>: fd 7b be a9 stp x29, x30, [sp, #-32]!
   0x00000000004005e8 <+4>: fd 03 00 91 mov x29, sp
   0x00000000004005ec <+8>: f3 53 01 a9 stp x19, x20, [sp, #16]
   0x00000000004005f0 <+12>:    93 00 00 b0 adrp    x19, 0x411000 <__libc_start_main@got.plt>
   0x00000000004005f4 <+16>:    14 00 00 90 adrp    x20, 0x400000
   0x00000000004005f8 <+20>:    61 32 40 b9 ldr w1, [x19, #48]
   0x00000000004005fc <+24>:    94 a2 1b 91 add x20, x20, #0x6e8
   0x0000000000400600 <+28>:    e0 03 14 aa mov x0, x20
   0x0000000000400604 <+32>:    a3 ff ff 97 bl  0x400490 <printf@plt>

22      g_status.idx++;
   0x0000000000400608 <+36>:    60 32 40 b9 ldr w0, [x19, #48]
   0x000000000040060c <+40>:    61 32 40 b9 ldr w1, [x19, #48]
   0x0000000000400610 <+44>:    00 5c 50 d3 ubfx    x0, x0, #16, #8
   0x0000000000400614 <+48>:    00 04 00 11 add w0, w0, #0x1
   0x0000000000400618 <+52>:    01 1c 10 33 bfi w1, w0, #16, #8
   0x000000000040061c <+56>:    61 32 00 b9 str w1, [x19, #48]

23      printf("status.value = 0x%08x\n", g_status.value);
   0x0000000000400620 <+60>:    e0 03 14 aa mov x0, x20
   0x0000000000400624 <+64>:    61 32 40 b9 ldr w1, [x19, #48]
   0x0000000000400628 <+68>:    f3 53 41 a9 ldp x19, x20, [sp, #16]
   0x000000000040062c <+72>:    fd 7b c2 a8 ldp x29, x30, [sp], #32
   0x0000000000400630 <+76>:    98 ff ff 17 b   0x400490 <printf@plt>
End of assembler dump.

同样的操作,g_status.idx++;在这里就变成了使用ldr指令的对齐的访问:

Refs

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