kprobes在arch_init_kprobes中調用register_undef_hook將kprobes_arm_break_hook註冊到undef_hook上。
register_undef_hook(&kprobes_arm_break_hook);
當使用register_kprobe把kprobe結構體(含要監控的指令以及在該指令之前或之後要做的動作)註冊時,用一個未定義指令替換要檢測的指令,當執行到該未定義指令時,會執行do_undefinstr,進而執行call_undef_hook,kprobes_arm_break_hook->fn會被依次,進而執行kprobe_handler。
static struct undef_hook kprobes_arm_break_hook = {
.instr_mask
= 0x0fffffff,
.instr_val
= KPROBE_ARM_BREAKPOINT_INSTRUCTION,
.cpsr_mask
= MODE_MASK,
.cpsr_val
= SVC_MODE,
.fn = kprobe_trap_handler,
};
static int __kprobes kprobe_trap_handler(struct pt_regs *regs, unsigned int instr)
{
unsigned long flags;
local_irq_save(flags);
kprobe_handler(regs);
local_irq_restore(flags);
return 0;
}
1 拷貝探測的code , 插入特殊指令(ARM是插入未定義指令)
2 CPU運行到未定義指令,會產生trap, 進入ISR,並保存當前寄出去的狀態
通過LINUX的通知機制,會執行“pre_handler”(前提是你已經註冊過了)
3 進入單步模式,運行你備份出來的代碼
(此代碼運行的是拷貝出來的,防止別的CPU也恰巧運行到此位置)
4 單步模式後,運行“post_handler”,恢復正常模式,接着運行下面的指令。
1. struct kprobe
struct kprobe {
structhlist_node hlist;
/*list of kprobes for multi-handler support */
structlist_head list;
/*countthe number of times this probe was temporarily disarmed */
unsignedlong nmissed;
/*location of the probe point */
kprobe_opcode_t*addr;
/*Allow user to indicate symbol name of the probe point */
constchar *symbol_name;
/*Offset into the symbol */
unsignedint offset;
/*Called before addr is executed. */
kprobe_pre_handler_tpre_handler;
/*Called after addr is executed, unless... */
kprobe_post_handler_t post_handler;
/*
* ... called if executing addr causes a fault(eg. page fault).
* Return 1 if it handled fault, otherwisekernel will see it.
*/
kprobe_fault_handler_t fault_handler;
/*
* ... called if breakpoint trap occurs inprobe handler.
* Return 1 if it handled break, otherwisekernel will see it.
*/
kprobe_break_handler_tbreak_handler;
/*Saved opcode (which has been replaced with breakpoint) */
kprobe_opcode_topcode;
/*copy of the original instruction */
structarch_specific_insn ainsn;
/*
* Indicates various status flags.
* Protected by kprobe_mutex after this kprobeis registered.
*/
u32flags;
};
2. ARM架構kprobe應用及實現分析(1.0 簡單示例)
http://blog.csdn.net/liyongming1982/article/details/16362147
網絡對krpobe的實現機制及擴展都不是特別詳細
由於工作需要及個人愛好,正好有這個機會好好學習此模塊及應用到實際中
並將整個應用擴展及當時的分析情況,詳細記錄下來,希望對感興趣的人有些許幫助
最開始還是先給個具體的栗子:
參考: kernel/samples 下面有不少的例子
當運行到監控的某個點的時候,會調用此函數
初始話,註冊一個kprobe,可以傳入函數名或者在內存的絕對地址(system.map)
註銷kprobe探測點
system.map
//現在我們只關心 do_fork testAddadd5 這兩個函數
它們在內存的地址如下:
c0052368 T testAddadd5
c00523c0 T do_fork
static struct kprobe kp = {
//.symbol_name = "do_fork",
.symbol_name = "testAddadd5",
};
register_kprobe(&kp)
printk(KERN_INFO " Planted kprobe at %p\n", kp.addr);
insmod
<6>[ 9749.442971] (0)[5251:insmod] Planted kprobe at c0052368
發現這裏打印的地址與system.map是一致的。
3. ARM架構kprobe應用及實現分析(2.0 register_kprobe error38)
最開始 register_kprobe 的時候,返回錯誤,一直註冊不成功,且返回錯誤號爲38
最後發現是一些kernel編譯的配置沒有打開導致的.
所以當你編譯kernel之前請確保下面選項是打開支持的:
general setup
--> kprobes
CONFIG_OPTPROBES=y
CONFIG_PREEMPT=y
CONFIG_OPTPROBES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODULES=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO=yAnd one more config flag I needed specific to my platform:
你可以在system.map 文件找查找 是否有register_kprobe 函數,來確定你的編譯是否正確。
4. ARM架構kprobe應用及實現分析(3.0 被探測函數說明)
在此係列中都是探測 testAddadd5 ,作爲分析入口,
我在do_fork 函數的最後的位置調用了testAddadd5 ,具體模樣如下:
此函數,很方便調試模擬,隨便調用個shell 命令都會調用此函數
5. ARM架構kprobe應用及實現分析(5.0打印寄存器的值)
kp.pre_handler = handler_pre;
在此函數中打印寄存器的值,才能對我們分析當時的情況有幫助。(如查看調用函數的參數值等)
下面是生產一個寄存器對應的索引及寄存器的名字(用途),具體可以參考ARM的相關資料
一共18個寄存器
struct pt_regs {
long uregs[18];
};
下面很多地方也用到了此數組的生成方法(perror等),我也班門弄斧下:)
dump出所有寄存器的值:
最後handler_pre回調的時候,調用dump_arm_regs 到處所有寄存器的值,以便分析之用
ARM_pc == 0xc0052368
且system.map中: c0052368 T testAddadd5
兩者相等與設想的一致
且 c0052538: ebffff8a bl c0052368 <testAddadd5>
調用BL之後LR寄存器應該保存的是下一條指令的地址即:c005253c = c0052538 + 4
testAddadd5 14 : 0xc005253c : -1073404612 : ARM_lr
上面兩者也一致,證明這個時候導出的寄存器的值是可信的
R0 R1 R2 R3 保存參數:testAddadd5(0x11,0x22,0x33,mytestbuf
其它參數保存在堆棧中
好了,導出寄存器值就介紹到這裏
6. ARM架構kprobe應用及實現分析(6.0導出堆棧的值)
上篇講過了導出寄存器的值
但是當函數參數多餘4個的話(R0 R1 R2 R3 ),其他的值會保存在堆棧中,所以必須導出SP附近的值才能查看其它參數的值
此函數實現如下:
第一個參數爲SP的值,第二個是一SP爲中心要打印周圍棧的數據
實際調用(regs->uregs[13] 即爲stack pointer):
當探測到實際printk輸出如下:
//導出當時堆棧的值
<4>[ 9749.267927]-(0)[186:adbd] addr:0xdbe8ff80 --- 0xdbe8ffa4 0x00000000 0xdbe8e000 0xc000e0a4
<4>[ 9749.269044]-(0)[186:adbd] addr:0xdbe8ff70 --- 0x00000002 0x00043d34 0x00042ff4 0x00000035
<4>[ 9749.270161]-(0)[186:adbd] addr:0xdbe8ff60 --- 0x00000035 0x80045430 0x00000000 0xc8131300
<4>[ 9749.271278]-(0)[186:adbd] addr:0xdbe8ff50 --- 0xdbe8ff7c 0x00000000 0x00000001 0x00000000
<4>[ 9749.272395]-(0)[186:adbd] addr:0xdbe8ff40 --- 0x00000001 0xc063bd50 0x00000088 0x00000055
<4>[ 9749.273512]-(0)[186:adbd] addr:0xdbe8ff30 $$$ 0x00000044 0xc00872a4 0xc00524fc 0xdbe8ff30 //這裏是當時堆棧的中心
<4>[ 9749.274629]-(0)[186:adbd] addr:0xdbe8ff20 --- 0xdbe8ff8c 0x00000000 0xdbe8e000 0xdc38f000
<4>[ 9749.275746]-(0)[186:adbd] addr:0xdbe8ff10 --- 0x00001482 0x00000000 0xdbe8e000 0xc4b6b000
<4>[ 9749.276864]-(0)[186:adbd] addr:0xdbe8ff00 --- 0xc06309f4 0x60000013 0xdbe8ff1c 0xc063bd98
<4>[ 9749.277981]-(0)[186:adbd] addr:0xdbe8fef0 --- 0xc00873c8 0xffffffff 0x60000013 0xc0052368
testAddadd5(0x11,0x22,0x33,mytestbuf,0x44,0x55,0x88);
可以看出上面printk輸出紅色部分與第5,6,7的傳入的參數是一致的。
說明導出的stack的值是可信的。
7. ARM架構kprobe應用及實現分析(7.0自動顯示參數的值)
通過前面的介紹
知道參數在寄存器及堆棧的位置,我們就有可能顯示參數的值
jprobe也可以顯示參數的值,但是其有缺點:不能探測函數時加上偏移量
具體上下文請參考: ARM架構kprobe應用及實現分析(3.0 被探測函數說明)
導出參數的函數:
使用情形:
kernel log 輸出如下:
func paras maybe : (0x00000011,0x00000022,0x00000033,0xc077c670,0x00000044,0x00000055)
與我們實際傳入的一致
8. ARM架構kprobe應用及實現分析(8.0 register_kprobe實現)
1.
參考: kernel/Documentation/kprobes.txt 有如下一段話:
3. Specify either the kprobe "symbol_name" OR the "addr". If both are
specified, kprobe registration will fail with -EINVAL.
大概查找形式如下圖:(mm slab 等地方都用到了此算法)
唯一的缺點就是當HASH值比較大的時候,會佔用比較多的內存
太小的話,算法的時間複雜度又退化成list
9. ARM架構kprobe應用及實現分析(9.0 arch_prepare_kprobe平臺相關注冊)
1.
10. ARM架構kprobe應用及實現分析(10 trap中斷註冊及回調)
首先可以看下探測點檢測到非法指令時候,產生中斷的dump_stack:
symbol<c0011df0>] (dump_backtrace+0x0/0x10c) from [<c0630370>] (dump_stack+0x18/0x1c)
symbol<c0630358>] (dump_stack+0x0/0x1c) from [<bf00c208>] (handler_pre+0x144/0x19c [kk])
symbol<bf00c0c4>] (handler_pre+0x0/0x19c [kk]) from [<c063d174>] (kprobe_handler+0x194/0x234)
symbol<c063cfe0>] (kprobe_handler+0x0/0x234) from [<c063d23c>] (kprobe_trap_handler+0x28/0x54)
symbol<c063d214>] (kprobe_trap_handler+0x0/0x54) from [<c00082ac>] (do_undefinstr+0x118/0x1b0)
symbol<c0008194>] (do_undefinstr+0x0/0x1b0) from [<c063ca68>] (__und_svc+0x48/0x60)
symbol<c00523e0>] (do_fork+0x0/0x464) from [<c0011aec>] (sys_clone+0x34/0x3c)
symbol<c0011ab8>] (sys_clone+0x0/0x3c) from [<c000df20>] (ret_fast_syscall+0x0/0x30)
11. ARM架構kprobe應用及實現分析(11原理)
1 拷貝探測的code , 插入特殊指令(ARM是插入未定義指令)
2 CPU運行到未定義指令,會產生trap, 進入ISR,並保存當前寄出去的狀態
通過LINUX的通知機制,會執行“pre_handler”(前提是你已經註冊過了)
3 進入單步模式,運行你備份出來的代碼
(此代碼運行的是拷貝出來的,防止別的CPU也恰巧運行到此位置)
4 單步模式後,運行“post_handler”,恢復正常模式,接着運行下面的指令。
參考: kprobes.txt
How Does a Kprobe Work?
When a kprobe is registered, Kprobes makes a copy of the probed
instruction and replaces the first byte(s) of the probed instruction
with a breakpoint instruction (e.g., int3 on i386 and x86_64).
When a CPU hits the breakpoint instruction, a trap occurs, the CPU's
registers are saved, and control passes to Kprobes via the
notifier_call_chain mechanism. Kprobes executes the "pre_handler"
associated with the kprobe, passing the handler the addresses of the
kprobe struct and the saved registers.
Next, Kprobes single-steps its copy of the probed instruction.
(It would be simpler to single-step the actual instruction in place,
but then Kprobes would have to temporarily remove the breakpoint
instruction. This would open a small time window when another CPU
could sail right past the probepoint.)
After the instruction is single-stepped, Kprobes executes the
"post_handler," if any, that is associated with the kprobe.
Execution then continues with the instruction following the probepoint.
1 拷貝探測的code , 插入特殊指令(ARM是插入未定義指令)
2 CPU運行到未定義指令,會產生trap, 進入ISR,並保存當前寄出去的狀態
通過LINUX的通知機制,會執行“pre_handler”(前提是你已經註冊過了)
3 進入單步模式,運行你備份出來的代碼
(此代碼運行的是拷貝出來的,防止別的CPU也恰巧運行到此位置)
4 單步模式後,運行“post_handler”,恢復正常模式,接着運行下面的指令。
參考: kprobes.txt
How Does a Kprobe Work?
When a kprobe is registered, Kprobes makes a copy of the probed
instruction and replaces the first byte(s) of the probed instruction
with a breakpoint instruction (e.g., int3 on i386 and x86_64).
When a CPU hits the breakpoint instruction, a trap occurs, the CPU's
registers are saved, and control passes to Kprobes via the
notifier_call_chain mechanism. Kprobes executes the "pre_handler"
associated with the kprobe, passing the handler the addresses of the
kprobe struct and the saved registers.
Next, Kprobes single-steps its copy of the probed instruction.
(It would be simpler to single-step the actual instruction in place,
but then Kprobes would have to temporarily remove the breakpoint
instruction. This would open a small time window when another CPU
could sail right past the probepoint.)
After the instruction is single-stepped, Kprobes executes the
"post_handler," if any, that is associated with the kprobe.
Execution then continues with the instruction following the probepoint.