首页 > 解决方案 > 两次 mov 后内存寄存器系统崩溃

问题描述

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

我必须使用 gcc inline mov 指令,并且我正在为嵌入式系统的定制版本的 linux 上开发 ARM 64 位架构。

这是我的代码:

  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 纳秒的 nanosleep,代码就可以工作了!

我环顾四周试图找到一个解决方案,我也使用了内存的破坏:

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 cross compile):

  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]

谢谢!

标签: gcclinux-device-driverembedded-linuxinline-assembly

解决方案


我试过 *reg1_addr = val_1; 我也有同样的问题。

那么这段代码就不是问题了。避免asm只是获得等效机器代码的一种更简洁的方法,而无需使用内联汇编。您的问题更有可能是您对寄存器和值或内核驱动程序的选择。

或者您是否需要在写入第一个映射位置之前将值存储在 CPU 寄存器中,以避免从存储之间的堆栈中加载任何内容?这是我能想到您需要内联汇编的唯一原因,其中编译器生成的存储可能不等效。


回答原始问题

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

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

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

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


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

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


如果您遇到内联 asm 的问题,调试的第 1 步应该是查看编译器在填充 asm 模板时发出的实际 asm 以及周围的代码。

然后通过代码单步说明(使用 GDB stepi,可能在layout reg模式下)。


推荐阅读