Skip to content

Instantly share code, notes, and snippets.

@nonowarn
Created January 8, 2011 08:54
Show Gist options
  • Save nonowarn/770696 to your computer and use it in GitHub Desktop.
Save nonowarn/770696 to your computer and use it in GitHub Desktop.
/* Copied, Pasted and summarized from ps' source code.
You can use sysctl to get other process' argv.
*/
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define pid_of(pproc) pproc->kp_proc.p_pid
void print_argv_of_pid(int);
int
main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s pid\n", argv[0]);
exit(1);
}
print_argv_of_pid(atoi(argv[1]));
return 0;
}
void print_argv_of_pid(int pid) {
int mib[3], argmax, nargs, c = 0;
size_t size;
char *procargs, *sp, *np, *cp;
int show_args = 1;
fprintf(stderr, "Getting argv of PID %d\n", pid);
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
size = sizeof(argmax);
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) {
goto ERROR_A;
}
/* Allocate space for the arguments. */
procargs = (char *)malloc(argmax);
if (procargs == NULL) {
goto ERROR_A;
}
/*
* Make a sysctl() call to get the raw argument space of the process.
* The layout is documented in start.s, which is part of the Csu
* project. In summary, it looks like:
*
* /---------------\ 0x00000000
* : :
* : :
* |---------------|
* | argc |
* |---------------|
* | arg[0] |
* |---------------|
* : :
* : :
* |---------------|
* | arg[argc - 1] |
* |---------------|
* | 0 |
* |---------------|
* | env[0] |
* |---------------|
* : :
* : :
* |---------------|
* | env[n] |
* |---------------|
* | 0 |
* |---------------| <-- Beginning of data returned by sysctl() is here.
* | argc |
* |---------------|
* | exec_path |
* |:::::::::::::::|
* | |
* | String area. |
* | |
* |---------------| <-- Top of stack.
* : :
* : :
* \---------------/ 0xffffffff
*/
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
size = (size_t)argmax;
if (sysctl(mib, 3, procargs, &size, NULL, 0) == -1) {
goto ERROR_B;
}
memcpy(&nargs, procargs, sizeof(nargs));
cp = procargs + sizeof(nargs);
/* Skip the saved exec_path. */
for (; cp < &procargs[size]; cp++) {
if (*cp == '\0') {
/* End of exec_path reached. */
break;
}
}
if (cp == &procargs[size]) {
goto ERROR_B;
}
/* Skip trailing '\0' characters. */
for (; cp < &procargs[size]; cp++) {
if (*cp != '\0') {
/* Beginning of first argument reached. */
break;
}
}
if (cp == &procargs[size]) {
goto ERROR_B;
}
/* Save where the argv[0] string starts. */
sp = cp;
/*
* Iterate through the '\0'-terminated strings and convert '\0' to ' '
* until a string is found that has a '=' character in it (or there are
* no more strings in procargs). There is no way to deterministically
* know where the command arguments end and the environment strings
* start, which is why the '=' character is searched for as a heuristic.
*/
for (np = NULL; c < nargs && cp < &procargs[size]; cp++) {
if (*cp == '\0') {
c++;
if (np != NULL) {
/* Convert previous '\0'. */
*np = ' ';
} else {
/* *argv0len = cp - sp; */
}
/* Note location of current '\0'. */
np = cp;
if (!show_args) {
/*
* Don't convert '\0' characters to ' '.
* However, we needed to know that the
* command name was terminated, which we
* now know.
*/
break;
}
}
}
/*
* sp points to the beginning of the arguments/environment string, and
* np should point to the '\0' terminator for the string.
*/
if (np == NULL || np == sp) {
/* Empty or unterminated string. */
goto ERROR_B;
}
/* Make a copy of the string. */
printf("%s\n", sp);
/* Clean up. */
free(procargs);
return;
ERROR_B:
free(procargs);
ERROR_A:
fprintf(stderr, "Sorry, failed\n");
exit(2);
}
@Motti-Shneor
Copy link

Thank you so much for this code sample.

Question: Where comment (copied from original 'ps' code?) says "* until a string is found that has a '=' character in it" - your code doesn't look for this character. When run on my machine your code prints huge pile of env junk after the args for some processes. Can you spare a link or hint on where to look for this '=', and when found - how to determine the end of last arg and beginning of the first env var?

Question: If I build your code as-is (command-line tool on my Mac), then unless run as root - it always prints the "Sorry failed" because ifsysctl(mib, 3, procargs, &size, NULL, 0) returns -1. However, when I insert your code into another source I'm developing - (where I use another sysctl for fetching all process info, then iterate the processes and try to obtain args for each) then it seems to work, even without root. What's going on here? same code, different behaviour?

Last Question: is there another way to extract the original command-line that launched the process? (this looks sometimes a little different than just the list of args)

@Lagerst
Copy link

Lagerst commented Oct 14, 2020

To Motti-Shneor:
I think you can try the open-source method in chromium here, which works well in my work and supports multiple platforms as well:
https://chromium.googlesource.com/crashpad/crashpad/+/refs/heads/master/util/posix/process_info_mac.cc
hope this helps :)

@Motti-Shneor
Copy link

Thanks a lot, I already de-C++'ed the chromium ProcessInfo::Arguments back into Obj-C for my usage, and it seems to work. I only need MacOS 10.13 and above. The code there is quite similar to your own work here - a compilation of the 'ps' code - although they base on newer version, and handle a fresh MacOS 11 Big-Sure beta-bug + few edge cases. Anyway, Some answer to my questions is: It works without root - but only for my processes, and to see args for all processes, I need root. The junk I've seen was... real arguments containing lots of junk. No bug there. Now I'm wondering about the "Process Name" that appears in the "Activity Monitor". it's not the ps -e-ocomm="CMD" or even the last path component from the executable. Where is it coming from? Haven't found and parallel in ps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment