歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

利用Kprobe探測內核中的變量

今天遇到一個問題,需要探測內核中buffer cache block的大小。我想到了Kprobe這個神奇的工具,並且很好的探測到了內核中的變量值,非常的方便,在此分享一下。

采用dd等工具寫設備的時候,是需要經過塊設備層的buffer cache,當請求塊大小小於buffer cache的block_size時,Linux的策略是首先需要從磁盤load數據至buffer cache,然後再將新寫入的“局部數據”寫入buffer cache。這一步驟完成之後,會將整個buffer cache標識成dirty,掛載到設備所屬的radix tree上,然後定時喚醒後台writeback線程刷新dirty block至磁盤。今天對linux-3.2和linux-2.6.23的順序寫進行了對比測試,發現請求大小在512至2048之間時,Linux-3.2的性能居然比Linux-2.6.23還差。測試後得到的性能特征似乎與buffer cache的塊大小有關系,因此,我采用Kprobe對兩個版本的塊大小進行了探測驗證。

為了探測這個值,首先需要找一個合適的探測點,根據代碼分析的結果,我選擇在__block_write_begin函數中調用create_empty_buffers函數時的機會點,采用Kprobe插入一段代碼,打印buffer cache block_size的值。探測點位置的源代碼如下所示:

int __block_write_begin(struct page *page, loff_t pos, unsigned len,  
        get_block_t *get_block)  
{  
    。。。  
     
    blocksize = 1 << inode->i_blkbits;  
    if (!page_has_buffers(page))  
        create_empty_buffers(page, blocksize, 0);  
    head = page_buffers(page);  
     
    bbits = inode->i_blkbits;  
    block = (sector_t)page->index << (PAGE_CACHE_SHIFT - bbits);  
    。。。  
}

通過上面函數,我們知道blocksize就是buffer cache block塊大小,因此,我們可以截獲create_empty_buffers函數之後,打印傳入的第二個參數就可以得到buffer cache塊大小值了。截獲create_empty_buffers函數很簡單,通過kallsyms_lookup_name函數或者/proc/kallsyms就可以得到截獲函數對應的內存地址。關鍵的問題在於截獲這個函數之後,我們如果得到他的第二個參數,這就關系到函數的參數傳遞問題了。

在X86_64平台上,Linux的參數傳遞通過如下9個寄存器完成,分別為:RDI,RSI,RDX,RCX,RAX,R8,R9,R10,R11。在pre_handler函數中,我們可以得到寄存器組變量,通過寄存器組變量我們可以通過RSI寄存器得到create_empty_buffers函數傳入的第二個參數值。對於Linux-2.6.23版本,函數調用過程中寄存器在棧中布局定義如下:

struct pt_regs {  
    unsigned long r15;  
    unsigned long r14;  
    unsigned long r13;  
    unsigned long r12;  
    unsigned long rbp;  
    unsigned long rbx;  
/* arguments: non interrupts/non tracing syscalls only save upto here*/
    unsigned long r11;  
    unsigned long r10;  
    unsigned long r9;  
    unsigned long r8;  
    unsigned long rax;  
    unsigned long rcx;  
    unsigned long rdx;  
    unsigned long rsi;  
    unsigned long rdi;  
    unsigned long orig_rax;  
/* end of arguments */
/* cpu exception frame or undefined */
    unsigned long rip;  
    unsigned long cs;  
    unsigned long eflags;  
    unsigned long rsp;  
    unsigned long ss;  
/* top of stack page */
};

對於Linux-3.2版本,寄存器的組織結構是相同的,但是名字定義有所差別,新版本的寄存器定義如下:

struct pt_regs {  
    unsigned long r15;  
    unsigned long r14;  
    unsigned long r13;  
    unsigned long r12;  
    unsigned long bp;  
    unsigned long bx;  
/* arguments: non interrupts/non tracing syscalls only save up to here*/
    unsigned long r11;  
    unsigned long r10;  
    unsigned long r9;  
    unsigned long r8;  
    unsigned long ax;  
    unsigned long cx;  
    unsigned long dx;  
    unsigned long si;  
    unsigned long di;  
    unsigned long orig_ax;  
/* end of arguments */
/* cpu exception frame or undefined */
    unsigned long ip;  
    unsigned long cs;  
    unsigned long flags;  
    unsigned long sp;  
    unsigned long ss;  
/* top of stack page */
};

知道如何訪問截獲函數的輸入參數之後,probe程序就可以很容易編寫了,我的實驗程序如下所示,僅供參考。

/*  
 * kprobe_jiffies.c  
 */
     
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/string.h> 
#include <linux/init.h> 
#include <linux/kprobes.h> 
#include <linux/kallsyms.h> 
#include "asm/ptrace.h"  
#include "asm/current.h"  
#include "linux/utsname.h"  
     
/* global probe object */
struct kprobe probe;  
     
/*  
 * enter the probe pointer  
 */
static int pre_probe(struct kprobe *probe, struct pt_regs *regs)  
{  
    printk("block_size = %d.\n", regs->si);  
    return 0;  
}  
/*  
 * exit the probe pointer  
 */
static void post_probe(struct kprobe *probe, struct pt_regs *regs, unsigned long flags)  
{}  
     

static int __init kprobe_ init(void)  
{  
    probe.pre_handler = pre_probe;  
    probe.post_handler = post_probe;  
     
    probe.addr = (kprobe_opcode_t *) kallsyms_lookup_name("create_empty_buffers");  
    if (probe.addr == NULL) {  
        return 1;  
    }  
     
    register_kprobe(&probe);  
    printk("register probe driver.\n");  
    return 0;  
}  
     
static void __exit kprobe_exit(void)  
{  
    unregister_kprobe(&probe);  
    printk("unregister probe driver.\n");  
    return;  
}  
     
module_init(kprobe_ init);  
module_exit(kprobe_ exit);  
     
MODULE_AUTHOR("xxx");  
MODULE_DESCRIPTION("kernel probe driver");  
MODULE_LICENSE("GPL");

Kprobe的確是個非常不錯的調試工具,我們可以不斷地發掘他的功能,從而更好地調試、探測內核代碼。

Copyright © Linux教程網 All Rights Reserved