直接读程序计数器

时间:2009-03-01 15:38:54

标签: assembly x86 program-counter

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

7 个答案:

答案 0 :(得分:33)

不,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_syntaxlea rax, [rip]

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

答案 1 :(得分:27)

如果你需要特定指令的地址,通常会有这样的诀窍:

thisone: 
   mov (e)ax,thisone

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

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

答案 2 :(得分:15)

在x86-64上,您可以这样做:

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

答案 3 :(得分:8)

没有指令直接读取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)。返回地址堆栈是处理器内部使用的单独堆栈返回地址,以便于branch target prediction用于RET指令。

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

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:0)

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

答案 6 :(得分:-1)

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

当您使用'call'调用一个函数时,eip被压入堆栈,而当您重新调用eip时,eip才从堆栈中弹出。因此,您要做的就是推销所需的值,然后重新输入。 例如:

mov eax, 0x100
push eax`
ret

就完成了。