初めてLinuxカーネルを使ったTracepointの体験


私は耻ずかしいとは思わない.少しもしない.
Linuxカーネルに関する仕事を何年もしていたのに、先週初めてtracepointを使ったということです.
これはおかしくありません.私にはできないものがたくさんあります.例えば、私はずっと強調しています.私はプログラミングができません.gitも使いません.
本題に戻りますが、もし何年もtracepointを使ったことがなければ、代わりに何を使いますか?
カーネルをデバッグしたり、カーネルモジュールをデバッグしたりする場合、最も一般的な方法は、コードにprintkを追加することです(ユーザー・ステート・プログラムであればprintfを追加します).理由を教えてあげます.
  • printk/printfは、他の依存パッケージをインストールする必要はありません.
  • printk/printfは使い勝手がよく、再利用のために過度に設計されたコードを書く必要はありません.
  • printk/printfはプログラミングできない人と友好的です.

  • だから、printkが好きです.私はgdp/crash toolが好きではありません.kprobe/systemtapも好きではありません.bpftraceも好きではありません.もちろん、私はこれらのものをよく知っています.ただ、複雑すぎて、記憶する文法が多すぎて、ホームレスのように、負担を持つのが好きではありません.
    こんなにたくさん引っ張ったのはtracepointと毛の関係がある.
    関係あります.printkを追加する必要がある場所にtracepointを追加するのは仕事量が少なく、効果が高いことに気づきました.tracepointはHOOKを追加したように、printk印刷にこだわるだけでなく、外で何をするかをカスタマイズすることができます.
    tracepointを使用して、printkの効果を達成したいだけなら、/sys/kernel/debug/tracing/traceで印刷する情報を見ることができます.他に何かしたいならeBPFを使うこともできます.
    『史記』のような太史公曰形而学校の部分は本文の最後に、今まず実際のものをあげます.
    カーネルコードにtracepointを追加するのは簡単ですが、カーネルを再コンパイルする必要があります.モジュールにtracepointを追加するHowtoをあげましょう.
    まずtracepoitを追加するカーネルモジュールコードを示します.
    #include 
    #include 
    #include 
    
    #define IPPROTO_MYPROTO  123
    int myproto_rcv(struct sk_buff *skb)
    {
         
        struct udphdr *uh;
        struct iphdr *iph;
    
        iph = ip_hdr(skb);
        uh = udp_hdr(skb);
    
        printk("proto 123
    "
    ); kfree_skb(skb); return 0; } static const struct net_protocol myproto_protocol = { .handler = myproto_rcv, .no_policy = 1, .netns_ok = 1, }; int init_module(void) { int ret = 0; ret = inet_add_protocol(&myproto_protocol, IPPROTO_MYPROTO); if (ret) { printk("failed
    "
    ); return ret; } return 0; } void cleanup_module(void) { inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO); } int init_module(void); void cleanup_module(void); MODULE_LICENSE("GPL");

    現実的な例では、このコードはTCP、UDPと平レベルの4層プロトコルを登録しているが、実際にはUDPであり、プロトコル番号が123に変更されただけで、このようなプロトコルパッケージを受信するとprintkがポートを印刷すると予想されている.
    printkの代わりにtracepointを使いたいのですが、どうすればいいですか?次は方法です.次は新しいコードです.
    /*
     *   :
     * 1. CREATE_TRACE_POINTS   define
     * 2. CREATE_TRACE_POINTS         define
     */
    
    #define CREATE_TRACE_POINTS
    #include "test_tp.h"
    
    #include 
    #include 
    #include 
    
    #define IPPROTO_MYPROTO  123
    int myproto_rcv(struct sk_buff *skb)
    {
         
        struct udphdr *uh;
        struct iphdr *iph;
    
        iph = ip_hdr(skb);
        uh = udp_hdr(skb);
    
        //       tracepoint 
        trace_myprot_port(uh->dest, uh->source);
    
        kfree_skb(skb);
        return 0;
    }
    
    static const struct net_protocol myproto_protocol = {
         
        .handler = myproto_rcv,
        .no_policy = 1,
        .netns_ok = 1,
    };
    
    int init_module(void)
    {
         
        int ret = 0;
        ret = inet_add_protocol(&myproto_protocol, IPPROTO_MYPROTO);
        if (ret) {
         
            printk("failed
    "
    ); return ret; } return 0; } void cleanup_module(void) { inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO); } int init_module(void); void cleanup_module(void); MODULE_LICENSE("GPL");

    ヘッダファイルリファレンスを追加し、マクロ定義を追加しましたが、他には何も変わっていません.簡単ではありませんか.
    tracepointに関連するメタデータ定義はすべてヘッダファイルにあります.
    // test_tp.h
    #undef TRACE_SYSTEM
    #define TRACE_SYSTEM myprot
    
    #if !defined(_TEST_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
    #define _TEST_TRACE_H
    #include 
    
    // tracepoint        。         ,  debugfs           TP_printk     。
    //   eBPF   ,            ,     。
    TRACE_EVENT(myprot_port,
        TP_PROTO(unsigned short dest, unsigned short source),
        TP_ARGS(dest, source),
        TP_STRUCT__entry(
            __field(unsigned short, dest)
            __field(unsigned short, source)
        ),
    TP_fast_assign(
        __entry->dest = dest;
        __entry->source = source;
    ),
    
    TP_printk("dest:%d, source:%d", __entry->dest, __entry->source)
    );
    
    #endif
    /*
     *          
     *              ,                ,
     * TRACE_INCLUDE_PATH       ,                 。
     *
     *          ,Makefile     :
     * CFLAGS_myprot.o = -I$(src)
     *
     */
    
    #undef TRACE_INCLUDE_PATH
    #define TRACE_INCLUDE_PATH .
    #define TRACE_INCLUDE_FILE test_tp //           
    #include 
    

    次はMakefileです.2行目に注意するだけです.
    obj-m += test.o
    # CFLAGS_xx.o    xx         
    CFLAGS_test.o = -DDEBUG -I$(src)
    

    OK、モジュールをコンパイルし、ロードします.
    まずtracepointを開きます.
    echo 1 >/sys/kernel/debug/tracing/events/myprot/enable
    

    このときraw socketによって123のプロトコルのメッセージが送信され、debugfsで出力が表示されます.
    cat /sys/kernel/debug/tracing/trace
    # tracer: nop
    #
    # entries-in-buffer/entries-written: 1/1   #P:4
    #
    #                                _-----=> irqs-off
    #                               / _----=> need-resched
    #                              | / _---=> hardirq/softirq
    #                              || / _--=> preempt-depth
    #                              ||| /     delay
    #           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
    #              | |         |   ||||      |         |
         ksoftirqd/1-16      [001] ..s. 174074.362291: myprot_port: dest:36895, source:53764
    

    もちろん、これはprintkをシミュレートしただけで、printkを一つの場所に統一しただけで、大したことはありません.tracepointは、eBPFプログラムをマウントできることを意味します.試してみましょう
    bpftrace -l|grep tracepoint.*myprot
    tracepoint:myprot:myprot_port
    

    bpftraceで簡単にデバッグできます.
    #!/usr/bin/bpftrace
    
    tracepoint:myprot:myprot_port
    {
         
    	$dest = args->dest;
    	$source = args->source;
    	printf("bpftrace:dest:%d  source:%d
    "
    , ($source & 0xff) << 8 | ($source & 0xff00) >> 8, ($dest & 0xff) << 8 | ($dest & 0xff00) >> 8); }

    運行する、発注する:
    ./test_bpf_tp.tp
    Attaching 1 probe...
    bpftrace:dest:1234  source:8080
    

    唯一ツッコミを入れるのはntohsのようによく使われるインタフェースにbuiltinがない理由です!
    tracepointとkprobe/kretprobeでは、それぞれメリットとデメリットは何ですか?どうして私はkprobeが好きではありませんて、私はlive patchさえ好きではありませんて、しかしこれはまたどうしてです.
    tracepointの唯一の欠点は、ソースコードの特定の位置にコードを書くことで静的に挿入し、再コンパイルしてから実行する必要があることです.これに対して、kprobeは動的で、実行時にHOOK動的attachを特定の位置に挿入することができますが、残念ながら、これはkprobeの唯一の利点です.
    誰もが前にして、私はkprobeに対する不満を明らかにしました.理由は私のlive patchに対する不満と同じです.それは、printkを動的に挿入するために、kprobe/live patchフレームワークを維持するコードの実行コストが高すぎるからです.
    ftraceフレームワークの下でlive patchの新しい関数を呼び出すコードパスがどれくらいあるかを追跡してください.krpobe/kretprobeがhandlerを実行するために、どれだけ余分なことをしたかを追跡してください.私に必要なのはhandlerだけです.handlerにはprintkの変数の値だけです.私はこんな小さなことのために大騒ぎしたくありません.handlerを呼び出すパスでspinlockに遭遇すると、パフォーマンスボトルネックをデバッグするために導入された新しいボトルネックは、最初のパフォーマンスボトルネック自体を上回ってしまいます.このような微細な動作を性能最適化する上で,この測定ミス効果は極めて困難である.
    私は職人で、kprobe、live patchという私から見れば全くコントロールできないものを信用するのは難しいです.もちろん私もこれらのツールを上手に操作できる人の恥笑を気にしないで、私は自分の方法があります.
    もし私がpatchの関数を望んでいるならば、あるいは私はどこかにprintkを挿入したいならば、私は手作業でして、とても簡単で、直接特定の位置でコードをcall myhandlerに修正すればいいです.myhandlerが戻って、置換された命令を再実行して、このような方法はとても簡単で直接して、私はこのような方法を見つけて、私がこのような方法を熟練して運用することができる時、私から見ればtracepointとkprobeにも本質的な違いはありません.
    kprobeとは何かを忘れて、live patchを忘れて、tracepointを忘れたほうが覚えているよりいいかもしれません.
    タクシーを止めたり、携帯電話より滴滴を打ったりするのが楽です.同じように、紙幣で支払ってもバッテリーの航続やプライバシーの問題を心配する必要はありません.
    浙江温州の靴は湿っていて、雨が降って水に入っても太らない.