内存寄存器两次移动后系统崩溃

时间:2018-10-02 09:51:36

标签: gcc linux-device-driver embedded-linux inline-assembly

我正在尝试通过用户空间(通过自定义驱动程序)在内存寄存器中执行一些写操作。我想写三个64位整数,并将变量“ value_1,value_2和value_3”初始化为uint64_t类型。

我必须使用gcc内联mov指令,并且正在针对嵌入式系统在定制版本的linux上开发ARM 64位体系结构。

Thi是我的代码:

  asm ( "MOV %[reg1], %[val1]\t\n"
        "MOV %[reg2], %[val2]\t\n"
        "MOV %[reg3], %[val3]\t\n"

        :[reg1] "=&r" (*register_1),[arg2] "=&r" (*register_2), [arg3] "=&r" (*register_3)
        :[val1] "r"(value_1),[val2] "r" (value_2), [val3] "r" (value_3)
     );

问题很奇怪... 如果我仅执行两个MOV,则代码有效。 如果执行全部三个MOV,则整个系统将崩溃,并且必须重新引导整个系统。

甚至是陌生人... 如果我在第二个和第三个MOV之间放置一个“ printf”甚至一个0纳秒的纳秒睡眠,代码就可以工作!

我环顾四周,试图找到解决方案,同时我也使用了内存的废话:

asm ( "MOV %[reg1], %[val1]\t\n"
      "MOV %[reg2], %[val2]\t\n"
      "MOV %[reg3], %[val3]\t\n"

      :[reg1] "=&r" (*register_1),[arg2] "=&r" (*register_2), [arg3] "=&r" (*register_3)
      :[val1] "r"(value_1),[val2] "r" (value_2), [val3] "r" (value_3)
      :"memory"
   );

...不起作用!

我还使用了第二个和第三个MOV之间或三个MOV末尾的内存屏障宏:

asm volatile("": : :"memory")

..不起作用!

此外,我尝试使用指针直接写入寄存器,并且具有相同的行为:第二次写入后,系统崩溃...

任何人都可以建议我一个解决方案。或者告诉我是否以错误的方式使用了gcc内联MOV或内存屏障?

---->更多详细信息<-----

这是我的主要爱好

int main() 
{ 
    int  dev_fd;
    volatile void * base_addr = NULL;
    volatile uint64_t * reg1_addr = NULL;

    volatile uint32_t * reg2_addr = NULL;

    volatile uint32_t * reg3_addr = NULL;

    dev_fd = open(MY_DEVICE, O_RDWR);
    if (dev_fd < 0)
    {
            perror("Open call failed");
            return -1;
    }

    base_addr = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, xsmll_dev_fd, 0);
    if (base_addr == MAP_FAILED)
    {
            perror("mmap operation failed");
            return -1;
    }
    printf("BASE ADDRESS VIRT: 0x%p\n", base_addr);

    /* Preparing the registers */
    reg1_addr = base_addr + REG1_OFF;
    reg2_addr = base_addr + REG2_OFF;
    reg3_addr = base_addr + REG3_OFF;

     uint64_t val_1 = 0xEEEEEEEE;
    uint64_t val_2 = 0x00030010;
    uint64_t val_3 = 0x01;

    asm ( "str %[val1], %[reg1]\t\n"
          "str %[val2], %[reg2]\t\n"
          "str %[val3], %[reg3]\t\n"

      :[reg1] "=&m" (*reg1_addr),[reg2] "=&m" (*reg2_addr), [reg3] "=&m" (*reg3_addr)
      :[val1] "r"(val_1),[val2] "r" (val_2), [val3] "r" (val_3)
      );

    printf("--- END ---\n");
    close(dev_fd);
    return 0;
  }

这是关于asm语句(linaro..I交叉编译)的编译器输出:

  400bfc:       f90013a0        str     x0, [x29,#32]
  400c00:       f94027a3        ldr     x3, [x29,#72]
  400c04:       f94023a4        ldr     x4, [x29,#64]
  400c08:       f9402ba5        ldr     x5, [x29,#80]
  400c0c:       f9401ba0        ldr     x0, [x29,#48]
  400c10:       f94017a1        ldr     x1, [x29,#40]
  400c14:       f94013a2        ldr     x2, [x29,#32]
  400c18:       f9000060        str     x0, [x3]
  400c1c:       f9000081        str     x1, [x4]
  400c20:       f90000a2        str     x2, [x5]

谢谢!

1 个答案:

答案 0 :(得分:2)

  

我尝试使用* reg1_addr = val_1;和我有同样的问题。

然后这不是问题所在。避免使用asm只是获得等效机器代码的一种更干净的方法,而不必使用嵌入式asm。问题可能出在您选择寄存器和值或内核驱动程序上。

还是您需要在写入第一个映射位置之前将值存储在CPU寄存器中,以避免在商店之间的堆栈中加载任何内容?这是我能想到的,您需要内联汇编代码的唯一原因,在此汇编程序生成的存储可能不相同。


原始问题的答案

"=&r"输出约束表示 CPU 寄存器。因此,您的inline-asm指令将按此顺序运行,组装成类似

mov x0, x5
mov x1, x6
mov x2, x7

然后,编译器生成的代码将以未指定的顺序将值存储回内存。该顺序取决于它选择如何为周围的C生成代码。这可能就是为什么更改周围的代码会改变行为的原因。

一种解决方案可能是使用"=&m"指令约束str,因此您的asm 确实存储到内存中。 str %[val1], %[reg1]是因为STR指令将寻址模式作为第二个操作数,即使它是目的地。


为什么不能像普通人一样使用volatile uint64_t* = register_1;来使编译器发出不允许重新排序或优化的存储指令? MMIO就是volatile的目的。

Linux是否没有用于执行MMIO加载/存储的宏或函数?


如果您对内联汇编有疑问,调试的第一步应该是查看编译器在填入asm模板时所发出的实际asm及其周围的代码。

然后通过代码单步执行指令(使用GDB stepi,也许处于layout reg模式)。