14

夏の研究のためにカーネルの作業に取り掛かります。特定の RTT 計算で、TCP に変更を加える予定です。私がやりたいことは、tcp_input.c 内の関数の 1 つの解決を、動的にロードされたカーネル モジュールによって提供される関数に置き換えることです。これにより、修正の開発と配布のペースが向上すると思います。

私が興味を持っている関数は静的として宣言されていましたが、非静的関数でカーネルを再コンパイルし、EXPORT_SYMBOL によってエクスポートしました。これは、関数がカーネルの他のモジュール/部分にアクセスできるようになったことを意味します。「cat /proc/kallsyms」でこれを確認しました。

ここで、シンボル アドレスをイニシャルから動的に読み込まれた関数に書き換えることができるモジュールを読み込めるようにしたいと考えています。同様に、モジュールをアンロードすると、元のアドレスが復元されます。これは実行可能なアプローチですか?これをより適切に実装するにはどうすればよいか、皆さんからの提案はありますか?

ありがとう!

Linux カーネルのモジュールによる機能のオーバーライドと同じ

編集:
これが私の最終的なアプローチでした。
次の関数を考えると(オーバーライドしたかったのですが、エクスポートされていません):

static void internal_function(void) 
{
  // do something interesting
  return;
}

次のように変更します。

static void internal_function_original(void)
{
  // do something interesting
  return;
}

static void (*internal_function)(void) = &internal_function_original;
EXPORT_SYMBOL(internal_function);

これにより、期待される関数識別子が、元の実装を指す関数ポインタ (同様の方法で呼び出すことができます) として再定義されます。EXPORT_SYMBOL() は、アドレスをグローバルにアクセスできるようにするため、モジュール (または他のカーネルの場所) から変更できます。

これで、次の形式でカーネル モジュールを作成できます。

static void (*original_function_reference)(void);
extern void (*internal_function)(void);

static void new_function_implementation(void)
{
  // do something new and interesting
  // return
}

int init_module(void)
{
  original_function_reference = internal_function;
  internal_function           = &new_function_implementation;
  return 0;
}

void cleanup_module(void)
{
  internal_function = original_function_reference;
}

このモジュールは、元の実装を動的にロードされたバージョンに置き換えます。アンロードすると、元の参照 (および実装) が復元されます。私の特定のケースでは、TCP で RTT の新しい推定器を提供しました。モジュールを使用することで、カーネルを再コンパイルして再起動することなく、小さな調整を行ってテストを再開することができます。

4

4 に答える 4

7

それが機能するかどうかはわかりません-置き換えたい関数への内部呼び出しのシンボル解決は、モジュールがロードされるまでに既に行われていると思います。

代わりに、既存の関数の名前を変更してコードを変更し、関数の元の名前でグローバル関数ポインターを作成することができます。関数ポインターを内部関数のアドレスに初期化して、既存のコードが変更されずに機能するようにします。グローバル関数ポインターのシンボルをエクスポートすると、モジュールは、モジュールのロードおよびアンロード時に割り当てによって値を変更できます。

于 2009-07-28T23:22:51.233 に答える
4

私はかつて、カーネル関数の代わりに独自の関数を挿入するハイジャックモジュールの概念実証を行いました。たまたま、新しいカーネルテーシングアーキテクチャは非常によく似たシステムを使用しています。

カスタム関数を指すジャンプでコードの最初の数バイトを上書きすることにより、カーネルに独自の関数を挿入しました。実際の関数が呼び出されるとすぐに、代わりに私の関数にジャンプします。それが実行された後は、元の関数と呼ばれる作業になります。


#include <linux/module.h>
#include <linux/kernel.h>

#define CODESIZE 12

static unsigned char original_code[CODESIZE];
static unsigned char jump_code[CODESIZE] =
    "\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00" /* movq $0, %rax */
    "\xff\xe0"                                          /* jump *%rax */
        ;
/* FILL THIS IN YOURSELF */
int (*real_printk)( char * fmt, ... ) = (int (*)(char *,...) )0xffffffff805e5f6e;

int hijack_start(void);
void hijack_stop(void);
void intercept_init(void);
void intercept_start(void);
void intercept_stop(void);
int fake_printk(char *, ... );


int hijack_start()
{
    real_printk(KERN_INFO "I can haz hijack?\n" );
    intercept_init();
    intercept_start();

    return 0;
}

void hijack_stop()
{
    intercept_stop();
    return;
}

void intercept_init()
{
    *(long *)&jump_code[2] = (long)fake_printk;
    memcpy( original_code, real_printk, CODESIZE );

    return;
}

void intercept_start()
{
    memcpy( real_printk, jump_code, CODESIZE );
}

void intercept_stop()
{
    memcpy( real_printk, original_code, CODESIZE );
}

int fake_printk( char *fmt, ... )
{
    int ret;
    intercept_stop();
    ret = real_printk(KERN_INFO "Someone called printk\n");
    intercept_start();
    return ret;
}

module_init( hijack_start );
module_exit( hijack_stop );

警告します。この種のことを実験するときは、カーネルパニックやその他の悲惨なイベントに注意してください。仮想化環境でこれを行うことをお勧めします。これは私が少し前に書いた概念実証コードですが、それでも機能するかどうかはわかりません。

これは本当に簡単な原則ですが、非常に効果的です。もちろん、実際の解決策では、ロックを使用して、関数を上書きしている間、誰も関数を呼び出さないようにします。

楽しむ!

于 2009-08-07T00:15:47.220 に答える
3

kspliceを試してみることができます-静的でなくてもかまいません。

于 2009-07-29T01:55:36.853 に答える
2

あなたが望むのはKprobeだと思います。

caf が言及した別の方法は、元のルーチンにフックを追加し、モジュールでフックを登録/登録解除することです。

于 2009-07-29T00:58:46.123 に答える