直接读程序计数器

Lir*_*evi 29 x86 assembly program-counter

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

Cod*_*ous 35

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

   mov eax, nearby_label    ; in position-dependent code
nearby_label:
Run Code Online (Sandbox Code Playgroud)

要使位置无关的32位代码获得EIP或IP:

        call _here
_here:  pop eax
; eax now holds the PC.
Run Code Online (Sandbox Code Playgroud)

在比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.
Run Code Online (Sandbox Code Playgroud)

在x86-64模式下,可以使用RIP相关直接读取RIPlea.

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

lea  rax, [_here]     ; RIP + 0
_here:
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这段代码实际上搞砸了返回值分支预测并减慢了很多.我会尝试为此找到参考... (8认同)
  • Re:_这个代码实际搞砸了返回值分支预测...我会尝试找到这个参考... _ - 参考是"Intel 64-ia-32优化手册" - > 3.4.1.4内联,调用和返回 - >"_返回地址堆栈机制增强了静态和动态预测器,专门针对调用和返回进行优化.它包含16个条目,足以覆盖大多数程序的调用深度....启用使用返回堆栈机制,调用和返回必须成对匹配_" (5认同)
  • 不知道为什么这是TrayMan答案的公认答案.TrayMan的版本没有意想不到的副作用,而且更短. (2认同)

Tra*_*Man 27

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

thisone: 
   mov (e)ax,thisone
Run Code Online (Sandbox Code Playgroud)

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

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

  • @csl:要么你在 OS X 上(除了使用 `lea rax, [thisone]` 之外没有其他解决方法),要么你在 Linux 上创建共享对象,因此这也不起作用。(“mov”-立即数需要链接时常量地址,因此它仅适用于位置相关的代码)。但是,如果您正在制作 Linux 可执行文件,[您的编译器可能默认制作位置无关的可执行文件,并且 `-no-pie -fno-pie` 制作位置相关的可执行文件,您可以在其中使用绝对寻址](https: //stackoverflow.com/questions/43367427/32-bit-absolute-addresses-no-longer-allowed-in-x86-64-linux)。 (2认同)

mat*_*tja 15

在x86-64上你可以这样做:

lea rax,[rip] (48 8d 05 00 00 00 00)
Run Code Online (Sandbox Code Playgroud)

  • `lea rax, [rip]` 在 NASM 2.10 中不起作用。似乎 RIP 只能与 `rel` 间接使用,如在 `lea rax, [rel _start]` 中? (2认同)

Ada*_*eld 8

没有指令直接读取x86上的指令指针(EIP).您可以使用一个内联汇编来获取当前指令的地址:

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

.汇编指令获取与由汇编当前指令的地址取代.请注意,如果您在函数调用中包装上面的代码段,则每次都会获得相同的地址(在该函数内).如果你想要一个更实用的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
Run Code Online (Sandbox Code Playgroud)

这意味着每次要获取指令指针时,由于需要额外的函数调用,效率会稍微降低.请注意,这样做不会破坏返回地址堆栈(RAS).返回地址堆栈是处理器内部使用的单独的返回地址堆栈,以便于RET指令的分支目标预测.

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