0

Linux カーネル 2.6.11 では、sysenter を使用してシステム コールを実行する場合、init 0x80 とほぼ同じで、save_all を使用してすべてのレジスタをカーネル スタックにプッシュしますが、コールが終了した後、関連するフラグが設定されていない場合、 sysexit を使用して戻りますが、スタックに保存されたすべてのレジスタを復元するわけではありません。

一部のシステム コールはレジスタ値を変更する可能性があります。すべてのレジスタを再設定する必要がないのはなぜですか

対応する i386 doc を読みました。

「Intel386 のすべてのレジスタはグローバルであるため、呼び出し関数と呼び出された関数の両方に表示されます。レジスタ %ebp、%ebx、%edi、%esi、および %esp は、呼び出し関数に「属します」。つまり、呼び出された関数呼び出し元のためにこれらのレジスタの値を保存する必要があります. 残りのレジスタは呼び出された関数に「属します」. 呼び出し元の関数がそのようなレジスタ値を関数呼び出し全体で保持したい場合は、ローカルスタックフレームに値を保存する必要があります.

したがって、保存作業を行うのは glibc ラッパー関数の責任であり、それを確認するためにいくつかの glibc コードを読みました。したがって、sysenter/sysexit を使用してシステム コールを実行する場合、最初に %ebp、%edx、%ecx をユーザー スタックにプッシュするのは理にかなっています。これは、%edx と %ecx が保存レジスタにないためです。完了後に後で復元する必要があります。また、%ebp を使用して、システム サービス ルーチンを呼び出す前にユーザー スタック ポインターを保存するため、パラメーターを渡すために復元する必要があります。

4

2 に答える 2

2

その理由は、 RCX がシステム コールにパラメーターを渡すために使用されず、64 ビット モードで R10 に置き換えられた理由と同じです。つまり、指示に関するインテルのドキュメントから:sysentersysexitsysexit

SYSEXIT を実行する前に、ソフトウェアは次の MSR および汎用レジスタに値を書き込むことによって、特権レベル 3 コード セグメントとコード エントリ ポイント、および特権レベル 3 スタック セグメントとスタック ポインタを指定する必要があります。

• IA32_SYSENTER_CS (MSR アドレス 174H) — 特権レベル 3 コードおよびスタック セグメントのセグメント セレクターを決定するために使用される 32 ビット値が含まれます (「操作」セクションを参照)。

RDX — このレジスタの標準アドレスは RIP にロードされます (したがって、この値はユーザー コードで実行される最初の命令を参照します)。戻りが 64 ビット モードでない場合、ビット 31:0 のみがロードされます。

ECX — このレジスタの標準アドレスは RSP にロードされます (したがって、この値には特権レベル 3 スタックのスタック ポインタが含まれます)。戻りが 64 ビット モードでない場合、ビット 31:0 のみがロードされます。

したがって、 rdx( edx) とrcx( ecx) は命令によって予約されています。今はどうebpですか?さて、sysenter指示に関するドキュメントから:

SYSENTER 命令と SYSEXIT 命令は対になる命令ですが、コールとリターンのペアを構成するものではありません。SYSENTER 命令を実行すると、プロセッサはユーザー コードの状態情報 (命令ポインタなど) を保存せず、SYSENTER 命令も SYSEXIT 命令もスタック上でのパラメータの受け渡しをサポートしません。

これは、 onにRSP置き換えられたという事実から明らかであるため、OS はユーザー空間スタックがどこにあるべきかさえ知りません。少なくとも、これを学ぶのは簡単ではありません。そのため、 Linuxはまさにこの目的のために予約しています。つまり、OS にユーザー スタックを提供することです。実行する前に で上書きする必要があるため、呼び出し元は保存する必要があります。IA32_SYSENTER_ESPsysenterebpebpespsysenter

Linuxがスタック ポインターを渡す目的で、edxまたは専用にしなかったのはなぜですか。これらの 2 つのレジスターは上書きされません。速度のためだと思います。通常の呼び出しでパラメーターを渡すために使用される場合、可能な最後の(6番目の)パラメーターです。syscall が 5 つ以上のパラメーターを必要とすることはめったにないため、ほぼすべてのシステム コール (またはがスタック ポインターに使用された場合) に対してユーザー空間スタックを読み取る代わりに、Linux は 6 つのパラメーターを使用するシステム コールに対してのみこれを行う必要があります。(実行する前に最後にプッシュする必要があることに注意してください。これは、カーネルが 6 番目のパラメーターの場所を認識している必要があるためです)。ecxsysenterebpint 0x80edxecxebpsysenter

これはすべて Linux ソースにまとめられていますarch/x86/entry/vdso/vdso32/sysenter.S

/*
 * The caller puts arg2 in %ecx, which gets pushed. The kernel will use
 * %ecx itself for arg2. The pushing is because the sysexit instruction
 * (found in entry.S) requires that we clobber %ecx with the desired %esp.
 * User code might expect that %ecx is unclobbered though, as it would be
 * for returning via the iret instruction, so we must push and pop.
 *
 * The caller puts arg3 in %edx, which the sysexit instruction requires
 * for %eip. Thus, exactly as for arg2, we must push and pop.
 *
 * Arg6 is different. The caller puts arg6 in %ebp. Since the sysenter
 * instruction clobbers %esp, the user's %esp won't even survive entry
 * into the kernel. We store %esp in %ebp. Code in entry.S must fetch
 * arg6 from the stack.
 *
 * You can not use this vsyscall for the clone() syscall because the
 * three words on the parent stack do not get copied to the child.
 */
于 2016-05-06T15:22:55.947 に答える
0

これは、使用される ABI (呼び出し規約) によって定義される必要があります。一部のレジスタは関数呼び出し間で保持されますが、一部は保持されません。プラットフォームで使用されている ABI を確認できます。

X64 については、 http: //x86-64.org/documentation/abi.pdfに文書化されています。図 3.4 を参照

呼び出し間で保持されるとは、レジスタが呼び出し先で保存されることを意味するため、関数は戻る前にレジスタを復元する必要があります。

保存されていないということは、呼び出し元が保存されていることを意味するため、関数はそれを直接使用できますが、復元することはできません。

于 2015-04-21T02:17:34.647 に答える