Intel CPUのプログラムカウンターは、カーネルモードまたはその他のモードで直接(つまり、「トリック」なしで)読み取ることができますか?
7 に答える
いいえ、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
、rel32 = 0は、差出人アドレス予測スタックに影響を与えないように特別な場合があります。したがって、これは最新のx86で効率的でコンパクトであり、clangが32ビットの位置に依存しないコードに使用するものです。
古い32ビットのPentiumProCPUでは、これにより呼び出し/戻り予測スタックのバランスが崩れるためret
、親関数で最大15程度の将来の命令で分岐の予測ミスを回避するために、実際に戻る関数を呼び出すことをお勧めします。(戻ってこない場合、またはめったに問題にならない場合を除きます。)ただし、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-relativeを使用して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
特定の命令のアドレスが必要な場合は、通常、次のような方法でうまくいきます。
thisone:
mov (e)ax,thisone
(注:一部のアセンブラーでは、これは間違ったことを実行し、[thisone]から単語を読み取る可能性がありますが、通常、アセンブラーに正しいことを実行させるための構文があります。)
コードが特定のアドレスに静的にロードされている場合、アセンブラはすべての命令の絶対アドレスをすでに知っています(正しい開始アドレスを指定した場合)。動的にロードされたコードは、たとえば最新のOS上のアプリケーションの一部として、動的リンカーによって行われるアドレス再配置のおかげで正しいアドレスを取得します(アセンブラーが再配置テーブルを生成するのに十分スマートである場合)。
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));
.
アセンブラ ディレクティブは、アセンブラによって現在の命令のアドレスに置き換えられます。上記のスニペットを関数呼び出しでラップすると、毎回同じアドレス (その関数内) が取得されることに注意してください。より使いやすい 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 が壊れることはありません。
ラベルを値として使用して実行されているアドレスにアクセスするアーキテクチャに依存しない (ただし 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 マンページを確認してください。