diff --git a/Makefile b/Makefile index e4cb626..4b004a8 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC=gcc CFLAGS=-MD -MP -ggdb -I./mujs LDFLAGS=-lm -SRCS=debugus.c linenoise.c hash.c +SRCS=debugus.c linenoise.c hash.c pmparser.c OBJS=$(patsubst %.c,%.o,$(SRCS)) DEPS=$(patsubst %.c,%.d,$(SRCS)) diff --git a/debugus.c b/debugus.c index 3e1b453..55ce61e 100644 --- a/debugus.c +++ b/debugus.c @@ -14,6 +14,7 @@ #include "linenoise.h" #include "mujs.h" #include "hash.h" +#include "pmparser.h" #define LOG_ERR(fmt, ...) fprintf(stderr, "Error: " fmt, ##__VA_ARGS__) #define LOG_INF(fmt, ...) fprintf(stdout, "Info: " fmt, ##__VA_ARGS__) @@ -33,6 +34,7 @@ typedef struct { pid_t pid; + pid_t proc_pid; uintptr_t addr; bool enabled; uint8_t data; @@ -180,12 +182,32 @@ void dbg_init_js(Dbg *dbg) #undef make_js_func } +void dbg_init_load_offset(Dbg *dbg) +{ + procmaps_iterator maps_iter = {0}; + procmaps_error_t parser_err = PROCMAPS_SUCCESS; + + parser_err = pmparser_parse(dbg->pid, &maps_iter); + if (parser_err) { + LOG_ERR("Failed to parse /proc/%d/maps (%d)\n", dbg->pid, (int)parser_err); + return; + } + + // We only need the first one + procmaps_struct *mem_region = pmparser_next(&maps_iter); + + dbg->program_load_offset = (uintptr_t)mem_region->addr_start; + + pmparser_free(&maps_iter); +} + void dbg_init(Dbg *dbg, const char *file, pid_t pid) { memset(dbg, 0, sizeof(*dbg)); dbg->file = file; dbg->pid = pid; dbg_init_js(dbg); + dbg_init_load_offset(dbg); hashtable_init(&dbg->brks, MAX_BRKS); } diff --git a/pmparser.c b/pmparser.c new file mode 100644 index 0000000..60cc973 --- /dev/null +++ b/pmparser.c @@ -0,0 +1,301 @@ +/* + @Author : ouadev + @date : December 2015 + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. No representations are made about the suitability of this +software for any purpose. It is provided "as is" without express or +implied warranty. +*/ +#include +#include +#include + +#include "pmparser.h" + +// maximum line length in a procmaps file +#define PROCMAPS_LINE_INIT_LENGTH (300) +// maximum length of the path of a maps file : /proc/[pid]/maps +#define PROCMAPS_MAPS_FILE_PATH_MAX_LENGTH 30 +// maximum token size while parsing the proc/pid/maps +#define PROCMAPS_LINE_TOKEN_MAX_LEN 100 + +/** + * pmparser_parse_line + * @description internal usage + */ +static void pmparser_parse_line(char *buf, procmaps_struct *mem_reg); + +/** + * @brief Copy into dest_ptr the string from src_ptr to the first occurence of delimiter + */ +static char *pmparser_helper_extract(char *src_ptr, const char *delimiter, char *dest_ptr); + +/** + * @brief Main function to parse process memory + * + * @param pid process ID + * @param maps_it output : the memory region iterator over the chained list, it should only be read when return is 0 + * @return procmaps_error_t outcome of the function + */ +procmaps_error_t pmparser_parse(int pid, procmaps_iterator *maps_it) +{ + char maps_path[PROCMAPS_MAPS_FILE_PATH_MAX_LENGTH]; + size_t line_len = 0; + char *line_ptr = NULL; + procmaps_struct *mem_reg = NULL; + procmaps_struct *tail_node = NULL; + procmaps_struct *head_node = NULL; + size_t node_count = 0; + + if (pid >= 0) + { + snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid); + } + else + { + sprintf(maps_path, "/proc/self/maps"); + } + + FILE *file = fopen(maps_path, "r"); + if (!file) + { + return PROCMAPS_ERROR_OPEN_MAPS_FILE; + } + + // scan maps file line by line + line_len = PROCMAPS_LINE_INIT_LENGTH; + if ((line_ptr = (char *)malloc(line_len)) == NULL) + { + fclose(file); + return PROCMAPS_ERROR_MALLOC_FAIL; + } + + while (1) + { + int read = getline(&line_ptr, &line_len, file); + if (read == -1) + { + if (!feof(file)) + { + return PROCMAPS_ERROR_READ_MAPS_FILE; + } + else + { + // end of file occured while no characters have been read + break; + } + } + + // allocate a node + mem_reg = (procmaps_struct *)malloc(sizeof(procmaps_struct)); + // fill the node + pmparser_parse_line(line_ptr, mem_reg); + mem_reg->next = NULL; + + // Attach the node + if (tail_node == NULL) + { + head_node = tail_node = mem_reg; + } + else + { + tail_node->next = mem_reg; + tail_node = mem_reg; + } + node_count++; + } + + // close file + fclose(file); + free(line_ptr); + + // set iterator + maps_it->head = maps_it->current = head_node; + maps_it->count = node_count; + + return PROCMAPS_SUCCESS; +} + +/** + * @brief move the iterator to the next memory region + * + * @param p_procmaps_it + * @return procmaps_struct* + */ +procmaps_struct *pmparser_next(procmaps_iterator *p_procmaps_it) +{ + if (p_procmaps_it->current == NULL) + return NULL; + procmaps_struct *p_current = p_procmaps_it->current; + p_procmaps_it->current = p_procmaps_it->current->next; + return p_current; +} + +/** + * @brief free the parser data + * + * @param p_procmaps_it + */ +void pmparser_free(procmaps_iterator *p_procmaps_it) +{ + procmaps_struct *cursor = p_procmaps_it->head; + procmaps_struct *next = NULL; + + if (p_procmaps_it->head == NULL) + return; + + while (cursor != NULL) + { + next = cursor->next; + free(cursor->pathname); + free(cursor); + cursor = next; + } + memset(p_procmaps_it, 0x00, sizeof(procmaps_iterator)); +} + +static char *pmparser_helper_extract(char *src_ptr, const char *delimiter, char *dest_ptr) +{ + char *p_separator = NULL; + size_t copy_len = 0; + + p_separator = strstr(src_ptr, delimiter); + copy_len = (p_separator - src_ptr); + memcpy(dest_ptr, src_ptr, copy_len); + dest_ptr[copy_len] = 0x00; + + return p_separator; +} + +static void pmparser_parse_line(char *buf, procmaps_struct *mem_reg) +{ + char token[PROCMAPS_LINE_TOKEN_MAX_LEN]; + size_t pathname_len = 0; + char *p_cursor = buf; + + // addr1 + p_cursor = pmparser_helper_extract(p_cursor, "-", token); + p_cursor++; + + sscanf(token, "%lx", (long unsigned *)&mem_reg->addr_start); + + // addr2 + p_cursor = pmparser_helper_extract(p_cursor, " ", token); + p_cursor++; + + sscanf(token, "%lx", (long unsigned *)&mem_reg->addr_end); + + // region size + mem_reg->length = (unsigned long)((char*)mem_reg->addr_end - (char*)mem_reg->addr_start); + + // perm + p_cursor = pmparser_helper_extract(p_cursor, " ", token); + p_cursor++; + + mem_reg->is_r = (token[0] == 'r'); + mem_reg->is_w = (token[1] == 'w'); + mem_reg->is_x = (token[2] == 'x'); + mem_reg->is_p = (token[3] == 'p'); + + // offset + p_cursor = pmparser_helper_extract(p_cursor, " ", token); + p_cursor++; + + sscanf(token, "%lx", &mem_reg->offset); + + // dev + p_cursor = pmparser_helper_extract(p_cursor, " ", token); + p_cursor++; + + sscanf(token, "%u:%u", &mem_reg->dev_major, &mem_reg->dev_minor); + + // inode + p_cursor = pmparser_helper_extract(p_cursor, " ", token); + p_cursor++; + + sscanf(token, "%llu", &mem_reg->inode); + + // pathname + // find the start of the pathname + while (*p_cursor == '\t' || *p_cursor == ' ') + p_cursor++; + // calculate its size + char *ptr_sz = p_cursor; + while (*ptr_sz != '\n') + { + ptr_sz++; + } + pathname_len = (ptr_sz - p_cursor); + // copy it + mem_reg->pathname = (char *)malloc(pathname_len * sizeof(char) + 1); + memcpy(mem_reg->pathname, p_cursor, pathname_len); + mem_reg->pathname[pathname_len] = 0x00; + + // Pathname decoding + if (mem_reg->pathname[0] == 0x00) + { + // empty path name + mem_reg->map_type = PROCMAPS_MAP_ANON_MMAPS; + } + else if (strncmp(mem_reg->pathname, "[stack]", 7) == 0) + { + // mapping backed by main thread stack + mem_reg->map_type = PROCMAPS_MAP_STACK; + } + else if (strncmp(mem_reg->pathname, "[stack:", 7) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_STACK_TID; + } + else if (strncmp(mem_reg->pathname, "[vdso]", 6) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_VDSO; + } + else if (strncmp(mem_reg->pathname, "[heap]", 6) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_HEAP; + } + else if (strncmp(mem_reg->pathname, "[anon:", 6) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_ANON_PRIV; + pmparser_helper_extract(mem_reg->pathname + 6, "]", token); + strncpy(mem_reg->map_anon_name, token, MAPPING_ANON_NAME_MAX_LEN); + } + else if (strncmp(mem_reg->pathname, "[anon_shmem:", 12) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_ANON_SHMEM; + pmparser_helper_extract(mem_reg->pathname + 12, "]", token); + strncpy(mem_reg->map_anon_name, token, MAPPING_ANON_NAME_MAX_LEN); + } + else if (strncmp(mem_reg->pathname, "[vvar]", 6) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_VVAR; + } + else if (strncmp(mem_reg->pathname, "[vsyscall]", 10) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_VSYSCALL; + } + else if (strncmp(mem_reg->pathname, "[", 1) == 0) + { + mem_reg->map_type = PROCMAPS_MAP_OTHER; + } + else + { + // file backed mapping then + mem_reg->map_type = PROCMAPS_MAP_FILE; + + // is the file deleted ? + // file_deleted + if (memcmp(mem_reg->pathname + strlen(mem_reg->pathname) - 9, "(deleted)", 9) == 0) + { + mem_reg->file_deleted = 1; + } + else + { + mem_reg->file_deleted = 0; + } + } +} diff --git a/pmparser.h b/pmparser.h new file mode 100644 index 0000000..3984a38 --- /dev/null +++ b/pmparser.h @@ -0,0 +1,112 @@ +/* + @Author : ouadev + @date : December 2015 + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. No representations are made about the suitability of this +software for any purpose. It is provided "as is" without express or +implied warranty. + + */ + +#ifndef H_PMPARSER +#define H_PMPARSER + +// Documentation link: https://man7.org/linux/man-pages/man5/proc_pid_maps.5.html + +// maximum length of the name of an anonymous mapping +#define MAPPING_ANON_NAME_MAX_LEN 80 + +/** + * @brief Type of a memory's region mapping. + * + */ +typedef enum +{ + PROCMAPS_MAP_FILE, + PROCMAPS_MAP_STACK, + PROCMAPS_MAP_STACK_TID, + PROCMAPS_MAP_VDSO, + PROCMAPS_MAP_VVAR, + PROCMAPS_MAP_VSYSCALL, + PROCMAPS_MAP_HEAP, + PROCMAPS_MAP_ANON_PRIV, + PROCMAPS_MAP_ANON_SHMEM, + PROCMAPS_MAP_ANON_MMAPS, + PROCMAPS_MAP_OTHER, +} procmaps_map_type; + +/** + * procmaps_struct + * @desc hold all the information about an area in the process's VM + */ +typedef struct procmaps_struct +{ + void *addr_start; //< start address of the area + void *addr_end; //< end address + size_t length; //< size of the range + short is_r; + short is_w; + short is_x; + short is_p; + size_t offset; //< offset + unsigned int dev_major; + unsigned int dev_minor; + unsigned long long inode; //< inode of the file that backs the area + char *pathname; //< the path of the file that backs the area ( dynamically allocated) + procmaps_map_type map_type; + char map_anon_name[MAPPING_ANON_NAME_MAX_LEN + 1]; //< name of the anonymous mapping in case map_type is an anon mapping + short file_deleted; //< whether the file backing the mapping was deleted + // chained list + struct procmaps_struct *next; //