diff mbox series

[v13,09/12] tracing/probes: Add BTF retval type support

Message ID 168507476195.913472.16290308831790216609.stgit@mhiramat.roam.corp.google.com (mailing list archive)
State Not Applicable
Headers show
Series tracing: Add fprobe/tracepoint events | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch, async
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-6 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-2 success Logs for build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-4 success Logs for build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-5 success Logs for build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-3 success Logs for build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-19 success Logs for test_progs_no_alu32_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for test_progs_no_alu32_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-22 success Logs for test_progs_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-28 success Logs for test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-29 success Logs for veristat
bpf/vmtest-bpf-next-VM_Test-7 success Logs for test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-15 success Logs for test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for test_progs_no_alu32 on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-24 success Logs for test_progs_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-16 success Logs for test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for test_progs on s390x with gcc
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-8 success Logs for test_maps on s390x with gcc

Commit Message

Masami Hiramatsu (Google) May 26, 2023, 4:19 a.m. UTC
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Check the target function has non-void retval type and set the correct
fetch type if user doesn't specify it.
If the function returns void, $retval is rejected as below;

 # echo 'f unregister_kprobes%return $retval' >> dynamic_events
sh: write error: No such file or directory
 # cat error_log
[   37.488397] trace_fprobe: error: This function returns 'void' type
  Command: f unregister_kprobes%return $retval
                                       ^

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v8:
 - Fix wrong indentation.
Changes in v7:
 - Introduce this as a new patch.
---
 kernel/trace/trace_probe.c |   69 ++++++++++++++++++++++++++++++++++++++++----
 kernel/trace/trace_probe.h |    1 +
 2 files changed, 63 insertions(+), 7 deletions(-)

Comments

pengdonglin June 12, 2023, 7:29 a.m. UTC | #1
On 2023/5/26 12:19, Masami Hiramatsu (Google) wrote:
> From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> 
> Check the target function has non-void retval type and set the correct
> fetch type if user doesn't specify it.
> If the function returns void, $retval is rejected as below;
> 
>   # echo 'f unregister_kprobes%return $retval' >> dynamic_events
> sh: write error: No such file or directory
>   # cat error_log
> [   37.488397] trace_fprobe: error: This function returns 'void' type
>    Command: f unregister_kprobes%return $retval
>                                         ^
> 
> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> ---
> Changes in v8:
>   - Fix wrong indentation.
> Changes in v7:
>   - Introduce this as a new patch.
> ---
>   kernel/trace/trace_probe.c |   69 ++++++++++++++++++++++++++++++++++++++++----
>   kernel/trace/trace_probe.h |    1 +
>   2 files changed, 63 insertions(+), 7 deletions(-)
> 
> diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> index 7318642aceb3..dfe3e1823eec 100644
> --- a/kernel/trace/trace_probe.c
> +++ b/kernel/trace/trace_probe.c
> @@ -371,15 +371,13 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
>   	return NULL;
>   }
>   
> -static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
> -						   bool tracepoint)
> +static const struct btf_type *find_btf_func_proto(const char *funcname)
>   {
>   	struct btf *btf = traceprobe_get_btf();
> -	const struct btf_param *param;
>   	const struct btf_type *t;
>   	s32 id;
>   
> -	if (!btf || !funcname || !nr)
> +	if (!btf || !funcname)
>   		return ERR_PTR(-EINVAL);
>   
>   	id = btf_find_by_name_kind(btf, funcname, BTF_KIND_FUNC);
> @@ -396,6 +394,22 @@ static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr
>   	if (!btf_type_is_func_proto(t))
>   		return ERR_PTR(-ENOENT);
>   
> +	return t;
> +}
> +
> +static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
> +						   bool tracepoint)
> +{
> +	const struct btf_param *param;
> +	const struct btf_type *t;
> +
> +	if (!funcname || !nr)
> +		return ERR_PTR(-EINVAL);
> +
> +	t = find_btf_func_proto(funcname);
> +	if (IS_ERR(t))
> +		return (const struct btf_param *)t;
> +
>   	*nr = btf_type_vlen(t);
>   	param = (const struct btf_param *)(t + 1);
>   
> @@ -462,6 +476,32 @@ static const struct fetch_type *parse_btf_arg_type(int arg_idx,
>   	return find_fetch_type(typestr, ctx->flags);
>   }
>   
> +static const struct fetch_type *parse_btf_retval_type(
> +					struct traceprobe_parse_context *ctx)
> +{

Can we make this a common interface so that the funcgraph-retval can
  also use it to get the return type?

-- Donglin Peng

> +	struct btf *btf = traceprobe_get_btf();
> +	const char *typestr = NULL;
> +	const struct btf_type *t;
> +
> +	if (btf && ctx->funcname) {
> +		t = find_btf_func_proto(ctx->funcname);
> +		if (!IS_ERR(t))
> +			typestr = type_from_btf_id(btf, t->type);
> +	}
> +
> +	return find_fetch_type(typestr, ctx->flags);
> +}
> +
> +static bool is_btf_retval_void(const char *funcname)
> +{
> +	const struct btf_type *t;
> +
> +	t = find_btf_func_proto(funcname);
> +	if (IS_ERR(t))
> +		return false;
> +
> +	return t->type == 0;
> +}
>   #else
>   static struct btf *traceprobe_get_btf(void)
>   {
> @@ -480,8 +520,15 @@ static int parse_btf_arg(const char *varname, struct fetch_insn *code,
>   	trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
>   	return -EOPNOTSUPP;
>   }
> +
>   #define parse_btf_arg_type(idx, ctx)		\
>   	find_fetch_type(NULL, ctx->flags)
> +
> +#define parse_btf_retval_type(ctx)		\
> +	find_fetch_type(NULL, ctx->flags)
> +
> +#define is_btf_retval_void(funcname)	(false)
> +
>   #endif
>   
>   #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
> @@ -512,6 +559,11 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
>   
>   	if (strcmp(arg, "retval") == 0) {
>   		if (ctx->flags & TPARG_FL_RETURN) {
> +			if ((ctx->flags & TPARG_FL_KERNEL) &&
> +			    is_btf_retval_void(ctx->funcname)) {
> +				err = TP_ERR_NO_RETVAL;
> +				goto inval;
> +			}
>   			code->op = FETCH_OP_RETVAL;
>   			return 0;
>   		}
> @@ -912,9 +964,12 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
>   		goto fail;
>   
>   	/* Update storing type if BTF is available */
> -	if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
> -	    !t && code->op == FETCH_OP_ARG)
> -		parg->type = parse_btf_arg_type(code->param, ctx);
> +	if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && !t) {
> +		if (code->op == FETCH_OP_ARG)
> +			parg->type = parse_btf_arg_type(code->param, ctx);
> +		else if (code->op == FETCH_OP_RETVAL)
> +			parg->type = parse_btf_retval_type(ctx);
> +	}
>   
>   	ret = -EINVAL;
>   	/* Store operation */
> diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
> index c864e6dea10f..eb43bea5c168 100644
> --- a/kernel/trace/trace_probe.h
> +++ b/kernel/trace/trace_probe.h
> @@ -449,6 +449,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
>   	C(BAD_EVENT_NAME,	"Event name must follow the same rules as C identifiers"), \
>   	C(EVENT_EXIST,		"Given group/event name is already used by another event"), \
>   	C(RETVAL_ON_PROBE,	"$retval is not available on probe"),	\
> +	C(NO_RETVAL,		"This function returns 'void' type"),	\
>   	C(BAD_STACK_NUM,	"Invalid stack number"),		\
>   	C(BAD_ARG_NUM,		"Invalid argument number"),		\
>   	C(BAD_VAR,		"Invalid $-valiable specified"),	\
> 
> 
>
Masami Hiramatsu (Google) July 7, 2023, 2:12 p.m. UTC | #2
On Mon, 12 Jun 2023 15:29:00 +0800
Donglin Peng <pengdonglin@sangfor.com.cn> wrote:

> On 2023/5/26 12:19, Masami Hiramatsu (Google) wrote:
> > From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> > 
> > Check the target function has non-void retval type and set the correct
> > fetch type if user doesn't specify it.
> > If the function returns void, $retval is rejected as below;
> > 
> >   # echo 'f unregister_kprobes%return $retval' >> dynamic_events
> > sh: write error: No such file or directory
> >   # cat error_log
> > [   37.488397] trace_fprobe: error: This function returns 'void' type
> >    Command: f unregister_kprobes%return $retval
> >                                         ^
> > 
> > Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
> > ---
> > Changes in v8:
> >   - Fix wrong indentation.
> > Changes in v7:
> >   - Introduce this as a new patch.
> > ---
> >   kernel/trace/trace_probe.c |   69 ++++++++++++++++++++++++++++++++++++++++----
> >   kernel/trace/trace_probe.h |    1 +
> >   2 files changed, 63 insertions(+), 7 deletions(-)
> > 
> > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > index 7318642aceb3..dfe3e1823eec 100644
> > --- a/kernel/trace/trace_probe.c
> > +++ b/kernel/trace/trace_probe.c
> > @@ -371,15 +371,13 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
> >   	return NULL;
> >   }
> >   
> > -static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
> > -						   bool tracepoint)
> > +static const struct btf_type *find_btf_func_proto(const char *funcname)
> >   {
> >   	struct btf *btf = traceprobe_get_btf();
> > -	const struct btf_param *param;
> >   	const struct btf_type *t;
> >   	s32 id;
> >   
> > -	if (!btf || !funcname || !nr)
> > +	if (!btf || !funcname)
> >   		return ERR_PTR(-EINVAL);
> >   
> >   	id = btf_find_by_name_kind(btf, funcname, BTF_KIND_FUNC);
> > @@ -396,6 +394,22 @@ static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr
> >   	if (!btf_type_is_func_proto(t))
> >   		return ERR_PTR(-ENOENT);
> >   
> > +	return t;
> > +}
> > +
> > +static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
> > +						   bool tracepoint)
> > +{
> > +	const struct btf_param *param;
> > +	const struct btf_type *t;
> > +
> > +	if (!funcname || !nr)
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	t = find_btf_func_proto(funcname);
> > +	if (IS_ERR(t))
> > +		return (const struct btf_param *)t;
> > +
> >   	*nr = btf_type_vlen(t);
> >   	param = (const struct btf_param *)(t + 1);
> >   
> > @@ -462,6 +476,32 @@ static const struct fetch_type *parse_btf_arg_type(int arg_idx,
> >   	return find_fetch_type(typestr, ctx->flags);
> >   }
> >   
> > +static const struct fetch_type *parse_btf_retval_type(
> > +					struct traceprobe_parse_context *ctx)
> > +{
> 
> Can we make this a common interface so that the funcgraph-retval can
>   also use it to get the return type?

It is possible to expose BTF part as an independent file so that
other ftrace tracers reuse it.
But you might need to store the result for each function somewhere
in the kernel. Would you have any idea?

Thank you,

> 
> -- Donglin Peng
> 
> > +	struct btf *btf = traceprobe_get_btf();
> > +	const char *typestr = NULL;
> > +	const struct btf_type *t;
> > +
> > +	if (btf && ctx->funcname) {
> > +		t = find_btf_func_proto(ctx->funcname);
> > +		if (!IS_ERR(t))
> > +			typestr = type_from_btf_id(btf, t->type);
> > +	}
> > +
> > +	return find_fetch_type(typestr, ctx->flags);
> > +}
> > +
> > +static bool is_btf_retval_void(const char *funcname)
> > +{
> > +	const struct btf_type *t;
> > +
> > +	t = find_btf_func_proto(funcname);
> > +	if (IS_ERR(t))
> > +		return false;
> > +
> > +	return t->type == 0;
> > +}
> >   #else
> >   static struct btf *traceprobe_get_btf(void)
> >   {
> > @@ -480,8 +520,15 @@ static int parse_btf_arg(const char *varname, struct fetch_insn *code,
> >   	trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
> >   	return -EOPNOTSUPP;
> >   }
> > +
> >   #define parse_btf_arg_type(idx, ctx)		\
> >   	find_fetch_type(NULL, ctx->flags)
> > +
> > +#define parse_btf_retval_type(ctx)		\
> > +	find_fetch_type(NULL, ctx->flags)
> > +
> > +#define is_btf_retval_void(funcname)	(false)
> > +
> >   #endif
> >   
> >   #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
> > @@ -512,6 +559,11 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
> >   
> >   	if (strcmp(arg, "retval") == 0) {
> >   		if (ctx->flags & TPARG_FL_RETURN) {
> > +			if ((ctx->flags & TPARG_FL_KERNEL) &&
> > +			    is_btf_retval_void(ctx->funcname)) {
> > +				err = TP_ERR_NO_RETVAL;
> > +				goto inval;
> > +			}
> >   			code->op = FETCH_OP_RETVAL;
> >   			return 0;
> >   		}
> > @@ -912,9 +964,12 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
> >   		goto fail;
> >   
> >   	/* Update storing type if BTF is available */
> > -	if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
> > -	    !t && code->op == FETCH_OP_ARG)
> > -		parg->type = parse_btf_arg_type(code->param, ctx);
> > +	if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && !t) {
> > +		if (code->op == FETCH_OP_ARG)
> > +			parg->type = parse_btf_arg_type(code->param, ctx);
> > +		else if (code->op == FETCH_OP_RETVAL)
> > +			parg->type = parse_btf_retval_type(ctx);
> > +	}
> >   
> >   	ret = -EINVAL;
> >   	/* Store operation */
> > diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
> > index c864e6dea10f..eb43bea5c168 100644
> > --- a/kernel/trace/trace_probe.h
> > +++ b/kernel/trace/trace_probe.h
> > @@ -449,6 +449,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
> >   	C(BAD_EVENT_NAME,	"Event name must follow the same rules as C identifiers"), \
> >   	C(EVENT_EXIST,		"Given group/event name is already used by another event"), \
> >   	C(RETVAL_ON_PROBE,	"$retval is not available on probe"),	\
> > +	C(NO_RETVAL,		"This function returns 'void' type"),	\
> >   	C(BAD_STACK_NUM,	"Invalid stack number"),		\
> >   	C(BAD_ARG_NUM,		"Invalid argument number"),		\
> >   	C(BAD_VAR,		"Invalid $-valiable specified"),	\
> > 
> > 
> > 
>
diff mbox series

Patch

diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 7318642aceb3..dfe3e1823eec 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -371,15 +371,13 @@  static const char *type_from_btf_id(struct btf *btf, s32 id)
 	return NULL;
 }
 
-static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
-						   bool tracepoint)
+static const struct btf_type *find_btf_func_proto(const char *funcname)
 {
 	struct btf *btf = traceprobe_get_btf();
-	const struct btf_param *param;
 	const struct btf_type *t;
 	s32 id;
 
-	if (!btf || !funcname || !nr)
+	if (!btf || !funcname)
 		return ERR_PTR(-EINVAL);
 
 	id = btf_find_by_name_kind(btf, funcname, BTF_KIND_FUNC);
@@ -396,6 +394,22 @@  static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr
 	if (!btf_type_is_func_proto(t))
 		return ERR_PTR(-ENOENT);
 
+	return t;
+}
+
+static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
+						   bool tracepoint)
+{
+	const struct btf_param *param;
+	const struct btf_type *t;
+
+	if (!funcname || !nr)
+		return ERR_PTR(-EINVAL);
+
+	t = find_btf_func_proto(funcname);
+	if (IS_ERR(t))
+		return (const struct btf_param *)t;
+
 	*nr = btf_type_vlen(t);
 	param = (const struct btf_param *)(t + 1);
 
@@ -462,6 +476,32 @@  static const struct fetch_type *parse_btf_arg_type(int arg_idx,
 	return find_fetch_type(typestr, ctx->flags);
 }
 
+static const struct fetch_type *parse_btf_retval_type(
+					struct traceprobe_parse_context *ctx)
+{
+	struct btf *btf = traceprobe_get_btf();
+	const char *typestr = NULL;
+	const struct btf_type *t;
+
+	if (btf && ctx->funcname) {
+		t = find_btf_func_proto(ctx->funcname);
+		if (!IS_ERR(t))
+			typestr = type_from_btf_id(btf, t->type);
+	}
+
+	return find_fetch_type(typestr, ctx->flags);
+}
+
+static bool is_btf_retval_void(const char *funcname)
+{
+	const struct btf_type *t;
+
+	t = find_btf_func_proto(funcname);
+	if (IS_ERR(t))
+		return false;
+
+	return t->type == 0;
+}
 #else
 static struct btf *traceprobe_get_btf(void)
 {
@@ -480,8 +520,15 @@  static int parse_btf_arg(const char *varname, struct fetch_insn *code,
 	trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
 	return -EOPNOTSUPP;
 }
+
 #define parse_btf_arg_type(idx, ctx)		\
 	find_fetch_type(NULL, ctx->flags)
+
+#define parse_btf_retval_type(ctx)		\
+	find_fetch_type(NULL, ctx->flags)
+
+#define is_btf_retval_void(funcname)	(false)
+
 #endif
 
 #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
@@ -512,6 +559,11 @@  static int parse_probe_vars(char *arg, const struct fetch_type *t,
 
 	if (strcmp(arg, "retval") == 0) {
 		if (ctx->flags & TPARG_FL_RETURN) {
+			if ((ctx->flags & TPARG_FL_KERNEL) &&
+			    is_btf_retval_void(ctx->funcname)) {
+				err = TP_ERR_NO_RETVAL;
+				goto inval;
+			}
 			code->op = FETCH_OP_RETVAL;
 			return 0;
 		}
@@ -912,9 +964,12 @@  static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
 		goto fail;
 
 	/* Update storing type if BTF is available */
-	if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
-	    !t && code->op == FETCH_OP_ARG)
-		parg->type = parse_btf_arg_type(code->param, ctx);
+	if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && !t) {
+		if (code->op == FETCH_OP_ARG)
+			parg->type = parse_btf_arg_type(code->param, ctx);
+		else if (code->op == FETCH_OP_RETVAL)
+			parg->type = parse_btf_retval_type(ctx);
+	}
 
 	ret = -EINVAL;
 	/* Store operation */
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index c864e6dea10f..eb43bea5c168 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -449,6 +449,7 @@  extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
 	C(BAD_EVENT_NAME,	"Event name must follow the same rules as C identifiers"), \
 	C(EVENT_EXIST,		"Given group/event name is already used by another event"), \
 	C(RETVAL_ON_PROBE,	"$retval is not available on probe"),	\
+	C(NO_RETVAL,		"This function returns 'void' type"),	\
 	C(BAD_STACK_NUM,	"Invalid stack number"),		\
 	C(BAD_ARG_NUM,		"Invalid argument number"),		\
 	C(BAD_VAR,		"Invalid $-valiable specified"),	\