gcc - 两次 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]
谢谢!
解决方案
我试过 *reg1_addr = val_1; 我也有同样的问题。
那么这段代码就不是问题了。避免asm
只是获得等效机器代码的一种更简洁的方法,而无需使用内联汇编。您的问题更有可能是您对寄存器和值或内核驱动程序的选择。
或者您是否需要在写入第一个映射位置之前将值存储在 CPU 寄存器中,以避免从存储之间的堆栈中加载任何内容?这是我能想到您需要内联汇编的唯一原因,其中编译器生成的存储可能不等效。
回答原始问题:
"=&r"
输出约束意味着CPU寄存器。因此,您的 inline-asm 指令将按该顺序运行,组装成类似的东西
mov x0, x5
mov x1, x6
mov x2, x7
然后,编译器生成的代码将以某种未指定的顺序将值存储回内存。该顺序取决于它选择如何为周围的 C 生成代码。这可能就是更改周围代码会改变行为的原因。
一种解决方案可能是指令"=&m"
约束,因此您的 asm实际上确实存储到内存中。 因为 STR 指令将寻址模式作为第二个操作数,即使它是目标。str
str %[val1], %[reg1]
为什么不能volatile uint64_t* = register_1;
像普通人一样使用,让编译器发出不允许重新排序或优化的存储指令?MMIO 正是volatile
为此而生的。
Linux 没有用于执行 MMIO 加载/存储的宏或函数吗?
如果您遇到内联 asm 的问题,调试的第 1 步应该是查看编译器在填充 asm 模板时发出的实际 asm 以及周围的代码。
然后通过代码单步说明(使用 GDB stepi
,可能在layout reg
模式下)。