How to set the process title on Linux


Last Updated: Monday, February 25 2002

Warning: hardcore C memory management stuff here. If you don't understand this stuff at first, don't worry. I don't think many people can in one read. Digest it one bite at a time and use your favorite search engine to look up any words or concepts you don't understand.

So one day I had to figure out which Bayonne process was the ccscript interpreter so I could attach a debugger to it so I could figure out why it was on crack...

Now, most BSD people would say, "just use setproctitle(3) to set the subprocess names and then use ps(1) to look at the names to figure out which one is the ccscript interpreter." The NetBSD people would say "use setprogname(2)", but the Linux people would say "uh... I'm not sure how to do that." Searching the net doesn't turn up much, either. I hope that this page shows up in the search engine indexes so that you don't have to go through what I did to figure this out.

I knew that proftpd and sendmail can do this trick, so I got the source for proftpd and started poking through it. Turns out that this is how you set the process title on Linux:

#include <stdio.h>

int main(int argc, char *argv[], char *envp[]) {
  snprintf(argv[0], 40, "%s", "this is my new process title");
  for(;;) {}
  return 0;
}
Seems easy, huh? Here's what ps says when you run this program:
17549 pts/5    R      0:00 this is an example of wookie asskicking
But there's a catch. Here's how the program's memory is laid out:
Memory diagram 1
And then when you write data into the buffer pointed to by argv[0], this happens:
Memory diagram 2
You can run this program to prove it:
#include <stdio.h>

int main(int argc, char *argv[], char *envp[]) {
  int i;

  printf("==> The environment looks like this before we change the process title:\n\n");
  for(i = 0; envp[i] != NULL; ++i) {
    printf("envp[%i]: %s\n", i, envp[i]);
  }

  snprintf(argv[0], 40, "%s", "this is an example of wookie asskicking");
  printf("\n==> And here's what it looks like afterwards:\n\n");
  for(i = 0; envp[i] != NULL; ++i) {
    printf("envp[%i]: %s\n", i, envp[i]);
  }
  for(;;) {}
  return 0;
}
That outputs this:
==> The environment looks like this before we change the process title:

envp[0]: PWD=/home/thalakan
envp[1]: SESSION_MANAGER=local/thom:/tmp/.ICE-unix/24955
envp[2]: USER=thalakan
.
.
.

==> And here's what it looks like afterwards:

envp[0]: xample of wookie asskicking
envp[1]: skicking
envp[2]: USER=thalakan
.
.
.

If you don't really care about your environment (maybe because you're a daemon), this is ok. It's ugly and it'll probably tickle a bug in the environment variable access routines, but it'll work for a simple demo.

But most people want the environment around so they have access to variables like HOME and LANG and SHELL. So what you do is copy the environment to somewhere else and point the global variable environ to it. Glibc does not have a copy of the envp (the third argument to main) pointer passed to your program; it references all environment variables through this global environ variable instead. Glibc copies this pointer and gives you the copy when it jumps to the main() routine in your program. If you do the copy correctly, your program's memory should look like the box that says "Copy of environment" in the above diagram.

Once you've copied the environment somewhere, you can overwrite it with the string you want ps(1) to print out when it looks at your process. Remember: you only have a limited amount of space equal to the size of the environment (see the DEBUG statement in the proftpd code below to how to get the size) to write the new process string into, so be careful. Your program will segfault if you try to read or write past this area:

(gdb) x/200c envp[19]
0xbfffff94:     80 'P'  65 'A'  84 'T'  72 'H'  61 '='  47 '/'  117 'u' 115 's'
0xbfffff9c:     114 'r' 47 '/'  108 'l' 111 'o' 99 'c'  97 'a'  108 'l' 47 '/'
0xbfffffa4:     98 'b'  105 'i' 110 'n' 58 ':'  47 '/'  117 'u' 115 's' 114 'r'
0xbfffffac:     47 '/'  98 'b'  105 'i' 110 'n' 58 ':'  47 '/'  98 'b'  105 'i'
0xbfffffb4:     110 'n' 58 ':'  47 '/'  117 'u' 115 's' 114 'r' 47 '/'  88 'X'
0xbfffffbc:     49 '1'  49 '1'  82 'R'  54 '6'  47 '/'  98 'b'  105 'i' 110 'n'
0xbfffffc4:     58 ':'  47 '/'  117 'u' 115 's' 114 'r' 47 '/'  103 'g' 97 'a'
0xbfffffcc:     109 'm' 101 'e' 115 's' 58 ':'  47 '/'  104 'h' 111 'o' 109 'm'
0xbfffffd4:     101 'e' 47 '/'  116 't' 104 'h' 97 'a'  108 'l' 97 'a'  107 'k'
0xbfffffdc:     97 'a'  110 'n' 47 '/'  98 'b'  105 'i' 110 'n' 0 '\000'       47 '/'
0xbfffffe4:     104 'h' 111 'o' 109 'm' 101 'e' 47 '/'  116 't' 104 'h' 97 'a'
0xbfffffec:     108 'l' 97 'a'  107 'k' 97 'a'  110 'n' 47 '/'  115 's' 101 'e'
0xbffffff4:     116 't' 116 't' 105 'i' 116 't' 108 'l' 101 'e' 50 '2'
0 '\000'0xbffffffc:     0 '\000'        0 '\000'        0 '\000'
0 '\000'       
Cannot access memory at address 0xc0000000

The situation is the same for argv[0]. Glibc does not use the same pointer that you get passed as argv. It has a copy of a pointer to it somewhere (I don't know if you can reference it as an extern variable), so you can't just point it somewhere else to change the process title by doing something like argv[0] = "Some new string";

Here's the code from proftpd, changed to make it a little more cut-and-paste friendly, and without the portability macros. Which means this will ONLY run on glibc based Linux systems. You can use it your code if you want: just call init_set_proc_title(); and then set_proc_title("Program name: %s", status); or whatever.

I have no idea why they're assigning to __progname and __progname_full because my system (Debian 3.0) reports the changed process title just fine when I comment it out, but there's a comment in the code saying that glibc will whine if you don't, so I'm including it.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <malloc.h>

/* Globals */
static char **Argv = ((void *)0);
extern char *__progname, *__progname_full;
static char *LastArgv = ((void *)0);

/* Prototypes */
static void set_proc_title(char *fmt,...);
static void init_set_proc_title(int argc, char *argv[], char *envp[]);

int main(int argc, char *argv[], char *envp[]) {
  init_set_proc_title(argc, argv, envp);
  set_proc_title("%s", "This is a nifty process title");
  printf("DEBUG: %s\n", Argv[0]);
  
  for(;;) {}
  return 0;
}

static void init_set_proc_title(int argc, char *argv[], char *envp[])
{
  int i, envpsize;
  extern char **environ;
  char **p;

  for(i = envpsize = 0; envp[i] != NULL; i++)
    envpsize += strlen(envp[i]) + 1;
  
  if((p = (char **) malloc((i + 1) * sizeof(char *))) != NULL ) {
    environ = p;

    for(i = 0; envp[i] != NULL; i++) {
      if((environ[i] = malloc(strlen(envp[i]) + 1)) != NULL)
	strcpy(environ[i], envp[i]);
    }
    
    environ[i] = NULL;
  }

  Argv = argv;
  
  for(i = 0; envp[i] != NULL; i++) {
    if((LastArgv + 1) == envp[i]) // Not sure if this conditional is needed
      LastArgv = envp[i] + strlen(envp[i]);
  }

// Pretty sure you don't need this either
  __progname = strdup("proftpd");
  __progname_full = strdup(argv[0]);
}

static void set_proc_title(char *fmt,...)
{
  va_list msg;
  static char statbuf[8192];
  char *p;
  int i,maxlen = (LastArgv - Argv[0]) - 2;

  printf("DEBUG: maxlen: %i\n", maxlen);

  va_start(msg,fmt);

  memset(statbuf, 0, sizeof(statbuf));
  vsnprintf(statbuf, sizeof(statbuf), fmt, msg);

  va_end(msg);

  i = strlen(statbuf);

  snprintf(Argv[0], maxlen, "%s", statbuf);
  p = &Argv[0][i];
  
  while(p < LastArgv)
    *p++ = '\0';
  Argv[1] = ((void *)0) ;
}


Home | Site Index | Email me