之所以想写这篇博客,是因为之前在使用ftrace的kprobe_events调试的时候,输入的寄存器名字一直不对,导致写kprobe_events接口一直报错。之前一直用的arm32,r0,r1这些用的就很方便,但是到了x86平台上,试了rdi,rsi等,一直都不对。索性,就去看看内核代码,确认究竟寄存器名字应该写什么。
下面通过代码演示我的查找步骤,代码基于linux-4.19.195
首先,找到内核注册kprobe_events接口的地方。
static const struct file_operations kprobe_events_ops = {
.owner = THIS_MODULE,
.open = probes_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
.write = probes_write,
};
/* Make a tracefs interface for controlling probe points */
static __init int init_kprobe_trace(void)
{
***
entry = tracefs_create_file("kprobe_events", 0644, d_tracer,
NULL, &kprobe_events_ops);
***
return 0;
}
fs_initcall(init_kprobe_trace);
可以看到,注册的回调函数在kprobe_events_ops里,我们写kprobe_events接口的话,自然是去看probes_write函数。
static ssize_t probes_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
return trace_parse_run_command(file, buffer, count, ppos,
create_trace_kprobe);
}
ssize_t trace_parse_run_command(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos,
int (*createfn)(int, char **))
{
***
ret = trace_run_command(buf, createfn);
***
}
可以看到,最终调用trace_run_command函数去对输入进行处理。createfn是个回调函数,传入的是create_trace_kprobe。trace_run_command里面正是调用了create_trace_kprobe进行了传入命令的处理。
static int create_trace_kprobe(int argc, char **argv)
{
***
/* Parse fetch argument */
ret = traceprobe_parse_probe_arg(arg, &tk->tp.size, parg,
is_return, true,
kprobes_fetch_type_table);
if (ret) {
pr_info("Parse error at argument[%d]. (%d)\n", i, ret);
goto error;
}
***
}
做了许多检查后,最终调用traceprobe_parse_probe_arg函数。
/* String length checking wrapper */
int traceprobe_parse_probe_arg(char *arg, ssize_t *size,
struct probe_arg *parg, bool is_return, bool is_kprobe,
const struct fetch_type *ftbl)
{
***
ret = parse_probe_arg(arg, parg->type, &parg->fetch, is_return,
is_kprobe, ftbl);
***
}
/* Recursive argument parser */
static int parse_probe_arg(char *arg, const struct fetch_type *t,
struct fetch_param *f, bool is_return, bool is_kprobe,
const struct fetch_type *ftbl)
{
***
case '%': /* named register */
ret = regs_query_register_offset(arg + 1);
if (ret >= 0) {
f->fn = t->fetch[FETCH_MTD_reg];
f->data = (void *)(unsigned long)ret;
ret = 0;
}
break;
*****
}
/**
* regs_query_register_offset() - query register offset from its name
* @name: the name of a register
*
* regs_query_register_offset() returns the offset of a register in struct
* pt_regs from its name. If the name is invalid, this returns -EINVAL;
*/
int regs_query_register_offset(const char *name)
{
const struct pt_regs_offset *roff;
for (roff = regoffset_table; roff->name != NULL; roff++)
if (!strcmp(roff->name, name))
return roff->offset;
return -EINVAL;
}
最终走进了regs_query_register_offset函数里,可以看到,对于百分号%后面字符串的解析,是通过strcmp进行的,对比的是regoffset_table,我们来看下regoffset_table。
static const struct pt_regs_offset regoffset_table[] = {
#ifdef CONFIG_X86_64
REG_OFFSET_NAME(r15),
REG_OFFSET_NAME(r14),
REG_OFFSET_NAME(r13),
REG_OFFSET_NAME(r12),
REG_OFFSET_NAME(r11),
REG_OFFSET_NAME(r10),
REG_OFFSET_NAME(r9),
REG_OFFSET_NAME(r8),
#endif
REG_OFFSET_NAME(bx),
REG_OFFSET_NAME(cx),
REG_OFFSET_NAME(dx),
REG_OFFSET_NAME(si),
REG_OFFSET_NAME(di),
REG_OFFSET_NAME(bp),
REG_OFFSET_NAME(ax),
#ifdef CONFIG_X86_32
REG_OFFSET_NAME(ds),
REG_OFFSET_NAME(es),
REG_OFFSET_NAME(fs),
REG_OFFSET_NAME(gs),
#endif
REG_OFFSET_NAME(orig_ax),
REG_OFFSET_NAME(ip),
REG_OFFSET_NAME(cs),
REG_OFFSET_NAME(flags),
REG_OFFSET_NAME(sp),
REG_OFFSET_NAME(ss),
REG_OFFSET_END,
};
原来regoffset_table数组就是寄存器表,把之前写的%rdi改成%di,%rsi改成%si,写入kprobe_events接口,果然成功了。
完