// SPDX-License-Identifier: GPL-2.0/* * Common code for probe-based Dynamic events. * * This code was copied from kernel/trace/trace_kprobe.c written by * Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> * * Updates to make this generic: * Copyright (C) IBM Corporation, 2010-2011 * Author: Srikar Dronamraju */#define pr_fmt(fmt) "trace_probe: " fmt #include "trace_probe.h" #undef C#define C(a, b) b static const char *trace_probe_err_text[] = { ERRORS }; static const char *reserved_field_names[] = { "common_type", "common_flags", "common_preempt_count", "common_pid", "common_tgid", FIELD_STRING_IP, FIELD_STRING_RETIP, FIELD_STRING_FUNC,}; /* Printing in basic type function template */#define DEFINE_BASIC_PRINT_TYPE_FUNC(tname, type, fmt) \int PRINT_TYPE_FUNC_NAME(tname)(struct trace_seq *s, void *data, void *ent)\{ \ trace_seq_printf(s, fmt, *(type *)data); \ return !trace_seq_has_overflowed(s); \} \const char PRINT_TYPE_FMT_NAME(tname)[] = fmt; DEFINE_BASIC_PRINT_TYPE_FUNC(u8, u8, "%u")DEFINE_BASIC_PRINT_TYPE_FUNC(u16, u16, "%u")DEFINE_BASIC_PRINT_TYPE_FUNC(u32, u32, "%u")DEFINE_BASIC_PRINT_TYPE_FUNC(u64, u64, "%Lu")DEFINE_BASIC_PRINT_TYPE_FUNC(s8, s8, "%d")DEFINE_BASIC_PRINT_TYPE_FUNC(s16, s16, "%d")DEFINE_BASIC_PRINT_TYPE_FUNC(s32, s32, "%d")DEFINE_BASIC_PRINT_TYPE_FUNC(s64, s64, "%Ld")DEFINE_BASIC_PRINT_TYPE_FUNC(x8, u8, "0x%x")DEFINE_BASIC_PRINT_TYPE_FUNC(x16, u16, "0x%x")DEFINE_BASIC_PRINT_TYPE_FUNC(x32, u32, "0x%x")DEFINE_BASIC_PRINT_TYPE_FUNC(x64, u64, "0x%Lx") int PRINT_TYPE_FUNC_NAME(symbol)(struct trace_seq *s, void *data, void *ent){ trace_seq_printf(s, "%pS", (void *)*(unsigned long *)data); return !trace_seq_has_overflowed(s);}const char PRINT_TYPE_FMT_NAME(symbol)[] = "%pS"; /* Print type function for string type */int PRINT_TYPE_FUNC_NAME(string)(struct trace_seq *s, void *data, void *ent){ int len = *(u32 *)data >> 16; if (!len) trace_seq_puts(s, "(fault)"); else trace_seq_printf(s, "\"%s\"", (const char *)get_loc_data(data, ent)); return !trace_seq_has_overflowed(s);} const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\""; /* Fetch type information table */static const struct fetch_type probe_fetch_types[] = { /* Special types */ __ASSIGN_FETCH_TYPE("string", string, string, sizeof(u32), 1, "__data_loc char[]"), __ASSIGN_FETCH_TYPE("ustring", string, string, sizeof(u32), 1, "__data_loc char[]"), /* Basic types */ ASSIGN_FETCH_TYPE(u8, u8, 0), ASSIGN_FETCH_TYPE(u16, u16, 0), ASSIGN_FETCH_TYPE(u32, u32, 0), ASSIGN_FETCH_TYPE(u64, u64, 0), ASSIGN_FETCH_TYPE(s8, u8, 1), ASSIGN_FETCH_TYPE(s16, u16, 1), ASSIGN_FETCH_TYPE(s32, u32, 1), ASSIGN_FETCH_TYPE(s64, u64, 1), ASSIGN_FETCH_TYPE_ALIAS(x8, u8, u8, 0), ASSIGN_FETCH_TYPE_ALIAS(x16, u16, u16, 0), ASSIGN_FETCH_TYPE_ALIAS(x32, u32, u32, 0), ASSIGN_FETCH_TYPE_ALIAS(x64, u64, u64, 0), ASSIGN_FETCH_TYPE_ALIAS(symbol, ADDR_FETCH_TYPE, ADDR_FETCH_TYPE, 0), ASSIGN_FETCH_TYPE_END}; static const struct fetch_type *find_fetch_type(const char *type){ int i; if (!type) type = DEFAULT_FETCH_TYPE_STR; /* Special case: bitfield */ if (*type == 'b') { unsigned long bs; type = strchr(type, '/'); if (!type) goto fail; type++; if (kstrtoul(type, 0, &bs)) goto fail; switch (bs) { case 8: return find_fetch_type("u8"); case 16: return find_fetch_type("u16"); case 32: return find_fetch_type("u32"); case 64: return find_fetch_type("u64"); default: goto fail; } } for (i = 0; probe_fetch_types[i].name; i++) { if (strcmp(type, probe_fetch_types[i].name) == 0) return &probe_fetch_types[i]; } fail: return NULL;} static struct trace_probe_log trace_probe_log; void trace_probe_log_init(const char *subsystem, int argc, const char **argv){ trace_probe_log.subsystem = subsystem; trace_probe_log.argc = argc; trace_probe_log.argv = argv; trace_probe_log.index = 0;} void trace_probe_log_clear(void){ memset(&trace_probe_log, 0, sizeof(trace_probe_log));} void trace_probe_log_set_index(int index){ trace_probe_log.index = index;} void __trace_probe_log_err(int offset, int err_type){ char *command, *p; int i, len = 0, pos = 0; if (!trace_probe_log.argv) return; /* Recalcurate the length and allocate buffer */ for (i = 0; i < trace_probe_log.argc; i++) { if (i == trace_probe_log.index) pos = len; len += strlen(trace_probe_log.argv[i]) + 1; } command = kzalloc(len, GFP_KERNEL); if (!command) return; if (trace_probe_log.index >= trace_probe_log.argc) { /** * Set the error position is next to the last arg + space. * Note that len includes the terminal null and the cursor * appaers at pos + 1. */ pos = len; offset = 0; } /* And make a command string from argv array */ p = command; for (i = 0; i < trace_probe_log.argc; i++) { len = strlen(trace_probe_log.argv[i]); strcpy(p, trace_probe_log.argv[i]); p[len] = ' '; p += len + 1; } *(p - 1) = '\0'; tracing_log_err(NULL, trace_probe_log.subsystem, command, trace_probe_err_text, err_type, pos + offset); kfree(command);} /* Split symbol and offset. */int traceprobe_split_symbol_offset(char *symbol, long *offset){ char *tmp; int ret; if (!offset) return -EINVAL; tmp = strpbrk(symbol, "+-"); if (tmp) { ret = kstrtol(tmp, 0, offset); if (ret) return ret; *tmp = '\0'; } else *offset = 0; return 0;} /* @buf must has MAX_EVENT_NAME_LEN size */int traceprobe_parse_event_name(const char **pevent, const char **pgroup, char *buf, int offset){ const char *slash, *event = *pevent; int len; slash = strchr(event, '/'); if (slash) { if (slash == event) { trace_probe_log_err(offset, NO_GROUP_NAME); return -EINVAL; } if (slash - event + 1 > MAX_EVENT_NAME_LEN) { trace_probe_log_err(offset, GROUP_TOO_LONG); return -EINVAL; } strlcpy(buf, event, slash - event + 1); if (!is_good_name(buf)) { trace_probe_log_err(offset, BAD_GROUP_NAME); return -EINVAL; } *pgroup = buf; *pevent = slash + 1; offset += slash - event + 1; event = *pevent; } len = strlen(event); if (len == 0) { trace_probe_log_err(offset, NO_EVENT_NAME); return -EINVAL; } else if (len > MAX_EVENT_NAME_LEN) { trace_probe_log_err(offset, EVENT_TOO_LONG); return -EINVAL; } if (!is_good_name(event)) { trace_probe_log_err(offset, BAD_EVENT_NAME); return -EINVAL; } return 0;} #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) static int parse_probe_vars(char *arg, const struct fetch_type *t, struct fetch_insn *code, unsigned int flags, int offs){ unsigned long param; int ret = 0; int len; if (strcmp(arg, "retval") == 0) { if (flags & TPARG_FL_RETURN) { code->op = FETCH_OP_RETVAL; } else { trace_probe_log_err(offs, RETVAL_ON_PROBE); ret = -EINVAL; } } else if ((len = str_has_prefix(arg, "stack"))) { if (arg[len] == '\0') { code->op = FETCH_OP_STACKP; } else if (isdigit(arg[len])) { ret = kstrtoul(arg + len, 10, ¶m); if (ret) { goto inval_var; } else if ((flags & TPARG_FL_KERNEL) && param > PARAM_MAX_STACK) { trace_probe_log_err(offs, BAD_STACK_NUM); ret = -EINVAL; } else { code->op = FETCH_OP_STACK; code->param = (unsigned int)param; } } else goto inval_var; } else if (strcmp(arg, "comm") == 0) { code->op = FETCH_OP_COMM;#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API } else if (((flags & TPARG_FL_MASK) == (TPARG_FL_KERNEL | TPARG_FL_FENTRY)) && (len = str_has_prefix(arg, "arg"))) { ret = kstrtoul(arg + len, 10, ¶m); if (ret) { goto inval_var; } else if (!param || param > PARAM_MAX_STACK) { trace_probe_log_err(offs, BAD_ARG_NUM); return -EINVAL; } code->op = FETCH_OP_ARG; code->param = (unsigned int)param - 1;#endif } else goto inval_var; return ret; inval_var: trace_probe_log_err(offs, BAD_VAR); return -EINVAL;} static int str_to_immediate(char *str, unsigned long *imm){ if (isdigit(str[0])) return kstrtoul(str, 0, imm); else if (str[0] == '-') return kstrtol(str, 0, (long *)imm); else if (str[0] == '+') return kstrtol(str + 1, 0, (long *)imm); return -EINVAL;} static int __parse_imm_string(char *str, char **pbuf, int offs){ size_t len = strlen(str); if (str[len - 1] != '"') { trace_probe_log_err(offs + len, IMMSTR_NO_CLOSE); return -EINVAL; } *pbuf = kstrndup(str, len - 1, GFP_KERNEL); return 0;} /* Recursive argument parser */static intparse_probe_arg(char *arg, const struct fetch_type *type, struct fetch_insn **pcode, struct fetch_insn *end, unsigned int flags, int offs){ struct fetch_insn *code = *pcode; unsigned long param; int deref = FETCH_OP_DEREF; long offset = 0; char *tmp; int ret = 0; switch (arg[0]) { case '$': ret = parse_probe_vars(arg + 1, type, code, flags, offs); break; case '%': /* named register */ ret = regs_query_register_offset(arg + 1); if (ret >= 0) { code->op = FETCH_OP_REG; code->param = (unsigned int)ret; ret = 0; } else trace_probe_log_err(offs, BAD_REG_NAME); break; case '@': /* memory, file-offset or symbol */ if (isdigit(arg[1])) { ret = kstrtoul(arg + 1, 0, ¶m); if (ret) { trace_probe_log_err(offs, BAD_MEM_ADDR); break; } /* load address */ code->op = FETCH_OP_IMM; code->immediate = param; } else if (arg[1] == '+') { /* kprobes don't support file offsets */ if (flags & TPARG_FL_KERNEL) { trace_probe_log_err(offs, FILE_ON_KPROBE); return -EINVAL; } ret = kstrtol(arg + 2, 0, &offset); if (ret) { trace_probe_log_err(offs, BAD_FILE_OFFS); break; } code->op = FETCH_OP_FOFFS; code->immediate = (unsigned long)offset; // imm64? } else { /* uprobes don't support symbols */ if (!(flags & TPARG_FL_KERNEL)) { trace_probe_log_err(offs, SYM_ON_UPROBE); return -EINVAL; } /* Preserve symbol for updating */ code->op = FETCH_NOP_SYMBOL; code->data = kstrdup(arg + 1, GFP_KERNEL); if (!code->data) return -ENOMEM; if (++code == end) { trace_probe_log_err(offs, TOO_MANY_OPS); return -EINVAL; } code->op = FETCH_OP_IMM; code->immediate = 0; } /* These are fetching from memory */ if (++code == end) { trace_probe_log_err(offs, TOO_MANY_OPS); return -EINVAL; } *pcode = code; code->op = FETCH_OP_DEREF; code->offset = offset; break; case '+': /* deref memory */ case '-': if (arg[1] == 'u') { deref = FETCH_OP_UDEREF; arg[1] = arg[0]; arg++; } if (arg[0] == '+') arg++; /* Skip '+', because kstrtol() rejects it. */ tmp = strchr(arg, '('); if (!tmp) { trace_probe_log_err(offs, DEREF_NEED_BRACE); return -EINVAL; } *tmp = '\0'; ret = kstrtol(arg, 0, &offset); if (ret) { trace_probe_log_err(offs, BAD_DEREF_OFFS); break; } offs += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0); arg = tmp + 1; tmp = strrchr(arg, ')'); if (!tmp) { trace_probe_log_err(offs + strlen(arg), DEREF_OPEN_BRACE); return -EINVAL; } else { const struct fetch_type *t2 = find_fetch_type(NULL); *tmp = '\0'; ret = parse_probe_arg(arg, t2, &code, end, flags, offs); if (ret) break; if (code->op == FETCH_OP_COMM || code->op == FETCH_OP_DATA) { trace_probe_log_err(offs, COMM_CANT_DEREF); return -EINVAL; } if (++code == end) { trace_probe_log_err(offs, TOO_MANY_OPS); return -EINVAL; } *pcode = code; code->op = deref; code->offset = offset; } break; case '\\': /* Immediate value */ if (arg[1] == '"') { /* Immediate string */ ret = __parse_imm_string(arg + 2, &tmp, offs + 2); if (ret) break; code->op = FETCH_OP_DATA; code->data = tmp; } else { ret = str_to_immediate(arg + 1, &code->immediate); if (ret) trace_probe_log_err(offs + 1, BAD_IMM); else code->op = FETCH_OP_IMM; } break; } if (!ret && code->op == FETCH_OP_NOP) { /* Parsed, but do not find fetch method */ trace_probe_log_err(offs, BAD_FETCH_ARG); ret = -EINVAL;Lines 1–500 of 1153. Load more
// SPDX-License-Identifier: GPL-2.0 /* * Common code for probe-based Dynamic events. * * This code was copied from kernel/trace/trace_kprobe.c written by * Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> * * Updates to make this generic: * Copyright (C) IBM Corporation, 2010-2011 * Author: Srikar Dronamraju */ #define pr_fmt(fmt) "trace_probe: " fmt #include "trace_probe.h" #undef C #define C(a, b) b static const char *trace_probe_err_text[] = { ERRORS }; static const char *reserved_field_names[] = { "common_type", "common_flags", "common_preempt_count", "common_pid", "common_tgid", FIELD_STRING_IP, FIELD_STRING_RETIP, FIELD_STRING_FUNC, }; /* Printing in basic type function template */ #define DEFINE_BASIC_PRINT_TYPE_FUNC(tname, type, fmt) \ int PRINT_TYPE_FUNC_NAME(tname)(struct trace_seq *s, void *data, void *ent)\ { \ trace_seq_printf(s, fmt, *(type *)data); \ return !trace_seq_has_overflowed(s); \ } \ const char PRINT_TYPE_FMT_NAME(tname)[] = fmt; DEFINE_BASIC_PRINT_TYPE_FUNC(u8, u8, "%u") DEFINE_BASIC_PRINT_TYPE_FUNC(u16, u16, "%u") DEFINE_BASIC_PRINT_TYPE_FUNC(u32, u32, "%u") DEFINE_BASIC_PRINT_TYPE_FUNC(u64, u64, "%Lu") DEFINE_BASIC_PRINT_TYPE_FUNC(s8, s8, "%d") DEFINE_BASIC_PRINT_TYPE_FUNC(s16, s16, "%d") DEFINE_BASIC_PRINT_TYPE_FUNC(s32, s32, "%d") DEFINE_BASIC_PRINT_TYPE_FUNC(s64, s64, "%Ld") DEFINE_BASIC_PRINT_TYPE_FUNC(x8, u8, "0x%x") DEFINE_BASIC_PRINT_TYPE_FUNC(x16, u16, "0x%x") DEFINE_BASIC_PRINT_TYPE_FUNC(x32, u32, "0x%x") DEFINE_BASIC_PRINT_TYPE_FUNC(x64, u64, "0x%Lx") int PRINT_TYPE_FUNC_NAME(symbol)(struct trace_seq *s, void *data, void *ent) { trace_seq_printf(s, "%pS", (void *)*(unsigned long *)data); return !trace_seq_has_overflowed(s); } const char PRINT_TYPE_FMT_NAME(symbol)[] = "%pS"; /* Print type function for string type */ int PRINT_TYPE_FUNC_NAME(string)(struct trace_seq *s, void *data, void *ent) { int len = *(u32 *)data >> 16; if (!len) trace_seq_puts(s, "(fault)"); else trace_seq_printf(s, "\"%s\"", (const char *)get_loc_data(data, ent)); return !trace_seq_has_overflowed(s); } const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\""; /* Fetch type information table */ static const struct fetch_type probe_fetch_types[] = { /* Special types */ __ASSIGN_FETCH_TYPE("string", string, string, sizeof(u32), 1, "__data_loc char[]"), __ASSIGN_FETCH_TYPE("ustring", string, string, sizeof(u32), 1, "__data_loc char[]"), /* Basic types */ ASSIGN_FETCH_TYPE(u8, u8, 0), ASSIGN_FETCH_TYPE(u16, u16, 0), ASSIGN_FETCH_TYPE(u32, u32, 0), ASSIGN_FETCH_TYPE(u64, u64, 0), ASSIGN_FETCH_TYPE(s8, u8, 1), ASSIGN_FETCH_TYPE(s16, u16, 1), ASSIGN_FETCH_TYPE(s32, u32, 1), ASSIGN_FETCH_TYPE(s64, u64, 1), ASSIGN_FETCH_TYPE_ALIAS(x8, u8, u8, 0), ASSIGN_FETCH_TYPE_ALIAS(x16, u16, u16, 0), ASSIGN_FETCH_TYPE_ALIAS(x32, u32, u32, 0), ASSIGN_FETCH_TYPE_ALIAS(x64, u64, u64, 0), ASSIGN_FETCH_TYPE_ALIAS(symbol, ADDR_FETCH_TYPE, ADDR_FETCH_TYPE, 0), ASSIGN_FETCH_TYPE_END }; static const struct fetch_type *find_fetch_type(const char *type) { int i; if (!type) type = DEFAULT_FETCH_TYPE_STR; /* Special case: bitfield */ if (*type == 'b') { unsigned long bs; type = strchr(type, '/'); if (!type) goto fail; type++; if (kstrtoul(type, 0, &bs)) goto fail; switch (bs) { case 8: return find_fetch_type("u8"); case 16: return find_fetch_type("u16"); case 32: return find_fetch_type("u32"); case 64: return find_fetch_type("u64"); default: goto fail; } } for (i = 0; probe_fetch_types[i].name; i++) { if (strcmp(type, probe_fetch_types[i].name) == 0) return &probe_fetch_types[i]; } fail: return NULL; } static struct trace_probe_log trace_probe_log; void trace_probe_log_init(const char *subsystem, int argc, const char **argv) { trace_probe_log.subsystem = subsystem; trace_probe_log.argc = argc; trace_probe_log.argv = argv; trace_probe_log.index = 0; } void trace_probe_log_clear(void) { memset(&trace_probe_log, 0, sizeof(trace_probe_log)); } void trace_probe_log_set_index(int index) { trace_probe_log.index = index; } void __trace_probe_log_err(int offset, int err_type) { char *command, *p; int i, len = 0, pos = 0; if (!trace_probe_log.argv) return; /* Recalcurate the length and allocate buffer */ for (i = 0; i < trace_probe_log.argc; i++) { if (i == trace_probe_log.index) pos = len; len += strlen(trace_probe_log.argv[i]) + 1; } command = kzalloc(len, GFP_KERNEL); if (!command) return; if (trace_probe_log.index >= trace_probe_log.argc) { /** * Set the error position is next to the last arg + space. * Note that len includes the terminal null and the cursor * appaers at pos + 1. */ pos = len; offset = 0; } /* And make a command string from argv array */ p = command; for (i = 0; i < trace_probe_log.argc; i++) { len = strlen(trace_probe_log.argv[i]); strcpy(p, trace_probe_log.argv[i]); p[len] = ' '; p += len + 1; } *(p - 1) = '\0'; tracing_log_err(NULL, trace_probe_log.subsystem, command, trace_probe_err_text, err_type, pos + offset); kfree(command); } /* Split symbol and offset. */ int traceprobe_split_symbol_offset(char *symbol, long *offset) { char *tmp; int ret; if (!offset) return -EINVAL; tmp = strpbrk(symbol, "+-"); if (tmp) { ret = kstrtol(tmp, 0, offset); if (ret) return ret; *tmp = '\0'; } else *offset = 0; return 0; } /* @buf must has MAX_EVENT_NAME_LEN size */ int traceprobe_parse_event_name(const char **pevent, const char **pgroup, char *buf, int offset) { const char *slash, *event = *pevent; int len; slash = strchr(event, '/'); if (slash) { if (slash == event) { trace_probe_log_err(offset, NO_GROUP_NAME); return -EINVAL; } if (slash - event + 1 > MAX_EVENT_NAME_LEN) { trace_probe_log_err(offset, GROUP_TOO_LONG); return -EINVAL; } strlcpy(buf, event, slash - event + 1); if (!is_good_name(buf)) { trace_probe_log_err(offset, BAD_GROUP_NAME); return -EINVAL; } *pgroup = buf; *pevent = slash + 1; offset += slash - event + 1; event = *pevent; } len = strlen(event); if (len == 0) { trace_probe_log_err(offset, NO_EVENT_NAME); return -EINVAL; } else if (len > MAX_EVENT_NAME_LEN) { trace_probe_log_err(offset, EVENT_TOO_LONG); return -EINVAL; } if (!is_good_name(event)) { trace_probe_log_err(offset, BAD_EVENT_NAME); return -EINVAL; } return 0; } #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) static int parse_probe_vars(char *arg, const struct fetch_type *t, struct fetch_insn *code, unsigned int flags, int offs) { unsigned long param; int ret = 0; int len; if (strcmp(arg, "retval") == 0) { if (flags & TPARG_FL_RETURN) { code->op = FETCH_OP_RETVAL; } else { trace_probe_log_err(offs, RETVAL_ON_PROBE); ret = -EINVAL; } } else if ((len = str_has_prefix(arg, "stack"))) { if (arg[len] == '\0') { code->op = FETCH_OP_STACKP; } else if (isdigit(arg[len])) { ret = kstrtoul(arg + len, 10, ¶m); if (ret) { goto inval_var; } else if ((flags & TPARG_FL_KERNEL) && param > PARAM_MAX_STACK) { trace_probe_log_err(offs, BAD_STACK_NUM); ret = -EINVAL; } else { code->op = FETCH_OP_STACK; code->param = (unsigned int)param; } } else goto inval_var; } else if (strcmp(arg, "comm") == 0) { code->op = FETCH_OP_COMM; #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API } else if (((flags & TPARG_FL_MASK) == (TPARG_FL_KERNEL | TPARG_FL_FENTRY)) && (len = str_has_prefix(arg, "arg"))) { ret = kstrtoul(arg + len, 10, ¶m); if (ret) { goto inval_var; } else if (!param || param > PARAM_MAX_STACK) { trace_probe_log_err(offs, BAD_ARG_NUM); return -EINVAL; } code->op = FETCH_OP_ARG; code->param = (unsigned int)param - 1; #endif } else goto inval_var; return ret; inval_var: trace_probe_log_err(offs, BAD_VAR); return -EINVAL; } static int str_to_immediate(char *str, unsigned long *imm) { if (isdigit(str[0])) return kstrtoul(str, 0, imm); else if (str[0] == '-') return kstrtol(str, 0, (long *)imm); else if (str[0] == '+') return kstrtol(str + 1, 0, (long *)imm); return -EINVAL; } static int __parse_imm_string(char *str, char **pbuf, int offs) { size_t len = strlen(str); if (str[len - 1] != '"') { trace_probe_log_err(offs + len, IMMSTR_NO_CLOSE); return -EINVAL; } *pbuf = kstrndup(str, len - 1, GFP_KERNEL); return 0; } /* Recursive argument parser */ static int parse_probe_arg(char *arg, const struct fetch_type *type, struct fetch_insn **pcode, struct fetch_insn *end, unsigned int flags, int offs) { struct fetch_insn *code = *pcode; unsigned long param; int deref = FETCH_OP_DEREF; long offset = 0; char *tmp; int ret = 0; switch (arg[0]) { case '$': ret = parse_probe_vars(arg + 1, type, code, flags, offs); break; case '%': /* named register */ ret = regs_query_register_offset(arg + 1); if (ret >= 0) { code->op = FETCH_OP_REG; code->param = (unsigned int)ret; ret = 0; } else trace_probe_log_err(offs, BAD_REG_NAME); break; case '@': /* memory, file-offset or symbol */ if (isdigit(arg[1])) { ret = kstrtoul(arg + 1, 0, ¶m); if (ret) { trace_probe_log_err(offs, BAD_MEM_ADDR); break; } /* load address */ code->op = FETCH_OP_IMM; code->immediate = param; } else if (arg[1] == '+') { /* kprobes don't support file offsets */ if (flags & TPARG_FL_KERNEL) { trace_probe_log_err(offs, FILE_ON_KPROBE); return -EINVAL; } ret = kstrtol(arg + 2, 0, &offset); if (ret) { trace_probe_log_err(offs, BAD_FILE_OFFS); break; } code->op = FETCH_OP_FOFFS; code->immediate = (unsigned long)offset; // imm64? } else { /* uprobes don't support symbols */ if (!(flags & TPARG_FL_KERNEL)) { trace_probe_log_err(offs, SYM_ON_UPROBE); return -EINVAL; } /* Preserve symbol for updating */ code->op = FETCH_NOP_SYMBOL; code->data = kstrdup(arg + 1, GFP_KERNEL); if (!code->data) return -ENOMEM; if (++code == end) { trace_probe_log_err(offs, TOO_MANY_OPS); return -EINVAL; } code->op = FETCH_OP_IMM; code->immediate = 0; } /* These are fetching from memory */ if (++code == end) { trace_probe_log_err(offs, TOO_MANY_OPS); return -EINVAL; } *pcode = code; code->op = FETCH_OP_DEREF; code->offset = offset; break; case '+': /* deref memory */ case '-': if (arg[1] == 'u') { deref = FETCH_OP_UDEREF; arg[1] = arg[0]; arg++; } if (arg[0] == '+') arg++; /* Skip '+', because kstrtol() rejects it. */ tmp = strchr(arg, '('); if (!tmp) { trace_probe_log_err(offs, DEREF_NEED_BRACE); return -EINVAL; } *tmp = '\0'; ret = kstrtol(arg, 0, &offset); if (ret) { trace_probe_log_err(offs, BAD_DEREF_OFFS); break; } offs += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0); arg = tmp + 1; tmp = strrchr(arg, ')'); if (!tmp) { trace_probe_log_err(offs + strlen(arg), DEREF_OPEN_BRACE); return -EINVAL; } else { const struct fetch_type *t2 = find_fetch_type(NULL); *tmp = '\0'; ret = parse_probe_arg(arg, t2, &code, end, flags, offs); if (ret) break; if (code->op == FETCH_OP_COMM || code->op == FETCH_OP_DATA) { trace_probe_log_err(offs, COMM_CANT_DEREF); return -EINVAL; } if (++code == end) { trace_probe_log_err(offs, TOO_MANY_OPS); return -EINVAL; } *pcode = code; code->op = deref; code->offset = offset; } break; case '\\': /* Immediate value */ if (arg[1] == '"') { /* Immediate string */ ret = __parse_imm_string(arg + 2, &tmp, offs + 2); if (ret) break; code->op = FETCH_OP_DATA; code->data = tmp; } else { ret = str_to_immediate(arg + 1, &code->immediate); if (ret) trace_probe_log_err(offs + 1, BAD_IMM); else code->op = FETCH_OP_IMM; } break; } if (!ret && code->op == FETCH_OP_NOP) { /* Parsed, but do not find fetch method */ trace_probe_log_err(offs, BAD_FETCH_ARG); ret = -EINVAL;