33

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

4

7 回答 7

41

不,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 rel32rel32=0 是特殊情况,不会影响返回地址预测器堆栈。因此,这在现代 x86 上既高效又紧凑,并且是 clang 用于 32 位位置无关代码的方法。

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

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-relative 直接读取 RIPlea

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

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

MASM 或 GNU .intel_syntaxlea rax, [rip]

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

于 2009-03-01T15:45:01.800 回答
27

如果您需要特定指令的地址,通常这样的方法可以解决问题:

thisone: 
   mov (e)ax,thisone

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

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

于 2009-03-01T16:18:42.157 回答
17

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

lea rax,[rip] (48 8d 05 00 00 00 00)
于 2009-06-26T08:25:38.397 回答
9

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

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

汇编器指令被.汇编器替换为当前指令的地址。请注意,如果您将上述代码段包装在函数调用中,则每次都会获得相同的地址(在该函数内)。如果你想要一个更有用的 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)。返回地址堆栈是处理器内部使用的一个单独的返回地址堆栈,用于帮助预测RET 指令的分支目标。

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

于 2009-03-01T17:04:56.103 回答
3

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

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

void foo()
{
  void *current_address = $$current_address_label;
  current_address_label:
      ....
}
于 2012-06-13T23:35:35.807 回答
0

您也可以从 /proc/stat 中阅读此内容。检查 proc 联机帮助页。

于 2010-11-24T12:49:16.527 回答
-1

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

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

mov eax, 0x100
push eax`
ret

它完成了。

于 2020-02-04T20:42:22.653 回答