7

Linuxカーネル(正確にはカーネルモジュール/ドライバー)の変数(またはメモリアドレス)をどうにかして「監視」したいと思います。何が変更されたのかを調べます。基本的には、変数が変更されたときにスタック トレースを出力します。

たとえば、この回答testjiffy-hr.cの最後に記載されているカーネル モジュールでは、変数が変更されるたびにスタック トレースを出力したいと考えています。うまくいけば、スタック トレースには、実際にその変数を変更する関数である への言及が含まれます。runcounttestjiffy_timer_function

さて、kgdbたとえば仮想マシンで実行されているデバッグ Linux カーネルに接続し、そのようにブレークポイント (できればウォッチポイントも) を設定するために使用できることはわかっていますが、問題は実際に ALSA ドライバーをデバッグしたいことです。特に再生dma_areaバッファ(予期しないデータを取得している場所)-タイミングに非常に敏感です。デバッグカーネル自体を実行すると、タイミングが台無しになります(仮想マシンで実行することは言うまでもありません)。

ここでのさらに大きな問題は、再生dma_areaポインタが再生操作中 (つまり、_start_stopハンドラの間) にのみ存在することです。そのため、コールバックdma_areaごとにアドレスを記録し_start、何らかの形で「スケジュール」する必要があります。視聴中」と表示されます。

だから私は、ドライバーコードで直接このようなことをする方法があることを望んでいました - のように、この_startコールバックにポインターを記録するコードを追加しdma_area、それをコマンドの引数として使用して、「ウォッチ」を開始します変化する; 対応するコールバック関数から出力されたスタック トレースを使用します。(これもタイミングに影響することは承知していますが、「ライブ」ドライバーの操作に影響を与えすぎないように「軽い」ことを望んでいました)。

私の質問は次のとおりです。Linux カーネルでデバッグするためのそのような手法は存在しますか?

そうでない場合: 特定のメモリアドレスの変更に反応するハードウェア (またはソフトウェア) 割り込みを設定することは可能ですか? 次に、スタック トレースを出力できるような割り込みハンドラを設定できますか? (ただし、IRQ ハンドラーが実行されるとコンテキスト全体が変化すると思うので、そこでスタック トレースを取得するのは間違っているのではないでしょうか)?

そうでない場合:カーネル内の特定のメモリ位置に格納されている値を変更したプロセスのスタック トレースを出力できる他の手法はありますか?

4

2 に答える 2

14

@CosminRatiuEugeneからの返信に感謝します。それらのおかげで、私は見つけました:

...ここに投稿している例、testhrarr.cカーネルモジュール/ドライバー、およびMakefile(以下)を開発できました。これは、ハードウェア ウォッチポイントのトレースが 2 つの方法で実現できることを示していますperf。または、ハードウェア ブレークポイント コードをドライバーに追加します (この例では、HWDEBUG_STACKdefine 変数によってエンベロープされています)。

基本的runcountに、カーネル モジュールでグローバル変数として定義されている限り、(変数のように) int などの標準のアトミック変数タイプの内容をデバッグするのは簡単です。そのため、カーネル シンボルとしてグローバルに表示されることになります。そのため、以下のコードでtesthrarr_は変数に as プレフィックスを追加します (名前の競合を避けるため)。ただし、逆参照が必要なため、配列の内容をデバッグするのは少し難しいかもしれません。この記事では、testhrarr_arr配列の最初のバイトのデバッグについて説明します。それはで行われました:

$ echo `cat /etc/lsb-release` 
DISTRIB_ID=Ubuntu DISTRIB_RELEASE=11.04 DISTRIB_CODENAME=natty DISTRIB_DESCRIPTION="Ubuntu 11.04"
$ uname -a
Linux mypc 2.6.38-16-generic #67-Ubuntu SMP Thu Sep 6 18:00:43 UTC 2012 i686 i686 i386 GNU/Linux
$ cat /proc/cpuinfo | grep "model name"
model name  : Intel(R) Atom(TM) CPU N450   @ 1.66GHz
model name  : Intel(R) Atom(TM) CPU N450   @ 1.66GHz

モジュールは基本的に、testhrarrモジュールの初期化時に小さな配列にメモリを割り当て、タイマー関数を設定し、/proc/testhrarr_proc(新しいproc_createインターフェイスを使用して) ファイルを公開します。/proc/testhrarr_proc次に、ファイルからの読み取りを試みると(たとえば、 を使用してcat)、タイマー関数がトリガーされ、testhrarr_arr配列の値が変更され、メッセージが にダンプされ/var/log/syslogます。testhrarr_arr[0]操作中に 3 回変更されると予想されます。に 1 回、testhrarr_startupに 2 回testhrarr_timer_function(ラッピングのため)。

使用してperf

でモジュールをビルドしmakeたら、次のようにロードできます。

sudo insmod ./testhrarr.ko

その時点で、/var/log/syslog以下が含まれます。

kernel: [40277.199913] Init testhrarr: 0 ; HZ: 250 ; 1/HZ (ms): 4 ; hrres: 0.000000001
kernel: [40277.199930]  Addresses: _runcount 0xf84be22c ; _arr 0xf84be2a0 ; _arr[0] 0xed182a80 (0xed182a80) ; _timer_function 0xf84bc1c3 ; my_hrtimer 0xf84be260; my_hrt.f 0xf84be27c
kernel: [40277.220329] HW Breakpoint for testhrarr_arr write installed (0xf84be2a0)

testhrarr_arrハードウェア ウォッチポイントのシンボルとして渡すだけ0xf84be2a0では、配列の最初の要素のアドレス ( ) ではなく、その変数のアドレス ( ) がスキャンされることに注意してください0xed182a80。このため、ハードウェア ブレークポイントはトリガーされません。そのため、動作はハードウェア ブレークポイント コードがまったく存在しないかのようになります (未定義にすることで実現できますHWDEBUG_STACK)。

したがって、カーネル モジュール コードを介してハードウェア ブレークポイントを設定しなくても、perfメモリ アドレスの変更を監視するために使用できperfます。実行する必要があるプロセス: ここで を実行するため、カーネル モジュール タイマーをトリガーする を実行し、続いてタイマーを完了させる を実行できます。パラメータも必要です。そうしないと、いくつかのイベントが見逃される可能性があります。testhrarr_arr0xed182a80bashcat /proc/testhrarr_procsleep 0.5-a

$ sudo perf record -a --call-graph --event=mem:0xed182a80:w bash -c 'cat /proc/testhrarr_proc ; sleep 0.5'
testhrarr proc: startup
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.485 MB perf.data (~21172 samples) ]

この時点で、/var/log/syslog次のようなものも含まれます。


[40822.114964] testhrarr_timer_function: testhrarr_runco​​unt 0
[40822.114980] testhrarr jiffies 10130528 ; 戻ります: 1 ; ktnsec: 40822114975062
[40822.118956] testhrarr_timer_function: testhrarr_runco​​unt 1
[40822.118977] testhrarr jiffies 10130529 ; 戻ります: 1 ; ktnsec: 40822118973195
[40822.122940] testhrarr_timer_function: testhrarr_runco​​unt 2
[40822.122956] testhrarr jiffies 10130530 ; 戻ります: 1 ; ktnsec: 40822122951143
[40822.126962] testhrarr_timer_function: testhrarr_runco​​unt 3
[40822.126978] testhrarr jiffies 10130531 ; 戻ります: 1 ; ktnsec: 40822126973583
[40822.130941] testhrarr_timer_function: testhrarr_runco​​unt 4
[40822.130961] testhrarr jiffies 10130532 ; 戻ります: 1 ; ktnsec: 40822130955167
[40822.134940] testhrarr_timer_function: testhrarr_runco​​unt 5
[40822.134962] testhrarr jiffies 10130533 ; 戻ります: 1 ; ktnsec: 40822134958888
[40822.138936] testhrarr_timer_function: testhrarr_runco​​unt 6
[40822.138958] testhrarr jiffies 10130534 ; 戻ります: 1 ; ktnsec: 40822138955693
[40822.142940] testhrarr_timer_function: testhrarr_runco​​unt 7
[40822.142962] testhrarr jiffies 10130535 ; 戻ります: 1 ; ktnsec: 40822142959345
[40822.146936] testhrarr_timer_function: testhrarr_runco​​unt 8
[40822.146957] testhrarr jiffies 10130536 ; 戻ります: 1 ; ktnsec: 40822146954479
[40822.150949] testhrarr_timer_function: testhrarr_runco​​unt 9
[40822.150970] testhrarr jiffies 10130537 ; 戻ります: 1 ; ktnsec: 40822150963438
[40822.154974] testhrarr_timer_function: testhrarr_runco​​unt 10
[40822.154988] テストラー [ 5, 7, 9, 11, 13, ]

perf(と呼ばれるファイル)のキャプチャを読み取るには、perf.data次を使用できます。

$ sudo perf report --call-graph flat --stdio
ビルド ID 5031df4d8668bcc45a7bdb4023909c6f8e2d3d34 の kallsyms または vmlinux が見つかりませんでした
[testhrarr] ビルド ID 5031df4d8668bcc45a7bdb4023909c6f8e2d3d34 が見つからず、シンボルなしで続行
/bin/cat を開けませんでした。シンボルなしで続行します
/usr/lib/libpixman-1.so.0.20.2 を開けませんでした。シンボルなしで続行します
/usr/lib/xorg/modules/drivers/intel_drv.so を開けませんでした。シンボルなしで続行します
/usr/bin/Xorg を開けませんでした。シンボルなしで続行します
# イベント: 5 不明
#
# オーバーヘッド コマンド共有オブジェクト シンボル
#…………………………………… ...............
#
    87.50% Xorg [testhrarr] [k] testhrarr_timer_function
            87.50%
                testhrarr_timer_function
                __run_hrtimer
                hrtimer_interrupt
                smp_apic_timer_interrupt
                apic_timer_interrupt
                0x30185d
                0x2ed701
                0x2ed8cc
                0x2edba0
                0x9d0386
                0x8126fc8
                0x81217a1
                0x811bdd3
                0x8070aa7
                0x806281c
                __libc_start_main
                0x8062411

     6.25% 猫 [testhrarr] [k] testhrarr_timer_function
             6.25%
                testhrarr_timer_function
                testhrarr_proc_show
                seq_read
                proc_reg_read
                vfs_read
                sys_read
                syscall_call
                0xaa2416
                0x8049f4d
                __libc_start_main
                0x8049081

     3.12% スワッパー [testhrarr] [k] testhrarr_timer_function
             3.12%
                testhrarr_timer_function
                __run_hrtimer
                hrtimer_interrupt
                smp_apic_timer_interrupt
                apic_timer_interrupt
                cpuidle_idle_call
                cpu_idle
                start_secondary

     3.12% 猫 [testhrarr] [k] 0x356   
             3.12%
                0xf84bc356
                0xf84bc3a7
                seq_read
                proc_reg_read
                vfs_read
                sys_read
                syscall_call
                0xaa2416
                0x8049f4d
                __libc_start_main
                0x8049081



#
# (より高いレベルの概要については、perf report --sort comm,dso を試してください)
#

したがって、デバッグをオンにしてカーネル モジュールをビルドしているため (-g内)、ライブ カーネルがデバッグ カーネルでなくても、このモジュールのシンボルを見つけることMakefileは問題になりません。そのため、ほとんどの場合、セッターとしてperf正しく解釈されますが、報告はされません(ただし、呼び出し元は報告されます)。解決できなかったおよびへの参照もあります。ただし、モジュールは次の場所にロードされることに注意してください。testhrarr_timer_functiontesthrarr_startuptesthrarr_proc_show0xf84bc3a70xf84bc3560xf84bc000

$ sudo cat /proc/modules | grep testhr
testhrarr 13433 0 - Live 0xf84bc000

...[k] 0x356...そして、そのエントリも;で始まります。objdumpカーネル モジュールのを調べると、次のようになります。

$ objdump -S testhrarr.ko | 少ない
...
00000323 :

static void testhrarr_startup(void)
{
...
    testhrarr_arr[0] = 0; //最初の要素だけ
 34b: a1 80 00 00 00 mov 0x80,%eax
 350: c7 00 00 00 00 00 movl $0x0,(%eax)
    hrtimer_start(&my_hrtimer, ktime_period_ns, HRTIMER_MODE_REL);
 356: c7 04 24 01 00 00 00 movl $0x1,(%esp) **********
 35d: 8b 15 1c 00 00 00 mov 0x1c、%edx
...
00000375 :


static int testhrarr_proc_show(struct seq_file *m, void *v) {
...
    seq_printf(m, "testhrarr proc: 起動\n");
 38f: c7 44 24 04 79 00 00 movl $0x79,0x4(%esp)
 396:00
 397: 8b 45 fc mov -0x4(%ebp),%eax
 39a: 89 04 24 mov %eax,(%esp)
 39d: e8 fc ff ff ff コール 39e
    testhrarr_startup();
 3a2: e8 7c ff ff ff コール 323
 3a7: eb 1c jmp 3c5 **********
  } そうしないと {
    seq_printf(m, "testhrarr proc: (実行中です, %d)\n", testhrarr_runco​​unt);
 3a9: a1 0c 00 00 00 mov 0xc,%eax
...

...どうやら;0xf84bc356を指しているようです。hrtimer_startそして0xf84bc3a7->3a7その呼び出しtesthrarr_proc_show関数を参照します。ありがたいことに、これは理にかなっています。(私は異なるバージョンのドライバーで経験したことに注意して_startくださいtimer_function.

ただし、 の1 つの問題はperf、発生しているこれらの関数の統計的な「オーバーヘッド」が得られることです (それが何を指しているのかわかりません - おそらく関数の開始と終了の間に費やされた時間ですか?) - しかし、私が本当に欲しいのはシーケンシャルなスタック トレースのログ。perfそのために設定できる かどうかはわかりませんが、ハードウェア ブレークポイント用のカーネル モジュール コードで確実に設定できます。

カーネル モジュールのハードウェア ブレークポイントを使用する

にあるコードはHWDEBUG_STACK、HW ブレークポイントのセットアップと処理を実装します。前述のように、シンボルのデフォルト設定ksym_name(指定されていない場合) はtesthrarr_arrで、ハードウェア ブレークポイントはまったくトリガーされません。このksym_nameパラメーターは、実行中にコマンド ラインで指定できますinsmod。ここで、次のことに注意してください。

$ sudo rmmod testhrarr    # remove module if still loaded
$ sudo insmod ./testhrarr.ko ksym=testhrarr_arr[0]

...結果はHW Breakpoint for testhrarr_arr[0] write installed (0x (null))in /var/log/syslog; - これは、配列へのアクセスにブラケット表記のシンボルを使用できないことを意味します。ありがたいことに、ここでのヌル ポインターは、HW ブレークポイントが再び起動しないことを意味します。OSを完全にクラッシュさせるわけではありません:)

testhrarr_arrただし、配列の最初の要素を参照するために作成されたグローバル変数があり、testhrarr_arr_firstこのグローバル変数がコード内でどのように特別に処理され、正しいアドレスを取得するために逆参照する必要があるかに注意してください。そこで、次のようにします。

$ sudo rmmod testhrarr    # remove module if still loaded
$ sudo insmod ./testhrarr.ko ksym=testhrarr_arr_first

...そしてsyslogは次のことを通知します:

kernel: [43910.509726] Init testhrarr: 0 ; HZ: 250 ; 1/HZ (ms): 4 ; hrres: 0.000000001
kernel: [43910.509765]  Addresses: _runcount 0xf84be22c ; _arr 0xf84be2a0 ; _arr[0] 0xedf6c5c0 (0xedf6c5c0) ; _timer_function 0xf84bc1c3 ; my_hrtimer 0xf84be260; my_hrt.f 0xf84be27c
kernel: [43910.538535] HW Breakpoint for testhrarr_arr_first write installed (0xedf6c5c0)

...そして、HW ブレークポイントが に設定されていることがわかります。0xedf6c5c0これは のアドレスですtesthrarr_arr[0]/procファイルを介してドライバーをトリガーすると、次のようになります。

$ cat /proc/testhrarr_proc 
testhrarr proc: startup

... で取得しsyslogます:

カーネル: [44069.735695] testhrarr_arr_first の値が変更されました
[44069.735711] Pid: 29320, comm: cat Not tainted 2.6.38-16-generic #67-Ubuntu
[44069.735719] コール トレース:
[44069.735737] [] ? sample_hbp_handler+0x2d/0x3b [testhrarr]
[44069.735755] [] ? __perf_event_overflow+0x90/0x240
[44069.735768] [] ? proc_alloc_inode+0x23/0x90
[44069.735778] [] ? proc_alloc_inode+0x23/0x90
[44069.735790] [] ? perf_swevent_event+0x136/0x140
[44069.735801] [] ? perf_bp_event+0x70/0x80
[44069.735812] [] ? prep_new_page+0x110/0x1a0
[44069.735824] [] ? get_page_from_freelist+0x12e/0x320
[44069.735836] [] ? seq_open+0x3d/0xa0
[44069.735848] [] ? hw_breakpoint_handler.clone.0+0x102/0x130
[44069.735861] [] ? hw_breakpoint_exceptions_notify+0x22/0x30
[44069.735872] [] ? notifier_call_chain+0x45/0x60
[44069.735883] [] ? atomic_notifier_call_chain+0x22/0x30
[44069.735894] [] ? notify_die+0x2d/0x30
[44069.735904] [] ? do_debug+0x88/0x180
[44069.735915] [] ? debug_stack_correct+0x30/0x38
[44069.735928] [] ? testhrarr_startup+0x33/0x52 [testhrarr]
[44069.735940] [] ? testhrarr_proc_show+0x32/0x57 [testhrarr]
[44069.735952] [] ? seq_read+0x145/0x390
[44069.735963] [] ? seq_read+0x0/0x390
[44069.735973] [] ? proc_reg_read+0x64/0xa0
[44069.735985] [] ? vfs_read+0x9f/0x160
[44069.735995] [] ? proc_reg_read+0x0/0xa0
[44069.736003] [] ? sys_read+0x42/0x70
[44069.736013] [] ? syscall_call+0x7/0xb
[44069.736019] sample_hbp_handler からスタックをダンプ
[44069.740132] testhrarr_timer_function: testhrarr_runco​​unt 0
[44069.740146] testhrarr jiffies 10942435 ; 戻ります: 1 ; ktnsec: 44069740142485
[44069.740159] testhrarr_arr_first の値が変更されました
[44069.740169] Pid: 4302、comm: gnome-terminal 汚染されていません 2.6.38-16-generic #67-Ubuntu
[44069.740176] コール トレース:
[44069.740195] [] ? sample_hbp_handler+0x2d/0x3b [testhrarr]
[44069.740213] [] ? __perf_event_overflow+0x90/0x240
[44069.740227] [] ? perf_swevent_event+0x136/0x140
[44069.740239] [] ? perf_bp_event+0x70/0x80
[44069.740253] [] ? sched_clock_local+0xd3/0x1c0
[44069.740267] [] ? format_decode+0x323/0x380
[44069.740280] [] ? hw_breakpoint_handler.clone.0+0x102/0x130
[44069.740292] [] ? hw_breakpoint_exceptions_notify+0x22/0x30
[44069.740302] [] ? notifier_call_chain+0x45/0x60
[44069.740313] [] ? atomic_notifier_call_chain+0x22/0x30
[44069.740324] [] ? notify_die+0x2d/0x30
[44069.740335] [] ? do_debug+0x88/0x180
[44069.740345] [] ? debug_stack_correct+0x30/0x38
[44069.740364] [] ? init_intel_cacheinfo+0​​x103/0x394
[44069.740379] [] ? testhrarr_timer_function+0xed/0x160 [testhrarr]
[44069.740391] [] ? __run_hrtimer+0x6f/0x190
[44069.740404] [] ? testhrarr_timer_function+0x0/0x160 [testhrarr]
[44069.740416] [] ? hrtimer_interrupt+0x108/0x240
[44069.740430] [] ? smp_apic_timer_interrupt+0x56/0x8a
[44069.740441] [] ? apic_timer_interrupt+0x31/0x38
[44069.740453] [] ? _raw_spin_unlock_irqrestore+0x15/0x20
[44069.740465] [] ? try_to_del_timer_sync+0x67/0xb0
[44069.740476] [] ? del_timer_sync+0x29/0x50
[44069.740486] [] ? flush_delayed_work+0x13/0x40
[44069.740500] [] ? tty_flush_to_ldisc+0x12/0x20
[44069.740510] [] ? n_tty_poll+0x4f/0x190
[44069.740523] [] ? tty_poll+0x6d/0x90
[44069.740531] [] ? n_tty_poll+0x0/0x190
[44069.740542] [] ? do_poll.clone.3+0xd0/0x210
[44069.740553] [] ? do_sys_poll+0x134/0x1e0
[44069.740563] [] ? __pollwait+0x0/0xd0
[44069.740572] [] ? pollwake+0x0/0x60
...
[44069.740742] [] ? pollwake+0x0/0x60
[44069.740757] [] ? rw_verify_area+0x6c/0x130
[44069.740770] [] ? ktime_get_ts+0xf8/0x120
[44069.740781] [] ? poll_select_set_timeout+0x64/0x70
[44069.740793] [] ? sys_poll+0x5a/0xd0
[44069.740804] [] ? syscall_call+0x7/0xb
[44069.740815] [] ? init_intel_cacheinfo+0​​x23/0x394
[44069.740822] sample_hbp_handler からスタックをダンプ
[44069.744130] testhrarr_timer_function: testhrarr_runco​​unt 1
[44069.744143] testhrarr jiffies 10942436 ; 戻ります: 1 ; ktnsec: 44069744140055
[44069.748132] testhrarr_timer_function: testhrarr_runco​​unt 2
[44069.748145] testhrarr jiffies 10942437 ; 戻ります: 1 ; ktnsec: 44069748141271
[44069.752131] testhrarr_timer_function: testhrarr_runco​​unt 3
[44069.752145] testhrarr jiffies 10942438 ; 戻ります: 1 ; ktnsec: 44069752141164
[44069.756131] testhrarr_timer_function: testhrarr_runco​​unt 4
[44069.756141] testhrarr jiffies 10942439 ; 戻ります: 1 ; ktnsec: 44069756138318
[44069.760130] testhrarr_timer_function: testhrarr_runco​​unt 5
[44069.760141] testhrarr jiffies 10942440 ; 戻ります: 1 ; ktnsec: 44069760138469
[44069.760154] testhrarr_arr_first の値が変更されました
[44069.760164] Pid: 4302、comm: gnome-terminal 汚染されていません 2.6.38-16-generic #67-Ubuntu
[44069.760170] コール トレース:
[44069.760187] [] ? sample_hbp_handler+0x2d/0x3b [testhrarr]
[44069.760202] [] ? __perf_event_overflow+0x90/0x240
[44069.760213] [] ? perf_swevent_event+0x136/0x140
[44069.760224] [] ? perf_bp_event+0x70/0x80
[44069.760235] [] ? sched_clock_local+0xd3/0x1c0
[44069.760247] [] ? format_decode+0x323/0x380
[44069.760258] [] ? hw_breakpoint_handler.clone.0+0x102/0x130
[44069.760269] [] ? hw_breakpoint_exceptions_notify+0x22/0x30
[44069.760279] [] ? notifier_call_chain+0x45/0x60
[44069.760289] [] ? atomic_notifier_call_chain+0x22/0x30
[44069.760299] [] ? notify_die+0x2d/0x30
[44069.760308] [] ? do_debug+0x88/0x180
[44069.760318] [] ? debug_stack_correct+0x30/0x38
[44069.760334] [] ? init_intel_cacheinfo+0​​x103/0x394
[44069.760345] [] ? testhrarr_timer_function+0xed/0x160 [testhrarr]
[44069.760356] [] ? __run_hrtimer+0x6f/0x190
[44069.760366] [] ? send_to_group.clone.1+0xf8/0x150
[44069.760376] [] ? testhrarr_timer_function+0x0/0x160 [testhrarr]
[44069.760387] [] ? hrtimer_interrupt+0x108/0x240
[44069.760396] [] ? fsnotify+0x1a5/0x290
[44069.760407] [] ? smp_apic_timer_interrupt+0x56/0x8a
[44069.760416] [] ? apic_timer_interrupt+0x31/0x38
[44069.760428] [] ? mem_cgroup_resize_limit+0x108/0x1c0
[44069.760437] [] ? fput+0x0/0x30
[44069.760446] [] ? sys_write+0x67/0x70
[44069.760455] [] ? syscall_call+0x7/0xb
[44069.760464] [] ? init_intel_cacheinfo+0​​x23/0x394
[44069.760470] sample_hbp_handler からスタックをダンプ
[44069.764134] testhrarr_timer_function: testhrarr_runco​​unt 6
[44069.764147] testhrarr jiffies 10942441 ; 戻ります: 1 ; ktnsec: 44069764144141
[44069.768133] testhrarr_timer_function: testhrarr_runco​​unt 7
[44069.768146] testhrarr jiffies 10942442 ; 戻ります: 1 ; ktnsec: 44069768142976
[44069.772134] testhrarr_timer_function: testhrarr_runco​​unt 8
[44069.772148] testhrarr jiffies 10942443 ; 戻ります: 1 ; ktnsec: 44069772144121
[44069.776132] testhrarr_timer_function: testhrarr_runco​​unt 9
[44069.776145] testhrarr jiffies 10942444 ; 戻ります: 1 ; ktnsec: 44069776141971
[44069.780133] testhrarr_timer_function: testhrarr_runco​​unt 10
[44069.780141] テストラー [ 5, 7, 9, 11, 13, ]

... スタック トレースを正確に3 回取得testhrarr_startuptesthrarr_timer_functionます。runcount==0runcount==5

さて、これが誰かに役立つことを願っています、
乾杯!


Makefile

CONFIG_MODULE_FORCE_UNLOAD=y

# debug build:
# "CFLAGS was changed ... Fix it to use EXTRA_CFLAGS."
override EXTRA_CFLAGS+=-g -O0 

obj-m += testhrarr.o
#testhrarr-objs  := testhrarr.o

all:
    @echo EXTRA_CFLAGS = $(EXTRA_CFLAGS)
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

testhrarr.c

/*
 * [http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN189 The Linux Kernel Module Programming Guide]
 * https://stackoverflow.com/questions/16920238/reliability-of-linux-kernel-add-timer-at-resolution-of-one-jiffy/17055867#17055867
 * https://stackoverflow.com/questions/8516021/proc-create-example-for-kernel-module/18924359#18924359
 * http://lxr.free-electrons.com/source/samples/hw_breakpoint/data_breakpoint.c
 */


#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */
#include <linux/init.h>     /* Needed for the macros */
#include <linux/jiffies.h>
#include <linux/time.h>
#include <linux/proc_fs.h>  /* /proc entry */
#include <linux/seq_file.h> /* /proc entry */
#define ARRSIZE 5
#define MAXRUNS 2*ARRSIZE

#include <linux/hrtimer.h>

#define HWDEBUG_STACK 1

#if (HWDEBUG_STACK == 1)
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>

struct perf_event * __percpu *sample_hbp;
static char ksym_name[KSYM_NAME_LEN] = "testhrarr_arr";
module_param_string(ksym, ksym_name, KSYM_NAME_LEN, S_IRUGO);
MODULE_PARM_DESC(ksym, "Kernel symbol to monitor; this module will report any"
      " write operations on the kernel symbol");
#endif

static volatile int testhrarr_runcount = 0;
static volatile int testhrarr_isRunning = 0;

static unsigned long period_ms;
static unsigned long period_ns;
static ktime_t ktime_period_ns;
static struct hrtimer my_hrtimer;

static int* testhrarr_arr;
static int* testhrarr_arr_first;

static enum hrtimer_restart testhrarr_timer_function(struct hrtimer *timer)
{
  unsigned long tjnow;
  ktime_t kt_now;
  int ret_overrun;

  printk(KERN_INFO
    " %s: testhrarr_runcount %d \n",
    __func__, testhrarr_runcount);

  if (testhrarr_runcount < MAXRUNS) {
    tjnow = jiffies;
    kt_now = hrtimer_cb_get_time(&my_hrtimer);
    ret_overrun = hrtimer_forward(&my_hrtimer, kt_now, ktime_period_ns);
    printk(KERN_INFO
      " testhrarr jiffies %lu ; ret: %d ; ktnsec: %lld\n",
      tjnow, ret_overrun, ktime_to_ns(kt_now));
    testhrarr_arr[(testhrarr_runcount % ARRSIZE)] += testhrarr_runcount;
    testhrarr_runcount++;
    return HRTIMER_RESTART;
  }
  else {
    int i;
    testhrarr_isRunning = 0;
    // do not use KERN_DEBUG etc, if printk buffering until newline is desired!
    printk("testhrarr_arr [ ");
    for(i=0; i<ARRSIZE; i++) {
      printk("%d, ", testhrarr_arr[i]);
    }
    printk("]\n");
    return HRTIMER_NORESTART;
  }
}

static void testhrarr_startup(void)
{
  if (testhrarr_isRunning == 0) {
    testhrarr_isRunning = 1;
    testhrarr_runcount = 0;
    testhrarr_arr[0] = 0; //just the first element
    hrtimer_start(&my_hrtimer, ktime_period_ns, HRTIMER_MODE_REL);
  }
}


static int testhrarr_proc_show(struct seq_file *m, void *v) {
  if (testhrarr_isRunning == 0) {
    seq_printf(m, "testhrarr proc: startup\n");
    testhrarr_startup();
  } else {
    seq_printf(m, "testhrarr proc: (is running, %d)\n", testhrarr_runcount);
  }
  return 0;
}

static int testhrarr_proc_open(struct inode *inode, struct  file *file) {
  return single_open(file, testhrarr_proc_show, NULL);
}

static const struct file_operations testhrarr_proc_fops = {
  .owner = THIS_MODULE,
  .open = testhrarr_proc_open,
  .read = seq_read,
  .llseek = seq_lseek,
  .release = single_release,
};


#if (HWDEBUG_STACK == 1)
static void sample_hbp_handler(struct perf_event *bp,
             struct perf_sample_data *data,
             struct pt_regs *regs)
{
  printk(KERN_INFO "%s value is changed\n", ksym_name);
  dump_stack();
  printk(KERN_INFO "Dump stack from sample_hbp_handler\n");
}
#endif

static int __init testhrarr_init(void)
{
  struct timespec tp_hr_res;
  #if (HWDEBUG_STACK == 1)
  struct perf_event_attr attr;
  #endif

  period_ms = 1000/HZ;
  hrtimer_get_res(CLOCK_MONOTONIC, &tp_hr_res);
  printk(KERN_INFO
    "Init testhrarr: %d ; HZ: %d ; 1/HZ (ms): %ld ; hrres: %lld.%.9ld\n",
               testhrarr_runcount,      HZ,        period_ms, (long long)tp_hr_res.tv_sec, tp_hr_res.tv_nsec );

  testhrarr_arr = (int*)kcalloc(ARRSIZE, sizeof(int), GFP_ATOMIC);
  testhrarr_arr_first = &testhrarr_arr[0];

  hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
  my_hrtimer.function = &testhrarr_timer_function;
  period_ns = period_ms*( (unsigned long)1E6L );
  ktime_period_ns = ktime_set(0,period_ns);

  printk(KERN_INFO
    " Addresses: _runcount 0x%p ; _arr 0x%p ; _arr[0] 0x%p (0x%p) ; _timer_function 0x%p ; my_hrtimer 0x%p; my_hrt.f 0x%p\n",
    &testhrarr_runcount, &testhrarr_arr, &(testhrarr_arr[0]), testhrarr_arr_first, &testhrarr_timer_function, &my_hrtimer, &my_hrtimer.function);


  proc_create("testhrarr_proc", 0, NULL, &testhrarr_proc_fops);


  #if (HWDEBUG_STACK == 1)
  hw_breakpoint_init(&attr);
  if (strcmp(ksym_name, "testhrarr_arr_first") == 0) {
    // just for testhrarr_arr_first - interpret the found symbol address
    // as int*, and dereference it to get the "real" address it points to
    attr.bp_addr = *((int*)kallsyms_lookup_name(ksym_name));
  } else {
    // the usual - address is kallsyms_lookup_name result
    attr.bp_addr = kallsyms_lookup_name(ksym_name);
  }
  attr.bp_len = HW_BREAKPOINT_LEN_1;
  attr.bp_type = HW_BREAKPOINT_W ; //| HW_BREAKPOINT_R;

  sample_hbp = register_wide_hw_breakpoint(&attr, (perf_overflow_handler_t)sample_hbp_handler);
  if (IS_ERR((void __force *)sample_hbp)) {
    int ret = PTR_ERR((void __force *)sample_hbp);
    printk(KERN_INFO "Breakpoint registration failed\n");
    return ret;
  }

  // explicit cast needed to show 64-bit bp_addr as 32-bit address
  // https://stackoverflow.com/questions/11796909/how-to-resolve-cast-to-pointer-from-integer-of-different-size-warning-in-c-co/11797103#11797103
  printk(KERN_INFO "HW Breakpoint for %s write installed (0x%p)\n", ksym_name, (void*)(uintptr_t)attr.bp_addr);
  #endif

  return 0;
}

static void __exit testhrarr_exit(void)
{
  int ret_cancel = 0;
  kfree(testhrarr_arr);
  while( hrtimer_callback_running(&my_hrtimer) ) {
    ret_cancel++;
  }
  if (ret_cancel != 0) {
    printk(KERN_INFO " testhrarr Waited for hrtimer callback to finish (%d)\n", ret_cancel);
  }
  if (hrtimer_active(&my_hrtimer) != 0) {
    ret_cancel = hrtimer_cancel(&my_hrtimer);
    printk(KERN_INFO " testhrarr active hrtimer cancelled: %d (%d)\n", ret_cancel, testhrarr_runcount);
  }
  if (hrtimer_is_queued(&my_hrtimer) != 0) {
    ret_cancel = hrtimer_cancel(&my_hrtimer);
    printk(KERN_INFO " testhrarr queued hrtimer cancelled: %d (%d)\n", ret_cancel, testhrarr_runcount);
  }
  remove_proc_entry("testhrarr_proc", NULL);
  #if (HWDEBUG_STACK == 1)
  unregister_wide_hw_breakpoint(sample_hbp);
  printk(KERN_INFO "HW Breakpoint for %s write uninstalled\n", ksym_name);
  #endif
  printk(KERN_INFO "Exit testhrarr\n");
}

module_init(testhrarr_init);
module_exit(testhrarr_exit);

MODULE_LICENSE("GPL");
于 2013-11-03T16:35:10.170 に答える
1

これにはハードウェア サポートが必要です。CPU は、特定のメモリ アドレスが書き込まれるタイミングを感知し、何らかのコード (割り込みまたは例外ハンドラー) を呼び出す必要があります。私の経験では、これは PowerPC プラットフォームで見られましたが、x86 では見られませんでした。これは、ハードウェア ウォッチポイントと呼ばれます。

理論的には、エミュレーターで実行すると、この動作をシミュレートできますが、現在存在するエミュレーターにはまったく慣れていません。

編集:もう少し掘り下げましたが、Linuxには汎用のハードウェアブレークポイントインターフェイスがあり、x86にはそのようなレジスタがあるようです。DR7といいます。「include/linux/hw_breakpoint.h」の関数を見てください。ptrace や perf がこれらのインターフェイスを使用しているようです。頑張ってデバッグしてください!

于 2013-11-01T20:22:36.337 に答える