Exec System Call
Welcome to the first installment in a blog series that will takes a deep dive into the mechanics of system calls. Today, we’re kicking things off with the exec system calls, a fundamental feature in Unix and Linux operating systems.
Glibc version used : 2.37 Bootlin
Introduction to the exec library calls
Note: Please skip this section if you are already familiar with exec library calls.
In Unix and Linux systems, the exec
family of library calls plays an essential role in process creation and management. While the general role of each function in the exec
family is similar, they differ in how they accept arguments and in certain behaviors. These are the functions a user would typically use to interface with the kernel (exec syscall) to create a new process.
Here’s an introduction to each member of the exec
family:
execve
: This function is the core of all exec
functions, which all others essentially wrap around. It allows the specification of the argument list and environment variables. It is the only exec
function that is a system call that interfaces directly with the kernel.
char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/usr/bin", NULL};
execve("/bin/ls", args, env);
execl
: This function takes a list of arguments individually, with the list terminated by a null pointer. It’s handy when the number of parameters is known in advance.
execl("/bin/ls", "ls", "-l", NULL);
execv
: This function takes an array of pointers to null-terminated strings that represent the argument list available to the new program. It’s used when the number of parameters isn’t known beforehand.
char *args[] = {"ls", "-l", NULL};
execv("/bin/ls", args);
execle
: This function is similar to execl
, but it also allows the caller to specify the environment of the executed program.
char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/usr/bin", NULL};
execle("/bin/ls", "ls", "-l", NULL, env);
execlp
, execvp
, execvpe
and execlpe
: These functions are similar to execl
and execv
, respectively. However, they use the PATH
environment variable to find the program file to execute, which means you can use relative paths to executable files.
char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/usr/bin", NULL};
execlp("ls", "ls", "-l", NULL);
//
execvp("ls", args);
//
execvpe("ls", args, env);
//
execlpe("ls", "ls", "-l", NULL, env);
//
Library to the Kernel - Invoking
The library calls can be found in the glibc source code, it’s not a surprice that they are just wrappers for calling the kernel functions. All functions were found in the posix
directory.
For example, the execv
function can be found in the execv.c
file Link, and looks like this :
int
execv (const char *path, char *const argv[])
{
return __execve (path, argv, __environ);
}
Let’s now look at how __execve in implemented, and how it calls the system-call. It can be found in execv.c
Link
int
__execve (const char *path, char *const argv[], char *const envp[])
{
if (path == NULL || argv == NULL || envp == NULL)
{
__set_errno (EINVAL);
return -1;
}
__set_errno (ENOSYS);
return -1;
}
stub_warning (execve)
weak_alias (__execve, execve)
Wait! So how does it actually call the system call? Okay looks like we are gonna have to dig into how the glibc calls the system call. Let’s look at both stub_warning
and weak_alias
functions.
What is stub_warning
?
stub_warning
is a macro that is defined in libc-symbols.h
Link and looks like this (with comments added to explain):
/* A canned warning for sysdeps/stub functions. */
#define stub_warning(name) \
__make_section_unallocated (".gnu.glibc-stub." #name) \
link_warning (name, #name " is not implemented and will always fail")
#define __make_section_unallocated(section_string) \
asm (".section " section_string "\n\t.previous");
#define link_warning(symbol, msg) \
__make_section_unallocated (".gnu.warning." #symbol) \
static const char __evoke_link_warning_##symbol[] \
__attribute__ ((used, section (".gnu.warning." #symbol __sec_comment))) \
= msg;
For some context :
.section
- This is a assembler directive to the assembler to switch to a different section. The section name is passed as an argument to the directive..previous
- This is a assembler directive to the assembler to switch back to the previous section.
So the __make_section_unallocated
switches to a different section, and then has the .previous
directive to switch back to the previous section. This is done to make sure that the section is not allocated in the final binary.
link_warning(symbol, msg)
: This macro defines a string with a warning message (msg) that will be associated with the symbol. The string is placed in a special section (.gnu.warning.symbol) of the binary.
The idea here is that when the function (stub) is used, the linker will see the .gnu.warning.symbol section and output the warning message. This allows for a message to be shown at link time if a stub function is being used.
…. TODO: rest