0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入探究Linux中的Kprobe機(jī)制

Linux閱碼場(chǎng) ? 來源:csdn ? 作者:liuhangtiant ? 2021-01-02 11:53 ? 次閱讀

概述

kprobe機(jī)制用于在內(nèi)核中動(dòng)態(tài)添加一些探測(cè)點(diǎn),可以滿足一些調(diào)試需求。本文主要探尋kprobe的執(zhí)行路徑,也就是說如何trap到kprobe,以及如何回到原路徑繼續(xù)執(zhí)行。

實(shí)例

先通過一個(gè)實(shí)例來感受下kprobe,linux中有一個(gè)現(xiàn)成的實(shí)例: samples/kprobes/kprobe_example.c 由于當(dāng)前驗(yàn)證環(huán)境是基于qemu+arm64,我刪除了其他架構(gòu)的代碼,并稍稍做了一下改動(dòng):

/* * NOTE: This example is works on x86 and powerpc. * Here‘s a sample kernel module showing the use of kprobes to dump a * stack trace and selected registers when _do_fork() is called. * * For more information on theory of operation of kprobes, see * Documentation/kprobes.txt * * You will see the trace data in /var/log/messages and on the console * whenever _do_fork() is invoked to create a new process. */ #include 《linux/kernel.h》#include 《linux/module.h》#include 《linux/kprobes.h》 #define MAX_SYMBOL_LEN 64static 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 ”, p-》symbol_name, p-》addr, (long)regs-》pc, (long)regs-》pstate); dump_stack(); /* 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 ”, p-》symbol_name, p-》addr, (long)regs-》pstate); dump_stack();} /* * fault_handler: this is called if an exception is generated for any * instruction within the pre- or post-handler, or when Kprobes * single-steps the probed instruction. */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 ”, ret); return ret; } pr_info(“Planted kprobe at %p ”, kp.addr); return 0;} static void __exit kprobe_exit(void){ unregister_kprobe(&kp); pr_info(“kprobe at %p unregistered ”, kp.addr);} module_init(kprobe_init)module_exit(kprobe_exit)MODULE_LICENSE(“GPL”);

這段代碼很簡(jiǎn)單,默認(rèn)情況下,kprobe做了3個(gè)鉤子,分別在_do_fork對(duì)應(yīng)位置的指令執(zhí)行之前,執(zhí)行之后,以及出異常的時(shí)候。

插入該內(nèi)核模塊之后,隨便輸入一條命令,可看到下面的打?。?/p>

[ 19.882832] kprobe_example: loading out-of-tree module taints kernel.[ 19.900442] Planted kprobe at (____ptrval____)[ 19.908571] 《_do_fork》 pre_handler: p-》addr = 0x(____ptrval____), pc = 0xffff0000080d2c98, pstate = 0x80000005[ 19.913657] CPU: 0 PID: 1358 Comm: udevd Tainted: G O 4.18.0 #7[ 19.916239] Hardware name: linux,dummy-virt (DT)[ 19.918400] Call trace:[ 19.919373] dump_backtrace+0x0/0x180[ 19.920681] show_stack+0x14/0x20[ 19.921817] dump_stack+0x90/0xb4[ 19.923678] handler_pre+0x24/0x68 [kprobe_example][ 19.926357] kprobe_breakpoint_handler+0xbc/0x160[ 19.926627] brk_handler+0x70/0x88[ 19.926802] do_debug_exception+0x94/0x160[ 19.927102] el1_dbg+0x18/0x78[ 19.927299] _do_fork+0x0/0x358[ 19.927465] el0_svc_naked+0x30/0x34[ 19.928973] 《_do_fork》 post_handler: p-》addr = 0x(____ptrval____), pstate = 0x80000005[ 19.929361] CPU: 0 PID: 1358 Comm: udevd Tainted: G O 4.18.0 #7[ 19.929693] Hardware name: linux,dummy-virt (DT)[ 19.929962] Call trace:[ 19.930102] dump_backtrace+0x0/0x180[ 19.930289] show_stack+0x14/0x20[ 19.930461] dump_stack+0x90/0xb4[ 19.934684] handler_post+0x24/0x30 [kprobe_example][ 19.934968] post_kprobe_handler+0x54/0x98[ 19.935234] kprobe_single_step_handler+0x74/0xa8[ 19.935389] single_step_handler+0x3c/0xb0[ 19.935516] do_debug_exception+0x94/0x160[ 19.935642] el1_dbg+0x18/0x78[ 19.935965] 0xffff000000ac8004[ 19.936067] el0_svc_naked+0x30/0x34

probe和post鉤子得到執(zhí)行,這對(duì)查看內(nèi)核的調(diào)用棧非常有幫助。

深入探究

是否只能基于symbol_name做kprobe?

顯然不太可能,struct kprobe中有一個(gè)addr成員,很明顯是可以直接基于地址做kprobe的。

把這段代碼:

#define MAX_SYMBOL_LEN 64static 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,};

修改為:

/* For each probe you need to allocate a kprobe structure */static struct kprobe kp = { .addr= (kprobe_opcode_t *)0xffff0000080d2c98,};

效果是一樣的。

kprobe是如何動(dòng)態(tài)添加探針的?

這個(gè)肯定要分析代碼了,好在代碼相當(dāng)簡(jiǎn)單:

register_kprobe |------arm_kprobe | |------__arm_kprobe | | |------arch_arm_kprobe /* arm kprobe: install breakpoint in text */void __kprobes arch_arm_kprobe(struct kprobe *p){ patch_text(p-》addr, BRK64_OPCODE_KPROBES);}

從注釋就可以很明顯看出來,是把a(bǔ)ddr對(duì)應(yīng)位置的指令修改為brk指令,當(dāng)然這里說的是ARM64架構(gòu)。那么一旦CPU執(zhí)行到addr,就會(huì)觸發(fā)異常,trap到kprobe注冊(cè)的鉤子上。

post鉤子為什么會(huì)用到single step

從上面的調(diào)用??梢钥吹?,post鉤子實(shí)際上是通過單步斷點(diǎn)trap過來的?為什么需要用到單步斷點(diǎn)呢?這個(gè)其實(shí)很好解釋。我們先來理一下kprobe的過程:

把a(bǔ)ddr位置的指令修改為brk指令

CPU執(zhí)行到addr處trap到pre執(zhí)行

pre執(zhí)行完畢后需要把a(bǔ)ddr處的指令恢復(fù)

CPU繼續(xù)執(zhí)行addr處的指令

CPU執(zhí)行post

那么CPU如何才能執(zhí)行到post,很簡(jiǎn)單,使能單步執(zhí)行就可以了??隙ㄓ腥藭?huì)說,可以把a(bǔ)ddr+4的指令也替換成brk,這個(gè)肯定是不行的,因?yàn)锳RM64可能是32位/16位指令混編的,即便是固定32位指令,CPU下一條要執(zhí)行的指令也不一定是addr+4,比如當(dāng)前addr是一條跳轉(zhuǎn)指令。

fault_handler 鉤子什么時(shí)候會(huì)用到

通過分析代碼可知,當(dāng)發(fā)生page fault的時(shí)候,會(huì)調(diào)用當(dāng)前正在running的kprobe的fault_handler鉤子,所以這里發(fā)生page fault的代碼并不一定是addr處的指令,也可能是pre或者post中的指令。我在pre中注入一段訪問0地址的邏輯:

static void * g_addr=0;static int handler_pre(struct kprobe *p, struct pt_regs *regs) __attribute__((optimize(“O0”)));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 ”, p-》symbol_name, p-》addr, (long)regs-》pc, (long)regs-》pstate); printk(“%d ”, *(char *)g_addr); /* A dump_stack() here will give a stack backtrace */ return 0;}

經(jīng)驗(yàn)證確實(shí)調(diào)用到了fault_handler鉤子:

[ 17.272594] kprobe_example: loading out-of-tree module taints kernel.[ 17.294266] Planted kprobe at (____ptrval____)# # ls[ 19.072586] 《(null)》 pre_handler: p-》addr = 0x(____ptrval____), pc = 0xffff0000080d2c98, pstate = 0x80000005[ 19.073189] fault_handler: p-》addr = 0x(____ptrval____), trap #-1778384890n[ 19.073568] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000[ 19.074271] Mem abort info:[ 19.074393] ESR = 0x96000006[ 19.074641] Exception class = DABT (current EL), IL = 32 bits[ 19.074887] SET = 0, FnV = 0[ 19.075014] EA = 0, S1PTW = 0[ 19.075174] Data abort info:[ 19.075324] ISV = 0, ISS = 0x00000006[ 19.075455] CM = 0, WnR = 0[ 19.075774] user pgtable: 4k pages, 48-bit VAs, pgdp = (____ptrval____)[ 19.076005] [0000000000000000] pgd=00000000485c6003, pud=00000000bb2f4003, pmd=0000000000000000[ 19.076596] Internal error: Oops: 96000006 [#1] PREEMPT SMP[ 19.076924] Modules linked in: kprobe_example(O)[ 19.077693] CPU: 0 PID: 1387 Comm: sh Tainted: G O 4.18.0 #7[ 19.077927] Hardware name: linux,dummy-virt (DT)[ 19.078298] pstate: 400003c5 (nZcv DAIF -PAN -UAO)[ 19.078962] pc : handler_pre+0x50/0x70 [kprobe_example][ 19.079149] lr : handler_pre+0x44/0x70 [kprobe_example][ 19.079359] sp : ffff00000ac63c00[ 19.079565] x29: ffff00000ac63c00 x28: ffff80007a3c9a80 [ 19.079821] x27: ffff000008ac1000 x26: 00000000000000dc [ 19.080047] x25: ffff80007dfb7788 x24: 0000000000000000 [ 19.080363] x23: ffff0000080d2c98 x22: ffff00000ac63d70 [ 19.080621] x21: ffff000000ac2000 x20: 0000800074f02000 [ 19.080863] x19: ffff0000090b5788 x18: ffffffffffffffff [ 19.081197] x17: 0000000000000000 x16: 0000000000000000 [ 19.081501] x15: ffff0000090d96c8 x14: 3030303030666666 [ 19.081720] x13: 667830203d206370 x12: ffff0000090d9940 [ 19.081933] x11: ffff0000085dd8d8 x10: 5f287830203d2072 [ 19.082189] x9 : 0000000000000017 x8 : 2065746174737020 [ 19.082455] x7 : 2c38396332643038 x6 : ffff80007dfb8240 [ 19.082660] x5 : ffff80007dfb8240 x4 : 0000000000000000 [ 19.082871] x3 : ffff80007dfbf030 x2 : 793b575e486def00 [ 19.083068] x1 : 0000000000000000 x0 : 0000000000000000 [ 19.083390] Process sh (pid: 1387, stack limit = 0x(____ptrval____))[ 19.083783] Call trace:[ 19.084020] handler_pre+0x50/0x70 [kprobe_example][ 19.084470] kprobe_breakpoint_handler+0xbc/0x160[ 19.084693] brk_handler+0x70/0x88[ 19.084839] do_debug_exception+0x94/0x160[ 19.085132] el1_dbg+0x18/0x78[ 19.085259] _do_fork+0x0/0x358[ 19.085443] el0_svc_naked+0x30/0x34[ 19.085939] Code: 95d9a53f b0000000 9101c000 f9400000 (39400000) [ 19.086713] ---[ end trace 3bb11c402bc37363 ]---

但由于fault_handler中沒有對(duì)該異常做處理,所以依然掛死了。

fault_handler可以用于報(bào)錯(cuò)或者糾錯(cuò),報(bào)錯(cuò)可以自定義一些錯(cuò)誤信息給用戶,以便分析錯(cuò)誤;糾錯(cuò)用于修改錯(cuò)誤,那么針對(duì)當(dāng)前這個(gè)錯(cuò)誤應(yīng)該怎么做糾錯(cuò)呢?

在fault_handler中為g_addr分配空間?,這顯然不行,g_addr肯定已經(jīng)被載入寄存器了,此時(shí)修改已經(jīng)太遲。唯一的方法就是修改寄存器的值,而寄存器此時(shí)肯定已經(jīng)入棧了,所以必須修改寄存器在棧里面的內(nèi)容。

下面我們來fixup這個(gè)掛死問題:

根據(jù)掛死信息

[ 19.084020] handler_pre+0x50/0x70 [kprobe_example]

是在handler_pre+0x50這個(gè)位置出異常的,通過反匯編得知這個(gè)位置對(duì)應(yīng)的指令是:

50: 39400000 ldrb w0, [x0]

x0的內(nèi)容是0,所以這里是讀0地址,很明顯,g_addr被載入到了x0中,所以只要修改x0就可以了。

fixup實(shí)現(xiàn)

修改fault_handler函數(shù):

static int g_addr1=0x5a;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); regs-》regs[0] = (unsigned long)&g_addr1; /* Return 0 because we don‘t handle the fault. */ return 1;}

驗(yàn)證

[ 58.882059] 《(null)》 pre_handler: p-》addr = 0x(____ptrval____), pc = 0xffff0000080d2c98, pstate = 0x80000005[ 58.882393] fault_handler: p-》addr = 0x(____ptrval____), trap #-1778384890n[ 58.882411] 90[ 58.882658] 《(null)》 post_handler: p-》addr = 0x(____ptrval____), pstate = 0x80000005[ 58.882960] CPU: 1 PID: 1388 Comm: sh Tainted: G O 4.18.0 #7

fault_handler之后,pre_handler打印了g_addr對(duì)應(yīng)地址的內(nèi)容是90,也就是0x5a。

大功告成,我們成功的讓內(nèi)核訪問了0地址,并且返回了0x5a。

當(dāng)然,這一切都是假的!

原文標(biāo)題:深入探究Linux Kprobe機(jī)制

文章出處:【微信公眾號(hào):Linuxer】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1336

    瀏覽量

    40084
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11123

    瀏覽量

    207920

原文標(biāo)題:深入探究Linux Kprobe機(jī)制

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    深入探討Linux的進(jìn)程調(diào)度器

    ,以及其運(yùn)行的順序。這篇文章將詳細(xì)探討Linux進(jìn)程調(diào)度器的工作原理、主要算法、調(diào)度策略以及其在實(shí)際操作的應(yīng)用。
    的頭像 發(fā)表于 08-13 13:36 ?817次閱讀
    <b class='flag-5'>深入</b>探討<b class='flag-5'>Linux</b>的進(jìn)程調(diào)度器

    深入探究石英可編程 DXO/VCXO 振蕩器 SWPQ201 系列(10MHz 至 1500 MHz)

    深入探究石英可編程 DXO/VCXO 振蕩器 SWPQ201 系列(10MHz 至 1500 MHz)
    的頭像 發(fā)表于 08-10 10:05 ?282次閱讀
    <b class='flag-5'>深入</b><b class='flag-5'>探究</b>石英可編程 DXO/VCXO 振蕩器 SWPQ201 系列(10MHz 至 1500 MHz)

    Linux內(nèi)核的頁面分配機(jī)制

    Linux內(nèi)核是如何分配出頁面的,如果我們站在CPU的角度去看這個(gè)問題,CPU能分配出來的頁面是以物理頁面為單位的。也就是我們計(jì)算機(jī)中常講的分頁機(jī)制。本文就看下Linux內(nèi)核是如何管
    的頭像 發(fā)表于 08-07 15:51 ?146次閱讀
    <b class='flag-5'>Linux</b>內(nèi)核<b class='flag-5'>中</b>的頁面分配<b class='flag-5'>機(jī)制</b>

    詳解Linux的權(quán)限控制

    本章將和大家分享Linux的權(quán)限控制。廢話不多說,下面我們直接進(jìn)入主題。
    的頭像 發(fā)表于 08-05 15:32 ?225次閱讀
    詳解<b class='flag-5'>Linux</b><b class='flag-5'>中</b>的權(quán)限控制

    深入探究 MEMS LVCMOS 振蕩器 SiT1602 系列 52 種標(biāo)準(zhǔn)頻率

    深入探究 MEMS LVCMOS 振蕩器 SiT1602 系列(52 種標(biāo)準(zhǔn)頻率)
    的頭像 發(fā)表于 07-19 16:16 ?194次閱讀

    系統(tǒng)的latency是如何產(chǎn)生的

    能夠有效地管理多任務(wù)處理,確保系統(tǒng)對(duì)用戶操作的快速響應(yīng),并在資源緊張的情況下仍能保持穩(wěn)定和流暢的運(yùn)行。 本篇文檔旨在詳細(xì)探討Linux內(nèi)核的搶占機(jī)制,涵蓋其基本概念、實(shí)現(xiàn)細(xì)節(jié)、性能影響以及相關(guān)的調(diào)試方法。通過對(duì)搶占
    的頭像 發(fā)表于 06-04 09:18 ?410次閱讀
    系統(tǒng)<b class='flag-5'>中</b>的latency是如何產(chǎn)生的

    如何解決Linux系統(tǒng)的網(wǎng)絡(luò)連接問題?

    如何解決Linux系統(tǒng)的網(wǎng)絡(luò)連接問題? Linux系統(tǒng)的網(wǎng)絡(luò)連接問題是常見的技術(shù)難題之一,通常涉及在Linux上配置網(wǎng)絡(luò)接口、解決網(wǎng)絡(luò)故
    的頭像 發(fā)表于 01-12 15:17 ?625次閱讀

    Linux環(huán)境變量配置方法

    想必大家平時(shí)工作也會(huì)配置Linux的環(huán)境變量,但是可能也僅僅是為解決某些工具的運(yùn)行環(huán)境,對(duì)于Linux環(huán)境變量本身的配置學(xué)問還沒深入了解。今天浩道跟大家分享
    的頭像 發(fā)表于 01-04 09:51 ?414次閱讀

    深入了解Linuxvi命令的使用

    深入了解Linuxvi命令的使用 VI是一款在Linux系統(tǒng)中使用的文本編輯器,它是一款功能強(qiáng)大、靈活性高的編輯器。VI編輯器具有非常高效的命令行操作方式,并且在各個(gè)版本的
    的頭像 發(fā)表于 12-25 11:15 ?346次閱讀

    探究振弦采集儀在工程監(jiān)測(cè)的應(yīng)用

    振弦采集儀是一種專門用于測(cè)量結(jié)構(gòu)振動(dòng)的儀器,在工程監(jiān)測(cè)中有著廣泛的應(yīng)用。它通過采集振動(dòng)信號(hào),分析結(jié)構(gòu)的振動(dòng)特性,從而評(píng)估結(jié)構(gòu)的安全性能,指導(dǎo)工程設(shè)計(jì)和施工。本文將從振弦采集儀的基本原理、應(yīng)用場(chǎng)景和優(yōu)勢(shì)等方面,探究其在工程監(jiān)測(cè)的應(yīng)用。
    的頭像 發(fā)表于 12-12 13:19 ?544次閱讀
    <b class='flag-5'>探究</b>振弦采集儀在工程監(jiān)測(cè)<b class='flag-5'>中</b>的應(yīng)用

    《Android Runtime源碼解析》+深入體會(huì)第六章ART的執(zhí)行(4)

    、RISC-V等開源社區(qū),主要研究?jī)?nèi)容為Clang/LLVM、JVM等。 在深入閱讀《Android Runtime源碼解析》這本書之后,我對(duì)Android Runtime的內(nèi)部機(jī)制有了更深入的理解。這本書不僅
    發(fā)表于 11-17 01:33

    Linux 內(nèi)存管理總結(jié)

    、緩存、交換分區(qū)等。Linux內(nèi)存管理的目標(biāo)是最大限度地利用可用內(nèi)存,同時(shí)保證系統(tǒng)的穩(wěn)定和可靠性。 1.1 什么是內(nèi)存管理 內(nèi)存管理是計(jì)算機(jī)系統(tǒng)負(fù)責(zé)管理系統(tǒng)內(nèi)存資源的一種機(jī)制,主要包括內(nèi)存分配、內(nèi)存釋放、內(nèi)存映射和虛擬內(nèi)存管理
    的頭像 發(fā)表于 11-10 14:58 ?432次閱讀
    <b class='flag-5'>Linux</b> 內(nèi)存管理總結(jié)

    如何實(shí)現(xiàn)一套linux進(jìn)程間通信的機(jī)制

    我們知道linux的進(jìn)程的間通信的組件有管道,消息隊(duì)列,socket, 信號(hào)量,共享內(nèi)存等。但是我們?nèi)绻约簩?shí)現(xiàn)一套進(jìn)程間通信的機(jī)制的話,要怎么做?了解android 開發(fā)的可能會(huì)知道
    的頭像 發(fā)表于 11-10 14:56 ?546次閱讀
    如何實(shí)現(xiàn)一套<b class='flag-5'>linux</b>進(jìn)程間通信的<b class='flag-5'>機(jī)制</b>

    linux和windows的區(qū)別

    的,用戶無法獲取其源代碼,因此對(duì)其內(nèi)部機(jī)制的了解不夠。 操作系統(tǒng)權(quán)限:Linux鼓勵(lì)使用者去獲取更多的權(quán)限,因?yàn)樗鼪]有嚴(yán)格的權(quán)限控制機(jī)制。而Windows則有較為嚴(yán)格的權(quán)限控制機(jī)制,用
    的頭像 發(fā)表于 11-08 11:08 ?4918次閱讀

    RTT的消息同步機(jī)制是如何實(shí)現(xiàn)的?

    RTT的消息同步機(jī)制是如何實(shí)現(xiàn)的
    發(fā)表于 11-02 07:00