diff options
| author | raven <citrons@mondecitronne.com> | 2026-04-08 22:51:39 -0500 |
|---|---|---|
| committer | raven <citrons@mondecitronne.com> | 2026-04-08 22:51:39 -0500 |
| commit | 18a86e1038b20cb6f8922beead08dcc24ba2a4d3 (patch) | |
| tree | 85cb74210d67d42763a4709d18af93aa60a8f400 /src | |
| parent | 4a3429a96b5b5ea7468540349aeb4535d5738053 (diff) | |
rewrite and port to SDL3
Diffstat (limited to 'src')
| -rw-r--r-- | src/curve.h | 25 | ||||
| -rw-r--r-- | src/hash.h | 15 | ||||
| -rw-r--r-- | src/main.c | 391 | ||||
| -rw-r--r-- | src/main.h | 7 | ||||
| -rw-r--r-- | src/memory.c | 140 | ||||
| -rw-r--r-- | src/memory.h | 44 | ||||
| -rw-r--r-- | src/panic.h | 28 | ||||
| -rw-r--r-- | src/procfs.c | 130 | ||||
| -rw-r--r-- | src/procfs.h | 27 |
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 |
