procdump

Process memory dumper
git clone https://git.sinitax.com/sinitax/procdump
Log | Files | Refs | sfeed.txt

procdump.c (4276B)


      1#include <asm-generic/errno-base.h>
      2#include <linux/limits.h>
      3#include <sys/stat.h>
      4#include <sys/signal.h>
      5#include <libgen.h>
      6#include <unistd.h>
      7#include <pwd.h>
      8
      9#include <errno.h>
     10#include <stdio.h>
     11#include <stdarg.h>
     12#include <stdlib.h>
     13#include <string.h>
     14#include <inttypes.h>
     15#include <stdint.h>
     16#include <stdbool.h>
     17
     18#define _STR(x) #x
     19#define STR(x) _STR(x)
     20
     21static pid_t pid = -1;
     22
     23void
     24__attribute__((format(printf, 1, 2)))
     25die(const char *fmt, ...)
     26{
     27	va_list ap;
     28
     29	va_start(ap, fmt);
     30	fprintf(stderr, "procdump: ");
     31	vfprintf(stderr, fmt, ap);
     32	if (*fmt && fmt[strlen(fmt)-1] == ':') {
     33		fputc(' ', stderr);
     34		perror(NULL);
     35	} else {
     36		fputc('\n', stderr);
     37	}
     38	va_end(ap);
     39	exit(1);
     40}
     41
     42void
     43dump(const char *dir, pid_t pid)
     44{
     45	char *path = malloc(PATH_MAX+1);
     46	if (!path) die("malloc:");
     47
     48	char *lib = malloc(PATH_MAX+1);
     49	if (!lib) die("malloc:");
     50
     51	if (setreuid(geteuid(), getuid()) || setregid(getegid(), getgid()))
     52		die("setreuid / setregid:");
     53
     54	snprintf(path, PATH_MAX+1, "/proc/%i/maps", pid);
     55	FILE *maps = fopen(path, "r");
     56	if (!maps) die("fopen %s:", path);
     57
     58	snprintf(path, PATH_MAX+1, "/proc/%i/mem", pid);
     59	FILE *mem = fopen(path, "r");
     60	if (!mem) die("fopen %s:", path);
     61
     62	if (setreuid(geteuid(), getuid()) || setregid(getegid(), getgid()))
     63		die("setreuid / setregid:");
     64
     65	snprintf(path, PATH_MAX+1, "%s/mem", dir);
     66	if (mkdir(path, 0755) && errno != EEXIST)
     67		die("mkdir %s", path);
     68
     69	snprintf(path, PATH_MAX+1, "%s/maps", dir);
     70	if (mkdir(path, 0755) && errno != EEXIST)
     71		die("mkdir %s", path);
     72
     73	ssize_t rc = 0;
     74	size_t linelen;
     75	char *line = NULL;
     76	while ((rc = getline(&line, &linelen, maps)) >= 0) {
     77		if (line[strlen(line)-1] == '\n')
     78			line[strlen(line)-1] = '\0';
     79
     80		uint64_t start, end;
     81		char r, w, x, p;
     82		uint64_t offset;
     83		int n = sscanf(line, "%"PRIx64"-%"PRIx64" %c%c%c%c %"PRIx64
     84			" %*02u:%*02u %*u %"STR(PATH_MAX)"s",
     85			&start, &end, &r, &w, &x, &p, &offset, lib);
     86		if (n != 7 && n != 8) die("Bad line: %s", line);
     87		if (n == 7) *lib = '\0';
     88
     89		if (end < start) die("Bad size: %s", line);
     90		uint64_t size = end - start;
     91
     92		if (fseek(mem, start, SEEK_SET)) {
     93			fprintf(stderr, "procdump: Skipping '%s' at 0x%lx\n", lib, start);
     94			continue;
     95		}
     96
     97		snprintf(path, PATH_MAX+1, "%s/mem/%016lx", dir, start);
     98		FILE *out = fopen(path, "w");
     99		if (!out) die("fopen %s:", path);
    100		char *buf = malloc(size);
    101		if (!buf) die("malloc:");
    102		if (fread(buf, size, 1, mem) != 1)
    103			fprintf(stderr, "procdump: Skipping '%s' at 0x%lx\n", lib, start);
    104		else if (fwrite(buf, size, 1, out) != 1)
    105			die("fwrite: %s", errno ? strerror(errno) : "trunc");
    106		free(buf);
    107		fclose(out);
    108
    109		snprintf(path, PATH_MAX+1, "../../%s/mem/%016lx", dir, start);
    110		char *from = strdup(path);
    111		if (!from) die("strdup:");
    112		snprintf(path, PATH_MAX+1, "%s/maps/%s-%016lx-%c%c%c%c",
    113			dir, basename(lib), start, r, w, x, p);
    114		unlink(path);
    115		if (symlink(from, path)) die("symlink:");
    116		free(from);
    117	}
    118	free(line);
    119
    120	fclose(maps);
    121	fclose(mem);
    122	free(path);
    123	free(lib);
    124}
    125
    126void
    127unstop(void)
    128{
    129	int rc = kill(pid, SIGCONT);
    130	if (rc) die("Sending SIGCONT:");
    131}
    132
    133int
    134main(int argc, const char **argv)
    135{
    136	const char **arg;
    137	char *end;
    138
    139	if (!argc) return 1;
    140
    141	const char *dir = NULL; 
    142	const char *unpriv = NULL;
    143	bool stop = false;
    144	for (arg = argv + 1; *arg; arg++) {
    145		if (!strcmp(*arg, "-s")) {
    146			stop = true;
    147		} else if (!strcmp(*arg, "-u")) {
    148			unpriv = *++arg;
    149		} else if (pid < 0) {
    150			pid = strtoul(*arg, &end, 10);
    151			if (!end || *end || pid > INT32_MAX)
    152				die("Bad pid: %s", *arg);
    153		} else if (!dir) {
    154			dir = *arg;
    155		} else {
    156			die("Invalid arg: %s", *arg);
    157		}
    158	}
    159	if (pid < 0) die("Usage: procdump [-s] PID [DIR]");
    160
    161	if (stop) {
    162		int rc = kill(pid, SIGSTOP);
    163		if (rc) die("Sending SIGSTOP:");
    164		atexit(unstop);
    165	}
    166
    167	if (unpriv) {
    168		struct passwd *pwd = calloc(1, sizeof(struct passwd));
    169		if (!pwd) die("calloc:");
    170		size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
    171		char *buf = malloc(buflen);
    172		if (!buf) die("malloc:");
    173		getpwnam_r(unpriv, pwd, buf, buflen, &pwd);
    174		if (!pwd) die("getpwnam_r %s:", unpriv);
    175		if (setegid(pwd->pw_gid)) die("setegid:");
    176		if (seteuid(pwd->pw_uid)) die("seteuid:");
    177	}
    178
    179	if (!dir) {
    180		dir = ".";
    181	} else {
    182		if (mkdir(dir, 0755) && errno != EEXIST)
    183			die("mkdir %s", dir);
    184	}
    185
    186	dump(dir, pid);
    187}