├── .gitignore
├── Makefile.linux
├── Makefile.mingw
├── Makefile.vc
├── README.md
├── UNLICENSE
└── memdig.c
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.obj
3 | *.o
4 | memdig
5 |
--------------------------------------------------------------------------------
/Makefile.linux:
--------------------------------------------------------------------------------
1 | CFLAGS = -std=c99 -Wall -Wextra -Os -g3 -pthread \
2 | -Wno-missing-field-initializers -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE
3 | LDFLAGS = -pthread
4 | LDLIBS = -lm
5 |
6 | memdig : memdig.c
7 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
8 |
9 | clean :
10 | $(RM) memdig
11 |
--------------------------------------------------------------------------------
/Makefile.mingw:
--------------------------------------------------------------------------------
1 | CC = x86_64-w64-mingw32-gcc
2 | CFLAGS = -std=c99 -Wall -Wextra -Os \
3 | -Wno-missing-field-initializers -D__USE_MINGW_ANSI_STDIO=1
4 |
5 | memdig.exe : memdig.c
6 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
7 |
8 | clean :
9 | $(RM) *.exe
10 |
--------------------------------------------------------------------------------
/Makefile.vc:
--------------------------------------------------------------------------------
1 | CFLAGS = -nologo -W4 -Ox -D_CRT_SECURE_NO_WARNINGS -wd4204 -wd4706 -wd4221
2 |
3 | memdig.exe : memdig.c
4 |
5 | clean :
6 | del *.exe *.obj
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MemDig: a memory cheat tool
2 |
3 | MemDig allows the user to manipulate the memory of another process,
4 | primary for the purposes of cheating. There have been many tools like
5 | this before, but this one is a scriptable command line program.
6 |
7 | There are a number of commands available from the program's command
8 | prompt. The "help" command provides a list with documentation. MemDig
9 | commands can also be supplied as command line arguments to the program
10 | itself, by prefixing them with one or two dashes.
11 |
12 | All commands can be shortened so long as they remain unambiguous,
13 | similar to gdb. For example, "attach" can be written as "a" or "att".
14 |
15 | The current set of commands is quite meager, though it can operate on
16 | integers and floats of any size. The command set will grow as more
17 | power is needed.
18 |
19 | ## Example Usage
20 |
21 | Here's how you might change the amount of gold in a game called
22 | Generic RPG. Suppose the process name is `grpg.exe` and you currently
23 | have 273 gold, stored as a 32-bit integer.
24 |
25 | memdig.exe --attach grpg.exe
26 | > find 273
27 | 317 values found
28 |
29 | (... perform an in-game action to change it to 312 gold ...)
30 |
31 | > narrow 312
32 | 1 value found
33 | > set 1000000
34 | 1 value set
35 |
36 | If all goes well, you would now have 1 million gold.
37 |
38 | The above could be scripted entirely as command arguments.
39 |
40 | memdig.exe -a grpg.exe -f 273 -w 10 -n 312 -s 1000000 -q
41 |
42 | The `-w 10` (i.e. `--wait 10`) will put a 10 second delay before the
43 | "narrow" command, giving you a chance to make changes to the game
44 | state. The `-q` (i.e. `--quit`) will exit the program before it beings
45 | the interactive prompt.
46 |
47 | ## Supported Types
48 |
49 | Suffixes can be used to set the type when searching memory. There are
50 | three integer width specifiers: byte (o), short (h), and quad (q), and
51 | each integer type is optionally unsigned (uo, uh, u, uq). For floating
52 | point, include a decimal or exponent in the normal format. An f suffix
53 | indicates single precision.
54 |
55 | * -45o (signed 8-bit)
56 | * 40000uh (unsigned 16-bit)
57 | * 0xffffq (unsigned 64-bit)
58 | * 10.0 (double)
59 | * 1e1f (float)
60 |
61 | ## Supported Platforms
62 |
63 | Currently Windows and Linux are supported. The platform API is fully
64 | abstracted, so support for additional platforms could be easily added.
65 |
66 | ## Future Plans
67 |
68 | * Remote, network interface
69 | * More values types (strings, SIMD)
70 | * Better handling of NaN and inf
71 | * Readline support (especially on Linux)
72 | * Automatic re-attach
73 | * Watchlist editing (add, remove)
74 | * Save/load address lists by name, to file
75 | * Address list transformations and filters
76 | * Progress indicator (find)
77 | * Symbol and region oriented commands (locate known addresses after ASLR)
78 | * (long shot) Export/create trainer EXE for a specific target
79 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/memdig.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | /* Platform API */
6 |
7 | #define REGION_ITERATOR_DONE (1UL << 0)
8 | #define REGION_ITERATOR_READ (1UL << 1)
9 | #define REGION_ITERATOR_WRITE (1UL << 2)
10 | #define REGION_ITERATOR_EXECUTE (1UL << 3)
11 |
12 | #define PROCESS_ITERATOR_DONE (1UL << 0)
13 |
14 | #if 0 // (missing typedefs)
15 | struct process_iterator;
16 | int process_iterator_init(struct process_iterator *);
17 | int process_iterator_next(struct process_iterator *);
18 | int process_iterator_done(struct process_iterator *);
19 | void process_iterator_destroy(struct process_iterator *);
20 |
21 | struct region_iterator;
22 | int region_iterator_init(struct region_iterator *, os_handle);
23 | int region_iterator_next(struct region_iterator *);
24 | int region_iterator_done(struct region_iterator *);
25 | void *region_iterator_memory(struct region_iterator *);
26 | void region_iterator_destroy(struct region_iterator *);
27 |
28 | int os_write_memory(os_handle, uintptr_t, void *, size_t);
29 | void os_sleep(double);
30 | os_handle os_process_open(os_pid);
31 | void os_process_close(os_handle);
32 | const char *os_last_error(void);
33 |
34 | void os_thread_start(struct os_thread *, struct memdig *);
35 | void os_thread_join(struct os_thread *);
36 | void os_mutex_lock(struct os_thread *);
37 | void os_mutex_unlock(struct os_thread *);
38 | #endif
39 |
40 | /* MemDig API for platform */
41 |
42 | struct memdig;
43 | static void memdig_locker(struct memdig *);
44 |
45 | /* Platform implementation */
46 |
47 | #ifdef _WIN32
48 | #include
49 | #include
50 | #include
51 |
52 | typedef HANDLE os_handle;
53 | typedef DWORD os_pid;
54 |
55 | struct process_iterator {
56 | os_pid pid;
57 | char *name;
58 | unsigned long flags;
59 |
60 | // private
61 | HANDLE snapshot;
62 | char buf[MAX_PATH];
63 | PROCESSENTRY32 entry;
64 | };
65 |
66 | static int
67 | process_iterator_next(struct process_iterator *i)
68 | {
69 | if (Process32Next(i->snapshot, &i->entry)) {
70 | strcpy(i->name, i->entry.szExeFile);
71 | i->pid = i->entry.th32ProcessID;
72 | return !(i->flags = 0);
73 | } else {
74 | return !(i->flags = PROCESS_ITERATOR_DONE);
75 | }
76 | }
77 |
78 | static int
79 | process_iterator_init(struct process_iterator *i)
80 | {
81 | i->entry = (PROCESSENTRY32){sizeof(i->entry)};
82 | i->flags = PROCESS_ITERATOR_DONE;
83 | i->name = i->buf;
84 | i->snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
85 | PROCESSENTRY32 entry[1] = {{sizeof(entry)}};
86 | if (Process32First(i->snapshot, entry))
87 | return process_iterator_next(i);
88 | else
89 | return 0;
90 | }
91 |
92 | static void
93 | process_iterator_destroy(struct process_iterator *i)
94 | {
95 | CloseHandle(i->snapshot);
96 | }
97 |
98 | struct region_iterator {
99 | uintptr_t base;
100 | size_t size;
101 | unsigned long flags;
102 |
103 | // private
104 | void *p;
105 | HANDLE process;
106 | void *buf;
107 | size_t bufsize;
108 | };
109 |
110 | static int
111 | region_iterator_next(struct region_iterator *i)
112 | {
113 | MEMORY_BASIC_INFORMATION info[1];
114 | for (;;) {
115 | if (VirtualQueryEx(i->process, i->p, info, sizeof(info))) {
116 | i->flags = 0;
117 | i->p = (char *)i->p + info->RegionSize;
118 | if (info->State == MEM_COMMIT) {
119 | i->size = info->RegionSize;
120 | i->base = (uintptr_t)info->AllocationBase;
121 | switch (info->AllocationProtect) {
122 | case PAGE_EXECUTE:
123 | i->flags |= REGION_ITERATOR_EXECUTE;
124 | break;
125 | case PAGE_EXECUTE_READ:
126 | i->flags |= REGION_ITERATOR_READ;
127 | i->flags |= REGION_ITERATOR_EXECUTE;
128 | break;
129 | case PAGE_EXECUTE_READWRITE:
130 | i->flags |= REGION_ITERATOR_READ;
131 | i->flags |= REGION_ITERATOR_WRITE;
132 | i->flags |= REGION_ITERATOR_EXECUTE;
133 | break;
134 | case PAGE_EXECUTE_WRITECOPY:
135 | i->flags |= REGION_ITERATOR_READ;
136 | i->flags |= REGION_ITERATOR_WRITE;
137 | i->flags |= REGION_ITERATOR_EXECUTE;
138 | break;
139 | case PAGE_READWRITE:
140 | i->flags |= REGION_ITERATOR_READ;
141 | i->flags |= REGION_ITERATOR_WRITE;
142 | break;
143 | case PAGE_READONLY:
144 | i->flags |= REGION_ITERATOR_READ;
145 | break;
146 | case PAGE_WRITECOPY:
147 | i->flags |= REGION_ITERATOR_READ;
148 | i->flags |= REGION_ITERATOR_WRITE;
149 | break;
150 | }
151 | break;
152 | }
153 | } else {
154 | i->flags = REGION_ITERATOR_DONE;
155 | break;
156 | }
157 | }
158 | return !(i->flags & REGION_ITERATOR_DONE);
159 | }
160 |
161 | static int
162 | region_iterator_init(struct region_iterator *i, os_handle process)
163 | {
164 | *i = (struct region_iterator){.process = process};
165 | return region_iterator_next(i);
166 | }
167 |
168 | static const void *
169 | region_iterator_memory(struct region_iterator *i)
170 | {
171 | if (i->bufsize < i->size) {
172 | free(i->buf);
173 | i->bufsize = i->size;
174 | i->buf = malloc(i->bufsize);
175 | }
176 | SIZE_T actual;
177 | void *base = (void *)i->base;
178 | if (!ReadProcessMemory(i->process, base, i->buf, i->size, &actual))
179 | return NULL;
180 | else if (actual < i->size)
181 | return NULL;
182 | return i->buf;
183 | }
184 |
185 | static void
186 | region_iterator_destroy(struct region_iterator *i)
187 | {
188 | free(i->buf);
189 | i->buf = NULL;
190 | }
191 |
192 | static int
193 | os_write_memory(os_handle target, uintptr_t base, void *buf, size_t bufsize)
194 | {
195 | SIZE_T actual;
196 | return WriteProcessMemory(target, (void *)base, buf, bufsize, &actual)
197 | && actual == bufsize;
198 | }
199 |
200 | static void
201 | os_sleep(double seconds)
202 | {
203 | DWORD ms = (DWORD)(seconds * 1000);
204 | Sleep(ms);
205 | }
206 |
207 | static os_handle
208 | os_process_open(os_pid id)
209 | {
210 | DWORD access = PROCESS_VM_READ |
211 | PROCESS_VM_WRITE |
212 | PROCESS_VM_OPERATION |
213 | PROCESS_QUERY_INFORMATION;
214 | HANDLE process = OpenProcess(access, 0, id);
215 | return process;
216 | }
217 |
218 | static void
219 | os_process_close(os_handle h)
220 | {
221 | CloseHandle(h);
222 | }
223 |
224 | static char error_buffer[4096];
225 |
226 | static const char *
227 | os_last_error(void)
228 | {
229 | DWORD e = GetLastError();
230 | SetLastError(0);
231 | DWORD lang = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
232 | DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
233 | FormatMessage(flags, 0, e, lang, error_buffer, sizeof(error_buffer), 0);
234 | for (char *p = error_buffer; *p; p++)
235 | if (*p == '\n')
236 | *p = 0;
237 | return error_buffer;
238 | }
239 |
240 | struct os_thread {
241 | HANDLE thread;
242 | CRITICAL_SECTION mutex;
243 | };
244 |
245 | static unsigned __stdcall
246 | os_stub(void *arg)
247 | {
248 | memdig_locker(arg);
249 | _endthreadex(0);
250 | }
251 |
252 | static void
253 | os_thread_start(struct os_thread *t, struct memdig *m)
254 | {
255 | InitializeCriticalSection(&t->mutex);
256 | t->thread = (HANDLE)_beginthreadex(0, 0, os_stub, m, 0, 0);
257 | }
258 |
259 | static void
260 | os_thread_join(struct os_thread *t)
261 | {
262 | WaitForSingleObject(t->thread, INFINITE);
263 | DeleteCriticalSection(&t->mutex);
264 | }
265 |
266 | static void
267 | os_mutex_lock(struct os_thread *t)
268 | {
269 | EnterCriticalSection(&t->mutex);
270 | }
271 |
272 | static void
273 | os_mutex_unlock(struct os_thread *t)
274 | {
275 | LeaveCriticalSection(&t->mutex);
276 | }
277 |
278 | #elif __linux__
279 | #include
280 | #include
281 | #include
282 | #include
283 |
284 | #include
285 | #include
286 | #include
287 |
288 | typedef pid_t os_handle;
289 | typedef pid_t os_pid;
290 |
291 | struct process_iterator {
292 | os_pid pid;
293 | char *name;
294 | unsigned long flags;
295 |
296 | // private
297 | size_t i;
298 | size_t count;
299 | pid_t *pids;
300 | char buf[256];
301 | };
302 |
303 | static int
304 | process_iterator_next(struct process_iterator *i)
305 | {
306 | for (;;) {
307 | if (i->i == i->count)
308 | return !(i->flags = PROCESS_ITERATOR_DONE);
309 | i->pid = i->pids[i->i++];
310 | sprintf(i->buf, "/proc/%ld/status", (long)i->pid);
311 | FILE *f = fopen(i->buf, "r");
312 | if (f) {
313 | if (fgets(i->buf, sizeof(i->buf), f)) {
314 | char *p = i->buf;
315 | for (; *p && *p != '\t'; p++);
316 | i->name = p + (*p ? 1 : 0);
317 | for (; *p; p++)
318 | if (*p == '\n')
319 | *p = 0;
320 | fclose(f);
321 | return !(i->flags = 0);
322 | } else {
323 | fclose(f);
324 | }
325 | }
326 | }
327 | }
328 |
329 | static int
330 | pid_cmp(const void *a, const void *b)
331 | {
332 | return (int)*(pid_t *)a - (int)*(pid_t *)b;
333 | }
334 |
335 | static int
336 | process_iterator_init(struct process_iterator *i)
337 | {
338 | size_t size = 4096;
339 | *i = (struct process_iterator){
340 | .pids = malloc(sizeof(i->pids[0]) * size),
341 | .flags = PROCESS_ITERATOR_DONE,
342 | };
343 | DIR *dir = opendir("/proc");
344 | if (!dir)
345 | return 0;
346 | struct dirent *e;
347 | while ((e = readdir(dir))) {
348 | int valid = 1;
349 | for (char *p = e->d_name; *p; p++)
350 | if (*p < '0' || *p > '9')
351 | valid = 0;
352 | if (valid) {
353 | if (i->count == size) {
354 | size *= 2;
355 | i->pids = realloc(i->pids, sizeof(i->pids[0]) * size);
356 | }
357 | i->pids[i->count++] = atoi(e->d_name);
358 | }
359 | }
360 | qsort(i->pids, i->count, sizeof(i->pids[0]), pid_cmp);
361 | closedir(dir);
362 | return process_iterator_next(i);
363 | }
364 |
365 | static void
366 | process_iterator_destroy(struct process_iterator *i)
367 | {
368 | free(i->pids);
369 | i->pids = NULL;
370 | }
371 |
372 | struct region_iterator {
373 | uintptr_t base;
374 | size_t size;
375 | unsigned long flags;
376 |
377 | //private
378 | pid_t pid;
379 | FILE *maps;
380 | char *buf;
381 | size_t bufsize;
382 | };
383 |
384 | static int
385 | region_iterator_next(struct region_iterator *i)
386 | {
387 | char perms[8];
388 | uintptr_t beg, end;
389 | int r = fscanf(i->maps, "%" SCNxPTR "-%" SCNxPTR " %7s", &beg, &end, perms);
390 | if (r != 3) {
391 | i->flags = REGION_ITERATOR_DONE;
392 | return 0;
393 | }
394 | int c;
395 | do
396 | c = fgetc(i->maps);
397 | while (c != '\n' && c != EOF);
398 | i->base = beg;
399 | i->size = end - beg;
400 | i->flags = 0;
401 | if (perms[0] == 'r')
402 | i->flags |= REGION_ITERATOR_READ;
403 | if (perms[1] == 'w')
404 | i->flags |= REGION_ITERATOR_WRITE;
405 | if (perms[2] == 'x')
406 | i->flags |= REGION_ITERATOR_EXECUTE;
407 | return 1;
408 | }
409 |
410 | static int
411 | region_iterator_init(struct region_iterator *i, os_handle pid)
412 | {
413 | i->flags = REGION_ITERATOR_DONE;
414 | char file[256];
415 | sprintf(file, "/proc/%ld/maps", (long)pid);
416 | FILE *maps = fopen(file, "r");
417 | if (!maps)
418 | return 0;
419 | *i = (struct region_iterator){
420 | .pid = pid,
421 | .maps = maps,
422 | };
423 | return region_iterator_next(i);
424 | }
425 |
426 | static const void *
427 | region_iterator_memory(struct region_iterator *i)
428 | {
429 | if (i->bufsize < i->size) {
430 | free(i->buf);
431 | i->bufsize = i->size;
432 | i->buf = malloc(i->bufsize);
433 | }
434 | struct iovec local = {
435 | .iov_base = i->buf,
436 | .iov_len = i->size,
437 | };
438 | struct iovec remote = {
439 | .iov_base = (void *)i->base,
440 | .iov_len = i->size,
441 | };
442 | ssize_t in = process_vm_readv(i->pid, &local, 1, &remote, 1, 0);
443 | return (in >= 0 && (size_t)in == i->size) ? i->buf : NULL;
444 | }
445 |
446 | static void
447 | region_iterator_destroy(struct region_iterator *i)
448 | {
449 | fclose(i->maps);
450 | free(i->buf);
451 | i->buf = NULL;
452 | }
453 |
454 | static int
455 | os_write_memory(os_handle pid, uintptr_t addr, void *buf, size_t bufsize)
456 | {
457 | struct iovec local = {
458 | .iov_base = buf,
459 | .iov_len = bufsize,
460 | };
461 | struct iovec remote = {
462 | .iov_base = (void *)addr,
463 | .iov_len = bufsize,
464 | };
465 | ssize_t out = process_vm_writev(pid, &local, 1, &remote, 1, 0);
466 | return out >= 0 && (size_t)out == bufsize;
467 | }
468 |
469 | static void
470 | os_sleep(double s)
471 | {
472 | struct timespec ts = {
473 | .tv_sec = s,
474 | .tv_nsec = (s - trunc(s)) * 1e9,
475 | };
476 | nanosleep(&ts, 0);
477 | }
478 |
479 | static os_handle
480 | os_process_open(os_pid pid)
481 | {
482 | return pid; // TODO: verify capability
483 | }
484 |
485 | static void
486 | os_process_close(os_handle h)
487 | {
488 | (void)h; // nothing to do
489 | }
490 |
491 | const char *
492 | os_last_error(void)
493 | {
494 | return strerror(errno);
495 | }
496 |
497 | struct os_thread {
498 | pthread_t thread;
499 | pthread_mutex_t mutex;
500 | };
501 |
502 | static void *
503 | os_stub(void *arg)
504 | {
505 | memdig_locker(arg);
506 | return NULL;
507 | }
508 |
509 | static void
510 | os_thread_start(struct os_thread *t, struct memdig *m)
511 | {
512 | pthread_mutex_init(&t->mutex, 0);
513 | pthread_create(&t->thread, 0, os_stub, m);
514 | }
515 |
516 | static void
517 | os_thread_join(struct os_thread *t)
518 | {
519 | pthread_join(t->thread, 0);
520 | pthread_mutex_destroy(&t->mutex);
521 | }
522 |
523 | static void
524 | os_mutex_lock(struct os_thread *t)
525 | {
526 | pthread_mutex_lock(&t->mutex);
527 | }
528 |
529 | static void
530 | os_mutex_unlock(struct os_thread *t)
531 | {
532 | pthread_mutex_unlock(&t->mutex);
533 | }
534 |
535 | #endif // __linux__
536 |
537 | static int
538 | process_iterator_done(struct process_iterator *i)
539 | {
540 | return !!(i->flags & PROCESS_ITERATOR_DONE);
541 | }
542 |
543 | static int
544 | region_iterator_done(struct region_iterator *i)
545 | {
546 | return !!(i->flags & REGION_ITERATOR_DONE);
547 | }
548 |
549 | /* Logging */
550 |
551 | static enum loglevel {
552 | LOGLEVEL_DEBUG = -2,
553 | LOGLEVEL_INFO = -1,
554 | LOGLEVEL_WARNING = 0,
555 | LOGLEVEL_ERROR = 1,
556 | } loglevel = 0;
557 |
558 | #define FATAL(...) \
559 | do { \
560 | fprintf(stderr, "memdig: " __VA_ARGS__); \
561 | exit(-1); \
562 | } while (0)
563 |
564 | #define LOG_ERROR(...) \
565 | do { \
566 | if (loglevel <= LOGLEVEL_ERROR) \
567 | fprintf(stderr, "error: " __VA_ARGS__); \
568 | goto fail; \
569 | } while (0)
570 |
571 | #define LOG_WARNING(...) \
572 | do { \
573 | if (loglevel <= LOGLEVEL_WARNING) \
574 | fprintf(stderr, "warning: " __VA_ARGS__); \
575 | } while (0)
576 |
577 | #define LOG_INFO(...) \
578 | do { \
579 | if (loglevel <= LOGLEVEL_INFO) \
580 | fprintf(stderr, "info: " __VA_ARGS__); \
581 | } while (0)
582 |
583 | #define LOG_DEBUG(...) \
584 | do { \
585 | if (loglevel <= LOGLEVEL_DEBUG) \
586 | fprintf(stderr, "debug: " __VA_ARGS__); \
587 | } while (0)
588 |
589 | /* MemDig's operatable types */
590 |
591 | enum value_type {
592 | VALUE_S8,
593 | VALUE_U8,
594 | VALUE_S16,
595 | VALUE_U16,
596 | VALUE_S32,
597 | VALUE_U32,
598 | VALUE_S64,
599 | VALUE_U64,
600 | VALUE_F32,
601 | VALUE_F64,
602 | };
603 |
604 | struct value {
605 | enum value_type type;
606 | union {
607 | int8_t s8;
608 | uint8_t u8;
609 | int16_t s16;
610 | uint16_t u16;
611 | int32_t s32;
612 | uint32_t u32;
613 | int64_t s64;
614 | uint64_t u64;
615 | float f32;
616 | double f64;
617 | } value;
618 | };
619 |
620 | #define VALUE_SIZE(v) ("bbcceeiiei"[(v).type] - 'a')
621 |
622 | enum value_parse_result {
623 | VALUE_PARSE_SUCCESS,
624 | VALUE_PARSE_OVERFLOW,
625 | VALUE_PARSE_INVALID,
626 | };
627 |
628 | static enum value_parse_result
629 | value_parse(struct value *v, const char *arg)
630 | {
631 | int base = 10;
632 | int is_signed = 1;
633 | int is_integer = 1;
634 | size_t len = strlen(arg);
635 | const char *suffix = arg + len;
636 | char digits[] = "0123456789abcdef";
637 |
638 | /* Check prefix to determine base and sign. */
639 | if (arg[0] == '0' && arg[1] == 'x') {
640 | is_signed = 0;
641 | base = 16;
642 | arg += 2;
643 | len -= 2;
644 | } else if (arg[0] == '0') {
645 | is_signed = 0;
646 | base = 8;
647 | arg += 1;
648 | len -= 1;
649 | }
650 | digits[base] = 0;
651 |
652 | /* Find the suffix. */
653 | while (suffix > arg && !strchr(digits, suffix[-1]))
654 | suffix--;
655 |
656 | /* Check for an integer. */
657 | for (const char *p = arg; p < suffix; p++) {
658 | if (p == arg && *p == '-')
659 | p++;
660 | if (!strchr(digits, *p))
661 | is_integer = 0;
662 | }
663 | if (is_integer && suffix[0] == 'u') {
664 | is_signed = 0;
665 | suffix++;
666 | }
667 |
668 | /* Parse the in-between. */
669 | if (is_integer) {
670 | errno = 0;
671 | if (is_signed) {
672 | intmax_t s = strtoimax(arg, 0, base);
673 | if (errno)
674 | return VALUE_PARSE_OVERFLOW;
675 | switch (suffix[0]) {
676 | case 'o':
677 | v->type = VALUE_S8;
678 | if (s > INT8_MAX || s < INT8_MIN)
679 | return VALUE_PARSE_OVERFLOW;
680 | v->value.s8 = (int8_t)s;
681 | return VALUE_PARSE_SUCCESS;
682 | case 'h':
683 | v->type = VALUE_S16;
684 | if (s > INT16_MAX || s < INT16_MIN)
685 | return VALUE_PARSE_OVERFLOW;
686 | v->value.s16 = (int16_t)s;
687 | return VALUE_PARSE_SUCCESS;
688 | case 0:
689 | v->type = VALUE_S32;
690 | if (s > INT32_MAX || s < INT32_MIN)
691 | return VALUE_PARSE_OVERFLOW;
692 | v->value.s32 = (int32_t)s;
693 | return VALUE_PARSE_SUCCESS;
694 | case 'q':
695 | v->type = VALUE_S64;
696 | if (s > INT64_MAX || s < INT64_MIN)
697 | return VALUE_PARSE_OVERFLOW;
698 | v->value.s64 = (int64_t)s;
699 | return VALUE_PARSE_SUCCESS;
700 | }
701 | } else {
702 | uintmax_t u = strtoumax(arg, 0, base);
703 | if (errno)
704 | return VALUE_PARSE_OVERFLOW;
705 | switch (suffix[0]) {
706 | case 'o':
707 | v->type = VALUE_U8;
708 | if (u > UINT8_MAX)
709 | return VALUE_PARSE_OVERFLOW;
710 | v->value.u8 = (uint8_t)u;
711 | return VALUE_PARSE_SUCCESS;
712 | case 'h':
713 | v->type = VALUE_U16;
714 | if (u > UINT16_MAX)
715 | return VALUE_PARSE_OVERFLOW;
716 | v->value.u16 = (uint16_t)u;
717 | return VALUE_PARSE_SUCCESS;
718 | case 0:
719 | v->type = VALUE_U32;
720 | if (u > UINT32_MAX)
721 | return VALUE_PARSE_OVERFLOW;
722 | v->value.u32 = (uint32_t)u;
723 | return VALUE_PARSE_SUCCESS;
724 | case 'q':
725 | v->type = VALUE_U64;
726 | if (u > UINT64_MAX)
727 | return VALUE_PARSE_OVERFLOW;
728 | v->value.u64 = (uint64_t)u;
729 | return VALUE_PARSE_SUCCESS;
730 | }
731 | }
732 | } else { /* float */
733 | errno = 0;
734 | char *end;
735 | switch (suffix[0]) {
736 | case 'f':
737 | v->type = VALUE_F32;
738 | v->value.f32 = strtof(arg, &end);
739 | if (end == suffix)
740 | return VALUE_PARSE_SUCCESS;
741 | break;
742 | case 0:
743 | v->type = VALUE_F64;
744 | v->value.f64 = strtod(arg, &end);
745 | if (end == suffix)
746 | return VALUE_PARSE_SUCCESS;
747 | break;
748 | }
749 | }
750 | return VALUE_PARSE_INVALID;
751 | }
752 |
753 | static void
754 | value_print(char *buf, size_t n, const struct value *v)
755 | {
756 | switch (v->type) {
757 | case VALUE_S8: {
758 | snprintf(buf, n, "%" PRId8 "o", v->value.s8);
759 | } return;
760 | case VALUE_U8: {
761 | snprintf(buf, n, "%" PRIu8 "uo", v->value.u8);
762 | } return;
763 | case VALUE_S16: {
764 | snprintf(buf, n, "%" PRId16 "h", v->value.s16);
765 | } return;
766 | case VALUE_U16: {
767 | snprintf(buf, n, "%" PRIu16 "uh", v->value.u16);
768 | } return;
769 | case VALUE_S32: {
770 | snprintf(buf, n, "%" PRId32 "", v->value.s32);
771 | } return;
772 | case VALUE_U32: {
773 | snprintf(buf, n, "%" PRIu32 "u", v->value.u32);
774 | } return;
775 | case VALUE_S64: {
776 | snprintf(buf, n, "%" PRId64 "q", v->value.s64);
777 | } return;
778 | case VALUE_U64: {
779 | snprintf(buf, n, "%" PRIu64 "uq", v->value.u64);
780 | } return;
781 | case VALUE_F32: {
782 | snprintf(buf, n, "%.9gf", v->value.f32);
783 | } return;
784 | case VALUE_F64: {
785 | snprintf(buf, n, "%.17g", v->value.f64);
786 | } return;
787 | }
788 | abort();
789 | }
790 |
791 | static void
792 | value_read(struct value *v, enum value_type t, const void *p)
793 | {
794 | v->type = t;
795 | switch (v->type) {
796 | case VALUE_S8: {
797 | memcpy(&v->value.s8, p, sizeof(v->value.s8));
798 | } return;
799 | case VALUE_U8: {
800 | memcpy(&v->value.u8, p, sizeof(v->value.u8));
801 | } return;
802 | case VALUE_S16: {
803 | memcpy(&v->value.s16, p, sizeof(v->value.s16));
804 | } return;
805 | case VALUE_U16: {
806 | memcpy(&v->value.u16, p, sizeof(v->value.u16));
807 | } return;
808 | case VALUE_S32: {
809 | memcpy(&v->value.s32, p, sizeof(v->value.s32));
810 | } return;
811 | case VALUE_U32: {
812 | memcpy(&v->value.u32, p, sizeof(v->value.u32));
813 | } return;
814 | case VALUE_S64: {
815 | memcpy(&v->value.s64, p, sizeof(v->value.s64));
816 | } return;
817 | case VALUE_U64: {
818 | memcpy(&v->value.u64, p, sizeof(v->value.u64));
819 | } return;
820 | case VALUE_F32: {
821 | memcpy(&v->value.f32, p, sizeof(v->value.f32));
822 | } return;
823 | case VALUE_F64: {
824 | memcpy(&v->value.f64, p, sizeof(v->value.f64));
825 | } return;
826 | }
827 | abort();
828 | }
829 |
830 | #define VALUE_COMPARE(a, b) ((a) < (b) ? -1 : (b) < (a) ? 1 : 0)
831 |
832 | static int
833 | value_compare(const struct value *a, const struct value *b)
834 | {
835 | if (a->type != b->type)
836 | return a->type - b->type;
837 | switch (a->type) {
838 | case VALUE_S8:
839 | return VALUE_COMPARE(a->value.s8, b->value.s8);
840 | case VALUE_U8:
841 | return VALUE_COMPARE(a->value.u8, b->value.u8);
842 | case VALUE_S16:
843 | return VALUE_COMPARE(a->value.s16, b->value.s16);
844 | case VALUE_U16:
845 | return VALUE_COMPARE(a->value.u16, b->value.u16);
846 | case VALUE_S32:
847 | return VALUE_COMPARE(a->value.s32, b->value.s32);
848 | case VALUE_U32:
849 | return VALUE_COMPARE(a->value.u32, b->value.u32);
850 | case VALUE_S64:
851 | return VALUE_COMPARE(a->value.s64, b->value.s64);
852 | case VALUE_U64:
853 | return VALUE_COMPARE(a->value.u64, b->value.u64);
854 | case VALUE_F32:
855 | return VALUE_COMPARE(a->value.f32, b->value.f32);
856 | case VALUE_F64:
857 | return VALUE_COMPARE(a->value.f64, b->value.f64);
858 | }
859 | abort();
860 | }
861 |
862 | /* Watchlist */
863 |
864 | struct watchlist {
865 | os_handle process;
866 | size_t count;
867 | size_t size;
868 | struct {
869 | uintptr_t addr;
870 | struct value prev;
871 | } *list;
872 | };
873 |
874 | static void
875 | watchlist_init(struct watchlist *s, os_handle process)
876 | {
877 | s->process = process;
878 | s->size = 4096;
879 | s->count = 0;
880 | s->list = malloc(s->size * sizeof(s->list[0]));
881 | }
882 |
883 | static void
884 | watchlist_push(struct watchlist *s, uintptr_t a, const struct value *v)
885 | {
886 | if (s->count == s->size) {
887 | s->size *= 2;
888 | s->list = realloc(s->list, s->size * sizeof(s->list[0]));
889 | }
890 | s->list[s->count].addr = a;
891 | s->list[s->count].prev = *v;
892 | s->count++;
893 | }
894 |
895 | static void
896 | watchlist_free(struct watchlist *s)
897 | {
898 | free(s->list);
899 | s->list = NULL;
900 | }
901 |
902 | static void
903 | watchlist_clear(struct watchlist *s)
904 | {
905 | s->count = 0;
906 | }
907 |
908 | /* Memory scanning */
909 |
910 | enum scan_op {
911 | SCAN_OP_EQ,
912 | SCAN_OP_LT,
913 | SCAN_OP_GT,
914 | SCAN_OP_LTEG,
915 | SCAN_OP_GTEQ,
916 | };
917 |
918 | static int
919 | scan_op_parse(const char *s, enum scan_op *op)
920 | {
921 | static const struct {
922 | char name[3];
923 | enum scan_op op;
924 | } table[] = {
925 | {"=", SCAN_OP_EQ},
926 | {"<", SCAN_OP_LT},
927 | {">", SCAN_OP_GT},
928 | {"<=", SCAN_OP_LTEG},
929 | {">=", SCAN_OP_GTEQ},
930 | };
931 | for (unsigned i = 0; i < sizeof(table) / sizeof(table[0]); i++)
932 | if (strcmp(table[i].name, s) == 0) {
933 | *op = table[i].op;
934 | return 1;
935 | }
936 | return 0;
937 | }
938 |
939 | static int
940 | scan(struct watchlist *wl, struct value *v, enum scan_op op)
941 | {
942 | unsigned value_size = VALUE_SIZE(*v);
943 | enum value_type type = v->type;
944 | watchlist_clear(wl);
945 | struct region_iterator it[1];
946 | region_iterator_init(it, wl->process);
947 | for (; !region_iterator_done(it); region_iterator_next(it)) {
948 | const char *buf;
949 | if ((buf = region_iterator_memory(it))) {
950 | size_t count = it->size / value_size;
951 | for (size_t i = 0; i < count; i++) {
952 | struct value read;
953 | value_read(&read, type, buf + i * value_size);
954 | int cmp = value_compare(&read, v);
955 | int pass = 0;
956 | switch (op) {
957 | case SCAN_OP_EQ:
958 | pass = cmp == 0;
959 | break;
960 | case SCAN_OP_LT:
961 | pass = cmp < 0;
962 | break;
963 | case SCAN_OP_GT:
964 | pass = cmp > 0;
965 | break;
966 | case SCAN_OP_LTEG:
967 | pass = cmp <= 0;
968 | break;
969 | case SCAN_OP_GTEQ:
970 | pass = cmp >= 0;
971 | break;
972 | }
973 | if (pass) {
974 | uintptr_t addr = it->base + i * value_size;
975 | watchlist_push(wl, addr, &read);
976 | }
977 | }
978 | } else {
979 | LOG_DEBUG("memory read failed [0x%016" PRIxPTR "]: %s\n",
980 | it->base, os_last_error());
981 | }
982 | }
983 | region_iterator_destroy(it);
984 | return 1;
985 | }
986 |
987 | typedef void (*watchlist_visitor)(uintptr_t, const struct value *, void *);
988 |
989 | static void
990 | watchlist_visit(struct watchlist *wl, watchlist_visitor f, void *arg)
991 | {
992 | struct region_iterator it[1];
993 | region_iterator_init(it, wl->process);
994 | size_t n = 0;
995 | for (; !region_iterator_done(it) && n < wl->count; region_iterator_next(it)) {
996 | const char *buf = NULL;
997 | uintptr_t base = it->base;
998 | uintptr_t tail = base + it->size;
999 | while (n < wl->count && wl->list[n].addr < base)
1000 | n++;
1001 | while (n < wl->count && wl->list[n].addr >= base && wl->list[n].addr < tail) {
1002 | if (!buf)
1003 | buf = region_iterator_memory(it);
1004 | uintptr_t addr = wl->list[n].addr;
1005 | enum value_type type = wl->list[n].prev.type;
1006 | size_t offset = wl->list[n].addr - base;
1007 | if (buf) {
1008 | struct value value;
1009 | value_read(&value, type, buf + offset);
1010 | f(addr, &value, arg);
1011 | } else {
1012 | f(addr, NULL, arg);
1013 | }
1014 | n++;
1015 | }
1016 | }
1017 | region_iterator_destroy(it);
1018 | }
1019 |
1020 | struct narrow_visitor_state {
1021 | struct watchlist *wl;
1022 | struct value target;
1023 | enum scan_op op;
1024 | };
1025 |
1026 | static void
1027 | narrow_visitor(uintptr_t addr, const struct value *v, void *arg)
1028 | {
1029 | char buf[64];
1030 | value_print(buf, sizeof(buf), v);
1031 | struct narrow_visitor_state *s = arg;
1032 | int cmp = value_compare(v, &s->target);
1033 | int pass = 0;
1034 | switch (s->op) {
1035 | case SCAN_OP_EQ:
1036 | pass = cmp == 0;
1037 | break;
1038 | case SCAN_OP_LT:
1039 | pass = cmp < 0;
1040 | break;
1041 | case SCAN_OP_GT:
1042 | pass = cmp > 0;
1043 | break;
1044 | case SCAN_OP_LTEG:
1045 | pass = cmp <= 0;
1046 | break;
1047 | case SCAN_OP_GTEQ:
1048 | pass = cmp >= 0;
1049 | break;
1050 | }
1051 | if (pass)
1052 | watchlist_push(s->wl, addr, v);
1053 | }
1054 |
1055 | static int
1056 | narrow(struct watchlist *wl, enum scan_op op, struct value *v)
1057 | {
1058 | struct watchlist out[1];
1059 | watchlist_init(out, wl->process);
1060 | struct narrow_visitor_state state = {
1061 | .wl = out,
1062 | .target = *v,
1063 | .op = op,
1064 | };
1065 | watchlist_visit(wl, narrow_visitor, &state);
1066 | watchlist_free(wl);
1067 | *wl = *out;
1068 | return 1;
1069 | }
1070 |
1071 | static void
1072 | display_memory_regions(os_handle target)
1073 | {
1074 | struct region_iterator it[1];
1075 | region_iterator_init(it, target);
1076 | for (; !region_iterator_done(it); region_iterator_next(it)) {
1077 | char protect[4] = {
1078 | it->flags & REGION_ITERATOR_READ ? 'R' : ' ',
1079 | it->flags & REGION_ITERATOR_WRITE ? 'W' : ' ',
1080 | it->flags & REGION_ITERATOR_EXECUTE ? 'X' : ' ',
1081 | };
1082 | uintptr_t tail = it->base + it->size;
1083 | printf("%s 0x%016" PRIxPTR " 0x%016" PRIxPTR " %10zu bytes\n",
1084 | protect, it->base, tail, it->size);
1085 | }
1086 | region_iterator_destroy(it);
1087 | }
1088 |
1089 | /* Processes */
1090 |
1091 | enum find_result {
1092 | FIND_SUCCESS,
1093 | FIND_FAILURE,
1094 | FIND_AMBIGUOUS,
1095 | };
1096 |
1097 | static os_pid
1098 | process_find(const char *pattern, os_pid *pid)
1099 | {
1100 | struct process_iterator it[1];
1101 | process_iterator_init(it);
1102 | *pid = 0;
1103 | os_pid target_pid = 0;
1104 | if (pattern[0] == ':')
1105 | target_pid = strtol(pattern + 1, NULL, 10);
1106 | for (; !process_iterator_done(it); process_iterator_next(it)) {
1107 | if (target_pid == it->pid || strstr(it->name, pattern)) {
1108 | if (*pid)
1109 | return FIND_AMBIGUOUS;
1110 | *pid = it->pid;
1111 | }
1112 | }
1113 | process_iterator_destroy(it);
1114 | if (!*pid)
1115 | return FIND_FAILURE;
1116 | return FIND_SUCCESS;
1117 | }
1118 |
1119 | /* Command processing */
1120 |
1121 | enum command {
1122 | COMMAND_AMBIGUOUS = -2,
1123 | COMMAND_UNKNOWN = -1,
1124 | COMMAND_ATTACH = 0,
1125 | COMMAND_MEMORY,
1126 | COMMAND_FIND,
1127 | COMMAND_NARROW,
1128 | COMMAND_PUSH,
1129 | COMMAND_LIST,
1130 | COMMAND_SET,
1131 | COMMAND_LOCK,
1132 | COMMAND_WAIT,
1133 | COMMAND_HELP,
1134 | COMMAND_QUIT,
1135 | };
1136 |
1137 | static struct {
1138 | const char *name;
1139 | const char *help;
1140 | const char *args;
1141 | } command_info[] = {
1142 | [COMMAND_ATTACH] = {
1143 | "attach", "select a new target process",
1144 | "[:pid|pattern]"
1145 | },
1146 | [COMMAND_MEMORY] = {
1147 | "memory", "list committed memory regions",
1148 | 0
1149 | },
1150 | [COMMAND_FIND] = {
1151 | "find", "find and remember integral memory values",
1152 | "[<|>|=|<=|>=] "
1153 | },
1154 | [COMMAND_NARROW] = {
1155 | "narrow", "filter the current list of addresses",
1156 | "[<|>|=|<=|>=] "
1157 | },
1158 | [COMMAND_PUSH] = {
1159 | "push", "manually add address to list",
1160 | ""
1161 | },
1162 | [COMMAND_LIST] = {
1163 | "list", "show the current address list",
1164 | "[proc|addr|lock]"
1165 | },
1166 | [COMMAND_SET] = {
1167 | "set", "set memory at each listed address",
1168 | ""
1169 | },
1170 | [COMMAND_LOCK] = {
1171 | "lock", "lock the memory at each listed address",
1172 | "[new value]"
1173 | },
1174 | [COMMAND_WAIT] = {
1175 | "wait", "wait a fractional number of seconds",
1176 | ""
1177 | },
1178 | [COMMAND_HELP] = {
1179 | "help", "print this help information",
1180 | 0},
1181 | [COMMAND_QUIT] = {
1182 | "quit", "exit the program",
1183 | 0},
1184 | };
1185 |
1186 | static enum command
1187 | command_parse(const char *c)
1188 | {
1189 | unsigned n = sizeof(command_info) / sizeof(command_info[0]);
1190 | size_t len = strlen(c);
1191 | enum command command = COMMAND_UNKNOWN;
1192 | for (unsigned i = 0; i < n; i++)
1193 | if (strncmp(command_info[i].name, c, len) == 0) {
1194 | if (command != COMMAND_UNKNOWN)
1195 | return COMMAND_AMBIGUOUS;
1196 | else
1197 | command = i;
1198 | }
1199 | return command;
1200 | }
1201 |
1202 | /* High level MemDig API */
1203 |
1204 | struct memdig {
1205 | os_pid id;
1206 | os_handle target;
1207 | struct os_thread thread;
1208 | enum value_type last_type;
1209 | struct watchlist active;
1210 | struct watchlist locked;
1211 | int running;
1212 | };
1213 |
1214 | static void
1215 | memdig_locker(struct memdig *m)
1216 | {
1217 | for (;;) {
1218 | os_sleep(0.1);
1219 | os_mutex_lock(&m->thread);
1220 | if (!m->running) {
1221 | os_mutex_unlock(&m->thread);
1222 | break;
1223 | }
1224 | if (m->target)
1225 | for (size_t i = 0; i < m->locked.count; i++) {
1226 | uintptr_t addr = m->locked.list[i].addr;
1227 | struct value *value = &m->locked.list[i].prev;
1228 | unsigned size = VALUE_SIZE(*value);
1229 | os_write_memory(m->target, addr, &value->value, size);
1230 | }
1231 | os_mutex_unlock(&m->thread);
1232 | }
1233 | }
1234 |
1235 | static void
1236 | memdig_init(struct memdig *m)
1237 | {
1238 | *m = (struct memdig){
1239 | .last_type = VALUE_S32,
1240 | .running = 1,
1241 | };
1242 | os_thread_start(&m->thread, m);
1243 | }
1244 |
1245 | static void
1246 | list_visitor(uintptr_t addr, const struct value *v, void *file)
1247 | {
1248 | char buf[64] = "???";
1249 | if (v)
1250 | value_print(buf, sizeof(buf), v);
1251 | fprintf(file, "0x%016" PRIxPTR " %s\n", addr, buf);
1252 | }
1253 |
1254 | enum memdig_result {
1255 | MEMDIG_RESULT_ERROR = -1,
1256 | MEMDIG_RESULT_OK = 0,
1257 | MEMDIG_RESULT_QUIT = 1,
1258 | };
1259 |
1260 | static enum memdig_result
1261 | memdig_exec(struct memdig *m, int argc, char **argv)
1262 | {
1263 | if (argc == 0)
1264 | return MEMDIG_RESULT_OK;
1265 | char *verb = argv[0];
1266 | enum command command = command_parse(verb);
1267 | switch (command) {
1268 | case COMMAND_AMBIGUOUS: {
1269 | LOG_ERROR("ambiguous command '%s'\n", verb);
1270 | } break;
1271 | case COMMAND_UNKNOWN: {
1272 | LOG_ERROR("unknown command '%s'\n", verb);
1273 | } break;
1274 | case COMMAND_ATTACH: {
1275 | if (argc == 1) {
1276 | if (m->target)
1277 | printf("attached to %ld\n", (long)m->id);
1278 | else
1279 | printf("not attached to a process\n");
1280 | return MEMDIG_RESULT_OK;
1281 | } else if (argc != 2)
1282 | LOG_ERROR("wrong number of arguments\n");
1283 | if (m->target) {
1284 | os_mutex_lock(&m->thread);
1285 | watchlist_free(&m->active);
1286 | watchlist_free(&m->locked);
1287 | os_process_close(m->target);
1288 | m->target = 0;
1289 | os_mutex_unlock(&m->thread);
1290 | }
1291 | char *pattern = argv[1];
1292 | switch (process_find(pattern, &m->id)) {
1293 | case FIND_FAILURE:
1294 | LOG_ERROR("no process found for '%s'\n", pattern);
1295 | break;
1296 | case FIND_AMBIGUOUS:
1297 | LOG_ERROR("ambiguous target '%s'\n", pattern);
1298 | break;
1299 | case FIND_SUCCESS:
1300 | os_mutex_lock(&m->thread);
1301 | if (!(m->target = os_process_open(m->id))) {
1302 | if (!m->target)
1303 | LOG_ERROR("open process %ld failed: %s\n",
1304 | (long)m->id, os_last_error());
1305 | m->id = 0;
1306 | } else {
1307 | watchlist_init(&m->active, m->target);
1308 | watchlist_init(&m->locked, m->target);
1309 | printf("attached to %ld\n", (long)m->id);
1310 | }
1311 | os_mutex_unlock(&m->thread);
1312 | break;
1313 | }
1314 | } break;
1315 | case COMMAND_MEMORY: {
1316 | if (!m->target)
1317 | LOG_ERROR("no process attached\n");
1318 | display_memory_regions(m->target);
1319 | } break;
1320 | case COMMAND_FIND: {
1321 | if (!m->target)
1322 | LOG_ERROR("no process attached\n");
1323 | if (argc < 2 || argc > 3)
1324 | LOG_ERROR("wrong number of arguments\n");
1325 | enum scan_op op = SCAN_OP_EQ;
1326 | const char *arg = argv[1];
1327 | if (argc == 3) {
1328 | if (!scan_op_parse(argv[1], &op))
1329 | LOG_ERROR("invalid operator '%s'\n", argv[1]);
1330 | arg = argv[2];
1331 | }
1332 | struct value value;
1333 | enum value_parse_result r = value_parse(&value, arg);
1334 | switch (r) {
1335 | case VALUE_PARSE_OVERFLOW: {
1336 | LOG_ERROR("overflow '%s'\n", arg);
1337 | } break;
1338 | case VALUE_PARSE_INVALID: {
1339 | LOG_ERROR("invalid value '%s'\n", arg);
1340 | } break;
1341 | case VALUE_PARSE_SUCCESS: {
1342 | char buf[64];
1343 | value_print(buf, sizeof(buf), &value);
1344 | LOG_INFO("finding %s\n", buf);
1345 | } break;
1346 | }
1347 | m->last_type = value.type;
1348 | if (!scan(&m->active, &value, op))
1349 | LOG_ERROR("scan failure'\n");
1350 | else
1351 | printf("%zu values found\n", m->active.count);
1352 | } break;
1353 | case COMMAND_NARROW: {
1354 | if (!m->target)
1355 | LOG_ERROR("no process attached\n");
1356 | if (argc < 2 || argc > 3)
1357 | LOG_ERROR("wrong number of arguments\n");
1358 | enum scan_op op = SCAN_OP_EQ;
1359 | const char *arg = argv[1];
1360 | if (argc == 3) {
1361 | if (!scan_op_parse(argv[1], &op))
1362 | LOG_ERROR("invalid operator '%s'\n", argv[1]);
1363 | arg = argv[2];
1364 | }
1365 | struct value value;
1366 | enum value_parse_result r = value_parse(&value, arg);
1367 | switch (r) {
1368 | case VALUE_PARSE_OVERFLOW: {
1369 | LOG_ERROR("overflow '%s'\n", arg);
1370 | } break;
1371 | case VALUE_PARSE_INVALID: {
1372 | LOG_ERROR("invalid value '%s'\n", arg);
1373 | } break;
1374 | case VALUE_PARSE_SUCCESS: {
1375 | char buf[64];
1376 | value_print(buf, sizeof(buf), &value);
1377 | LOG_INFO("narrowing to %s\n", buf);
1378 | } break;
1379 | }
1380 |
1381 | if (!narrow(&m->active, op, &value))
1382 | LOG_ERROR("scan failure'\n");
1383 | else
1384 | printf("%zu values found\n", m->active.count);
1385 | } break;
1386 | case COMMAND_PUSH: {
1387 | if (!m->target)
1388 | LOG_ERROR("no process attached\n");
1389 | if (argc != 2)
1390 | LOG_ERROR("wrong number of arguments");
1391 | char *addrs = argv[1];
1392 | if (strncmp(addrs, "0x", 2) != 0)
1393 | LOG_ERROR("unknown address format '%s'\n", addrs);
1394 | uintptr_t addr = (uintptr_t)strtoull(addrs + 2, NULL, 16);
1395 | struct value value = {.type = m->last_type};
1396 | watchlist_push(&m->active, addr, &value);
1397 | } break;
1398 | case COMMAND_LIST: {
1399 | char arg = 'a';
1400 | if (argc > 1)
1401 | arg = argv[1][0];
1402 | switch (arg) {
1403 | case 'a': {
1404 | if (!m->target)
1405 | LOG_ERROR("no process attached\n");
1406 | watchlist_visit(&m->active, list_visitor, stdout);
1407 | } break;
1408 | case 'p': {
1409 | struct process_iterator i[1];
1410 | process_iterator_init(i);
1411 | for (; !process_iterator_done(i); process_iterator_next(i))
1412 | printf("%8ld %s\n", (long)i->pid, i->name);
1413 | process_iterator_destroy(i);
1414 | } break;
1415 | case 'l': {
1416 | if (!m->target)
1417 | LOG_ERROR("no process attached\n");
1418 | watchlist_visit(&m->locked, list_visitor, 0);
1419 | } break;
1420 | default: {
1421 | LOG_ERROR("unknown list type '%s'\n", argv[1]);
1422 | } break;
1423 | }
1424 | } break;
1425 | case COMMAND_SET: {
1426 | if (!m->target)
1427 | LOG_ERROR("no process attached\n");
1428 | if (argc != 2)
1429 | LOG_ERROR("wrong number of arguments");
1430 | const char *arg = argv[1];
1431 | struct value value;
1432 | enum value_parse_result r = value_parse(&value, arg);
1433 | switch (r) {
1434 | case VALUE_PARSE_OVERFLOW: {
1435 | LOG_ERROR("overflow '%s'\n", arg);
1436 | } break;
1437 | case VALUE_PARSE_INVALID: {
1438 | LOG_ERROR("invalid value '%s'\n", arg);
1439 | } break;
1440 | case VALUE_PARSE_SUCCESS: {
1441 | char buf[64];
1442 | value_print(buf, sizeof(buf), &value);
1443 | LOG_INFO("setting to %s\n", buf);
1444 | } break;
1445 | }
1446 | m->last_type = value.type;
1447 | size_t set_count = 0;
1448 | for (size_t i = 0; i < m->active.count; i++) {
1449 | uintptr_t addr = m->active.list[i].addr;
1450 | unsigned size = VALUE_SIZE(value);
1451 | if (!os_write_memory(m->target, addr, &value.value, size))
1452 | LOG_WARNING("write memory failed: %s\n",
1453 | os_last_error());
1454 | else
1455 | set_count++;
1456 | }
1457 | printf("%zu values set\n", set_count);
1458 | } break;
1459 | case COMMAND_LOCK: {
1460 | if (!m->target)
1461 | LOG_ERROR("no process attached\n");
1462 | if (argc > 2)
1463 | LOG_ERROR("wrong number of arguments");
1464 | struct value value;
1465 | int have_value = 0;
1466 | if (argc == 2) {
1467 | have_value = 1;
1468 | const char *arg = argv[1];
1469 | enum value_parse_result r = value_parse(&value, arg);
1470 | switch (r) {
1471 | case VALUE_PARSE_OVERFLOW: {
1472 | LOG_ERROR("overflow '%s'\n", arg);
1473 | } break;
1474 | case VALUE_PARSE_INVALID: {
1475 | LOG_ERROR("invalid value '%s'\n", arg);
1476 | } break;
1477 | case VALUE_PARSE_SUCCESS: {
1478 | char buf[64];
1479 | value_print(buf, sizeof(buf), &value);
1480 | LOG_INFO("setting to %s\n", buf);
1481 | } break;
1482 | }
1483 | m->last_type = value.type;
1484 | }
1485 | os_mutex_lock(&m->thread);
1486 | for (size_t i = 0; i < m->active.count; i++) {
1487 | uintptr_t addr = m->active.list[i].addr;
1488 | struct value *prev = &m->active.list[i].prev;
1489 | watchlist_push(&m->locked, addr, have_value ? &value : prev);
1490 | }
1491 | os_mutex_unlock(&m->thread);
1492 | } break;
1493 | case COMMAND_WAIT: {
1494 | if (argc != 2)
1495 | LOG_ERROR("wrong number of arguments");
1496 | os_sleep(atof(argv[1]));
1497 | } break;
1498 | case COMMAND_HELP: {
1499 | unsigned n = sizeof(command_info) / sizeof(command_info[0]);
1500 | for (unsigned i = 0; i < n; i++) {
1501 | const char *name = command_info[i].name;
1502 | const char *help = command_info[i].help;
1503 | const char *args = command_info[i].args;
1504 | int argsize = (int)(30 - strlen(name));
1505 | printf("%s %-*s %s\n", name, argsize, args ? args : "", help);
1506 | }
1507 | putchar('\n');
1508 | puts("Commands can also be supplied as command line arguments, "
1509 | "where each\ncommand verb is prefixed with one or two "
1510 | "dashes.\n");
1511 | puts("By default, memory is scanned for 32-bit signed integers. "
1512 | "The numeric\narguments to find, narrow, and set may have "
1513 | "C-like suffixes specifying\ntheir width and signedness "
1514 | "(o, h, q, uo, uh, uq). Floating point\nvalues are also "
1515 | "an option, with an 'f' suffix for single precision.");
1516 | } break;
1517 | case COMMAND_QUIT: {
1518 | return MEMDIG_RESULT_QUIT;
1519 | } break;
1520 | }
1521 | return MEMDIG_RESULT_OK;
1522 | fail:
1523 | return MEMDIG_RESULT_ERROR;
1524 | }
1525 |
1526 | static void
1527 | memdig_free(struct memdig *m)
1528 | {
1529 | os_mutex_lock(&m->thread);
1530 | if (m->target) {
1531 | watchlist_free(&m->active);
1532 | watchlist_free(&m->locked);
1533 | os_process_close(m->target);
1534 | m->target = 0;
1535 | }
1536 | m->running = 0;
1537 | os_mutex_unlock(&m->thread);
1538 | os_thread_join(&m->thread);
1539 | }
1540 |
1541 | #define PROMPT(f) \
1542 | do { \
1543 | fputs("> ", f); \
1544 | fflush(f); \
1545 | } while(0)
1546 |
1547 | int
1548 | main(int argc, char **argv)
1549 | {
1550 | int result = 0;
1551 | struct memdig memdig[1];
1552 | memdig_init(memdig);
1553 |
1554 | char *xargv[16];
1555 | int xargc = 0;
1556 | int i = 1;
1557 | while (i < argc && argv[i][0] == '-') {
1558 | xargc = 1;
1559 | xargv[0] = argv[i++];
1560 | while (xargv[0][0] == '-' && xargv[0][0] != 0)
1561 | xargv[0]++;
1562 | while (i < argc && argv[i][0] != '-' && xargc < 15)
1563 | xargv[xargc++] = argv[i++];
1564 | xargv[xargc] = NULL;
1565 | enum memdig_result r = memdig_exec(memdig, xargc, xargv);
1566 | if (r == MEMDIG_RESULT_ERROR)
1567 | result = -1;
1568 | if (r != MEMDIG_RESULT_OK)
1569 | goto quit;
1570 | if (strcmp(xargv[0], "help") == 0)
1571 | goto quit;
1572 | if (strcmp(xargv[0], "version") == 0)
1573 | goto quit;
1574 | }
1575 |
1576 | char line[4096];
1577 | PROMPT(stdout);
1578 | const char *delim = " \n\t";
1579 | while (fgets(line, sizeof(line), stdin)) {
1580 | xargc = 0;
1581 | xargv[0] = strtok(line, delim);
1582 | if (xargv[0]) {
1583 | do
1584 | xargc++;
1585 | while (xargc < 15 && (xargv[xargc] = strtok(NULL, delim)));
1586 | xargv[xargc] = NULL;
1587 | }
1588 | if (memdig_exec(memdig, xargc, xargv) == MEMDIG_RESULT_QUIT)
1589 | goto quit;
1590 | PROMPT(stdout);
1591 | }
1592 |
1593 | quit:
1594 | memdig_free(memdig);
1595 | return result;
1596 | }
1597 |
--------------------------------------------------------------------------------