summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorraven <citrons@mondecitronne.com>2026-04-08 22:51:39 -0500
committerraven <citrons@mondecitronne.com>2026-04-08 22:51:39 -0500
commit18a86e1038b20cb6f8922beead08dcc24ba2a4d3 (patch)
tree85cb74210d67d42763a4709d18af93aa60a8f400 /src
parent4a3429a96b5b5ea7468540349aeb4535d5738053 (diff)
rewrite and port to SDL3
Diffstat (limited to 'src')
-rw-r--r--src/curve.h25
-rw-r--r--src/hash.h15
-rw-r--r--src/main.c391
-rw-r--r--src/main.h7
-rw-r--r--src/memory.c140
-rw-r--r--src/memory.h44
-rw-r--r--src/panic.h28
-rw-r--r--src/procfs.c130
-rw-r--r--src/procfs.h27
9 files changed, 807 insertions, 0 deletions
diff --git a/src/curve.h b/src/curve.h
new file mode 100644
index 0000000..dc003c1
--- /dev/null
+++ b/src/curve.h
@@ -0,0 +1,25 @@
+#ifndef CURVE_H
+#define CURVE_H
+
+#include <limits.h>
+
+#define BIT_OF(X, I) (((X) >> (I)) & 1ULL)
+
+static uintptr_t zorder(int x, int y) {
+ uintptr_t z = 0;
+ // interleaving bits results in a point on the z-order curve
+ for (int i = 0; i < sizeof(x) * CHAR_BIT; i++)
+ z |= (BIT_OF(x, i) << i*2) | (BIT_OF(y, i) << (i*2 + 1));
+ return z;
+}
+
+static void unzorder(uintptr_t z, int *x, int *y) {
+ *x = 0; *y = 0;
+ for (int i = 0; i < sizeof(z) * CHAR_BIT / 2; i++) {
+ *x |= BIT_OF(z, i*2) << i;
+ *y |= BIT_OF(z, i*2 + 1) << i;
+ }
+}
+
+#undef BIT_OF
+#endif
diff --git a/src/hash.h b/src/hash.h
new file mode 100644
index 0000000..238b15b
--- /dev/null
+++ b/src/hash.h
@@ -0,0 +1,15 @@
+#ifndef HASH_H
+#define HASH_H
+
+#include <stdint.h>
+
+static uint64_t fnv(uint8_t *data, size_t size) {
+ uint64_t hash = 0xcbf29ce484222325;
+ for (size_t i = 0; i < size; i++) {
+ hash ^= data[i];
+ hash *= 0x100000001b3;
+ }
+ return hash;
+}
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..a806692
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,391 @@
+#include <SDL3/SDL.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include "panic.h"
+#include "curve.h"
+#include "procfs.h"
+#include "memory.h"
+
+SDL_Window *window;
+SDL_Renderer *renderer;
+
+static int window_width = 640;
+static int window_height = 480;
+
+static double scale = 1;
+static double pos_x = 1024;
+static double pos_y = 0;
+static bool brush_bit = true;
+
+static pid_t pid = -1;
+static bool is_child = false;
+static bool stopped = false;
+static page_list pages = {.fd = -1, .first = NULL};
+static procfs_map *mappings = NULL;
+static int n_mappings = 0;
+
+static double get_scale() {
+ if (scale >= 1) {
+ return SDL_round(scale);
+ }
+ return scale;
+}
+
+static void add_scale(double amount) {
+ float mx, my;
+ SDL_GetMouseState(&mx, &my);
+ double old_scale = get_scale();
+ scale += amount;
+ if (scale > 50) scale = 50;
+ else if (scale < 0.125) scale = 0.125;
+ pos_x -= mx/get_scale() - mx/old_scale;
+ pos_y -= my/get_scale() - my/old_scale;
+}
+
+static void absolute_pos(double *x, double *y) {
+ *x = *x / get_scale() + pos_x;
+ *y = *y / get_scale() + pos_y;
+}
+
+static void go_to(uintptr_t addr) {
+ int ipos_x, ipos_y;
+ to_pos(addr, &ipos_x, &ipos_y);
+ pos_x = ipos_x; pos_y = ipos_y;
+ float mx, my;
+ SDL_GetMouseState(&mx, &my);
+ pos_x -= mx / get_scale();
+ pos_y -= my / get_scale();
+}
+
+static void detach() {
+ if (pid == -1) return;
+ if (pages.fd != -1) {
+ int fd = pages.fd;
+ free_page_list(&pages);
+ close(fd);
+ }
+ if (is_child) {
+ kill(pid, SIGKILL);
+ while (true) {
+ int status;
+ pid_t w = waitpid(pid, &status, 0);
+ if (w == -1) {
+ break;
+ }
+ if (w == pid && WIFEXITED(status)) {
+ break;
+ }
+ }
+ }
+ pid = -1;
+ is_child = false;
+}
+
+static bool attach(pid_t p) {
+ detach();
+ pid = p;
+ int fd = procfs_open(pid);
+ if (fd == -1) {
+ pid = -1;
+ fprintf(stderr, "error attaching to pid %d: %s\n", p, strerror(errno));
+ return false;
+ }
+ init_page_list(&pages, fd);
+ stopped = false;
+ n_mappings = procfs_maps(pid, &mappings);
+ for (int i = 0; i < n_mappings; i++) {
+ if (strcmp("[stack]", mappings[i].name) == 0) {
+ go_to(mappings[i].max);
+ }
+ }
+ return true;
+}
+
+static bool spawn(char *const cmdline[]) {
+ detach();
+ int fildes[2];
+ if (pipe(fildes) == -1) {
+ fprintf(stderr, "error starting child: pipe: %d\n", strerror(errno));
+ return false;
+ }
+ pid_t child = fork();
+ if (child == -1) {
+ fprintf(stderr, "error starting child: %d\n", strerror(errno));
+ close(fildes[0]);
+ close(fildes[1]);
+ return false;
+ }
+ if (child == 0) {
+ close(fildes[0]);
+ if (fcntl(fildes[1], F_SETFD, FD_CLOEXEC) == -1) {
+ perror("fcntl");
+ exit(-1);
+ }
+ if (execvp(cmdline[0], cmdline) == -1) {
+ const char *err = strerror(errno);
+ write(fildes[1], err, strlen(err));
+ exit(-1);
+ }
+ }
+ close(fildes[1]);
+ char child_err[2048] = {0};
+ ssize_t n = read(fildes[0], child_err, 2047);
+ close(fildes[0]);
+ if (n == -1) {
+ fprintf(stderr,
+ "error starting child: read pipe: %d\n", strerror(errno)
+ );
+ goto err;
+ }
+ if (n != 0) {
+ fprintf(stderr, "%s: %s\n", cmdline[0], child_err);
+ }
+
+ if (attach(child)) {
+ is_child = true;
+ return true;
+ }
+err:
+ kill(child, SIGKILL);
+ while (true) {
+ int status;
+ pid_t w = waitpid(child, &status, 0);
+ if (w == -1) {
+ break;
+ }
+ if (w == child && WIFEXITED(status)) {
+ break;
+ }
+ }
+ return false;
+}
+
+static void handle_child() {
+ int status;
+ pid_t w = waitpid(pid, &status, WNOHANG);
+ if (w == -1) {
+ return;
+ }
+ if (w == pid && WIFEXITED(status)) {
+ is_child = false;
+ detach();
+ fprintf(
+ stderr, "child exited with status %d\n", WEXITSTATUS(status)
+ );
+ }
+}
+
+static bool held(SDL_Keycode k) {
+ const bool *state = SDL_GetKeyboardState(NULL);
+ return state[SDL_GetScancodeFromKey(k, NULL)];
+}
+
+static void handle_key(SDL_Keycode key) {
+ switch (key) {
+ case SDLK_SPACE:
+ if (pid == -1) break;
+ kill(pid, stopped ? SIGSTOP : SIGCONT);
+ stopped = !stopped;
+ break;
+ case SDLK_E:
+ brush_bit = !brush_bit;
+ break;
+ default:
+ }
+}
+
+static bool handle_event(SDL_Event e) {
+ switch (e.type) {
+ case SDL_EVENT_KEY_DOWN:
+ handle_key(e.key.key);
+ break;
+ case SDL_EVENT_MOUSE_MOTION:
+ SDL_MouseButtonFlags state = e.motion.state;
+ if (state & SDL_BUTTON_LMASK) {
+ pos_x -= (double) e.motion.xrel / get_scale();
+ pos_y -= (double) e.motion.yrel / get_scale();
+ } break;
+ case SDL_EVENT_MOUSE_WHEEL:
+ add_scale(e.wheel.y * get_scale() / 10);
+ break;
+ case SDL_EVENT_QUIT:
+ return false;
+ break;
+ default:
+ }
+ return true;
+}
+
+static void update() {
+ SDL_GetWindowSize(window, &window_width, &window_height);
+ if (pid != -1) {
+ n_mappings = procfs_maps(pid, &mappings);
+ if (is_child) handle_child();
+ }
+ if (held(SDLK_W)) {
+ pos_y -= 5 / get_scale();
+ }
+ if (held(SDLK_S)) {
+ pos_y += 5 / get_scale();
+ }
+ if (held(SDLK_A)) {
+ pos_x -= 5 / get_scale();
+ }
+ if (held(SDLK_D)) {
+ pos_x += 5 / get_scale();
+ }
+ float mx, my;
+ static bool mouse_held = false;
+ static double px, py;
+ SDL_MouseButtonFlags state = SDL_GetMouseState(&mx, &my);
+ if (state & SDL_BUTTON_RMASK) {
+ double x2 = mx, y2 = my;
+ absolute_pos(&x2, &y2);
+ double x1, y1;
+ if (mouse_held) {
+ x1 = px; y1 = py;
+ } else {
+ x1 = x2; y1 = y2;
+ }
+ px = x2; py = y2;
+ mouse_held = true;
+ draw_line(&pages, x1, y1, x2, y2, brush_bit);
+ } else {
+ mouse_held = false;
+ }
+}
+
+static SDL_Texture *missing_texture();
+
+static void show_pages() {
+ double s = get_scale();
+
+ int page_x = pos_x / PAGE_WIDTH;
+ int page_y = pos_y / PAGE_HEIGHT;
+
+ double offs_x = SDL_fmod(pos_x, PAGE_WIDTH) * s;
+ double offs_y = SDL_fmod(pos_y, PAGE_HEIGHT) * s;
+
+ double page_width = PAGE_WIDTH * s;
+ double page_height = PAGE_HEIGHT * s;
+
+ double view_width = window_width / page_width;
+ double view_height = window_height / page_height;
+
+ for (int draw_y = -1; draw_y <= view_height + 1; draw_y++) {
+ for (int draw_x = -1; draw_x <= view_width + 1; draw_x++) {
+ SDL_FRect src = {0, 0, PAGE_WIDTH, PAGE_HEIGHT};
+ SDL_FRect dest = {
+ draw_x * page_width - offs_x, draw_y * page_height - offs_y,
+ page_width, page_height
+ };
+ SDL_Texture *tex = NULL;
+ if (pid != -1) {
+ uintptr_t addr = zorder(page_x + draw_x, page_y + draw_y);
+ addr *= PAGE_SIZE;
+ page *p = get_page(&pages, addr);
+ tex = get_texture(p);
+ }
+ if (!tex) {
+ tex = missing_texture();
+ }
+ if (s < 0.75) {
+ SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
+ } else {
+ SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
+ }
+ SDL_RenderTexture(renderer, tex, &src, &dest);
+ }
+ }
+ free_unused_pages(&pages);
+}
+
+static SDL_Texture *missing_texture() {
+ static SDL_Texture *tex = NULL;
+ if (!tex) {
+ tex = must(SDL_CreateTexture(renderer,
+ SDL_PIXELFORMAT_RGBA32,
+ SDL_TEXTUREACCESS_STREAMING,
+ PAGE_WIDTH, PAGE_HEIGHT
+ ));
+ uint32_t *pixels; int pitch;
+ SDL_Rect rect = {0, 0, PAGE_WIDTH, PAGE_HEIGHT};
+ must(SDL_LockTexture(tex, &rect, (void **) &pixels, &pitch));
+ for (int i = 0; i < PAGE_WIDTH * PAGE_HEIGHT; i++) {
+ if (((i / 32) + i / PAGE_WIDTH / 32) % 2) {
+ pixels[i] = 0xFF151515;
+ } else {
+ pixels[i] = 0xFF222222;
+ }
+ }
+ SDL_UnlockTexture(tex);
+ }
+ return tex;
+}
+
+static void main_loop() {
+ int last_time = 0, current_time = 0;
+ while (true) {
+ update();
+ SDL_Event e;
+ while (SDL_PollEvent(&e)) {
+ if (!handle_event(e)) {
+ return;
+ }
+ }
+ SDL_GetWindowSize(window, &window_width, &window_height);
+ SDL_RenderClear(renderer);
+ show_pages();
+ SDL_RenderPresent(renderer);
+
+ last_time = current_time;
+ current_time = SDL_GetTicks();
+ int elapsed = current_time - last_time;
+ if (elapsed < 5) SDL_Delay(5 - elapsed);
+ }
+}
+
+static void usage(const char *name) {
+ fprintf(stderr, "usage: %s -p <pid>\n", name);
+ fprintf(stderr, " %s command [arg...]\n", name);
+ exit(-1);
+}
+
+int main(int argc, char *argv[]) {
+ must(SDL_Init(SDL_INIT_VIDEO));
+ must(SDL_CreateWindowAndRenderer("core",
+ window_width, window_height, SDL_WINDOW_RESIZABLE, &window, &renderer
+ ));
+ SDL_SetRenderVSync(renderer, 1);
+
+ if (argc > 1) {
+ if (argv[1][0] == '-') {
+ if (strcmp(argv[1], "-p") == 0) {
+ if (argc < 3) {
+ usage(argv[0]);
+ }
+ pid_t arg = strtol(argv[2], NULL, 10);
+ if (arg == 0 && errno == EINVAL) {
+ usage(argv[0]);
+ }
+ attach(arg);
+ } else {
+ usage(argv[0]);
+ }
+ } else {
+ spawn(argv + 1);
+ }
+ }
+ main_loop();
+ detach();
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+} \ No newline at end of file
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..83adbe8
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,7 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+extern SDL_Window *window;
+extern SDL_Renderer *renderer;
+
+#endif
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 0000000..373ca27
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,140 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <SDL3/SDL.h>
+#include <sys/mman.h>
+#include "main.h"
+#include "procfs.h"
+#include "hash.h"
+#include "curve.h"
+#include "panic.h"
+#include "memory.h"
+
+uintptr_t to_addr(int x, int y) {
+ unsigned int ux, uy;
+ memcpy(&ux, &x, sizeof(unsigned int));
+ memcpy(&uy, &y, sizeof(unsigned int));
+ uintptr_t page = zorder(ux / PAGE_WIDTH, uy / PAGE_HEIGHT);
+ int offset =
+ x % PAGE_WIDTH / CHAR_BIT + y % PAGE_HEIGHT * PAGE_WIDTH / CHAR_BIT;
+ return page * PAGE_SIZE + offset;
+}
+
+void to_pos(uintptr_t addr, int *x, int *y) {
+ int px, py;
+ unzorder(addr / PAGE_SIZE, &px, &py);
+ int offset = addr % PAGE_SIZE;
+ int ux = px * PAGE_WIDTH + offset % PAGE_WIDTH;
+ int uy = py * PAGE_HEIGHT + offset / PAGE_WIDTH;
+ memcpy(x, &ux, sizeof(int));
+ memcpy(y, &uy, sizeof(int));
+}
+
+int init_page_list(page_list *l, int fd) {
+ l->fd = fd;
+}
+
+static void free_page(page *p) {
+ if (p->tex) {
+ SDL_DestroyTexture(p->tex);
+ }
+ free(p);
+}
+
+void free_page_list(page_list *l) {
+ page *p = l->first;
+ while (p) {
+ page *next = p->next;
+ free_page(p);
+ p = next;
+ }
+ l->fd = -1;
+ l->first = NULL;
+}
+
+void free_unused_pages(page_list *l) {
+ page **prev_link = &l->first;
+ page *p = l->first;
+ while (p) {
+ page *next = p->next;
+ if (!p->in_use) {
+ *prev_link = next;
+ free_page(p);
+ } else {
+ prev_link = &p->next;
+ p->in_use = false;
+ }
+ p = next;
+ }
+}
+
+page *get_page(page_list *l, uintptr_t addr) {
+ addr = addr - addr % PAGE_SIZE;
+ for (page *p = l->first; p; p = p->next) {
+ if (p->address == addr) {
+ return p;
+ }
+ }
+ page *p = calloc(1, sizeof(page));
+ if (!p) panic("out of memory");
+ p->address = addr;
+ p->l = l;
+ p->next = l->first;
+ l->first = p;
+ return p;
+}
+
+static SDL_Palette *texture_palette() {
+ SDL_Color colors[] = {{0x00, 0x00, 0x00, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}};
+ static SDL_Palette *palette = NULL;
+ if (!palette) {
+ palette = must(SDL_CreatePalette(2));
+ must(SDL_SetPaletteColors(palette, colors, 0, 2));
+ }
+ return palette;
+}
+
+SDL_Texture *get_texture(page *p) {
+ static char data[PAGE_SIZE];
+ if (read_mem(p->l->fd, p->address, data, PAGE_SIZE) != 0) {
+ return NULL;
+ }
+ uint32_t hash = fnv(data, PAGE_SIZE);
+ if (p->hash != hash) {
+ if (p->tex) SDL_DestroyTexture(p->tex);
+ SDL_Surface *surface = must(SDL_CreateSurfaceFrom(
+ PAGE_WIDTH, PAGE_HEIGHT, SDL_PIXELFORMAT_INDEX1LSB, data,
+ PAGE_WIDTH / 8
+ ));
+ p->tex = must(SDL_CreateTextureFromSurface(renderer, surface));
+ must(SDL_SetTexturePalette(p->tex, texture_palette()));
+ SDL_DestroySurface(surface);
+ }
+ p->hash = hash;
+ p->in_use = true;
+ return p->tex;
+}
+
+static void write_bit(int fd, int x, int y, bool bit) {
+ uintptr_t addr = to_addr(x, y);
+ int bit_offs = x % CHAR_BIT;
+ uint8_t byte;
+ if (read_mem(fd, addr, &byte, 1) == -1) return;
+ byte = (byte & ~(1 << bit_offs)) | (bit << bit_offs);
+ write_mem(fd, addr, &byte, 1);
+}
+
+void draw_line(page_list *l,
+ double x1, double y1, double x2, double y2, bool bit) {
+ double dx = (x2 - x1);
+ double dy = (y2 - y1);
+ int step = abs(dx) >= abs(dy) ? abs(dx) : abs(dy);
+ dx = dx / step; dy = dy / step;
+ double x = x1; double y = y1;
+ for (int i = 0; i <= step; i++) {
+ write_bit(l->fd, x, y, bit);
+ x = x + dx;
+ y = y + dy;
+ }
+}
diff --git a/src/memory.h b/src/memory.h
new file mode 100644
index 0000000..b472d31
--- /dev/null
+++ b/src/memory.h
@@ -0,0 +1,44 @@
+#ifndef MEMORY_H
+#define MEMORY_H
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <SDL3/SDL.h>
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 4096
+#endif
+
+#ifndef PAGE_WIDTH
+#define PAGE_WIDTH 256
+#endif
+#define PAGE_HEIGHT (PAGE_SIZE * CHAR_BIT / PAGE_WIDTH)
+
+typedef struct page {
+ struct page_list *l;
+ uintptr_t address;
+ SDL_Texture *tex;
+ uint64_t hash;
+ bool in_use;
+ struct page *next;
+} page;
+
+typedef struct page_list {
+ int fd;
+ page *first;
+} page_list;
+
+uintptr_t to_addr(int x, int y);
+void to_pos(uintptr_t addr, int *x, int *y);
+
+int init_page_list(page_list *l, int fd);
+page *get_page(page_list *l, uintptr_t addr);
+void free_unused_pages(page_list *l);
+SDL_Texture *get_texture(page *p);
+void free_page_list(page_list *l);
+
+void draw_line(page_list *l,
+ double x1, double y1, double x2, double y2, bool bit);
+#endif
diff --git a/src/panic.h b/src/panic.h
new file mode 100644
index 0000000..fed75d8
--- /dev/null
+++ b/src/panic.h
@@ -0,0 +1,28 @@
+#ifndef PANIC_H
+#define PANIC_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <SDL3/SDL.h>
+
+static void panic(const char *why, ...) {
+ if (!why) return;
+ va_list ap;
+ va_start(ap, why);
+ vfprintf(stderr, why, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ abort();
+}
+
+#define must(result) ({ \
+ typeof(result) must_result = result; \
+ if (!must_result) { \
+ panic("SDL: %s", SDL_GetError()); \
+ } \
+ must_result; \
+})
+
+#endif
diff --git a/src/procfs.c b/src/procfs.c
new file mode 100644
index 0000000..d377e02
--- /dev/null
+++ b/src/procfs.c
@@ -0,0 +1,130 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "procfs.h"
+
+int procfs_open(pid_t pid) {
+ char path[2048];
+ sprintf(path, "/proc/%d/mem", pid);
+ return open(path, O_RDWR);
+}
+
+ssize_t procfs_maps(pid_t pid, struct procfs_map **maps) {
+ char path[2048];
+ sprintf(path, "/proc/%d/maps", pid);
+ FILE *f = fopen(path, "r");
+ if (!f) return -1;
+
+ int n = 0;
+ int capacity = 2;
+ if (*maps) {
+ free(*maps);
+ }
+ *maps = calloc(capacity, sizeof(**maps));
+ if (!*maps) return -1;
+ while (1) {
+ void *base, *max;
+ char prot_str[4];
+ char name[4096] = {0};
+ void *unused;
+ int matches = fscanf(
+ f, "%p-%p %4c %p %p:%p %d",
+ &base, &max, &prot_str, &unused, &unused, &unused, &unused
+ );
+ if (matches == EOF) goto eof;
+ if (matches < 7) goto err;
+
+ int c = ' ';
+ while (c == ' ') {
+ c = fgetc(f);
+ }
+ if (c == EOF) goto err;
+ ungetc(c, f);
+ if (fgets(name, 4096, f) == NULL) goto err;
+ name[strlen(name) - 1] = '\0'; // remove newline
+
+ int prot = 0;
+ for (int i = 0; i < 4; i++) {
+ switch (prot_str[i]) {
+ case 'r':
+ prot |= PROT_READ;
+ break;
+ case 'w':
+ prot |= PROT_WRITE;
+ break;
+ case 'x':
+ prot |= PROT_EXEC;
+ break;
+ default:
+ break;
+ }
+ }
+ if (prot == 0) prot = PROT_NONE;
+
+ if (n > 0) {
+ procfs_map *prev = *maps + (n - 1);
+ if ((strlen(name) == 0 || strncmp(prev->name, name, 1024) == 0) &&
+ prev->max == (uintptr_t)base) {
+ prev->max = (uintptr_t)max;
+ continue;
+ }
+ }
+
+ n++;
+ if (n > capacity) {
+ capacity <<= 1;
+ struct procfs_map *r = reallocarray(*maps, capacity, sizeof(*r));
+ if (!r) goto err;
+ *maps = r;
+ }
+ procfs_map *new = *maps + (n - 1);
+ new->base = (uintptr_t)base;
+ new->max = (uintptr_t)max;
+ new->prot = prot;
+ strncpy(new->name, name, sizeof(new->name) - 1);
+ }
+eof:
+ if (ferror(f)) goto err;
+ fclose(f);
+ if (n == 0) {
+ free(*maps);
+ *maps = NULL;
+ }
+ return n;
+err:
+ fclose(f);
+ free(*maps);
+ *maps = NULL;
+ return -1;
+}
+
+// mmap does not work on /proc/pid/mem, so we issue reads/writes instead
+int read_mem(int fd, uintptr_t addr, uint8_t *data, size_t size) {
+ if (lseek(fd, addr, SEEK_SET) == -1) return -1;
+ size_t n = 0;
+ while (n < size) {
+ ssize_t result = read(fd, data + n, size - n);
+ if (result <= 0) return -1;
+ n += result;
+ }
+ return 0;
+}
+
+int write_mem(int fd, uintptr_t addr, uint8_t *data, size_t size) {
+ if (lseek(fd, addr, SEEK_SET) == -1) return -1;
+ size_t n = 0;
+ while (n < size) {
+ ssize_t result = write(fd, data + n, size - n);
+ if (result <= 0) return -1;
+ n += result;
+ }
+ return 0;
+}
diff --git a/src/procfs.h b/src/procfs.h
new file mode 100644
index 0000000..7196a24
--- /dev/null
+++ b/src/procfs.h
@@ -0,0 +1,27 @@
+#ifndef CORE_PROCFS_H
+#define CORE_PROCFS_H
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE (4096)
+#endif
+
+int procfs_open(pid_t pid);
+int read_mem(int fd, uintptr_t addr, uint8_t *data, size_t size);
+int write_mem(int fd, uintptr_t addr, uint8_t *data, size_t size);
+
+typedef struct procfs_map {
+ uintptr_t base;
+ uintptr_t max;
+ char name[1024];
+ int prot;
+} procfs_map;
+
+ssize_t procfs_maps(pid_t pid, struct procfs_map **maps);
+char procfs_state(pid_t pid);
+
+#endif