1. 程式人生 > >Linux核心除錯技術——jprobe使用與實現

Linux核心除錯技術——jprobe使用與實現

前一篇博文介紹了kprobes的原理與kprobe的使用與實現方式,本文介紹kprobes中的第二種探測技術jprobe,它基於kprobe實現,不能在函式的任意位置插入探測點,只能在函式的入口處探測,一般用於監測函式的入參值。本文首先通過一個簡單的示例介紹jprobe的使用方式,然後通過原始碼詳細分析jprobe的實現流程。

核心原始碼:Linux-4.1.x

實驗環境:Fedora25(x86_64)、樹莓派1b

1、jprobe使用例項

使用jprobe探測函式的入參值,需要編寫核心模組。同kprobe一樣,核心同樣提供了jprobe的例項程式jprobe_example.c(位於sample/kprobes目錄),該程式實現了探測do_fork函式入參的功能,使用者可以以它為模板來探測其他函式(當然不是說什麼函式都能探測的,限制同kprobe一樣,另外需要注意的是一個被探測函式只能註冊一個jprobe)。在分析jprobe_example.c之前先熟悉一下jprobe的基本結構與API介面。

1.1、jprobe結構體與API介紹

struct jprobe結構體定義如下:

/*
 * Special probe type that uses setjmp-longjmp type tricks to resume
 * execution at a specified entry with a matching prototype corresponding
 * to the probed function - a trick to enable arguments to become
 * accessible seamlessly by probe handling logic.
 * Note:
 * Because of the way compilers allocate stack space for local variables
 * etc upfront, regardless of sub-scopes within a function, this mirroring
 * principle currently works only for probes placed on function entry points.
 */
struct jprobe {
	struct kprobe kp;
	void *entry;	/* probe handling code to jump to */
};
該結構非常的簡單,僅包含了一個kprobe結構(因為它是基於kprobe實現的)和一個entry指標,它儲存的是探測點執行回撥函式的地址,當觸發呼叫被探測函式時,儲存到該指標的地址會作為目標地址跳轉執行(probe handling code to jump to),因此使用者指定的探測函式得以執行。

相關的API如下:

int register_jprobe(struct jprobe *jp)      //向核心註冊jprobe探測點  
void unregister_jprobe(struct jprobe *jp)   //解除安裝jprobe探測點  
int register_jprobes(struct jprobe **jps, int num)     //註冊探測函式向量,包含多個不同探測點  
void unregister_jprobes(struct jprobe **jps, int num)  //解除安裝探測函式向量,包含多個不同探測點  
int disable_jprobe(struct jprobe *jp)       //臨時暫停指定探測點的探測  
int enable_jprobe(struct jprobe *jp)        //恢復指定探測點的探測

1.2、示例jprobe_example分析與演示

同kprobe_example.c一樣,該示例程式仍以do_fork作為被探測函式進行探測。當建立程序時,探測函式會呼叫它打印出do_fork函式的入參值。下面詳細分析:

static struct jprobe my_jprobe = {
	.entry			= jdo_fork,
	.kp = {
		.symbol_name	= "do_fork",
	},
};

static int __init jprobe_init(void)
{
	int ret;

	ret = register_jprobe(&my_jprobe);
	if (ret < 0) {
		printk(KERN_INFO "register_jprobe failed, returned %d\n", ret);
		return -1;
	}
	printk(KERN_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);
	printk(KERN_INFO "jprobe at %p unregistered\n", my_jprobe.kp.addr);
}
程式定義了一個struct jprobe例項my_jprobe,指定被探測函式的名字是do_fork(可以修改它以達到探測其他函式的目的),然後探測回撥函式為jdo_fork。在模組的初始化函式中,呼叫register_jprobe函式向kprobe子系統註冊my_jprobe,這樣jprobe探測預設就啟用了,最後在exit函式中呼叫unregister_jprobe函式解除安裝。
/* Proxy routine having the same arguments as actual do_fork() routine */
static long jdo_fork(unsigned long clone_flags, unsigned long stack_start,
	      unsigned long stack_size, int __user *parent_tidptr,
	      int __user *child_tidptr)
{
	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;
}
jdo_fork函式也僅僅打印出了在呼叫do_fork函式時傳入的clone_flags、stack_start和stack_size這三個入參值,整個實現非常簡單直觀,但是有兩點需要注意:

1)探測回撥函式的入參必須同被探測函式的一致,否則無法達到探測函式入參的目的,例如此處的jdo_fork函式入參unsigned long clone_flags、unsigned long stack_start、unsigned long stack_size、int __user *parent_tidptr和int __user *child_tidptr同do_fork函式是完全一致的(注意返回值固定為long型別)。

2)在回撥函式執行完畢以後,必須呼叫jprobe_return函式(註釋中也有強調),否則執行流程就回不到正常的執行流程中了,這一點後文會詳細分析。

下面在x86_64環境下演示該程式的實際效果(環境配置請參考前一篇博文):

<6>[15817.544375] jprobe: clone_flags = 0x1200011, stack_start = 0x0 stack_size = 0x0
<6>[15817.551217] jprobe: clone_flags = 0x1200011, stack_start = 0x0 stack_size = 0x0
<6>[15817.905328] jprobe: clone_flags = 0x1200011, stack_start = 0x0 stack_size = 0x0
<6>[15822.684688] jprobe: clone_flags = 0x1200011, stack_start = 0x0 stack_size = 0x0
<6>[15822.704001] jprobe: clone_flags = 0x1200011, stack_start = 0x0 stack_size = 0x0

在載入jprobe_example.ko模組以後,在終端隨便敲幾個命令觸發程序建立,核心打印出以上message,可以看到do_fork的入參就被非常容易的獲取到了,其他函式的探測也類似,不再詳細描述。

2、jprobe實現分析

jpeobe的實現基於kprobe,因此這裡將在前一篇博文《Linux核心除錯技術——kprobe使用與實現》的基礎之上分析它的實現,述主要包括jprobe註冊流程和觸發探測流程,涉及kprobe的部分不再詳細描。

2.1、jprobe實現原理

利用kprobe,jprobe是一種特殊形式的kprobe,它有自己的pre_handler和break_handler回撥函式,其中pre_handler回撥函式負責儲存原始呼叫上下文併為呼叫使用者指定的探測函式jprobe->entry準備環境,然後跳轉到jprobe->entry執行(被探測函式的入參資訊在此得到輸出),接著再次觸發kprobe流程,在break_handler函式中恢復原始上下文,最後返回正常執行流程。

2.2、註冊一個jprobe例項

jprobe探測模組呼叫register_jprobe函式向核心註冊一個jprobe例項,程式碼路徑kernel/kprobes.c,其主要流程如下圖:


圖1 jpobe註冊流程

int register_jprobe(struct jprobe *jp)
{
	return register_jprobes(&jp, 1);
}
EXPORT_SYMBOL_GPL(register_jprobe);

register_jprobe函式只是register_jprobes的一個封裝,主要註冊功能由register_jprobes函式完成。

int register_jprobes(struct jprobe **jps, int num)
{
	struct jprobe *jp;
	int ret = 0, i;

	if (num <= 0)
		return -EINVAL;
	for (i = 0; i < num; i++) {
		unsigned long addr, offset;
		jp = jps[i];
		addr = arch_deref_entry_point(jp->entry);

		/* Verify probepoint is a function entry point */
		if (kallsyms_lookup_size_offset(addr, NULL, &offset) &&
		    offset == 0) {
			jp->kp.pre_handler = setjmp_pre_handler;
			jp->kp.break_handler = longjmp_break_handler;
			ret = register_kprobe(&jp->kp);
		} else
			ret = -EINVAL;

		if (ret < 0) {
			if (i > 0)
				unregister_jprobes(jps, i);
			break;
		}
	}
	return ret;
}
EXPORT_SYMBOL_GPL(register_jprobes);

函式是一個迴圈,對每個jprobe執行相同的註冊流程,首先從jp->entry中取出探測回撥函式的地址,對它進行驗證。kallsyms_lookup_size_offset函式的作用是從核心或者模組的符號表中找到addr地址所在的符號,找到後會通過offset值返回addr與符號起始的偏移,這偏移值必須為0,即必須為一個函式的入口。若條件符合,則設定kprobe的pre_handler和break_handler這兩個回撥函式setjmp_pre_handler和longjmp_break_handler,最後呼叫register_kprobe函式註冊kprobe。

可見jprobe的註冊流程非常的簡單,它的本質就是註冊一個kprobe,利用kprobe機制實現探測,只是探測回撥函式並非使用者自己定義,使用jprobe私有的而已。在註冊完成後,jprobe(kprobe)機制啟動,當函式呼叫流程執行到被探測函式時就會觸發jprobe(kprobe)探測。

最後需要注意的是,jprobe是不能在同一個被探測點註冊多個的,在kprobe的註冊流程register_kprobe->register_aggr_kprobe->add_new_kprobe中會有判斷:

	if (p->break_handler) {
		if (ap->break_handler)
			return -EEXIST;

2.3、觸發jprobe探測

基於kprobe機制,在執行到被探測函式後,會觸發CPU異常,按照kprobe的執行流程,由kprobe_handler函式呼叫到pre_handler回撥函式,即setjmp_pre_handler。該函式架構相關,它根據架構的不同進行一些棧或者暫存器相關的操作,儲存現場以備呼叫結束後恢復,隨後跳轉到使用者定的jprobe->entry處執行,在打印出使用者需要的資訊後,返回原有正常的流程繼續執行。主要流程如下圖:


圖2 jprobe觸發流程

2.3.1、arm架構實現

int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
	struct jprobe *jp = container_of(p, struct jprobe, kp);
	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
	long sp_addr = regs->ARM_sp;
	long cpsr;

	kcb->jprobe_saved_regs = *regs;
	memcpy(kcb->jprobes_stack, (void *)sp_addr, MIN_STACK_SIZE(sp_addr));
	regs->ARM_pc = (long)jp->entry;

	cpsr = regs->ARM_cpsr | PSR_I_BIT;
#ifdef CONFIG_THUMB2_KERNEL
	/* Set correct Thumb state in cpsr */
	if (regs->ARM_pc & 1)
		cpsr |= PSR_T_BIT;
	else
		cpsr &= ~PSR_T_BIT;
#endif
	regs->ARM_cpsr = cpsr;

	preempt_disable();
	return 1;
}
首先再次明確入參struct pt_regs *regs的含義是觸發CPU異常前所儲存的正常執行流上下文的暫存器值。函式首先獲取觸發的jprobe結構例項,並呼叫get_kprobe_ctlblk取得當前CPU的kprobe_ctlblk結構全域性變數,這個struct kprobe_ctlblk結構定義在kprobe分析中已經見過,不過jprobe使用到了其中定義的另兩個欄位:
/* per-cpu kprobe control block */
struct kprobe_ctlblk {
	unsigned int kprobe_status;
	struct prev_kprobe prev_kprobe;
	struct pt_regs jprobe_saved_regs;
	char jprobes_stack[MAX_STACK_SIZE];
};
其中jprobe_saved_regs用於儲存暫存器資訊,jprobes_stack則用於儲存棧資訊,它們用於在jprobe返回時恢復呼叫探測前的上下文,這一點從setjmp_pre_handler函式的前兩行就可以看出。先提個問題,為何kprobe不需要儲存原上下文資訊而jprobe需要?

函式接下來修改傳入的ARM_pc值為使用者指定的探測回撥函式地址,注意這個值本來在正常的kprobe流程中是要被設定為正常流程的下一條指令的(執行完kprobe流程後就會回到原流程繼續執行),這裡在kprobe的整個流程結束後就不會回到原流程執行了,而是會進入到使用者指定的探測函式執行。

函式然後修改入參的CPSR暫存器值,置位PSR_I_BIT,表示禁用中斷,最後禁止搶佔並返回1。回到kprobe_handler函式中看返回1後接下來kprobe就不會執行singlestep和呼叫post_handler回撥函數了,注意也不會呼叫reset_current_kprobe函式復位當前執行的kprobe為NULL:

			if (!p->pre_handler || !p->pre_handler(p, regs)) {
				kcb->kprobe_status = KPROBE_HIT_SS;
				singlestep(p, regs, kcb);
				if (p->post_handler) {
					kcb->kprobe_status = KPROBE_HIT_SSDONE;
					p->post_handler(p, regs, 0);
				}
				reset_current_kprobe();
			}
在kprobe_handler流程返回後,執行流程進入到了使用者指定的探測函式執行,對於前文中的jprobe_example程式來說就是jdo_fork函式。提第二個問題,被探測函式的入參值是如何獲取的?

從setjmp_pre_handler的實現可以看出,該函式僅僅修改了kprobe的返回地址,並沒有修改棧和其他的暫存器值,因此在CPU跳轉到jdo_fork執行時,它的暫存器和棧中的內容同原本呼叫do_fork函式時幾乎是一模一樣的(僅僅是禁用了中斷而已),因此不論是通過暫存器傳參還是通過壓棧的方式傳參,使用者在定義jdo_fork函式時只需要將函式入參定義的同do_fork一樣就可以輕輕鬆鬆的獲取到原有的入參值了。另外從這裡的實現可以看出另外一個資訊,jprobe的回撥執行上下文同原函式執行的上下文是一樣的,這點不同於kprobe,kprobe的回撥函式執行的上下文是在CPU異常的中斷上下文。

最後由於探測函式(jdo_fork)是在kprobe_handler流程執行完成後跳轉執行的,跳過了single_step流程,這也就說它不能利用原有kprobe的機制回到原始執行流程中去執行,需要另想他法,其實在setjmp_pre_handler函式中儲存的暫存器pt_regs就是用於這個目的的,也就解釋了前文中提出的第一個問題,接下來詳細分析。

回到探測函式jdo_fork中,使用者在獲取需要的資訊後,接下來進入現場恢復的流程,其中的關鍵部分就是jdo_fork函式最後呼叫的jprobe_return函式,它是由嵌入彙編實現的

void __kprobes jprobe_return(void)
{
	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();

	__asm__ __volatile__ (
		/*
		 * Setup an empty pt_regs. Fill SP and PC fields as
		 * they're needed by longjmp_break_handler.
		 *
		 * We allocate some slack between the original SP and start of
		 * our fabricated regs. To be precise we want to have worst case
		 * covered which is STMFD with all 16 regs so we allocate 2 *
		 * sizeof(struct_pt_regs)).
		 *
		 * This is to prevent any simulated instruction from writing
		 * over the regs when they are accessing the stack.
		 */
#ifdef CONFIG_THUMB2_KERNEL
		...
#else
		"sub    sp, %0, %1		\n\t"
#endif
		"ldr    r0, ="__stringify(JPROBE_MAGIC_ADDR)"\n\t"
		"str    %0, [sp, %2]		\n\t"
		"str    r0, [sp, %3]		\n\t"
		"mov    r0, sp			\n\t"
		"bl     kprobe_handler		\n\t"

		/*
		 * Return to the context saved by setjmp_pre_handler
		 * and restored by longjmp_break_handler.
		 */
#ifdef CONFIG_THUMB2_KERNEL
		...
#else
		"ldr	r0, [sp, %4]		\n\t"
		"msr	cpsr_cxsf, r0		\n\t"
		"ldmia	sp, {r0 - pc}		\n\t"
#endif
		:
		: "r" (kcb->jprobe_saved_regs.ARM_sp),
		  "I" (sizeof(struct pt_regs) * 2),
		  "J" (offsetof(struct pt_regs, ARM_sp)),
		  "J" (offsetof(struct pt_regs, ARM_pc)),
		  "J" (offsetof(struct pt_regs, ARM_cpsr)),
		  "J" (offsetof(struct pt_regs, ARM_lr))
		: "memory", "cc");
}
這裡模擬出了一個假的pt_regs結構體,僅僅填充了其中的sp和pc欄位(後文中的longjmp_break_handler函式需要),其中pc的值為JPROBE_MAGIC_ADDR,然後長跳轉到kprobe_handler執行,kprobe_handler函式判斷當前已經有kprobe正在運行了,因此進入以下呼叫流程:
	} else if (cur) {
		/* We probably hit a jprobe.  Call its break handler. */
		if (cur->break_handler && cur->break_handler(cur, regs)) {
			kcb->kprobe_status = KPROBE_HIT_SS;
			singlestep(cur, regs, kcb);
			if (cur->post_handler) {
				kcb->kprobe_status = KPROBE_HIT_SSDONE;
				cur->post_handler(cur, regs, 0);
			}
		}
		reset_current_kprobe();
首先呼叫kprobe的break_handler回撥函式,即longjmp_break_handler函式:
int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
{
	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
	long stack_addr = kcb->jprobe_saved_regs.ARM_sp;
	long orig_sp = regs->ARM_sp;
	struct jprobe *jp = container_of(p, struct jprobe, kp);

	if (regs->ARM_pc == JPROBE_MAGIC_ADDR) {
		if (orig_sp != stack_addr) {
			struct pt_regs *saved_regs =
				(struct pt_regs *)kcb->jprobe_saved_regs.ARM_sp;
			printk("current sp %lx does not match saved sp %lx\n",
			       orig_sp, stack_addr);
			printk("Saved registers for jprobe %p\n", jp);
			show_regs(saved_regs);
			printk("Current registers\n");
			show_regs(regs);
			BUG();
		}
		*regs = kcb->jprobe_saved_regs;
		memcpy((void *)stack_addr, kcb->jprobes_stack,
		       MIN_STACK_SIZE(stack_addr));
		preempt_enable_no_resched();
		return 1;
	}
	return 0;
}
這個函式很簡單,首先會判斷sp的值和儲存的sp值是都是一樣的,若不一樣則報BUG,否則恢復儲存在kprobe_ctlblk結構體中的暫存器值和棧,最後啟用核心搶佔,至此jprobe的處理流程全部完畢,接下來會回到kprobe的kprobe_handler中繼續完成本次kprobe,執行單步執行single_step和post_handler,最後回到原流程執行。由此可見,在使用者定義的探測函式末尾,必須要呼叫jprobe_return函式,否則程式碼的執行就“飛了”,再也回不到原有的流程中去了。

2.3.2、x86_64架構實現
int setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
	struct jprobe *jp = container_of(p, struct jprobe, kp);
	unsigned long addr;
	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();

	kcb->jprobe_saved_regs = *regs;
	kcb->jprobe_saved_sp = stack_addr(regs);
	addr = (unsigned long)(kcb->jprobe_saved_sp);

	/*
	 * As Linus pointed out, gcc assumes that the callee
	 * owns the argument space and could overwrite it, e.g.
	 * tailcall optimization. So, to be absolutely safe
	 * we also save and restore enough stack bytes to cover
	 * the argument area.
	 */
	memcpy(kcb->jprobes_stack, (kprobe_opcode_t *)addr,
	       MIN_STACK_SIZE(addr));
	regs->flags &= ~X86_EFLAGS_IF;
	trace_hardirqs_off();
	regs->ip = (unsigned long)(jp->entry);

	/*
	 * jprobes use jprobe_return() which skips the normal return
	 * path of the function, and this messes up the accounting of the
	 * function graph tracer to get messed up.
	 *
	 * Pause function graph tracing while performing the jprobe function.
	 */
	pause_graph_tracing();
	return 1;
}
NOKPROBE_SYMBOL(setjmp_pre_handler);
x86_64架構的實現整體同arm的大同小異,函式首先同樣是儲存現場,然後關閉中斷並設定IP暫存器的值為jp->entry,最後返回1,這樣在kprobe_int3_handler函式會跳過single_step。
			/*
			 * If we have no pre-handler or it returned 0, we
			 * continue with normal processing.  If we have a
			 * pre-handler and it returned non-zero, it prepped
			 * for calling the break_handler below on re-entry
			 * for jprobe processing, so get out doing nothing
			 * more here.
			 */
			if (!p->pre_handler || !p->pre_handler(p, regs))
				setup_singlestep(p, regs, kcb, 0);
			return 1;
於是在kprobe呼叫流程結束後跳轉到使用者的探測函式執行。在來看jprobe_return函式的實現:
void jprobe_return(void)
{
	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();

	asm volatile (
#ifdef CONFIG_X86_64
			"       xchg   %%rbx,%%rsp	\n"
#else
			"       xchgl   %%ebx,%%esp	\n"
#endif
			"       int3			\n"
			"       .globl jprobe_return_end\n"
			"       jprobe_return_end:	\n"
			"       nop			\n"::"b"
			(kcb->jprobe_saved_sp):"memory");
}
同arm的實現不同,這裡使用int3指令再次觸發CPU3異常,並且異常出的地址已經不再是BREAKPOINT_INSTRUCTION了,所以會進入到kprobe_int3_handler的以下流程執行:
	} else if (kprobe_running()) {
		p = __this_cpu_read(current_kprobe);
		if (p->break_handler && p->break_handler(p, regs)) {
			if (!skip_singlestep(p, regs, kcb))
				setup_singlestep(p, regs, kcb, 0);
			return 1;
		}
同樣是呼叫kprobe的break_handler回撥函式執行,也即是longjmp_break_handler函式。
int longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
{
	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
	u8 *addr = (u8 *) (regs->ip - 1);
	struct jprobe *jp = container_of(p, struct jprobe, kp);
	void *saved_sp = kcb->jprobe_saved_sp;

	if ((addr > (u8 *) jprobe_return) &&
	    (addr < (u8 *) jprobe_return_end)) {
		if (stack_addr(regs) != saved_sp) {
			struct pt_regs *saved_regs = &kcb->jprobe_saved_regs;
			printk(KERN_ERR
			       "current sp %p does not match saved sp %p\n",
			       stack_addr(regs), saved_sp);
			printk(KERN_ERR "Saved registers for jprobe %p\n", jp);
			show_regs(saved_regs);
			printk(KERN_ERR "Current registers\n");
			show_regs(regs);
			BUG();
		}
		/* It's OK to start function graph tracing again */
		unpause_graph_tracing();
		*regs = kcb->jprobe_saved_regs;
		memcpy(saved_sp, kcb->jprobes_stack, MIN_STACK_SIZE(saved_sp));
		preempt_enable_no_resched();
		return 1;
	}
	return 0;
}
longjmp_break_handler函式同arm實現基本一致,恢復程式碼的原有上下文,開啟核心搶佔,最後交回給kprobe繼續執行後面的single_step和恢復流程。不過值的注意的是第一條判斷語句,由於本次int3異常是在jprobe_return函式中觸發的,因此longjmp_break_handler函式的struct pt_regs *regs入參值是在呼叫jprobe_return函式環境上下文中的暫存器值,因此addr一定是在jprobe_return函式的地址範圍內,所以以此判斷本次呼叫的有效性,防止誤入。

3、總結

jprobe探測技術基於kprobe實現,是kprobes三種探測技術中的第二種,核心開發人員可以用它來探測核心函式的呼叫以及呼叫時的入參值,使用非常方便。本文介紹了jprobe探測工具的使用方式及其原理,並通過原始碼分析了arm架構和x86_64架構下它的實現方式。下一篇博文將介紹kprobes中的最後一種用來探測函式返回值的kretprobe探測技術。

相關推薦

Linux核心除錯技術——jprobe使用實現

前一篇博文介紹了kprobes的原理與kprobe的使用與實現方式,本文介紹kprobes中的第二種探測技術jprobe,它基於kprobe實現,不能在函式的任意位置插入探測點,只能在函式的入口處探測,一般用於監測函式的入參值。本文首先通過一個簡單的示例介紹jprobe的使

Linux核心除錯技術——kretprobe使用實現

前兩篇博文介紹了kprobes探測技術中kprobe和jprobe的使用與實現。本文介紹kprobes中的最後一種探測技術kretprobe,它同樣基於kprobe實現,可用於探測函式的返回值以及計算函式執行的耗時。本文首先通過一個簡單的示例程式介紹kretprobe的使用

嵌入式Linux開發——(十六)Linux核心除錯技術

1、核心列印函式printk     ①printk函式與printf函式用法格式完全相同     ②它所列印的字串頭部可以加入“<n>”樣式字元,n=0---7表示這條資訊的記錄  級別     ③對於p

linux 核心除錯技術

核心開發比使用者空間開發更難的一個因素就是核心除錯艱難。核心錯誤往往會導致系統宕機,很難保留出錯時的現場。除錯核心的關鍵在於你的對核心的深刻理解。 一  除錯前的準備 在除錯一個bug之前,我們所要做的準備工作有: 有一個被確認的bug。 包含這個bug的核心版本號,需要分析出這個b

Linux核心除錯技術

Linux除錯技術介紹 對於任何編寫核心程式碼的人來說,最吸引他們注意的問題之一就是如何完成除錯。由於核心是一個不與某個程序相關的功能集,其程式碼不能很輕鬆地放在偵錯程式中執行,而且也不能跟蹤。 本章介紹你可以用來監視核心程式碼和跟蹤錯誤的技術。 用列印資訊除錯 最一般的除錯技術就是監視,就是在應用內部合適的

Linux核心模組驅動載入dmesg除錯

  因為近期用到了Linux核心的相關知識,下面隨筆將給出核心模組的編寫記錄,供大家參考。 1、執行環境   Ubuntu 版本:20.04   Linux核心版本:5.4.0-42-generic   gcc版本:gcc version 9.3.0   驅動和一般應用程式的執行方式很大不同 2、核

分頁技術原理實現之分頁的意義及方法(一)

轉載自https://www.jb51.net/article/86326.htm。 什麼是分頁技術  分頁,是一種將所有資料分段展示給使用者的技術.使用者每次看到的不是全部資料,而是其中的一部分,如果在其中沒有找到自習自己想要的內容,使用者可以通過制定頁碼或是翻頁的方式轉換可見內容,

linux核心除錯技巧之一 dump_stack【轉】

在核心中程式碼呼叫過程難以跟蹤,上下文關係複雜,確實讓人頭痛 呼叫dump_stack()就會列印當前cpu的堆疊的呼叫函數了。 如此,一目瞭然的就能看到當前上下文環境,呼叫關係了 假設: 遇到uvc_probe_video這麼一個函式,不知道它最終是被誰呼叫到的,根據linux裝置模型,初步推測,p

linux核心版本號檢視含義

檢視核心版本 uname -r 或 cat /proc/version 檢視distribution版本 cat /etc/issue 或 cat /etc/redhat-release(cat /etc/centos-release) 版本號含義 linux核心

HTTP Live Streaming直播(iOS直播)技術分析實現

   不經意間發現,大半年沒寫部落格了,自覺汗顏。實則2012後半年(2018年注:這是我以前寫的文章,不要奇怪時間了),家中的事一樣接著一樣發生,實在是沒有時間。快過年了,總算忙裡偷閒,把最近的一些技術成果,總結成了文章,與大家分享。   前些日子,也是專案需要,花了一

amlogic平臺android 系統linux核心中新增i2c裝置實現i2c的讀寫

上一篇,我介紹瞭如何在uboot中新增i2c裝置,以及移植i2c的讀寫介面。簡單來說uboot階段使用i2c裝置和平臺關聯性比較大,但不同平臺套路是差不多的。你可以將uboot階段看作是引導androi

linux核心除錯環境搭建

版本linux4.17 ubuntu18.04先給系統至少80G記憶體1。編譯核心先配置檔案make mrpropermake menuconfig我這裡需要的依賴有 sudo apt install make cmake gcc g++ clang sudo apt-get install libnc

Linux核心除錯的方式以及工具集錦

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作 因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的除錯工具以供收錄, 鄙人在此謝

恩智浦杯(飛思卡爾)全國大學生智慧車競賽攝像頭簡單的影象失真矯正技術原理實現(透視變換)

  先說一些廢話(沒耐心看可直接看分割線下面的內容):   博主是去年參加了十二屆的恩智浦杯(飛思卡爾)全國大學生智慧車競賽光電競速組,我們隊當時獲得的是區賽預賽第三、決賽第四的成績,我們區賽的光電競速組可以選拔五組進入全國總決賽,但因為我們學校另一個隊獲得了區賽決賽第三,

linux核心除錯方法

核心開發比使用者空間開發更難的一個因素就是核心除錯艱難。核心錯誤往往會導致系統宕機,很難保留出錯時的現場。除錯核心的關鍵在於你的對核心的深刻理解。 一  除錯前的準備 在除錯一個bug之前,我們所要做的準備工作有: 有一個被確認的bug。 包含這個bug的核心

基於Nginx的負載均衡技術研究實現

一、下載安裝及使用 1、版本 日期 版本 CHANGES NGINX NGINX/WINDOWS 2018.6.27 MainLine(開發版) C

linux核心分析———SLAB原理及實現

//填充CPU快取記憶體 static void *cache_alloc_refill(structkmem_cache *cachep, gfp_t flags) { int batchcount; struct kmem_list3 *l3; struct arra

Linux核心除錯方法總結

核心開發比使用者空間開發更難的一個因素就是核心除錯艱難。核心錯誤往往會導致系統宕機,很難保留出錯時的現場。除錯核心的關鍵在於你的對核心的深刻理解。  一  除錯前的準備 在除錯一個bug之前,我們所要做的準備工作有:  有一個被確認的bug。 包含這

嵌入式Linux核心配置、裁剪編譯淺析(ARM版)

/*====================*/ 9、Device Drivers  --->  9.1、Generic Driver Options  --->  9.1.1、()  path to uevent helper  9.1.2、[ ] Maintain a devtmpfs fil

linux核心除錯技巧四:gdb除錯+vmlinux

vmlinux是個elf檔案,它的符號表中包含了所有核心符號。 注意linux中很多檔案是沒有後綴的,比如我見到的這個elf檔案的檔名是“vmlinux-3.10.62”,沒有後綴。 既然是elf檔案