Linux kprobe调试技术使用
kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。
利用kprobe技术,可以在内核绝大多数函数中动态插入探测点,收集调试状态所需信息而基本不影响原有执行流程。
kprobe提供三种探测手段:kprobe、jprobe和kretprobe,其中jprobe和kretprobe基于kprobe实现,分别应用于不同探测场景中。
可以通过两种方式使用kprobe:第一种是编写内核模块,向内核注册探测点,探测函数根据需要自行定制,但是使用不方便;
第二种是使用kprobes in ftrace,这种方式结合kprobe和ftrace,可以通过kprobe来优化ftrace跟踪函数。
1. kprobe技术背景
如果需要知道内核函数是否被调用、被调用上下文、入参以及返回值,比较简单的方法是加printk,但是效率低。
利用kprobe技术,用户可以自定义自己的回调函数,可以再几乎所有的函数中动态插入探测点。
当内核执行流程执行到指定的探测函数时,会调用该回调函数,用户即可收集所需的信息了,同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息,不再需要继续探测,则同样可以动态的移除探测点。
kprobes技术包括的3种探测手段分别时kprobe、jprobe和kretprobe。
首先kprobe是最基本的探测方式,是实现后两种的基础,它可以在任意的位置放置探测点(就连函数内部的某条指令处也可以),它提供了探测点的调用前、调用后和内存访问出错3种回调方式,分别是pre_handler、post_handler和fault_handler,其中pre_handler函数将在被探测指令被执行前回调,post_handler会在被探测指令执行完毕后回调(注意不是被探测函数),fault_handler会在内存访问出错时被调用;jprobe基于kprobe实现,它用于获取被探测函数的入参值;最后kretprobe从名字种就可以看出其用途了,它同样基于kprobe实现,用于获取被探测函数的返回值。
1.1 kprobes的特点与使用限制
1、kprobes允许在同一个被被探测位置注册多个kprobe,但是目前jprobe却不可以;同时也不允许以其他的jprobe回掉函数和kprobe的post_handler回调函数作为被探测点。
2、一般情况下,可以探测内核中的任何函数,包括中断处理函数。不过在kernel/kprobes.c和arch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数是不允许被探测的,另外还有do_page_fault和notifier_call_chain;
3、如果以一个内联函数为探测点,则kprobes可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数,因此可能无法达到用户预期的探测效果;
4、一个探测点的回调函数可能会修改被探测函数运行的上下文,例如通过修改内核的数据结构或者保存与struct pt_regs结构体中的触发探测之前寄存器信息。因此kprobes可以被用来安装bug修复代码或者注入故障测试代码;
5、kprobes会避免在处理探测点函数时再次调用另一个探测点的回调函数,例如在printk()函数上注册了探测点,则在它的回调函数中可能再次调用printk函数,此时将不再触发printk探测点的回调,仅仅时增加了kprobe结构体中nmissed字段的数值;
6、在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存;
7、kprobes回调函数的运行期间是关闭内核抢占的,同时也可能在关闭中断的情况下执行,具体要视CPU架构而定。因此不论在何种情况下,在回调函数中不要调用会放弃CPU的函数(如信号量、mutex锁等);
8、kretprobe通过替换返回地址为预定义的trampoline的地址来实现,因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址;
9、如果一个函数的调用此处和返回次数不相等,则在类似这样的函数上注册kretprobe将可能不会达到预期的效果,例如do_exit()函数会存在问题,而do_execve()函数和do_fork()函数不会;
10、如果当在进入和退出一个函数时,CPU运行在非当前任务所有的栈上,那么往该函数上注册kretprobe可能会导致不可预料的后果,因此,kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe,将直接返回-EINVAL。
1.2 kprobe原理
kprobe工作具体流程见下图:
、当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86_64是int3,arm是设置一个未定义指令(目前的x86_64架构支持一种跳转优化方案Jump Optimization,内核需开启CONFIG_OPTPROBES选项,该种方案使用跳转指令来代替断点指令); 、当CPU流程执行到探测点的断点指令时,就触发了一个trap,在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数,该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数,kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息; 、随后kprobe单步执行前面所拷贝的被探测指令,具体执行方式各个架构不尽相同,arm会在异常处理流程中使用模拟函数执行,而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行; 、在单步执行完成后,kprobe执行用户注册的post_handler回调函数; 、最后,执行流程回到被探测指令之后的正常流程继续执行。
2. kprobe使用实例
2.1 编写kprobe探测模块
2.1.1 struct kprobe结构体
内核提供了struct kprobe表示一个探测点,以及一系列API接口,用户可以通过这些接口实现回调函数并实现struct kprobe结构,然后将它注册到内核的kprobe子系统中来达到探测的目的。
struct kprobe {
struct hlist_node hlist;-----------------------------------------------被用于kprobe全局hash,索引值为被探测点的地址。
/* list of kprobes for multi-handler support */
struct list_head list;-------------------------------------------------用于链接同一被探测点的不同探测kprobe。
/*count the number of times this probe was temporarily disarmed */
unsigned long nmissed;
/* location of the probe point */
kprobe_opcode_t *addr;-------------------------------------------------被探测点的地址。
/* Allow user to indicate symbol name of the probe point */
const char *symbol_name;-----------------------------------------------被探测函数的名称。
/* Offset into the symbol */
unsigned int offset;---------------------------------------------------被探测点在函数内部的偏移,用于探测函数内核的指令,如果该值为0表示函数的入口。
/* Called before addr is executed. */
kprobe_pre_handler_t pre_handler;--------------------------------------被探测点指令执行之前调用的回调函数。
/* Called after addr is executed, unless... */
kprobe_post_handler_t post_handler;------------------------------------被探测点指令执行之后调用的回调函数。
kprobe_fault_handler_t fault_handler;----------------------------------在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数。
kprobe_break_handler_t break_handler;----------------------------------在执行某一kprobe过程中出发了断点指令后会调用该函数,用于实现jprobe。
kprobe_opcode_t opcode;------------------------------------------------保存的被探测点原始指令。
struct arch_specific_insn ainsn;---------------------------------------被复制的被探测点的原始指令,用于单步执行,架构强相关。
u32 flags;-------------------------------------------------------------状态标记。
};
2.1.2 kprobe API函数
内核使用kprobe,可以使用register_kprobe()/unregister_kprobe()进行注册/卸载,还可以临时关闭/使能探测点。
int register_kprobe(struct kprobe *p);--------------------------注册kprobe探测点
void unregister_kprobe(struct kprobe *p);-----------------------卸载kprobe探测点
int register_kprobes(struct kprobe **kps, int num);-------------注册多个kprobe探测点
void unregister_kprobes(struct kprobe **kps, int num);----------卸载多个kprobe探测点
int disable_kprobe(struct kprobe *kp);--------------------------暂停指定定kprobe探测点
int enable_kprobe(struct kprobe *kp);---------------------------回复指定kprobe探测点
void dump_kprobe(struct kprobe *kp);----------------------------打印指定kprobe探测点的名称、地址、偏移
2.1.3 kprobe_example.c解读
下面以内核中samples/kprobes/kprobe_example.c为例,介绍如何使用kprobe进行内核函数探测。
该kprobe实例实现了_do_fork的探测,该函数会在fork系统调用或者kernel_kthread创建内核线程时被调用。
对原%p修改为%pF后,可读性更强。可以显示函数名称以及偏移量。
#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), ); /* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {---------------------------------------------------------定义一个实例kp并初始化symbol_name为"_do_fork",将探测_do_fork函数。
.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)
{
#ifdef CONFIG_X86
pr_info("<%s> pre_handler: p->addr = %pF, ip = %lx, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
#ifdef CONFIG_ARM64
pr_info("<%s> pre_handler: p->addr = %pF, pc = 0x%lx,"
" pstate = 0x%lx\n",
p->symbol_name, p->addr, (long)regs->pc, (long)regs->pstate);
#endif /* A dump_stack() here will give a stack backtrace */
return ;
} /* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
#ifdef CONFIG_X86
pr_info("<%s> post_handler: p->addr = %pF, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->flags);
#endif
#ifdef CONFIG_ARM64
pr_info("<%s> post_handler: p->addr = %pF, pstate = 0x%lx\n",
p->symbol_name, p->addr, (long)regs->pstate);
#endif
} /*
* 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 = %pF, trap #%dn", p->addr, trapnr);
/* Return 0 because we don't handle the fault. */
return ;
} static int __init kprobe_init(void)
{
int ret;
kp.pre_handler = handler_pre;---------------------------------------------------初始化kp的三个回调函数。
kp.post_handler = handler_post;
kp.fault_handler = handler_fault; ret = register_kprobe(&kp);-----------------------------------------------------注册kp探测点到内核。
if (ret < ) {
pr_err("register_kprobe failed, returned %d\n", ret);
return ret;
}
pr_info("Planted kprobe at %pF\n", kp.addr);
return ;
} static void __exit kprobe_exit(void)
{
unregister_kprobe(&kp);
pr_info("kprobe at %pF unregistered\n", kp.addr);
} module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
模块的编译Makefile如下:
obj-m := kprobe_example.o CROSS_COMPILE='' KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c .*.cmd *.symvers modul*
执行结果如下:
[ 9363.905687] Planted kprobe at _do_fork+0x0/0x3f0
[ 9366.924852] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x3f0, ip = ffffffff88a86a61, flags = 0x246
[ 9366.924858] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x3f0, flags = 0x246
[ 9366.932935] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x3f0, ip = ffffffff88a86a61, flags = 0x246
[ 9366.932938] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x3f0, flags = 0x246
[ 9366.957594] kprobe at _do_fork+0x0/0x3f0 unregistered
可以通过sudo cat /proc/kallsyms | grep l_do_fork来验证地址和符号是否对应。
若没有sudo,看不到真实的地址。
2.2 基于ftrace使用kprobe
kprobe和内核的ftrac结合使用,需要对内核进行配置,然后添加探测点、进行探测、查看结果。
2.2.1 kprobe配置
打开"General setup"->"Kprobes",以及"Kernel hacking"->"Tracers"->"Enable kprobes-based dynamic events"。
CONFIG_KPROBES=y
CONFIG_OPTPROBES=y
CONFIG_KPROBES_ON_FTRACE=y
CONFIG_UPROBES=y
CONFIG_KRETPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_HAVE_KRETPROBES=y
CONFIG_HAVE_OPTPROBES=y
CONFIG_HAVE_KPROBES_ON_FTRACE=y
CONFIG_KPROBE_EVENT=y
2.2.2 kprobe trace events使用
kprobe事件相关的节点有如下:
/sys/kernel/debug/tracing/kprobe_events-----------------------配置kprobe事件属性,增加事件之后会在kprobes下面生成对应目录。
/sys/kernel/debug/tracing/kprobe_profile----------------------kprobe事件统计属性文件。
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/enabled-------使能kprobe事件
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/filter--------过滤kprobe事件
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/format--------查询kprobe事件显示格式
下面就结合实例,看一下如何使用kprobe事件。
2.2.2.1 kprobe事件配置
新增一个kprobe事件,通过写kprobe_events来设置。
p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]-------------------设置一个probe探测点
r[:[GRP/]EVENT] [MOD:]SYM[+] [FETCHARGS]------------------------------设置一个return probe探测点
-:[GRP/]EVENT----------------------------------------------------------删除一个探测点
细节解释如下:
GRP : Group name. If omitted, use "kprobes" for it.------------设置后会在events/kprobes下创建<GRP>目录。
EVENT : Event name. If omitted, the event name is generated based on SYM+offs or MEMADDR.---指定后在events/kprobes/<GRP>生成<EVENT>目录。
MOD : Module name which has given SYM.--------------------------模块名,一般不设
SYM[+offs] : Symbol+offset where the probe is inserted.-------------被探测函数名和偏移
MEMADDR : Address where the probe is inserted.----------------------指定被探测的内存绝对地址
FETCHARGS : Arguments. Each probe can have up to args.----------指定要获取的参数信息。
%REG : Fetch register REG---------------------------------------获取指定寄存器值
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)--------获取指定内存地址的值
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)---获取全局变量的值
$stackN : Fetch Nth entry of stack (N >= )----------------------------------获取指定栈空间值,即sp寄存器+N后的位置值
$stack : Fetch stack address.-----------------------------------------------获取sp寄存器值
$retval : Fetch return value.(*)--------------------------------------------获取返回值,用户return kprobe
$comm : Fetch current task comm.----------------------------------------获取对应进程名称。
+|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**)-------------
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "string" and bitfield are supported.----------------设置参数的类型,可以支持字符串和比特类型
(*) only for return probe.
(**) this is useful for fetching a field of data structures.
执行如下两条命令就会生成目录/sys/kernel/debug/tracing/events/kprobes/myprobe;第三条命令则可以删除指定kprobe事件,如果要全部删除则echo > /sys/kernel/debug/tracing/kprobe_events。
echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
echo 'r:myretprobe do_sys_open ret=$retval' >> /sys/kernel/debug/tracing/kprobe_events-----------------------------------------------------这里面一定要用">>",不然就会覆盖前面的设置。 echo '-:myprobe' >> /sys/kernel/debug/tracing/kprobe_events
echo '-:myretprobe' >> /sys/kernel/debug/tracing/kprobe_events
参数后面的寄存器是跟架构相关的,%ax、%dx、%cx表示第1/2/3个参数,超出部分使用$stack来存储参数。
函数返回值保存在$retval中。
2.2.2.2 kprobe使能
对kprobe事件的是能通过往对应事件的enable写1开启探测;写0暂停探测。
echo > /sys/kernel/debug/tracing/trace
echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
echo 'r:myretprobe do_sys_open ret=$retval' >> /sys/kernel/debug/tracing/kprobe_events echo > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
ls
echo > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable cat /sys/kernel/debug/tracing/trace
然后在/sys/kernel/debug/tracing/trace中可以看到结果。
sourceinsight4.- [] .... 3542865.754536: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd6764a0 filename=0x8000 flags=0x1b6 mode=0xe3afff48ffffffff
bash- [] .... 3542865.757014: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8241 flags=0x1b6 mode=0xe0c0ff48ffffffff
ls- [] .... 3542865.757950: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.757953: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.757966: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x6168 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.757969: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758001: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x6168 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758004: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758030: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1000 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758033: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758055: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1000 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758057: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758080: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x19d0 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758082: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758289: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8000 flags=0x1b6 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758297: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758339: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x0 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758343: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls- [] .... 3542865.758444: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x98800 flags=0x2 mode=0xc1b7bf48ffffffff
ls- [] d... 3542865.758446: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
bash- [] .... 3542865.760416: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8241 flags=0x1b6 mode=0xe0c0ff48ffffffff
bash- [] d... 3542865.760426: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
bash- [] d... 3542865.793477: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
2.2.2.3 kprobe事件过滤
跟踪函数需要通过filter进行过滤,可以有效过滤掉冗余信息。
filter文件用于设置过滤条件,可以减少trace中输出的信息,它支持的格式和c语言的表达式类似,支持 ==,!=,>,<,>=,<=判断,并且支持与&&,或||,还有()。
echo 'filename==0x8241' > /sys/kernel/debug/tracing/events/kprobes/myprobe/filter
2.2.2.4 kprobe和栈配合使用
如果要在显示函数的同时显示其栈信息,可以通过配置trace_options来达到。
echo stacktrace > /sys/kernel/debug/tracing/trace_options
2.2.2.5 kprobe_profile统计信息
获取一段kprobe时间之后,可以再kprobe_profile中查看统计信息。
后面两列分别表示命中和未命中的次数。
cat /sys/kernel/debug/tracing/kprobe_profile
myprobe
myretprobe
3. kprobe相关源码分析
kprobe在ARM和X86_64架构下的源码分析,可以参考《Linux内核调试技术——kprobe使用与实现》 第四节《四、kprobe实现源码分析》。
参考文档:
Linux kprobe调试技术使用的更多相关文章
- Linux内核调试技术——jprobe使用与实现
前一篇博文介绍了kprobes的原理与kprobe的使用与实现方式,本文介绍kprobes中的另外一种探測技术jprobe.它基于kprobe实现,不能在函数的任何位置插入探測点,仅仅能在函数的入口处 ...
- 嵌入式Linux的调试技术
本节我们研究嵌入式Linux的调试技术,对于复杂的Linux驱动及HAL等程序库,需要使用各种方法对其进行调试.刚开始讲了打印内核调试信息:printk,这个函数的用法与printf函数类似,只不过p ...
- linux内核调试技术之printk
原创博客:欢迎转载,转载请注明出处https://i.cnblogs.com/EditPosts.aspx?postid=6218383 1.简介(基于s3c2440 linux) 在内核调试技术之中 ...
- 第十章 嵌入式Linux的调试技术
对调试工具进行简介.Linux中提供了一类工具,通过这些工具可以逐行跟踪程序的代码,用于测试用户空间程序的gdb.gdbserver和调试内核空间程序的kgdb. 用gdb调试用户空间程序:gdb可跟 ...
- linux应用调试技术之GDB和GDBServer
1.调试原理 GDB调试是应用程序在开发板上运行,然后在PC机上对开发板上得应用程序进行调试,PC机运行GDB,开发板上运行GDBServer.在应用程序调试的时候,pc机上的gdb向开发板上的GDB ...
- linux内核调试技术之自构proc
1.简介 在上一篇中,在内核中使用printk可以讲调试信息保存在log_buf缓冲区中,可以使用命令 #cat /proc/kmsg 将缓冲区的数区的数数据打印出来,今天我们就来研究一下,自己写k ...
- 第10章 嵌入式Linux的调试技术
printk函数运行在内核空间,printf函数运行在用户空间.也就是说像Linux驱动这样的Linux内核程序只能使用printk函数输出调试信息.printk函数在控制台(也称终端)显示消息是通过 ...
- 第10章 嵌入式Linux 的调试技术
10.1 打印内核调试信息:printk printk位函数运行在内核空间, printf函数运行在用户空间.也就是说,像Linux 驱动这样的Linux内核程序只能使用printk函数输出调试信息 ...
- linux内核调试技术之修改内核定时器来定位系统僵死问题
1.简介 在内核调试中,会经常出现内核僵死的问题,也就是发生死循环,内核不能产生调度.导致内核失去响应.这种情况下我们可以采用修改系统内核中的系统时钟的中断来定位发生僵死的进程和函数名称.因为内核系统 ...
随机推荐
- Linux PCI设备驱动的实现思路与思想
概述 1.PCI设备一般都具有双重身份,一方面作为PCI设备注册到Linux内核,另一方面,作为字符设备或者块设备,或者网络设备注册到Linux内核,所以,在看PCI设备时一定要注意到这点. 2. 一 ...
- 30分钟彻底弄懂flex布局
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由elson发表于云+社区专栏 目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布 ...
- 你不可不知的Java引用类型之——虚引用
定义 虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个.一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获 ...
- SAP 销售条件表增强栏位
有时遇到一个比较特殊的业务,比如公司间免费订单,既要让价格为0,不读取VK11里创建的价格, 又要让公司间的价格读取VK11,这实际上是有矛盾的,也就是说一个订单里面的两行,物料一样,客户一样,就会出 ...
- java开发基础知识学习
java环境配置 classpath: .当前目录 path: java 命令所在目录 jdk安装目录 jdk/bin jre安装目录 jre/bin 安装JDK后配置环境变量如下: 安装过程用到了j ...
- 【算法】LeetCode算法题-Merge Two Sorted List
这是悦乐书的第148次更新,第150篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第7题(顺位题号是21).合并两个已排序的链表并将其作为新链表返回. 新链表应该通过拼接 ...
- ABAP 7.40, SP08 中的 Open SQL 新特性
1,使用 data_source~*指定列 在7.40, SP08中,可以在SELECT语句中使用data_source~*来指定选取不同的数据库表.视图的全部列来作为结果集.它也可以和单独指定的列c ...
- python 管道、数据共享、进程池
一.管道(Pipe)(了解) (详情参考:https://www.cnblogs.com/clschao/articles/9629392.html) 进程间通信(IPC)方式二:管道(不推荐使用,了 ...
- 1. 路过面了个试就拿到2个offer。是运气吗?
路过随便面个试就拿到2个offer.是运气吗? #复习很重要#看看面试问的问题,再瞧瞧师兄的学习态度,你就明白 机会为何总与你擦肩而过了.[玫瑰] 以下是我和师兄的聊天记录,你会几个?
- JS页面打印
平常浏览网页和文档的时候,随处可见打印两个字,有时候不小心点到或者快捷键触发到,就会弹出一个打印的页面,上边显示的打印机是GoldGrid Virtual Printer,这是计算机的虚拟打印机,打印 ...