procdump

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

commit d9d200dbe38f62844e5ddb885e1778a888669c18
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri, 10 May 2024 05:10:00 +0200

Add initial version

Diffstat:
A.gitignore | 1+
AMakefile | 24++++++++++++++++++++++++
Aprocdump.c | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 212 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +procdump diff --git a/Makefile b/Makefile @@ -0,0 +1,24 @@ +PREFIX ?= /usr/local +BINDIR ?= /bin + +ifeq ($(DEBUG),1) +CFLAGS = -Og -g +else +CFLAGS = -O3 +endif + +all: procdump + +procdump: procdump.c + gcc -o $@ $< $(CFLAGS) + +clean: + rm -f procdump + +install: + install -m755 procdump -t "$(DESTDIR)$(PREFIX)$(BINDIR)" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)$(BINDIR)/procdump" + +.PHONY: all install uninstall diff --git a/procdump.c b/procdump.c @@ -0,0 +1,187 @@ +#include <asm-generic/errno-base.h> +#include <linux/limits.h> +#include <sys/stat.h> +#include <sys/signal.h> +#include <libgen.h> +#include <unistd.h> +#include <pwd.h> + +#include <errno.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdbool.h> + +#define _STR(x) #x +#define STR(x) _STR(x) + +static pid_t pid = -1; + +void +__attribute__((format(printf, 1, 2))) +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "procdump: "); + vfprintf(stderr, fmt, ap); + if (*fmt && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + va_end(ap); + exit(1); +} + +void +dump(const char *dir, pid_t pid) +{ + char *path = malloc(PATH_MAX+1); + if (!path) die("malloc:"); + + char *lib = malloc(PATH_MAX+1); + if (!lib) die("malloc:"); + + if (setreuid(geteuid(), getuid()) || setregid(getegid(), getgid())) + die("setreuid / setregid:"); + + snprintf(path, PATH_MAX+1, "/proc/%i/maps", pid); + FILE *maps = fopen(path, "r"); + if (!maps) die("fopen %s:", path); + + snprintf(path, PATH_MAX+1, "/proc/%i/mem", pid); + FILE *mem = fopen(path, "r"); + if (!mem) die("fopen %s:", path); + + if (setreuid(geteuid(), getuid()) || setregid(getegid(), getgid())) + die("setreuid / setregid:"); + + snprintf(path, PATH_MAX+1, "%s/mem", dir); + if (mkdir(path, 0755) && errno != EEXIST) + die("mkdir %s", path); + + snprintf(path, PATH_MAX+1, "%s/maps", dir); + if (mkdir(path, 0755) && errno != EEXIST) + die("mkdir %s", path); + + ssize_t rc = 0; + size_t linelen; + char *line = NULL; + while ((rc = getline(&line, &linelen, maps)) >= 0) { + if (line[strlen(line)-1] == '\n') + line[strlen(line)-1] = '\0'; + + uint64_t start, end; + char r, w, x, p; + uint64_t offset; + int n = sscanf(line, "%"PRIx64"-%"PRIx64" %c%c%c%c %"PRIx64 + " %*02u:%*02u %*u %"STR(PATH_MAX)"s", + &start, &end, &r, &w, &x, &p, &offset, lib); + if (n != 7 && n != 8) die("Bad line: %s", line); + if (n == 7) *lib = '\0'; + + if (end < start) die("Bad size: %s", line); + uint64_t size = end - start; + + if (fseek(mem, start, SEEK_SET)) { + fprintf(stderr, "procdump: Skipping '%s' at 0x%lx\n", lib, start); + continue; + } + + snprintf(path, PATH_MAX+1, "%s/mem/%016lx", dir, start); + FILE *out = fopen(path, "w"); + if (!out) die("fopen %s:", path); + char *buf = malloc(size); + if (!buf) die("malloc:"); + if (fread(buf, size, 1, mem) != 1) + fprintf(stderr, "procdump: Skipping '%s' at 0x%lx\n", lib, start); + else if (fwrite(buf, size, 1, out) != 1) + die("fwrite: %s", errno ? strerror(errno) : "trunc"); + free(buf); + fclose(out); + + snprintf(path, PATH_MAX+1, "../../%s/mem/%016lx", dir, start); + char *from = strdup(path); + if (!from) die("strdup:"); + snprintf(path, PATH_MAX+1, "%s/maps/%s-%016lx-%c%c%c%c", + dir, basename(lib), start, r, w, x, p); + unlink(path); + if (symlink(from, path)) die("symlink:"); + free(from); + } + free(line); + + fclose(maps); + fclose(mem); + free(path); + free(lib); +} + +void +unstop(void) +{ + int rc = kill(pid, SIGCONT); + if (rc) die("Sending SIGCONT:"); +} + +int +main(int argc, const char **argv) +{ + const char **arg; + char *end; + + if (!argc) return 1; + + const char *dir = NULL; + const char *unpriv = NULL; + bool stop = false; + for (arg = argv + 1; *arg; arg++) { + if (!strcmp(*arg, "-s")) { + stop = true; + } else if (!strcmp(*arg, "-u")) { + unpriv = *++arg; + } else if (pid < 0) { + pid = strtoul(*arg, &end, 10); + if (!end || *end || pid > INT32_MAX) + die("Bad pid: %s", *arg); + } else if (!dir) { + dir = *arg; + } else { + die("Invalid arg: %s", *arg); + } + } + if (pid < 0) die("Usage: procdump [-s] PID [DIR]"); + + if (stop) { + int rc = kill(pid, SIGSTOP); + if (rc) die("Sending SIGSTOP:"); + atexit(unstop); + } + + if (unpriv) { + struct passwd *pwd = calloc(1, sizeof(struct passwd)); + if (!pwd) die("calloc:"); + size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + char *buf = malloc(buflen); + if (!buf) die("malloc:"); + getpwnam_r(unpriv, pwd, buf, buflen, &pwd); + if (!pwd) die("getpwnam_r %s:", unpriv); + if (setegid(pwd->pw_gid)) die("setegid:"); + if (seteuid(pwd->pw_uid)) die("seteuid:"); + } + + if (!dir) { + dir = "."; + } else { + if (mkdir(dir, 0755) && errno != EEXIST) + die("mkdir %s", dir); + } + + dump(dir, pid); +}