主頁 > 作業系統 > 【原創】Kernel除錯追蹤技術之 Kprobe on ARM64

【原創】Kernel除錯追蹤技術之 Kprobe on ARM64

2021-02-01 06:07:59 作業系統

Kernel除錯追蹤技術之 Kprobe on ARM64

本題目目標

  1. 熟悉kprobe的由來、介面使用方式和基本原理
  2. 詳解ARM64 Kprobe的實作方式
  3. 思考探索kprobe可以解決哪些問題
  4. 簡介目前基于kprobe的工具

kprobe是什么?

kprobe 是一種動態除錯機制,用于debugging,動態跟蹤,性能分析,動態修改內核行為等,2004年由IBM發布,是名為Dprobes工具集的底層實作機制[1][2],2005年合入Linux kernel,probe的含義是像一個探針,可以不修改分析物件原始碼的情況下,獲取Kernel的運行時資訊,

kprobe的實作原理是把指定地址(探測點)的指令替換成一個可以讓cpu進入debug模式的指令,使執行路徑暫停,跳轉到probe 處理函式后收集、修改資訊,再跳轉回來繼續執行,

X86中使用的是int3指令,ARM64中使用的是BRK指令進入debug monitor模式,

這種指令替換機制使得kprobe可以在大部分kernel的代碼段插入探測點,除了一些分支類的指令如brexceptioneret等,另外kprobe模塊本身的代碼不能probe,在pre_handlerpost_handler中可以訪問、修改所有暫存器和全域變數,其變體jprobe可以檢查傳入引數,kretprobe可以檢查回傳值,且不需要修改探測目標的原始碼,方便用于生產系統的debug、性能分析、log記錄等,

kprobe一直在X86系統上使用,ARM64的平臺支持在2015年合入kernel [8],

kprobe代碼示例

  • sample/kprobe/kprobe_example.c

    // sample/kprobe/kprobe_example.c 
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/kprobes.h>
    
    #define MAX_SYMBOL_LEN  64
    static char symbol[MAX_SYMBOL_LEN] = "_do_fork";
    module_param_string(symbol, symbol, sizeof(symbol), 0644);
    
    /* For each probe you need to allocate a kprobe structure */
    static struct kprobe kp = {
            .symbol_name    = symbol,
    };
    
    /* kprobe pre_handler: called just before the probed instruction is executed */
    static int handler_pre(struct kprobe *p, struct pt_regs *regs)
    {
            pr_info("<%s> pre_handler: p->addr = 0x%p, pc = 0x%lx,"
                            " pstate = 0x%lx\n",
                    p->symbol_name, p->addr, (long)regs->pc, (long)regs->pstate);
    
            /* A dump_stack() here will give a stack backtrace */
            return 0;
    }
    
    /* kprobe post_handler: called after the probed instruction is executed */
    static void handler_post(struct kprobe *p, struct pt_regs *regs,
                                    unsigned long flags)
    {
            pr_info("<%s> post_handler: p->addr = 0x%p, pstate = 0x%lx\n",
                    p->symbol_name, p->addr, (long)regs->pstate);
    }
    
    static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
    {
            pr_info("fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr);
            /* Return 0 because we don't handle the fault. */
            return 0;
    }
    
    static int __init kprobe_init(void)
    {
            int ret;
            kp.pre_handler = handler_pre;
            kp.post_handler = handler_post;
            kp.fault_handler = handler_fault;
    
            ret = register_kprobe(&kp);
            if (ret < 0) {
                    pr_err("register_kprobe failed, returned %d\n", ret);
                    return ret;
            }
            pr_info("Planted kprobe at %p\n", kp.addr);
            return 0;
    }
    
    static void __exit kprobe_exit(void)
    {
            unregister_kprobe(&kp);
            pr_info("kprobe at %p unregistered\n", kp.addr);
    }
    
  • sample/kprobe/jprobe_example.c

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/kprobes.h>
    
    /* Proxy routine having the same arguments as actual _do_fork() routine */
    static long j_do_fork(unsigned long clone_flags, unsigned long stack_start,
                  unsigned long stack_size, int __user *parent_tidptr,
                  int __user *child_tidptr, unsigned long tls)
    {
            pr_info("jprobe: clone_flags = 0x%lx, stack_start = 0x%lx "
                    "stack_size = 0x%lx\n", clone_flags, stack_start, stack_size);
    
            /* Always end with a call to jprobe_return(). */
            jprobe_return();
            return 0;
    }
    
    static struct jprobe my_jprobe = {
            .entry                  = j_do_fork,
            .kp = {
                    .symbol_name    = "_do_fork",
            },
    };
    
    static int __init jprobe_init(void)
    {
            int ret;
    
            ret = register_jprobe(&my_jprobe);
            if (ret < 0) {
                    pr_err("register_jprobe failed, returned %d\n", ret);
                    return -1;
            }
            pr_info("Planted jprobe at %p, handler addr %p\n",
                   my_jprobe.kp.addr, my_jprobe.entry);
            return 0;
    }
    
    static void __exit jprobe_exit(void)
    {
            unregister_jprobe(&my_jprobe);
            pr_info("jprobe at %p unregistered\n", my_jprobe.kp.addr);
    }
    
    module_init(jprobe_init)
    module_exit(jprobe_exit)
    MODULE_LICENSE("GPL");
    
  • sample/kprobe/kretprobe_example.c

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/kprobes.h>
    #include <linux/ktime.h>
    #include <linux/limits.h>
    #include <linux/sched.h>
    
    static char func_name[NAME_MAX] = "_do_fork";
    module_param_string(func, func_name, NAME_MAX, S_IRUGO);
    MODULE_PARM_DESC(func, "Function to kretprobe; this module will report the"
                            " function's execution time");
    
    /* per-instance private data */
    struct my_data {
            ktime_t entry_stamp;
    };
    
    /* Here we use the entry_hanlder to timestamp function entry */
    static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
    {
            struct my_data *data;
    
            if (!current->mm)
                    return 1;       /* Skip kernel threads */
    
            data = https://www.cnblogs.com/hpyu/archive/2021/01/30/(struct my_data *)ri->data;
            data->entry_stamp = ktime_get();
            return 0;
    }
    
    static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
    {
            unsigned long retval = regs_return_value(regs);
            struct my_data *data = (struct my_data *)ri->data;
            s64 delta;
            ktime_t now;
    
            now = ktime_get();
            delta = ktime_to_ns(ktime_sub(now, data->entry_stamp));
            pr_info("%s returned %lu and took %lld ns to execute\n",
                            func_name, retval, (long long)delta);
            return 0;
    }
    
    static struct kretprobe my_kretprobe = {
            .handler                = ret_handler,
            .entry_handler          = entry_handler,
            .data_size              = sizeof(struct my_data),
            /* Probe up to 20 instances concurrently. */
            .maxactive              = 20,
    };
    
    static int __init kretprobe_init(void)
    {
            int ret;
    
            my_kretprobe.kp.symbol_name = func_name;
            ret = register_kretprobe(&my_kretprobe);
            if (ret < 0) {
                    pr_err("register_kretprobe failed, returned %d\n", ret);
                    return -1;
            }
            pr_info("Planted return probe at %s: %p\n",
                            my_kretprobe.kp.symbol_name, my_kretprobe.kp.addr);
            return 0;
    }
    
    static void __exit kretprobe_exit(void)
    {
            unregister_kretprobe(&my_kretprobe);
            pr_info("kretprobe at %p unregistered\n", my_kretprobe.kp.addr);
    
            /* nmissed > 0 suggests that maxactive was set too low. */
            pr_info("Missed probing %d instances of %s\n",
                    my_kretprobe.nmissed, my_kretprobe.kp.symbol_name);
    }
    

kprobe原始碼和介面

原始碼

./include/linux/kprobes.h             # kprobe頭檔案
./include/asm-generic/kprobes.h
./kernel/kprobes.c                    # kprobe核心實作
./arch/arm64/include/asm/kprobes.h
./arch/arm64/kernel/probes/kprobes.c  # kprobe arm64支持
./arch/arm64/kernel/probes/kprobes_trampoline.S
./samples/kprobes/kprobe_example.c    # kprobe使用例子程式
./samples/kprobes/jprobe_example.c
./samples/kprobes/kretprobe_example.c

對外介面

struct kprobe {
    struct hlist_node hlist; // hash list保存所有kprobe,key是指令地址
    struct list_head list;   // 鏈接一個地址上注冊的多個kprobe
    unsigned long nmissed;   // 被臨時disabled的次數

    kprobe_opcode_t *addr;   // 探測點地址
    const char *symbol_name; // 探測點函式名
    unsigned int offset;     // 探測點在函式內的偏移

~~~~    kprobe_pre_handler_t pre_handler;       // 前處理函式
    kprobe_post_handler_t post_handler;     // 后處理函式
    kprobe_fault_handler_t fault_handler;   // 探測指令發生fault時的處理函式
    kprobe_break_handler_t break_handler;   // 在前三個handler里發生break trap的處理函式

    /* Saved opcode (which has been replaced with breakpoint) */
    kprobe_opcode_t opcode;  // 被breakpoint替換了的操作碼

    /* copy of the original instruction */
    struct arch_specific_insn ainsn;  // 保存平臺相關的被探測指令和下一條指令

    u32 flags;    // 狀態標記
};

struct jprobe {
    struct kprobe kp;                                   
    void *entry;    /* probe handling code to jump to */
};
struct kretprobe {
    struct kprobe kp;                 
    kretprobe_handler_t handler;
    kretprobe_handler_t entry_handler;
    int maxactive;                    
    int nmissed;
    size_t data_size;
    struct hlist_head free_instances; 
    raw_spinlock_t lock;              
};
int register_kprobe(struct kprobe *p);                                       
int register_kprobes(struct kprobe **kps, int num);       
int register_jprobe(struct jprobe *p);                                       
int register_jprobes(struct jprobe **jps, int num);   
int register_kretprobe(struct kretprobe *rp);                                
int register_kretprobes(struct kretprobe **rps, int num);    
int disable_kprobe(struct kprobe *kp);
int enable_kprobe(struct kprobe *kp);                
void jprobe_return(void);                                                    
void unregister_jprobe(struct jprobe *p);                                    
void unregister_jprobes(struct jprobe **jps, int num);                       
void unregister_kprobe(struct kprobe *p);                                    
void unregister_kprobes(struct kprobe **kps, int num);                       
void unregister_kretprobe(struct kretprobe *rp);                             
void unregister_kretprobes(struct kretprobe **rps, int num);

kprobe的使用比較簡單,只需要指定探測點地址,或者使用符號名+偏移的方式,定義xxx_handler,注冊即可,注冊后探測指令被替換,可以使用kprobe_enable/disable函式動態開關,

jprobe是kprobe的一種方便檢查函式引數的實作方式,通過定義一個和探測函式相同原型的函式實作,

kretprobe用于檢查函式回傳值,可以定義函式入口handler和回傳時的handler,

可以參看kernel原始碼中的samples/kprobes/下的例子程式,

kprobe的實作及Arm64支持

kprobe的管理

kprobe可以支持大量的探測點,為了快速查詢和插入,使用哈希鏈表管理所有的kprobes,hash的key值是探測點的地址值,

static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE];
struct kprobe *get_kprobe(void *addr)
{
    struct hlist_head *head;
    struct kprobe *p;

    head = &kprobe_table[hash_ptr(addr, KPROBE_HASH_BITS)];
    hlist_for_each_entry_rcu(p, head, hlist) {
        if (p->addr == addr)
            return p;
    }
    return NULL;
}
int register_kprobe(struct kprobe *p)
{..

	INIT_HLIST_NODE(&p->hlist);                                    
	hlist_add_head_rcu(&p->hlist,                                  
	           &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
..}

kprobe的注冊

kprobe注冊時傳入的是struct kprobe結構體,里面包含指令地址或者函式名地址和函式內偏移,傳入地址需要先檢查是否在代碼段里,且不在blacklist里,blacklist包含不能probe的函式,主要是kprobe本身的函式,

然后呼叫arch_prepare_kprobe解碼指令,看指令是否是一些分支等特殊指令,需要特別處理,如果是正常可以probe的指令,呼叫arch_prepare_ss_slot把探測點的指令備份到slot page里,把下一條指令存入struct arch_probe_insn結構的restore成員里,在post_handler之后恢復執行,

arch_prepare_krpobe無誤后把kprobe加入kprobe_table哈希鏈表,

然后呼叫arch_arm_kprobe替換探測點指令為BRK64_OPCODE_KPROBES指令,

kprobe的觸發和處理

kprobe的觸發和處理是通過brk exceptionsingle step 單步exception執行的,每次的處理函式中會修改被例外中斷的背景關系(struct pt_regs)的指令暫存器,實作執行流的跳轉,

例外機制依賴于CPU的體系結構,所以這部分代碼是在arch/arm64/kernel/probes/kprobes.c 中實作的,這也是各個體系結構支持kprobe的主要功能,

例外處理的注冊

例外處理的注冊在arch/arm64/kernel/debug-monitors.c, 是arm64的通用debug模塊,kgdb也基于這個模塊,

static int __init debug_traps_init(void)                                 
{
    hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,   // 單步例外處理函式
                  TRAP_TRACE, "single-step handler");
    hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,            // 斷點例外處理函式
                  TRAP_BRKPT, "ptrace BRK handler");
    return 0; 
}

hook_debug_fault_code是替換arch/arm64/mm/fault.c 中的debug_fault_info例外表項:

/*      
 * __refdata because early_brk64 is __init, but the reference to it is
 * clobbered at arch_initcall time.
 * See traps.c and debug-monitors.c:debug_traps_init().
 */ 
static struct fault_info __refdata debug_fault_info[] = {
    { do_bad,   SIGTRAP,    TRAP_HWBKPT,    "hardware breakpoint"   },
    { do_bad,   SIGTRAP,    TRAP_HWBKPT,    "hardware single-step"  },
    { do_bad,   SIGTRAP,    TRAP_HWBKPT,    "hardware watchpoint"   },
    { do_bad,   SIGBUS,     0,      "unknown 3"     },
    { do_bad,   SIGTRAP,    TRAP_BRKPT, "aarch32 BKPT"      },
    { do_bad,   SIGTRAP,    0,      "aarch32 vector catch"  },
    { early_brk64,  SIGTRAP,    TRAP_BRKPT, "aarch64 BRK"       },
    { do_bad,   SIGBUS,     0,      "unknown 7"     },
};

void __init hook_debug_fault_code(int nr,
                  int (*fn)(unsigned long, unsigned int, struct pt_regs *),
                  int sig, int code, const char *name)
{                 
    BUG_ON(nr < 0 || nr >= ARRAY_SIZE(debug_fault_info));

    debug_fault_info[nr].fn     = fn;
    debug_fault_info[nr].sig    = sig;
    debug_fault_info[nr].code   = code;
    debug_fault_info[nr].name   = name;
}

BRK和SS例外執行流程

arch/arm64/kernel/entry.S:
el1_dbg:
      bl  do_debug_exception
            fault_info *inf = debug_fault_info + DBG_ESR_EVT(esr);
                  inf->fn(addr, esr, regs)
                        brk_handler()
                              kprobe_breakpoint_handler()
			single_step_handler()
			      kprobe_single_step_handler()

breakpoint斷點執行流程主要任務是執行pre_handler,把slot中保存的原指令設定進regs背景關系的指令暫存器里,這樣退出brk例外后,會單步執行被probe的指令,

單步執行探測點的指令后,會觸發單步例外,進入single_step_handler,呼叫kprobe_single_step_handler,主要任務是恢復執行路徑,呼叫用戶注冊的post_handler

首先檢查當前單步例外的指令地址是否是之前設定的下一條指令地址;然后關閉單步狀態,即只執行一次單步,

最后設定暫存器背景關系中的指令暫存器為探測點指令的下一條指令,呼叫post_handler,結束這個kprobe的作業,

其它未詳細介紹的部分

  1. 被替換的指令放在哪里?
    • slot page,使用了module_alloc分配可以執行的記憶體頁
  2. 一個探測點由多個probe注冊怎么處理
    • aggrprobe
  3. SMP、中斷、搶占時可能有kprobe重入,如何處理
    • 實作了reenter檢查機制,允許probe嵌套
  4. kprobe的性能
    • break指令導致CPU執行停止,時間開銷較大
    • x86實作了優化機制,使用jmp指令替換int3 這種break指令,速度提升10倍;ARM64中未實作,

kprobes: Kprobes jump optimization support[5]

kprobe 的問題

  1. probe函式的定義要非常小心,否則會引起kernel panic或其它例外,比如probe的一個函式臨界區被呼叫,而你的probe handler是睡眠的,就會報錯
  2. 需要依賴kernel原始碼樹或build檔案,撰寫kernel 模塊

kprobe的優勢

  1. 可以不重編kernel對生產系統進行探測,在PC和Server中比較有意義
  2. 可以動態觀察、修改幾乎任意代碼路徑的狀態,比Ftrace有更強的定制性

kprobe適用場景

1. 觀察 Observability

  • kprobe觀察任一點的暫存器狀態,全域變數,區域變數,jprobe觀察函式引數,kretprobe觀察回傳值
  • 函式呼叫,profiling等
    • 這塊Ftrace可以替代
  • 資料收集
    • 例如:列印genpool的bitmap,觀察ION carveout的使用情況

2. 篡改 Hacking

  • error-injection
    • 修改函式回傳值或者判斷條件,使代碼進入錯誤處理路徑,提高測驗覆寫率
  • data-injection
    • 動態修改資料,仿真測驗資料,比如注入溫度傳感器溫度,模仿各種測驗值測驗
    • 實驗:對pvt做溫度抽樣值注入,測驗各種溫度下的計算問題
  • 動態補丁

3. 除錯

4. 其它?

kprobe的應用

1. trace_kprobe

kernel ftrace子系統基于kprobe實作了kprobe_event,可以probe任意函式,

優點是無需寫代碼,方便簡單的函式trace,可以獲得函式引數值和回傳值

缺點是不能做動態修改

使用方法參看kernel/Documentation/trace/kprobetrace.txt

  • kprobe_event使用說明和示例

     24 Synopsis of kprobe_events
     25 -------------------------
     26   p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]  : Set a probe
     27   r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS]  : Set a return probe
     28   -:[GRP/]EVENT                     : Clear a probe
     29 
     30  GRP        : Group name. If omitted, use "kprobes" for it.
     31  EVENT      : Event name. If omitted, the event name is generated
     32           based on SYM+offs or MEMADDR.
     33  MOD        : Module name which has given SYM.
     34  SYM[+offs] : Symbol+offset where the probe is inserted.
     35  MEMADDR    : Address where the probe is inserted.
     36  MAXACTIVE  : Maximum number of instances of the specified function that
     37           can be probed simultaneously, or 0 for the default value
     38           as defined in Documentation/kprobes.txt section 1.3.1.
     39 
     40  FETCHARGS  : Arguments. Each probe can have up to 128 args.
     41   %REG      : Fetch register REG
     42   @ADDR     : Fetch memory at ADDR (ADDR should be in kernel)
     43   @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)
     44   $stackN   : Fetch Nth entry of stack (N >= 0)
     45   $stack    : Fetch stack address.
     46   $retval   : Fetch return value.(*)
     47   $comm     : Fetch current task comm.
     48   +|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**)
     49   NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
     50   FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
     51           (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
     52           (x8/x16/x32/x64), "string" and bitfield are supported.
     53 
     54   (*) only for return probe.
     55   (**) this is useful for fetching a field of data structures.
    
    echo 'p:myprobe do_sys_open filename=+0(%x1):string flags=%x2 mode=%x3' > /sys/kernel/debug/tracing/kprobe_events
    echo 'r:myretprobe do_sys_open $retval ' >> /sys/kernel/debug/tracing/kprobe_events
    echo > trace
    echo 1 > events/kprobes/enable
    
    root@x3dvbj3-hynix2G-2666:/sys/kernel/debug/tracing# cat trace
    # tracer: nop
    #
    #                              _-----=> irqs-off
    #                             / _----=> need-resched
    #                            | / _---=> hardirq/softirq
    #                            || / _--=> preempt-depth
    #                            ||| /     delay
    #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
    #              | |       |   ||||       |         |
                  sh-1499  [002] d...  1902.985663: myprobe: (do_sys_open+0x0/0x208) filename="/etc/passwd" flags=0xa0000 mode=0x0
                  sh-1499  [002] d..1  1902.985730: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
                  ps-12596 [001] d...  1903.694056: myprobe: (do_sys_open+0x0/0x208) filename="/etc/ld.so.cache" flags=0xa0000 mode=0x0
                grep-12597 [002] d...  1903.694056: myprobe: (do_sys_open+0x0/0x208) filename="/etc/ld.so.cache" flags=0xa0000 mode=0x0
                grep-12597 [002] d..1  1903.694103: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
                  ps-12596 [001] d..1  1903.694103: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
                grep-12597 [002] d...  1903.694147: myprobe: (do_sys_open+0x0/0x208) filename="/lib/libm.so.6" flags=0xa0000 mode=0x0
                  ps-12596 [001] d...  1903.694148: myprobe: (do_sys_open+0x0/0x208) filename="/lib/libm.so.6" flags=0xa0000 mode=0x0
                grep-12597 [002] d..1  1903.694179: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
                  ps-12596 [001] d..1  1903.694179: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
                grep-12597 [002] d...  1903.694364: myprobe: (do_sys_open+0x0/0x208) filename="/lib/libresolv.so.2" flags=0xa0000 mode=0x0
                  ps-12596 [001] d...  1903.694381: myprobe: (do_sys_open+0x0/0x208) filename="/lib/libresolv.so.2" flags=0xa0000 mode=0x0
                grep-12597 [002] d..1  1903.694398: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
                  ps-12596 [001] d..1  1903.694413: myretprobe: (SyS_openat+0x3c/0x50 <- do_sys_open) arg1=0x3
    

通過offset和型別列印,實作結構體內部成員的列印,但是需要知道暫存器和引數的對應關系和結構體成員的偏移,[13]提到了新的function_event機制,可以直接傳遞引數名,

  • 獲取資料結構偏移的例子:列印ip_rcv的網路設備名和收發包數

    $ aarch64-linux-gnu-gdb vmlinux
    (gdb) ptype/o struct net_device
    /* offset    |  size */  type = struct net_device {
    /*    0      |    16 */    char name[16];      //設備名
    /*   16      |    16 */    struct hlist_node {
    /*   16      |     8 */        struct hlist_node *next;
    /*   24      |     8 */        struct hlist_node **pprev;
    ...
    /*  272      |   184 */    struct net_device_stats {
    /*  272      |     8 */        unsigned long rx_packets;
    /*  280      |     8 */        unsigned long tx_packets;
    /*  288      |     8 */        unsigned long rx_bytes;
    
    (gdb) print (int)&((struct net_device *)0)->stats
    $3 = 272
    
    cd /sys/kernel/debug/tracing/
    echo 'p:net ip_rcv name=+0(%x1):string rx_pkts=+272(%x1):u64 tx_pkts=+280(%x1):u64 ' > kprobe_events
    echo 1 > events/kprobes/enable
    
    root@x3dvbj3-hynix2G-2666:/sys/kernel/debug/tracing# cat trace
    # tracer: nop
    #
    #                              _-----=> irqs-off
    #                             / _----=> need-resched
    #                            | / _---=> hardirq/softirq
    #                            || / _--=> preempt-depth
    #                            ||| /     delay
    #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
    #              | |       |   ||||       |         |
              <idle>-0     [000] d.s1   837.456030: net: (ip_rcv+0x0/0x3c8) name="eth0" rx_pkts=2445 tx_pkts=131
            dropbear-2776  [000] d.s1   837.457538: net: (ip_rcv+0x0/0x3c8) name="eth0" rx_pkts=2446 tx_pkts=133
              <idle>-0     [000] d.s1   837.662158: net: (ip_rcv+0x0/0x3c8) name="eth0" rx_pkts=2447 tx_pkts=133
              <idle>-0     [000] d.s1   837.668020: net: (ip_rcv+0x0/0x3c8) name="eth0" rx_pkts=2448 tx_pkts=135
    root@x3dvbj3-hynix2G-2666:/sys/kernel/debug/tracing# ifconfig
    eth0      Link encap:Ethernet  HWaddr 00:70:64:7b:59:23  
              inet addr:192.168.1.10  Bcast:192.168.1.255  Mask:255.255.255.0
              inet6 addr: fe80::270:64ff:fe7b:5923/64 Scope:Link
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:2450 errors:0 dropped:0 overruns:0 frame:0
              TX packets:136 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:196959 (192.3 KiB)  TX bytes:16262 (15.8 KiB)
              Interrupt:44 Base address:0x4000
    

2. systemtap

systemap 實作了一種腳本語言,把腳本翻譯成C代碼,編譯成kernel 模塊,注冊成kprobe handler,對kernel進行探測[12],

由Redhat開發,在Redhat系統里運行比較穩定,其它環境易用性較差,

  • Systemtap腳本示例

    # cat inode-watch.stp
    probe kernel.function ("vfs_write"),
    kernel.function ("vfs_read")
    {
    	if (@defined($file->f_path->dentry)) {
    		dev_nr = $file->f_path->dentry->d_inode->i_sb->s_dev
    		inode_nr = $file->f_path->dentry->d_inode->i_ino
    	} else {
    		dev_nr = $file->f_dentry->d_inode->i_sb->s_dev
    		inode_nr = $file->f_dentry->d_inode->i_ino
    	}
    	if (dev_nr == ($1 << 20 | $2) # major/minor device
    				&& inode_nr == $3)
    	printf ("%s(%d) %s 0x%x/%u\n",
    	execname(), pid(), ppfunc(), dev_nr, inode_nr)
    }
    # stat -c "%D %i" /etc/crontab
    fd03 133099
    # stap inode-watch.stp 0xfd 3 133099
    more(30789) vfs_read 0xfd00003/133099
    more(30789) vfs_read 0xfd00003/133099
    

3. perf probe

perf 的probe命令提供了添加動態探測點的功能, 參看 kernel/tools/perf/Documentation/perf-probe.txt

236 EXAMPLES
237 --------
238 Display which lines in schedule() can be probed:
239 
240  ./perf probe --line schedule
241 
242 Add a probe on schedule() function 12th line with recording cpu local variable:
243 
244  ./perf probe schedule:12 cpu
245  or
246  ./perf probe --add='schedule:12 cpu'
247 
248 Add one or more probes which has the name start with "schedule".
249 
250  ./perf probe schedule*
251  or
252  ./perf probe --add='schedule*'
253 
254 Add probes on lines in schedule() function which calls update_rq_clock().
255 
256  ./perf probe 'schedule;update_rq_clock*

4. eBPF

eBPF (Extended Berkeley Packet Filter)是最近比較流行的內核探測技術,通過在Kernel中運行一個虛擬機,執行用戶傳入的位元組碼,實作對kernel內部資訊的探測能力,比較安全易用,

其動態探測部分是基于kprobe和kretprobe實作的,可以實作對任意函式的探測、資料處理等能力,

Appendix

ARM64 BRK 指令

IMM16是自定義的,用于標識是哪種brk用途,比如kgdb或者kprobe,主要使用的幾個BRK立即數定義如下

#define FAULT_BRK_IMM           0x100    
#define KGDB_DYN_DBG_BRK_IMM        0x400
#define KGDB_COMPILED_DBG_BRK_IMM   0x401
#define BUG_BRK_IMM         0x800
#define BRK64_ESR_KPROBES   0x0004
#define BRK64_ESR_UPROBES   0x0005

#define AARCH64_BREAK_MON   0xd4200000                                        

#define BRK64_OPCODE_KPROBES    (AARCH64_BREAK_MON | (BRK64_ESR_KPROBES << 5))                                        
#define BRK64_OPCODE_UPROBES    (AARCH64_BREAK_MON | (BRK64_ESR_UPROBES << 5))
#define AARCH64_BREAK_FAULT (AARCH64_BREAK_MON | (FAULT_BRK_IMM << 5))        
#define AARCH64_BREAK_KGDB_DYN_DBG  (AARCH64_BREAK_MON | (KGDB_DYN_DBG_BRK_IMM << 5)) 

ABI

寫kprobe驅動要知道函式引數和暫存器的對應關系,需要了解對應體系架構的ABI (Application Binary Interface)

X86 and X86_64 ABI :

1. User-level applications use as integer registers for passing the sequence
%rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi,
%rsi, %rdx, %r10, %r8 and %r9.
2. A system-call is done via the syscall instruction. The kernel destroys
registers %rcx and %r11.
3. The number of the syscall has to be passed in register %rax.
4. System-calls are limited to six arguments, no argument is passed directly on
the stack.
5. Returning from the syscall, register %rax contains the result of the
system-call. A value in the range between -4095 and -1 indicates an error,
it is -errno.
6. Only values of class INTEGER or class MEMORY are passed to the kernel.

ARM64 ABI : ARM64 Parameters in general-purpose registers

Reference

  1. 2004, Kernel debugging with Kprobes, IBM
  2. 2005, An introduction to KProbes [LWN.net]
  3. 2006, Kprobes: User space probes support [LWN.net]
  4. 2009, tracing: kprobe-based event tracer [LWN.net]
  5. 2010, kprobes: Kprobes jump optimization support [LWN.net]
  6. 2012, ftrace, kprobes: Ftrace-based kprobe optimization [LWN.net]
  7. 2015, Early kprobe: enable kprobes at very early booting LWN.net Wang Nan
  8. 2015, arm64: Add kernel probes (kprobes) support [LWN.net]
  9. 2016, Kprobes Event Tracing on Armv8 - Linaro
  10. Kernel Probes (Kprobes) [kernel/Documentation]
  11. Kprobe-based Event Tracing
  12. https://sourceware.org/systemtap/tutorial.pdf
  13. https://events19.linuxfoundation.org/wp-content/uploads/2017/12/oss-eu-2018-fun-with-dynamic-trace-events_steven-rostedt.pdf

文章標題:Kernel除錯追蹤技術之 Kprobe on ARM64
本文作者:hpyu
本文鏈接:https://www.cnblogs.com/hpyu/articles/14257305.html
歡迎轉載,請注明原文鏈接

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/255039.html

標籤:其他

上一篇:IIC 踩過的坑

下一篇:windows上傳檔案到linux服務器

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more