#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <SDL.h>
#include "procfs.h"
#include "memview.h"
static int screen_width = 640;
static int screen_height = 480;
static double scale = 5;
static double pos_x = 1024;
static double pos_y = 0;
static int mouse_x = 0;
static int mouse_y = 0;
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
pid_t pid;
int mem_fd;
struct viewer *viewer;
static void add_scale(double amount) {
pos_x += mouse_x / scale;
pos_y += mouse_y / scale;
scale += amount;
if (scale > 50) scale = 50;
else if (scale < 0.5) scale = 0.5;
pos_x -= mouse_x / scale;
pos_y -= mouse_y / scale;
}
static void world_pos(double *x, double *y) {
*x = *x / scale + floor(pos_x);
*y = *y / scale + floor(pos_y);
}
static void goto_addr(uintptr_t addr) {
int x, y;
to_pos(addr, &x, &y);
pos_x = x; pos_y = y;
pos_x -= (double) screen_width / 2 * scale;
pos_y -= (double) screen_height / 2 * scale;
}
static size_t current_map = 0;
static void next_map() {
struct procfs_map *maps;
int n = procfs_maps(pid, &maps);
if (n < 1) return;
for (int i = 0; i < n; i++) {
current_map = (current_map + 1) % n;
if (maps[current_map].prot & PROT_READ)
break;
}
goto_addr(maps[current_map].base);
free(maps);
}
static void prev_map() {
struct procfs_map *maps;
int n = procfs_maps(pid, &maps);
if (n < 1) return;
for (int i = 0; i < n; i++) {
current_map = current_map > 1 ? current_map - 1 : n - 1;
if (maps[current_map].prot & PROT_READ)
break;
}
goto_addr(maps[current_map].base);
free(maps);
}
static void report_error() {
fprintf(stderr, "SDL Error: %s\n", SDL_GetError());
exit(-1);
}
static void init_sdl(const char *title) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) report_error();
window = SDL_CreateWindow(title,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_width, screen_height,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (window == NULL) report_error();
renderer = SDL_CreateRenderer(window, -1, 0);
if (renderer == NULL) report_error();
}
static void deinit_sdl() {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
static bool held(SDL_Keycode k) {
const uint8_t *state = SDL_GetKeyboardState(NULL);
return state[SDL_GetScancodeFromKey(k)];
}
static void handle_events() {
bool refresh = false;
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
exit(0); break;
case SDL_WINDOWEVENT:
switch (e.window.event) {
case SDL_WINDOWEVENT_RESIZED:
screen_width = e.window.data1;
screen_height = e.window.data2;
SDL_SetWindowSize(window, screen_width, screen_height);
refresh = true;
break;
case SDL_WINDOWEVENT_EXPOSED:
refresh = true;
break;
default:
break;
}
break;
case SDL_MOUSEMOTION:;
uint32_t state = e.motion.state;
if (state & SDL_BUTTON_MMASK ||
(state & SDL_BUTTON_RMASK && held(SDLK_LSHIFT))) {
pos_x -= (double) e.motion.xrel / scale;
pos_y -= (double) e.motion.yrel / scale;
refresh = true;
} else if (state & (SDL_BUTTON_LMASK | SDL_BUTTON_RMASK)) {
double x1 = e.motion.x - e.motion.xrel;
double y1 = e.motion.y - e.motion.yrel;
double x2 = e.motion.x; double y2 = e.motion.y;
world_pos(&x1, &y1); world_pos(&x2, &y2);
pencil(viewer,
x1, y1, x2, y2, !(state & SDL_BUTTON_RMASK));
refresh = true;
}
mouse_x = e.motion.x;
mouse_y = e.motion.y;
break;
case SDL_MOUSEBUTTONDOWN:;
double x = e.button.x; double y = e.button.y;
world_pos(&x, &y);
if (e.button.button == SDL_BUTTON_LEFT)
pencil(viewer, x, y, x, y, true);
else if (e.button.button == SDL_BUTTON_RIGHT)
pencil(viewer, x, y, x, y, false);
break;
case SDL_MOUSEWHEEL:
if (e.wheel.y == 0) break;
add_scale((double) e.wheel.y / 2);
refresh = true;
break;
case SDL_KEYDOWN:
switch (e.key.keysym.sym) {
case SDLK_RIGHT:
next_map();
refresh = true;
break;
case SDLK_LEFT:
prev_map();
refresh = true;
break;
case SDLK_SPACE:;
static bool stopped = false;
kill(pid, stopped ? SIGSTOP : SIGCONT);
stopped = !stopped;
break;
default:
break;
}
default:
break;
}
}
if (render_pages(viewer, pos_x, pos_y,
screen_width, screen_height, scale, refresh))
SDL_RenderPresent(renderer);
}
void cleanup() {
destroy_viewer(viewer);
deinit_sdl();
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "todo: show usage\n");
return -1;
}
pid = atoi(argv[1]);
if (pid == 0) {
fprintf(stderr, "todo: show usage\n");
return -1;
}
mem_fd = procfs_open(pid);
if (mem_fd == -1) perror("error attaching to process");
char title[1024];
snprintf(title, 1024, "%s [%d]", argv[0], pid);
init_sdl(title);
viewer = create_viewer(mem_fd, renderer);
if (!viewer) abort();
atexit(cleanup);
prev_map();
int last_time = 0;
int current_time = 0;
while (1) {
handle_events();
last_time = current_time;
current_time = SDL_GetTicks();
int elapsed = current_time - last_time;
if (elapsed < 1000 / 30)
SDL_Delay(1000 / 30 - elapsed);
}
}