You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2026/10/14 13:27:17 UTC
(nuttx) 02/05: binfmt/script: Give the interpreter the script path and its #! argument.
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 7c4ad4b4ee38eb0e08da0e9e96bee7683f96eb1e
Author: Marco Casaroli <ma...@gmail.com>
AuthorDate: Mon Oct 12 05:31:41 2026 +0200
binfmt/script: Give the interpreter the script path and its #! argument.
The interpreter of a script got the script's own argv as its arguments,
with the file name as argv[0]. The script path was only the caller's
argv[0], which is often not a path: a script found through PATH ran as
"sh name" and the shell did not find the file. The argument after the
interpreter on the "#!" line was dropped, so with "#!/usr/bin/env sh"
env got the caller's argv[0], the script itself, as the program to run.
In a kernel build the task name was read from the caller's memory after
the switch to the new address environment, and the kernel faulted.
Let a loader give, in struct binary_s, an argument list that takes the
place of the caller's argv[0]. The script handler puts there the
interpreter, the optional argument and the full script path.
binfmt_copyargv() builds the list that Linux uses, interp [arg] script
argv[1] ...: in a kernel build in the copy that it already makes, in
other builds in a new list of pointers, only when a loader gave one.
exec_module() frees the loader's list when the task has its arguments,
and unload_module() when the program does not start. For a script
whose interpreter is a script, the entries of the inner script come
first. The task takes its name from argv[0], the interpreter, as for
any other program.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
binfmt/binfmt.h | 28 ++---
binfmt/binfmt_copyargv.c | 247 +++++++++++++++++++++++++++++++++---------
binfmt/binfmt_execmodule.c | 19 ++--
binfmt/binfmt_unloadmodule.c | 5 +
binfmt/script.c | 132 +++++++++++++++++++---
include/nuttx/binfmt/binfmt.h | 9 +-
6 files changed, 344 insertions(+), 96 deletions(-)
diff --git a/binfmt/binfmt.h b/binfmt/binfmt.h
index 84000211c44..c8103990503 100644
--- a/binfmt/binfmt.h
+++ b/binfmt/binfmt.h
@@ -84,44 +84,44 @@ int binfmt_dumpmodule(FAR const struct binary_s *bin);
* Name: binfmt_copyargv
*
* Description:
+ * Get the argument list of the new program. If ldargv is not NULL, it
+ * takes the place of argv[0]: a loader gives the program to run and its
+ * first arguments (for example, the interpreter of a script).
+ *
* In the kernel build, the argv list will likely lie in the caller's
* address environment and, hence, be inaccessible when we switch to the
* address environment of the new process address environment. So we
* do not have any real option other than to copy the callers argv[] list.
*
* Input Parameters:
+ * copy - The argument list to use
+ * ldargv - The argument list of the loader, or NULL
* argv - Argument list
*
* Returned Value:
- * A non-zero copy is returned on success.
+ * Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
-#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL)
-int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv);
-#else
-# define binfmt_copyargv(copy, argv) (*(copy) = (argv), 0)
-#endif
+int binfmt_copyargv(FAR char * const **copy, FAR char * const *ldargv,
+ FAR char * const *argv);
/****************************************************************************
* Name: binfmt_freeargv
*
* Description:
- * Release the copied argv[] list.
+ * Release the list that binfmt_copyargv() returned, if it allocated it.
*
* Input Parameters:
* argv - Argument list
+ * ldargv - The argument list of the loader that binfmt_copyargv() got
*
* Returned Value:
* None
*
****************************************************************************/
-#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL)
-void binfmt_freeargv(FAR char * const *argv);
-#else
-# define binfmt_freeargv(argv)
-#endif
+void binfmt_freeargv(FAR char * const *argv, FAR char * const *ldargv);
/****************************************************************************
* Name: binfmt_copyenv
@@ -142,7 +142,7 @@ void binfmt_freeargv(FAR char * const *argv);
****************************************************************************/
#ifndef CONFIG_DISABLE_ENVIRON
-# define binfmt_copyenv(copy, envp) binfmt_copyargv(copy, envp)
+# define binfmt_copyenv(copy, envp) binfmt_copyargv(copy, NULL, envp)
#else
# define binfmt_copyenv(copy, envp) (*(copy) = (envp), 0)
#endif
@@ -162,7 +162,7 @@ void binfmt_freeargv(FAR char * const *argv);
****************************************************************************/
#ifndef CONFIG_DISABLE_ENVIRON
-# define binfmt_freeenv(envp) binfmt_freeargv(envp)
+# define binfmt_freeenv(envp) binfmt_freeargv(envp, NULL)
#else
# define binfmt_freeenv(envp)
#endif
diff --git a/binfmt/binfmt_copyargv.c b/binfmt/binfmt_copyargv.c
index d59aa62e3bf..018fcc63d6b 100644
--- a/binfmt/binfmt_copyargv.c
+++ b/binfmt/binfmt_copyargv.c
@@ -35,12 +35,75 @@
#include "binfmt.h"
-#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL) && !defined(CONFIG_BINFMT_DISABLE)
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: binfmt_nargs
+ *
+ * Description:
+ * Count the arguments of the list that binfmt_copyargv() builds: all of
+ * ldargv, then argv without argv[0] if ldargv is not NULL, or all of argv
+ * if it is.
+ *
+ ****************************************************************************/
+
+static int binfmt_nargs(FAR char * const *ldargv, FAR char * const *argv,
+ FAR int *nld)
+{
+ int nargs = 0;
+ int i;
+
+ *nld = 0;
+ if (ldargv != NULL)
+ {
+ while (ldargv[*nld] != NULL)
+ {
+ (*nld)++;
+ }
+ }
+
+ if (argv != NULL)
+ {
+ for (i = ldargv != NULL && argv[0] != NULL; argv[i] != NULL; i++)
+ {
+ if (++nargs > CONFIG_TASK_MAX_ARGS)
+ {
+ berr("ERROR: Too many arguments\n");
+ return -E2BIG;
+ }
+ }
+ }
+
+ return *nld + nargs;
+}
+
+/****************************************************************************
+ * Name: binfmt_arg
+ *
+ * Description:
+ * Return argument i of the list that binfmt_copyargv() builds.
+ *
+ ****************************************************************************/
+
+static FAR char *binfmt_arg(FAR char * const *ldargv,
+ FAR char * const *argv, int nld, int i)
+{
+ if (i < nld)
+ {
+ return ldargv[i];
+ }
+
+ return argv[i - nld + (ldargv != NULL)];
+}
/****************************************************************************
* Public Functions
****************************************************************************/
+#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL)
+
/****************************************************************************
* Name: binfmt_copyargv
*
@@ -50,15 +113,22 @@
* address environment of the new process address environment. So we
* do not have any real option other than to copy the callers argv[] list.
*
+ * If ldargv is not NULL, it takes the place of argv[0]: a loader gives
+ * the program to run and its first arguments (for example, the
+ * interpreter of a script).
+ *
* Input Parameters:
+ * copy - The copy of the argument list
+ * ldargv - The argument list of the loader, or NULL
* argv - Argument list
*
* Returned Value:
- * A non-zero copy is returned on success.
+ * Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
-int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv)
+int binfmt_copyargv(FAR char * const **copy, FAR char * const *ldargv,
+ FAR char * const *argv)
{
FAR char **argvbuf = NULL;
FAR const char *arg;
@@ -67,73 +137,60 @@ int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv)
size_t argvsize;
size_t argsize = 0;
size_t len;
- int nargs = 0;
+ int nargs;
+ int nld;
int i;
/* Get the number of arguments and the size of the argument list */
- if (argv)
+ nargs = binfmt_nargs(ldargv, argv, &nld);
+ if (nargs < 0)
{
- for (i = 0; (arg = argv[i]) != NULL; i++)
- {
- /* Increment the size of the allocation with the size of the next
- * string
- */
+ return nargs;
+ }
- argsize += strlen(arg) + 1;
- nargs++;
+ for (i = 0; i < nargs; i++)
+ {
+ argsize += strlen(binfmt_arg(ldargv, argv, nld, i)) + 1;
+ }
- /* This is a sanity check to prevent running away with an
- * unterminated argv[] list.
- * CONFIG_TASK_MAX_ARGS should be sufficiently large that this
- * never happens in normal usage.
- */
+ binfo("args=%d argsize=%zu\n", nargs, argsize);
- if (nargs > CONFIG_TASK_MAX_ARGS)
- {
- berr("ERROR: Too many arguments: %zu\n", argsize);
- return -E2BIG;
- }
- }
+ /* Allocate the argv array and an argument buffer */
- binfo("args=%d argsize=%zu\n", nargs, argsize);
+ if (argsize > 0)
+ {
+ argvsize = (nargs + 1) * sizeof(FAR char *);
+ ptr = kmm_malloc(argvsize + argsize);
+ if (!ptr)
+ {
+ berr("ERROR: Failed to allocate the argument buffer\n");
+ return -ENOMEM;
+ }
- /* Allocate the argv array and an argument buffer */
+ /* Copy the argv list */
- if (argsize > 0)
+ argvbuf = (FAR char **)ptr;
+ ptr += argvsize;
+ end = ptr + argsize;
+ for (i = 0; i < nargs; i++)
{
- argvsize = (nargs + 1) * sizeof(FAR char *);
- ptr = kmm_malloc(argvsize + argsize);
- if (!ptr)
+ arg = binfmt_arg(ldargv, argv, nld, i);
+ if (ptr == end || arg == NULL)
{
- berr("ERROR: Failed to allocate the argument buffer\n");
- return -ENOMEM;
+ kmm_free(argvbuf);
+ return -EFAULT;
}
- /* Copy the argv list */
-
- argvbuf = (FAR char **)ptr;
- ptr += argvsize;
- end = ptr + argsize;
- for (i = 0; i < nargs; i++)
- {
- arg = argv[i];
- if (ptr == end || arg == NULL)
- {
- kmm_free(argvbuf);
- return -EFAULT;
- }
-
- len = strnlen(arg, end - ptr - 1);
- argvbuf[i] = memcpy(ptr, arg, len);
- ptr[len] = '\0';
- ptr += len + 1;
- }
+ len = strnlen(arg, end - ptr - 1);
+ argvbuf[i] = memcpy(ptr, arg, len);
+ ptr[len] = '\0';
+ ptr += len + 1;
+ }
- /* Terminate the argv[] list */
+ /* Terminate the argv[] list */
- argvbuf[i] = NULL;
- }
+ argvbuf[i] = NULL;
}
*copy = argvbuf;
@@ -148,16 +205,19 @@ int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv)
*
* Input Parameters:
* argv - Argument list
+ * ldargv - The argument list of the loader that binfmt_copyargv() got
*
* Returned Value:
* None
*
****************************************************************************/
-void binfmt_freeargv(FAR char * const *argv)
+void binfmt_freeargv(FAR char * const *argv, FAR char * const *ldargv)
{
/* Is there an allocated argument buffer */
+ UNUSED(ldargv);
+
if (argv)
{
/* Free the argument buffer */
@@ -166,4 +226,83 @@ void binfmt_freeargv(FAR char * const *argv)
}
}
+#else
+
+/****************************************************************************
+ * Name: binfmt_copyargv
+ *
+ * Description:
+ * All tasks share one address space, so the caller's argv[] list is used
+ * as it is. If ldargv is not NULL, it takes the place of argv[0]: the
+ * new list of pointers is allocated, the strings are not copied.
+ *
+ * Input Parameters:
+ * copy - The argument list to use
+ * ldargv - The argument list of the loader, or NULL
+ * argv - Argument list
+ *
+ * Returned Value:
+ * Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int binfmt_copyargv(FAR char * const **copy, FAR char * const *ldargv,
+ FAR char * const *argv)
+{
+ FAR char **argvbuf;
+ int nargs;
+ int nld;
+ int i;
+
+ if (ldargv == NULL)
+ {
+ *copy = argv;
+ return OK;
+ }
+
+ nargs = binfmt_nargs(ldargv, argv, &nld);
+ if (nargs < 0)
+ {
+ return nargs;
+ }
+
+ argvbuf = kmm_malloc((nargs + 1) * sizeof(FAR char *));
+ if (argvbuf == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < nargs; i++)
+ {
+ argvbuf[i] = binfmt_arg(ldargv, argv, nld, i);
+ }
+
+ argvbuf[nargs] = NULL;
+ *copy = argvbuf;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: binfmt_freeargv
+ *
+ * Description:
+ * Release the list that binfmt_copyargv() allocated, if it did.
+ *
+ * Input Parameters:
+ * argv - Argument list
+ * ldargv - The argument list of the loader that binfmt_copyargv() got
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+void binfmt_freeargv(FAR char * const *argv, FAR char * const *ldargv)
+{
+ if (ldargv != NULL)
+ {
+ kmm_free((FAR char **)argv);
+ }
+}
+
#endif
diff --git a/binfmt/binfmt_execmodule.c b/binfmt/binfmt_execmodule.c
index 63513eb3928..dc33d9baee1 100644
--- a/binfmt/binfmt_execmodule.c
+++ b/binfmt/binfmt_execmodule.c
@@ -212,7 +212,7 @@ int exec_module(FAR struct binary_s *binp,
return -ENOMEM;
}
- ret = binfmt_copyargv(&argv, argv);
+ ret = binfmt_copyargv(&argv, binp->argv, argv);
if (ret < 0)
{
goto errout_with_tcb;
@@ -279,13 +279,7 @@ int exec_module(FAR struct binary_s *binp,
stackaddr = binp->stackaddr;
#endif
- /* If the binfmt handler requested argv preservation (e.g. script
- * interpreter rewrites), use filename as the task name and forward the
- * full argv. Otherwise the conventional path consumes argv[0] as the
- * task name and forwards argv[1..] as arguments.
- */
-
- if (argv && argv[0] && !binp->filename)
+ if (argv && argv[0])
{
ret = nxtask_init(tcb, argv[0], binp->priority, stackaddr,
binp->stacksize, binp->entrypt, &argv[1],
@@ -307,9 +301,14 @@ int exec_module(FAR struct binary_s *binp,
/* The copied argv and envp can now be released */
binfmt_freeactions(actions);
- binfmt_freeargv(argv);
+ binfmt_freeargv(argv, binp->argv);
binfmt_freeenv(envp);
+ /* The task has its arguments: the list of the loader is not needed */
+
+ kmm_free(binp->argv);
+ binp->argv = NULL;
+
#ifdef CONFIG_PIC
/* Add the D-Space address as the PIC base address. By convention, this
* must be the first allocated address space.
@@ -420,7 +419,7 @@ errout_with_actions:
errout_with_envp:
binfmt_freeenv(envp);
errout_with_args:
- binfmt_freeargv(argv);
+ binfmt_freeargv(argv, binp->argv);
errout_with_tcb:
kmm_free(tcb);
return ret;
diff --git a/binfmt/binfmt_unloadmodule.c b/binfmt/binfmt_unloadmodule.c
index c4710936571..5a26e1b93f0 100644
--- a/binfmt/binfmt_unloadmodule.c
+++ b/binfmt/binfmt_unloadmodule.c
@@ -86,6 +86,11 @@ int unload_module(FAR struct binary_s *binp)
}
}
+ /* Free the argument list of the loader */
+
+ kmm_free(binp->argv);
+ binp->argv = NULL;
+
/* Unmap mapped address spaces */
if (binp->mapped)
diff --git a/binfmt/script.c b/binfmt/script.c
index dfedcb9d55d..4e14af5fe0d 100644
--- a/binfmt/script.c
+++ b/binfmt/script.c
@@ -34,6 +34,7 @@
#include <nuttx/binfmt/binfmt.h>
#include <nuttx/fs/fs.h>
+#include <nuttx/kmalloc.h>
#include "binfmt.h"
@@ -67,15 +68,85 @@ static struct binfmt_s g_script_binfmt =
* Private Functions
****************************************************************************/
+/****************************************************************************
+ * Name: script_setargv
+ *
+ * Description:
+ * Set binp->argv to: interp, arg (if not NULL), filename, then the
+ * entries after the first of the old list (a script whose interpreter is
+ * a script). All strings are copied into one allocation.
+ *
+ ****************************************************************************/
+
+static int script_setargv(FAR struct binary_s *binp, FAR const char *interp,
+ FAR const char *arg, FAR const char *filename)
+{
+ FAR char **old = binp->argv;
+ FAR const char *src[3];
+ FAR char **list;
+ FAR char *str;
+ size_t size = 0;
+ int nsrc = 0;
+ int nold = 0;
+ int nargs;
+ int i;
+
+ src[nsrc++] = interp;
+ if (arg != NULL)
+ {
+ src[nsrc++] = arg;
+ }
+
+ src[nsrc++] = filename;
+
+ if (old != NULL)
+ {
+ while (old[nold] != NULL)
+ {
+ nold++;
+ }
+ }
+
+ nargs = nsrc + (nold > 1 ? nold - 1 : 0);
+ for (i = 0; i < nsrc; i++)
+ {
+ size += strlen(src[i]) + 1;
+ }
+
+ for (i = 1; i < nold; i++)
+ {
+ size += strlen(old[i]) + 1;
+ }
+
+ list = kmm_malloc((nargs + 1) * sizeof(FAR char *) + size);
+ if (list == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ str = (FAR char *)&list[nargs + 1];
+ for (i = 0; i < nargs; i++)
+ {
+ FAR const char *s = i < nsrc ? src[i] : old[i - nsrc + 1];
+
+ list[i] = strcpy(str, s);
+ str += strlen(s) + 1;
+ }
+
+ list[nargs] = NULL;
+ binp->argv = list;
+ return OK;
+}
+
/****************************************************************************
* Name: script_loadbinary
*
* Description:
* Verify that the file starts with "#!" and parse the shebang line to
* extract the interpreter path (and optional argument), then delegate
- * loading to the interpreter via load_module(). Sets binp->filename
- * so exec_module() launches the interpreter with the script path
- * preserved in the forwarded argv.
+ * loading to the interpreter via load_module(). Sets binp->argv
+ * so exec_module() runs the interpreter with the optional argument and
+ * the script path before the caller's arguments.
*
****************************************************************************/
@@ -85,7 +156,9 @@ static int script_loadbinary(FAR struct binary_s *binp,
int nexports)
{
char header[SCRIPT_HEADER_MAX];
+ FAR char **oldargv = binp->argv;
FAR char *interp;
+ FAR char *arg = NULL;
FAR char *end;
struct file file;
int ret;
@@ -137,10 +210,9 @@ static int script_loadbinary(FAR struct binary_s *binp,
return -ENOEXEC;
}
- /* Terminate the interpreter token at the first whitespace. Any
- * optional argument after the interpreter is ignored -- the
- * interpreter is relaunched with the script path as argv[1], which
- * is enough for sh / lua / python to auto-execute the script.
+ /* The interpreter ends at the first whitespace. The rest of the line,
+ * without the whitespace around it, is one optional argument, as in
+ * Linux.
*/
end = interp;
@@ -149,18 +221,50 @@ static int script_loadbinary(FAR struct binary_s *binp,
end++;
}
- *end = '\0';
+ if (*end != '\0')
+ {
+ *end++ = '\0';
+ while (isspace(*end))
+ {
+ end++;
+ }
+
+ if (*end != '\0')
+ {
+ arg = end;
+ end = arg + strlen(arg);
+ while (isspace(*(end - 1)))
+ {
+ end--;
+ }
+
+ *end = '\0';
+ }
+ }
- binfo("Script: interp='%s' script='%s'\n", interp, filename);
+ binfo("Script: interp='%s' arg='%s' script='%s'\n",
+ interp, arg ? arg : "", filename);
- /* Ask exec_module() to keep the original filename as the task name and
- * forward the unchanged argv, so the interpreter receives the script
- * path (the original filename) at argv[1].
+ /* The interpreter runs with: interp [arg] filename argv[1] ...
+ * filename is the path that was loaded, after the PATH search.
*/
- binp->filename = true;
+ ret = script_setargv(binp, interp, arg, filename);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ ret = load_module(binp, interp, exports, nexports);
+ if (ret < 0)
+ {
+ kmm_free(binp->argv);
+ binp->argv = oldargv;
+ return ret;
+ }
- return load_module(binp, interp, exports, nexports);
+ kmm_free(oldargv);
+ return ret;
}
/****************************************************************************
diff --git a/include/nuttx/binfmt/binfmt.h b/include/nuttx/binfmt/binfmt.h
index e3789330f18..669357557a7 100644
--- a/include/nuttx/binfmt/binfmt.h
+++ b/include/nuttx/binfmt/binfmt.h
@@ -30,7 +30,6 @@
#include <nuttx/config.h>
#include <spawn.h>
-#include <stdbool.h>
#include <sys/types.h>
#include <nuttx/sched.h>
@@ -112,9 +111,11 @@ struct binary_s
FAR void *stackaddr; /* Task stack address */
#endif
- bool filename; /* Use filename as task name instead
- * of argv[0]; preserve full argv.
- */
+ FAR char **argv; /* Arguments in place of argv[0],
+ * from the loader, or NULL: for
+ * example the interpreter of a
+ * script, its argument and the
+ * script. Freed by unload_module() */
/* Unload module callback */