31

On Linux, an application can easily get its absolute path by querying /proc/self/exe. On FreeBSD, it's more involved, since you have to build up a sysctl call:

int mib[4]; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PATHNAME; mib[3] = -1; char buf[1024]; size_t cb = sizeof(buf); sysctl(mib, 4, buf, &cb, NULL, 0); 

but it's still completely doable. Yet I cannot find a way to determine this on OS X for a command-line application. If you're running from within an app bundle, you can determine it by running [[NSBundle mainBundle] bundlePath], but because command-line applications are not in bundles, this doesn't help.

(Note: consulting argv[0] is not a reasonable answer, since, if launched from a symlink, argv[0] will be that symlink--not the ultimate path to the executable called. argv[0] can also lie if a dumb application uses an exec() call and forget to initialize argv properly, which I have seen in the wild.)

2
  • 1
    Reading argv[0] is the solution and nothing in this thread yet convinced me. Commented Apr 29, 2009 at 7:49
  • 13
    @bortzmeyer:consider execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0); - the value of 'argv[0]` is "/bin/ls" but that is nothing to do with the name of the executable. Commented May 24, 2011 at 6:23

8 Answers 8

64

The function _NSGetExecutablePath will return a full path to the executable (GUI or not). The path may contain symbolic links, "..", etc. but the realpath function can be used to clean those up if needed. See man 3 dyld for more information.

char path[1024]; uint32_t size = sizeof(path); if (_NSGetExecutablePath(path, &size) == 0) printf("executable path is %s\n", path); else printf("buffer too small; need size %u\n", size); 

The secret to this function is that the Darwin kernel puts the executable path on the process stack immediately after the envp array when it creates the process. The dynamic link editor dyld grabs this on initialization and keeps a pointer to it. This function uses that pointer.

Sign up to request clarification or add additional context in comments.

6 Comments

Isn't the last line a bit broken?
It returns 0 on success, or -1 if the buffer is not large enough in which case size is filled in with the required size. In that case you could allocate a buffer with malloc().
Is the returned path null-terminated?
@mic_e: yes, if 0 is returned then the path will be null terminated. If the buffer is too small then -1 is returned and the path buffer is not modified.
#include <sys/syslimits.h> char path[PATH_MAX+1]; // would be better
|
36

I believe there is much more elegant solution, which actually works for any PID, and also returns the absolute path directly:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <libproc.h> int main (int argc, char* argv[]) { int ret; pid_t pid; char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; pid = getpid(); ret = proc_pidpath (pid, pathbuf, sizeof(pathbuf)); if ( ret <= 0 ) { fprintf(stderr, "PID %d: proc_pidpath ();\n", pid); fprintf(stderr, " %s\n", strerror(errno)); } else { printf("proc %d: %s\n", pid, pathbuf); } return 0; } 

5 Comments

Thanks! One thing though, using OS X 10.8.5, this didn't work for me without: #include <unistd.h>
The best solution I could ever get. Very clean work!
This implies that _NSGetExecutablePath doesn't work for all PIDs - can you elaborate on how so? I cannot find a reference elsewhere that states this.
@hyperum does _NSGetExecutablePath work for PID too? If so, can you provide an example to further improve the accepted answer?
@hpm Perhaps Alen was just referring to the fact that proc_pidpath can be called to retrieve information about an arbitrary process from any process (since it has a pid parameter), while _NSGetExecutablePath only gives you information about the current process.
4

Looks like the answer is that you can't do it:

I'm trying to achieve something like lsof's functionality and gather a whole bunch of statistics and info about running processes. If lsof weren't so slow, I'd be happy sticking with it.

If you reimplement lsof, you will find that it's slow because it's doing a lot of work.

I guess that's not really because lsof is user-mode, it's more that it has to scan through a task's address space looking for things backed by an external pager. Is there any quicker way of doing this when I'm in the kernel?

No. lsof is not stupid; it's doing what it has to do. If you just want a subset of its functionality, you might want to consider starting with the lsof source (which is available) and trimming it down to meet your requirements.

Out of curiosity, is p_textvp used at all? It looks like it's set to the parent's p_textvp in kern_fork (and then getting released??) but it's not getting touched in any of kern_exec's routines.

p_textvp is not used. In Darwin, the proc is not the root of the address space; the task is. There is no concept of "the vnode" for a task's address space, as it is not necessarily initially populated by mapping one.

If exec were to populate p_textvp, it would pander to the assumption that all processes are backed by a vnode. Then programmers would assume that it was possible to get a path to the vnode, and from there it is a short jump to the assumption that the current path to the vnode is the path from which it was launched, and that text processing on the string might lead to the application bundle name... all of which would be impossible to guarantee without substantial penalty.

Mike Smith, Darwin Drivers mailing list

2 Comments

I really, really hate accepting answers that say, "You can't," but that quote certainly seems to put the nail in my question's coffin quite painfully.
Yeah, I hated giving the answer, too. I spent a while on a wild goose chase, trying to see if I could figure out how to get the information out of p_textvp, before I discovered this.
3

This is late, but [[NSBundle mainBundle] executablePath] works just fine for non-bundled, command-line programs.

Comments

2

There is no guaranteed way I think. If argv[0] is a symlink then you could use readlink(). If command is executed through the $PATH then one could try some of: search(getenv("PATH")), getenv("_"), dladdr()

3 Comments

That will cover many cases, but still fails in the case you were launched by an application that neglected to initialize argv[0] properly--which, from personal experience, applies to a disturbing number of them.
Can you give an example of such an application? It is not the application which initializes argv, it's the libc and the application would need to do something very special to scramble argv[0].
I can't give an example of an application off the top of my head that gets it wrong, but all they have to do to screw up argv[0] is forget to set it properly when invoking an application via one of the exec* calls. libc would only get involved if invoking the application via system().
1

In case someone is looking for a cross-platform solution across all the unix or unix-like operating system mentioned in the question: macOS, Linux, FreeBSD and maybe a few more. dladdr(3) is able to give this information by asking it where main is.

#define _GNU_SOURCE // ask for dladdr under Linux/glibc #include <dlfcn.h> // dladdr, .. #include <stdlib.h> #include <stdio.h> int main() { Dl_info info; if (dladdr(&main, &info) == 0) { fprintf(stderr, "error: dladdr: %s\n", dlerror()); exit(1); } printf("%s\n", info.dli_fname); } 

If the code in question is not able to take an address to main directly, then dlsym(3) can ask for its address.

#define _GNU_SOURCE // ask for dladdr under Linux/glibc #include <dlfcn.h> // dladdr, .. #include <stdlib.h> #include <stdio.h> int main() { void* pmain = dlsym(RTLD_DEFAULT, "main"); if (!pmain) { fprintf(stderr, "error: dlsym: %s\n", dlerror()); exit(1); } Dl_info info; if (dladdr(pmain, &info) == 0) { fprintf(stderr, "error: dladdr: %s\n", dlerror()); exit(1); } printf("%s\n", info.dli_fname); } 

1 Comment

Note: the path-part in the returned file-name can be absolute, relative or no path, depending on how we started the executable, e.g. dli_fname=./dladdr_test
0

Why not simply realpath(argv[0], actualpath);? True, realpath has some limits (documented in the manual page) but it handles symbolic links fine. Tested on FreeBSD and Linux

 % ls -l foobar lrwxr-xr-x 1 bortzmeyer bortzmeyer 22 Apr 29 07:39 foobar -> /tmp/get-real-name-exe % ./foobar My real path: /tmp/get-real-name-exe 
#include <limits.h> #include <stdlib.h> #include <stdio.h> #include <libgen.h> #include <string.h> #include <sys/stat.h> int main(argc, argv) int argc; char **argv; { char actualpath[PATH_MAX + 1]; if (argc > 1) { fprintf(stderr, "Usage: %s\n", argv[0]); exit(1); } realpath(argv[0], actualpath); fprintf(stdout, "My real path: %s\n", actualpath); exit(0); } 

If the program is launched via PATH, see pixelbeat's solution.

3 Comments

This fails when a dumb program invokes you via an exec* call and improperly initializes the argv structure so that argv[0] is either just the executable name (i.e., not the full path) or flat-out wrong (absent, null string, or what have you).
If argv[0] is not the full path, no problem, realpath() will handle it. If it is empty or NULL, well, that's the fault of the caller, not of my program :-)
@BenjaminPollack: There's nothing remotely improper about the former if the executable is in $PATH!
0

http://developer.apple.com/documentation/Carbon/Reference/Process_Manager/Reference/reference.html#//apple_ref/c/func/GetProcessBundleLocation

GetProcessBundleLocation seems to work.

2 Comments

It works, provided your application is a GUI application launched via the Finder and links against Carbon. In that case, though, [[NSBundle mainBundle] bundlePath] would work, too--and avoid creating the FSRef and locating the process serial number.
@BenjaminPollack [[NSBundle mainBundle] bundlePath] also works for non-UI applications that are just a single binary as long as they link against Foundation. If your app just links against CoreFoundation, you can use CFBundle. All bundle methods also work for a plain binary that is no bundle at all, though they may not always return useful info but they work for getting the executable path of your binary.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.