直接读取程序计数器

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

不,(E / R)IP不能直接访问。 为拿到它,为实现它:

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

如果你需要一个特定的指令的地址,通常这样的伎俩:

 thisone: mov (e)ax,thisone 

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

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

在x86-64上你可以做例如:

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

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

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

. 汇编程序指令由汇编程序replace为当前指令的地址。 请注意,如果您在函数调用中包装上面的代码片段,则每次只会获得相同的地址(在该函数内)。 如果你想要一个更有用的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被popup,最高值被用作该指令的分支目标预测。 如果你搞砸了RAS(比如不把每个CALL与RET匹配,就像在Cody的解决scheme中一样 ),你会得到一大堆不必要的分支预测失误,从而减慢程序的运行速度。 这种方法不会使RAS受到影响,因为它有一对匹配的CALL和RET指令。

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

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

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

你也可以从/ proc / stat中读到这个。 检查proc手册。