15

最近、カスタムLinuxカーネル(2.6.31.5、x86)ドライバーで、copy_to_userが定期的にバイトをユーザースペースにコピーしないという問題が発生しました。渡されたバイト数を返し、何もコピーしていないことを示します。コードを調べたところ、copy_to_userを呼び出しているときに、コードが割り込みを無効にしていて、コントラクトに違反していることがわかりました。これを修正した後、問題は発生しなくなりました。問題が発生する頻度は非常に低いため、割り込みを無効にすると問題が発生したことを証明する必要があります。

arch / x86 / lib /usercopy_32.crepから以下のコードスニペットを見ると; movslは、CXのカウントによって単語をユーザースペースにコピーします。サイズは終了時にCXで更新されます。movslが正しく実行されると、CXは0になります。CXはゼロではないので、movs?copy_to_userの定義と観察された動作に合わせるために、命令が実行されていない必要があります。

/* Generic arbitrary sized copy.  */
#define __copy_user(to, from, size)                 \
do {                                    \
    int __d0, __d1, __d2;                       \
    __asm__ __volatile__(                       \
        "   cmp  $7,%0\n"                   \
        "   jbe  1f\n"                  \
        "   movl %1,%0\n"                   \
        "   negl %0\n"                  \
        "   andl $7,%0\n"                   \
        "   subl %0,%3\n"                   \
        "4: rep; movsb\n"                   \
        "   movl %3,%0\n"                   \
        "   shrl $2,%0\n"                   \
        "   andl $3,%3\n"                   \
        "   .align 2,0x90\n"                \
        "0: rep; movsl\n"                   \
        "   movl %3,%0\n"                   \
        "1: rep; movsb\n"                   \
        "2:\n"                          \
        ".section .fixup,\"ax\"\n"              \
        "5: addl %3,%0\n"                   \
        "   jmp 2b\n"                   \
        "3: lea 0(%3,%0,4),%0\n"                \
        "   jmp 2b\n"                   \
        ".previous\n"                       \
        ".section __ex_table,\"a\"\n"               \
        "   .align 4\n"                 \
        "   .long 4b,5b\n"                  \
        "   .long 0b,3b\n"                  \
        "   .long 1b,2b\n"                  \
        ".previous"                     \
        : "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2)   \
        : "3"(size), "0"(size), "1"(to), "2"(from)      \
        : "memory");                        \
} while (0)

私が持っている2つのアイデアは次のとおりです。

  1. 割り込みが無効になっている場合、ページフォールトは発生せず、その後繰り返します。movs?何もせずにスキップされます。その場合、戻り値は、定義で指定され、観察された動作に応じて、CX、つまりユーザースペースにコピーされなかった量になります。
  2. ページフォールトは発生しますが、割り込みが無効になっているためLinuxはそれを処理できないため、ページフォールトハンドラーがこれをどのように行うかはわかりませんが、ページフォールトハンドラーは命令をスキップします。この場合も、CXは変更されずに残り、戻り値は正しくなります。

誰かが私にこの動作を指定するIntelマニュアルのセクションを指摘したり、役立つ可能性のある追加のLinuxソースを指摘したりできますか?

4

2 に答える 2

9

私は答えを見つけました。私の#2の提案は正しく、メカニズムは私の顔の真正面にありました。ページフォールトは発生しますが、fixup_exceptionメカニズムを使用して例外/続行メカニズムを提供します。このセクションでは、例外ハンドラテーブルにエントリを追加します。

    ".section __ex_table,\"a\"\n"               \
    "   .align 4\n"                 \
    "   .long 4b,5b\n"                  \
    "   .long 0b,3b\n"                  \
    "   .long 1b,6b\n"                  \
    ".previous"                     \

つまり、IPアドレスが最初のエントリであり、フォールトハンドラーで例外が発生した場合は、IPアドレスを2番目のアドレスに設定して続行します。

したがって、例外が「4:」で発生した場合は、「5:」にジャンプします。例外が「0:」で発生した場合は「3:」にジャンプし、例外が「1:」で発生した場合は「6:」にジャンプします。

欠落している部分は、arch / x86 / mm / fault.cのdo_page_fault()にあります。

/*
 * If we're in an interrupt, have no user context or are running
 * in an atomic region then we must not take the fault:
 */
if (unlikely(in_atomic() || !mm)) {
    bad_area_nosemaphore(regs, error_code, address);
    return;
}

write_lock_bh()ロックにあるため、in_atomicはtrueを返しました。bad_area_nosemaphoreは最終的に修正を行います。

page_faultが発生した場合(作業スペースの概念のため、発生する可能性は低いです)、関数呼び出しは失敗し、プリエンプションが無効になっているため、コピーされていないバイトがサイズに設定された状態で__copy_userマクロからジャンプします。

于 2012-09-27T20:53:12.350 に答える
4

ページフォールトはマスク可能な割り込みではありません。実際、それらは技術的にはまったく中断ではなく、むしろ例外ですが、違いがより意味論的であることに同意します。

割り込みを無効にしてアトミックコンテキストでcopy_to_userを呼び出したときに、copy_to_userが失敗した理由は、コードにこれに対する明示的なチェックがあるためです。

http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L575を参照してください

于 2012-09-27T13:45:57.613 に答える