tracing: kprobe-based event tracer and x86 instruction decoder
Subsystems (7)
v8 cover tracing: kprobe-based event tracer and x86 instruction decoder
Hi,
Here are the patches of kprobe-based event tracer for x86, version 8, which allows you to probe various kernel events through ftrace interface.
This version, I added per-probe filtering support which allows you to set filters on each probe and shows formats of each probe. I think this is more generic integration with ftrace, especially event-tracer.
This patchset also includes x86(-64) instruction decoder which supports non-SSE/FP opcodes and includes x86 opcode map. The decoder is used for finding the instruction boundaries when inserting new kprobes. I think it will be possible to share this opcode map with KVM's decoder.
The decoder is tested when building kernel, the test compares the results of objdump and the decoder right after building vmlinux. You can enable that test by CONFIG_X86_DECODER_SELFTEST=y.
This series can be applied on the latest linux-2.6-tip tree.
This supports only x86(-32/-64) (but porting it on other arch just needs kprobes/kretprobes and register and stack access APIs).
This patchset includes following changes:
- Add x86 instruction decoder [1/7] (FIXED)
- Add x86 instruction decoder selftest [2/7] (FIXED)
- Check insertion point safety in kprobe [3/7]
- Cleanup fix_riprel() with insn decoder [4/7]
- Add arch-dep register and stack fetching functions [5/7]
- Add dynamic event_call support to ftrace [6/7] (NEW)
- Add kprobe-based event tracer [7/7] (UPDATED)
Enhancement ideas will be added after merging:
- .init function tracing support.
- Support primitive types(long, ulong, int, uint, etc) for args.
Kprobe-based Event Tracer
=========================
Overview
--------
This tracer is similar to the events tracer which is based on Tracepoint infrastructure. Instead of Tracepoint, this tracer is based on kprobes(kprobe and kretprobe). It probes anywhere where kprobes can probe(this means, all functions body except for __kprobes functions).
Unlike the function tracer, this tracer can probe instructions inside of kernel functions. It allows you to check which instruction has been executed.
Unlike the Tracepoint based events tracer, this tracer can add new probe points on the fly.
Similar to the events tracer, this tracer doesn't need to be activated via current_tracer, instead of that, just set probe points via /debug/tracing/kprobe_events.
Synopsis of kprobe_events
-------------------------
p[:EVENT] SYMBOL[+offs|-offs]|MEMADDR [FETCHARGS] : set a probe
r[:EVENT] SYMBOL[+0] [FETCHARGS] : set a return probe
EVENT : Event name
SYMBOL[+offs|-offs] : Symbol+offset where the probe is inserted
MEMADDR : Address where the probe is inserted
FETCHARGS : Arguments
%REG : Fetch register REG
sN : Fetch Nth entry of stack (N >= 0)
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)
aN : Fetch function argument. (N >= 0)(*)
rv : Fetch return value.(**)
ra : Fetch return address.(**)
+|-offs(FETCHARG) : fetch memory at FETCHARG +|- offs address.(***)
(*) aN may not correct on asmlinkaged functions and at the middle of
function body.
(**) only for return probe.
(***) this is useful for fetching a field of data structures.
Per-Probe Event Filtering
-------------------------
Per-probe event filtering feature allows you to set different filter on each
probe and gives you what arguments will be shown in trace buffer. If an event name is specified right after 'p:' or 'r:' in kprobe_events, the tracer adds an event under tracing/events/kprobes/<EVENT>, at the directory you can see 'id', 'enabled', 'format' and 'filter'.
enabled:
You can enable/disable the probe by writing 1 or 0 on it.
format:
It shows the format of this probe event. It also shows aliases of arguments
which you specified to kprobe_events.
filter:
You can write filtering rules of this event. And you can use both of aliase
names and field names for describing filters.
Usage examples
--------------
To add a probe as a new event, write a new definition to kprobe_events as below.
echo p:myprobe do_sys_open a0 a1 a2 a3 > /debug/tracing/kprobe_events
This sets a kprobe on the top of do_sys_open() function with recording
1st to 4th arguments as "myprobe" event.
echo r:myretprobe do_sys_open rv ra >> /debug/tracing/kprobe_events
This sets a kretprobe on the return point of do_sys_open() function with
recording return value and return address as "myretprobe" event.
You can see the format of these events via
tracing/events/kprobes/<EVENT>/format.
cat /debug/tracing/events/kprobes/myprobe/format
name: myprobe
ID: 23
format:
field:unsigned short common_type; offset:0; size:2; field:unsigned char common_flags; offset:2; size:1; field:unsigned char common_preempt_count; offset:3; size:1; field:int common_pid; offset:4; size:4; field:int common_tgid; offset:8; size:4; field: unsigned long ip; offset:16;tsize:8; field: int nargs; offset:24;tsize:4; field: unsigned long arg0; offset:32;tsize:8; field: unsigned long arg1; offset:40;tsize:8; field: unsigned long arg2; offset:48;tsize:8; field: unsigned long arg3; offset:56;tsize:8; alias: a0; original: arg0; alias: a1; original: arg1; alias: a2; original: arg2; alias: a3; original: arg3;
print fmt: "%lx: 0x%lx 0x%lx 0x%lx 0x%lx", ip, arg0, arg1, arg2, arg3
You can see that the event has 4 arguments and alias expressions
corresponding to it.
echo > /debug/tracing/kprobe_events
This clears all probe points. and you can see the traced information via
/debug/tracing/trace.
cat /debug/tracing/trace
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
<...>-1447 [001] 1038282.286875: do_sys_open+0x0/0xd6: 0x3 0x7fffd1ec4440 0x8000 0x0
<...>-1447 [001] 1038282.286878: sys_openat+0xc/0xe <- do_sys_open: 0xfffffffffffffffe 0xffffffff81367a3a
<...>-1447 [001] 1038282.286885: do_sys_open+0x0/0xd6: 0xffffff9c 0x40413c 0x8000 0x1b6
<...>-1447 [001] 1038282.286915: sys_open+0x1b/0x1d <- do_sys_open: 0x3 0xffffffff81367a3a
<...>-1447 [001] 1038282.286969: do_sys_open+0x0/0xd6: 0xffffff9c 0x4041c6 0x98800 0x10
<...>-1447 [001] 1038282.286976: sys_open+0x1b/0x1d <- do_sys_open: 0x3 0xffffffff81367a3a Each line shows when the kernel hits a probe, and <- SYMBOL means kernel
returns from SYMBOL(e.g. "sys_open+0x1b/0x1d <- do_sys_open" means kernel returns from do_sys_open to sys_open+0x1b).
Thank you,
---
Masami Hiramatsu (7):
tracing: add kprobe-based event tracer
tracing: ftrace dynamic ftrace_event_call support
x86: add pt_regs register and stack access APIs
kprobes: cleanup fix_riprel() using insn decoder on x86
kprobes: checks probe address is instruction boudary on x86
x86: x86 instruction decoder build-time selftest
x86: instruction decoder API
Documentation/trace/kprobes.txt | 138 ++++ arch/x86/Kconfig.debug | 9 arch/x86/Makefile | 3 arch/x86/include/asm/inat.h | 127 +++ arch/x86/include/asm/insn.h | 136 ++++ arch/x86/include/asm/ptrace.h | 67 ++ arch/x86/kernel/kprobes.c | 197 +++-- arch/x86/kernel/ptrace.c | 60 ++ arch/x86/lib/Makefile | 13 arch/x86/lib/inat.c | 82 ++ arch/x86/lib/insn.c | 473 +++++++++++++ arch/x86/lib/x86-opcode-map.txt | 711 +++++++++++++++++++ arch/x86/scripts/Makefile | 19 + arch/x86/scripts/distill.awk | 42 + arch/x86/scripts/gen-insn-attr-x86.awk | 314 +++++++++ arch/x86/scripts/test_get_len.c | 99 +++ arch/x86/scripts/user_include.h | 49 + include/linux/ftrace_event.h | 13 include/trace/ftrace.h | 22 - kernel/trace/Kconfig | 12 kernel/trace/Makefile | 1 kernel/trace/trace.h | 22 + kernel/trace/trace_event_types.h | 20 + kernel/trace/trace_events.c | 54 + kernel/trace/trace_export.c | 27 - kernel/trace/trace_kprobe.c | 1174 ++++++++++++++++++++++++++++++++ 26 files changed, 3732 insertions(+), 152 deletions(-) create mode 100644 Documentation/trace/kprobes.txt create mode 100644 arch/x86/include/asm/inat.h create mode 100644 arch/x86/include/asm/insn.h create mode 100644 arch/x86/lib/inat.c create mode 100644 arch/x86/lib/insn.c create mode 100644 arch/x86/lib/x86-opcode-map.txt create mode 100644 arch/x86/scripts/Makefile create mode 100644 arch/x86/scripts/distill.awk create mode 100644 arch/x86/scripts/gen-insn-attr-x86.awk create mode 100644 arch/x86/scripts/test_get_len.c create mode 100644 arch/x86/scripts/user_include.h create mode 100644 kernel/trace/trace_kprobe.c
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
PATCH v8 1/7 x86: instruction decoder API
Add x86 instruction decoder to arch-specific libraries. This decoder can decode x86 instructions used in kernel into prefix, opcode, modrm, sib, displacement and immediates. This can also show the length of instructions.
This version introduces instruction attributes for decoding instructions. The instruction attribute tables are generated from the opcode map file (x86-opcode-map.txt) by the generator script(gen-insn-attr-x86.awk).
Currently, the opcode maps are based on opcode maps in Intel(R) 64 and IA-32 Architectures Software Developers Manual Vol.2: Appendix.A, and consist of below two types of opcode tables.
1-byte/2-bytes/3-bytes opcodes, which has 256 elements, are written as below;
Table: table-name
Referrer: escaped-name
opcode: mnemonic|GrpXXX [operand1[,operand2...]] [(extra1)[,(extra2)...] [| 2nd-mnemonic ...]
(or)
opcode: escape # escaped-name
EndTable
Group opcodes, which has 8 elements, are written as below;
GrpTable: GrpXXX
reg: mnemonic [operand1[,operand2...]] [(extra1)[,(extra2)...] [| 2nd-mnemonic ...] EndTable
These opcode maps do NOT include most of SSE and FP opcodes, because those opcodes are not used in the kernel.
Changes from v6.1:
- fix patch title.
---
arch/x86/include/asm/inat.h | 125 ++++++ arch/x86/include/asm/insn.h | 134 ++++++ arch/x86/lib/Makefile | 13 + arch/x86/lib/inat.c | 80 ++++ arch/x86/lib/insn.c | 471 +++++++++++++++++++++ arch/x86/lib/x86-opcode-map.txt | 711 ++++++++++++++++++++++++++++++++ arch/x86/scripts/gen-insn-attr-x86.awk | 314 ++++++++++++++ 7 files changed, 1848 insertions(+), 0 deletions(-) create mode 100644 arch/x86/include/asm/inat.h create mode 100644 arch/x86/include/asm/insn.h create mode 100644 arch/x86/lib/inat.c create mode 100644 arch/x86/lib/insn.c create mode 100644 arch/x86/lib/x86-opcode-map.txt create mode 100644 arch/x86/scripts/gen-insn-attr-x86.awk
arch/x86/include/asm/inat.h new file +125 -0
@@ -0,0 +1,125 @@ +#ifndef _ASM_INAT_INAT_H +#define _ASM_INAT_INAT_H +/* +* x86 instruction attributes +* +* Written by Masami Hiramatsu <[email protected]> +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* +*/ +#include <linux/types.h> + +/* Instruction attributes */ +typedef u32 insn_attr_t ; + +/* +* Internal bits. Don't use bitmasks directly, because these bits are +* unstable. You should add checking macros and use that macro in +* your code. +*/ + +#define INAT_OPCODE_TABLE_SIZE 256 +#define INAT_GROUP_TABLE_SIZE 8 + +/* Legacy instruction prefixes */ +#define INAT_PFX_OPNDSZ 1/* 0x66 */ /* LPFX1 */ +#define INAT_PFX_REPNE 2/* 0xF2 */ /* LPFX2 */ +#define INAT_PFX_REPE 3/* 0xF3 */ /* LPFX3 */ +#define INAT_PFX_LOCK 4/* 0xF0 */ +#define INAT_PFX_CS 5/* 0x2E */ +#define INAT_PFX_DS 6/* 0x3E */ +#define INAT_PFX_ES 7/* 0x26 */ +#define INAT_PFX_FS 8/* 0x64 */ +#define INAT_PFX_GS 9/* 0x65 */ +#define INAT_PFX_SS 10/* 0x36 */ +#define INAT_PFX_ADDRSZ 11/* 0x67 */ + +#define INAT_LPREFIX_MAX 3 + +/* Immediate size */ +#define INAT_IMM_BYTE 1 +#define INAT_IMM_WORD 2 +#define INAT_IMM_DWORD 3 +#define INAT_IMM_QWORD 4 +#define INAT_IMM_PTR 5 +#define INAT_IMM_VWORD32 6 +#define INAT_IMM_VWORD 7 + +/* Legacy prefix */ +#define INAT_PFX_OFFS 0 +#define INAT_PFX_BITS 4 +#define INAT_PFX_MAX ((1 << INAT_PFX_BITS) - 1) +#define INAT_PFX_MASK (INAT_PFX_MAX << INAT_PFX_OFFS) +/* Escape opcodes */ +#define INAT_ESC_OFFS (INAT_PFX_OFFS + INAT_PFX_BITS) +#define INAT_ESC_BITS 2 +#define INAT_ESC_MAX ((1 << INAT_ESC_BITS) - 1) +#define INAT_ESC_MASK (INAT_ESC_MAX << INAT_ESC_OFFS) +/* Group opcodes (1-16) */ +#define INAT_GRP_OFFS (INAT_ESC_OFFS + INAT_ESC_BITS) +#define INAT_GRP_BITS 5 +#define INAT_GRP_MAX ((1 << INAT_GRP_BITS) - 1) +#define INAT_GRP_MASK (INAT_GRP_MAX << INAT_GRP_OFFS) +/* Immediates */ +#define INAT_IMM_OFFS (INAT_GRP_OFFS + INAT_GRP_BITS) +#define INAT_IMM_BITS 3 +#define INAT_IMM_MASK (((1 << INAT_IMM_BITS) - 1) << INAT_IMM_OFFS) +/* Flags */ +#define INAT_FLAG_OFFS (INAT_IMM_OFFS + INAT_IMM_BITS) +#define INAT_REXPFX (1 << INAT_FLAG_OFFS) +#define INAT_MODRM (1 << (INAT_FLAG_OFFS + 1)) +#define INAT_FORCE64 (1 << (INAT_FLAG_OFFS + 2)) +#define INAT_ADDIMM (1 << (INAT_FLAG_OFFS + 3)) +#define INAT_MOFFSET (1 << (INAT_FLAG_OFFS + 4)) +#define INAT_VARIANT (1 << (INAT_FLAG_OFFS + 5)) + +/* Attribute search APIs */ +extern insn_attr_t inat_get_opcode_attribute (u8 opcode); +extern insn_attr_t inat_get_escape_attribute (u8 opcode,u8 last_pfx, +insn_attr_t esc_attr); +extern insn_attr_t inat_get_group_attribute (u8 modrm,u8 last_pfx, +insn_attr_t esc_attr); + +/* Attribute checking macros. Use these macros in your code */ +#define INAT_IS_PREFIX (attr) (attr & INAT_PFX_MASK) +#define INAT_IS_ADDRSZ (attr) ((attr & INAT_PFX_MASK) == INAT_PFX_ADDRSZ) +#define INAT_IS_OPNDSZ (attr) ((attr & INAT_PFX_MASK) == INAT_PFX_OPNDSZ) +#define INAT_LPREFIX_NUM (attr) \ + (((attr & INAT_PFX_MASK) > INAT_LPREFIX_MAX) ? 0 :\ + (attr & INAT_PFX_MASK)) +#define INAT_MAKE_PREFIX (pfx) (pfx << INAT_PFX_OFFS) + +#define INAT_IS_ESCAPE (attr) (attr & INAT_ESC_MASK) +#define INAT_ESCAPE_NUM (attr) ((attr & INAT_ESC_MASK) >> INAT_ESC_OFFS) +#define INAT_MAKE_ESCAPE (esc) (esc << INAT_ESC_OFFS) + +#define INAT_IS_GROUP (attr) (attr & INAT_GRP_MASK) +#define INAT_GROUP_NUM (attr) ((attr & INAT_GRP_MASK) >> INAT_GRP_OFFS) +#define INAT_GROUP_COMMON (attr) (attr & ~INAT_GRP_MASK) +#define INAT_MAKE_GROUP (grp) ((grp << INAT_GRP_OFFS) | INAT_MODRM) + +#define INAT_HAS_IMM (attr) (attr & INAT_IMM_MASK) +#define INAT_IMM_SIZE (attr) ((attr & INAT_IMM_MASK) >> INAT_IMM_OFFS) +#define INAT_MAKE_IMM (imm) (imm << INAT_IMM_OFFS) + +#define INAT_IS_REX_PREFIX (attr) (attr & INAT_REXPFX) +#define INAT_HAS_MODRM (attr) (attr & INAT_MODRM) +#define INAT_IS_FORCE64 (attr) (attr & INAT_FORCE64) +#define INAT_HAS_ADDIMM (attr) (attr & INAT_ADDIMM) +#define INAT_HAS_MOFFSET (attr) (attr & INAT_MOFFSET) +#define INAT_HAS_VARIANT (attr) (attr & INAT_VARIANT) + +#endif
arch/x86/include/asm/insn.h new file +134 -0
134 lines not on this page · raw message
arch/x86/lib/Makefile +13 -0
@@ -2,12 +2,25 @@ # Makefile for x86 specific library files. # +inat_tables_script = $(srctree)/arch/x86/scripts/gen-insn-attr-x86.awk +inat_tables_maps = $(srctree)/arch/x86/lib/x86-opcode-map.txt +quiet_cmd_inat_tables = GEN $@ + cmd_inat_tables = $(AWK) -f $(inat_tables_script) $(inat_tables_maps) > $@ + +$(obj)/inat-tables.c: $(inat_tables_script) $(inat_tables_maps) + $(call cmd,inat_tables) + +$(obj)/inat.o: $(obj)/inat-tables.c + +clean-files := inat-tables.c + obj-$(CONFIG_SMP) := msr-on-cpu.o lib-y := delay.o lib-y += thunk_$(BITS).o lib-y += usercopy_$(BITS).o getuser.o putuser.o lib-y += memcpy_$(BITS).o +lib-y += insn.o inat.o ifeq ($(CONFIG_X86_32),y) lib-y += checksum_32.o
arch/x86/lib/inat.c new file +80 -0
80 lines not on this page · raw message
arch/x86/lib/insn.c new file +471 -0
471 lines not on this page · raw message
arch/x86/lib/x86-opcode-map.txt new file +711 -0
711 lines not on this page · raw message
arch/x86/scripts/gen-insn-attr-x86.awk new file +314 -0
314 lines not on this page · raw message
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
PATCH v8 2/7 x86: x86 instruction decoder build-time selftest
In reply to Masami HiramatsuAdd a user-space selftest of x86 instruction decoder at kernel build time. When CONFIG_X86_DECODER_SELFTEST=y, Kbuild builds a test harness of x86 instruction decoder and performs it after building vmlinux. The test compares the results of objdump and x86 instruction decoder code and check there are no differences.
Changes from v7:
- Add data, addr, rep, lock prefixes to skip instructions list.
- Add license comments.
---
arch/x86/Kconfig.debug | 9 ++++ arch/x86/Makefile | 3 + arch/x86/include/asm/inat.h | 2 + arch/x86/include/asm/insn.h | 2 + arch/x86/lib/inat.c | 2 + arch/x86/lib/insn.c | 2 + arch/x86/scripts/Makefile | 19 +++++++ arch/x86/scripts/distill.awk | 42 +++++++++++++++++ arch/x86/scripts/test_get_len.c | 99 +++++++++++++++++++++++++++++++++++++++ arch/x86/scripts/user_include.h | 49 +++++++++++++++++++ 10 files changed, 229 insertions(+), 0 deletions(-) create mode 100644 arch/x86/scripts/Makefile create mode 100644 arch/x86/scripts/distill.awk create mode 100644 arch/x86/scripts/test_get_len.c create mode 100644 arch/x86/scripts/user_include.h
arch/x86/Kconfig.debug +9 -0
@@ -179,6 +179,15 @@ config X86_DS_SELFTEST config HAVE_MMIOTRACE_SUPPORT def_bool y +config X86_DECODER_SELFTEST + bool "x86 instruction decoder selftest" + depends on DEBUG_KERNEL + ---help--- + Perform x86 instruction decoder selftests at build time. + This option is useful for checking the sanity of x86 instruction + decoder code. + If unsure, say "N". + # # IO delay types: #
arch/x86/Makefile +3 -0
@@ -154,6 +154,9 @@ all: bzImage KBUILD_IMAGE := $(boot)/bzImage bzImage: vmlinux +ifeq ($(CONFIG_X86_DECODER_SELFTEST),y) + $(Q)$(MAKE) $(build)=arch/x86/scripts posttest +endif $(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE) $(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot $(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@
arch/x86/include/asm/inat.h +2 -0
@@ -20,7 +20,9 @@* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ +#ifdef __KERNEL__#include <linux/types.h> +#endif /* Instruction attributes */ typedef u32 insn_attr_t ;
arch/x86/include/asm/insn.h +2 -0
@@ -20,7 +20,9 @@* Copyright (C) IBM Corporation, 2009 */ +#ifdef __KERNEL__#include <linux/types.h> +#endif /* insn_attr_t is defined in inat.h */ #include <asm/inat.h>
arch/x86/lib/inat.c +2 -0
@@ -18,7 +18,9 @@* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ +#ifdef __KERNEL__#include <linux/module.h> +#endif #include <asm/insn.h> /* Attribute tables are generated from opcode map */
arch/x86/lib/insn.c +2 -0
@@ -18,8 +18,10 @@* Copyright (C) IBM Corporation, 2002, 2004, 2009 */ +#ifdef __KERNEL__#include <linux/string.h> #include <linux/module.h> +#endif #include <asm/inat.h> #include <asm/insn.h>
arch/x86/scripts/Makefile new file +19 -0
@@ -0,0 +1,19 @@ +PHONY += posttest +quiet_cmd_posttest = TEST $@ + cmd_posttest = objdump -d $(objtree)/vmlinux | awk -f $(srctree)/arch/x86/scripts/distill.awk | $(obj)/test_get_len + +posttest: $(obj)/test_get_len vmlinux + $(call cmd,posttest) + +test_get_len_SRC = $(srctree)/arch/x86/scripts/test_get_len.c $(srctree)/arch/x86/lib/insn.c $(srctree)/arch/x86/lib/inat.c +test_get_len_INC = $(srctree)/arch/x86/include/asm/inat.h $(srctree)/arch/x86/include/asm/insn.h $(objtree)/arch/x86/lib/inat-tables.c + +quiet_cmd_test_get_len = CC $@ + cmd_test_get_len = $(CC) -Wall $(test_get_len_SRC) -I$(objtree)/arch/x86/lib/ -I$(srctree)/arch/x86/include -include $(srctree)/arch/x86/scripts/user_include.h -o $@ + + +$(obj)/test_get_len: $(test_get_len_SRC) $(test_get_len_INC) + $(call cmd,test_get_len) + +clean-files := test_get_len +
arch/x86/scripts/distill.awk new file +42 -0
42 lines not on this page · raw message
arch/x86/scripts/test_get_len.c new file +99 -0
99 lines not on this page · raw message
arch/x86/scripts/user_include.h new file +49 -0
49 lines not on this page · raw message
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
PATCH v8 3/7 kprobes: checks probe address is instruction boudary on x86
In reply to Masami HiramatsuEnsure safeness of inserting kprobes by checking whether the specified address is at the first byte of a instruction on x86.
This is done by decoding probed function from its head to the probe point.
---
arch/x86/kernel/kprobes.c | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 69 insertions(+), 0 deletions(-)
arch/x86/kernel/kprobes.c +69 -0
@@ -48,12 +48,14 @@#include <linux/preempt.h> #include <linux/module.h> #include <linux/kdebug.h> +#include <linux/kallsyms.h> #include <asm/cacheflush.h> #include <asm/desc.h> #include <asm/pgtable.h> #include <asm/uaccess.h> #include <asm/alternative.h> +#include <asm/insn.h> void jprobe_return_end (void );
@@ -244,6 +246,71 @@ retry: } } +/* Recover the probed instruction at addr for further analysis. */ +static int recover_probed_instruction (kprobe_opcode_t *buf,unsigned long addr) +{ +struct kprobe *kp; + kp =get_kprobe ((void *)addr); +if (!kp) +return -EINVAL ; + +/* +* Basically, kp->ainsn.insn has an original instruction. +* However, RIP-relative instruction can not do single-stepping +* at different place, fix_riprel() tweaks the displacement of +* that instruction. In that case, we can't recover the instruction +* from the kp->ainsn.insn. +* +* On the other hand, kp->opcode has a copy of the first byte of +* the probed instruction, which is overwritten by int3. And +* the instruction at kp->addr is not modified by kprobes except +* for the first byte, we can recover the original instruction +* from it and kp->opcode. +*/ +memcpy (buf, kp->addr,MAX_INSN_SIZE *sizeof (kprobe_opcode_t)); + buf[0 ] = kp->opcode; +return 0 ; +} + +/* Dummy buffers for kallsyms_lookup */ +static char __dummy_buf[KSYM_NAME_LEN ]; + +/* Check if paddr is at an instruction boundary */ +static int __kprobes can_probe (unsigned long paddr) +{ +int ret; +unsigned long addr, offset =0 ; +struct insn insn; +kprobe_opcode_t buf[MAX_INSN_SIZE ]; + +if (!kallsyms_lookup (paddr,NULL , &offset,NULL , __dummy_buf)) +return 0 ; + +/* Decode instructions */ + addr = paddr - offset; +while (addr < paddr) { +kernel_insn_init (&insn, (void *)addr); +insn_get_opcode (&insn); + +/* Check if the instruction has been modified. */ +if (OPCODE1 (&insn) ==BREAKPOINT_INSTRUCTION ) { + ret =recover_probed_instruction (buf, addr); +if (ret) +/* +* Another debugging subsystem might insert +* this breakpoint. In that case, we can't +* recover it. +*/ +return 0 ; +kernel_insn_init (&insn, buf); + } +insn_get_length (&insn); + addr += insn.length; + } + +return (addr == paddr); +} +/* * Returns non-zero if opcode modifies the interrupt flag. */
@@ -359,6 +426,8 @@ static void __kprobes arch_copy_kprobe(struct kprobe *p)int __kprobes arch_prepare_kprobe (struct kprobe *p) { +if (!can_probe ((unsigned long )p->addr)) +return -EILSEQ ;/* insn: must be on special executable page on x86. */ p->ainsn.insn =get_insn_slot ();if (!p->ainsn.insn)
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
PATCH v8 4/7 kprobes: cleanup fix_riprel() using insn decoder on x86
In reply to Masami HiramatsuCleanup fix_riprel() in arch/x86/kernel/kprobes.c by using x86 instruction decoder.
---
arch/x86/kernel/kprobes.c | 128 ++++++++------------------------------------- 1 files changed, 23 insertions(+), 105 deletions(-)
arch/x86/kernel/kprobes.c +23 -105
@@ -108,50 +108,6 @@ static const u32 twobyte_is_boostable[256 / 32] = {/* ----------------------------------------------- */ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ }; -static const u32 onebyte_has_modrm[256 /32 ] = { -/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ -/* ----------------------------------------------- */ -W (0x00 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ) |/* 00 */ -W (0x10 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ) ,/* 10 */ -W (0x20 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ) |/* 20 */ -W (0x30 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ) ,/* 30 */ -W (0x40 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) |/* 40 */ -W (0x50 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) ,/* 50 */ -W (0x60 ,0 ,0 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ) |/* 60 */ -W (0x70 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) ,/* 70 */ -W (0x80 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) |/* 80 */ -W (0x90 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) ,/* 90 */ -W (0xa0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) |/* a0 */ -W (0xb0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) ,/* b0 */ -W (0xc0 ,1 ,1 ,0 ,0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) |/* c0 */ -W (0xd0 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) ,/* d0 */ -W (0xe0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) |/* e0 */ -W (0xf0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,1 )/* f0 */ -/* ----------------------------------------------- */ -/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ -}; -static const u32 twobyte_has_modrm[256 /32 ] = { -/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ -/* ----------------------------------------------- */ -W (0x00 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,1 ) |/* 0f */ -W (0x10 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) ,/* 1f */ -W (0x20 ,1 ,1 ,1 ,1 ,1 ,0 ,1 ,0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) |/* 2f */ -W (0x30 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) ,/* 3f */ -W (0x40 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) |/* 4f */ -W (0x50 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) ,/* 5f */ -W (0x60 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) |/* 6f */ -W (0x70 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ) ,/* 7f */ -W (0x80 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) |/* 8f */ -W (0x90 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) ,/* 9f */ -W (0xa0 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,1 ) |/* af */ -W (0xb0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,0 ,0 ,1 ,1 ,1 ,1 ,1 ,1 ) ,/* bf */ -W (0xc0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ) |/* cf */ -W (0xd0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) ,/* df */ -W (0xe0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ) |/* ef */ -W (0xf0 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,0 )/* ff */ -/* ----------------------------------------------- */ -/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ -};#undef Wstruct kretprobe_blackpoint kretprobe_blacklist[] = {
@@ -344,68 +300,30 @@ static int __kprobes is_IF_modifier(kprobe_opcode_t *insn)static void __kprobes fix_riprel (struct kprobe *p) {#ifdef CONFIG_X86_64 -u8 *insn = p->ainsn.insn; -s64 disp; -int need_modrm; - -/* Skip legacy instruction prefixes. */ -while (1 ) { -switch (*insn) { -case 0x66 : -case 0x67 : -case 0x2e : -case 0x3e : -case 0x26 : -case 0x64 : -case 0x65 : -case 0x36 : -case 0xf0 : -case 0xf3 : -case 0xf2 : - ++insn; -continue ; - } -break ; - } +struct insn insn; +kernel_insn_init (&insn, p->ainsn.insn); -/* Skip REX instruction prefix. */ -if (is_REX_prefix (insn)) - ++insn; - -if (*insn ==0x0f ) { -/* Two-byte opcode. */ - ++insn; - need_modrm =test_bit (*insn, - (unsigned long *)twobyte_has_modrm); - }else -/* One-byte opcode. */ - need_modrm =test_bit (*insn, - (unsigned long *)onebyte_has_modrm); - -if (need_modrm) { -u8 modrm = *++insn; -if ((modrm &0xc7 ) ==0x05 ) { -/* %rip+disp32 addressing mode */ -/* Displacement follows ModRM byte. */ - ++insn; -/* -* The copied instruction uses the %rip-relative -* addressing mode. Adjust the displacement for the -* difference between the original location of this -* instruction and the location of the copy that will -* actually be run. The tricky bit here is making sure -* that the sign extension happens correctly in this -* calculation, since we need a signed 32-bit result to -* be sign-extended to 64 bits when it's added to the -* %rip value and yield the same 64-bit result that the -* sign-extension of the original signed 32-bit -* displacement would have given. -*/ - disp = (u8 *) p->addr + *((s32 *) insn) - - (u8 *) p->ainsn.insn; -BUG_ON ((s64 ) (s32 ) disp != disp);/* Sanity check. */ - *(s32 *)insn = (s32 ) disp; - } +if (insn_rip_relative (&insn)) { +s64 newdisp; +u8 *disp; +insn_get_displacement (&insn); +/* +* The copied instruction uses the %rip-relative addressing +* mode. Adjust the displacement for the difference between +* the original location of this instruction and the location +* of the copy that will actually be run. The tricky bit here +* is making sure that the sign extension happens correctly in +* this calculation, since we need a signed 32-bit result to +* be sign-extended to 64 bits when it's added to the %rip +* value and yield the same 64-bit result that the sign- +* extension of the original signed 32-bit displacement would +* have given. +*/ + newdisp = (u8 *) p->addr + (s64 ) insn.displacement.value - + (u8 *) p->ainsn.insn; +BUG_ON ((s64 ) (s32 ) newdisp != newdisp);/* Sanity check. */ + disp = (u8 *) p->ainsn.insn +INSN_DISPLACEMENT_OFFS (&insn); + *(s32 *) disp = (s32 ) newdisp; }#endif }
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
PATCH v8 5/7 x86: add pt_regs register and stack access APIs
In reply to Masami HiramatsuAdd following APIs for accessing registers and stack entries from pt_regs.
- query_register_offset(const char *name) Query the offset of "name" register.
- query_register_name(unsigned offset)
Query the name of register by its offset.
- get_register(struct pt_regs *regs, unsigned offset) Get the value of a register by its offset.
- within_kernel_stack(struct pt_regs *regs, unsigned long addr) Check the address is in the kernel stack.
- get_kernel_stack_nth(struct pt_regs *reg, unsigned nth) Get Nth entry of the kernel stack. (N >= 0)
- get_argument_nth(struct pt_regs *reg, unsigned nth) Get Nth argument at function call. (N >= 0)
---
arch/x86/include/asm/ptrace.h | 67 +++++++++++++++++++++++++++++++++++++++++ arch/x86/kernel/ptrace.c | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 0 deletions(-)
arch/x86/include/asm/ptrace.h +67 -0
@@ -7,6 +7,7 @@#ifdef __KERNEL__#include <asm/segment.h> +#include <asm/page_types.h> #endif #ifndef __ASSEMBLY__
@@ -216,6 +217,72 @@ static inline unsigned long user_stack_pointer(struct pt_regs *regs)return regs->sp; } +/* Query offset/name of register from its name/offset */ +extern int query_register_offset (const char *name); +extern const char *query_register_name (unsigned offset); +#define MAX_REG_OFFSET (offsetof(struct pt_regs, ss)) + +/* Get register value from its offset */ +static inline unsigned long get_register (struct pt_regs *regs,unsigned offset) +{ +if (unlikely (offset >MAX_REG_OFFSET )) +return 0 ; +return *(unsigned long *)((unsigned long )regs + offset); +} + +/* Check the address in the stack */ +static inline int within_kernel_stack (struct pt_regs *regs,unsigned long addr) +{ +return ((addr & ~(THREAD_SIZE -1 )) == + (kernel_stack_pointer (regs) & ~(THREAD_SIZE -1 ))); +} + +/* Get Nth entry of the stack */ +static inline unsigned long get_kernel_stack_nth (struct pt_regs *regs, +unsigned n) +{ +unsigned long *addr = (unsigned long *)kernel_stack_pointer (regs); + addr += n; +if (within_kernel_stack (regs, (unsigned long )addr)) +return *addr; +else +return 0 ; +} + +/* Get Nth argument at function call */ +static inline unsigned long get_argument_nth (struct pt_regs *regs,unsigned n) +{ +#ifdef CONFIG_X86_32 +#define NR_REGPARMS 3 +if (n <NR_REGPARMS ) { +switch (n) { +case 0 :return regs->ax; +case 1 :return regs->dx; +case 2 :return regs->cx; + } +return 0 ; +#else /* CONFIG_X86_64 */ +#define NR_REGPARMS 6 +if (n <NR_REGPARMS ) { +switch (n) { +case 0 :return regs->di; +case 1 :return regs->si; +case 2 :return regs->dx; +case 3 :return regs->cx; +case 4 :return regs->r8; +case 5 :return regs->r9; + } +return 0 ; +#endif + }else { +/* +* The typical case: arg n is on the stack. +* (Note: stack[0] = return address, so skip it) +*/ +return get_kernel_stack_nth (regs,1 + n -NR_REGPARMS ); + } +} +/* * These are defined as per linux/ptrace.h, which see. */
arch/x86/kernel/ptrace.c +60 -0
66 lines not on this page · raw message
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
Re: [PATCH -tip v8 5/7] x86: add pt_regs register and stack access APIs
You might want to run this past linux-arch to make sure this is suitable for other architectures.
arch/x86/include/asm/ptrace.h line 11
Quoted 8 lines
arch/x86/include/asm/ptrace.h +1
-0@@ -7,6 +7,7 @@#ifdef __KERNEL__#include <asm/segment.h> +#include <asm/page_types.h> #endif
I really wonder if we should split asm/ptrace.h into one file just defining pt_regs both for userspace and the kernel, and one with all kinds of register access helpers and maybe another one for the ptrace architecture interface.
Unforuntately we would have to keep the ptrace.h name for the one carrying pt_regs as it's exposed to userland.
arch/x86/include/asm/ptrace.h line 13
Quoted 2 lines
arch/x86/include/asm/ptrace.h +2
-0+static inline unsigned long get_register (struct pt_regs *regs,unsigned offset) +{
I woner if all these names aren't a bit generic. Shoud we maybe add a regs_ prefix to make it clear it operates on a pt_regs register set?
Also some kerneldoc documentation for all these helpers would be nice.
arch/x86/include/asm/ptrace.h static inline unsigned long user_stack_pointer(struct pt_regs *regs) · line 256
Quoted 5 lines
arch/x86/include/asm/ptrace.h +5
-0+/* Get Nth argument at function call */ +static inline unsigned long get_argument_nth (struct pt_regs *regs,unsigned n) +{ +#ifdef CONFIG_X86_32 +#define NR_REGPARMS 3
I think completely separate version for 32 vs 64 bit for this one would be cleaner.
arch/x86/include/asm/ptrace.h static inline unsigned long user_stack_pointer(struct pt_regs *regs) · line 262
Quoted 6 lines
arch/x86/include/asm/ptrace.h +6
-0+if (n <NR_REGPARMS ) { +switch (n) { +case 0 :return regs->ax; +case 1 :return regs->dx; +case 2 :return regs->cx; + }
Normal kernel style would be
switch (n) {
case 0:
return regs->ax;
case 1:
return regs->dx;
case 2:
return regs->cx;
}arch/x86/include/asm/ptrace.h
Quoted 3 lines
arch/x86/include/asm/ptrace.h +3
-0+#define REG_OFFSET (r) offsetof(struct pt_regs, r) +#define REG_OFFSET_NAME (r) {.name = #r, .offset = REG_OFFSET(r)} +#define REG_OFFSET_END {.name = NULL, .offset = 0}
At least the REG_OFFSET macro seems superflous to me.
Re: [PATCH -tip v8 5/7] x86: add pt_regs register and stack access APIs
Hi Christoph,
Thank you for review.
Quoted 2 lines · Christoph Hellwig
You might want to run this past linux-arch to make sure this is suitable for other architectures.
Frankly, I'm not sure about linux-arch, could you explain it? Anyway, I'm interested in that idea.
Quoted 4 lines
Quoted 4 lines
arch/x86/include/asm/ptrace.h +0
-0@@ -7,6 +7,7 @@
Quoted 9 lines
arch/x86/include/asm/ptrace.h line 11
Quoted 4 lines
arch/x86/include/asm/ptrace.h +1
-0#ifdef __KERNEL__#include <asm/segment.h> +#include <asm/page_types.h> #endif I really wonder if we should split
asm/ptrace.hinto one file just definingpt_regsboth for userspace and the kernel, and one with all kinds of register access helpers and maybe another one for the ptrace architecture interface.
Agreed, pt_regs is used more broadly than ptrace itself in kernel.
Quoted 2 lines
Unforuntately we would have to keep the
ptrace.hname for the one carryingpt_regsas it's exposed to userland.
Perhaps, we should split pt_regs from ptrace.h, like as ptrace-regs.h.
Quoted 5 lines
arch/x86/include/asm/ptrace.h line 13
Quoted 2 lines
arch/x86/include/asm/ptrace.h +2
-0+static inline unsigned long get_register (struct pt_regs *regs,unsigned offset) +{I woner if all these names aren't a bit generic. Shoud we maybe add a
regs_prefix to make it clear it operates on apt_regsregister set?
Indeed.
Quoted 1 line
Also some kerneldoc documentation for all these helpers would be nice.
Sure.
Quoted 8 lines
arch/x86/include/asm/ptrace.h static inline unsigned long user_stack_pointer(struct pt_regs *regs) · line 256
Quoted 5 lines
arch/x86/include/asm/ptrace.h +5
-0+/* Get Nth argument at function call */ +static inline unsigned long get_argument_nth (struct pt_regs *regs,unsigned n) +{ +#ifdef CONFIG_X86_32 +#define NR_REGPARMS 3I think completely separate version for 32 vs 64 bit for this one would be cleaner.
Agreed,
Quoted 10 lines
arch/x86/include/asm/ptrace.h static inline unsigned long user_stack_pointer(struct pt_regs *regs) · line 262
Quoted 7 lines
arch/x86/include/asm/ptrace.h +6
-0+if (n <NR_REGPARMS ) { +switch (n) { +case 0 :return regs->ax; +case 1 :return regs->dx; +case 2 :return regs->cx; + }Normal kernel style would be
Quoted 8 lines
switch (n) {
case 0:
return regs->ax;
case 1:
return regs->dx;
case 2:
return regs->cx;
}Oops, thanks,
Quoted 7 lines
arch/x86/include/asm/ptrace.h
Quoted 4 lines
arch/x86/include/asm/ptrace.h +3
-0+#define REG_OFFSET (r) offsetof(struct pt_regs, r) +#define REG_OFFSET_NAME (r) {.name = #r, .offset = REG_OFFSET(r)} +#define REG_OFFSET_END {.name = NULL, .offset = 0}At least the
REG_OFFSETmacro seems superflous to me.
Exactly.
Thank you again!
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America) Inc. Software Solutions Division e-mail: [email protected]
Re: [PATCH -tip v8 5/7] x86: add pt_regs register and stack access APIs
In reply to Christoph HellwigQuoted 9 lines · Christoph Hellwig
arch/x86/include/asm/ptrace.h static inline unsigned long user_stack_pointer(struct pt_regs *regs) · line 262
Quoted 6 lines
arch/x86/include/asm/ptrace.h +6
-0+if (n <NR_REGPARMS ) { +switch (n) { +case 0 :return regs->ax; +case 1 :return regs->dx; +case 2 :return regs->cx; + }Normal kernel style would be
Quoted 8 lines
switch (n) {
case 0:
return regs->ax;
case 1:
return regs->dx;
case 2:
return regs->cx;
}the original single-line shortcut is acceptable too.
Ingo
PATCH v8 6/7 tracing: ftrace dynamic ftrace_event_call support
In reply to Masami HiramatsuAdd dynamic ftrace_event_call support to ftrace. Trace engines can adds new ftrace_event_call to ftrace on the fly. Each operator functions of the call takes a ftrace_event_call data structure as an argument, because these functions may be shared among several ftrace_event_calls.
---
include/linux/ftrace_event.h | 13 ++++++---- include/trace/ftrace.h | 22 +++++++++-------- kernel/trace/trace_events.c | 54 +++++++++++++++++++++++++++++------------- kernel/trace/trace_export.c | 27 ++++++++++----------- 4 files changed, 69 insertions(+), 47 deletions(-)
include/linux/ftrace_event.h +8 -5
@@ -108,12 +108,13 @@ struct ftrace_event_call {struct dentry *dir;struct trace_event *event;int enabled; -int (*regfunc)(void ); -void (*unregfunc)(void ); +int (*regfunc)(struct ftrace_event_call *); +void (*unregfunc)(struct ftrace_event_call *);int id; -int (*raw_init)(void ); -int (*show_format)(struct trace_seq *s); -int (*define_fields)(void ); +int (*raw_init)(struct ftrace_event_call *); +int (*show_format)(struct ftrace_event_call *, +struct trace_seq *); +int (*define_fields)(struct ftrace_event_call *);struct list_head fields;int filter_active;void *filter;
@@ -138,6 +139,8 @@ extern int filter_current_check_discard(struct ftrace_event_call *call,extern int trace_define_field (struct ftrace_event_call *call,char *type,char *name,int offset,int size,int is_signed); +extern int trace_add_event_call (struct ftrace_event_call *call); +extern void trace_remove_event_call (struct ftrace_event_call *call);#define is_signed_type (type) (((type)(-1)) < 0)
include/trace/ftrace.h +11 -11
@@ -229,7 +229,8 @@ ftrace_raw_output_##call(struct trace_iterator *iter, int flags) \#undef TRACE_EVENT#define TRACE_EVENT (call, proto, args, tstruct, func, print) \ static int \ -ftrace_format_##call(struct trace_seq *s) \ +ftrace_format_##call(struct ftrace_event_call *event_call, \ + struct trace_seq *s) \ { \ struct ftrace_raw_##call field __attribute__((unused)); \ int ret = 0; \
@@ -269,10 +270,9 @@ ftrace_format_##call(struct trace_seq *s) \#undef TRACE_EVENT#define TRACE_EVENT (call, proto, args, tstruct, func, print) \ int \ -ftrace_define_fields_##call(void) \ +ftrace_define_fields_##call(struct ftrace_event_call *event_call) \ { \ struct ftrace_raw_##call field; \ - struct ftrace_event_call *event_call = &event_##call; \ int ret; \ \ __common_field(int, type, 1); \
@@ -298,7 +298,7 @@ ftrace_define_fields_##call(void) \* event_trace_printk(_RET_IP_, "<call>: " <fmt>); * } * -* static int ftrace_reg_event_<call>(void) +* static int ftrace_reg_event_<call>(struct ftrace_event_call *dummy) * { * int ret; *
@@ -309,7 +309,7 @@ ftrace_define_fields_##call(void) \* return ret; * } * -* static void ftrace_unreg_event_<call>(void) +* static void ftrace_unreg_event_<call>(struct ftrace_event_call *dummy) * { * unregister_trace_<call>(ftrace_event_<call>); * }
@@ -342,7 +342,7 @@ ftrace_define_fields_##call(void) \* trace_current_buffer_unlock_commit(event, irq_flags, pc); * } * -* static int ftrace_raw_reg_event_<call>(void) +* static int ftrace_raw_reg_event_<call>(struct ftrace_event_call *dummy) * { * int ret; *
@@ -353,7 +353,7 @@ ftrace_define_fields_##call(void) \* return ret; * } * -* static void ftrace_unreg_event_<call>(void) +* static void ftrace_unreg_event_<call>(struct ftrace_event_call *dummy) * { * unregister_trace_<call>(ftrace_raw_event_<call>); * }
@@ -362,7 +362,7 @@ ftrace_define_fields_##call(void) \* .trace = ftrace_raw_output_<call>, <-- stage 2 * }; * -* static int ftrace_raw_init_event_<call>(void) +* static int ftrace_raw_init_event_<call>(struct ftrace_event_call *dummy) * { * int id; *
@@ -477,7 +477,7 @@ static void ftrace_raw_event_##call(proto) \trace_nowake_buffer_unlock_commit (event, irq_flags, pc); \ } \ \ -static int ftrace_raw_reg_event_##call(void) \ +static int ftrace_raw_reg_event_##call(struct ftrace_event_call *dummy) \ { \ int ret; \ \
@@ -488,7 +488,7 @@ static int ftrace_raw_reg_event_##call(void) \return ret; \ } \ \ -static void ftrace_raw_unreg_event_##call(void) \ +static void ftrace_raw_unreg_event_##call(struct ftrace_event_call *dummy)\ { \ unregister_trace_##call(ftrace_raw_event_##call); \ } \
@@ -497,7 +497,7 @@ static struct trace_event ftrace_event_type_##call = { \ .trace = ftrace_raw_output_##call, \ }; \ \ -static int ftrace_raw_init_event_##call(void) \ +static int ftrace_raw_init_event_##call(struct ftrace_event_call *dummy)\ { \ int id; \ \
kernel/trace/trace_events.c +37 -17
108 lines not on this page · raw message
kernel/trace/trace_export.c +13 -14
@@ -60,7 +60,7 @@ extern void __bad_type_size(void);#undef TRACE_EVENT_FORMAT#define TRACE_EVENT_FORMAT (call, proto, args, fmt, tstruct, tpfmt) \ static int \ -ftrace_format_##call(struct trace_seq *s) \ +ftrace_format_##call(struct ftrace_event_call *dummy, struct trace_seq *s)\ { \ struct args field; \ int ret; \
@@ -76,7 +76,7 @@ ftrace_format_##call(struct trace_seq *s) \#define TRACE_EVENT_FORMAT_NOFILTER (call, proto, args, fmt, tstruct, \ tpfmt) \ static int \ -ftrace_format_##call(struct trace_seq *s) \ +ftrace_format_##call(struct ftrace_event_call *dummy, struct trace_seq *s)\ { \ struct args field; \ int ret; \
@@ -115,10 +115,16 @@ ftrace_format_##call(struct trace_seq *s) \#define TRACE_FIELD_SPECIAL (type_item, item, len, cmd) \ cmd; +static int ftrace_raw_init_event (struct ftrace_event_call *event_call) +{ +INIT_LIST_HEAD (&event_call->fields); +init_preds (event_call); +return 0 ; +} +#undef TRACE_EVENT_FORMAT#define TRACE_EVENT_FORMAT (call, proto, args, fmt, tstruct, tpfmt) \ -int ftrace_define_fields_##call(void); \ -static int ftrace_raw_init_event_##call(void); \ +int ftrace_define_fields_##call(struct ftrace_event_call *c); \ \ struct ftrace_event_call __used \ __attribute__((__aligned__(4))) \
@@ -126,16 +132,10 @@ __attribute__((section("_ftrace_events"))) event_##call = { \ .name = #call, \ .id = proto, \ .system = __stringify(TRACE_SYSTEM), \ - .raw_init = ftrace_raw_init_event_##call, \ + .raw_init = ftrace_raw_init_event, \ .show_format = ftrace_format_##call, \ .define_fields = ftrace_define_fields_##call, \ -}; \ -static int ftrace_raw_init_event_##call(void) \ -{ \ - INIT_LIST_HEAD(&event_##call.fields); \ - init_preds(&event_##call); \ - return 0; \ -} \ +};#undef TRACE_EVENT_FORMAT_NOFILTER #define TRACE_EVENT_FORMAT_NOFILTER(call, proto, args, fmt, tstruct, \
@@ -182,9 +182,8 @@ __attribute__((section("_ftrace_events"))) event_##call = { \#undef TRACE_EVENT_FORMAT#define TRACE_EVENT_FORMAT (call, proto, args, fmt, tstruct, tpfmt) \ int \ -ftrace_define_fields_##call(void) \ +ftrace_define_fields_##call(struct ftrace_event_call *event_call) \ { \ - struct ftrace_event_call *event_call = &event_##call; \ struct args field; \ int ret; \ \
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
Steven Rostedt
maintainerRe: [PATCH -tip v8 6/7] tracing: ftrace dynamic ftrace_event_call support
Quoted 77 lines · Masami Hiramatsu
Add dynamic
ftrace_event_callsupport to ftrace. Trace engines can adds newftrace_event_callto ftrace on the fly. Each operator functions of the call takes aftrace_event_calldata structure as an argument, because these functions may be shared among severalftrace_event_calls.Signed-off-by Masami Hiramatsu <[email protected]>Cc Steven Rostedt <[email protected]>Cc Ingo Molnar <[email protected]>Cc Tom Zanussi <[email protected]>Cc Frederic Weisbecker <[email protected]>---
include/linux/ftrace_event.h | 13 ++++++---- include/trace/ftrace.h | 22 +++++++++-------- kernel/trace/trace_events.c | 54 +++++++++++++++++++++++++++++------------- kernel/trace/trace_export.c | 27 ++++++++++----------- 4 files changed, 69 insertions(+), 47 deletions(-)include/linux/ftrace_event.h +8
-5@@ -108,12 +108,13 @@ struct ftrace_event_call {struct dentry *dir;struct trace_event *event;int enabled; -int (*regfunc)(void ); -void (*unregfunc)(void ); +int (*regfunc)(struct ftrace_event_call *); +void (*unregfunc)(struct ftrace_event_call *);int id; -int (*raw_init)(void ); -int (*show_format)(struct trace_seq *s); -int (*define_fields)(void ); +int (*raw_init)(struct ftrace_event_call *); +int (*show_format)(struct ftrace_event_call *, +struct trace_seq *); +int (*define_fields)(struct ftrace_event_call *);struct list_head fields;int filter_active;void *filter;@@ -138,6 +139,8 @@ extern int filter_current_check_discard(struct ftrace_event_call *call,extern int trace_define_field (struct ftrace_event_call *call,char *type,char *name,int offset,int size,int is_signed); +extern int trace_add_event_call (struct ftrace_event_call *call); +extern void trace_remove_event_call (struct ftrace_event_call *call);#define is_signed_type (type) (((type)(-1)) < 0)include/trace/ftrace.h +3
-3@@ -229,7 +229,8 @@ ftrace_raw_output_##call(struct trace_iterator *iter, int flags) \#undef TRACE_EVENT#define TRACE_EVENT (call, proto, args, tstruct, func, print) \ static int \ -ftrace_format_##call(struct trace_seq *s) \ +ftrace_format_##call(struct ftrace_event_call *event_call, \ + struct trace_seq *s) \ { \ struct ftrace_raw_##call field __attribute__((unused)); \ int ret = 0; \@@ -269,10 +270,9 @@ ftrace_format_##call(struct trace_seq *s) \#undef TRACE_EVENT#define TRACE_EVENT (call, proto, args, tstruct, func, print) \ int \ -ftrace_define_fields_##call(void) \ +ftrace_define_fields_##call(struct ftrace_event_call *event_call) \ { \ struct ftrace_raw_##call field; \ - struct ftrace_event_call *event_call = &event_##call; \ int ret; \ \ __common_field(int, type, 1); \
include/trace/ftrace.h ftrace_define_fields_##call(void) \ · line 301
Quoted 5 lines
include/trace/ftrace.h +1
-1@@ -298,7 +298,7 @@ ftrace_define_fields_##call(void) \* event_trace_printk(_RET_IP_, "<call>: " <fmt>); * } * -* static int ftrace_reg_event_<call>(void) +* static int ftrace_reg_event_<call>(struct ftrace_event_call *dummy)
I would prefer "unused" or similar as a variable name over "dummy".
Quoted 94 lines
include/trace/ftrace.h +7
-7* { * int ret; * @@ -309,7 +309,7 @@ ftrace_define_fields_##call(void) \* return ret; * } * -* static void ftrace_unreg_event_<call>(void) +* static void ftrace_unreg_event_<call>(struct ftrace_event_call *dummy) * { * unregister_trace_<call>(ftrace_event_<call>); * } @@ -342,7 +342,7 @@ ftrace_define_fields_##call(void) \* trace_current_buffer_unlock_commit(event, irq_flags, pc); * } * -* static int ftrace_raw_reg_event_<call>(void) +* static int ftrace_raw_reg_event_<call>(struct ftrace_event_call *dummy) * { * int ret; * @@ -353,7 +353,7 @@ ftrace_define_fields_##call(void) \* return ret; * } * -* static void ftrace_unreg_event_<call>(void) +* static void ftrace_unreg_event_<call>(struct ftrace_event_call *dummy) * { * unregister_trace_<call>(ftrace_raw_event_<call>); * } @@ -362,7 +362,7 @@ ftrace_define_fields_##call(void) \* .trace = ftrace_raw_output_<call>, <-- stage 2 * }; * -* static int ftrace_raw_init_event_<call>(void) +* static int ftrace_raw_init_event_<call>(struct ftrace_event_call *dummy) * { * int id; * @@ -477,7 +477,7 @@ static void ftrace_raw_event_##call(proto) \trace_nowake_buffer_unlock_commit (event, irq_flags, pc); \ } \ \ -static int ftrace_raw_reg_event_##call(void) \ +static int ftrace_raw_reg_event_##call(struct ftrace_event_call *dummy) \ { \ int ret; \ \@@ -488,7 +488,7 @@ static int ftrace_raw_reg_event_##call(void) \return ret; \ } \ \ -static void ftrace_raw_unreg_event_##call(void) \ +static void ftrace_raw_unreg_event_##call(struct ftrace_event_call *dummy)\ { \ unregister_trace_##call(ftrace_raw_event_##call); \ } \@@ -497,7 +497,7 @@ static struct trace_event ftrace_event_type_##call = { \ .trace = ftrace_raw_output_##call, \ }; \ \ -static int ftrace_raw_init_event_##call(void) \ +static int ftrace_raw_init_event_##call(struct ftrace_event_call *dummy)\ { \ int id; \ \kernel/trace/trace_events.c +1
-5@@ -60,9 +60,7 @@ err: }EXPORT_SYMBOL_GPL (trace_define_field); -#ifdef CONFIG_MODULES - -static void trace_destroy_fields (struct ftrace_event_call *call) +void trace_destroy_fields (struct ftrace_event_call *call) {struct ftrace_event_field *field, *next;@@ -74,8 +72,6 @@ static void trace_destroy_fields(struct ftrace_event_call *call) } } -#endif /* CONFIG_MODULES */ -static void ftrace_event_enable_disable (struct ftrace_event_call *call,int enable) {@@ -84,14 +80,14 @@ static void ftrace_event_enable_disable(struct ftrace_event_call *call,if (call->enabled) { call->enabled =0 ;tracing_stop_cmdline_record ();
kernel/trace/trace_events.c static void ftrace_event_enable_disable(struct ftrace_event_call *call, · line 90
Quoted 10 lines
kernel/trace/trace_events.c +2
-2- call->unregfunc (); + call->unregfunc (call); }break ;case 1 :if (!call->enabled) { call->enabled =1 ;tracing_start_cmdline_record (); - call->regfunc (); + call->regfunc (call);
Cute.
Quoted 37 lines
kernel/trace/trace_events.c +6
-3}break ; }@@ -558,7 +554,7 @@ event_format_read(struct file *filp, char __user *ubuf, size_t cnt,trace_seq_printf (s,"format:\n" );trace_write_header (s); - r = call->show_format (s); + r = call->show_format (call, s);if (!r) {/* * ug! The format output is bigger than a PAGE!! @@ -905,7 +901,7 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events, d_events =event_subsystem_dir (call->system, d_events);if (call->raw_init) { - ret = call->raw_init (); + ret = call->raw_init (call);if (ret <0 ) {pr_warning ("Could not initialize trace point" " events/%s\n" , call->name);@@ -929,7 +925,7 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events, id);if (call->define_fields) { - ret = call->define_fields (); + ret = call->define_fields (call);if (ret <0 ) {pr_warning ("Could not initialize trace point" " events/%s\n" , call->name);@@ -949,6 +945,36 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events,return 0 ; } +/* Add an additional event_call dynamically */ +int trace_add_event_call (struct ftrace_event_call *call) +{
kernel/trace/trace_events.c event_create_dir(struct ftrace_event_call *call, struct dentry *d_events, · line 960
Quoted 10 lines
kernel/trace/trace_events.c +10
-0+struct dentry *d_events; + +if (!call->name) +return -EINVAL ; + + d_events =event_trace_events_dir (); +if (!d_events) +return -ENOENT ; + +list_add (&call->list, &ftrace_events);
ftrace_events needs to be protected by the event_mutex.
kernel/trace/trace_events.c event_create_dir(struct ftrace_event_call *call, struct dentry *d_events, · line 973
Quoted 13 lines
kernel/trace/trace_events.c +13
-0+return event_create_dir (call, d_events, &ftrace_event_id_fops, + &ftrace_enable_fops, &ftrace_event_filter_fops, + &ftrace_event_format_fops); +} + +/* Remove an event_call */ +void trace_remove_event_call (struct ftrace_event_call *event_call) +{ +ftrace_event_enable_disable (event_call,0 ); +if (event_call->event) +unregister_ftrace_event (event_call->event); +debugfs_remove_recursive (event_call->dir); +list_del (&event_call->list);
Same here.
Quoted 9 lines
kernel/trace/trace_events.c +4
-0+trace_destroy_fields (event_call); +destroy_preds (event_call); +} +#define for_each_event (event, start, end) \ for (event = start; \ (unsigned long)event < (unsigned long)end; \@@ -1053,13 +1079,7 @@ static void trace_module_remove_events(struct module *mod)list_for_each_entry_safe (call, p, &ftrace_events, list) {
kernel/trace/trace_events.c static void trace_module_remove_events(struct module *mod) · line 1082
Quoted 10 lines
kernel/trace/trace_events.c +1
-7if (call->mod == mod) { found = true; -ftrace_event_enable_disable (call,0 ); -if (call->event) -unregister_ftrace_event (call->event); -debugfs_remove_recursive (call->dir); -list_del (&call->list); -trace_destroy_fields (call); -destroy_preds (call); +trace_remove_event_call (call);
We'll need a version that does not take the event_mutex, because it is held at this point.
-- Steve
Quoted 85 lines
kernel/trace/trace_events.c +0
-0} }kernel/trace/trace_export.c +13
-14@@ -60,7 +60,7 @@ extern void __bad_type_size(void);#undef TRACE_EVENT_FORMAT#define TRACE_EVENT_FORMAT (call, proto, args, fmt, tstruct, tpfmt) \ static int \ -ftrace_format_##call(struct trace_seq *s) \ +ftrace_format_##call(struct ftrace_event_call *dummy, struct trace_seq *s)\ { \ struct args field; \ int ret; \@@ -76,7 +76,7 @@ ftrace_format_##call(struct trace_seq *s) \#define TRACE_EVENT_FORMAT_NOFILTER (call, proto, args, fmt, tstruct, \ tpfmt) \ static int \ -ftrace_format_##call(struct trace_seq *s) \ +ftrace_format_##call(struct ftrace_event_call *dummy, struct trace_seq *s)\ { \ struct args field; \ int ret; \@@ -115,10 +115,16 @@ ftrace_format_##call(struct trace_seq *s) \#define TRACE_FIELD_SPECIAL (type_item, item, len, cmd) \ cmd; +static int ftrace_raw_init_event (struct ftrace_event_call *event_call) +{ +INIT_LIST_HEAD (&event_call->fields); +init_preds (event_call); +return 0 ; +} +#undef TRACE_EVENT_FORMAT#define TRACE_EVENT_FORMAT (call, proto, args, fmt, tstruct, tpfmt) \ -int ftrace_define_fields_##call(void); \ -static int ftrace_raw_init_event_##call(void); \ +int ftrace_define_fields_##call(struct ftrace_event_call *c); \ \ struct ftrace_event_call __used \ __attribute__((__aligned__(4))) \@@ -126,16 +132,10 @@ __attribute__((section("_ftrace_events"))) event_##call = { \ .name = #call, \ .id = proto, \ .system = __stringify(TRACE_SYSTEM), \ - .raw_init = ftrace_raw_init_event_##call, \ + .raw_init = ftrace_raw_init_event, \ .show_format = ftrace_format_##call, \ .define_fields = ftrace_define_fields_##call, \ -}; \ -static int ftrace_raw_init_event_##call(void) \ -{ \ - INIT_LIST_HEAD(&event_##call.fields); \ - init_preds(&event_##call); \ - return 0; \ -} \ +};#undef TRACE_EVENT_FORMAT_NOFILTER #define TRACE_EVENT_FORMAT_NOFILTER(call, proto, args, fmt, tstruct, \@@ -182,9 +182,8 @@ __attribute__((section("_ftrace_events"))) event_##call = { \#undef TRACE_EVENT_FORMAT#define TRACE_EVENT_FORMAT (call, proto, args, fmt, tstruct, tpfmt) \ int \ -ftrace_define_fields_##call(void) \ +ftrace_define_fields_##call(struct ftrace_event_call *event_call) \ { \ - struct ftrace_event_call *event_call = &event_##call; \ struct args field; \ int ret; \ \Signature · 8 lines
-- Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
PATCH v8 7/7 tracing: add kprobe-based event tracer
In reply to Masami HiramatsuAdd kprobes-based event tracer on ftrace.
This tracer is similar to the events tracer which is based on Tracepoint infrastructure. Instead of Tracepoint, this tracer is based on kprobes(kprobe and kretprobe). It probes anywhere where kprobes can probe(this means, all functions body except for __kprobes functions).
Similar to the events tracer, this tracer doesn't need to be activated via current_tracer, instead of that, just set probe points via /debug/tracing/kprobe_events. And you can set filters on each probe events via /debug/tracing/events/kprobes/<EVENT>/filter.
This tracer supports following probe arguments for each probe.
%REG : Fetch register REG
sN : Fetch Nth entry of stack (N >= 0)
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol) aN : Fetch function argument. (N >= 0) rv : Fetch return value. ra : Fetch return address.
+|-offs(FETCHARG) : fetch memory at FETCHARG +|- offs address.
See Documentation/trace/kprobes.txt for details.
Changes from v7:
- Fix document example.
- Remove solved TODO.
- Support per-probe event filtering.
---
Documentation/trace/kprobes.txt | 138 ++++ kernel/trace/Kconfig | 12 kernel/trace/Makefile | 1 kernel/trace/trace.h | 22 + kernel/trace/trace_event_types.h | 20 + kernel/trace/trace_kprobe.c | 1174 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1367 insertions(+), 0 deletions(-) create mode 100644 Documentation/trace/kprobes.txt create mode 100644 kernel/trace/trace_kprobe.c
Documentation/trace/kprobes.txt new file +138 -0
@@ -0,0 +1,138 @@ + Kprobe-based Event Tracer + ========================= + + Documentation is written by Masami Hiramatsu + + +Overview +-------- +This tracer is similar to the events tracer which is based on Tracepoint +infrastructure. Instead of Tracepoint, this tracer is based on kprobes(kprobe +and kretprobe). It probes anywhere where kprobes can probe(this means, all +functions body except for __kprobes functions). + +Unlike the function tracer, this tracer can probe instructions inside of +kernel functions. It allows you to check which instruction has been executed. + +Unlike the Tracepoint based events tracer, this tracer can add and remove +probe points on the fly. + +Similar to the events tracer, this tracer doesn't need to be activated via +current_tracer, instead of that, just set probe points via +/debug/tracing/kprobe_events. And you can set filters on each probe events +via /debug/tracing/events/kprobes/<EVENT>/filter. + + +Synopsis of kprobe_events +------------------------- + p[:EVENT] SYMBOL[+offs|-offs]|MEMADDR [FETCHARGS] : set a probe + r[:EVENT] SYMBOL[+0] [FETCHARGS] : set a return probe + + EVENT : Event name + SYMBOL[+offs|-offs] : Symbol+offset where the probe is inserted + MEMADDR : Address where the probe is inserted + + FETCHARGS : Arguments + %REG : Fetch register REG + sN : Fetch Nth entry of stack (N >= 0) + @ADDR : Fetch memory at ADDR (ADDR should be in kernel) + @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol) + aN : Fetch function argument. (N >= 0)(*) + rv : Fetch return value.(**) + ra : Fetch return address.(**) + +|-offs(FETCHARG) : fetch memory at FETCHARG +|- offs address.(***) + + (*) aN may not correct on asmlinkaged functions and at the middle of + function body. + (**) only for return probe. + (***) this is useful for fetching a field of data structures. + + +Per-Probe Event Filtering +------------------------- + Per-probe event filtering feature allows you to set different filter on each +probe and gives you what arguments will be shown in trace buffer. If an event +name is specified right after 'p:' or 'r:' in kprobe_events, the tracer adds +an event under tracing/events/kprobes/<EVENT>, at the directory you can see +'id', 'enabled', 'format' and 'filter'. + +enabled: + You can enable/disable the probe by writing 1 or 0 on it. + +format: + It shows the format of this probe event. It also shows aliases of arguments + which you specified to kprobe_events. + +filter: + You can write filtering rules of this event. And you can use both of aliase + names and field names for describing filters. + + +Usage examples +-------------- +To add a probe as a new event, write a new definition to kprobe_events +as below. + + echo p:myprobe do_sys_open a0 a1 a2 a3 > /debug/tracing/kprobe_events + + This sets a kprobe on the top of do_sys_open() function with recording +1st to 4th arguments as "myprobe" event. + + echo r:myretprobe do_sys_open rv ra >> /debug/tracing/kprobe_events + + This sets a kretprobe on the return point of do_sys_open() function with +recording return value and return address as "myretprobe" event. + You can see the format of these events via +tracing/events/kprobes/<EVENT>/format. + + cat /debug/tracing/events/kprobes/myprobe/format +name: myprobe +ID: 23 +format: + field:unsigned short common_type; offset:0; size:2; + field:unsigned char common_flags; offset:2; size:1; + field:unsigned char common_preempt_count; offset:3; size:1; + field:int common_pid; offset:4; size:4; + field:int common_tgid; offset:8; size:4; + + field: unsigned long ip; offset:16;tsize:8; + field: int nargs; offset:24;tsize:4; + field: unsigned long arg0; offset:32;tsize:8; + field: unsigned long arg1; offset:40;tsize:8; + field: unsigned long arg2; offset:48;tsize:8; + field: unsigned long arg3; offset:56;tsize:8; + + alias: a0; original: arg0; + alias: a1; original: arg1; + alias: a2; original: arg2; + alias: a3; original: arg3; + +print fmt: "%lx: 0x%lx 0x%lx 0x%lx 0x%lx", ip, arg0, arg1, arg2, arg3 + + + You can see that the event has 4 arguments and alias expressions +corresponding to it. + + echo > /debug/tracing/kprobe_events + + This clears all probe points. and you can see the traced information via +/debug/tracing/trace. + + cat /debug/tracing/trace +# tracer: nop +# +# TASK-PID CPU# TIMESTAMP FUNCTION +# | | | | | + <...>-1447 [001] 1038282.286875: do_sys_open+0x0/0xd6: 0x3 0x7fffd1ec4440 0x8000 0x0 + <...>-1447 [001] 1038282.286878: sys_openat+0xc/0xe <- do_sys_open: 0xfffffffffffffffe 0xffffffff81367a3a + <...>-1447 [001] 1038282.286885: do_sys_open+0x0/0xd6: 0xffffff9c 0x40413c 0x8000 0x1b6 + <...>-1447 [001] 1038282.286915: sys_open+0x1b/0x1d <- do_sys_open: 0x3 0xffffffff81367a3a + <...>-1447 [001] 1038282.286969: do_sys_open+0x0/0xd6: 0xffffff9c 0x4041c6 0x98800 0x10 + <...>-1447 [001] 1038282.286976: sys_open+0x1b/0x1d <- do_sys_open: 0x3 0xffffffff81367a3a + + + Each line shows when the kernel hits a probe, and <- SYMBOL means kernel +returns from SYMBOL(e.g. "sys_open+0x1b/0x1d <- do_sys_open" means kernel +returns from do_sys_open to sys_open+0x1b). + +
kernel/trace/Kconfig +12 -0
@@ -403,6 +403,18 @@ config BLK_DEV_IO_TRACE If unsure, say N. +config KPROBE_TRACER + depends on KPROBES + depends on X86 + bool "Trace kprobes" + select TRACING + select EVENT_TRACING + help + This tracer probes everywhere where kprobes can probe it, and + records various registers and memories specified by user. + This also allows you to trace kprobe probe points as a dynamic + defined events. It provides per-probe event filtering interface. + config DYNAMIC_FTRACE bool "enable/disable ftrace tracepoints dynamically" depends on FUNCTION_TRACER
kernel/trace/Makefile +1 -0
@@ -52,5 +52,6 @@ obj-$(CONFIG_FTRACE_SYSCALLS) += trace_syscalls.o obj-$(CONFIG_EVENT_PROFILE) += trace_event_profile.o obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o obj-$(CONFIG_EVENT_TRACING) += trace_mm.o +obj-$(CONFIG_KPROBE_TRACER) += trace_kprobe.o libftrace-y := ftrace.o
kernel/trace/trace.h +22 -0
@@ -40,6 +40,8 @@ enum trace_type {TRACE_KMEM_FREE ,TRACE_POWER ,TRACE_BLK , +TRACE_KPROBE , +TRACE_KRETPROBE , __TRACE_LAST_TYPE, };
@@ -207,6 +209,22 @@ struct syscall_trace_exit {unsigned long ret; }; +#define TRACE_KPROBE_ARGS 6 + +struct kprobe_trace_entry { +struct trace_entry ent; +unsigned long ip; +int nargs; +unsigned long args[TRACE_KPROBE_ARGS ]; +}; + +struct kretprobe_trace_entry { +struct trace_entry ent; +unsigned long func; +unsigned long ret_ip; +int nargs; +unsigned long args[TRACE_KPROBE_ARGS ]; +};/* * trace_flag_type is an enumeration that holds different
@@ -323,6 +341,10 @@ extern void __ftrace_bad_type(void); TRACE_SYSCALL_ENTER); \ IF_ASSIGN(var, ent, struct syscall_trace_exit, \TRACE_SYSCALL_EXIT ); \ + IF_ASSIGN(var, ent, struct kprobe_trace_entry, \ +TRACE_KPROBE ); \ + IF_ASSIGN(var, ent, struct kretprobe_trace_entry, \ +TRACE_KRETPROBE ); \__ftrace_bad_type (); \ }while (0 )
kernel/trace/trace_event_types.h +20 -0
@@ -172,4 +172,24 @@ TRACE_EVENT_FORMAT(kmem_free, TRACE_KMEM_FREE, kmemtrace_free_entry, ignore,TP_RAW_FMT ("type:%u call_site:%lx ptr:%p" ) ); +TRACE_EVENT_FORMAT (kprobe,TRACE_KPROBE , kprobe_trace_entry, ignore, +TRACE_STRUCT ( + TRACE_FIELD(unsigned long, ip, ip) + TRACE_FIELD(int, nargs, nargs) + TRACE_FIELD_SPECIAL(unsigned long args[TRACE_KPROBE_ARGS], + args,TRACE_KPROBE_ARGS , args) + ), +TP_RAW_FMT ("%08lx: args:0x%lx ..." ) +); + +TRACE_EVENT_FORMAT (kretprobe,TRACE_KRETPROBE , kretprobe_trace_entry, ignore, +TRACE_STRUCT ( + TRACE_FIELD(unsigned long, func, func) + TRACE_FIELD(unsigned long, ret_ip, ret_ip) + TRACE_FIELD(int, nargs, nargs) + TRACE_FIELD_SPECIAL(unsigned long args[TRACE_KPROBE_ARGS], + args,TRACE_KPROBE_ARGS , args) + ), +TP_RAW_FMT ("%08lx <- %08lx: args:0x%lx ..." ) +);#undef TRACE_SYSTEM
kernel/trace/trace_kprobe.c new file +1174 -0
1,174 lines not on this page · raw message
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America), Inc. Software Solutions Division e-mail: [email protected]
Steven Rostedt
maintainerRe: [PATCH -tip v8 7/7] tracing: add kprobe-based event tracer
Quoted 198 lines · Masami Hiramatsu
Add kprobes-based event tracer on ftrace.
This tracer is similar to the events tracer which is based on Tracepoint infrastructure. Instead of Tracepoint, this tracer is based on kprobes(kprobe and kretprobe). It probes anywhere where kprobes can
probe(this means, all functions body except for __kprobes functions).Similar to the events tracer, this tracer doesn't need to be activated via
current_tracer, instead of that, just set probe points via /debug/tracing/kprobe_events. And you can set filters on each probe events via /debug/tracing/events/kprobes/<EVENT>/filter.This tracer supports following probe arguments for each probe.
%REG : Fetch register REG
sN : Fetch Nth entry of stack (N >= 0)
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol) aN : Fetch function argument. (N >= 0) rv : Fetch return value. ra : Fetch return address.+|-offs(FETCHARG) : fetch memory at FETCHARG +|- offs address.
See
Documentation/trace/kprobes.txtfor details.Changes from v7:
- Fix document example.
- Remove solved TODO.
- Support per-probe event filtering.Signed-off-by Masami Hiramatsu <[email protected]>Cc Christoph Hellwig <[email protected]>Cc Steven Rostedt <[email protected]>Cc Ananth N Mavinakayanahalli <[email protected]>Cc Ingo Molnar <[email protected]>Cc Frederic Weisbecker <[email protected]>Cc Tom Zanussi <[email protected]>---
Documentation/trace/kprobes.txt | 138 ++++ kernel/trace/Kconfig | 12 kernel/trace/Makefile | 1 kernel/trace/trace.h | 22 + kernel/trace/trace_event_types.h | 20 + kernel/trace/trace_kprobe.c | 1174 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1367 insertions(+), 0 deletions(-) create mode 100644 Documentation/trace/kprobes.txt create mode 100644 kernel/trace/trace_kprobe.cDocumentation/trace/kprobes.txt new file +138
-0@@ -0,0 +1,138 @@ + Kprobe-based Event Tracer + ========================= + + Documentation is written by Masami Hiramatsu + + +Overview +-------- +This tracer is similar to the events tracer which is based on Tracepoint +infrastructure. Instead of Tracepoint, this tracer is based on kprobes(kprobe +and kretprobe). It probes anywhere where kprobes can probe(this means, all +functions body except for __kprobes functions). + +Unlike the function tracer, this tracer can probe instructions inside of +kernel functions. It allows you to check which instruction has been executed. + +Unlike the Tracepoint based events tracer, this tracer can add and remove +probe points on the fly. + +Similar to the events tracer, this tracer doesn't need to be activated via +current_tracer, instead of that, just set probe points via +/debug/tracing/kprobe_events. And you can set filters on each probe events +via /debug/tracing/events/kprobes/<EVENT>/filter. + + +Synopsis of kprobe_events +------------------------- + p[:EVENT] SYMBOL[+offs|-offs]|MEMADDR [FETCHARGS] : set a probe + r[:EVENT] SYMBOL[+0] [FETCHARGS] : set a return probe + + EVENT : Event name + SYMBOL[+offs|-offs] : Symbol+offset where the probe is inserted + MEMADDR : Address where the probe is inserted + + FETCHARGS : Arguments + %REG : Fetch register REG + sN : Fetch Nth entry of stack (N >= 0) + @ADDR : Fetch memory at ADDR (ADDR should be in kernel) + @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol) + aN : Fetch function argument. (N >= 0)(*) + rv : Fetch return value.(**) + ra : Fetch return address.(**) + +|-offs(FETCHARG) : fetch memory at FETCHARG +|- offs address.(***) + + (*) aN may not correct on asmlinkaged functions and at the middle of + function body. + (**) only for return probe. + (***) this is useful for fetching a field of data structures. + + +Per-Probe Event Filtering +------------------------- + Per-probe event filtering feature allows you to set different filter on each +probe and gives you what arguments will be shown in trace buffer. If an event +name is specified right after 'p:' or 'r:' in kprobe_events, the tracer adds +an event under tracing/events/kprobes/<EVENT>, at the directory you can see +'id', 'enabled', 'format' and 'filter'. + +enabled: + You can enable/disable the probe by writing 1 or 0 on it. + +format: + It shows the format of this probe event. It also shows aliases of arguments + which you specified to kprobe_events. + +filter: + You can write filtering rules of this event. And you can use both of aliase + names and field names for describing filters. + + +Usage examples +-------------- +To add a probe as a new event, write a new definition to kprobe_events +as below. + + echo p:myprobe do_sys_open a0 a1 a2 a3 > /debug/tracing/kprobe_events + + This sets a kprobe on the top of do_sys_open() function with recording +1st to 4th arguments as "myprobe" event. + + echo r:myretprobe do_sys_open rv ra >> /debug/tracing/kprobe_events + + This sets a kretprobe on the return point of do_sys_open() function with +recording return value and return address as "myretprobe" event. + You can see the format of these events via +tracing/events/kprobes/<EVENT>/format. + + cat /debug/tracing/events/kprobes/myprobe/format +name: myprobe +ID: 23 +format: + field:unsigned short common_type; offset:0; size:2; + field:unsigned char common_flags; offset:2; size:1; + field:unsigned char common_preempt_count; offset:3; size:1; + field:int common_pid; offset:4; size:4; + field:int common_tgid; offset:8; size:4; + + field: unsigned long ip; offset:16;tsize:8; + field: int nargs; offset:24;tsize:4; + field: unsigned long arg0; offset:32;tsize:8; + field: unsigned long arg1; offset:40;tsize:8; + field: unsigned long arg2; offset:48;tsize:8; + field: unsigned long arg3; offset:56;tsize:8; + + alias: a0; original: arg0; + alias: a1; original: arg1; + alias: a2; original: arg2; + alias: a3; original: arg3; + +print fmt: "%lx: 0x%lx 0x%lx 0x%lx 0x%lx", ip, arg0, arg1, arg2, arg3 + + + You can see that the event has 4 arguments and alias expressions +corresponding to it. + + echo > /debug/tracing/kprobe_events + + This clears all probe points. and you can see the traced information via +/debug/tracing/trace. + + cat /debug/tracing/trace +# tracer: nop +# +# TASK-PID CPU# TIMESTAMP FUNCTION +# | | | | | + <...>-1447 [001] 1038282.286875: do_sys_open+0x0/0xd6: 0x3 0x7fffd1ec4440 0x8000 0x0 + <...>-1447 [001] 1038282.286878: sys_openat+0xc/0xe <- do_sys_open: 0xfffffffffffffffe 0xffffffff81367a3a + <...>-1447 [001] 1038282.286885: do_sys_open+0x0/0xd6: 0xffffff9c 0x40413c 0x8000 0x1b6 + <...>-1447 [001] 1038282.286915: sys_open+0x1b/0x1d <- do_sys_open: 0x3 0xffffffff81367a3a + <...>-1447 [001] 1038282.286969: do_sys_open+0x0/0xd6: 0xffffff9c 0x4041c6 0x98800 0x10 + <...>-1447 [001] 1038282.286976: sys_open+0x1b/0x1d <- do_sys_open: 0x3 0xffffffff81367a3a + + + Each line shows when the kernel hits a probe, and <- SYMBOL means kernel +returns from SYMBOL(e.g. "sys_open+0x1b/0x1d <- do_sys_open" means kernel +returns from do_sys_open to sys_open+0x1b). + +kernel/trace/Kconfig +0
-0
kernel/trace/Kconfig config BLK_DEV_IO_TRACE · line 411
Quoted 9 lines
kernel/trace/Kconfig +6
-0@@ -403,6 +403,18 @@ config BLK_DEV_IO_TRACE If unsure, say N. +config KPROBE_TRACER + depends on KPROBES + depends on X86 + bool "Trace kprobes" + select TRACING + select EVENT_TRACING
TRACING selects EVENT_TRACING so you don't need to do it here.
Quoted 1,266 lines
kernel/trace/Kconfig +6
-0+ help + This tracer probes everywhere where kprobes can probe it, and + records various registers and memories specified by user. + This also allows you to trace kprobe probe points as a dynamic + defined events. It provides per-probe event filtering interface. + config DYNAMIC_FTRACE bool "enable/disable ftrace tracepoints dynamically" depends on FUNCTION_TRACERkernel/trace/Makefile +1
-0@@ -52,5 +52,6 @@ obj-$(CONFIG_FTRACE_SYSCALLS) += trace_syscalls.o obj-$(CONFIG_EVENT_PROFILE) += trace_event_profile.o obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o obj-$(CONFIG_EVENT_TRACING) += trace_mm.o +obj-$(CONFIG_KPROBE_TRACER) += trace_kprobe.o libftrace-y := ftrace.okernel/trace/trace.h +22
-0@@ -40,6 +40,8 @@ enum trace_type {TRACE_KMEM_FREE ,TRACE_POWER ,TRACE_BLK , +TRACE_KPROBE , +TRACE_KRETPROBE , __TRACE_LAST_TYPE, };@@ -207,6 +209,22 @@ struct syscall_trace_exit {unsigned long ret; }; +#define TRACE_KPROBE_ARGS 6 + +struct kprobe_trace_entry { +struct trace_entry ent; +unsigned long ip; +int nargs; +unsigned long args[TRACE_KPROBE_ARGS ]; +}; + +struct kretprobe_trace_entry { +struct trace_entry ent; +unsigned long func; +unsigned long ret_ip; +int nargs; +unsigned long args[TRACE_KPROBE_ARGS ]; +};/* * trace_flag_type is an enumeration that holds different @@ -323,6 +341,10 @@ extern void __ftrace_bad_type(void); TRACE_SYSCALL_ENTER); \ IF_ASSIGN(var, ent, struct syscall_trace_exit, \TRACE_SYSCALL_EXIT ); \ + IF_ASSIGN(var, ent, struct kprobe_trace_entry, \ +TRACE_KPROBE ); \ + IF_ASSIGN(var, ent, struct kretprobe_trace_entry, \ +TRACE_KRETPROBE ); \__ftrace_bad_type (); \ }while (0 )kernel/trace/trace_event_types.h +20
-0@@ -172,4 +172,24 @@ TRACE_EVENT_FORMAT(kmem_free, TRACE_KMEM_FREE, kmemtrace_free_entry, ignore,TP_RAW_FMT ("type:%u call_site:%lx ptr:%p" ) ); +TRACE_EVENT_FORMAT (kprobe,TRACE_KPROBE , kprobe_trace_entry, ignore, +TRACE_STRUCT ( + TRACE_FIELD(unsigned long, ip, ip) + TRACE_FIELD(int, nargs, nargs) + TRACE_FIELD_SPECIAL(unsigned long args[TRACE_KPROBE_ARGS], + args,TRACE_KPROBE_ARGS , args) + ), +TP_RAW_FMT ("%08lx: args:0x%lx ..." ) +); + +TRACE_EVENT_FORMAT (kretprobe,TRACE_KRETPROBE , kretprobe_trace_entry, ignore, +TRACE_STRUCT ( + TRACE_FIELD(unsigned long, func, func) + TRACE_FIELD(unsigned long, ret_ip, ret_ip) + TRACE_FIELD(int, nargs, nargs) + TRACE_FIELD_SPECIAL(unsigned long args[TRACE_KPROBE_ARGS], + args,TRACE_KPROBE_ARGS , args) + ), +TP_RAW_FMT ("%08lx <- %08lx: args:0x%lx ..." ) +);#undef TRACE_SYSTEMkernel/trace/trace_kprobe.c new file +1164
-0@@ -0,0 +1,1174 @@ +/* +* kprobe based kernel tracer +* +* Created by Masami Hiramatsu <[email protected]> +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/kprobes.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include <linux/debugfs.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/ptrace.h> + +#include "trace.h" +#include "trace_output.h" + +#define MAX_ARGSTR_LEN 63 + +/* currently, trace_kprobe only supports X86. */ + +struct fetch_func { +unsigned long (*func)(struct pt_regs *,void *); +void *data; +}; + +static __kprobes unsigned long call_fetch (struct fetch_func *f, +struct pt_regs *regs) +{ +return f->func (regs, f->data); +} + +/* fetch handlers */ +static __kprobes unsigned long fetch_register (struct pt_regs *regs, +void *offset) +{ +return get_register (regs, (unsigned )((unsigned long )offset)); +} + +static __kprobes unsigned long fetch_stack (struct pt_regs *regs, +void *num) +{ +return get_kernel_stack_nth (regs, (unsigned )((unsigned long )num)); +} + +static __kprobes unsigned long fetch_memory (struct pt_regs *regs,void *addr) +{ +unsigned long retval; +if (probe_kernel_address (addr, retval)) +return 0 ; +return retval; +} + +static __kprobes unsigned long fetch_argument (struct pt_regs *regs,void *num) +{ +return get_argument_nth (regs, (unsigned )((unsigned long )num)); +} + +static __kprobes unsigned long fetch_retvalue (struct pt_regs *regs, +void *dummy) +{ +return regs_return_value (regs); +} + +static __kprobes unsigned long fetch_ip (struct pt_regs *regs,void *dummy) +{ +return instruction_pointer (regs); +} + +/* Memory fetching by symbol */ +struct symbol_cache { +char *symbol; +long offset; +unsigned long addr; +}; + +static unsigned long update_symbol_cache (struct symbol_cache *sc) +{ + sc->addr = (unsigned long )kallsyms_lookup_name (sc->symbol); +if (sc->addr) + sc->addr += sc->offset; +return sc->addr; +} + +static void free_symbol_cache (struct symbol_cache *sc) +{ +kfree (sc->symbol); +kfree (sc); +} + +static struct symbol_cache *alloc_symbol_cache (const char *sym,long offset) +{ +struct symbol_cache *sc; +if (!sym ||strlen (sym) ==0 ) +return NULL ; + sc =kzalloc (sizeof (struct symbol_cache ),GFP_KERNEL ); +if (!sc) +return NULL ; + + sc->symbol =kstrdup (sym,GFP_KERNEL ); +if (!sc->symbol) { +kfree (sc); +return NULL ; + } + sc->offset = offset; + +update_symbol_cache (sc); +return sc; +} + +static __kprobes unsigned long fetch_symbol (struct pt_regs *regs,void *data) +{ +struct symbol_cache *sc = data; +if (sc->addr) +return fetch_memory (regs, (void *)sc->addr); +else +return 0 ; +} + +/* Special indirect memory access interface */ +struct indirect_fetch_data { +struct fetch_func orig; +long offset; +}; + +static __kprobes unsigned long fetch_indirect (struct pt_regs *regs,void *data) +{ +struct indirect_fetch_data *ind = data; +unsigned long addr; + addr =call_fetch (&ind->orig, regs); +if (addr) { + addr += ind->offset; +return fetch_memory (regs, (void *)addr); + }else +return 0 ; +} + +static __kprobes void free_indirect_fetch_data (struct indirect_fetch_data *data) +{ +if (data->orig.func == fetch_indirect) +free_indirect_fetch_data (data->orig.data); +else if (data->orig.func == fetch_symbol) +free_symbol_cache (data->orig.data); +kfree (data); +} + +/** +* kprobe_trace_core +*/ + +struct trace_probe { +struct list_head list; +union { +struct kprobe kp; +struct kretprobe rp; + }; +const char *symbol;/* symbol name */ +unsigned int nr_args; +struct fetch_func args[TRACE_KPROBE_ARGS ]; +struct ftrace_event_call call; +}; + +static int kprobe_trace_func (struct kprobe *kp,struct pt_regs *regs); +static int kretprobe_trace_func (struct kretprobe_instance *ri, +struct pt_regs *regs); + +static __kprobes int probe_is_return (struct trace_probe *tp) +{ +return (tp->rp.handler == kretprobe_trace_func); +} + +static __kprobes const char *probe_symbol (struct trace_probe *tp) +{ +return tp->symbol ? tp->symbol :"unknown" ; +} + +static __kprobes long probe_offset (struct trace_probe *tp) +{ +return (probe_is_return (tp)) ? tp->rp.kp.offset : tp->kp.offset; +} + +static __kprobes void *probe_address (struct trace_probe *tp) +{ +return (probe_is_return (tp)) ? tp->rp.kp.addr : tp->kp.addr; +} + +static int trace_arg_string (char *buf,size_t n,struct fetch_func *ff) +{ +int ret = -EINVAL ; +if (ff->func == fetch_argument) + ret =snprintf (buf, n,"a%lu" , (unsigned long )ff->data); +else if (ff->func == fetch_register) { +const char *name; + name =query_register_name ((unsigned )((long )ff->data)); + ret =snprintf (buf, n,"%%%s" , name); + }else if (ff->func == fetch_stack) + ret =snprintf (buf, n,"s%lu" , (unsigned long )ff->data); +else if (ff->func == fetch_memory) + ret =snprintf (buf, n,"@0x%p" , ff->data); +else if (ff->func == fetch_symbol) { +struct symbol_cache *sc = ff->data; + ret =snprintf (buf, n,"@%s%+ld" , sc->symbol, sc->offset); + }else if (ff->func == fetch_retvalue) + ret =snprintf (buf, n,"rv" ); +else if (ff->func == fetch_ip) + ret =snprintf (buf, n,"ra" ); +else if (ff->func == fetch_indirect) { +struct indirect_fetch_data *id = ff->data; + ret =snprintf (buf, n,"%+ld(" , id->offset); +if (ret > n) + goto end; + n -= ret; + ret =trace_arg_string (buf, n, &id->orig); +if (ret > n) + goto end; + n -= ret; + ret =snprintf (buf, n,")" ); + } +end: +if (ret > n) +return -ENOSPC ; +return 0 ; +} + +static int register_probe_event (struct trace_probe *tp); +static void unregister_probe_event (struct trace_probe *tp); + +static DEFINE_MUTEX (probe_lock); +static LIST_HEAD (probe_list); + +static struct trace_probe *alloc_trace_probe (const char *symbol, +const char *event) +{ +struct trace_probe *tp; + + tp =kzalloc (sizeof (struct trace_probe ),GFP_KERNEL ); +if (!tp) +return ERR_PTR (-ENOMEM ); + +if (symbol) { + tp->symbol =kstrdup (symbol,GFP_KERNEL ); +if (!tp->symbol) + goto error; + } +if (event) { + tp->call.name =kstrdup (event,GFP_KERNEL ); +if (!tp->call.name) + goto error; + } + +INIT_LIST_HEAD (&tp->list); +return tp; +error: +kfree (tp->symbol); +kfree (tp); +return ERR_PTR (-ENOMEM ); +} + +static void free_trace_probe (struct trace_probe *tp) +{ +int i; +for (i =0 ; i < tp->nr_args; i++) +if (tp->args[i].func == fetch_symbol) +free_symbol_cache (tp->args[i].data); +else if (tp->args[i].func == fetch_indirect) +free_indirect_fetch_data (tp->args[i].data); + +kfree (tp->call.name); +kfree (tp->symbol); +kfree (tp); +} + +static struct trace_probe *find_probe_event (const char *event) +{ +struct trace_probe *tp; +list_for_each_entry (tp, &probe_list, list) +if (tp->call.name && !strcmp (tp->call.name, event)) +return tp; +return NULL ; +} + +static void __unregister_trace_probe (struct trace_probe *tp) +{ +if (probe_is_return (tp)) +unregister_kretprobe (&tp->rp); +else +unregister_kprobe (&tp->kp); +} + +/* Unregister a trace_probe and probe_event: call with locking probe_lock */ +static void unregister_trace_probe (struct trace_probe *tp) +{ +if (tp->call.name) +unregister_probe_event (tp); +__unregister_trace_probe (tp); +list_del (&tp->list); +} + +/* Register a trace_probe and probe_event */ +static int register_trace_probe (struct trace_probe *tp) +{ +struct trace_probe *old_tp; +int ret; + +mutex_lock (&probe_lock); + +if (probe_is_return (tp)) + ret =register_kretprobe (&tp->rp); +else + ret =register_kprobe (&tp->kp); + +if (ret) { +pr_warning ("Could not insert probe(%d)\n" , ret); +if (ret == -EILSEQ ) { +pr_warning ("Probing address(0x%p) is not an " +"instruction boundary.\n" , +probe_address (tp)); + ret = -EINVAL ; + } + goto end; + } +/* register as an event */ +if (tp->call.name) { + old_tp =find_probe_event (tp->call.name); +if (old_tp) { +/* delete old event */ +unregister_trace_probe (old_tp); +free_trace_probe (old_tp); + } + ret =register_probe_event (tp); +if (ret) { +pr_warning ("Faild to register probe event(%d)\n" , ret); +__unregister_trace_probe (tp); + } + } +list_add_tail (&tp->list, &probe_list); +end: +mutex_unlock (&probe_lock); +return ret; +} + +/* Split symbol and offset. */ +static int split_symbol_offset (char *symbol,long *offset) +{ +char *tmp; +int ret; + +if (!offset) +return -EINVAL ; + + tmp =strchr (symbol,'+' ); +if (!tmp) + tmp =strchr (symbol,'-' ); + +if (tmp) { +/* skip sign because strict_strtol doesn't accept '+' */ + ret =strict_strtol (tmp +1 ,0 , offset); +if (ret) +return ret; +if (*tmp =='-' ) + *offset = -(*offset); + *tmp ='\0' ; + }else + *offset =0 ; +return 0 ; +} + +#define PARAM_MAX_ARGS 16 +#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) + +static int parse_trace_arg (char *arg,struct fetch_func *ff,int is_return) +{ +int ret =0 ; +unsigned long param; +long offset; +char *tmp; + +switch (arg[0 ]) { +case 'a' :/* argument */ + ret =strict_strtoul (arg +1 ,10 , ¶m); +if (ret || param >PARAM_MAX_ARGS ) + ret = -EINVAL ; +else { + ff->func = fetch_argument; + ff->data = (void *)param; + } +break ; +case 'r' :/* retval or retaddr */ +if (is_return && arg[1 ] =='v' ) { + ff->func = fetch_retvalue; + ff->data =NULL ; + }else if (is_return && arg[1 ] =='a' ) { + ff->func = fetch_ip; + ff->data =NULL ; + }else + ret = -EINVAL ; +break ; +case '%' :/* named register */ + ret =query_register_offset (arg +1 ); +if (ret >=0 ) { + ff->func = fetch_register; + ff->data = (void *)(unsigned long )ret; + ret =0 ; + } +break ; +case 's' :/* stack */ + ret =strict_strtoul (arg +1 ,10 , ¶m); +if (ret || param >PARAM_MAX_STACK ) + ret = -EINVAL ; +else { + ff->func = fetch_stack; + ff->data = (void *)param; + } +break ; +case '@' :/* memory or symbol */ +if (isdigit (arg[1 ])) { + ret =strict_strtoul (arg +1 ,0 , ¶m); +if (ret) +break ; + ff->func = fetch_memory; + ff->data = (void *)param; + }else { + ret =split_symbol_offset (arg +1 , &offset); +if (ret) +break ; + ff->data =alloc_symbol_cache (arg +1 , + offset); +if (ff->data) + ff->func = fetch_symbol; +else + ret = -EINVAL ; + } +break ; +case '+' :/* indirect memory */ +case '-' : + tmp =strchr (arg,'(' ); +if (!tmp) { + ret = -EINVAL ; +break ; + } + *tmp ='\0' ; + ret =strict_strtol (arg +1 ,0 , &offset); +if (ret) +break ; +if (arg[0 ] =='-' ) + offset = -offset; + arg = tmp +1 ; + tmp =strrchr (arg,')' ); +if (tmp) { +struct indirect_fetch_data *id; + *tmp ='\0' ; + id =kzalloc (sizeof (struct indirect_fetch_data ), +GFP_KERNEL ); +if (!id) +return -ENOMEM ; + id->offset = offset; + ret =parse_trace_arg (arg, &id->orig, is_return); +if (ret) +kfree (id); +else { + ff->func = fetch_indirect; + ff->data = (void *)id; + } + }else + ret = -EINVAL ; +break ; +default : +/* TODO: support custom handler */ + ret = -EINVAL ; + } +return ret; +} + +static int create_trace_probe (int argc,char **argv) +{ +/* +* Argument syntax: +* - Add kprobe: p[:EVENT] SYMBOL[+OFFS|-OFFS]|ADDRESS [FETCHARGS] +* - Add kretprobe: r[:EVENT] SYMBOL[+0] [FETCHARGS] +* Fetch args: +* aN : fetch Nth of function argument. (N:0-) +* rv : fetch return value +* ra : fetch return address +* sN : fetch Nth of stack (N:0-) +* @ADDR : fetch memory at ADDR (ADDR should be in kernel) +* @SYM[+|-offs] : fetch memory at SYM +|- offs (SYM is a data symbol) +* %REG : fetch register REG +* Indirect memory fetch: +* +|-offs(ARG) : fetch memory at ARG +|- offs address. +*/ +struct trace_probe *tp; +struct kprobe *kp; +int i, ret =0 ; +int is_return =0 ; +char *symbol =NULL , *event =NULL ; +long offset =0 ; +void *addr =NULL ; + +if (argc <2 ) +return -EINVAL ; + +if (argv[0 ][0 ] =='p' ) + is_return =0 ; +else if (argv[0 ][0 ] =='r' ) + is_return =1 ; +else +return -EINVAL ; + +if (argv[0 ][1 ] ==':' ) { + event = &argv[0 ][2 ]; +if (strlen (event) ==0 ) { +pr_info ("Event name is not specifiled\n" ); +return -EINVAL ; + } + } + +if (isdigit (argv[1 ][0 ])) { +if (is_return) +return -EINVAL ; +/* an address specified */ + ret =strict_strtoul (&argv[0 ][2 ],0 , (unsigned long *)&addr); +if (ret) +return ret; + }else { +/* a symbol specified */ + symbol = argv[1 ]; +/* TODO: support .init module functions */ + ret =split_symbol_offset (symbol, &offset); +if (ret) +return ret; +if (offset && is_return) +return -EINVAL ; + } + +/* setup a probe */ + tp =alloc_trace_probe (symbol, event); +if (IS_ERR (tp)) +return PTR_ERR (tp); + +if (is_return) { + kp = &tp->rp.kp; + tp->rp.handler = kretprobe_trace_func; + }else { + kp = &tp->kp; + tp->kp.pre_handler = kprobe_trace_func; + } + +if (tp->symbol) { + kp->symbol_name = tp->symbol; + kp->offset = offset; + }else + kp->addr = addr; + +/* parse arguments */ + argc -=2 ; argv +=2 ; ret =0 ; +for (i =0 ; i < argc && i <TRACE_KPROBE_ARGS ; i++) { +if (strlen (argv[i]) >MAX_ARGSTR_LEN ) { +pr_info ("Argument%d(%s) is too long.\n" , i, argv[i]); + ret = -ENOSPC ; + goto error; + } + ret =parse_trace_arg (argv[i], &tp->args[i], is_return); +if (ret) + goto error; + } + tp->nr_args = i; + + ret =register_trace_probe (tp); +if (ret) + goto error; +return 0 ; + +error: +free_trace_probe (tp); +return ret; +} + +static void cleanup_all_probes (void ) +{ +struct trace_probe *tp; +mutex_lock (&probe_lock); +/* TODO: Use batch unregistration */ +while (!list_empty (&probe_list)) { + tp = list_entry(probe_list.next, struct trace_probe, list); +unregister_trace_probe (tp); +free_trace_probe (tp); + } +mutex_unlock (&probe_lock); +} + + +/* Probes listing interfaces */ +static void *probes_seq_start (struct seq_file *m,loff_t *pos) +{ +mutex_lock (&probe_lock); +return seq_list_start (&probe_list, *pos); +} + +static void *probes_seq_next (struct seq_file *m,void *v,loff_t *pos) +{ +return seq_list_next (v, &probe_list, pos); +} + +static void probes_seq_stop (struct seq_file *m,void *v) +{ +mutex_unlock (&probe_lock); +} + +static int probes_seq_show (struct seq_file *m,void *v) +{ +struct trace_probe *tp = v; +int i, ret; +char buf[MAX_ARGSTR_LEN +1 ]; + +if (tp ==NULL ) +return 0 ; + +seq_printf (m,"%c" ,probe_is_return (tp) ?'r' :'p' ); +if (tp->call.name) +seq_printf (m,":%s" , tp->call.name); + +if (tp->symbol) +seq_printf (m," %s%+ld" ,probe_symbol (tp),probe_offset (tp)); +else +seq_printf (m," 0x%p" ,probe_address (tp)); + +for (i =0 ; i < tp->nr_args; i++) { + ret =trace_arg_string (buf,MAX_ARGSTR_LEN , &tp->args[i]); +if (ret) { +pr_warning ("Argument%d is too long.\n" , i); +break ; + } +seq_printf (m," %s" , buf); + } +seq_printf (m,"\n" ); +return 0 ; +} + +static const struct seq_operations probes_seq_op = { + .start = probes_seq_start, + .next = probes_seq_next, + .stop = probes_seq_stop, + .show = probes_seq_show +}; + +static int probes_open (struct inode *inode,struct file *file) +{ +if ((file->f_mode &FMODE_WRITE ) && + !(file->f_flags &O_APPEND )) +cleanup_all_probes (); + +return seq_open (file, &probes_seq_op); +} + +static int command_trace_probe (const char *buf) +{ +char **argv; +int argc =0 , ret =0 ; + + argv =argv_split (GFP_KERNEL , buf, &argc); +if (!argv) +return -ENOMEM ; + +if (argc) + ret =create_trace_probe (argc, argv); + +argv_free (argv); +return ret; +} + +#define WRITE_BUFSIZE 128 + +static ssize_t probes_write (struct file *file,const char __user *buffer, +size_t count,loff_t *ppos) +{ +char *kbuf, *tmp; +int ret; +size_t done; +size_t size; + +if (!count || count <0 ) +return 0 ; + + kbuf =kmalloc (WRITE_BUFSIZE ,GFP_KERNEL ); +if (!kbuf) +return -ENOMEM ; + + ret = done =0 ; +do { + size = count - done; +if (size >WRITE_BUFSIZE ) + size =WRITE_BUFSIZE ; +if (copy_from_user (kbuf, buffer + done, size)) { + ret = -EFAULT ; + goto out; + } + kbuf[size] ='\0' ; + tmp =strchr (kbuf,'\n' ); +if (!tmp) { +pr_warning ("Line length is too long: " +"Should be less than %d." ,WRITE_BUFSIZE ); + ret = -EINVAL ; + goto out; + } + *tmp ='\0' ; + size = tmp - kbuf +1 ; + done += size; +/* Remove comments */ + tmp =strchr (kbuf,'#' ); +if (tmp) + *tmp ='\0' ; + + ret =command_trace_probe (kbuf); +if (ret) + goto out; + + }while (done < count); + ret = done; +out: +kfree (kbuf); +return ret; +} + +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, +}; + +/* Kprobe handler */ +static __kprobes int kprobe_trace_func (struct kprobe *kp,struct pt_regs *regs) +{ + struct trace_probe *tp = container_of(kp, struct trace_probe, kp); +struct kprobe_trace_entry *entry; +struct ring_buffer_event *event; +int size, i, pc; +unsigned long irq_flags; +struct ftrace_event_call *call = &event_kprobe; +if (&tp->call.name) + call = &tp->call; + +local_save_flags (irq_flags); + pc =preempt_count (); + + size =sizeof (struct kprobe_trace_entry ) - + (sizeof (unsigned long ) * (TRACE_KPROBE_ARGS - tp->nr_args)); + + event =trace_current_buffer_lock_reserve (TRACE_KPROBE , size, + irq_flags, pc); +if (!event) +return 0 ; + + entry =ring_buffer_event_data (event); + entry->nargs = tp->nr_args; + entry->ip = (unsigned long )kp->addr; +for (i =0 ; i < tp->nr_args; i++) + entry->args[i] =call_fetch (&tp->args[i], regs); + +if (!filter_current_check_discard (call, entry, event)) +trace_nowake_buffer_unlock_commit (event, irq_flags, pc); +return 0 ; +} + +/* Kretprobe handler */ +static __kprobes int kretprobe_trace_func (struct kretprobe_instance *ri, +struct pt_regs *regs) +{ + struct trace_probe *tp = container_of(ri->rp, struct trace_probe, rp); +struct kretprobe_trace_entry *entry; +struct ring_buffer_event *event; +int size, i, pc; +unsigned long irq_flags; +struct ftrace_event_call *call = &event_kretprobe; +if (&tp->call.name) + call = &tp->call; + +local_save_flags (irq_flags); + pc =preempt_count (); + + size =sizeof (struct kretprobe_trace_entry ) - + (sizeof (unsigned long ) * (TRACE_KPROBE_ARGS - tp->nr_args)); + + event =trace_current_buffer_lock_reserve (TRACE_KRETPROBE , size, + irq_flags, pc); +if (!event) +return 0 ; + + entry =ring_buffer_event_data (event); + entry->nargs = tp->nr_args; + entry->func = (unsigned long )probe_address (tp); + entry->ret_ip = (unsigned long )ri->ret_addr; +for (i =0 ; i < tp->nr_args; i++) + entry->args[i] =call_fetch (&tp->args[i], regs); + +if (!filter_current_check_discard (call, entry, event)) +trace_nowake_buffer_unlock_commit (event, irq_flags, pc); + +return 0 ; +} + +/* Event entry printers */ +enum print_line_t +print_kprobe_event (struct trace_iterator *iter,int flags) +{ +struct kprobe_trace_entry *field; +struct trace_seq *s = &iter->seq; +int i; + +trace_assign_type (field, iter->ent); + +if (!seq_print_ip_sym (s, field->ip, flags |TRACE_ITER_SYM_OFFSET )) + goto partial; + +if (!trace_seq_puts (s,":" )) + goto partial; + +for (i =0 ; i < field->nargs; i++) +if (!trace_seq_printf (s," 0x%lx" , field->args[i])) + goto partial; + +if (!trace_seq_puts (s,"\n" )) + goto partial; + +return TRACE_TYPE_HANDLED ; +partial: +return TRACE_TYPE_PARTIAL_LINE ; +} + +enum print_line_t +print_kretprobe_event (struct trace_iterator *iter,int flags) +{ +struct kretprobe_trace_entry *field; +struct trace_seq *s = &iter->seq; +int i; + +trace_assign_type (field, iter->ent); + +if (!seq_print_ip_sym (s, field->ret_ip, flags |TRACE_ITER_SYM_OFFSET )) + goto partial; + +if (!trace_seq_puts (s," <- " )) + goto partial; + +if (!seq_print_ip_sym (s, field->func, flags & ~TRACE_ITER_SYM_OFFSET )) + goto partial; + +if (!trace_seq_puts (s,":" )) + goto partial; + +for (i =0 ; i < field->nargs; i++) +if (!trace_seq_printf (s," 0x%lx" , field->args[i])) + goto partial; + +if (!trace_seq_puts (s,"\n" )) + goto partial; + +return TRACE_TYPE_HANDLED ; +partial: +return TRACE_TYPE_PARTIAL_LINE ; +} + +static struct trace_event kprobe_trace_event = { + .type =TRACE_KPROBE , + .trace = print_kprobe_event, +}; + +static struct trace_event kretprobe_trace_event = { + .type =TRACE_KRETPROBE , + .trace = print_kretprobe_event, +}; + +static int probe_event_enable (struct ftrace_event_call *call) +{ + struct trace_probe *tp = container_of(call, struct trace_probe, call); +if (probe_is_return (tp)) +return enable_kretprobe (&tp->rp); +else +return enable_kprobe (&tp->kp); +} + +static void probe_event_disable (struct ftrace_event_call *call) +{ + struct trace_probe *tp = container_of(call, struct trace_probe, call); +if (probe_is_return (tp)) +disable_kretprobe (&tp->rp); +else +disable_kprobe (&tp->kp); +} + +static int probe_event_raw_init (struct ftrace_event_call *event_call) +{ +INIT_LIST_HEAD (&event_call->fields); +init_preds (event_call); +return 0 ; +} + +#undef DEFINE_FIELD +#define DEFINE_FIELD (type, item, name, is_signed) \ + do { \ + ret = trace_define_field(event_call, #type, name, \ + offsetof(typeof(field), item), \ + sizeof(field.item), is_signed);\ + if (ret) \ + return ret; \ + } while (0) + +static int kprobe_event_define_fields (struct ftrace_event_call *event_call) +{ +int ret, i; +struct kprobe_trace_entry field; +char buf[MAX_ARGSTR_LEN +1 ]; +struct trace_probe *tp =container_of (event_call, + struct trace_probe, call); + +__common_field (int, type,1 ); + __common_field(unsigned char, flags, 0); + __common_field(unsigned char, preempt_count, 0); +__common_field (int, pid,1 ); +__common_field (int, tgid,1 ); + + DEFINE_FIELD(unsigned long, ip, "ip", 0); +DEFINE_FIELD (int, nargs,"nargs" ,1 ); +for (i =0 ; i < tp->nr_args; i++) { +/* Set argN as a field */ +sprintf (buf,"arg%d" , i); + DEFINE_FIELD(unsigned long, args[i], buf, 0); +/* Set argument string as an alias field */ + ret =trace_arg_string (buf,MAX_ARGSTR_LEN , &tp->args[i]); +if (ret) +return ret; + DEFINE_FIELD(unsigned long, args[i], buf, 0); + } +return 0 ; +} + +static int kretprobe_event_define_fields (struct ftrace_event_call *event_call) +{ +int ret, i; +struct kretprobe_trace_entry field; +char buf[MAX_ARGSTR_LEN +1 ]; +struct trace_probe *tp =container_of (event_call, + struct trace_probe, call); + +__common_field (int, type,1 ); + __common_field(unsigned char, flags, 0); + __common_field(unsigned char, preempt_count, 0); +__common_field (int, pid,1 ); +__common_field (int, tgid,1 ); + + DEFINE_FIELD(unsigned long, func, "func", 0); + DEFINE_FIELD(unsigned long, ret_ip, "ret_ip", 0); +DEFINE_FIELD (int, nargs,"nargs" ,1 ); +for (i =0 ; i < tp->nr_args; i++) { +/* Set argN as a field */ +sprintf (buf,"arg%d" , i); + DEFINE_FIELD(unsigned long, args[i], buf, 0); +/* Set argument string as an alias field */ + ret =trace_arg_string (buf,MAX_ARGSTR_LEN , &tp->args[i]); +if (ret) +return ret; + DEFINE_FIELD(unsigned long, args[i], buf, 0); + } +return 0 ; +} + +#undef SHOW_FIELD +#define SHOW_FIELD (type, item, name) \ + do { \ + ret = trace_seq_printf(s, "\tfield: " #type " %s;\t" \ + "offset:%u;tsize:%u;\n", name, \ + (unsigned)offsetof(typeof(field), item),\ + (unsigned)sizeof(type)); \ + if (!ret) \ + return 0; \ + } while (0) + +static int __probe_event_show_format (struct ftrace_event_call *event_call, +struct trace_seq *s,const char *fmt, +const char *arg) +{ + struct kprobe_trace_entry field __attribute__((unused)); +int ret, i; +char buf[MAX_ARGSTR_LEN +1 ]; +struct trace_probe *tp =container_of (event_call, + struct trace_probe, call); + +/* Show fields */ +for (i =0 ; i < tp->nr_args; i++) { +sprintf (buf,"arg%d" , i); + SHOW_FIELD(unsigned long, args[i], buf); + } +trace_seq_puts (s,"\n" ); + +/* Show aliases */ +for (i =0 ; i < tp->nr_args; i++) { +if (trace_arg_string (buf,MAX_ARGSTR_LEN , &tp->args[i])) +return 0 ; +if (!trace_seq_printf (s,"\talias: %s;\toriginal: arg%d;\n" , + buf, i)) +return 0 ; + } +/* Show format */ +if (!trace_seq_printf (s,"\nprint fmt: \"%s" , fmt)) +return 0 ; + +for (i =0 ; i < tp->nr_args; i++) +if (!trace_seq_puts (s," 0x%lx" )) +return 0 ; + +if (!trace_seq_printf (s,"\", %s" , arg)) +return 0 ; + +for (i =0 ; i < tp->nr_args; i++) +if (!trace_seq_printf (s,", arg%d" , i)) +return 0 ; + +return trace_seq_puts (s,"\n" ); +} + +static int kprobe_event_show_format (struct ftrace_event_call *call, +struct trace_seq *s) +{ + struct kprobe_trace_entry field __attribute__((unused)); +int ret; + + SHOW_FIELD(unsigned long, ip, "ip"); +SHOW_FIELD (int, nargs,"nargs" ); + +return __probe_event_show_format (call, s,"%lx:" ,"ip" ); +} + +static int kretprobe_event_show_format (struct ftrace_event_call *call, +struct trace_seq *s) +{ + struct kretprobe_trace_entry field __attribute__((unused)); +int ret; + + SHOW_FIELD(unsigned long, func, "func"); + SHOW_FIELD(unsigned long, ret_ip, "ret_ip"); +SHOW_FIELD (int, nargs,"nargs" ); + +return __probe_event_show_format (call, s,"%lx <- %lx:" , +"func, ret_ip" ); +} + +static int register_probe_event (struct trace_probe *tp) +{ +struct ftrace_event_call *call = &tp->call; +int ret; + +/* Initialize ftrace_event_call */ + call->system ="kprobes" ; +if (probe_is_return (tp)) { + call->event = &kretprobe_trace_event; + call->id =TRACE_KRETPROBE ; + call->raw_init = probe_event_raw_init; + call->show_format = kretprobe_event_show_format; + call->define_fields = kretprobe_event_define_fields; + }else { + call->event = &kprobe_trace_event; + call->id =TRACE_KPROBE ; + call->raw_init = probe_event_raw_init; + call->show_format = kprobe_event_show_format; + call->define_fields = kprobe_event_define_fields; + } + call->enabled =1 ; + call->regfunc = probe_event_enable; + call->unregfunc = probe_event_disable; + ret =trace_add_event_call (call); +if (ret) +pr_info ("Failed to register kprobe event: %s\n" , call->name); +return ret; +} + +static void unregister_probe_event (struct trace_probe *tp) +{ +/* +* Prevent to unregister event itself because the event is shared +* among other probes. +*/ + tp->call.event =NULL ; +trace_remove_event_call (&tp->call); +} + +/* Make a debugfs interface for controling probe points */ +static __init int init_kprobe_trace (void ) +{ +struct dentry *d_tracer; +struct dentry *entry; +int ret; + + ret =register_ftrace_event (&kprobe_trace_event); +if (!ret) { +pr_warning ("Could not register kprobe_trace_event type.\n" ); +return 0 ; + } + ret =register_ftrace_event (&kretprobe_trace_event); +if (!ret) { +pr_warning ("Could not register kretprobe_trace_event type.\n" ); +return 0 ; + } + + d_tracer =tracing_init_dentry (); +if (!d_tracer) +return 0 ; + + entry =debugfs_create_file ("kprobe_events" ,0644 , d_tracer, +NULL , &kprobe_events_ops); + +if (!entry) +pr_warning ("Could not create debugfs " +"'kprobe_events' entry\n" ); +return 0 ; +} +fs_initcall (init_kprobe_trace); + + +#ifdef CONFIG_FTRACE_STARTUP_TEST + +static int kprobe_trace_selftest_target (int a1,int a2,int a3, +int a4,int a5,int a6) +{ +return a1 + a2 + a3 + a4 + a5 + a6; +} + +static __init int kprobe_trace_self_tests_init (void ) +{ +int ret; +int (*target)(int ,int ,int ,int ,int ,int ); + target = kprobe_trace_selftest_target; + +pr_info ("Testing kprobe tracing: " ); + + ret =command_trace_probe ("p:testprobe kprobe_trace_selftest_target " +"a1 a2 a3 a4 a5 a6" ); +if (WARN_ON_ONCE (ret)) +pr_warning ("error enabling function entry\n" ); + + ret =command_trace_probe ("r:testprobe2 kprobe_trace_selftest_target " +"ra rv" ); +if (WARN_ON_ONCE (ret)) +pr_warning ("error enabling function return\n" ); + + ret =target (1 ,2 ,3 ,4 ,5 ,6 );
kernel/trace/trace_kprobe.c line 1174
Quoted 10 lines
kernel/trace/trace_kprobe.c new file +10
-0+ +cleanup_all_probes (); + +pr_cont ("OK\n" ); + return 0; +} + +late_initcall (kprobe_trace_self_tests_init); + +#endif
Very nice!
I'll pull it into my tree and play with it. Just fix up the locking issues I stated in the previous patch and the above extra select, and hopefully this will be good to go.
-- Steve
Steven Rostedt
maintainerRe: [PATCH -tip v8 7/7] tracing: add kprobe-based event tracer
In reply to Masami HiramatsuQuoted 6 lines · Masami Hiramatsu
kernel/trace/trace_kprobe.c new file +6
-0+#undef SHOW_FIELD +#define SHOW_FIELD (type, item, name) \ + do { \ + ret = trace_seq_printf(s, "\tfield: " #type " %s;\t" \ + "offset:%u;tsize:%u;\n", name, \ + (unsigned)offsetof(typeof(field), item),\
kernel/trace/trace_kprobe.c line 1001
Quoted 10 lines
kernel/trace/trace_kprobe.c new file +10
-0+ (unsigned)sizeof(type)); \ +if (!ret) \ +return 0 ; \ + }while (0 ) + +static int __probe_event_show_format(struct ftrace_event_call *event_call, +struct trace_seq *s,const char *fmt, +const char *arg) +{ + struct kprobe_trace_entry field __attribute__((unused));
You use kprobe_trace_entry for both kprobe and kretprobe.
Quoted 53 lines
kernel/trace/trace_kprobe.c new file +53
-0+int ret, i; +char buf[MAX_ARGSTR_LEN +1 ]; +struct trace_probe *tp =container_of (event_call, + struct trace_probe, call); + +/* Show fields */ +for (i =0 ; i < tp->nr_args; i++) { +sprintf (buf,"arg%d" , i); + SHOW_FIELD(unsigned long, args[i], buf); + } +trace_seq_puts (s,"\n" ); + +/* Show aliases */ +for (i =0 ; i < tp->nr_args; i++) { +if (trace_arg_string (buf,MAX_ARGSTR_LEN , &tp->args[i])) +return 0 ; +if (!trace_seq_printf (s,"\talias: %s;\toriginal: arg%d;\n" , + buf, i)) +return 0 ; + } +/* Show format */ +if (!trace_seq_printf (s,"\nprint fmt: \"%s" , fmt)) +return 0 ; + +for (i =0 ; i < tp->nr_args; i++) +if (!trace_seq_puts (s," 0x%lx" )) +return 0 ; + +if (!trace_seq_printf (s,"\", %s" , arg)) +return 0 ; + +for (i =0 ; i < tp->nr_args; i++) +if (!trace_seq_printf (s,", arg%d" , i)) +return 0 ; + +return trace_seq_puts (s,"\n" ); +} + +static int kprobe_event_show_format (struct ftrace_event_call *call, +struct trace_seq *s) +{ + struct kprobe_trace_entry field __attribute__((unused)); +int ret; + + SHOW_FIELD(unsigned long, ip, "ip"); +SHOW_FIELD (int, nargs,"nargs" ); + +return __probe_event_show_format (call, s,"%lx:" ,"ip" ); +} + +static int kretprobe_event_show_format (struct ftrace_event_call *call, +struct trace_seq *s) +{
kernel/trace/trace_kprobe.c line 1064
Quoted 10 lines
kernel/trace/trace_kprobe.c new file +10
-0+ struct kretprobe_trace_entry field __attribute__((unused)); +int ret; + + SHOW_FIELD(unsigned long, func, "func"); + SHOW_FIELD(unsigned long, ret_ip, "ret_ip"); +SHOW_FIELD (int, nargs,"nargs" ); + +return __probe_event_show_format (call, s,"%lx <- %lx:" , +"func, ret_ip" ); +}
Thus we end up with:
format:
field:unsigned short common_type; offset:0; size:2; field:unsigned char common_flags; offset:2; size:1; field:unsigned char common_preempt_count; offset:3; size:1; field:int common_pid; offset:4; size:4; field:int common_tgid; offset:8; size:4; field: unsigned long func; offset:16;tsize:8; field: unsigned long ret_ip; offset:24;tsize:8; field: int nargs; offset:32;tsize:4; field: unsigned long arg0; offset:32;tsize:8; field: unsigned long arg1; offset:40;tsize:8; field: unsigned long arg2; offset:48;tsize:8; field: unsigned long arg3; offset:56;tsize:8;
Notice that nargs and arg0 are both at offest 32.
-- Steve
Re: [PATCH -tip v8 7/7] tracing: add kprobe-based event tracer
Quoted 14 lines · Steven Rostedt
Quoted 10 lines · Masami Hiramatsu
kernel/trace/trace_kprobe.c new file +10
-0+#undef SHOW_FIELD +#define SHOW_FIELD (type, item, name) \ + do { \ + ret = trace_seq_printf(s, "\tfield: " #type " %s;\t" \ + "offset:%u;tsize:%u;\n", name, \ + (unsigned)offsetof(typeof(field), item),\ + (unsigned)sizeof(type)); \ + if (!ret) \ + return 0; \ + } while (0)
Quoted 8 lines
kernel/trace/trace_kprobe.c line 1001
Quoted 6 lines
kernel/trace/trace_kprobe.c new file +6
-0+ +static int __probe_event_show_format (struct ftrace_event_call *event_call, +struct trace_seq *s,const char *fmt, +const char *arg) +{ + struct kprobe_trace_entry field __attribute__((unused));You use
kprobe_trace_entryfor both kprobe and kretprobe.
Ah, right. I'll fix that.
Thank you for reviewing!
Quoted 87 lines
Quoted 54 lines
kernel/trace/trace_kprobe.c new file +53
-0+int ret, i; +char buf[MAX_ARGSTR_LEN +1 ]; +struct trace_probe *tp =container_of (event_call, + struct trace_probe, call); + +/* Show fields */ +for (i =0 ; i < tp->nr_args; i++) { +sprintf (buf,"arg%d" , i); + SHOW_FIELD(unsigned long, args[i], buf); + } +trace_seq_puts (s,"\n" ); + +/* Show aliases */ +for (i =0 ; i < tp->nr_args; i++) { +if (trace_arg_string (buf,MAX_ARGSTR_LEN , &tp->args[i])) +return 0 ; +if (!trace_seq_printf (s,"\talias: %s;\toriginal: arg%d;\n" , + buf, i)) +return 0 ; + } +/* Show format */ +if (!trace_seq_printf (s,"\nprint fmt: \"%s" , fmt)) +return 0 ; + +for (i =0 ; i < tp->nr_args; i++) +if (!trace_seq_puts (s," 0x%lx" )) +return 0 ; + +if (!trace_seq_printf (s,"\", %s" , arg)) +return 0 ; + +for (i =0 ; i < tp->nr_args; i++) +if (!trace_seq_printf (s,", arg%d" , i)) +return 0 ; + +return trace_seq_puts (s,"\n" ); +} + +static int kprobe_event_show_format (struct ftrace_event_call *call, +struct trace_seq *s) +{ + struct kprobe_trace_entry field __attribute__((unused)); +int ret; + + SHOW_FIELD(unsigned long, ip, "ip"); +SHOW_FIELD (int, nargs,"nargs" ); + +return __probe_event_show_format (call, s,"%lx:" ,"ip" ); +} + +static int kretprobe_event_show_format (struct ftrace_event_call *call, +struct trace_seq *s) +{kernel/trace/trace_kprobe.c line 1064
Quoted 10 lines
kernel/trace/trace_kprobe.c new file +10
-0+ struct kretprobe_trace_entry field __attribute__((unused)); +int ret; + + SHOW_FIELD(unsigned long, func, "func"); + SHOW_FIELD(unsigned long, ret_ip, "ret_ip"); +SHOW_FIELD (int, nargs,"nargs" ); + +return __probe_event_show_format (call, s,"%lx <- %lx:" , +"func, ret_ip" ); +}Thus we end up with:
format:
field:unsigned short common_type; offset:0; size:2; field:unsigned char common_flags; offset:2; size:1; field:unsigned char common_preempt_count; offset:3; size:1; field:int common_pid; offset:4; size:4; field:int common_tgid; offset:8; size:4; field: unsigned long func; offset:16;tsize:8; field: unsigned long ret_ip; offset:24;tsize:8; field: int nargs; offset:32;tsize:4; field: unsigned long arg0; offset:32;tsize:8; field: unsigned long arg1; offset:40;tsize:8; field: unsigned long arg2; offset:48;tsize:8; field: unsigned long arg3; offset:56;tsize:8;Notice that nargs and arg0 are both at offest 32.
-- Steve
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America) Inc. Software Solutions Division e-mail: [email protected]
Re: [PATCH -tip v8 7/7] tracing: add kprobe-based event tracer
In reply to Masami HiramatsuQuoted 1 line · Masami Hiramatsu
Add kprobes-based event tracer on ftrace.
Wouldn't it make more sense to call this the dynamic event tracer?
The use of kprobes is more an implementation detail than something the user cares about.
Re: [PATCH -tip v8 7/7] tracing: add kprobe-based event tracer
Quoted 7 lines · Christoph Hellwig
Quoted 1 line · Masami Hiramatsu
Add kprobes-based event tracer on ftrace.
Wouldn't it make more sense to call this the dynamic event tracer?
The use of kprobes is more an implementation detail than something the user cares about.
Hmm, I don't think so, because other tracers (e.g. hw breakpoint tracer) can also add their events dynamically by trace_add/remove_event_call. It's more flexible than this tracer includes those events.
Thank you,
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America) Inc. Software Solutions Division e-mail: [email protected]
Small question to start with: What's your (or Hitachi s/Red Hat's) use case for this? It's obviously really cool technology, but I fear without some good user space side to make it easy to use it will most likely bit-rot which would be sad.
Quoted 4 lines · Christoph Hellwig
Small question to start with: What's your (or Hitachi s/Red Hat's) use case for this? It's obviously really cool technology, but I fear without some good user space side to make it easy to use it will most likely bit-rot which would be sad.
Good question,
Frankly to say, this was started just for the in-kernel user of kprobes. However, now I think this can be used for some ideas,
- Quick debugging (checking variables, execution path, etc.) in asm level. (elfutils might help us to make it source level debugging)
- Prototyping new tracepoints.
- Stress test of kprobes to find functions which should be __kprobes.
- Vehicle of tracing facilities which will be ported from systemtap runtime.
- As Frank said, we can make systemtap to control this for simple scripts.
Even now, it's not hard to use systemtap for finding variables in stack/registers. Key options are -v and -p2,
e.g.
$ stap -v -p2 -e 'probe kernel.statement("*@fs/open.c:1024")\
{printf("%d", $files)}'Then, you'll get below output,
------
# functions
_dwarf_tvar_get_files_0:long ()
%{{
{
uint64_t addr;
{ // DWARF expression: 0x5c
{ uint64_t value = fetch_register (12);addr = value; }
}
THIS->__retvalue = addr;
}
goto out;
if (0) goto deref_fault;
deref_fault:
goto out;
}
/* pure */%}
# probes
kernel.statement("fd_install@/home/mhiramat/ksrc/linux-2.6-tip/fs/open.c:1024")
/* pc=_stext+0xd1f72 */ /* <- kernel.statement("*@fs/open.c:1024") */
printf("%d", _dwarf_tvar_get_files_0())
------This shows that 'fs/open.c:1024' is located at _stext+0xd1f72, and 'files' is mapped to %r12 (note, pt_regs mapping is different from dwarf register mapping.) So, below command adds probe at fs/open.c:1024 and trace 'files' variable :-)
$ echo p _stext+0xd1f72 %r12 > /debug/tracing/kprobe_events
Thank you,
Signature · 7 lines
Masami Hiramatsu Software Engineer Hitachi Computer Products (America) Inc. Software Solutions Division e-mail: [email protected]