将寄存器值读入C变量

时间:2010-01-22 00:46:54

标签: c assembly inline-assembly

我记得有一种方法可以使用扩展的gcc内联汇编来读取寄存器值并将其存储到C变量中。我不能为我的生活记住如何形成asm陈述。任何帮助深表感谢。

8 个答案:

答案 0 :(得分:31)

到目前为止,与其他答案的方向不同,因为我不确定你想要什么。

GCC Manual § 5.40 Variables in Specified Registers

register int *foo asm ("a5");
     

这里a5是应该使用的寄存器的名称......

     

当然,寄存器名称依赖于cpu,但这不是问题,因为特定的寄存器通常对显式汇编程序指令很有用(参见Extended Asm)。这两件事通常都要求您根据cpu类型对程序进行条件化。

     

定义这样的寄存器变量不会保留寄存器;它仍可用于流量控制确定变量值不存在的地方的其他用途。

GCC Manual § 3.18 Options for Code Generation Conventions

  

-ffixed- REG

     
    

将名为 reg 的寄存器视为固定寄存器;生成的代码永远不应该引用它(除了可能作为堆栈指针,帧指针或其他固定角色)。

  

这可以用更简单的方式复制理查德的答案,

int main() {
    register int i asm("ebx");
    return i + 1;
}

虽然这是毫无意义的,因为你不知道ebx注册表中的内容。

如果将这两者结合起来,请使用gcc -ffixed-ebx

进行编译
#include <stdio.h>
register int counter asm("ebx");
void check(int n) {
    if (!(n % 2 && n % 3 && n % 5)) counter++;
}
int main() {
    int i;
    counter = 0;
    for (i = 1; i <= 100; i++) check(i);
    printf("%d Hamming numbers between 1 and 100\n", counter);
    return 0;
}

您可以确保C变量始终使用驻留在寄存器中以便快速访问,并且不会被其他生成的代码破坏。 (实际上,ebx是通常的x86调用约定下的callee-save,所以即使它被调用没有-ffixed-*编译的其他函数所破坏,它也应该被恢复。)

另一方面,这绝对不是可移植的,并且通常也不是性能优势,因为你限制了编译器的自由。

答案 1 :(得分:19)

以下是获取ebx的方法:

int main()
{
    int i;
    asm("\t movl %%ebx,%0" : "=r"(i));
    return i + 1;
}

结果:

main:
    subl    $4, %esp
    #APP
             movl %ebx,%eax
    #NO_APP
    incl    %eax
    addl    $4, %esp
    ret

<小时/> 编辑:

“= r”(i)是输出约束,告诉编译器第一个输出(%0)是应该放在变量“i”中的寄存器。在此优化级别(-O5),变量i永远不会存储到存储器中,而是保存在eax寄存器中,这也恰好是返回值寄存器。

答案 2 :(得分:5)

我不知道gcc,但在VS中这是如何:

int data = 0;   
__asm
{
    mov ebx, 30
    mov data, ebx
}
cout<<data;

基本上,我将ebx中的数据移动到您的变量data

答案 3 :(得分:3)

这会将堆栈指针寄存器移动到sp变量中。

intptr_t sp;
asm ("movl %%esp, %0" : "=r" (sp) );

只需将'esp'替换为您感兴趣的实际注册表(但请确保不要丢失%%)和'sp'替换变量。

答案 4 :(得分:2)

来自海湾合作委员会文件本身:http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

答案 5 :(得分:2)

#include <stdio.h>

void gav(){
        //rgv_t argv = get();
        register unsigned long long i asm("rax");
        register unsigned long long ii asm("rbx");
        printf("I`m gav - first arguman is: %s - 2th arguman is: %s\n", (char *)i, (char *)ii);
}

int main(void)
{
    char *test = "I`m main";
    char *test1 = "I`m main2";
    printf("0x%llx\n", (unsigned long long)&gav);
    asm("call %P0" : :"i"((unsigned long long)&gav), "a"(test), "b"(test1));
    return 0;
}

答案 6 :(得分:2)

当您的内联asm语句运行时,您无法知道编译器生成的代码将存储在任何寄存器中的值,因此该值通常没有意义,并且您可以做得更好使用调试器在断点处停止时查看寄存器值。

话虽这么说,如果你要做这项奇怪的任务,你也可以有效地做到这一点。

在某些目标(如x86)上,您可以使用特定寄存器输出约束来告诉编译器输出将在哪个寄存器中。使用特定寄存器输出约束为空asm模板(零指令)告诉编译器你的asm语句并不关心输入上的寄存器值,但之后给定的C变量将在该寄存器中。

#include <stdint.h>

int foo() {
    uint64_t rax_value;           // type width determines register size
    asm("" : "=a"(rax_value));  // =letter determines which register (or partial reg)

    uint32_t ebx_value;
    asm("" : "=b"(ebx_value));

    uint16_t si_value;
    asm("" : "=S"(si_value) );

    uint8_t sil_value;  // x86-64 required to use the low 8 of a reg other than a-d
       // With -m32:  error: unsupported size for integer register
    asm("# Hi mom, my output constraint picked %0" : "=S"(sil_value) );

    return sil_value + ebx_value;
}

使用clang5.0 on Godbolt for x86-64编译。请注意,2个未使用的输出值已经过优化,没有#APP / #NO_APP编译器生成的asm-comment对(将汇编程序切换到快速解析模式,或至少用于如果& #39; s不再是一件事)。这是因为我没有使用asm volatile,并且他们有一个输出操作数,因此他们不会隐式volatile

foo():                                # @foo()
# BB#0:
    push    rbx
    #APP
    #NO_APP
    #DEBUG_VALUE: foo:ebx_value <- %EBX
    #APP
    # Hi mom, my output constraint picked %sil
    #NO_APP
    #DEBUG_VALUE: foo:sil_value <- %SIL
    movzx   eax, sil
    add     eax, ebx
    pop     rbx
    ret
                                    # -- End function
                                    # DW_AT_GNU_pubnames
                                    # DW_AT_external

注意编译器生成的代码可以直接从指定的寄存器中将两个输出一起添加。还要注意RBX的push / pop,因为RBX是x86-64 System V调用约定中的一个调用保留寄存器。 (基本上所有32位和64位x86调用约定)。但是我们告诉编译器我们的asm语句在那里写了一个值。 (使用空的asm语句是一种破解;没有语法可以直接告诉编译器我们只想读取寄存器,因为就像我说的那样你不知道编译器在做什么插入asm语句时注册。)

编译器会将你的asm语句视为实际该寄存器,所以如果它需要以后的值,它会将它复制到另一个寄存器(或溢出到内存) asm声明&#34;运行&#34;。

其他x86 register constraintsb(bl / bx / ebx / rbx),c(... / rcx),d(.. ./rdx),S(sil / si / esi / rsi),D(... / rdi)。 bpl / bp / ebp / rbp没有特定的约束,即使它在没有帧指针的函数中并不特殊。 (也许是因为使用它会使你的代码不能用-fno-omit-frame-pointer进行编译。)

您可以使用register uint64_t rbp_var asm ("rbp"),在这种情况下,asm("" : "=r" (rbp_var));可以保证"=r"约束将选择rbp。同样地,对于r8-r15,它也没有任何明确的约束。在某些体系结构(如ARM)上,asm-register变量是指定asm输入/输出约束所需的寄存器的唯一方法。 (请注意 asm constraints are the only supported use of register asm variables ;不保证变量的值会在任何其他时间都在该寄存器中。

没有什么可以阻止编译器将这些asm语句放在函数内的任何位置(或内联后的父函数)。因此,您无法控制在哪里您正在对寄存器的值进行采样。 asm volatile可以避免某些重新排序,但可能仅针对其他volatile次访问。你可以检查编译器生成的asm,看看你是否得到了你想要的东西,但要注意它可能是偶然的,可能会在以后破坏。

您可以在依赖关系链中放置一个asm语句,以控制编译器放置它的位置。使用"+rm"约束来告诉编译器它修改了一些其他变量,这些变量实际上用于没有优化的东西。

uint32_t ebx_value;
asm("" : "=b"(ebx_value), "+rm"(some_used_variable) );

其中some_used_variable可能是来自一个函数的返回值,并且(在某些处理之后)作为arg传递给另一个函数。或者在循环中计算,并将作为函数的返回值返回。在这种情况下,asm语句保证在循环结束后的某个时刻出现,并且在依赖于该变量的后一个值的任何代码之前。

但这会破坏对该变量的常量传播等优化。 https://gcc.gnu.org/wiki/DontUseInlineAsm。编译器不能假设关于输出值的任何;它没有检查asm语句是否为零指令。

对于某些gcc不会让你用作输出操作数或clobbers的寄存器,这不起作用,例如:堆栈指针。

将值读入C变量可能对堆栈指针有意义,但是,如果你的程序对堆栈执行特殊操作。

作为inline-asm的替代方案,有__builtin_frame_address(0)来获取堆栈地址。 (但是IIRC,导致该函数创建一个完整的堆栈帧,即使启用了-fomit-frame-pointer,就像默认情况下在x86上一样。)

尽管如此,在许多函数中几乎是免费的(并且使堆栈帧对代码大小有利,因为RBP相对于RSP相对访问局部变量的寻址模式较小)。

mov语句中使用asm指令当然也可以。

答案 7 :(得分:-1)

不是this你在寻找什么?

语法:

 asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));