linux kprobe

###1. kprobes Kprobes 提供了一个强行进入任何(除了Kprobes模块自身的实现代码)内核例程,并从中断处理器无干扰地收集信息的接口。使用 Kprobes 可以轻松地收集处理器寄存器和全局数据结构等调试信息,而无需对Linux内核频繁编译和启动。 它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。 kprobe实现了三种类型的探测点: kprobes, jprobes和kretprobes (也叫返回探测点)。 kprobes是可以被插入到内核的任何指令位置的探测点,jprobes则只能被插入到一个内核函数的入口(一般用于调试函数参数),而kretprobes则是在指定的内核函数返回时才被执行(一般用于调试函数返回值)。 一般,使用kprobe的程序实现作一个内核模块,模块的初始化函数来负责安装探测点,退出函数卸载那些被安装的探测点。kprobe提供了接口函数(APIs)来安装或卸载探测点。目前kprobe支持如下架构:i386、x86_64、ppc64、ia64(不支持对slot1指令的探测)、sparc64 (返回探测还没有实现)。 ###2. kprobes 族的工作原理 当安装一个kprobes探测点时,kprobe首先备份被探测的指令,然后使用断点指令(即在i386和x86_64的int3指令)来取代被探测指令的头一个或几个字节。当CPU执行到探测点时,将因运行断点指令而执行trap操作,那将导致保存CPU的寄存器,调用相应的trap处理函数,而trap处理函数将调用相应的notifier_call_chain(内核中一种异步工作机制)中注册的所有notifier函数,kprobe正是通过向trap对应的notifier_call_chain注册关联到探测点的处理函数来实现探测处理的。当kprobe注册的notifier被执行时,它首先执行关联到探测点的pre_handler函数,并把相应的kprobe struct和保存的寄存器作为该函数的参数,接着,kprobe单步执行被探测指令的备份,最后,kprobe执行post_handler。等所有这些运行完毕后,紧跟在被探测指令后的指令流将被正常执行。 jprobe通过注册kprobes在被探测函数入口的来实现,它能无缝地访问被探测函数的参数。jprobe处理函数应当和被探测函数有同样的原型,而且该处理函数在函数末必须调用kprobe提供的函数jprobe_return()。当执行到该探测点时,kprobe备份CPU寄存器和栈的一些部分,然后修改指令寄存器指向jprobe处理函数,当执行该jprobe处理函数时,寄存器和栈内容与执行真正的被探测函数一模一样,因此它不需要任何特别的处理就能访问函数参数, 在该处理函数执行到最后时,它调用jprobe_return(),那导致寄存器和栈恢复到执行探测点时的状态,因此被探测函数能被正常运行。需要注意,被探测函数的参数可能通过栈传递,也可能通过寄存器传递,但是jprobe对于两种情况都能工作,因为它既备份了栈,又备份了寄存器,当然,前提是jprobe处理函数原型必须与被探测函数完全一样。 kretprobe也使用了kprobes来实现,当用户调用register_kretprobe()时,kprobe在被探测函数的入口建立了一个探测点,当执行到探测点时,kprobe保存了被探测函数的返回地址并取代返回地址为一个trampoline的地址,kprobe在初始化时定义了该trampoline并且为该trampoline注册了一个kprobe,当被探测函数执行它的返回指令时,控制传递到该trampoline,因此kprobe已经注册的对应于trampoline的处理函数将被执行,而该处理函数会调用用户关联到该kretprobe上的处理函数,处理完毕后,设置指令寄存器指向已经备份的函数返回地址,因而原来的函数返回被正常执行。 被探测函数的返回地址保存在类型为kretprobe_instance的变量中,结构kretprobe的maxactive字段指定了被探测函数可以被同时探测的实例数,函数register_kretprobe()将预分配指定数量的kretprobe_instance。如果被探测函数是非递归的并且调用时已经保持了自旋锁(spinlock),那么maxactive为1就足够了; 如果被探测函数是非递归的且运行时是抢占失效的,那么maxactive为NR_CPUS就可以了;如果maxactive被设置为小于等于0, 它被设置到缺省值(如果抢占使能, 即配置了 CONFIG_PREEMPT,缺省值为10和2*NR_CPUS中的最大值,否则缺省值为NR_CPUS)。 如果maxactive被设置的太小了,一些探测点的执行可能被丢失,但是不影响系统的正常运行,在结构kretprobe中nmissed字段将记录被丢失的探测点执行数,它在返回探测点被注册时设置为0,每次当执行探测函数而没有kretprobe_instance可用时,它就加1。 ###3. kprobes的不足 kprobes也有无能为力的时候, 在使用前,请你看看这些。 kprobe允许在同一地址注册多个kprobes,但是不能同时在该地址上有多个jprobes。 通常,用户可以在内核的任何位置注册探测点,特别是可以对中断处理函数注册探测点,但是也有一些例外。如果用户尝试在实现kprobe的代码(包括kernel/kprobes.c和arch//kernel/kprobes.c以及do_page_fault和notifier_call_chain)中注册探测点,register_probe将返回-EINVAL. 如果为一个内联(inline)函数注册探测点,kprobe无法保证对该函数的所有实例都注册探测点,因为gcc可能隐式地内联一个函数。因此,要记住,用户可能看不到预期的探测点的执行。 一个探测点处理函数能够修改被探测函数的上下文,如修改内核数据结构,寄存器等。因此,kprobe可以用来安装bug解决代码或注入一些错误或测试代码。 如果一个探测处理函数调用了另一个探测点,该探测点的处理函数不将运行,但是它的nmissed数将加1。多个探测点处理函数或同一处理函数的多个实例能够在不同的CPU上同时运行。 除了注册和卸载,kprobe不会使用mutexe或分配内存。 探测点处理函数在运行时是失效抢占的,依赖于特定的架构,探测点处理函数运行时也可能是中断失效的。因此,对于任何探测点处理函数,不要使用导致睡眠或进程调度的任何内核函数(如尝试获得semaphore)。 kretprobe是通过取代返回地址为预定义的trampoline的地址来实现的,因此栈回溯和gcc内嵌函数...

gcc attribute 扩展

###1. gcc attribute 机制 atrribute 机制是gcc 的一大特色, 可以为函数, 变量和类型设定不同的属性 ###2. attribute的使用 gcc attribute的语法为 __attribute__ ((attribute-list)) 例如 int var __attribute__ ((__unused__)) 注意: 放置在声明结尾的 “;” 之前,最少两对外括号,不能省 对于属性名, 可以在前后都加上双下划线(如 unused和 _unused_ 相同), 防止和源码中定义的宏名冲突 注意, attribute应用c/c++的声明语句里面, 而不是定义语句里面 一次使用多个属性可重复使用 _attribute_ 宏 有些属性会在编译时给出警告,但这需要打开 –Wall 选项 按照...

7-在真机上引导linux

###1. 使用U盘在真机上引导内核 我们已经能够从qemu中引导linux内核了,这一次, 我们使用一个u盘在一台真实的计算机上引导自己build的内核 ###2. 使用grub来引导 ####2.1 安装grub 你需要一台安装了linux的机器, 大部分发行版都是使用grub来进行引导,里面都自带了grub的tool 插上u盘, 确认是哪个块设备设备, 可以使用 lsblk命令来查看,我的pc上只有一块硬盘, 当我只插上一个U盘时, 它是 /dev/sdb 格式化U盘, 建立ext4文件系统 sudo fdisk /dev/sdb 删除原有的分区, 并建立一个主分区,那么现在就有“/dev/sdb”和“/dev/sdb1”(当然你的情况可能有所不同,一定要确认好, 否则可能破坏你的本地的引导) 然后格式化为ext4文件系统 sudo mkfs.ext4 /dev/sdb1 然后,挂载U盘 sudo mount -t ext4 /mnt /dev/sdb1 安装grub到U盘 sudo grub-install --root-directory...

6-添加initrc脚本

###1. 完善init程序 在前面我们通过内核启动参数 “init=/bin/sh”来指定shell作为init程序,在启动后打开一个shell, 但是,我们需要在开机时自动做一些工作, 比如挂载 /proc 和 /sys 文件系统, 这个时候,我们需要一个完整的init程序 默认情况下, bootloader 会传递 “init=/linuxrc” 来指定 “linuxrc” 来作为init进程, 我们可以将 /linuxrc 链接到 /bin/buxybox , 使用 busybox 来作为 init 进程 ###2. 使用busybox的init模板 busybox的init程序在运行时,如果存在/etc/inittable文件,Busybox init 程序解析它,然后按照他的指示创建各种子进程,否则使用默认的配置创建子进程.busybox的源码文件的示例带有一份 inittable文件, 我们可以利用这一份现有的模板 sudo mount -o loop initrd.img...

5-在qemu中引导linux

###1. 使用qemu引导linux 现在, 我们已经有了已经build好了的kernel, 和基于ramdisk的根文件系统(存储了busybox在其中), 现在, 可以使用qemu引导一个使用内存盘的linux系统了 ###2. 方法一 cp linux-src/linux-linux-3.10.40/arch/x86_64/boot/bzImage . qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "root=/dev/ram0 rw ramdisk_size=30720 init=/bin/sh" 其中, 参数的意义如下 -kernel 指定了内核镜象 -initrd 指定了启动内核时的initrd -append 指定了linux内核的启动参数, root 指定根文件系统所在的设备, 这里我们将ramdisk作为最终的根文件系统, 因此指定“/dev/ram0” rw 指定以rw的方式挂载根文件系统 ramdisk_size 因为ramdisk的默认大小为4M, 而我们制作的根文件系统大小为30M,因此需要用“ramdisk_size”来指定ramdisk的大小, init...