[PATCH 2/6] trace: add support for 32 bit compat syscalls on x86_64

Vaibhav Nagarnaik
2012-03-28T21:12:10Z

From: Michael Davidson <md@google.com>

Add support for a set of events to trace 32 bit compat system calls
in addition to the native 64 bit system calls.

Events for compat system calls have event names of the form:
syscalls:sys_enter_compat_<name>
syscalls:sys_exit_compat_<name>

The ascii formatted version of trace events that can be read from
the tracing/trace file reports compat system calls as:
compat_<name>(...)

- add CONFIG_FTRACE_COMPAT_SYSCALLS

- add a "compat" flag to the syscall_metadata struct so that we can
distinguish between "native" and "compat" metadata at init time
when building the system call # to metadata mapping tables

- add a COMPAT_SYSCALL_METADATAx() macro to define system call
metadata for compat system calls

- modify syscall_nr_to_meta() to know about compat system calls
and return a pointer to the correct metadata

- modify print_syscall_{enter|exit}() to find the system call metadata
by looking in the containing ftrace_event_call struct

Signed-off-by: Vaibhav Nagarnaik <vnagarnaik@google.com>
---
Changelog:
* Remove unmaintainable list of syscalls and use SYSCALL_DEFINEx macro
to define the metadata for equivalent compat syscall

arch/x86/ia32/Makefile | 2 +
arch/x86/ia32/ia32_syscall_metadata.c | 84 +++++++++++++++++++++++++++++++++
include/linux/syscalls.h | 17 ++++++-
include/trace/syscall.h | 23 ++++++++-
kernel/trace/Kconfig | 6 ++
kernel/trace/trace_syscalls.c | 20 ++++++--
6 files changed, 142 insertions(+), 10 deletions(-)
create mode 100644 arch/x86/ia32/ia32_syscall_metadata.c

diff --git a/arch/x86/ia32/Makefile b/arch/x86/ia32/Makefile
index 455646e..ba6d3c8 100644
--- a/arch/x86/ia32/Makefile
+++ b/arch/x86/ia32/Makefile
@@ -12,3 +12,5 @@ obj-$(CONFIG_IA32_AOUT) += ia32_aout.o

audit-class-$(CONFIG_AUDIT) := audit.o
obj-$(CONFIG_IA32_EMULATION) += $(audit-class-y)
+
+obj-$(CONFIG_FTRACE_COMPAT_SYSCALLS) += ia32_syscall_metadata.o
diff --git a/arch/x86/ia32/ia32_syscall_metadata.c b/arch/x86/ia32/ia32_syscall_metadata.c
new file mode 100644
index 0000000..f3f554a
--- /dev/null
+++ b/arch/x86/ia32/ia32_syscall_metadata.c
@@ -0,0 +1,84 @@
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/module.h>
+#include <asm/asm-offsets.h>
+
+extern long ia32_sys_call_table[];
+
+int nr_compat_syscalls;
+struct syscall_metadata **compat_syscalls_metadata;
+
+static const char *prefixes[] = { "sys32", "stub32", "compat_sys",
+ "sys", NULL };
+
+/*
+ * For each entry in the 32 bit system call table:
+ * Look up the address in the kernel symbol table
+ * Strip off any "sys32|stub32|sys|compat" prefix
+ * Search through all of the compat metadata entries for a matching name
+ */
+static struct syscall_metadata __init *
+find_compat_syscall_meta(unsigned long addr)
+{
+ struct syscall_metadata **start;
+ struct syscall_metadata **stop;
+ char str[KSYM_SYMBOL_LEN];
+ const char *name;
+ const char **p;
+ extern struct syscall_metadata *__start_syscalls_metadata[];
+ extern struct syscall_metadata *__stop_syscalls_metadata[];
+
+ start = __start_syscalls_metadata;
+ stop = __stop_syscalls_metadata;
+ kallsyms_lookup(addr, NULL, NULL, NULL, str);
+
+ /*
+ * If there is a {sys|compat|sys32|stub32} prefix strip it off
+ */
+ for (p = prefixes, name = str; *p; p++) {
+ int len = strlen(*p);
+ if (strncmp(name, *p, len) == 0) {
+ name += len;
+ break;
+ }
+ }
+
+ for ( ; start < stop; start++) {
+ if (!(*start)->compat)
+ continue;
+
+ /*
+ * ignore the "compat_" prefix on the metadata name
+ * when doing the comparison
+ */
+ if ((*start)->name && !strcmp((*start)->name + 6, name))
+ return *start;
+ }
+ return NULL;
+}
+
+static int __init init_compat_syscall_metadata(void)
+{
+ struct syscall_metadata *meta;
+ int i;
+
+ nr_compat_syscalls = __NR_ia32_syscall_max;
+ compat_syscalls_metadata = kzalloc(sizeof(*compat_syscalls_metadata) *
+ nr_compat_syscalls, GFP_KERNEL);
+ if (!compat_syscalls_metadata) {
+ nr_compat_syscalls = -1;
+ WARN_ON(1);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < nr_compat_syscalls; i++) {
+ if ((meta = find_compat_syscall_meta(ia32_sys_call_table[i]))) {
+ meta->syscall_nr = i;
+ compat_syscalls_metadata[i] = meta;
+ }
+ }
+
+ return 0;
+}
+
+core_initcall(init_compat_syscall_metadata);
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index ed0003c..f2e4106 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -159,7 +159,7 @@ extern struct trace_event_functions exit_syscall_print_funcs;
__attribute__((section("_ftrace_events"))) \
*__event_exit_##sname = &event_exit_##sname;

-#define SYSCALL_METADATAx(x, sname, ...) \
+#define _SYSCALL_METADATAx(x, mname, sname, _compat, ...) \
static const char *types_##sname[] = { \
__SC_STR_TDECL##x(__VA_ARGS__) \
}; \
@@ -170,11 +170,12 @@ extern struct trace_event_functions exit_syscall_print_funcs;
SYSCALL_TRACE_EXIT_EVENT(sname); \
static struct syscall_metadata __used \
__syscall_meta_##sname = { \
- .name = "sys"#sname, \
+ .name = mname, \
.syscall_nr = -1, /* Filled in at boot */ \
.nb_args = x, \
.types = types_##sname, \
.args = args_##sname, \
+ .compat = _compat, \
.enter_event = &event_enter_##sname, \
.exit_event = &event_exit_##sname, \
.enter_fields = LIST_HEAD_INIT(__syscall_meta_##sname.enter_fields), \
@@ -182,10 +183,21 @@ extern struct trace_event_functions exit_syscall_print_funcs;
static struct syscall_metadata __used \
__attribute__((section("__syscalls_metadata"))) \
*__p_syscall_meta_##sname = &__syscall_meta_##sname;
+
+#define SYSCALL_METADATAx(x, sname, ...) \
+ _SYSCALL_METADATAx(x, "sys"#sname, sname, 0, __VA_ARGS__)
+
#else
#define SYSCALL_METADATAx(x, name, ...)
#endif /* CONFIG_FTRACE_SYSCALLS */

+#if defined(CONFIG_FTRACE_COMPAT_SYSCALLS)
+#define COMPAT_SYSCALL_METADATAx(x, sname, ...) \
+ _SYSCALL_METADATAx(x, "compat_"#sname, compat_##sname, 1, __VA_ARGS__)
+#else
+#define COMPAT_SYSCALL_METADATAx(x, sname, ...)
+#endif /* CONFIG_FTRACE_COMPAT_SYSCALLS */
+
#define SYSCALL_DEFINE0(name, ...) SYSCALL_DEFINEx(0, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
@@ -210,6 +222,7 @@ extern struct trace_event_functions exit_syscall_print_funcs;

#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATAx(x, sname, __VA_ARGS__) \
+ COMPAT_SYSCALL_METADATAx(x, sname, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS
diff --git a/include/trace/syscall.h b/include/trace/syscall.h
index 31966a4..29169e6 100644
--- a/include/trace/syscall.h
+++ b/include/trace/syscall.h
@@ -21,8 +21,9 @@
*/
struct syscall_metadata {
const char *name;
- int syscall_nr;
- int nb_args;
+ u16 syscall_nr;
+ u8 nb_args;
+ u8 compat;
const char **types;
const char **args;
struct list_head enter_fields;
@@ -32,6 +33,24 @@ struct syscall_metadata {
};

#ifdef CONFIG_FTRACE_SYSCALLS
+
+#ifdef CONFIG_FTRACE_COMPAT_SYSCALLS
+
+extern int nr_compat_syscalls;
+extern struct syscall_metadata **compat_syscalls_metadata;
+
+static inline struct syscall_metadata *compat_syscall_nr_to_meta(int nr)
+{
+ return (nr < nr_compat_syscalls) ? compat_syscalls_metadata[nr]
+ : NULL;
+}
+#else
+static inline struct syscall_metadata *compat_syscall_nr_to_meta(int nr)
+{
+ return NULL;
+}
+#endif /* CONFIG_FTRACE_COMPAT_SYSCALLS */
+
extern unsigned long arch_syscall_addr(int nr);
extern int init_syscall_trace(struct ftrace_event_call *call);

diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index cd31345..cd0954b 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -240,6 +240,12 @@ config FTRACE_SYSCALLS
help
Basic tracer to catch the syscall entry and exit events.

+config FTRACE_COMPAT_SYSCALLS
+ bool "Trace 32 bit compat syscalls"
+ depends on FTRACE_SYSCALLS
+ help
+ Trace syscall entry and exit events for 32 bit compat syscalls.
+
config TRACE_BRANCH_PROFILING
bool
select GENERIC_TRACER
diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
index cb65454..8bc89c5 100644
--- a/kernel/trace/trace_syscalls.c
+++ b/kernel/trace/trace_syscalls.c
@@ -5,6 +5,7 @@
#include <linux/module.h> /* for MODULE_NAME_LEN via KSYM_SYMBOL_LEN */
#include <linux/ftrace.h>
#include <linux/perf_event.h>
+#include <linux/compat.h>
#include <asm/syscall.h>

#include "trace_output.h"
@@ -90,6 +91,8 @@ find_syscall_meta(unsigned long syscall)
return NULL;

for ( ; start < stop; start++) {
+ if ((*start)->compat) /* skip compat syscalls */
+ continue;
if ((*start)->name && arch_syscall_match_sym_name(str, (*start)->name))
return *start;
}
@@ -98,6 +101,8 @@ find_syscall_meta(unsigned long syscall)

static struct syscall_metadata *syscall_nr_to_meta(int nr)
{
+ if (is_compat_task())
+ return compat_syscall_nr_to_meta(nr);
if (!syscalls_metadata || nr >= NR_syscalls || nr < 0)
return NULL;

@@ -112,11 +117,13 @@ print_syscall_enter(struct trace_iterator *iter, int flags,
struct trace_entry *ent = iter->ent;
struct syscall_trace_enter *trace;
struct syscall_metadata *entry;
- int i, ret, syscall;
+ int i, ret;
+ struct ftrace_event_call *call;

trace = (typeof(trace))ent;
- syscall = trace->nr;
- entry = syscall_nr_to_meta(syscall);
+ event = ftrace_find_event(ent->type);
+ call = container_of(event, struct ftrace_event_call, event);
+ entry = call->data;

if (!entry)
goto end;
@@ -164,13 +171,14 @@ print_syscall_exit(struct trace_iterator *iter, int flags,
struct trace_seq *s = &iter->seq;
struct trace_entry *ent = iter->ent;
struct syscall_trace_exit *trace;
- int syscall;
struct syscall_metadata *entry;
int ret;
+ struct ftrace_event_call *call;

trace = (typeof(trace))ent;
- syscall = trace->nr;
- entry = syscall_nr_to_meta(syscall);
+ event = ftrace_find_event(ent->type);
+ call = container_of(event, struct ftrace_event_call, event);
+ entry = call->data;

if (!entry) {
trace_seq_printf(s, "\n");
--
1.7.7.3