Skip to main content
added 49 characters in body
Source Link
Stéphane Chazelas
  • 584.6k
  • 96
  • 1.1k
  • 1.7k

If execve() succeeds, it will mostly wipe the process memory, andwould handle the file based on its type:

NowThe process memory will have been mostly wiped in the process and execve() may fail for a number of reasons and will then return anever returns as the process is now running the code identifying itin the executable. One of them is ENOEXEC when

If the file format is not in a format recognised byexecve() will return -1 (indicating failure) with ENOEXEC as the systemerror code.

If execve() succeeds, it will mostly wipe the process memory, and handle the file based on its type

Now execve() may fail for a number of reasons and will then return a code identifying it. One of them is ENOEXEC when the file is not in a format recognised by the system.

If execve() would handle the file based on its type:

The process memory will have been mostly wiped in the process and execve() never returns as the process is now running the code in the executable.

If the file format is not recognised execve() will return -1 (indicating failure) with ENOEXEC as the error code.

Source Link
Stéphane Chazelas
  • 584.6k
  • 96
  • 1.1k
  • 1.7k

Before all, it's the system that executes commands.

With this shell code:

cmd and its args

The shell looks up cmd in aliases, functions, builtins and executable files (regular files which you have permission to execute) in directories listed in $PATH (not the current working directory unless it happens to be in $PATH which would be bad practice).

If the latter, it calls the execve() system call usually in a child process with 3 arguments:

  1. The path to the the file (/path/to/cmd)
  2. A list of arguments (["cmd", "and", "its", "args", 0])
  3. A list of var=value strings for each of the exported variables.

If execve() succeeds, it will mostly wipe the process memory, and handle the file based on its type

  • if a ELF binary executable, it will load/map it (or sections thereof) in memory along with possibly a dynamic linker which will take care loading more shared libraries... and start running it.
  • if it starts with #!, it (not the shell) will interpret the rest of the line as another file to execute and will pass the path of the file as an extra argument to that command. (if it's #! foo bar, it will do the equivalent of execve("foo", ["foo" or "cmd", "bar", "/path/to/cmd", "and", "its", "args"], env)).
  • systems may support a number of different native executable file formats instead of in addition to ELF, and some can also be configured to associate interpreters to patterns matching the start of the file (see binfmt_misc on Linux).

Now execve() may fail for a number of reasons and will then return a code identifying it. One of them is ENOEXEC when the file is not in a format recognised by the system.

In that case, POSIX shells, but also things like the execlp() C function (not syscall) or env/find -exec... (or generally all things that are meant to execute commands in the POSIX toolchest) are required to treat that as a sh script (though possibly after having checked it looks like one using some heuristics to avoid running sh on some random stuff).

Most shells do that by executing sh on it as if there was a #! /path/to/the/standard/sh - shebang. Some which are POSIX compliant sh implementations or have a POSIX sh mode do it by interpreting the files themselves in a child process.

So for your three cases:

  1. shebang-less script, run with ./script rather than script which would likely run /usr/bin/script instead: the shell runs execve("./script", ["./script", 0], env) in a child process, and execve() fails with ENOEXEC because it's not in a known format, so the shell will (optionally) look at it, see it looks like it could be in sh syntax and run execve("/bin/sh", ["sh", "-", "./script"]) (with variations on the argv[0], or -) or interpret it itself (and the parent shell process waits for its termination).
  2. ./exe: the shell does execve("./exe", ["./exe", 0], env) in a child process which succeeds and the parent waits for its termination.
  3. ./script with a #! /bin/tcsh shebang: the shell does execve("./script", ["./script", 0], env) which also succeeds as the system recognises it as a script. execve() will in turn run /bin/tcsh with ./script as argument. And the parent shell waits for its child as usual.