如何在Linux 64位上从C编写简单的内联asm指令?

San*_*p P 4 x86 assembly x86-64 cpu-registers

我正在写一个简单的c程序,我的要求是从程序的某些功能打印RIP(指令指针).我不想使用ptrace.

我尝试使用内联asm的一件事是: asm("movl %% rip,%0;":"= r"(val)) 这应该将我的rip寄存器值复制到变量val,但是我得到了编译错误.

如果我使用ebp/esp作为32位机器的基指针和堆栈指针,我不会得到任何编译错误,我的val有一些十六进制数分配.

我在这里几个问题:

1)因为我的机器是63位,上面的指令怎么能读取32位寄存器?

2)为什么我无法读取64位的任何寄存器,是否有任何问题b'caz的'r'

3)当我使用32位的eip我得到编译错误时,是否意味着IP寄存器被限制读取?

Ste*_*non 10

  1. 你的机器是64位,而不是63位=).您能够读取32位寄存器,因为您正在编译程序并将程序作为32位可执行文件运行[1].
  2. 您无法读取64位寄存器,因为您正在编译程序并将程序作为32位可执行文件运行.是的,你有一个64位处理器,但它仍然可以运行32位可执行文件(如果你的操作系统支持它,你明确地支持它),显然你的编译器工具链默认构建32位.如果您正在使用gcc,请尝试使用该-m64标志,或阅读编译器文档以获取更多信息.
  3. 与之不同rip,eip无法直接访问寄存器.你可以得到的值eip中所描述的方式吉姆在他的回答.

[1]无论如何,你将能够从64位可执行文件读取32位寄存器; 32位寄存器仍然可以在64位模式下使用,就像在32位模式下可以访问16位寄存器一样.


您的示例中仍存在一些问题:

首先,虽然rip可以在64位模式下访问,但它可以作为寻址模式访问; 这不是一个正常的登记册.如果要加载其值,则需要使用LEA,而不是MOV.

其次,因为rip是一个64位寄存器,你需要q在你的指令上使用后缀而不是l.这是一个解决了这两个问题的示例程序:

#include <stdio.h>
#include <inttypes.h>

int main(int argc, char *argv[]) {
    uint64_t ip;
    asm("leaq (%%rip), %0;": "=r"(ip));
    printf("rip is 0x%016" PRIx64 "\n", ip);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这似乎在我的机器上工作得很好.


小智 5

您可以通过执行以下操作获取EIP寄存器的值:

    call a  ; this pushes the value of EIP onto the stack
a:          
    pop ebx ; pops the value of EIP into register ebx
Run Code Online (Sandbox Code Playgroud)

然后你就可以阅读ebx了.

  • 请注意,这个历史悠久的技巧在64位上是不必要的; 你可以从`rip`中读取. (4认同)
  • 即,'lea rax,[rip]`. (2认同)