// 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
void __trace_probe_log_err(int offset, int err_type)
{ char *command, *p; int i, len = 0, pos = 0;
lockdep_assert_held(&dyn_event_ops_mutex);
if (!trace_probe_log.argv) return;
/* Recalculate 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 * appears 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';
/* 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;
}
/** * traceprobe_parse_event_name() - Parse a string into group and event names * @pevent: A pointer to the string to be parsed. * @pgroup: A pointer to the group name. * @buf: A buffer to store the parsed group name. * @offset: The offset of the string in the original user command, for logging. * * This parses a string with the format `[GROUP/][EVENT]` or `[GROUP.][EVENT]` * (either GROUP or EVENT or both must be specified). * Since the parsed group name is stored in @buf, the caller must ensure @buf * is at least MAX_EVENT_NAME_LEN bytes. * * Return: 0 on success, or -EINVAL on failure. * * If success, *@pevent is updated to point to the event name part of the * original string, or NULL if there is no event name. * Also, *@pgroup is updated to point to the parsed group which is stored * in @buf, or NULL if there is no group name.
*/ int traceprobe_parse_event_name(constchar **pevent, constchar **pgroup, char *buf, int offset)
{ constchar *slash, *event = *pevent; int len;
slash = strchr(event, '/'); if (!slash)
slash = strchr(event, '.');
/* TODO: const char * could be converted as a string */ switch (BTF_INFO_KIND(type->info)) { case BTF_KIND_ENUM: /* enum is "int", so convert to "s32" */ return"s32"; case BTF_KIND_ENUM64: return"s64"; case BTF_KIND_PTR: /* pointer will be converted to "x??" */ if (IS_ENABLED(CONFIG_64BIT)) return"x64"; else return"x32"; case BTF_KIND_INT:
intdata = btf_type_int(type); if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) { switch (BTF_INT_BITS(intdata)) { case 8: return"s8"; case 16: return"s16"; case 32: return"s32"; case 64: return"s64";
}
} else { /* unsigned */ switch (BTF_INT_BITS(intdata)) { case 8: return"u8"; case 16: return"u16"; case 32: return"u32"; case 64: return"u64";
} /* bitfield, size is encoded in the type */
ctx->last_bitsize = BTF_INT_BITS(intdata);
ctx->last_bitoffs += BTF_INT_OFFSET(intdata); return"u64";
}
} /* TODO: support other types */
type = btf_find_func_proto(ctx->funcname, &btf); if (!type) return -ENOENT;
ctx->btf = btf;
ctx->proto = type;
/* ctx->params is optional, since func(void) will not have params. */
nr = 0;
param = btf_get_func_param(type, &nr); if (!IS_ERR_OR_NULL(param)) { /* Hide the first 'data' argument of tracepoint */ if (ctx->flags & TPARG_FL_TPOINT) {
nr--;
param++;
}
}
/* Return 1 if the field separater is arrow operator ('->') */ staticint split_next_field(char *varname, char **next_field, struct traceprobe_parse_context *ctx)
{ char *field; int ret = 0;
field = strpbrk(varname, ".-"); if (field) { if (field[0] == '-' && field[1] == '>') {
field[0] = '\0';
field += 2;
ret = 1;
} elseif (field[0] == '.') {
field[0] = '\0';
field += 1;
} else {
trace_probe_log_err(ctx->offset + field - varname, BAD_HYPHEN); return -EINVAL;
}
*next_field = field;
}
return ret;
}
/* * Parse the field of data structure. The @type must be a pointer type * pointing the target data structure type.
*/ staticint parse_btf_field(char *fieldname, conststruct btf_type *type, struct fetch_insn **pcode, struct fetch_insn *end, struct traceprobe_parse_context *ctx)
{ struct fetch_insn *code = *pcode; conststruct btf_member *field;
u32 bitoffs, anon_offs; char *next; int is_ptr;
s32 tid;
do { /* Outer loop for solving arrow operator ('->') */ if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
trace_probe_log_err(ctx->offset, NO_PTR_STRCT); return -EINVAL;
} /* Convert a struct pointer type to a struct type */
type = btf_type_skip_modifiers(ctx->btf, type->type, &tid); if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL;
}
bitoffs = 0; do { /* Inner loop for solving dot operator ('.') */
next = NULL;
is_ptr = split_next_field(fieldname, &next, ctx); if (is_ptr < 0) return is_ptr;
is_ptr = split_next_field(varname, &field, ctx); if (is_ptr < 0) return is_ptr; if (!is_ptr && field) { /* dot-connected field on an argument is not supported. */
trace_probe_log_err(ctx->offset + field - varname,
NOSUP_DAT_ARG); return -EOPNOTSUPP;
}
if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
code->op = FETCH_OP_RETVAL; /* Check whether the function return type is not void */ if (query_btf_context(ctx) == 0) { if (ctx->proto->type == 0) {
trace_probe_log_err(ctx->offset, NO_RETVAL); return -ENOENT;
}
tid = ctx->proto->type; goto found;
} if (field) {
trace_probe_log_err(ctx->offset + field - varname,
NO_BTF_ENTRY); return -ENOENT;
} return 0;
}
if (!ctx->btf) {
ret = query_btf_context(ctx); if (ret < 0 || ctx->nr_params == 0) {
trace_probe_log_err(ctx->offset, NO_BTF_ENTRY); return -ENOENT;
}
}
params = ctx->params;
for (i = 0; i < ctx->nr_params; i++) { constchar *name = btf_name_by_offset(ctx->btf, params[i].name_off);
if (name && !strcmp(name, varname)) { if (tparg_is_function_entry(ctx->flags)) {
code->op = FETCH_OP_ARG; if (ctx->flags & TPARG_FL_TPOINT)
code->param = i + 1; else
code->param = i;
} elseif (tparg_is_function_return(ctx->flags)) {
code->op = FETCH_OP_EDATA;
ret = __store_entry_arg(ctx->tp, i); if (ret < 0) { /* internal error */ return ret;
}
code->offset = ret;
}
tid = params[i].type; goto found;
}
}
trace_probe_log_err(ctx->offset, NO_BTFARG); return -ENOENT;
found:
type = btf_type_skip_modifiers(ctx->btf, tid, &tid); if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL;
} /* Initialize the last type information */
ctx->last_type = type;
ctx->last_bitoffs = 0;
ctx->last_bitsize = 0; if (field) {
ctx->offset += field - varname; return parse_btf_field(field, type, pcode, end, ctx);
} return 0;
}
staticint get_entry_arg_max_offset(struct probe_entry_arg *earg)
{ int i, max_offset = 0;
/* * earg->code[] array has an operation sequence which is run in * the entry handler. * The sequence stopped by FETCH_OP_END and each data stored in * the entry data buffer by FETCH_OP_ST_EDATA. The FETCH_OP_ST_EDATA * stores the data at the data buffer + its offset, and all data are * "unsigned long" size. The offset must be increased when a data is * stored. Thus we need to find the last FETCH_OP_ST_EDATA in the * code array.
*/ for (i = 0; i < earg->size - 1 && earg->code[i].op != FETCH_OP_END; i++) { if (earg->code[i].op == FETCH_OP_ST_EDATA) if (earg->code[i].offset > max_offset)
max_offset = earg->code[i].offset;
} return max_offset;
}
/* * Add the entry code to store the 'argnum'th parameter and return the offset * in the entry data buffer where the data will be stored.
*/ staticint __store_entry_arg(struct trace_probe *tp, int argnum)
{ struct probe_entry_arg *earg = tp->entry_arg; int i, offset, last_offset = 0;
if (!earg) {
earg = kzalloc(sizeof(*tp->entry_arg), GFP_KERNEL); if (!earg) return -ENOMEM;
earg->size = 2 * tp->nr_args + 1;
earg->code = kcalloc(earg->size, sizeof(struct fetch_insn),
GFP_KERNEL); if (!earg->code) {
kfree(earg); return -ENOMEM;
} /* Fill the code buffer with 'end' to simplify it */ for (i = 0; i < earg->size; i++)
earg->code[i].op = FETCH_OP_END;
tp->entry_arg = earg;
store_entry_arg_at(earg->code, argnum, 0); return 0;
}
/* * NOTE: if anyone change the following rule, please rewrite this. * The entry code array is filled with the pair of * * [FETCH_OP_ARG(argnum)] * [FETCH_OP_ST_EDATA(offset of entry data buffer)] * * and the rest of entries are filled with [FETCH_OP_END]. * The offset should be incremented, thus the last pair should * have the largest offset.
*/
/* Search the offset for the sprcified argnum. */ for (i = 0; i < earg->size - 1 && earg->code[i].op != FETCH_OP_END; i += 2) { if (WARN_ON_ONCE(earg->code[i].op != FETCH_OP_ARG)) return -EINVAL;
if (earg->code[i].param != argnum) continue;
if (WARN_ON_ONCE(earg->code[i + 1].op != FETCH_OP_ST_EDATA)) return -EINVAL;
return earg->code[i + 1].offset;
} /* Not found, append new entry if possible. */ if (i >= earg->size - 1) return -ENOSPC;
/* The last entry must have the largest offset. */ if (i != 0) { if (WARN_ON_ONCE(earg->code[i - 1].op != FETCH_OP_ST_EDATA)) return -EINVAL;
last_offset = earg->code[i - 1].offset;
}
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
len = str_has_prefix(arg, "arg"); if (len) {
ret = kstrtoul(arg + len, 10, ¶m); if (ret) goto inval;
if (!param || param > PARAM_MAX_STACK) {
err = TP_ERR_BAD_ARG_NUM; goto inval;
}
param--; /* argN starts from 1, but internal arg[N] starts from 0 */
if (tparg_is_function_entry(ctx->flags)) {
code->op = FETCH_OP_ARG;
code->param = (unsignedint)param; /* * The tracepoint probe will probe a stub function, and the * first parameter of the stub is a dummy and should be ignored.
*/ if (ctx->flags & TPARG_FL_TPOINT)
code->param++;
} elseif (tparg_is_function_return(ctx->flags)) { /* function entry argument access from return probe */
ret = __store_entry_arg(ctx->tp, param); if (ret < 0) /* This error should be an internal error */ return ret;
/* Split type part from @arg and return it. */ staticchar *parse_probe_arg_type(char *arg, struct probe_arg *parg, struct traceprobe_parse_context *ctx)
{ char *t = NULL, *t2, *t3; int offs;
t = strchr(arg, ':'); if (t) {
*t++ = '\0';
t2 = strchr(t, '['); if (t2) {
*t2++ = '\0';
t3 = strchr(t2, ']'); if (!t3) {
offs = t2 + strlen(t2) - arg;
/* * Since $comm and immediate string can not be dereferenced, * we can find those by strcmp. But ignore for eprobes.
*/ if (!(ctx->flags & TPARG_FL_TEVENT) &&
(strcmp(arg, "$comm") == 0 || strcmp(arg, "$COMM") == 0 ||
strncmp(arg, "\\\"", 2) == 0)) { /* The type of $comm must be "string", and not an array type. */ if (parg->count || (t && strcmp(t, "string"))) {
trace_probe_log_err(ctx->offset + offs, NEED_STRING_TYPE); return ERR_PTR(-EINVAL);
}
parg->type = find_fetch_type("string", ctx->flags);
} else
parg->type = find_fetch_type(t, ctx->flags);
if (!parg->type) {
trace_probe_log_err(ctx->offset + offs, BAD_TYPE); return ERR_PTR(-EINVAL);
}
return t;
}
/* After parsing, adjust the fetch_insn according to the probe_arg */ staticint finalize_fetch_insn(struct fetch_insn *code, struct probe_arg *parg, char *type, int type_offset, struct traceprobe_parse_context *ctx)
{ struct fetch_insn *scode; int ret;
/* Store operation */ if (parg->type->is_string) { /* Check bad combination of the type and the last fetch_insn. */ if (!strcmp(parg->type->name, "symstr")) { if (code->op != FETCH_OP_REG && code->op != FETCH_OP_STACK &&
code->op != FETCH_OP_RETVAL && code->op != FETCH_OP_ARG &&
code->op != FETCH_OP_DEREF && code->op != FETCH_OP_TP_ARG) {
trace_probe_log_err(ctx->offset + type_offset,
BAD_SYMSTRING); return -EINVAL;
}
} else { if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) {
trace_probe_log_err(ctx->offset + type_offset,
BAD_STRING); return -EINVAL;
}
}
if (!strcmp(parg->type->name, "symstr") ||
(code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM ||
code->op == FETCH_OP_DATA) || code->op == FETCH_OP_TP_ARG ||
parg->count) { /* * IMM, DATA and COMM is pointing actual address, those * must be kept, and if parg->count != 0, this is an * array of string pointers instead of string address * itself. * For the symstr, it doesn't need to dereference, thus * it just get the value.
*/
code++; if (code->op != FETCH_OP_NOP) {
trace_probe_log_err(ctx->offset, TOO_MANY_OPS); return -EINVAL;
}
}
ctx->last_type = NULL;
ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
ctx); if (ret < 0) goto fail;
/* Update storing type if BTF is available */ if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
ctx->last_type) { if (!type) {
parg->type = find_fetch_type_from_btf_type(ctx);
} elseif (strstr(type, "string")) {
ret = check_prepare_btf_string_fetch(type, &code, ctx); if (ret) goto fail;
}
}
parg->offset = *size;
*size += parg->type->size * (parg->count ?: 1);
if (parg->count) {
len = strlen(parg->type->fmttype) + 6;
parg->fmt = kmalloc(len, GFP_KERNEL); if (!parg->fmt) {
ret = -ENOMEM; goto fail;
}
snprintf(parg->fmt, len, "%s[%d]", parg->type->fmttype,
parg->count);
}
ret = finalize_fetch_insn(code, parg, type, type ? type - arg : 0, ctx); if (ret < 0) goto fail;
for (; code < tmp + FETCH_INSN_MAX; code++) if (code->op == FETCH_OP_END) break; /* Shrink down the code buffer */
parg->code = kcalloc(code - tmp + 1, sizeof(*code), GFP_KERNEL); if (!parg->code)
ret = -ENOMEM; else
memcpy(parg->code, tmp, sizeof(*code) * (code - tmp + 1));
fail: if (ret < 0) { for (code = tmp; code < tmp + FETCH_INSN_MAX; code++) if (code->op == FETCH_NOP_SYMBOL ||
code->op == FETCH_OP_DATA)
kfree(code->data);
}
kfree(tmp);
return ret;
}
/* Return 1 if name is reserved or already used by another argument */ staticint traceprobe_conflict_field_name(constchar *name, struct probe_arg *args, int narg)
{ int i;
for (i = 0; i < ARRAY_SIZE(reserved_field_names); i++) if (strcmp(reserved_field_names[i], name) == 0) return 1;
for (i = 0; i < narg; i++) if (strcmp(args[i].name, name) == 0) return 1;
/* * If argument name is omitted, try arg as a name (BTF variable) * or "argN".
*/ if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
end = strchr(arg, ':'); if (!end)
end = arg + strlen(arg);
name = kmemdup_nul(arg, end - arg, GFP_KERNEL); if (!name || !is_good_name(name)) {
kfree(name);
name = NULL;
}
}
if (!name)
name = kasprintf(GFP_KERNEL, "arg%d", idx + 1);
return name;
}
int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, constchar *arg, struct traceprobe_parse_context *ctx)
{ struct probe_arg *parg = &tp->args[i]; constchar *body;
ctx->tp = tp;
body = strchr(arg, '='); if (body) { if (body - arg > MAX_ARG_NAME_LEN) {
trace_probe_log_err(0, ARG_NAME_TOO_LONG); return -EINVAL;
} elseif (body == arg) {
trace_probe_log_err(0, NO_ARG_NAME); return -EINVAL;
}
parg->name = kmemdup_nul(arg, body - arg, GFP_KERNEL);
body++;
} else {
parg->name = generate_probe_arg_name(arg, i);
body = arg;
} if (!parg->name) return -ENOMEM;
staticint sprint_nth_btf_arg(int idx, constchar *type, char *buf, int bufsize, struct traceprobe_parse_context *ctx)
{ constchar *name; int ret;
if (idx >= ctx->nr_params) {
trace_probe_log_err(0, NO_BTFARG); return -ENOENT;
}
name = btf_name_by_offset(ctx->btf, ctx->params[idx].name_off); if (!name) {
trace_probe_log_err(0, NO_BTF_ENTRY); return -ENOENT;
}
ret = snprintf(buf, bufsize, "%s%s", name, type); if (ret >= bufsize) {
trace_probe_log_err(0, ARGS_2LONG); return -E2BIG;
} return ret;
}
/* Return new_argv which must be freed after use */ constchar **traceprobe_expand_meta_args(int argc, constchar *argv[], int *new_argc, char *buf, int bufsize, struct traceprobe_parse_context *ctx)
{ conststruct btf_param *params = NULL; int i, j, n, used, ret, args_idx = -1; constchar **new_argv __free(kfree) = NULL;
ret = argv_has_var_arg(argc, argv, &args_idx, ctx); if (ret < 0) return ERR_PTR(ret);
if (!ret) {
*new_argc = argc; return NULL;
}
ret = query_btf_context(ctx); if (ret < 0 || ctx->nr_params == 0) { if (args_idx != -1) { /* $arg* requires BTF info */
trace_probe_log_err(0, NOSUP_BTFARG); return (constchar **)params;
}
*new_argc = argc; return NULL;
}
new_argv = kcalloc(*new_argc, sizeof(char *), GFP_KERNEL); if (!new_argv) return ERR_PTR(-ENOMEM);
used = 0; for (i = 0, j = 0; i < argc; i++) {
trace_probe_log_set_index(i + 2); if (i == args_idx) { for (n = 0; n < ctx->nr_params; n++) {
ret = sprint_nth_btf_arg(n, "", buf + used,
bufsize - used, ctx); if (ret < 0) return ERR_PTR(ret);
new_argv[j++] = buf + used;
used += ret + 1;
} continue;
}
if (str_has_prefix(argv[i], "$arg")) { char *type = NULL;
n = simple_strtoul(argv[i] + 4, &type, 10); if (type && !(*type == ':' || *type == '\0')) {
trace_probe_log_err(0, BAD_VAR); return ERR_PTR(-ENOENT);
} /* Note: $argN starts from $arg1 */
ret = sprint_nth_btf_arg(n - 1, type, buf + used,
bufsize - used, ctx); if (ret < 0) return ERR_PTR(ret);
new_argv[j++] = buf + used;
used += ret + 1;
} else
new_argv[j++] = argv[i];
}
return_ptr(new_argv);
}
/* @buf: *buf must be equal to NULL. Caller must to free *buf */ int traceprobe_expand_dentry_args(int argc, constchar *argv[], char **buf)
{ int i, used, ret; constint bufsize = MAX_DENTRY_ARGS_LEN; char *tmpbuf __free(kfree) = NULL;
if (*buf) return -EINVAL;
used = 0; for (i = 0; i < argc; i++) { char *tmp __free(kfree) = NULL; char *equal;
size_t arg_len;
if (!glob_match("*:%p[dD]", argv[i])) continue;
if (!tmpbuf) {
tmpbuf = kmalloc(bufsize, GFP_KERNEL); if (!tmpbuf) return -ENOMEM;
}
tmp = kstrdup(argv[i], GFP_KERNEL); if (!tmp) return -ENOMEM;
/* When len=0, we just calculate the needed length */ #define LEN_OR_ZERO (len ? len - pos : 0) staticint __set_print_fmt(struct trace_probe *tp, char *buf, int len, enum probe_print_type ptype)
{ struct probe_arg *parg; int i, j; int pos = 0; constchar *fmt, *arg;
for (i = 0; i < tp->nr_args; i++) {
parg = tp->args + i; if (parg->count) { if (parg->type->is_string)
fmt = ", __get_str(%s[%d])"; else
fmt = ", REC->%s[%d]"; for (j = 0; j < parg->count; j++)
pos += snprintf(buf + pos, LEN_OR_ZERO,
fmt, parg->name, j);
} else { if (parg->type->is_string)
fmt = ", __get_str(%s)"; else
fmt = ", REC->%s";
pos += snprintf(buf + pos, LEN_OR_ZERO,
fmt, parg->name);
}
}
/* return the length of print_fmt */ return pos;
} #undef LEN_OR_ZERO
int traceprobe_set_print_fmt(struct trace_probe *tp, enum probe_print_type ptype)
{ struct trace_event_call *call = trace_probe_event_call(tp); int len; char *print_fmt;
/* First: called with 0 length to calculate the needed length */
len = __set_print_fmt(tp, NULL, 0, ptype);
print_fmt = kmalloc(len + 1, GFP_KERNEL); if (!print_fmt) return -ENOMEM;
/* Second: actually write the @print_fmt */
__set_print_fmt(tp, print_fmt, len + 1, ptype);
call->print_fmt = print_fmt;
return 0;
}
int traceprobe_define_arg_fields(struct trace_event_call *event_call,
size_t offset, struct trace_probe *tp)
{ int ret, i;
/* Set argument names as fields */ for (i = 0; i < tp->nr_args; i++) { struct probe_arg *parg = &tp->args[i]; constchar *fmt = parg->type->fmttype; int size = parg->type->size;
if (parg->fmt)
fmt = parg->fmt; if (parg->count)
size *= parg->count;
ret = trace_define_field(event_call, fmt, parg->name,
offset + parg->offset, size,
parg->type->is_signed,
FILTER_OTHER); if (ret) return ret;
} return 0;
}
if (list_empty(&tp->event->files))
trace_probe_clear_flag(tp, TP_FLAG_TRACE);
return 0;
}
/* * Return the smallest index of different type argument (start from 1). * If all argument types and name are same, return 0.
*/ int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b)
{ int i;
/* In case of more arguments */ if (a->nr_args < b->nr_args) return a->nr_args + 1; if (a->nr_args > b->nr_args) return b->nr_args + 1;
for (i = 0; i < a->nr_args; i++) { if ((b->nr_args <= i) ||
((a->args[i].type != b->args[i].type) ||
(a->args[i].count != b->args[i].count) ||
strcmp(a->args[i].name, b->args[i].name))) return i + 1;
}
return 0;
}
bool trace_probe_match_command_args(struct trace_probe *tp, int argc, constchar **argv)
{ char buf[MAX_ARGSTR_LEN + 1]; int i;
if (tp->nr_args < argc) returnfalse;
for (i = 0; i < argc; i++) {
snprintf(buf, sizeof(buf), "%s=%s",
tp->args[i].name, tp->args[i].comm); if (strcmp(buf, argv[i])) returnfalse;
} returntrue;
}
int trace_probe_create(constchar *raw_command, int (*createfn)(int, constchar **))
{ int argc = 0, ret = 0; char **argv;
argv = argv_split(GFP_KERNEL, raw_command, &argc); if (!argv) return -ENOMEM;
if (argc)
ret = createfn(argc, (constchar **)argv);
argv_free(argv);
return ret;
}
int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_args,
u8 *data, void *field)
{ void *p; int i, j;
for (i = 0; i < nr_args; i++) { struct probe_arg *a = args + i;
trace_seq_printf(s, " %s=", a->name); if (likely(!a->count)) { if (!a->type->print(s, data + a->offset, field)) return -ENOMEM; continue;
}
trace_seq_putc(s, '{');
p = data + a->offset; for (j = 0; j < a->count; j++) { if (!a->type->print(s, p, field)) return -ENOMEM;
trace_seq_putc(s, j == a->count - 1 ? '}' : ',');
p += a->type->size;
}
} return 0;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.