初学rust,没有途径修改argv[0]
Tag argv0, rust, 进程标题, on by view 48

我们知道在C语言程序中,可以通过修改argv[0]的值,来实现改变一个进程在ps命令中显示的标题,先给一个C语言的demo如下:

#include <stdio.h>
#include <string.h>
  
extern char **environ;
  
int main(int argc , char *argv[]) {
    int i;
  
    printf("argc:%d\n" , argc);
    for (i = 0; i < argc; i++) {
        printf("argv[%d]:%s\t0x%x\n" , i , argv[i], argv[i]);
    }
  
    for (i = 0; i < argc && environ[i]; i++) {
        printf("evriron[%d]:%s\t0x%x\n" , i , environ[i], environ[i]);
    }

    strcpy(argv[0], "nginx: process is shuting down");

    sleep(1000);
    return 0;
}

进程原本的名称是demo,但是我们通过strcpy修改了argv[0]之后,ps命令显示进程的名称变为我们指定的nginx: process is shuting down,如下

➜  build git:(master) ✗ ps -ef | grep nginx
root     1263942 1252322  0 15:29 pts/2    00:00:00 nginx: process is shuting down
root     1263973 1253542  0 15:29 pts/3    00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox nginx

这也是nginx程序实现在修改进程标题为nginx: master, nginx: worker 以及 nginx: process is shuting down 的原理,从而实现在ps的标题中标识不同类别的进程。

我们在rust语言中如何达到相同的效果呢,查阅资料了解到,通过下面方法可以修改进程名

use nix::libc::{PR_SET_NAME, prctl};
use std::ffi::CString;

fn main() {
    let new_name = CString::new("NewProcessName").expect("CString::new failed");
    unsafe {
        prctl(PR_SET_NAME, new_name.as_ptr() as usize, 0, 0, 0);
    }
}

但是实际使用后,发现,这种方法修改的进程名并不是ps命令显示的进程标题,ps命令显示的进程标题还是不变,而是修改了pstree命令显示的进程树种的进程名称,所以,这种方法并不能达到我们想要的效果。

我们尝试修改argv[0],在rust中是通过env::args()来获取程序的传参,也即argv,追踪到env::args()调用的是env::args_os(),于是我们有这么一段代码尝试修改argv[0]:

use std::env;
use std::ffi::{CStr, CString, OsString};
use std::os::unix::ffi::OsStringExt;

fn set_process_title(title: &str) {
    let args: Vec<OsString> = env::args_os().collect();
    let mut argv: Vec<*mut i8> = args.iter()
        .map(|arg| {
            let arg_cstring = CString::new(arg.as_bytes()).expect("Failed to create CString");
            arg_cstring.into_raw()
        })
        .collect();
    argv.push(std::ptr::null_mut());

    let title_cstring = CString::new(title).expect("Failed to create CString");

    unsafe {
        strcpy(argv[0] as *mut c_char, title_cstring.as_ptr());
    }
}

fn main() {
    set_process_title("MyWorker");

    // 继续执行其他操作...
}

但是很遗憾,并没有修改argv[0]的效果,继续追踪args_os()发现,其实在多个地方存在clone()操作,我们获取到的argv早就不是原始的argvargs_os()的实现如下

#[stable(feature = "env", since = "1.0.0")]
pub fn args_os() -> ArgsOs {
    ArgsOs { inner: sys::args::args() }
}

sys::args::args()的实现如下

/// Returns the command line arguments
pub fn args() -> Args {
    imp::args()
}

imp::args()的实现如下

pub fn args() -> Args {
    Args { iter: clone().into_iter() }
}

这里clone()实现如下

fn clone() -> Vec<OsString> {
    unsafe {
        // Load ARGC and ARGV, which hold the unmodified system-provided
        // argc/argv, so we can read the pointed-to memory without atomics
        // or synchronization.
        //
        // If either ARGC or ARGV is still zero or null, then either there
        // really are no arguments, or someone is asking for `args()`
        // before initialization has completed, and we return an empty
        // list.
        let argv = ARGV.load(Ordering::Relaxed);
        let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
        let mut args = Vec::with_capacity(argc as usize);
        for i in 0..argc {
            let ptr = *argv.offset(i) as *const libc::c_char;

            // Some C commandline parsers (e.g. GLib and Qt) are replacing already
            // handled arguments in `argv` with `NULL` and move them to the end. That
            // means that `argc` might be bigger than the actual number of non-`NULL`
            // pointers in `argv` at this point.
            //
            // To handle this we simply stop iterating at the first `NULL` argument.
            //
            // `argv` is also guaranteed to be `NULL`-terminated so any non-`NULL` arguments
            // after the first `NULL` can safely be ignored.
            if ptr.is_null() {
                break;
            }

            let cstr = CStr::from_ptr(ptr);
            args.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
        }

        args
    }
}

可以看到argv是从ARGV.load(Ordering::Relaxed);中加载出来的。可以看到argv经历了多次拷贝,最终才到args,然后通过args_os()再呈现在我们面前,实际早就不再是最初的那个argv。