web-dev-qa-db-ja.com

プログラムカウンタを直接読み取る

Intel CPUのプログラムカウンタは、カーネルモードまたはその他のモードで直接(つまり、「トリック」なしで)読み取ることができますか?

29
Liran Orevi

いいえ、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は、リターンアドレス予測スタックに影響を与えないように特別な場合です 。したがって、これは最新のx86で効率的かつコンパクトであり、clangが32ビットの位置に依存しないコードに使用するものです。

古い32ビットのPentiumPro CPUでは、これにより呼び出し/戻り予測スタックのバランスが崩れるため、実際に戻る関数を呼び出すことをお勧めします。これにより、親の最大15個程度の将来のret命令での分岐の予測ミスを回避できます。関数。 (戻ってこない場合、またはめったに問題にならない場合を除きます。)ただし、return-addresspredictorsスタックは回復します。

_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はRIP相対leaを使用して直接読み取ることができます。

_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

35
Cody Brocious

特定の命令のアドレスが必要な場合は、通常、次のような方法でうまくいきます。

thisone: 
   mov (e)ax,thisone

(注:一部のアセンブラーでは、これが間違ったことを行い、[thisone]からWordを読み取る場合がありますが、通常、アセンブラーに正しいことを実行させるための構文がいくつかあります。)

コードが特定のアドレスに静的にロードされている場合、アセンブラはすべての命令の絶対アドレスをすでに知っています(正しい開始アドレスを指定した場合)。動的にロードされたコードは、たとえば最新のOS上のアプリケーションの一部として、動的リンカーによって行われるアドレス再配置のおかげで正しいアドレスを取得します(アセンブラーが再配置テーブルを生成するのに十分スマートである場合)。

27
TrayMan

X86-64では、次のように実行できます。

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

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( Codyのソリューション のように各CALLをRETと一致させないなど)を台無しにすると、次のようになります。不要な分岐予測が大量に発生し、プログラムの速度が低下します。このメソッドは、CALL命令とRET命令のペアが一致しているため、RASを破壊しません。

8
Adam Rosenfield

ラベルを値として使用して実行されているアドレスにアクセスするには、アーキテクチャに依存しない(ただし、gccに依存する)方法があります。

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

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

これは/ proc/statからも読み取ることができます。 procのマンページを確認してください。

0
Paul Praet