Linux - execfn
In Linux, execfn (short for execution filename) is the pathname of the program that was executed. The Linux kernel pushes this value onto the stack when starting a new process, presenting it as part of the Auxiliary Vector (auxv) under the key AT_EXECFN.
Understanding how execfn works is useful when you need to know exactly how a program was invoked, especially for purposes like self-relocation, finding configuration files, or debugging.
What is auxv?
Auxiliary Vector is a way for the kernel to pass information to the user space process at startup. It is a list of key-value pairs that are stored in memory and can be read by the user space process. The auxiliary vector is stored at the top of the stack and can be accessed by the user space process using the getauxval() function.
The auxiliary vector is generated per execve call, meaning it is unique to each individual process image instantiation, rather than being a static blueprint assigned to a program file.
Why does execfn exist?
To understand execfn, we must look at how it compares to argv[0], which is the traditional way a program finds its own name.
argv[0]is untrustworthy: When a process executes another file (using a system call likeexecve), the caller can pass any arbitrary string asargv[0]. It does not have to match the actual executable name or its path.execfnis reliable: The kernel populatesAT_EXECFNwith the actual pathname string passed to theexecvesystem call. Even if the caller lies inargv[0],AT_EXECFNstill points to the executable file that was loaded.
How to Access execfn in C/C++
In modern glibc-based systems (Linux), the standard way to retrieve this value is using the getauxval() function declared in <sys/auxv.h> with the key AT_EXECFN.
Here is a simple example:
#include <stdio.h>
#include <sys/auxv.h>
int main(int argc, char *argv[]) {
// Retrieve the execution path from the auxiliary vector
char *execfn = (char *)getauxval(AT_EXECFN);
printf("argv[0]: %s\n", argv[0]);
if (execfn) {
printf("AT_EXECFN: %s\n", execfn);
} else {
printf("AT_EXECFN is not available on this system.\n");
}
return 0;
}
If you compile this to a binary named test_execfn and run it by forging argv[0] (e.g., using a wrapper or exec-family calls), you will see that argv[0] changes but AT_EXECFN remains the path to test_execfn.
Example: Script Execution (Shebang)
A classic example of the divergence between argv[0] and AT_EXECFN happens when executing a script with a shebang (#!) line.
Suppose you have a custom interpreter compiled from the code above, located at /usr/local/bin/my_interpreter.
You then write a script named myscript.sh with the following content:
#!/usr/local/bin/my_interpreter
# Some scripting commands...
If you execute this script from your terminal:
./myscript.sh arg1 arg2
The Linux kernel detects the shebang, starts the interpreter /usr/local/bin/my_interpreter, and automatically rewrites the arguments. In the interpreter process, the arguments and auxiliary vector will look like this:
argv[0]:/usr/local/bin/my_interpreter(the path to the interpreter binary)argv[1]:./myscript.sh(the script file passed to the interpreter)argv[2]:arg1argv[3]:arg2AT_EXECFN:./myscript.sh(the original script pathname that was invoked)
Here, argv[0] is rewritten to point to the interpreter, but the kernel preserves the original command path (./myscript.sh) in AT_EXECFN. This allows the interpreter to know exactly which script file it is executing even if argv has been modified.
How the Kernel Passes execfn
When a new process is executed via a variant of execve, the kernel prepares the stack of the new process. It places the following information at the top of the stack (from lowest address to highest):
argc(argument count)argv[](list of argument strings)envp[](list of environment variable strings)- Auxiliary Vector (
auxv) (key-value pairs of raw binary metadata about the process environment) - Data arguments and environmental strings (the actual characters of the arguments, environments, and filename strings like
execfn)
The entries in the auxiliary vector are key-value pairs (using structures like Elf32_auxv_t or Elf64_auxv_t). One of these keys is AT_EXECFN. The value associated with it is a memory address pointing to the actual execution path string stored further up the stack.
Reading execfn from /proc
You can inspect the auxiliary vector of a running process (including your own) by reading the virtual file /proc/[pid]/auxv.
Since it is binary data, you can parse it or use utility commands. For example, to read the auxiliary vector of the current shell:
# Dump the auxiliary vector in a readable format
LD_SHOW_AUXV=1 /bin/true
In the output, you will see a line like:
AT_EXECFN: /bin/true
You can also read /proc/self/auxv directly. The helper tool /usr/bin/hexdump or custom scripts can extract the AT_EXECFN string from the tail of the stack. On a modern 64-bit system, each field is 8 bytes long (16 bytes per entry):
# Show raw 64-bit entries
hexdump -v -e '/16x "0x%016lx 0x%016lx\n"' /proc/self/auxv
# Look for AT_EXECFN (e.g., key 23 = 0x17)
hexdump -v -e '/16x "0x%016lx 0x%016lx\n"' /proc/self/auxv | grep 0000000000000017