【问题标题】:Reading program counter directly直接读取程序计数器
【发布时间】:2010-10-10 15:24:05
【问题描述】:

可以在内核模式或其他模式下直接读取 Intel CPU 上的程序计数器(即没有“技巧”)吗?

【问题讨论】:

  • 相关:Why can't you set the instruction pointer directly?。你可以;该指令称为jmp。我在那里的回答解释了为什么 x86 机器代码的设计使您无法将 mov 编码为 eip,就像在 PC 是通用整数寄存器之一的 ARM 上那样。

标签: assembly x86 program-counter


【解决方案1】:

不,EIP / IP 不能直接访问,但在位置相关代码中,它是一个链接时间常数,因此您可以使用附近(或远处)符号作为立即数。

   mov eax, nearby_label    ; in position-dependent code
nearby_label:

获取与位置无关的 32 位代码中的 EIP 或 IP:

        call _here
_here:  pop eax
; eax now holds the PC.

在比 Pentium Pro(或 PIII 可能)更新的 CPU 上,call rel32 with rel32=0 is special-cased to not affect the return-address predictor stack。因此,这在现代 x86 上既高效又紧凑,并且是 clang 用于 32 位位置无关代码的方法。

在旧的 32 位 Pentium Pro CPU 上,这会使调用/返回预测器堆栈失衡,因此最好调用一个确实返回的函数,以避免在您的父级中多达 15 条左右的未来 ret 指令出现分支错误预测职能。 (除非您不打算返回,或者很少返回无关紧要。)不过,返回地址预测器堆栈会恢复。

get_retaddr_ppro:
    mov  eax, [esp]
    ret                ; keeps the return-address predictor stack balanced
                       ; even on CPUs where  call +0 isn't a no-op.

在 x86-64 模式下,可以使用 RIP 相关的lea 直接读取 RIP。

default rel           ; NASM directive: use RIP-relative by default

lea  rax, [_here]     ; RIP + 0
_here:

MASM 或 GNU .intel_syntax: lea rax, [rip]

AT&T 语法:lea 0(%rip), %rax

【讨论】:

  • 这段代码实际上搞砸了返回值分支预测,并大大减慢了你的速度。我会尝试为此找到参考...
  • 不知道为什么这是 TrayMan 答案的公认答案。 TrayMan 的版本没有意外的副作用,而且更短。
  • ptlsim.org/Documentation/html/node31.html对“返回地址堆栈”有很好的描述
  • 回复:这段代码实际上搞砸了返回值分支预测...我会尝试为此找到参考... - 参考是“英特尔的 64 -ia-32 优化手册” -> 3.4.1.4 内联、调用和返回 -> “返回地址堆栈机制增加了静态和动态预测器,专门针对调用和返回进行优化。它包含 16 个条目,足够大覆盖大多数程序的调用深度。...要启用返回堆栈机制,调用和返回必须成对匹配"
【解决方案2】:

如果您需要特定指令的地址,通常可以这样:

thisone: 
   mov (e)ax,thisone

(注意:在某些汇编器上,这可能会做错事并从 [thisone] 读取一个单词,但通常有一些语法可以让汇编器做正确的事情。)

如果您的代码静态加载到特定地址,则汇编器已经知道(如果您告诉它正确的起始地址)所有指令的绝对地址。动态加载的代码,比如作为任何现代操作系统上的应用程序的一部分,将获得正确的地址,这要归功于动态链接器完成的地址重定位(假设汇编器足够聪明,可以生成重定位表,它们通常是)。

【讨论】:

【解决方案3】:

在 x86-64 上,您可以执行以下操作:

lea rax,[rip] (48 8d 05 00 00 00 00)

【讨论】:

  • 谢谢!数字的含义是什么?
  • 这就是指令编码——隐含的 32 位偏移量为 0,我不确定是否有更短的编码
  • lea rax, [rip] 在 NASM 2.10 中不起作用。看来 RIP 只能像 lea rax, [rel _start] 那样间接与 rel 一起使用?
【解决方案4】:

x86 上没有直接读取指令指针(EIP)的指令。您可以通过一个小内联汇编获得当前正在汇编的指令的地址:

// GCC inline assembler; for MSVC, syntax is different
uint32_t eip;
__asm__ __volatile__("movl $., %0", : "=r"(eip));

. 汇编器指令被汇编器替换为当前指令的地址。请注意,如果您将上述 sn-p 包装在函数调用中,则每次都会获得相同的地址(在该函数内)。如果你想要一个更有用的 C 函数,你可以使用一些非内联汇编:

// In a C header file:
uint32_t get_eip(void);

// In a separate assembly (.S) file:
.globl _get_eip
_get_eip:
    mov 0(%esp), %eax
    ret

这意味着每次您想要获取指令指针时,它的效率都会稍低一些,因为您需要一个额外的函数调用。请注意,这样做不会破坏返回地址堆栈 (RAS)。返回地址堆栈是处理器内部使用的一个单独的返回地址堆栈,以方便 branch target prediction 用于 RET 指令。

每次有 CALL 指令时,当前的 EIP 都会被推送到 RAS 上,每次有 RET 指令时,都会弹出 RAS,并将顶部值用作该指令的分支目标预测。如果您弄乱了 RAS(例如没有将每个 CALL 与 RET 匹配,如Cody's solution),您将获得一大堆不必要的分支错误预测,从而减慢您的程序速度。这种方法不会破坏 RAS,因为它有一对匹配的 CALL 和 RET 指令。

【讨论】:

  • 非常感谢您的信息,我不知道有两个堆栈.. :)
  • RAS是处理器使用的内部堆栈;代码无法以任何方式访问它。它仅用于分支目标预测。没有它,代码仍然可以正常运行,只是速度更慢。
  • 非常感谢。如果在 Push 之后手动设置 ESP,RAS 会不会乱?
  • 或者只是获取当前函数的地址uintptr_t func_addr = (uintptr_t)&function_name;。在 x86 C/C++ 实现中,函数指针只是代码地址。这将组装成任何需要的东西。
【解决方案5】:

有一种独立于体系结构(但依赖于 gcc)的方式来访问通过使用标签作为值执行的地址:

http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

void foo()
{
  void *current_address = $$current_address_label;
  current_address_label:
      ....
}

【讨论】:

  • 这不完全是组装 :-)
  • 应该是&&current_address_label,而不是$$
  • 这不是相对地址(不是IP)吗?
  • @PeterMortensen:IDK 任何人都可以使用 C 函数中的某些代码的地址做什么有用,但这似乎与asm("movl $., %0", : "=r"(eip)) 答案一样好(如果不是更好,因为这个可以工作在 PIC 代码中也是如此。在这种情况下,编译器将不得不使用 RIP-relative LEA 或 32 位 PIC 方法来获取静态地址,但是当执行通过该标签时它将匹配 EIP,除非您已复制以破坏静态寻址的方式编写代码。)
【解决方案6】:

您也可以从 /proc/stat 中读取此内容。检查 proc 手册页。

【讨论】:

  • 我想你的意思是/proc/self/stat,引用手册页也很酷。
  • /proc/self/stat有一个字段根据man proc(5)kstkeip %lu :当前EIP(指令指针)。
  • 你能扩展这个答案吗?例如。包括一些上下文(这仅适用于 Linux 吗?)并考虑其他 cmets 中的信息。
  • 如果你按照“正常”的方式来做,你不会总是在libc的read()函数中得到syscall指令的地址吗?对于找出您自己的 EIP/RIP 值似乎没有用。
【解决方案7】:

有一个简单的方法可以改变程序计数器(eip)

当你用'call'调用一个函数时,eip被压入堆栈,然后当你ret时,eip只是从堆栈中弹出。所以,你所要做的就是推动你想要的价值,然后收回。 例如:

mov eax, 0x100
push eax`
ret

它已经完成了。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 2015-07-17
  • 2022-04-29
  • 1970-01-01
  • 2016-05-20
  • 1970-01-01
相关资源
最近更新 更多