9

関数に kprobe を配置したので、kprobe のプリハンドラー関数でその引数の値を取得する必要があります。

これが私の機能です:

void foobar(int arg, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8)
{
    printk("foobar called\n");
}

kprobe を配置し、関数を呼び出します。

...
kp.addr = (kprobe_opcode_t *) foobar;
register_kprobe(&kp);

foobar(0xdead1, 0xdead2, 0xdead3, 0xdead4, 0xdead5, 0xdead6, 0xdead7, 0xdead8);

そして最後にプリハンドラー関数 (ここから取得):

static int inst_generic_make_request(struct kprobe *p, struct pt_regs *regs)
{
  printk(KERN_INFO "eax: %08lx   ebx: %08lx   ecx: %08lx   edx: %08lx\n",
    regs->ax, regs->bx, regs->cx, regs->dx);
    printk(KERN_INFO "esi: %08lx   edi: %08lx   ebp: %08lx   esp: %08lx\n",
      regs->si, regs->di, regs->bp, regs->sp);
    regs++;
    //...
}

プリハンドラー関数からの出力は次のようになります (regsポインターを 3 回インクリメントしました)

May 10 22:58:07 kernel: [  402.640994] eax: 000dead1   ebx: f7d80086   ecx: 000dead3   edx: 000dead2
May 10 22:58:07 kernel: [  402.640996] esi: 00000000   edi: b77c8040   ebp: 00000000   esp: f7d8006c

May 10 22:58:07 kernel: [  402.641006] eax: f7d8032c   ebx: 000dead5   ecx: 000dead6   edx: 000dead7
May 10 22:58:07 kernel: [  402.641007] esi: 000dead8   edi: f7d800e0   ebp: f7d80330   esp: 08049674

May 10 22:58:07 kernel: [  402.641014] eax: 00000080   ebx: 0992b018   ecx: 0000108e   edx: 0992b008
May 10 22:58:07 kernel: [  402.641015] esi: 08049674   edi: b77c8040   ebp: bfe23fb8   esp: bfe23f50

関数の引数がfoobarさまざまなレジスタに表示されるようになりました (ただし、? はどこにあり0xdead4ますか)。それらはスタックにあるべきではありませんか? プリハンドラー関数からスタックにアクセスするにはどうすればよいですか? または、関数の型と数を知らずに関数の引数を取得するにはどうすればよいでしょうか? これは簡単な作業ではない (すべての値を取得することさえできない) ことはわかっていますが、おおよその値だけで十分です。2 つの関数の引数間の相関を計算していますが、正確な値は必要ありません。引数がスタックにプッシュされる呼び出し元関数のアセンブリ コードがあれば役に立ちますか)。

4

2 に答える 2

14

少なくとも 2 つのアプローチがあります。

アプローチ 1: Jprobes

おそらく最も簡単な方法です。Jprobesが自分のタスクに適している場合は、試してみてください。これらは kprobes の近親者です (詳細な説明とカーネル ドキュメントの例へのリンクを参照してください)。

Jprobes を使用すると、プローブされたものと同じ署名を使用して関数を呼び出して、後者に入ることができます。このようにして、すべての引数を自動的に取得します。

アプローチ 2: レジスタとスタック

もう 1 つのアプローチは、既に行っていることを少し拡張して、レジスタとスタックから引数を取得することです。質問の出力ログから、32 ビット x86 システムで作業していると思います。

x86、32 ビット

私が見た限りでは、x86 上の Linux カーネルでの引数の受け渡しには、2 つの最も一般的な規則があります (詳細は、Agner Fog によるマニュアルを参照してください)。システム コールは他の規則に従うことに注意してください (詳細についてはマニュアルを参照してください)。

大会1

でマークさasmlinkageれた関数と可変引数リストを持つ関数の場合、すべてのパラメーターがスタックに渡されます。関数の戻りアドレスは、関数へのエントリのスタックの一番上にある必要があり、最初のパラメーターはそのすぐ下にあります。2 番目のパラメータは最初のパラメータより下にあり、以下同様です。

の保存された値があるespので、それが何を指しているかを見つけることができます。この規則が使用されている場合は*(esp+4)、最初の引数、 - 2 番目の引数などにする必要があります。*(esp+8)

コンベンション2

質問で言及したものを含め、ほとんどのカーネル機能に使用されているようです。

カーネルは でコンパイルされ-mregparm=3ているため、最初の 3 つの引数が に渡され、eaxこの順序で残りがスタックに置かれます。は 4 番目の引数、- 5 番目の引数などになります。edxecx*(esp+4)*(esp+8)

x86、64ビット

x86-64 では少し単純なようです。ほとんどのカーネル関数 (可変引数リストを含むものを含む) は、最初の 6 つの引数をrdi, rsi, rdx, rcx,の順r8に取得しr9、残りの引数はスタックに置かれます。*(esp+8)は 7 番目の引数である必要があります*(esp+16)- 8 番目など。

編集:

x86-32 では、カーネル モード トラップ (KProbes が依存するソフトウェア ブレークポイントを含む)の値espは保存されないことに注意してください。<asm/ptrace.h>は、 の正しい値を取得する関数を提供 します。これは、x86-32 と x86-64 の両方で機能します。詳細については、そのヘッダー ファイルの の説明を参照してください。pt_regskernel_stack_pointer()espkernel_stack_pointer()

さらに、regs_get_kernel_stack_nth()(そのヘッダーでも定義されています) は、ハンドラーでスタックの内容を取得する便利な方法を提供します。

于 2012-05-13T19:32:52.927 に答える
0

私の知る限り、特に Linux カーネル コードの場合、アセンブリ コードを分析して関数の引数を見つけるのは難しい作業です。関数の引数を見つける方法の 1 つは、デバッグ情報を使用することです。これを順を追って説明しましょう。

1) デバッグ情報 (-g オプション) を使用してカーネルまたはモジュールをビルドします。たとえば、デバッグ情報を使用して「test.ko」という名前のモジュールをビルドしたとします。

2) readelf コマンドを使用して、デバッグ情報をデコードします。このような:

   $readelf debug-dump=info test.ko > log.info

ここで、readelf の出力を log.info ファイルにリダイレクトしました。

3) log.info を開いて、関数の引数を調べたい関数を検索します。ここでは、'foobar()' とします。関数 foobar() の TAG DW_TAG_subprogram を持つ Dwarf エントリがあります。この TAG の後に、関数の引数名を持つ他のいくつかの dwarf エントリが見つかります。このエントリでは、関数が呼び出されたときのこれらの関数引数の場所を見つけることができます。たとえば、最初の引数 'arg' は ebx レジスタにあり、2 番目の引数は esp+8 にあり、3 番目の引数は ecx レジスタにあるというようになります。

4) これらの情報を取得したら、kprobe プリハンドラですべてのレジスタを出力します。また、スタック データも印刷します。これは、プリハンドラーの esp レジスタを知っているので、印刷できます。

5) 3 番目のステップで取得した情報に基づいて、引数の値を検索します。

于 2012-05-12T16:22:28.643 に答える