【问题标题】:After two mov in memory register system crash两次 mov 后内存寄存器系统崩溃
【发布时间】:2018-10-02 09:51:35
【问题描述】:

我正在尝试从用户空间(通过自定义驱动程序)对内存寄存器执行一些写入操作。我想写三个 64 位整数并将变量“value_1, value_2 and 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]

谢谢!

【问题讨论】:

  • 哦,您应该使用asm volatile,因为您需要在正常编译(使用-O2-O3)而不是调试版本时不优化它。除此之外,IDK 看起来您的 asm 正在执行您指定的操作,因此请使用调试器单步执行它,和/或调试支持您 mmap 的设备文件的任何内核驱动程序。
  • 看起来你不需要内联汇编;如果您只是 *reg1_addr = val_1; 等等,编译器将为您发出那些 str 指令。
  • asm volatile 不能解决问题...我会尝试使用 GDB... 非常感谢您的帮助
  • 我试过 *reg1_addr = val_1;我也有同样的问题...
  • 那么这段代码不是问题。避免asm 只是获得等效机器代码的一种更简洁的方法,而无需使用内联汇编。您的问题可能是您选择的寄存器和值,或内核驱动程序。 (或者您是否需要在写入第一个映射位置之前将这些值放在 CPU 寄存器中,以避免从存储之间的堆栈中加载任何内容?这是您需要内联 asm 的唯一原因)

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


【解决方案1】:

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

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

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


回答原始问题

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

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

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

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


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

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


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

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

【讨论】:

  • 我用 str 修改了我的代码,但问题仍然存在......我用 volatile uint64_t * 定义了寄存器,并且我尝试直接用 poiters 编写,但它没有解决问题.. .
  • @colbacc8:然后显示足够的代码来生成minimal reproducible example,包括定义和编译器的 asm 输出。
  • @colbacc8:用您的代码编辑您的问题。您不能在 cmets 中格式化大块代码,无论如何它都属于问题。
  • 对不起...我在问题中添加了更多详细信息..(我也造成了很多混乱...回答我的问题...我点击按钮将其删除)
猜你喜欢
  • 2015-11-01
  • 2012-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-21
  • 2020-07-23
相关资源
最近更新 更多