如何在ARM内联汇编中访问本地C变量?

8

我想在ARM内联汇编中访问C语言中声明的局部变量。我该怎么做?

可以像这样访问全局变量:

int temp = 0;
Function(){
    __asm(
       ".global temp\n\t"           
        "LDR R2, =temp\n\t"                                                     
        "LDR R2, [R2, #0]\n\t"
    );
}       

但是我如何访问本地变量?我尝试将“.global”更改为本地变量的“.local”,但它生成了错误(未定义对 `temp` 的引用)。 我使用的IDE是KEIL。

有什么想法吗? 提前感谢。


2
本地变量存在于堆栈或寄存器中。如果没有编译器的支持,将很难转移它们。GCC内联汇编器会为您将它们放入寄存器中。我认为Keil是基于GCC的。.local只是意味着具有静态链接的“全局”变量。如果您声明void foo(void) { static int bar; },则可以通过这种方式访问bar。然而,对于C程序员来说,这不是“本地”的正常概念,而是“静态”的概念。 - artless noise
2个回答

6
根据GCC文档:6.45.2.3 输出操作数 您可以这样传递值:
#include <stdio.h>

int main(int argc, char *argv[]) {

  int src = 1;
  int dst;   

  asm ("mov %1, %0\n\t add $1, %0" : "=r" (dst) : "r" (src));

  printf("0x%X\n", dst);

  return 0;
}

在您的汇编代码后,您需要添加':'字符和要传递的值,如下所示:"(=|+)(r|m)" (variable)。当覆盖值时,请使用'=',当读取或覆盖值时,请使用'+',然后如果该值驻留在寄存器中,请使用字母'r',如果该值驻留在内存中,则使用'm'

1
这是使用AT&T语法(add src,dst)的x86,但是是的,带有约束的扩展ASM是正确的答案。 但是,您绝对不应该在内联asm中包含mov。始终使用约束条件,并让编译器生成mov以保存src的旧值。例如:asm(“inc%0”:“= r”(dst):“0”(src));因此%0从src开始并完成为dst(匹配约束)。当然,您无法以这种方式针对所有情况获得最佳代码;仍然会阻止编译器发出lea 1(%rax),%esi进行复制和添加。 https://gcc.gnu.org/wiki/DontUseInlineAsm - Peter Cordes

3

r最小可运行示例

main.c

#include <assert.h>
#include <inttypes.h>

int main(void) {
    uint64_t in0 = 1, in1 = 2, out;
    __asm__ (
        "add %[out], %[in0], %[in1];"
        : [out] "=r" (out)
        : [in0] "r"  (in0),
          [in1] "r"  (in1)
    );
    assert(in0 == 1);
    assert(in1 == 2);
    assert(out == 3);
}

GitHub 上游

编译并运行:

sudo apt-get install qemu-user gcc-aarch64-linux-gnu
aarch64-linux-gnu-gcc -std=c99 -ggdb3 -march=armv8-a -pedantic -Wall -Wextra -o main.out main.c
qemu-aarch64 -L /usr/aarch64-linux-gnu -E LD_BIND_NOW=1 main.out

反汇编:

gdb-multiarch -nh -batch -ex 'disassemble/rs main' add.out

输出摘录:

Dump of assembler code for function main:
add.c:
6   int main(void) {
   0x0000000000000764 <+0>: fd 7b bd a9 stp x29, x30, [sp, #-48]!
   0x0000000000000768 <+4>: fd 03 00 91 mov x29, sp

7       uint64_t in0 = 1, in1 = 2, out;
   0x000000000000076c <+8>: 20 00 80 d2 mov x0, #0x1                    // #1
   0x0000000000000770 <+12>:    e0 0f 00 f9 str x0, [sp, #24]
   0x0000000000000774 <+16>:    40 00 80 d2 mov x0, #0x2                    // #2
   0x0000000000000778 <+20>:    e0 13 00 f9 str x0, [sp, #32]

8       __asm__ (
   0x000000000000077c <+24>:    e0 0f 40 f9 ldr x0, [sp, #24]
   0x0000000000000780 <+28>:    e1 13 40 f9 ldr x1, [sp, #32]
   0x0000000000000784 <+32>:    00 00 01 8b add x0, x0, x1
   0x0000000000000788 <+36>:    e0 17 00 f9 str x0, [sp, #40]

9           "add %[out], %[in0], %[in1];"
10          : [out] "=r" (out)
11          : [in0] "r"  (in0),
12            [in1] "r"  (in1)
13      );

因此我们可以看到,r 被翻译成了相对于堆栈指针 spstr 加载,这就是本地变量所在的位置。

在 Ubuntu 18.10、GCC 8.2.0 和 QEMU 2.12 中进行了测试。


我认为你的assert无法真正测试asm未破坏仅输入寄存器。如果您不进行优化编译,则值将从堆栈重新加载。如果您进行优化编译,常量传播将在编译时将in0 == 1评估为true。您需要像escape函数这样的东西,它使用asm(“”:“+r”(in0));使编译器失去有关变量值*的任何知识,同时将其保留在寄存器中,在assert之前。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接