From d9c44f1d201e64b49afb35c789a0ea89e2097622 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 3 Nov 2024 02:01:05 +0000 Subject: [PATCH 01/45] fix incorrect last object in updated maps (untested) --- TODO | 7 ++ doc/convert_md_to_roff.sh | 4 +- doc/md/map.md | 3 +- doc/roff/man3/liblain_error.3 | 2 +- doc/roff/man3/liblain_iface.3 | 2 +- doc/roff/man3/liblain_map.3 | 5 +- doc/roff/man3/liblain_util.3 | 2 +- src/lib/liblain.h | 5 +- src/lib/map.c | 153 +++++++++++++++++++++++++++++++++- 9 files changed, 170 insertions(+), 13 deletions(-) diff --git a/TODO b/TODO index 55e7e1b..0de8eab 100644 --- a/TODO +++ b/TODO @@ -1 +1,8 @@ +[bugs]: +- when the map is updated, vm_area's last_obj pointers need to be intelligently + updated. this is quite difficult. implement tests to check this is handled + correctly. + + +[features]: - add 3rd interface that uses process_vm_readv/process_vm_writev diff --git a/doc/convert_md_to_roff.sh b/doc/convert_md_to_roff.sh index c5554b4..53e4856 100755 --- a/doc/convert_md_to_roff.sh +++ b/doc/convert_md_to_roff.sh @@ -1,8 +1,8 @@ #!/bin/bash PROJECT="liblain" -VERSION="v1.0.2" -DATE="Oct 2024" +VERSION="v1.0.3" +DATE="Nov 2024" SRC_DIR="md" DST_DIR="roff/man3" diff --git a/doc/md/map.md b/doc/md/map.md index 90abbb7..6be0cfa 100644 --- a/doc/md/map.md +++ b/doc/md/map.md @@ -47,7 +47,8 @@ struct _ln_vm_obj { uintptr_t start_addr; uintptr_t end_addr; - cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area + cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area + cm_list last_vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area int id; bool mapped; //can be set to false with map update diff --git a/doc/roff/man3/liblain_error.3 b/doc/roff/man3/liblain_error.3 index d2d89c7..54246b7 100644 --- a/doc/roff/man3/liblain_error.3 +++ b/doc/roff/man3/liblain_error.3 @@ -1,5 +1,5 @@ .IX Title "ERROR 3 -.TH ERROR 3 "Oct 2024" "liblain v1.0.2" "error" +.TH ERROR 3 "Nov 2024" "liblain v1.0.3" "error" .\" Automatically generated by Pandoc 3.1.2 .\" .\" Define V font for inline verbatim, using C font in formats diff --git a/doc/roff/man3/liblain_iface.3 b/doc/roff/man3/liblain_iface.3 index 4e5ec23..4e487f5 100644 --- a/doc/roff/man3/liblain_iface.3 +++ b/doc/roff/man3/liblain_iface.3 @@ -1,5 +1,5 @@ .IX Title "IFACE 3 -.TH IFACE 3 "Oct 2024" "liblain v1.0.2" "iface" +.TH IFACE 3 "Nov 2024" "liblain v1.0.3" "iface" .\" Automatically generated by Pandoc 3.1.2 .\" .\" Define V font for inline verbatim, using C font in formats diff --git a/doc/roff/man3/liblain_map.3 b/doc/roff/man3/liblain_map.3 index 7c9c605..a03e988 100644 --- a/doc/roff/man3/liblain_map.3 +++ b/doc/roff/man3/liblain_map.3 @@ -1,5 +1,5 @@ .IX Title "MAP 3 -.TH MAP 3 "Oct 2024" "liblain v1.0.2" "map" +.TH MAP 3 "Nov 2024" "liblain v1.0.3" "map" .\" Automatically generated by Pandoc 3.1.2 .\" .\" Define V font for inline verbatim, using C font in formats @@ -67,7 +67,8 @@ struct _ln_vm_obj { uintptr_t start_addr; uintptr_t end_addr; - cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area + cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area + cm_list last_vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area int id; bool mapped; //can be set to false with map update diff --git a/doc/roff/man3/liblain_util.3 b/doc/roff/man3/liblain_util.3 index 470be67..90da01c 100644 --- a/doc/roff/man3/liblain_util.3 +++ b/doc/roff/man3/liblain_util.3 @@ -1,5 +1,5 @@ .IX Title "UTIL 3 -.TH UTIL 3 "Oct 2024" "liblain v1.0.2" "util" +.TH UTIL 3 "Nov 2024" "liblain v1.0.3" "util" .\" Automatically generated by Pandoc 3.1.2 .\" .\" Define V font for inline verbatim, using C font in formats diff --git a/src/lib/liblain.h b/src/lib/liblain.h index 56afdd7..13f1900 100644 --- a/src/lib/liblain.h +++ b/src/lib/liblain.h @@ -34,7 +34,7 @@ extern "C"{ #define LN_IFACE_PROCFS 1 -// +//pseudo object id #define ZERO_OBJ_ID -1 @@ -74,7 +74,8 @@ struct _ln_vm_obj { uintptr_t start_addr; uintptr_t end_addr; - cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area + cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area + cm_list last_vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area int id; bool mapped; //can be set to false with map update diff --git a/src/lib/map.c b/src/lib/map.c index 1a1bae7..3d88512 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -68,6 +68,7 @@ static void _new_vm_obj(ln_vm_obj * vm_obj, ln_vm_map * vm_map, //initialise area list cm_new_list(&vm_obj->vm_area_node_ptrs, sizeof(cm_list_node *)); + cm_new_list(&vm_obj->last_vm_area_node_ptrs, sizeof(cm_list_node *)); vm_obj->mapped = true; @@ -83,7 +84,8 @@ static void _new_vm_obj(ln_vm_obj * vm_obj, ln_vm_map * vm_map, static void _del_vm_obj(ln_vm_obj * vm_obj) { cm_del_list(&vm_obj->vm_area_node_ptrs); - + cm_del_list(&vm_obj->last_vm_area_node_ptrs); + return; } @@ -100,7 +102,7 @@ static inline void _make_zero_obj(ln_vm_obj * vm_obj) { } -//add an area to an obj (is not called +//add an area to an obj static inline int _obj_add_area(ln_vm_obj * obj, const cm_list_node * area_node) { cm_list_node * ret_node; @@ -133,6 +135,24 @@ static inline int _obj_add_area(ln_vm_obj * obj, const cm_list_node * area_node) } +static inline int _obj_add_last_area(ln_vm_obj * obj, + const cm_list_node * last_area_node) { + + cm_list_node * ret_node; + + //simply appending preserves chronological order; out of order vm_areas + //are guaranteed(?) to be removed + ret_node = cm_list_append(&obj->last_vm_area_node_ptrs, + (const cm_byte *) &last_area_node); + if (ret_node == NULL) { + ln_errno = LN_ERR_LIBCMORE; + return -1; + } + + return 0; +} + + //find if vm area's pathname belongs in object static bool _is_pathname_in_obj(const char * pathname, const ln_vm_obj * obj) { @@ -236,6 +256,118 @@ static inline int _update_obj_addr_range(ln_vm_obj * obj) { } +//called when deleting an object to move last_obj_node_ptr refernces an object back +static inline int _backtrack_unmapped_obj_last_vm_areas(cm_list_node * node) { + + int ret; + int iterations; + + ln_vm_obj * temp_obj; + + cm_list_node * last_area_node; + ln_vm_area * last_area; + + + //setup iteration + temp_obj = LN_GET_NODE_OBJ(node); + last_area_node = temp_obj->last_vm_area_node_ptrs.head; + if (last_area_node == NULL) return 0; + + last_area = LN_GET_NODE_AREA(last_area_node); + + + //for every area, backtrack last object pointer + iterations = temp_obj->last_vm_area_node_ptrs.len; + for (int i = 0; i < iterations; ++i) { + + //update ptr + last_area->last_obj_node_ptr = node->prev; + + //advance iteration (part 1) + last_area_node = last_area_node->next; + + //remove this area from the object's last_vm_area_node_ptrs list + ret = cm_list_remove(&temp_obj->last_vm_area_node_ptrs, 0); + if (ret == -1) { + ln_errno = LN_ERR_LIBCMORE; + return -1; + } + + //advance iteration (part 2) + last_area = LN_GET_NODE_AREA(last_area_node); + } + + return 0; +} + + +//called when adding a new object to move last_obj_node_ptr references forward +//if necessary +static inline int _forward_unmapped_obj_last_vm_areas(cm_list_node * node) { + + int ret; + + //declarations + cm_list_node * prev_node; + ln_vm_obj * temp_obj; + ln_vm_obj * temp_prev_obj; + + cm_list_node * last_area_node; + ln_vm_area * last_area; + + + /* + * This is not that complicated, C syntax for generics is just awful + */ + + //setup iteration + prev_node = node->prev; + + temp_obj = LN_GET_NODE_OBJ(node); + temp_prev_obj = LN_GET_NODE_OBJ(prev_node); + + last_area_node = temp_prev_obj->last_vm_area_node_ptrs.head; + if (last_area_node == NULL) return 0; + + last_area = LN_GET_NODE_AREA(last_area_node); + + + //for every area, move last object pointer forward if necessary + for (int i = 0; i < temp_prev_obj->last_vm_area_node_ptrs.len; ++i) { + + //advance iteration (part 1) + last_area_node = last_area_node->next; + + //if this area's address range comes completely after this object + if (last_area->start_addr >= temp_obj->end_addr) { + + //set this object as the new last object pointer + last_area->last_obj_node_ptr = node; + + //add this area to this object's last_vm_area_node_ptrs list + cm_list_append(&temp_obj->last_vm_area_node_ptrs, + (const cm_byte *) &last_area_node); + + //remove this area from the previous last object's last_vm_area_node_ptrs + ret = cm_list_remove(&temp_prev_obj->last_vm_area_node_ptrs, i); + if (ret == -1) { + ln_errno = LN_ERR_LIBCMORE; + return -1; + } + + i -= 1; + + } //end if + + //advance iteration (part 2) + last_area = LN_GET_NODE_AREA(last_area_node); + + } //end for + + return 0; +} + + //correctly remove unmapped obj node static inline int _unlink_unmapped_obj(cm_list_node * node, const _traverse_state * state, @@ -254,6 +386,10 @@ static inline int _unlink_unmapped_obj(cm_list_node * node, return 0; } + //correct last_obj_node_ptr of every vm_area using this object as its last object + ret = _backtrack_unmapped_obj_last_vm_areas(node); + if (ret == -1) return -1; + //unlink this node from the list of mapped vm areas ret = cm_list_unlink(&vm_map->vm_objs, state->prev_obj_index + 1); if (ret) { @@ -494,6 +630,14 @@ static cm_list_node * _map_add_obj(ln_vm_map * vm_map, _traverse_state * state, return NULL; } + /* + * with the insertion of this object, the vm_areas in the previous object's + * last_vm_area_node_ptrs list may now incorrectly treat the previous object + * as the last object, when in fact this newly inserted object should be + * their new last object. this needs to be corrected now. + */ + _forward_unmapped_obj_last_vm_areas(obj_node); + //advance state _state_inc_obj(vm_map, state); @@ -561,10 +705,13 @@ static int _map_add_area(ln_vm_map * vm_map, _traverse_state * state, } //add area to the obj ptr list + vm_obj = LN_GET_NODE_OBJ(state->prev_obj); if (use_obj) { - vm_obj = LN_GET_NODE_OBJ(state->prev_obj); ret = _obj_add_area(vm_obj, area_node); if (ret == -1) return -1; + } else { + ret = _obj_add_last_area(vm_obj, area_node); + if (ret == -1) return -1; } //increment area state From e18b08ac04ce0a2258de6d4a1bb146d8be4d41cf Mon Sep 17 00:00:00 2001 From: vykt Date: Thu, 19 Dec 2024 00:28:46 +0000 Subject: [PATCH 02/45] start testing & refactor --- Makefile | 111 +++-- README.md | 4 +- TODO | 2 + liblain.png => lain.png | Bin src/lib/Makefile | 50 ++- src/lib/debug.h | 18 + src/lib/error.c | 143 ++++--- src/lib/error.h | 5 +- src/lib/iface.c | 73 ++-- src/lib/iface.h | 22 +- src/lib/krncry.h | 79 ++++ src/lib/{lainko_iface.c => krncry_iface.c} | 146 ++++--- src/lib/krncry_iface.h | 31 ++ src/lib/lainko.h | 71 ---- src/lib/lainko_iface.h | 21 - src/lib/liblain.h | 259 ----------- src/lib/map.c | 473 +++++++++++---------- src/lib/map.h | 75 +++- src/lib/map_util.c | 169 ++++---- src/lib/map_util.h | 38 +- src/lib/memcry.h | 277 ++++++++++++ src/lib/procfs_iface.c | 79 ++-- src/lib/procfs_iface.h | 30 +- src/lib/util.c | 82 ++-- src/lib/util.h | 19 +- src/test/Makefile | 44 +- src/test/check_map.c | 255 +++++++++++ src/test/iface.c | 52 --- src/test/main.c | 98 +++-- src/test/map.c | 210 --------- src/test/suites.h | 19 + src/test/test.h | 25 -- src/test/util.c | 51 --- src/tgt/Makefile | 33 -- src/tgt/main.c | 103 ----- src/tgt/tgt | Bin 19200 -> 0 bytes 36 files changed, 1650 insertions(+), 1517 deletions(-) rename liblain.png => lain.png (100%) create mode 100644 src/lib/debug.h create mode 100644 src/lib/krncry.h rename src/lib/{lainko_iface.c => krncry_iface.c} (56%) create mode 100644 src/lib/krncry_iface.h delete mode 100644 src/lib/lainko.h delete mode 100644 src/lib/lainko_iface.h delete mode 100644 src/lib/liblain.h create mode 100644 src/lib/memcry.h create mode 100644 src/test/check_map.c delete mode 100644 src/test/iface.c delete mode 100644 src/test/map.c create mode 100644 src/test/suites.h delete mode 100644 src/test/test.h delete mode 100644 src/test/util.c delete mode 100644 src/tgt/Makefile delete mode 100644 src/tgt/main.c delete mode 100755 src/tgt/tgt diff --git a/Makefile b/Makefile index 89ce355..1f19eff 100644 --- a/Makefile +++ b/Makefile @@ -1,63 +1,96 @@ .RECIPEPREFIX:=> -#TODO [set as required] TODO + +#[set as required] +INSTALL_DIR=/usr/local/lib +INCLUDE_INSTALL_DIR=/usr/local/include +MAN_INSTALL_DIR=/usr/local/share/man +MD_INSTALL_DIR=/usr/local/share/doc/lain +LD_DIR=/etc/ld.so.conf.d + CC=gcc -CFLAGS=-ggdb -Wall -fPIC -INCLUDE=-lcmore +CFLAGS= +CFLAGS_DBG=-ggdb -O0 +WARN_OPTS=-Wall -Wextra +LDFLAGS= + +#[build constants] LIB_DIR="./src/lib" TEST_DIR="./src/test" -TGT_DIR="./src/tgt" +DOC_DIR=./doc +BUILD_DIR=${shell pwd}/build -SRC_DIR=$(shell pwd)/src -BUILD_DIR=$(shell pwd)/build -DOC_DIR=$(shell pwd)/doc -INSTALL_DIR=/usr/local -LD_DIR=/etc/ld.so.conf.d +#[installation constants] +SHARED=liblain.so +STATIC=liblain.a +HEADER=lain.h #[set build options] ifeq ($(build),debug) - CFLAGS += -O0 + CFLAGS += -O0 -ggdb -fsanitize=address -DDEBUG + CFLAGS_DBG += -DDEBUG + LDFLAGS += -static-libasan else - CFLAGS += -O2 + CFLAGS += -O3 endif -#[process targets] -all: lib test tgt +#[set static analysis options] +ifeq ($(fanalyzer),true) + CFLAGS += -fanalyzer +endif -install: -> cp ${BUILD_DIR}/lib/liblain.so ${INSTALL_DIR}/lib -> mkdir -pv ${INSTALL_DIR}/include -> cp ${SRC_DIR}/lib/liblain.h ${INSTALL_DIR}/include -> mkdir -pv ${INSTALL_DIR}/share/man -> cp -R ${DOC_DIR}/roff/* ${INSTALL_DIR}/share/man -> echo "${INSTALL_DIR}/lib" > ${LD_DIR}/90lain.conf -> ldconfig -install_doc: -> mkdir -pv ${INSTALL_DIR}/share/doc/liblain -> cp ${DOC_DIR}/md/* ${INSTALL_DIR}/share/doc/liblain +#[process targets] +test: shared +> $(MAKE) -C ${TEST_DIR} tests CC='${CC}' _CFLAGS='${CFLAGS_DBG}' \ + _WARN_OPTS='${WARN_OPTS}' \ + BUILD_DIR='${BUILD_DIR}/test' \ + LIB_BIN_DIR='${BUILD_DIR}/lib' -uninstall: -> rm -vf ${INSTALL_DIR}/lib/liblain.so ${INSTALL_DIR}/include/liblain.h \ - ${INSTALL_DIR}/share/man/man3/liblain_* ${INSTALL_DIR}/share/doc/liblain/* -> rmdir ${INSTALL_DIR}/share/doc/liblain -> rm ${LD_DIR}/90lain.conf -> ldconfig +all: shared static -tgt: -> $(MAKE) -C ${TGT_DIR} tgt CC='${CC}' BUILD_DIR='${BUILD_DIR}' +shared: +> $(MAKE) -C ${LIB_DIR} shared CC='${CC}' _CFLAGS='${CFLAGS} -fPIC' \ + _WARN_OPTS='${WARN_OPTS}' \ + _LDFLAGS='${LDFLAGS}' \ + BUILD_DIR='${BUILD_DIR}/lib' -test: lib -> $(MAKE) -C ${TEST_DIR} test CC='${CC}' BUILD_DIR='${BUILD_DIR}' +static: +> $(MAKE) -C ${LIB_DIR} static CC='${CC}' _CFLAGS='${CFLAGS}' \ + _WARN_OPTS='${WARN_OPTS}' \ + _LDFLAGS='${LDFLAGS}' \ + BUILD_DIR='${BUILD_DIR}/lib' -lib: -> $(MAKE) -C ${LIB_DIR} lib CC='${CC}' CFLAGS='${CFLAGS}' INCLUDE='${INCLUDE}' BUILD_DIR='${BUILD_DIR}' +docs: +> $(MAKE) -C ${DOC_DIR} all clean: -> $(MAKE) -C ${TEST_DIR} clean_all CC='${CC}' BUILD_DIR='${BUILD_DIR}' -> $(MAKE) -C ${LIB_DIR} clean_all CC='${CC}' CFLAGS='${CFLAGS}' BUILD_DIR='${BUILD_DIR}' -> ${MAKE} -C ${TGT_DIR} clean_all CC='${CC}' BUILD_DIR='${BUILD_DIR}' +> $(MAKE) -C ${TEST_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/test' +> $(MAKE) -C ${LIB_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/lib' + +install: +> mkdir -pv ${INSTALL_DIR} +> cp -v ${BUILD_DIR}/lib/{${SHARED},${STATIC}} ${INSTALL_DIR} +> mkdir -pv ${INCLUDE_INSTALL_DIR} +> cp -v ${LIB_DIR}/${HEADER} ${INCLUDE_INSTALL_DIR} +> mkdir -pv ${MAN_INSTALL_DIR} +> cp -Rv ${DOC_DIR}/groff/man/* ${MAN_INSTALL_DIR} +> echo "${INSTALL_DIR}" > ${LD_DIR}/90cmore.conf +> ldconfig + +install_docs: +> mkdir -pv ${MD_INSTALL_DIR} +> cp -v ${DOC_DIR}/md/* ${MD_INSTALL_DIR} + +uninstall: +> -rm -v ${INSTALL_DIR}/{${SHARED},${STATIC}} +> -rm -v ${INCLUDE_INSTALL_DIR}/${HEADER} +> -rm -v ${MAN_INSTALL_DIR}/man7/cmore_*.7 +> -rm -v ${MD_INSTALL_DIR}/*.md +> -rmdir ${MD_INSTALL_DIR} +> -rm ${LD_DIR}/90cmore.conf +> ldconfig diff --git a/README.md b/README.md index 8ee0b63..44cd3ba 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# liblain +# Lain

- +

### ABOUT: diff --git a/TODO b/TODO index 0de8eab..2ad6197 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,6 @@ [bugs]: +-make mc_vm_area link to mc_vm_obj list nodes, instead of objects directly + - when the map is updated, vm_area's last_obj pointers need to be intelligently updated. this is quite difficult. implement tests to check this is handled correctly. diff --git a/liblain.png b/lain.png similarity index 100% rename from liblain.png rename to lain.png diff --git a/src/lib/Makefile b/src/lib/Makefile index cd56e16..b89aabc 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -2,34 +2,42 @@ # This makefile takes the following variables: # -# CC - Compiler. -# CFLAGS - Compiler flags. -# INCLUDE - Shared objects to link. -# BUILD_DIR - Base build directory. +# CC - Compiler. +# BUILD_DIR - Library build directory. +# +# _LDFLAGS - Linker flags. +# _CFLAGS - Compiler flags. +# _WARN_OPTS - Compiler warnings. + -SOURCES_LIB=error.c iface.c lainko_iface.c map.c map_util.c procfs_iface.c util.c -HEADERS_LIB=error.h iface.h lainko.h lainko_iface.h liblain.h map.h map_util.h procfs_iface.h util.h -OBJECTS_LIB=${SOURCES_LIB:.c=.o} +CFLAGS=${_CFLAGS} +WARN_OPTS=${_WARN_OPTS} -Wno-unused-parameter +LDFLAGS=${_LDFLAGS} -LIB=liblain.so +SOURCES_LIB=error.c iface.c krncry_iface.c \ + map.c map_util.c procfs_iface.c util.c +OBJECTS_LIB=${SOURCES_LIB:%.c=${BUILD_DIR}/%.o} +SHARED=libcmore.so +STATIC=libcmore.a -WARN_OPTS := -Wno-unused-but-set-variable -Wno-stringop-truncation -Wno-maybe-uninitialized -lib: ${LIB} -> mkdir -p ${BUILD_DIR}/lib -> mv ${LIB} ${BUILD_DIR}/lib +shared: ${SHARED} +> mkdir -p ${BUILD_DIR} +> mv ${SHARED} ${BUILD_DIR} -${LIB}: ${OBJECTS_LIB} -> ${CC} ${CFLAGS} -shared -o ${LIB} ${OBJECTS_LIB} ${HEADERS_LIB} ${WARN_OPTS} ${INCLUDE} +static: ${STATIC} +> mkdir -p ${BUILD_DIR} +> mv ${STATIC} ${BUILD_DIR} -${OBJECTS_LIB}: ${SOURCES_LIB} ${HEADERS_LIB} -> ${CC} ${CFLAGS} -c ${SOURCES_LIB} ${WARN_OPTS} ${INCLUDE} +${SHARED}: ${OBJECTS_LIB} +> ${CC} ${CFLAGS} -shared -o $@ $^ ${LDFLAGS} -clean_all: clean_src clean_build +${STATIC}: ${OBJECTS_LIB} +> ar rcs $@ $^ -clean_src: -> -rm -f *.o +${BUILD_DIR}/%.o: %.c +> ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ -clean_build: -> -rm ${BUILD_DIR}/lib/${LIB} +clean: +> -rm -v ${BUILD_DIR}/{*.o,${SHARED},${STATIC}} diff --git a/src/lib/debug.h b/src/lib/debug.h new file mode 100644 index 0000000..7d1e2c1 --- /dev/null +++ b/src/lib/debug.h @@ -0,0 +1,18 @@ +#ifndef DEBUG_H +#define DEBUG_H + + +/* + * Do not define internal functions as static in debug builds + */ + +#ifdef DEBUG +#define DBG_STATIC +#define DBG_INLINE +#else +#define DBG_STATIC static +#define DBG_INLINE inline +#endif + + +#endif diff --git a/src/lib/error.c b/src/lib/error.c index b235824..bab9355 100644 --- a/src/lib/error.c +++ b/src/lib/error.c @@ -1,82 +1,84 @@ +//standard library #include -#include "liblain.h" +//local headers +#include "memcry.h" #include "error.h" -__thread int ln_errno; +__thread int mc_errno; -void ln_perror() { +void mc_perror(const char * prefix) { - switch(ln_errno) { + switch(mc_errno) { // 1XX - user errors - case LN_ERR_PROC_MEM: - fprintf(stderr, LN_ERR_PROC_MEM_MSG); + case MC_ERR_PROC_MEM: + fprintf(stderr, "%s: %s", prefix, MC_ERR_PROC_MEM_MSG); break; - case LN_ERR_PROC_MAP: - fprintf(stderr, LN_ERR_PROC_MAP_MSG); + case MC_ERR_PROC_MAP: + fprintf(stderr, "%s: %s", prefix, MC_ERR_PROC_MAP_MSG); break; - case LN_ERR_SEEK_ADDR: - fprintf(stderr, LN_ERR_SEEK_ADDR_MSG); + case MC_ERR_SEEK_ADDR: + fprintf(stderr, "%s: %s", prefix, MC_ERR_SEEK_ADDR_MSG); break; // 2XX - internal errors - case LN_ERR_INTERNAL_INDEX: - fprintf(stderr, LN_ERR_INTERNAL_INDEX_MSG); + case MC_ERR_INTERNAL_INDEX: + fprintf(stderr, "%s: %s", prefix, MC_ERR_INTERNAL_INDEX_MSG); break; - case LN_ERR_UNEXPECTED_NULL: - fprintf(stderr, LN_ERR_UNEXPECTED_NULL_MSG); + case MC_ERR_UNEXPECTED_NULL: + fprintf(stderr, "%s: %s", prefix, MC_ERR_UNEXPECTED_NULL_MSG); break; - case LN_ERR_LIBCMORE: - fprintf(stderr, LN_ERR_LIBCMORE_MSG); + case MC_ERR_LIBCMORE: + fprintf(stderr, "%s: %s", prefix, MC_ERR_LIBCMORE_MSG); break; - case LN_ERR_READ_WRITE: - fprintf(stderr, LN_ERR_READ_WRITE_MSG); + case MC_ERR_READ_WRITE: + fprintf(stderr, "%s: %s", prefix, MC_ERR_READ_WRITE_MSG); break; - case LN_ERR_MEMU_TARGET: - fprintf(stderr, LN_ERR_MEMU_TARGET_MSG); + case MC_ERR_MEMU_TARGET: + fprintf(stderr, "%s: %s", prefix, MC_ERR_MEMU_TARGET_MSG); break; - case LN_ERR_MEMU_MAP_SZ: - fprintf(stderr, LN_ERR_MEMU_MAP_SZ_MSG); + case MC_ERR_MEMU_MAP_SZ: + fprintf(stderr, "%s: %s", prefix, MC_ERR_MEMU_MAP_SZ_MSG); break; - case LN_ERR_MEMU_MAP_GET: - fprintf(stderr, LN_ERR_MEMU_MAP_GET_MSG); + case MC_ERR_MEMU_MAP_GET: + fprintf(stderr, "%s: %s", prefix, MC_ERR_MEMU_MAP_GET_MSG); break; - case LN_ERR_PROC_STATUS: - fprintf(stderr, LN_ERR_PROC_STATUS_MSG); + case MC_ERR_PROC_STATUS: + fprintf(stderr, "%s: %s", prefix, MC_ERR_PROC_STATUS_MSG); break; - case LN_ERR_PROC_NAV: - fprintf(stderr, LN_ERR_PROC_NAV_MSG); + case MC_ERR_PROC_NAV: + fprintf(stderr, "%s: %s", prefix, MC_ERR_PROC_NAV_MSG); break; // 3XX - environmental errors - case LN_ERR_MEM: - fprintf(stderr, LN_ERR_MEM_MSG); + case MC_ERR_MEM: + fprintf(stderr, "%s: %s", prefix, MC_ERR_MEM_MSG); break; - case LN_ERR_PAGESIZE: - fprintf(stderr, LN_ERR_PAGESIZE_MSG); + case MC_ERR_PAGESIZE: + fprintf(stderr, "%s: %s", prefix, MC_ERR_PAGESIZE_MSG); break; - case LN_ERR_LAINKO_MAJOR: - fprintf(stderr, LN_ERR_LAINKO_MAJOR_MSG); + case MC_ERR_KRNCRY_MAJOR: + fprintf(stderr, "%s: %s", prefix, MC_ERR_KRNCRY_MAJOR_MSG); break; - case LN_ERR_MEMU_OPEN: - fprintf(stderr, LN_ERR_MEMU_OPEN_MSG); + case MC_ERR_MEMU_OPEN: + fprintf(stderr, "%s: %s", prefix, MC_ERR_MEMU_OPEN_MSG); break; default: @@ -88,60 +90,61 @@ void ln_perror() { } -const char * ln_strerror(const int ln_errnum) { - switch (ln_errnum) { +const char * mc_strerror(const int mc_errnum) { + + switch (mc_errnum) { // 1XX - user errors - case LN_ERR_PROC_MEM: - return LN_ERR_PROC_MEM_MSG; + case MC_ERR_PROC_MEM: + return MC_ERR_PROC_MEM_MSG; - case LN_ERR_PROC_MAP: - return LN_ERR_PROC_MAP_MSG; + case MC_ERR_PROC_MAP: + return MC_ERR_PROC_MAP_MSG; - case LN_ERR_SEEK_ADDR: - return LN_ERR_SEEK_ADDR_MSG; + case MC_ERR_SEEK_ADDR: + return MC_ERR_SEEK_ADDR_MSG; // 2xx - internal errors - case LN_ERR_INTERNAL_INDEX: - return LN_ERR_INTERNAL_INDEX_MSG; + case MC_ERR_INTERNAL_INDEX: + return MC_ERR_INTERNAL_INDEX_MSG; - case LN_ERR_UNEXPECTED_NULL: - return LN_ERR_UNEXPECTED_NULL_MSG; + case MC_ERR_UNEXPECTED_NULL: + return MC_ERR_UNEXPECTED_NULL_MSG; - case LN_ERR_LIBCMORE: - return LN_ERR_LIBCMORE_MSG; + case MC_ERR_LIBCMORE: + return MC_ERR_LIBCMORE_MSG; - case LN_ERR_READ_WRITE: - return LN_ERR_READ_WRITE_MSG; + case MC_ERR_READ_WRITE: + return MC_ERR_READ_WRITE_MSG; - case LN_ERR_MEMU_TARGET: - return LN_ERR_MEMU_TARGET_MSG; + case MC_ERR_MEMU_TARGET: + return MC_ERR_MEMU_TARGET_MSG; - case LN_ERR_MEMU_MAP_SZ: - return LN_ERR_MEMU_MAP_SZ_MSG; + case MC_ERR_MEMU_MAP_SZ: + return MC_ERR_MEMU_MAP_SZ_MSG; - case LN_ERR_MEMU_MAP_GET: - return LN_ERR_MEMU_MAP_GET_MSG; + case MC_ERR_MEMU_MAP_GET: + return MC_ERR_MEMU_MAP_GET_MSG; - case LN_ERR_PROC_STATUS: - return LN_ERR_PROC_STATUS_MSG; + case MC_ERR_PROC_STATUS: + return MC_ERR_PROC_STATUS_MSG; - case LN_ERR_PROC_NAV: - return LN_ERR_PROC_NAV_MSG; + case MC_ERR_PROC_NAV: + return MC_ERR_PROC_NAV_MSG; // 3XX - environmental errors - case LN_ERR_MEM: - return LN_ERR_MEM_MSG; + case MC_ERR_MEM: + return MC_ERR_MEM_MSG; - case LN_ERR_PAGESIZE: - return LN_ERR_PAGESIZE_MSG; + case MC_ERR_PAGESIZE: + return MC_ERR_PAGESIZE_MSG; - case LN_ERR_LAINKO_MAJOR: - return LN_ERR_LAINKO_MAJOR_MSG; + case MC_ERR_KRNCRY_MAJOR: + return MC_ERR_KRNCRY_MAJOR_MSG; - case LN_ERR_MEMU_OPEN: - return LN_ERR_MEMU_OPEN_MSG; + case MC_ERR_MEMU_OPEN: + return MC_ERR_MEMU_OPEN_MSG; default: return "Undefined error code.\n"; diff --git a/src/lib/error.h b/src/lib/error.h index 0c41a8c..b1a9aa2 100644 --- a/src/lib/error.h +++ b/src/lib/error.h @@ -3,8 +3,7 @@ //external -void ln_perror(); -const char * ln_strerror(const int ln_errnum); - +void mc_perror(const char * prefix); +const char * mc_strerror(const int mc_errnum); #endif diff --git a/src/lib/iface.c b/src/lib/iface.c index d3148cc..c3a5cc6 100644 --- a/src/lib/iface.c +++ b/src/lib/iface.c @@ -1,49 +1,64 @@ +//standard library #include #include -#include +//external libraries +#include +//local headers #include "iface.h" -#include "liblain.h" - +#include "memcry.h" #include "procfs_iface.h" -#include "lainko_iface.h" +#include "krncry_iface.h" +#include "debug.h" + + +/* + * --- [INTERNAL] --- + */ -static inline void set_procfs_session(ln_session * session) { +DBG_STATIC DBG_INLINE +void _set_procfs_session(mc_session * session) { - session->iface.open = _procfs_open; - session->iface.close = _procfs_close; - session->iface.update_map = _procfs_update_map; - session->iface.read = _procfs_read; - session->iface.write = _procfs_write; + session->iface.open = procfs_open; + session->iface.close = procfs_close; + session->iface.update_map = procfs_update_map; + session->iface.read = procfs_read; + session->iface.write = procfs_write; return; } -static inline void set_lainko_session(ln_session * session) { - session->iface.open = _lainko_open; - session->iface.close = _lainko_close; - session->iface.update_map = _lainko_update_map; - session->iface.read = _lainko_read; - session->iface.write = _lainko_write; +DBG_STATIC DBG_INLINE +void _set_krncry_session(mc_session * session) { + + session->iface.open = krncry_open; + session->iface.close = krncry_close; + session->iface.update_map = krncry_update_map; + session->iface.read = krncry_read; + session->iface.write = krncry_write; return; } -//open session -int ln_open(ln_session * session, const int iface, const pid_t pid) { + +/* + * --- [EXTERNAL] --- + */ + +int mc_open(mc_session * session, const int iface, const pid_t pid) { int ret; //if requesting procfs interface - if (iface == LN_IFACE_PROCFS) { - set_procfs_session(session); + if (iface == MC_IFACE_PROCFS) { + _set_procfs_session(session); } else { - set_lainko_session(session); + _set_krncry_session(session); } ret = session->iface.open(session, pid); @@ -53,8 +68,8 @@ int ln_open(ln_session * session, const int iface, const pid_t pid) { } -//close session -int ln_close(ln_session * session) { + +int mc_close(mc_session * session) { int ret; @@ -65,8 +80,8 @@ int ln_close(ln_session * session) { } -//update a map -int ln_update_map(const ln_session * session, ln_vm_map * vm_map) { + +int mc_update_map(const mc_session * session, mc_vm_map * vm_map) { int ret; @@ -77,8 +92,8 @@ int ln_update_map(const ln_session * session, ln_vm_map * vm_map) { } -//read memory -int ln_read(const ln_session * session, const uintptr_t addr, + +int mc_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { int ret; @@ -90,8 +105,8 @@ int ln_read(const ln_session * session, const uintptr_t addr, } -//write memory -int ln_write(const ln_session * session, uintptr_t addr, + +int mc_write(const mc_session * session, uintptr_t addr, const cm_byte * buf, const size_t buf_sz) { int ret; diff --git a/src/lib/iface.h b/src/lib/iface.h index 76d5ccd..1ac1465 100644 --- a/src/lib/iface.h +++ b/src/lib/iface.h @@ -1,19 +1,27 @@ #ifndef IFACE_H #define IFACE_H +//standard library #include -#include "liblain.h" +//local headers +#include "memcry.h" + + +#ifdef DEBUG +//internal +void _set_procfs_session(mc_session * session); +void _set_krncry_session(mc_session * session); +#endif //external -int ln_open(ln_session * session, const int iface, const pid_t pid); -int ln_close(ln_session * session); -int ln_update_map(const ln_session * session, ln_vm_map * vm_map); -int ln_read(const ln_session * session, const uintptr_t addr, +int mc_open(mc_session * session, const int iface, const pid_t pid); +int mc_close(mc_session * session); +int mc_update_map(const mc_session * session, mc_vm_map * vm_map); +int mc_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz); -int ln_write(const ln_session * session, const uintptr_t addr, +int mc_write(const mc_session * session, const uintptr_t addr, const cm_byte * buf, const size_t buf_sz); - #endif diff --git a/src/lib/krncry.h b/src/lib/krncry.h new file mode 100644 index 0000000..c8b6eb3 --- /dev/null +++ b/src/lib/krncry.h @@ -0,0 +1,79 @@ +#ifndef KRNCRY_H +#define KRNCRY_H + +#ifdef __cplusplus +extern "C" { +#endif + +//system headers +#include + + + +/* + * This header comes from the krncry project. + */ + +//krncry ioctl call numbers +#define KRNCRY_IOCTL_OPEN_TGT 0 +#define KRNCRY_IOCTL_RELEASE_TGT 1 +#define KRNCRY_IOCTL_GET_MAP 2 +#define KRNCRY_IOCTL_GET_MAP_SZ 3 + +//templates for ioctl calls +#define KRNCRY_TEMPLATE_OPEN_TGT 0x40080000 +#define KRNCRY_TEMPLATE_RELEASE_TGT 0x00000001 +#define KRNCRY_TEMPLATE_GET_MAP 0xc0080002 +#define KRNCRY_TEMPLATE_GET_MAP_SZ 0x40080003 + +//template macro +#define KRNCRY_APPLY_TEMPLATE(major, krncry_template) (((major << 8) & 0x0000ff00) | krncry_template) + + +//vma protection - taken from linux/pgtable_types.h +typedef unsigned long krncry_pgprot_t; + +//permission bitmask +#define VM_PROT_MASK 0x0000000F + +//specific permission bitmasks +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 +#define VM_SHARED 0x00000008 + + + +/* + * --- [DATA TYPES] --- + */ + +// [byte] +typedef unsigned char kc_byte; + + + +// [ioctl argument] +struct ioctl_arg { + kc_byte * u_buf; + int target_pid; +}; + + +// [map entry] +struct vm_entry { + + unsigned long vm_start; + unsigned long vm_end; + + unsigned long file_off; + krncry_pgprot_t prot; + char file_path[PATH_MAX]; +}; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/lainko_iface.c b/src/lib/krncry_iface.c similarity index 56% rename from src/lib/lainko_iface.c rename to src/lib/krncry_iface.c index 846c609..f2b246e 100644 --- a/src/lib/lainko_iface.c +++ b/src/lib/krncry_iface.c @@ -1,46 +1,52 @@ +//standard library #include #include #include +//system headers #include #include - #include #include - #include -#include +//external libraries +#include -#include "lainko_iface.h" -#include "liblain.h" -#include "lainko.h" +//local headers +#include "krncry_iface.h" +#include "memcry.h" +#include "krncry.h" #include "map.h" +#include "debug.h" -// --- LAINKO INTERFACE INTERNALS -//read lainko's major number -static inline char _get_major() { +/* + * --- [INTERNAL] --- + */ + +//read krncry's major number +DBG_STATIC DBG_INLINE +char _krncry_iface_get_major() { int fd; size_t read_bytes; - char read_buf[8]; + char major, read_buf[8]; - char major; //open major attribute - fd = open(LAINKO_MAJOR_PATH, O_RDONLY); + fd = open(KRNCRY_MAJOR_PATH, O_RDONLY); if (fd == -1) { - ln_errno = LN_ERR_LAINKO_MAJOR; + mc_errno = MC_ERR_KRNCRY_MAJOR; return -1; } //read string representation into buffer read_bytes = read(fd, &read_buf, 8); if (read_bytes == -1) { - ln_errno = LN_ERR_LAINKO_MAJOR; + mc_errno = MC_ERR_KRNCRY_MAJOR; return -1; } @@ -52,46 +58,49 @@ static inline char _get_major() { } -// --- CALLED BY VIRTUAL INTERFACE -//locate and open the lainmemu device, and then open the target -int _lainko_open(ln_session * session, const pid_t pid) { +/* + * --- [INTERFACE] --- + */ + +int krncry_open(mc_session * session, const pid_t pid) { int ret; - char memu_path[PATH_MAX]; + char device_path[PATH_MAX]; uint32_t ioctl_call; struct ioctl_arg arg; - //get page size + + //get page size to determine maximum read/write size session->page_size = sysconf(_SC_PAGESIZE); if (session->page_size < 0) { - ln_errno = LN_ERR_PAGESIZE; + mc_errno = MC_ERR_PAGESIZE; return -1; } - //get major of the module, -1 if unloaded - session->major = _get_major(); + //get major of the module, returns -1 if unloaded + session->major = _krncry_iface_get_major(); if (session->major == -1) return -1; - //build lainmemu device path - snprintf(memu_path, PATH_MAX, "/dev/char/%d:%d", - (unsigned char) session->major, LAINMEMU_MINOR); + //build krncry device path + snprintf(device_path, PATH_MAX, "/dev/char/%d:%d", + (unsigned char) session->major, KRNCRY_MINOR); - //open the lainmemu device - session->fd_dev_memu = open(memu_path, O_RDWR); - if (session->fd_dev_memu == -1) { - ln_errno = LN_ERR_MEMU_OPEN; + //open the krncry device + session->fd_dev_krncry = open(device_path, O_RDWR); + if (session->fd_dev_krncry == -1) { + mc_errno = MC_ERR_MEMU_OPEN; return -1; } - //call ioctl to set target + //call ioctl to set the target process arg.target_pid = pid; - ioctl_call = LAINKO_APPLY_TEMPLATE((char) session->major, - LAINMEMU_TEMPLATE_OPEN_TGT); - ret = ioctl(session->fd_dev_memu, ioctl_call, &arg); + ioctl_call = KRNCRY_APPLY_TEMPLATE((char) session->major, + KRNCRY_TEMPLATE_OPEN_TGT); + ret = ioctl(session->fd_dev_krncry, ioctl_call, &arg); if (ret) { - close(session->fd_dev_memu); - ln_errno = LN_ERR_MEMU_TARGET; + close(session->fd_dev_krncry); + mc_errno = MC_ERR_MEMU_TARGET; return -1; } @@ -99,27 +108,28 @@ int _lainko_open(ln_session * session, const pid_t pid) { } -//close the target -int _lainko_close(ln_session * session) { + +int krncry_close(mc_session * session) { int ret; uint32_t ioctl_call; struct ioctl_arg arg; //not used + //call ioctl to release target - ioctl_call = LAINKO_APPLY_TEMPLATE((char) session->major, - LAINMEMU_TEMPLATE_RELEASE_TGT); - ret = ioctl(session->fd_dev_memu, ioctl_call, &arg); + ioctl_call = KRNCRY_APPLY_TEMPLATE((char) session->major, + KRNCRY_TEMPLATE_RELEASE_TGT); + ret = ioctl(session->fd_dev_krncry, ioctl_call, &arg); //close device - close(session->fd_dev_memu); + close(session->fd_dev_krncry); return 0; } -//update the map -int _lainko_update_map(const ln_session * session, ln_vm_map * vm_map) { + +int krncry_update_map(const mc_session * session, mc_vm_map * vm_map) { int ret, count; uint32_t ioctl_call; @@ -130,11 +140,11 @@ int _lainko_update_map(const ln_session * session, ln_vm_map * vm_map) { //call ioctl to get the map size - ioctl_call = LAINKO_APPLY_TEMPLATE((char) session->major, - LAINMEMU_TEMPLATE_GET_MAP_SZ); - count = ioctl(session->fd_dev_memu, ioctl_call, &arg); + ioctl_call = KRNCRY_APPLY_TEMPLATE((char) session->major, + KRNCRY_TEMPLATE_GET_MAP_SZ); + count = ioctl(session->fd_dev_krncry, ioctl_call, &arg); if (count <= 0) { - ln_errno = LN_ERR_MEMU_MAP_SZ; + mc_errno = MC_ERR_MEMU_MAP_SZ; return -1; } @@ -143,18 +153,18 @@ int _lainko_update_map(const ln_session * session, ln_vm_map * vm_map) { arg.u_buf = mmap(NULL, u_buf_sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (arg.u_buf == MAP_FAILED) { - ln_errno = LN_ERR_MEM; + mc_errno = MC_ERR_MEM; return -1; } //get the map - ioctl_call = LAINKO_APPLY_TEMPLATE((char) session->major, - LAINMEMU_TEMPLATE_GET_MAP); + ioctl_call = KRNCRY_APPLY_TEMPLATE((char) session->major, + KRNCRY_TEMPLATE_GET_MAP); - count = ioctl(session->fd_dev_memu, ioctl_call, &arg); + count = ioctl(session->fd_dev_krncry, ioctl_call, &arg); if (count <= 0) { munmap(arg.u_buf, u_buf_sz); - ln_errno = LN_ERR_MEMU_MAP_GET; + mc_errno = MC_ERR_MEMU_MAP_GET; return -1; } @@ -180,19 +190,21 @@ int _lainko_update_map(const ln_session * session, ln_vm_map * vm_map) { } -//read memory -int _lainko_read(const ln_session * session, const uintptr_t addr, + +int krncry_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { off_t off_ret; ssize_t read_bytes, read_done, read_left; - + + + //initialise read state read_done = read_left = 0; //seek to address - off_ret = lseek(session->fd_dev_memu, (off_t) addr, SEEK_SET); + off_ret = lseek(session->fd_dev_krncry, (off_t) addr, SEEK_SET); if (off_ret == -1) { - ln_errno = LN_ERR_SEEK_ADDR; + mc_errno = MC_ERR_SEEK_ADDR; return -1; } @@ -203,12 +215,12 @@ int _lainko_read(const ln_session * session, const uintptr_t addr, read_left = buf_sz - read_done; //read into buffer - read_bytes = read(session->fd_dev_memu, buf + read_done, + read_bytes = read(session->fd_dev_krncry, buf + read_done, read_left > session->page_size ? session->page_size : read_left); //if error or EOF before reading len bytes if (read_bytes == -1 || (read_bytes == 0 && read_done < buf_sz)) { - ln_errno = LN_ERR_READ_WRITE; + mc_errno = MC_ERR_READ_WRITE; return -1; } read_done += read_bytes; @@ -219,19 +231,21 @@ int _lainko_read(const ln_session * session, const uintptr_t addr, } -//write memory -int _lainko_write(const ln_session * session, const uintptr_t addr, + +int krncry_write(const mc_session * session, const uintptr_t addr, const cm_byte * buf, const size_t buf_sz) { off_t off_ret; ssize_t write_bytes, write_done, write_left; - + + + //initialise write state write_done = write_left = 0; //seek to address - off_ret = lseek(session->fd_dev_memu, (off_t) addr, SEEK_SET); + off_ret = lseek(session->fd_dev_krncry, (off_t) addr, SEEK_SET); if (off_ret == -1) { - ln_errno = LN_ERR_SEEK_ADDR; + mc_errno = MC_ERR_SEEK_ADDR; return -1; } @@ -242,12 +256,12 @@ int _lainko_write(const ln_session * session, const uintptr_t addr, write_left = buf_sz - write_done; //write into buffer - write_bytes = write(session->fd_dev_memu, buf + write_done, + write_bytes = write(session->fd_dev_krncry, buf + write_done, write_left > session->page_size ? session->page_size : write_left); //if error or EOF before writing len bytes if (write_bytes == -1 || (write_bytes == 0 && write_done < buf_sz)) { - ln_errno = LN_ERR_READ_WRITE; + mc_errno = MC_ERR_READ_WRITE; return -1; } write_done += write_bytes; diff --git a/src/lib/krncry_iface.h b/src/lib/krncry_iface.h new file mode 100644 index 0000000..9e92590 --- /dev/null +++ b/src/lib/krncry_iface.h @@ -0,0 +1,31 @@ +#ifndef KRNCRY_IFACE_H +#define KRNCRY_IFACE_H + +//external libraries +#include + +//local headers +#include "memcry.h" +#include "debug.h" + + +#define KRNCRY_MAJOR_PATH "/sys/class/krncry/krncry_major" +#define KRNCRY_MINOR 0 + + +#ifdef DEBUG +//internal +char _krncry_iface_get_major(); +#endif + + +//interface +int krncry_open(mc_session * session, const pid_t pid); +int krncry_close(mc_session * session); +int krncry_update_map(const mc_session * session, mc_vm_map * vm_map); +int krncry_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +int krncry_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); + +#endif diff --git a/src/lib/lainko.h b/src/lib/lainko.h deleted file mode 100644 index e05ad85..0000000 --- a/src/lib/lainko.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef LAINKO_H -#define LAINKO_H - -#ifdef __cplusplus -extern "C" { -#endif - - -#include - - -/* - * This header is shared with userspace. - */ - -//lainmemu ioctl call numbers -#define LAINMEMU_IOCTL_OPEN_TGT 0 -#define LAINMEMU_IOCTL_RELEASE_TGT 1 -#define LAINMEMU_IOCTL_GET_MAP 2 -#define LAINMEMU_IOCTL_GET_MAP_SZ 3 - - -//templates for ioctl calls -#define LAINMEMU_TEMPLATE_OPEN_TGT 0x40080000 -#define LAINMEMU_TEMPLATE_RELEASE_TGT 0x00000001 -#define LAINMEMU_TEMPLATE_GET_MAP 0xc0080002 -#define LAINMEMU_TEMPLATE_GET_MAP_SZ 0x40080003 - -//template macro -#define LAINKO_APPLY_TEMPLATE(major, lainmemu_template) (((major << 8) & 0x0000ff00) | lainmemu_template) - - -//vma protection - taken from linux/pgtable_types.h -typedef unsigned long lainko_pgprot_t; - -#define VM_READ 0x00000001 -#define VM_WRITE 0x00000002 -#define VM_EXEC 0x00000004 -#define VM_SHARED 0x00000008 - -#define VM_PROT_MASK 0x0000000F - - -typedef char lainko_byte; -typedef unsigned char lainko_ubyte; - - - -//ioctl argument -struct ioctl_arg { - lainko_byte * u_buf; - int target_pid; -}; - - -//map entry -struct vm_entry { - - unsigned long vm_start; - unsigned long vm_end; - - unsigned long file_off; - lainko_pgprot_t prot; - char file_path[PATH_MAX]; -}; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/lib/lainko_iface.h b/src/lib/lainko_iface.h deleted file mode 100644 index 386b34e..0000000 --- a/src/lib/lainko_iface.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef LAINKO_IFACE_H -#define LAINKO_IFACE_H - -#include - -#include "liblain.h" - - -#define LAINKO_MAJOR_PATH "/sys/class/lainko/lainmemu_major" -#define LAINMEMU_MINOR 0 - -//internal -int _lainko_open(ln_session * session, const pid_t pid); -int _lainko_close(ln_session * session); -int _lainko_update_map(const ln_session * session, ln_vm_map * vm_map); -int _lainko_read(const ln_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -int _lainko_write(const ln_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); - -#endif diff --git a/src/lib/liblain.h b/src/lib/liblain.h deleted file mode 100644 index 13f1900..0000000 --- a/src/lib/liblain.h +++ /dev/null @@ -1,259 +0,0 @@ -#ifndef LIBMOD_H -#define LIBMOD_H - -#ifdef __cplusplus -extern "C"{ -#endif - -#include -#include -#include - -#include - -#include - -#include - - -//these macros take a cm_list_node pointer -#define LN_GET_NODE_AREA(node) ((ln_vm_area *) (node->data)) -#define LN_GET_NODE_OBJ(node) ((ln_vm_obj *) (node->data)) -#define LN_GET_NODE_PTR(node) *((cm_list_node **) (node->data)) - - -//ln_vm_area.access bitmasks -#define LN_ACCESS_READ 0x01 -#define LN_ACCESS_WRITE 0x02 -#define LN_ACCESS_EXEC 0x04 -#define LN_ACCESS_SHARED 0x08 - - -//interface types -#define LN_IFACE_LAINKO 0 -#define LN_IFACE_PROCFS 1 - - -//pseudo object id -#define ZERO_OBJ_ID -1 - - -/* - * --- [DATA TYPES] --- - */ - - -struct _ln_vm_obj; - -// --- [memory area] -typedef struct { - - char * pathname; - char * basename; - - uintptr_t start_addr; - uintptr_t end_addr; - - cm_byte access; - - cm_list_node * obj_node_ptr; //STORES: own vm_obj * - cm_list_node * last_obj_node_ptr; //STORES: last encountered vm_obj * - - int id; - bool mapped; //can be set to false with map update - -} ln_vm_area; - - -// --- ['backing' object] -struct _ln_vm_obj { - - char pathname[PATH_MAX]; - char basename[NAME_MAX]; - - uintptr_t start_addr; - uintptr_t end_addr; - - cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area - cm_list last_vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area - - int id; - bool mapped; //can be set to false with map update -}; -typedef struct _ln_vm_obj ln_vm_obj; - - -// --- [memory map] -typedef struct { - - //up to date entries - cm_list vm_areas; //STORES: ln_vm_area - cm_list vm_objs; //STORES: ln_vm_obj - - //unmapped entries (storage for future deallocation) - cm_list vm_areas_unmapped; //STORES: cm_list_node * of ln_vm_area - cm_list vm_objs_unmapped; //STORES: cm_list_node * of ln_vm_obj - - // [internal] - int next_id_area; - int next_id_obj; - -} ln_vm_map; - - -// --- [interface / session] -struct _ln_session; - -typedef struct { - - int (*open)(struct _ln_session *, int); - int (*close)(struct _ln_session *); - int (*update_map)(const struct _ln_session *, ln_vm_map *); - int (*read)(const struct _ln_session *, const uintptr_t, - cm_byte *, const size_t); - int (*write)(const struct _ln_session *, const uintptr_t, - const cm_byte *, const size_t); - -} ln_iface; - - -struct _ln_session { - - union { - struct { - int fd_mem; - pid_t pid; - }; //procfs_data - struct { - char major; - int fd_dev_memu; - }; //lainko_data - }; - - long page_size; - ln_iface iface; - -}; -typedef struct _ln_session ln_session; - - -/* - * --- [FUNCTIONS] --- - */ - -// --- [utils] -//return: basename = success, NULL = fail/error -extern const char * ln_pathname_to_basename(const char * pathname); -//must destroy 'pid_vector' manually on success | pid = success, -1 = fail/error -extern pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector); -//return: 0 = success, -1 = fail/error -extern int ln_name_by_pid(const pid_t pid, char * name_buf); -//'out' must have space for double the length of 'inp' + 1 -extern void ln_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); - - -// --- [virtual interface (session)] -//all return 0 = success, -1 = fail/error -extern int ln_open(ln_session * session, const int iface, const pid_t pid); -extern int ln_close(ln_session * session); -extern int ln_update_map(const ln_session * session, ln_vm_map * vm_map); -extern int ln_read(const ln_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -extern int ln_write(const ln_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); - -// --- [map operations] -//all return 0 = success, -1 = fail/error -extern void ln_new_vm_map(ln_vm_map * vm_map); -extern int ln_del_vm_map(ln_vm_map * vm_map); -extern int ln_map_clean_unmapped(ln_vm_map * vm_map); - - -// --- [map utils] -//return: offset from start of area/object -extern off_t ln_get_area_offset(const cm_list_node * area_node, const uintptr_t addr); -extern off_t ln_get_obj_offset(const cm_list_node * obj_node, const uintptr_t addr); -//return: offset from start of area/object = success, -1 = not in area/object -extern off_t ln_get_area_offset_bnd(const cm_list_node * area_node, - const uintptr_t addr); -extern off_t ln_get_obj_offset_bnd(const cm_list_node * obj_node, - const uintptr_t addr); -//return area node * = success, NULL = fail/error -extern cm_list_node * ln_get_vm_area_by_addr(const ln_vm_map * vm_map, - const uintptr_t addr, off_t * offset); -//return obj node * = success, NULL = fail/error -extern cm_list_node * ln_get_vm_obj_by_addr(const ln_vm_map * vm_map, - const uintptr_t addr, off_t * offset); -extern cm_list_node * ln_get_vm_obj_by_pathname(const ln_vm_map * vm_map, - const char * pathname); -extern cm_list_node * ln_get_vm_obj_by_basename(const ln_vm_map * vm_map, - const char * basename); - - -// --- [error handling] -//void return -extern void ln_perror(); -extern const char * ln_strerror(const int ln_errnum); - - -/* - * --- [ERROR HANDLING] --- - */ - -extern __thread int ln_errno; - -// [error codes] - -// 1XX - user errors -#define LN_ERR_PROC_MEM 2100 -#define LN_ERR_PROC_MAP 2101 -#define LN_ERR_SEEK_ADDR 2102 - -// 2XX - internal errors -#define LN_ERR_INTERNAL_INDEX 2200 -#define LN_ERR_UNEXPECTED_NULL 2201 -#define LN_ERR_LIBCMORE 2202 -#define LN_ERR_READ_WRITE 2203 -#define LN_ERR_MEMU_TARGET 2204 -#define LN_ERR_MEMU_MAP_SZ 2205 -#define LN_ERR_MEMU_MAP_GET 2206 -#define LN_ERR_PROC_STATUS 2207 -#define LN_ERR_PROC_NAV 2208 - -// 3XX - environmental errors -#define LN_ERR_MEM 2300 -#define LN_ERR_PAGESIZE 2301 -#define LN_ERR_LAINKO_MAJOR 2302 -#define LN_ERR_MEMU_OPEN 2303 - - -// [error code messages] - -// 1XX - user errors -#define LN_ERR_PROC_MEM_MSG "Could not open /proc//mem for specified pid.\n" -#define LN_ERR_PROC_MAP_MSG "Could not open /proc//maps for specified pid.\n" -#define LN_ERR_SEEK_ADDR_MSG "Could not seek to specified address.\n" - -// 2XX - internal errors -#define LN_ERR_INTERNAL_INDEX_MSG "Internal: Indexing error.\n" -#define LN_ERR_UNEXPECTED_NULL_MSG "Internal: Unexpected NULL pointer.\n" -#define LN_ERR_LIBCMORE_MSG "Internal: Libcmore error. See cm_perror().\n" -#define LN_ERR_READ_WRITE_MSG "Internal: Read/write failed.\n" -#define LN_ERR_MEMU_TARGET_MSG "Internal: Lainko target open failed.\n" -#define LN_ERR_MEMU_MAP_SZ_MSG "Internal: Lainko map size fetch failed..\n" -#define LN_ERR_MEMU_MAP_GET_MSG "Internal: Lainko map transfer failed.\n" -#define LN_ERR_PROC_STATUS_MSG "Internal: Failed to use /proc//status.\n" -#define LN_ERR_PROC_NAV_MSG "Internal: Failed to navigate /proc directories.\n" - -// 3XX - environmental errors -#define LN_ERR_MEM_MSG "Failed to acquire the necessary memory.\n" -#define LN_ERR_PAGESIZE_MSG "Unable to fetch pagesize through sysconf().\n" -#define LN_ERR_LAINKO_MAJOR_MSG "Could not fetch lainko's major number.\n" -#define LN_ERR_MEMU_OPEN_MSG "Could not open lainmemu device.\n" - - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/lib/map.c b/src/lib/map.c index 3d88512..1a8304e 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -1,31 +1,38 @@ +//standard library #include #include #include +//system headers #include -#include +//external libraries +#include -#include "liblain.h" +//local headers +#include "memcry.h" #include "map.h" #include "util.h" +#include "debug.h" -// --- INTERNAL CONSTRUCTORS & DESTRUCTORS +/* + * --- [INTERNAL] + */ -//ln_vm_area constructor -static void _new_vm_area(ln_vm_area * vm_area, ln_vm_map * vm_map, - const cm_list_node * obj_node, - const cm_list_node * last_obj_node, - const struct vm_entry * entry) { +DBG_STATIC +void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, + const cm_lst_node * obj_node, + const cm_lst_node * last_obj_node, + const struct vm_entry * entry) { - ln_vm_obj * vm_obj; + mc_vm_obj * vm_obj; //set pathname for area if applicable if (obj_node != NULL) { - vm_obj = LN_GET_NODE_OBJ(obj_node); + vm_obj = MC_GET_NODE_OBJ(obj_node); vm_area->pathname = vm_obj->pathname; vm_area->basename = vm_obj->basename; @@ -36,8 +43,8 @@ static void _new_vm_area(ln_vm_area * vm_area, ln_vm_map * vm_map, } //end if - vm_area->obj_node_ptr = (cm_list_node *) obj_node; - vm_area->last_obj_node_ptr = (cm_list_node *) last_obj_node; + vm_area->obj_node_p = (cm_lst_node *) obj_node; + vm_area->last_obj_node_p = (cm_lst_node *) last_obj_node; vm_area->start_addr = entry->vm_start; vm_area->end_addr = entry->vm_end; @@ -54,11 +61,12 @@ static void _new_vm_area(ln_vm_area * vm_area, ln_vm_map * vm_map, } -//ln_vm_obj constructor -static void _new_vm_obj(ln_vm_obj * vm_obj, ln_vm_map * vm_map, - const char * pathname) { - const char * basename = ln_pathname_to_basename(pathname); +DBG_STATIC +void _map_new_vm_obj(mc_vm_obj * vm_obj, + mc_vm_map * vm_map, const char * pathname) { + + const char * basename = mc_pathname_to_basename(pathname); strncpy(vm_obj->pathname, pathname, PATH_MAX); strncpy(vm_obj->basename, basename, NAME_MAX); @@ -67,8 +75,8 @@ static void _new_vm_obj(ln_vm_obj * vm_obj, ln_vm_map * vm_map, vm_obj->end_addr = 0x0; //initialise area list - cm_new_list(&vm_obj->vm_area_node_ptrs, sizeof(cm_list_node *)); - cm_new_list(&vm_obj->last_vm_area_node_ptrs, sizeof(cm_list_node *)); + cm_new_lst(&vm_obj->vm_area_node_ps, sizeof(cm_lst_node *)); + cm_new_lst(&vm_obj->last_vm_area_node_ps, sizeof(cm_lst_node *)); vm_obj->mapped = true; @@ -80,21 +88,20 @@ static void _new_vm_obj(ln_vm_obj * vm_obj, ln_vm_map * vm_map, } -//ln_vm_obj destructor -static void _del_vm_obj(ln_vm_obj * vm_obj) { - cm_del_list(&vm_obj->vm_area_node_ptrs); - cm_del_list(&vm_obj->last_vm_area_node_ptrs); +DBG_STATIC +void _map_del_vm_obj(mc_vm_obj * vm_obj) { + + cm_del_lst(&vm_obj->vm_area_node_ps); + cm_del_lst(&vm_obj->last_vm_area_node_ps); return; } -// --- MAP GENERATION INTERNALS - -//modify object to act as pseudo object -static inline void _make_zero_obj(ln_vm_obj * vm_obj) { +DBG_STATIC DBG_INLINE +void _map_make_zero_obj(mc_vm_obj * vm_obj) { vm_obj->id = ZERO_OBJ_ID; @@ -102,11 +109,12 @@ static inline void _make_zero_obj(ln_vm_obj * vm_obj) { } -//add an area to an obj -static inline int _obj_add_area(ln_vm_obj * obj, const cm_list_node * area_node) { - cm_list_node * ret_node; - ln_vm_area * area = LN_GET_NODE_AREA(area_node); +DBG_STATIC DBG_INLINE +int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node) { + + cm_lst_node * ret_node; + mc_vm_area * area = MC_GET_NODE_AREA(area_node); //if this object has no areas yet if (obj->start_addr == -1 || obj->start_addr == 0x0) { @@ -119,15 +127,17 @@ static inline int _obj_add_area(ln_vm_obj * obj, const cm_list_node * area_node) } else { //set new addr bounds if necessary - if (area->start_addr < obj->start_addr) obj->start_addr = area->start_addr; - if (area->end_addr > obj->end_addr) obj->end_addr = area->end_addr; + if (area->start_addr < obj->start_addr) + obj->start_addr = area->start_addr; + if (area->end_addr > obj->end_addr) + obj->end_addr = area->end_addr; } //simply appending preserves chronological order; out of order vm_areas //are guaranteed(?) to be removed - ret_node = cm_list_append(&obj->vm_area_node_ptrs, (const cm_byte *) &area_node); + ret_node = cm_lst_apd(&obj->vm_area_node_ps, &area_node); if (ret_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -135,17 +145,17 @@ static inline int _obj_add_area(ln_vm_obj * obj, const cm_list_node * area_node) } -static inline int _obj_add_last_area(ln_vm_obj * obj, - const cm_list_node * last_area_node) { + +DBG_STATIC DBG_INLINE +int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node) { - cm_list_node * ret_node; + cm_lst_node * ret_node; //simply appending preserves chronological order; out of order vm_areas //are guaranteed(?) to be removed - ret_node = cm_list_append(&obj->last_vm_area_node_ptrs, - (const cm_byte *) &last_area_node); + ret_node = cm_lst_apd(&obj->last_vm_area_node_ps, &last_area_node); if (ret_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -153,8 +163,9 @@ static inline int _obj_add_last_area(ln_vm_obj * obj, } -//find if vm area's pathname belongs in object -static bool _is_pathname_in_obj(const char * pathname, const ln_vm_obj * obj) { + +DBG_STATIC +bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { if (obj == NULL) return false; @@ -164,27 +175,29 @@ static bool _is_pathname_in_obj(const char * pathname, const ln_vm_obj * obj) { } + #define _MAP_OBJ_PREV 0 #define _MAP_OBJ_NEW 1 #define _MAP_OBJ_NEXT 2 -static inline int _find_obj_for_area(const _traverse_state * state, - const struct vm_entry * entry) { +DBG_STATIC DBG_INLINE +int _map_find_obj_for_area(const _traverse_state * state, + const struct vm_entry * entry) { - cm_list_node * prev_node, * next_node; - ln_vm_obj * prev_obj, * next_obj; + cm_lst_node * prev_node, * next_node; + mc_vm_obj * prev_obj, * next_obj; //check for null if (state->prev_obj == NULL) return _MAP_OBJ_NEW; //get prev obj prev_node = state->prev_obj; - prev_obj = LN_GET_NODE_OBJ(prev_node); + prev_obj = MC_GET_NODE_OBJ(prev_node); if (prev_node->next == NULL) { next_obj = NULL; } else { next_node = prev_node->next; - next_obj = LN_GET_NODE_OBJ(next_node); + next_obj = MC_GET_NODE_OBJ(next_node); } //return appropriate index @@ -195,20 +208,21 @@ static inline int _find_obj_for_area(const _traverse_state * state, } -//find index of vm area in its corresponding object -static inline int _get_area_index(const ln_vm_area * area, const ln_vm_obj * obj) { + +DBG_STATIC DBG_INLINE +int _map_get_area_index(const mc_vm_area * area, const mc_vm_obj * obj) { int ret; - cm_list_node * temp_node; - ln_vm_area * temp_area; + cm_lst_node * temp_node; + mc_vm_area * temp_area; //for all vm areas in this object - for (int i = 0; i < obj->vm_area_node_ptrs.len; ++i) { + for (int i = 0; i < obj->vm_area_node_ps.len; ++i) { - ret = cm_list_get_val(&obj->vm_area_node_ptrs, i, (cm_byte *) &temp_node); + ret = cm_lst_get(&obj->vm_area_node_ps, i, &temp_node); if (ret) return -1; - temp_area = LN_GET_NODE_AREA(temp_node); + temp_area = MC_GET_NODE_AREA(temp_node); if (temp_area->id == area->id) return i; @@ -218,102 +232,106 @@ static inline int _get_area_index(const ln_vm_area * area, const ln_vm_obj * obj } -//find the new start and end addresses for an object -static inline int _update_obj_addr_range(ln_vm_obj * obj) { + +DBG_STATIC DBG_INLINE +int _map_update_obj_addr_range(mc_vm_obj * obj) { int ret, end_index; - cm_list_node * temp_node; - ln_vm_area * temp_area; + cm_lst_node * temp_node; + mc_vm_area * temp_area; //get start addr - ret = cm_list_get_val(&obj->vm_area_node_ptrs, 0, (cm_byte *) &temp_node); + ret = cm_lst_get(&obj->vm_area_node_ps, 0, &temp_node); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - temp_area = LN_GET_NODE_AREA(temp_node); + temp_area = MC_GET_NODE_AREA(temp_node); obj->start_addr = temp_area->start_addr; //get end addr - if (obj->vm_area_node_ptrs.len == 1) { + if (obj->vm_area_node_ps.len == 1) { end_index = 0; } else { end_index = -1; } - ret = cm_list_get_val(&obj->vm_area_node_ptrs, end_index, (cm_byte *) &temp_node); + ret = cm_lst_get(&obj->vm_area_node_ps, end_index, &temp_node); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - temp_area = LN_GET_NODE_AREA(temp_node); + temp_area = MC_GET_NODE_AREA(temp_node); obj->end_addr = temp_area->end_addr; return 0; } -//called when deleting an object to move last_obj_node_ptr refernces an object back -static inline int _backtrack_unmapped_obj_last_vm_areas(cm_list_node * node) { + +//called when deleting an object; moves `last_obj_node_p` back if needed +DBG_STATIC DBG_INLINE +int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * node) { int ret; int iterations; - ln_vm_obj * temp_obj; + mc_vm_obj * temp_obj; - cm_list_node * last_area_node; - ln_vm_area * last_area; + cm_lst_node * last_area_node; + mc_vm_area * last_area; //setup iteration - temp_obj = LN_GET_NODE_OBJ(node); - last_area_node = temp_obj->last_vm_area_node_ptrs.head; + temp_obj = MC_GET_NODE_OBJ(node); + last_area_node = temp_obj->last_vm_area_node_ps.head; if (last_area_node == NULL) return 0; - last_area = LN_GET_NODE_AREA(last_area_node); + last_area = MC_GET_NODE_AREA(last_area_node); //for every area, backtrack last object pointer - iterations = temp_obj->last_vm_area_node_ptrs.len; + iterations = temp_obj->last_vm_area_node_ps.len; for (int i = 0; i < iterations; ++i) { - //update ptr - last_area->last_obj_node_ptr = node->prev; + //update pointer + last_area->last_obj_node_p = node->prev; //advance iteration (part 1) last_area_node = last_area_node->next; - //remove this area from the object's last_vm_area_node_ptrs list - ret = cm_list_remove(&temp_obj->last_vm_area_node_ptrs, 0); + //remove this area from the object's last_vm_area_node_ps list + ret = cm_lst_rem(&temp_obj->last_vm_area_node_ps, 0); if (ret == -1) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } //advance iteration (part 2) - last_area = LN_GET_NODE_AREA(last_area_node); + last_area = MC_GET_NODE_AREA(last_area_node); } return 0; } -//called when adding a new object to move last_obj_node_ptr references forward -//if necessary -static inline int _forward_unmapped_obj_last_vm_areas(cm_list_node * node) { + +//called when adding an object; moves `last_obj_node_p` forward if needed +DBG_STATIC DBG_INLINE +int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * node) { int ret; //declarations - cm_list_node * prev_node; - ln_vm_obj * temp_obj; - ln_vm_obj * temp_prev_obj; + cm_lst_node * prev_node; + mc_vm_obj * temp_obj; + mc_vm_obj * temp_prev_obj; - cm_list_node * last_area_node; - ln_vm_area * last_area; + cm_lst_node * last_area_node; + mc_vm_area * last_area; /* @@ -323,17 +341,17 @@ static inline int _forward_unmapped_obj_last_vm_areas(cm_list_node * node) { //setup iteration prev_node = node->prev; - temp_obj = LN_GET_NODE_OBJ(node); - temp_prev_obj = LN_GET_NODE_OBJ(prev_node); + temp_obj = MC_GET_NODE_OBJ(node); + temp_prev_obj = MC_GET_NODE_OBJ(prev_node); - last_area_node = temp_prev_obj->last_vm_area_node_ptrs.head; + last_area_node = temp_prev_obj->last_vm_area_node_ps.head; if (last_area_node == NULL) return 0; - last_area = LN_GET_NODE_AREA(last_area_node); + last_area = MC_GET_NODE_AREA(last_area_node); //for every area, move last object pointer forward if necessary - for (int i = 0; i < temp_prev_obj->last_vm_area_node_ptrs.len; ++i) { + for (int i = 0; i < temp_prev_obj->last_vm_area_node_ps.len; ++i) { //advance iteration (part 1) last_area_node = last_area_node->next; @@ -342,16 +360,17 @@ static inline int _forward_unmapped_obj_last_vm_areas(cm_list_node * node) { if (last_area->start_addr >= temp_obj->end_addr) { //set this object as the new last object pointer - last_area->last_obj_node_ptr = node; + last_area->last_obj_node_p = node; - //add this area to this object's last_vm_area_node_ptrs list - cm_list_append(&temp_obj->last_vm_area_node_ptrs, - (const cm_byte *) &last_area_node); + //add this area to this object's last_vm_area_node_ps list + cm_lst_apd(&temp_obj->last_vm_area_node_ps, + &last_area_node); - //remove this area from the previous last object's last_vm_area_node_ptrs - ret = cm_list_remove(&temp_prev_obj->last_vm_area_node_ptrs, i); + //remove this area from the previous + //last object's last_vm_area_node_ps + ret = cm_lst_rem(&temp_prev_obj->last_vm_area_node_ps, i); if (ret == -1) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -360,7 +379,7 @@ static inline int _forward_unmapped_obj_last_vm_areas(cm_list_node * node) { } //end if //advance iteration (part 2) - last_area = LN_GET_NODE_AREA(last_area_node); + last_area = MC_GET_NODE_AREA(last_area_node); } //end for @@ -368,44 +387,46 @@ static inline int _forward_unmapped_obj_last_vm_areas(cm_list_node * node) { } -//correctly remove unmapped obj node -static inline int _unlink_unmapped_obj(cm_list_node * node, - const _traverse_state * state, - ln_vm_map * vm_map) { + +DBG_STATIC DBG_INLINE +int _map_unlink_unmapped_obj(cm_lst_node * node, + const _traverse_state * state, + mc_vm_map * vm_map) { int ret; - cm_list_node * ret_node; - ln_vm_obj * temp_obj; + cm_lst_node * ret_node; + mc_vm_obj * temp_obj; //if this is the pseudo object, just reset it - temp_obj = LN_GET_NODE_OBJ(node); + temp_obj = MC_GET_NODE_OBJ(node); if (temp_obj->id == ZERO_OBJ_ID) { temp_obj->start_addr = 0x0; temp_obj->end_addr = 0x0; return 0; } - //correct last_obj_node_ptr of every vm_area using this object as its last object + //correct last_obj_node_p of every vm_area + //using this object as its last object ret = _backtrack_unmapped_obj_last_vm_areas(node); if (ret == -1) return -1; //unlink this node from the list of mapped vm areas - ret = cm_list_unlink(&vm_map->vm_objs, state->prev_obj_index + 1); + ret = cm_lst_uln(&vm_map->vm_objs, state->prev_obj_index + 1); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } //set node's values to be unmapped - (LN_GET_NODE_OBJ(node))->mapped = false; + (MC_GET_NODE_OBJ(node))->mapped = false; node->next = NULL; node->prev = NULL; //add a pointer to this node to the list containing nodes to dealloc later - ret_node = cm_list_append(&vm_map->vm_objs_unmapped, (cm_byte *) &node); + ret_node = cm_lst_apd(&vm_map->vm_objs_unmapped, &node); if (ret_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -413,44 +434,45 @@ static inline int _unlink_unmapped_obj(cm_list_node * node, } -//correctly remove unmapped area node -static inline int _unlink_unmapped_area(cm_list_node * node, - const _traverse_state * state, - ln_vm_map * vm_map) { + +DBG_STATIC DBG_INLINE +int _map_unlink_unmapped_area(cm_lst_node * node, + const _traverse_state * state, + mc_vm_map * vm_map) { int ret, index; - cm_list_node * obj_node, * temp_node; + cm_lst_node * obj_node, * temp_node; - ln_vm_area * temp_area; - ln_vm_obj * temp_obj; + mc_vm_area * temp_area; + mc_vm_obj * temp_obj; //get vm area of node - temp_area = LN_GET_NODE_AREA(node); + temp_area = MC_GET_NODE_AREA(node); //remove this area from its parent object if necessary - if (temp_area->obj_node_ptr != NULL) { + if (temp_area->obj_node_p != NULL) { - obj_node = temp_area->obj_node_ptr; - temp_obj = LN_GET_NODE_OBJ(obj_node); + obj_node = temp_area->obj_node_p; + temp_obj = MC_GET_NODE_OBJ(obj_node); //find the index for this area in the obj index = _get_area_index(temp_area, temp_obj); if (index != -1) { - ret = cm_list_remove(&temp_obj->vm_area_node_ptrs, index); + ret = cm_lst_rem(&temp_obj->vm_area_node_ps, index); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } } else { - ln_errno = LN_ERR_INTERNAL_INDEX; + mc_errno = MC_ERR_INTERNAL_INDEX; return -1; } //if the object now has no areas, set it to be unmapped - if (temp_obj->vm_area_node_ptrs.len == 0) { + if (temp_obj->vm_area_node_ps.len == 0) { ret = _unlink_unmapped_obj(obj_node, state, vm_map); if (ret) return -1; @@ -465,9 +487,9 @@ static inline int _unlink_unmapped_area(cm_list_node * node, //unlink this node from the list of mapped vm areas - ret = cm_list_unlink(&vm_map->vm_areas, state->next_area_index); + ret = cm_lst_uln(&vm_map->vm_areas, state->next_area_index); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -477,9 +499,9 @@ static inline int _unlink_unmapped_area(cm_list_node * node, node->prev = NULL; //add a pointer to this node to the list containing nodes to dealloc later - temp_node = cm_list_append(&vm_map->vm_areas_unmapped, (cm_byte *) &node); + temp_node = cm_lst_apd(&vm_map->vm_areas_unmapped, &node); if (temp_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -487,11 +509,12 @@ static inline int _unlink_unmapped_area(cm_list_node * node, } -//check if the new vm_area is the same as the old vm_area -static inline int _check_area_eql(const struct vm_entry * entry, - const cm_list_node * area_node) { - ln_vm_area * area = LN_GET_NODE_AREA(area_node); +DBG_STATIC DBG_INLINE +int _map_check_area_eql(const struct vm_entry * entry, + const cm_lst_node * area_node) { + + mc_vm_area * area = MC_GET_NODE_AREA(area_node); //check misc if ((entry->vm_start != area->start_addr) @@ -508,17 +531,18 @@ static inline int _check_area_eql(const struct vm_entry * entry, } -//remove old entries and get in sync again -static inline int _resync_area(ln_vm_map * vm_map, - _traverse_state * state, const struct vm_entry * entry) { + +DBG_STATIC DBG_INLINE +int _map_resync_area(mc_vm_map * vm_map, _traverse_state * state, + const struct vm_entry * entry) { int ret; - ln_vm_area * iter_area; - cm_list_node * iter_node; + mc_vm_area * iter_area; + cm_lst_node * iter_node; iter_node = state->next_area; - iter_area = LN_GET_NODE_AREA(iter_node); + iter_area = MC_GET_NODE_AREA(iter_node); //while there are vm areas left to discard @@ -538,7 +562,7 @@ static inline int _resync_area(ln_vm_map * vm_map, //update iterator nodes if (state->next_area == NULL) break; iter_node = state->next_area; - iter_area = LN_GET_NODE_AREA(iter_node); + iter_area = MC_GET_NODE_AREA(iter_node); } //end while @@ -546,12 +570,14 @@ static inline int _resync_area(ln_vm_map * vm_map, } + #define _STATE_AREA_NODE_KEEP 0 #define _STATE_AREA_NODE_ADVANCE 1 #define _STATE_AREA_NODE_REASSIGN 2 -//move area state forward -static void _state_inc_area(ln_vm_map * vm_map, _traverse_state * state, - const cm_list_node * assign_node, const int inc_type) { + +DBG_STATIC +void _map_state_inc_area(mc_vm_map * vm_map, _traverse_state * state, + const cm_lst_node * assign_node, const int inc_type) { switch (inc_type) { @@ -559,7 +585,8 @@ static void _state_inc_area(ln_vm_map * vm_map, _traverse_state * state, break; case _STATE_AREA_NODE_ADVANCE: - //advance next area if we haven't reached end & dont circle back to start + //advance next area if we haven't reached the end + //& dont circle back to the start if (state->next_area != NULL && state->next_area_index < vm_map->vm_areas.len) { @@ -571,7 +598,7 @@ static void _state_inc_area(ln_vm_map * vm_map, _traverse_state * state, break; case _STATE_AREA_NODE_REASSIGN: - state->next_area = (cm_list_node *) assign_node; + state->next_area = (cm_lst_node *) assign_node; break; } //end switch @@ -583,8 +610,9 @@ static void _state_inc_area(ln_vm_map * vm_map, _traverse_state * state, } -//move obj state forward -static void _state_inc_obj(ln_vm_map * vm_map, _traverse_state * state) { + +DBG_STATIC +void _map_state_inc_obj(mc_vm_map * vm_map, _traverse_state * state) { //if there is no prev obj, initialise it if (state->prev_obj == NULL) { @@ -607,14 +635,15 @@ static void _state_inc_obj(ln_vm_map * vm_map, _traverse_state * state) { } -//add a new obj to the map -static cm_list_node * _map_add_obj(ln_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry) { + +DBG_STATIC +cm_lst_node * _map_add_obj(mc_vm_map * vm_map, _traverse_state * state, + const struct vm_entry * entry) { int index; - ln_vm_obj vm_obj; - cm_list_node * obj_node; + mc_vm_obj vm_obj; + cm_lst_node * obj_node; //create new object _new_vm_obj(&vm_obj, vm_map, entry->file_path); @@ -623,19 +652,19 @@ static cm_list_node * _map_add_obj(ln_vm_map * vm_map, _traverse_state * state, index = (vm_map->vm_objs.len == 0) ? 0 : state->prev_obj_index + 1; //insert obj into map at state's index - obj_node = cm_list_insert(&vm_map->vm_objs, - index, (cm_byte *) &vm_obj); + obj_node = cm_lst_ins(&vm_map->vm_objs, index, &vm_obj); if (obj_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return NULL; } /* - * with the insertion of this object, the vm_areas in the previous object's - * last_vm_area_node_ptrs list may now incorrectly treat the previous object + * With the insertion of this object, vm_areas in the previous object's + * last_vm_area_node_ps list may now incorrectly treat the previous object * as the last object, when in fact this newly inserted object should be - * their new last object. this needs to be corrected now. + * their new last object. This needs to now be corrected. */ + _forward_unmapped_obj_last_vm_areas(obj_node); //advance state @@ -645,18 +674,19 @@ static cm_list_node * _map_add_obj(ln_vm_map * vm_map, _traverse_state * state, } -//add a new area to the map -static int _map_add_area(ln_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry, const int inc_type) { + +DBG_STATIC +int _map_add_area(mc_vm_map * vm_map, _traverse_state * state, + const struct vm_entry * entry, const int inc_type) { int ret; bool use_obj = false; - ln_vm_area vm_area; - ln_vm_obj * vm_obj; + mc_vm_area vm_area; + mc_vm_obj * vm_obj; - cm_list_node * area_node; - cm_list_node * obj_node; + cm_lst_node * area_node; + cm_lst_node * obj_node; //if no obj for this area if (entry->file_path[0] != '\0') use_obj = true; @@ -664,7 +694,8 @@ static int _map_add_area(ln_vm_map * vm_map, _traverse_state * state, if (!use_obj) { /* - * It should never be possible for prev_obj to point at/ahead of this vm_area + * It should never be possible for prev_obj + * to point at/ahead of this vm_area. */ _new_vm_area(&vm_area, vm_map, NULL, state->prev_obj, entry); @@ -697,15 +728,14 @@ static int _map_add_area(ln_vm_map * vm_map, _traverse_state * state, } //add area to the map list - area_node = cm_list_insert(&vm_map->vm_areas, - state->next_area_index, (cm_byte *) &vm_area); + area_node = cm_lst_ins(&vm_map->vm_areas, state->next_area_index, &vm_area); if (area_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - //add area to the obj ptr list - vm_obj = LN_GET_NODE_OBJ(state->prev_obj); + //add area to the object pointer list + vm_obj = MC_GET_NODE_OBJ(state->prev_obj); if (use_obj) { ret = _obj_add_area(vm_obj, area_node); if (ret == -1) return -1; @@ -722,11 +752,12 @@ static int _map_add_area(ln_vm_map * vm_map, _traverse_state * state, -// --- CALLED BY MEMORY INTERFACES +/* + * --- [INTERFACE] --- + */ -//send information about a vm area from an interface to a map -int _map_send_entry(ln_vm_map * vm_map, - _traverse_state * state, const struct vm_entry * entry) { +int map_send_entry(mc_vm_map * vm_map, + _traverse_state * state, const struct vm_entry * entry) { int ret; @@ -767,11 +798,8 @@ int _map_send_entry(ln_vm_map * vm_map, } -//initialise traverse state for a map -void _map_init_traverse_state(const ln_vm_map * vm_map, _traverse_state * state) { - state->next_area_index = 0; - state->prev_obj_index = 0; +void map_init_traverse_state(const mc_vm_map * vm_map, _traverse_state * state) { //set up next area node state->next_area = vm_map->vm_areas.head; @@ -782,26 +810,27 @@ void _map_init_traverse_state(const ln_vm_map * vm_map, _traverse_state * state) -// --- USER INTERFACE +/* + * --- [EXTERNAL] --- + */ -//ln_vm_map constructor -void ln_new_vm_map(ln_vm_map * vm_map) { +void mc_new_vm_map(mc_vm_map * vm_map) { //pseudo object, will adopt leading parentless vm_areas - ln_vm_obj zero_obj; + mc_vm_obj zero_obj; //initialise lists - cm_new_list(&vm_map->vm_areas, sizeof(ln_vm_area)); - cm_new_list(&vm_map->vm_objs, sizeof(ln_vm_obj)); + cm_new_lst(&vm_map->vm_areas, sizeof(mc_vm_area)); + cm_new_lst(&vm_map->vm_objs, sizeof(mc_vm_obj)); - cm_new_list(&vm_map->vm_areas_unmapped, sizeof(cm_list_node *)); - cm_new_list(&vm_map->vm_objs_unmapped, sizeof(cm_list_node *)); + cm_new_lst(&vm_map->vm_areas_unmapped, sizeof(cm_lst_node *)); + cm_new_lst(&vm_map->vm_objs_unmapped, sizeof(cm_lst_node *)); //setup pseudo object at start of map - _new_vm_obj(&zero_obj, vm_map, "0x0"); - _make_zero_obj(&zero_obj); + _map_new_vm_obj(&zero_obj, vm_map, "0x0"); + _map_make_zero_obj(&zero_obj); - cm_list_append(&vm_map->vm_objs, (cm_byte *) &zero_obj); + cm_lst_apd(&vm_map->vm_objs, &zero_obj); //reset id vm_map->next_id_area = vm_map->next_id_obj = 0; @@ -810,18 +839,18 @@ void ln_new_vm_map(ln_vm_map * vm_map) { } -//ln_vm_map destructor -int ln_del_vm_map(ln_vm_map * vm_map) { + +int mc_del_vm_map(mc_vm_map * vm_map) { int ret; //unallocate all unmapped nodes - ret = ln_map_clean_unmapped(vm_map); + ret = mc_map_clean_unmapped(vm_map); if (ret) return -1; - cm_list_node * iter_node; - ln_vm_obj * iter_obj; + cm_lst_node * iter_node; + mc_vm_obj * iter_obj; int len_obj = vm_map->vm_objs.len; @@ -829,15 +858,15 @@ int ln_del_vm_map(ln_vm_map * vm_map) { //manually free all unmapped obj nodes for (int i = 0; i < len_obj; ++i) { - iter_node = cm_list_get_node(&vm_map->vm_objs_unmapped, i); + iter_node = cm_lst_get_n(&vm_map->vm_objs_unmapped, i); if (iter_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - iter_obj = LN_GET_NODE_OBJ(iter_node); + iter_obj = MC_GET_NODE_OBJ(iter_node); if (iter_obj == NULL) { - ln_errno = LN_ERR_UNEXPECTED_NULL; + mc_errno = MC_ERR_UNEXPECTED_NULL; return -1; } @@ -845,23 +874,25 @@ int ln_del_vm_map(ln_vm_map * vm_map) { } //end for - cm_del_list(&vm_map->vm_areas); - cm_del_list(&vm_map->vm_objs); + cm_del_lst(&vm_map->vm_areas); + cm_del_lst(&vm_map->vm_objs); return 0; } + /* - * The nodes of 'vm_map->_unmapped' lists hold pointers to other nodes. - * This means to thoroughly + * The nodes of 'vm_map->vm_{areas,objs}_unmapped' lists hold pointers + * to other nodes. These nodes must be freed as appropriate. */ -int ln_map_clean_unmapped(ln_vm_map * vm_map) { + +int mc_map_clean_unmapped(mc_vm_map * vm_map) { int ret; - cm_list_node * iter_node; - cm_list_node * del_node; + cm_lst_node * iter_node; + cm_lst_node * del_node; int len_area = vm_map->vm_areas_unmapped.len; int len_obj = vm_map->vm_objs_unmapped.len; @@ -870,13 +901,13 @@ int ln_map_clean_unmapped(ln_vm_map * vm_map) { //manually free all unmapped area nodes for (int i = 0; i < len_area; ++i) { - iter_node = cm_list_get_node(&vm_map->vm_areas_unmapped, i); + iter_node = cm_lst_get_n(&vm_map->vm_areas_unmapped, i); if (iter_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - del_node = *((cm_list_node **) iter_node->data); + del_node = *((cm_lst_node **) iter_node->data); free(del_node->data); free(del_node); @@ -885,28 +916,28 @@ int ln_map_clean_unmapped(ln_vm_map * vm_map) { //manually free all unmapped obj nodes for (int i = 0; i < len_obj; ++i) { - iter_node = cm_list_get_node(&vm_map->vm_objs_unmapped, i); + iter_node = cm_lst_get_n(&vm_map->vm_objs_unmapped, i); if (iter_node == NULL) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - del_node = *((cm_list_node **) iter_node->data); + del_node = *((cm_lst_node **) iter_node->data); free(del_node->data); free(del_node); } //end for //empty out both unmapped lists - ret = cm_list_empty(&vm_map->vm_areas_unmapped); + ret = cm_lst_emp(&vm_map->vm_areas_unmapped); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } - ret = cm_list_empty(&vm_map->vm_objs_unmapped); + ret = cm_lst_emp(&vm_map->vm_objs_unmapped); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } diff --git a/src/lib/map.h b/src/lib/map.h index 1f8881b..29f9d6d 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -1,38 +1,87 @@ #ifndef MAP_H #define MAP_H -#include "liblain.h" -#include "lainko.h" +//local headers +#include "memcry.h" +#include "krncry.h" + + +//TODO REMOVE +#define DEBUG /* - * initialise this struct manually on the first call to + * Initialise _traverse_state manually on the first call to * send_map_node() for a map generated by a memory interface. * * send_map_node() will automatically update it for use by * all subsequent calls. */ -typedef struct { - cm_list_node * next_area; //ln_vm_area - int next_area_index; +typedef struct { - cm_list_node * prev_obj; //ln_vm_obj - int prev_obj_index; + cm_lst_node * next_area; //mc_vm_area + cm_lst_node * prev_obj; //mc_vm_obj } _traverse_state; +#ifdef DEBUG //internal -int _map_send_entry(ln_vm_map * vm_map, +void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, + const cm_lst_node * obj_node, + const cm_lst_node * last_obj_node, + const struct vm_entry * entry); +void _map_new_vm_obj(mc_vm_obj * vm_obj, + mc_vm_map * vm_map, const char * pathname); +void _map_del_vm_obj(mc_vm_obj * vm_obj); + +void _map_make_zero_obj(mc_vm_obj * vm_obj); + +int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node); +int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node); + +bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj); +int _map_get_area_index(const mc_vm_area * area, const mc_vm_obj * obj); + +int _map_update_obj_addr_range(mc_vm_obj * obj); +int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * node); +int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * node); + +int _map_unlink_unmapped_obj(cm_lst_node * node, + const _traverse_state * state, + mc_vm_map * vm_map); +int _map_unlink_unmapped_area(cm_lst_node * node, + const _traverse_state * state, + mc_vm_map * vm_map); + +int _map_check_area_eql(const struct vm_entry * entry, + const cm_lst_node * area_node); +int _map_resync_area(mc_vm_map * vm_map, + _traverse_state * state, const struct vm_entry * entry); + +void _map_state_inc_area(mc_vm_map * vm_map, _traverse_state * state, + const cm_lst_node * assign_node, const int inc_type); +void _map_state_inc_obj(mc_vm_map * vm_map, _traverse_state * state); + +cm_lst_node * _map_add_obj(mc_vm_map * vm_map, _traverse_state * state, + const struct vm_entry * entry); +int _map_add_area(mc_vm_map * vm_map, _traverse_state * state, + const struct vm_entry * entry, const int inc_type); +#endif + + +//interface +int map_send_entry(mc_vm_map * vm_map, _traverse_state * state, const struct vm_entry * entry); -void _map_init_traverse_state(const ln_vm_map * vm_map, _traverse_state * state); +void map_init_traverse_state(const mc_vm_map * vm_map, + _traverse_state * state); //external -void ln_new_vm_map(ln_vm_map * vm_map); -int ln_del_vm_map(ln_vm_map * vm_map); -int ln_map_clean_unmapped(ln_vm_map * vm_map); +void mc_new_vm_map(mc_vm_map * vm_map); +int mc_del_vm_map(mc_vm_map * vm_map); +int mc_map_clean_unmapped(mc_vm_map * vm_map); #endif diff --git a/src/lib/map_util.c b/src/lib/map_util.c index 50ebf5b..6c1b1ad 100644 --- a/src/lib/map_util.c +++ b/src/lib/map_util.c @@ -1,22 +1,29 @@ +//standard library #include -#include +//external libraries +#include -#include "liblain.h" +//local headers +#include "memcry.h" #include "map_util.h" +#include "debug.h" -#define MAP_UTIL_GET_AREA 0 -#define MAP_UTIL_GET_OBJ 1 +#define _MAP_UTIL_GET_AREA 0 +#define _MAP_UTIL_GET_OBJ 1 -#define MAP_UTIL_USE_PATHNAME 0 -#define MAP_UTIL_USE_BASENAME 1 +#define _MAP_UTIL_USE_PATHNAME 0 +#define _MAP_UTIL_USE_BASENAME 1 -// --- INTERNAL -//check map is initialised -static inline bool _is_map_empty(const ln_vm_map * vm_map) { +/* + * --- [INTERNAL] --- + */ + +DBG_STATIC DBG_INLINE +bool _is_map_empty(const mc_vm_map * vm_map) { if (vm_map->vm_areas.len == 0) return true; if (vm_map->vm_objs.len == 0) return true; @@ -25,14 +32,20 @@ static inline bool _is_map_empty(const ln_vm_map * vm_map) { } -//skip pseudo object if it is empty -static inline cm_list_node * _get_starting_obj(const ln_vm_map * vm_map) { - cm_list_node * obj_node; - ln_vm_obj * vm_obj; +/* + * Determines starting object for iteration. + * Will skip the pseudo-object if it is empty. + */ + +DBG_STATIC DBG_INLINE +cm_lst_node * _get_starting_obj(const mc_vm_map * vm_map) { + + cm_lst_node * obj_node; + mc_vm_obj * vm_obj; obj_node = vm_map->vm_objs.head; - vm_obj = LN_GET_NODE_OBJ(obj_node); + vm_obj = MC_GET_NODE_OBJ(obj_node); //if pseudo object has no areas if (vm_obj->start_addr == -1 || vm_obj->start_addr == 0x0) { @@ -43,35 +56,37 @@ static inline cm_list_node * _get_starting_obj(const ln_vm_map * vm_map) { } -//get object's last area -static inline cm_list_node * _get_obj_last_area(const ln_vm_obj * vm_obj) { - cm_list_node * last_node; +DBG_STATIC DBG_INLINE +cm_lst_node * _get_obj_last_area(const mc_vm_obj * vm_obj) { + + cm_lst_node * last_node; //if this object has multiple areas if (vm_obj->vm_area_node_ptrs.len > 1) { - last_node = LN_GET_NODE_PTR(vm_obj->vm_area_node_ptrs.head->prev); + last_node = MC_GET_NODE_PTR(vm_obj->vm_area_node_ptrs.head->prev); //else this object only has one area } else { - last_node = LN_GET_NODE_PTR(vm_obj->vm_area_node_ptrs.head); + last_node = MC_GET_NODE_PTR(vm_obj->vm_area_node_ptrs.head); } return last_node; } -//iterate through objects, then areas for a fast search -static cm_list_node * _fast_addr_find(const ln_vm_map * vm_map, - const uintptr_t addr, const int mode) { - cm_list_node * iter_obj_node; - cm_list_node * iter_area_node; +DBG_STATIC +cm_lst_node * _fast_addr_find(const mc_vm_map * vm_map, + const uintptr_t addr, const int mode) { + + cm_lst_node * iter_obj_node; + cm_lst_node * iter_area_node; - ln_vm_obj * iter_vm_obj; - ln_vm_area * iter_vm_area; + mc_vm_obj * iter_vm_obj; + mc_vm_area * iter_vm_area; - ln_vm_area * prev_area; + mc_vm_area * prev_area; //check map is not empty @@ -79,10 +94,10 @@ static cm_list_node * _fast_addr_find(const ln_vm_map * vm_map, //init object iteration iter_obj_node = _get_starting_obj(vm_map); - iter_vm_obj = LN_GET_NODE_OBJ(iter_obj_node); + iter_vm_obj = MC_GET_NODE_OBJ(iter_obj_node); - iter_area_node = LN_GET_NODE_PTR(iter_vm_obj->vm_area_node_ptrs.head); - iter_vm_area = LN_GET_NODE_AREA(iter_area_node); + iter_area_node = MC_GET_NODE_PTR(iter_vm_obj->vm_area_node_ptrs.head); + iter_vm_area = MC_GET_NODE_AREA(iter_area_node); //if the address is in the very first object @@ -95,17 +110,17 @@ static cm_list_node * _fast_addr_find(const ln_vm_map * vm_map, if (iter_vm_obj->end_addr > addr) break; iter_area_node = _get_obj_last_area(iter_vm_obj); - iter_vm_area = LN_GET_NODE_AREA(iter_area_node); + iter_vm_area = MC_GET_NODE_AREA(iter_area_node); iter_obj_node = iter_obj_node->next; - iter_vm_obj = LN_GET_NODE_OBJ(iter_obj_node); + iter_vm_obj = MC_GET_NODE_OBJ(iter_obj_node); } //end object search }//end if //if an obj was requested - if (mode == MAP_UTIL_GET_OBJ) { + if (mode == _MAP_UTIL_GET_OBJ) { //if the address is in range of the obj if (iter_vm_obj->start_addr <= addr && iter_vm_obj->end_addr > addr) { @@ -116,18 +131,19 @@ static cm_list_node * _fast_addr_find(const ln_vm_map * vm_map, } //end if an obj was requested - //switch to area search and continue the search from last object's final area + //switch to area search and continue the search from last object's end area while (1) { //if found a matching area - if (iter_vm_area->start_addr <= addr && iter_vm_area->end_addr > addr) { + if (iter_vm_area->start_addr <= addr + && iter_vm_area->end_addr > addr) { return iter_area_node; } //move to next object prev_area = iter_vm_area; iter_area_node = iter_area_node->next; - iter_vm_area = LN_GET_NODE_AREA(iter_area_node); + iter_vm_area = MC_GET_NODE_AREA(iter_area_node); //check if back to start of dl-list, and backtrack if true if (prev_area->start_addr > iter_vm_area->end_addr) { @@ -142,14 +158,15 @@ static cm_list_node * _fast_addr_find(const ln_vm_map * vm_map, } -//find object with a given basename/pathname -static cm_list_node * _obj_name_find(const ln_vm_map * vm_map, - const char * name, const int mode) { + +DBG_STATIC +cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, + const char * name, const int mode) { int ret; - cm_list_node * iter_obj_node; - ln_vm_obj * iter_vm_obj; + cm_lst_node * iter_obj_node; + mc_vm_obj * iter_vm_obj; //check map is not empty if (_is_map_empty(vm_map)) return NULL; @@ -157,13 +174,13 @@ static cm_list_node * _obj_name_find(const ln_vm_map * vm_map, //init search iter_obj_node = vm_map->vm_objs.head; - iter_vm_obj = LN_GET_NODE_OBJ(iter_obj_node); + iter_vm_obj = MC_GET_NODE_OBJ(iter_obj_node); //while can still iterate through objects for (int i = 0; i < vm_map->vm_objs.len; ++i) { //carry out comparison - if (mode == MAP_UTIL_USE_PATHNAME) { + if (mode == _MAP_UTIL_USE_PATHNAME) { ret = strncmp(name, iter_vm_obj->pathname, PATH_MAX); } else { ret = strncmp(name, iter_vm_obj->basename, NAME_MAX); @@ -175,7 +192,7 @@ static cm_list_node * _obj_name_find(const ln_vm_map * vm_map, } iter_obj_node = iter_obj_node->next; - iter_vm_obj = LN_GET_NODE_OBJ(iter_obj_node); + iter_vm_obj = MC_GET_NODE_OBJ(iter_obj_node); } //end object search @@ -184,40 +201,43 @@ static cm_list_node * _obj_name_find(const ln_vm_map * vm_map, -// --- EXTERNAL +/* + * --- [EXTERNAL] --- + */ -//get area offset -off_t ln_get_area_offset(const cm_list_node * area_node, const uintptr_t addr) { +off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr) { - ln_vm_area * vm_area = LN_GET_NODE_AREA(area_node); + mc_vm_area * vm_area = MC_GET_NODE_AREA(area_node); return (addr - vm_area->start_addr); } -//get obj offset -off_t ln_get_obj_offset(const cm_list_node * obj_node, const uintptr_t addr) { - ln_vm_obj * vm_obj = LN_GET_NODE_OBJ(obj_node); +off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr) { + + mc_vm_obj * vm_obj = MC_GET_NODE_OBJ(obj_node); return (addr - vm_obj->start_addr); } -//get area offset -off_t ln_get_area_offset_bnd(const cm_list_node * area_node, const uintptr_t addr) { - ln_vm_area * vm_area = LN_GET_NODE_AREA(area_node); +off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, + const uintptr_t addr) { + + mc_vm_area * vm_area = MC_GET_NODE_AREA(area_node); if ((addr >= vm_area->end_addr) || (addr < vm_area->start_addr)) return -1; return (addr - vm_area->start_addr); } -//get obj offset -off_t ln_get_obj_offset_bnd(const cm_list_node * obj_node, const uintptr_t addr) { - ln_vm_obj * vm_obj = LN_GET_NODE_OBJ(obj_node); +off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, + const uintptr_t addr) { + + mc_vm_obj * vm_obj = MC_GET_NODE_OBJ(obj_node); if ((addr >= vm_obj->end_addr) || (addr < vm_obj->start_addr)) return -1; return (addr - vm_obj->start_addr); @@ -225,56 +245,55 @@ off_t ln_get_obj_offset_bnd(const cm_list_node * obj_node, const uintptr_t addr) -//return the area node at a given address, optionally include offset into the area -cm_list_node * ln_get_vm_area_by_addr(const ln_vm_map * vm_map, +cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset) { - cm_list_node * area_node; + cm_lst_node * area_node; - area_node = _fast_addr_find(vm_map, addr, MAP_UTIL_GET_AREA); + area_node = _fast_addr_find(vm_map, addr, _MAP_UTIL_GET_AREA); if (!area_node) return NULL; - if (offset != NULL) *offset = ln_get_area_offset(area_node, addr); + if (offset != NULL) *offset = mc_get_area_offset(area_node, addr); return area_node; } -//return the obj node at a given address, optionally include offset into the obj -cm_list_node * ln_get_vm_obj_by_addr(const ln_vm_map * vm_map, + +cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset) { - cm_list_node * obj_node; + cm_lst_node * obj_node; - obj_node = _fast_addr_find(vm_map, addr, MAP_UTIL_GET_OBJ); + obj_node = _fast_addr_find(vm_map, addr, _MAP_UTIL_GET_OBJ); if (!obj_node) return NULL; - if (offset != NULL) *offset = ln_get_obj_offset(obj_node, addr); + if (offset != NULL) *offset = mc_get_obj_offset(obj_node, addr); return obj_node; } -//return object matching pathname -cm_list_node * ln_get_vm_obj_by_pathname(const ln_vm_map * vm_map, + +cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, const char * pathname) { - cm_list_node * obj_node; + cm_lst_node * obj_node; - obj_node = _obj_name_find(vm_map, pathname, MAP_UTIL_USE_PATHNAME); + obj_node = _obj_name_find(vm_map, pathname, _MAP_UTIL_USE_PATHNAME); if (!obj_node) return NULL; return obj_node; } -//return object matching basename -cm_list_node * ln_get_vm_obj_by_basename(const ln_vm_map * vm_map, + +cm_lst_node * mc_get_vm_obj_by_basename(const mc_vm_map * vm_map, const char * basename) { - cm_list_node * obj_node; + cm_lst_node * obj_node; - obj_node = _obj_name_find(vm_map, basename, MAP_UTIL_USE_BASENAME); + obj_node = _obj_name_find(vm_map, basename, _MAP_UTIL_USE_BASENAME); if (!obj_node) return NULL; return obj_node; diff --git a/src/lib/map_util.h b/src/lib/map_util.h index 35f84e3..d42032b 100644 --- a/src/lib/map_util.h +++ b/src/lib/map_util.h @@ -1,23 +1,41 @@ #ifndef MAP_UTIL_H #define MAP_UTIL_H -#include +//external libraries +#include -#include "liblain.h" +//local headers +#include "memcry.h" + + +#ifdef DEBUG +//internal +bool _is_map_empty(const mc_vm_map * vm_map); +cm_lst_node * _get_starting_obj(const mc_vm_map * vm_map); +cm_lst_node * _get_obj_last_area(const mc_vm_obj * vm_obj); +cm_lst_node * _fast_addr_find(const mc_vm_map * vm_map, + const uintptr_t addr, const int mode); +cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, + const char * name, const int mode); +#endif //external -off_t ln_get_area_offset(const cm_list_node * area_node, const uintptr_t addr); -off_t ln_get_obj_offset(const cm_list_node * obj_node, const uintptr_t addr); -off_t ln_get_area_offset_bnd(const cm_list_node * area_node, const uintptr_t addr); -off_t ln_get_obj_offset_bnd(const cm_list_node * obj_node, const uintptr_t addr); -cm_list_node * ln_get_vm_area_by_addr(const ln_vm_map * vm_map, +off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr); +off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr); +off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, + const uintptr_t addr); +off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, + const uintptr_t addr); + +cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset); -cm_list_node * ln_get_vm_obj_by_addr(const ln_vm_map * vm_map, +cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset); -cm_list_node * ln_get_vm_obj_by_pathname(const ln_vm_map * vm_map, + +cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, const char * pathname); -cm_list_node * ln_get_vm_obj_by_basename(const ln_vm_map * vm_map, +cm_lst_node * mc_get_vm_obj_by_basename(const mc_vm_map * vm_map, const char * basename); diff --git a/src/lib/memcry.h b/src/lib/memcry.h new file mode 100644 index 0000000..d60dcf8 --- /dev/null +++ b/src/lib/memcry.h @@ -0,0 +1,277 @@ +#ifndef MEMCRY_H +#define MEMCRY_H + +#ifdef __cplusplus +extern "C"{ +#endif + +//standard library +#include +#include +#include + +//system headers +#include +#include + +//external libraries +#include + + +//these macros take a cm_list_node pointer +#define MC_GET_NODE_AREA(node) ((mc_vm_area *) (node->data)) +#define MC_GET_NODE_OBJ(node) ((mc_vm_obj *) (node->data)) +#define MC_GET_NODE_PTR(node) *((cm_lst_node **) (node->data)) + +//mc_vm_area.access bitmasks +#define MC_ACCESS_READ 0x01 +#define MC_ACCESS_WRITE 0x02 +#define MC_ACCESS_EXEC 0x04 +#define MC_ACCESS_SHARED 0x08 + +//interface types +#define MC_IFACE_KRNCRY 0 +#define MC_IFACE_PROCFS 1 + +//pseudo object id +#define ZERO_OBJ_ID -1 + + + +/* + * --- [DATA TYPES] --- + */ + +// [memory area] +typedef struct { + + char * pathname; + char * basename; + + uintptr_t start_addr; + uintptr_t end_addr; + + cm_byte access; //logically AND with MC_ACCESS macros + + //mutually exclusive + cm_lst_node * obj_node_p; //STORES: parent mc_vm_obj * + cm_lst_node * last_obj_node_p; //STORES: last encountered mc_vm_obj * + + int id; + bool mapped; //set to false when a map update discovers area to be unmapped + +} mc_vm_area; + + +// [backing object] +typedef struct { + + char pathname[PATH_MAX]; + char basename[NAME_MAX]; + + uintptr_t start_addr; + uintptr_t end_addr; + + cm_lst vm_area_node_ps; //STORES: cm_list_node * of mc_vm_area + cm_lst last_vm_area_node_ps; //STORES: cm_list_node * of mc_vm_area + + int id; + bool mapped; //set to false when a map update discovers obj. to be unmapped + +} mc_vm_obj; + + +// [memory map] +typedef struct { + + //up to date entries + cm_lst vm_areas; //STORES: mc_vm_area + cm_lst vm_objs; //STORES: mc_vm_obj + + //unmapped entries (storage for future deallocation) + cm_lst vm_areas_unmapped; //STORES: cm_list_node * of mc_vm_area + cm_lst vm_objs_unmapped; //STORES: cm_list_node * of mc_vm_obj + + // [internal] + int next_id_area; + int next_id_obj; + +} mc_vm_map; + + + +// [session] +struct _mc_session; + +typedef struct { + + int (*open)(struct _mc_session *, int); + int (*close)(struct _mc_session *); + int (*update_map)(const struct _mc_session *, mc_vm_map *); + int (*read)(const struct _mc_session *, const uintptr_t, + cm_byte *, const size_t); + int (*write)(const struct _mc_session *, const uintptr_t, + const cm_byte *, const size_t); + +} mc_iface; + + +struct _mc_session { + + union { + struct { + int fd_mem; + pid_t pid; + }; //procfs_data + struct { + char major; + int fd_dev_krncry; + }; //krncry_data + }; + + long page_size; + mc_iface iface; + +}; +typedef struct _mc_session mc_session; + + + +/* + * --- [FUNCTIONS] --- + */ + +// [util] +//return: basename = success, NULL = fail/error +extern const char * mc_pathname_to_basename(const char * pathname); +//must destroy 'pid_vector' manually on success | pid = success, -1 = fail/error +extern pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector); +//return: 0 = success, -1 = fail/error +extern int mc_name_by_pid(const pid_t pid, char * name_buf); +//'out' must have space for double the length of 'inp' + 1 +extern void mc_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); + + +// [virtual interface] +//all return 0 = success, -1 = fail/error +extern int mc_open(mc_session * session, const int iface, const pid_t pid); +extern int mc_close(mc_session * session); +extern int mc_update_map(const mc_session * session, mc_vm_map * vm_map); +extern int mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +extern int mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); + +// --- [map] +//all return 0 = success, -1 = fail/error +extern void mc_new_vm_map(mc_vm_map * vm_map); +extern int mc_del_vm_map(mc_vm_map * vm_map); +extern int mc_map_clean_unmapped(mc_vm_map * vm_map); + + +// --- [map util] +//return: offset from start of area/object +extern off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr); +extern off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr); +//return: offset from start of area/object = success, -1 = not in area/object +extern off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, + const uintptr_t addr); +extern off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, + const uintptr_t addr); +//return area node * = success, NULL = fail/error +extern cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset); +//return obj node * = success, NULL = fail/error +extern cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset); +extern cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, + const char * pathname); +extern cm_lst_node * mc_get_vm_obj_by_basename(const mc_vm_map * vm_map, + const char * basename); + + +// --- [error handling] +//void return +extern void mc_perror(const char * prefix); +extern const char * mc_strerror(const int mc_errnum); + + + +/* + * --- [ERROR HANDLING] --- + */ + +extern __thread int mc_errno; + + +// [error codes] + +// 1XX - user errors +#define MC_ERR_PROC_MEM 2100 +#define MC_ERR_PROC_MAP 2101 +#define MC_ERR_SEEK_ADDR 2102 + +// 2XX - internal errors +#define MC_ERR_INTERNAL_INDEX 2200 +#define MC_ERR_UNEXPECTED_NULL 2201 +#define MC_ERR_LIBCMORE 2202 +#define MC_ERR_READ_WRITE 2203 +#define MC_ERR_MEMU_TARGET 2204 +#define MC_ERR_MEMU_MAP_SZ 2205 +#define MC_ERR_MEMU_MAP_GET 2206 +#define MC_ERR_PROC_STATUS 2207 +#define MC_ERR_PROC_NAV 2208 + +// 3XX - environmental errors +#define MC_ERR_MEM 2300 +#define MC_ERR_PAGESIZE 2301 +#define MC_ERR_KRNCRY_MAJOR 2302 +#define MC_ERR_MEMU_OPEN 2303 + + +// [error code messages] + +// 1XX - user errors +#define MC_ERR_PROC_MEM_MSG \ + "Could not open /proc//mem for specified pid.\n" +#define MC_ERR_PROC_MAP_MSG \ + "Could not open /proc//maps for specified pid.\n" +#define MC_ERR_SEEK_ADDR_MSG \ + "Could not seek to specified address.\n" + +// 2XX - internal errors +#define MC_ERR_INTERNAL_INDEX_MSG \ + "Internal: Indexing error.\n" +#define MC_ERR_UNEXPECTED_NULL_MSG \ + "Internal: Unexpected NULL pointer.\n" +#define MC_ERR_LIBCMORE_MSG \ + "Internal: Libcmore error. See cm_perror().\n" +#define MC_ERR_READ_WRITE_MSG \ + "Internal: Read/write failed.\n" +#define MC_ERR_MEMU_TARGET_MSG \ + "Internal: Lainko target open failed.\n" +#define MC_ERR_MEMU_MAP_SZ_MSG \ + "Internal: Lainko map size fetch failed..\n" +#define MC_ERR_MEMU_MAP_GET_MSG \ + "Internal: Lainko map transfer failed.\n" +#define MC_ERR_PROC_STATUS_MSG \ + "Internal: Failed to use /proc//status.\n" +#define MC_ERR_PROC_NAV_MSG \ + "Internal: Failed to navigate /proc directories.\n" + +// 3XX - environmental errors +#define MC_ERR_MEM_MSG \ + "Failed to acquire the necessary memory.\n" +#define MC_ERR_PAGESIZE_MSG \ + "Unable to fetch pagesize through sysconf().\n" +#define MC_ERR_KRNCRY_MAJOR_MSG \ + "Could not fetch krncry's major number.\n" +#define MC_ERR_MEMU_OPEN_MSG \ + "Could not open krncry device.\n" + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/procfs_iface.c b/src/lib/procfs_iface.c index c441559..dcf6ff4 100644 --- a/src/lib/procfs_iface.c +++ b/src/lib/procfs_iface.c @@ -1,29 +1,32 @@ +//standard library #include #include #include #include #include +//system headers #include - #include -#include +//external libraries +#include +//local headers #include "procfs_iface.h" -#include "liblain.h" +#include "memcry.h" #include "map.h" -#include "lainko.h" - - - -#define LINE_LEN PATH_MAX + 128 +#include "krncry.h" -// --- PROCFS INTERFACE INTERNALS +/* + * --- [INTERNAL] --- + */ + //build vm_entry from a line in procfs maps -static inline void _build_entry(struct vm_entry * entry, const char * line_buf) { +DBG_STATIC DBG_INLINE +void _build_entry(struct vm_entry * entry, const char * line_buf) { int mode = 0; int done = 0; @@ -37,6 +40,7 @@ static inline void _build_entry(struct vm_entry * entry, const char * line_buf) char perm_chars[] = {'r', 'w', 'x', 's'}; cm_byte perm_vals[] = {VM_READ, VM_WRITE, VM_EXEC, VM_SHARED}; + //save str for start addr start_str = (char *) line_buf; @@ -66,7 +70,7 @@ static inline void _build_entry(struct vm_entry * entry, const char * line_buf) //for every char in perms field for (int j = 0; j < 4; ++j) { if (*(line_buf + i + j) == perm_chars[j]) { - entry->prot += (lainko_pgprot_t) perm_vals[j]; + entry->prot += (krncry_pgprot_t) perm_vals[j]; } } //end for @@ -104,28 +108,31 @@ static inline void _build_entry(struct vm_entry * entry, const char * line_buf) } -// --- CALLED BY VIRTUAL INTERFACE -//open handles on /proc/pid/mem and /proc/pid/maps -int _procfs_open(ln_session * session, const int pid) { +/* + * --- [INTERFACE] --- + */ + +int procfs_open(mc_session * session, const int pid) { int fd; char mem_buf[PATH_MAX] = {0}; + session->pid = pid; - //get page size + //get page size to determine maximum read/write size session->page_size = sysconf(_SC_PAGESIZE); if (session->page_size < 0) { - ln_errno = LN_ERR_PAGESIZE; + mc_errno = MC_ERR_PAGESIZE; return -1; } - //open memory + //open procfs mem file snprintf(mem_buf, PATH_MAX, "/proc/%d/mem", pid); fd = open(mem_buf, O_RDWR); if (fd == -1) { - ln_errno = LN_ERR_PROC_MEM; + mc_errno = MC_ERR_PROC_MEM; return -1; } session->fd_mem = fd; @@ -134,17 +141,18 @@ int _procfs_open(ln_session * session, const int pid) { } -//close handles on /proc/pid/mem and /proc/pid/maps -int _procfs_close(ln_session * session) { +int procfs_close(mc_session * session) { + + //close procfs mem file close(session->fd_mem); return 0; } -//update or build a map -int _procfs_update_map(const ln_session * session, ln_vm_map * vm_map) { + +int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { int ret; FILE * fs; @@ -155,16 +163,17 @@ int _procfs_update_map(const ln_session * session, ln_vm_map * vm_map) { struct vm_entry new_entry; _traverse_state state; + //open memory map snprintf(map_buf, PATH_MAX, "/proc/%d/maps", session->pid); fs = fopen(map_buf, "r"); if (fs == NULL) { - ln_errno = LN_ERR_PROC_MAP; + mc_errno = MC_ERR_PROC_MAP; return -1; } //init traverse state for this map - _map_init_traverse_state(vm_map, &state); + map_init_traverse_state(vm_map, &state); //while there are entries left while (fgets(line_buf, LINE_LEN, fs) != NULL) { @@ -172,7 +181,7 @@ int _procfs_update_map(const ln_session * session, ln_vm_map * vm_map) { memset(&new_entry, 0, sizeof(new_entry)); _build_entry(&new_entry, line_buf); - ret = _map_send_entry(vm_map, &state, &new_entry); + ret = map_send_entry(vm_map, &state, &new_entry); if (ret != 0) return -1; } //end while @@ -185,14 +194,8 @@ int _procfs_update_map(const ln_session * session, ln_vm_map * vm_map) { } -/* - * Just calling read/write of buf_sz returns success, but the actual read/write - * fails on sizes above PAGESIZE. So all of the below is necessary. - * - */ -//read memory -int _procfs_read(const ln_session * session, const uintptr_t addr, +int procfs_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { off_t off_ret; @@ -203,7 +206,7 @@ int _procfs_read(const ln_session * session, const uintptr_t addr, //seek to address off_ret = lseek(session->fd_mem, (off_t) addr, SEEK_SET); if (off_ret == -1) { - ln_errno = LN_ERR_SEEK_ADDR; + mc_errno = MC_ERR_SEEK_ADDR; return -1; } @@ -219,7 +222,7 @@ int _procfs_read(const ln_session * session, const uintptr_t addr, ? session->page_size : read_left); //if error or EOF before reading len bytes if (read_bytes == -1 || (read_bytes == 0 && read_done < buf_sz)) { - ln_errno = LN_ERR_READ_WRITE; + mc_errno = MC_ERR_READ_WRITE; return -1; } read_done += read_bytes; @@ -230,8 +233,8 @@ int _procfs_read(const ln_session * session, const uintptr_t addr, } -//write memory -int _procfs_write(const ln_session * session, const uintptr_t addr, + +int procfs_write(const mc_session * session, const uintptr_t addr, const cm_byte * buf, const size_t buf_sz) { off_t off_ret; @@ -242,7 +245,7 @@ int _procfs_write(const ln_session * session, const uintptr_t addr, //seek to address off_ret = lseek(session->fd_mem, (off_t) addr, SEEK_SET); if (off_ret == -1) { - ln_errno = LN_ERR_SEEK_ADDR; + mc_errno = MC_ERR_SEEK_ADDR; return -1; } @@ -258,7 +261,7 @@ int _procfs_write(const ln_session * session, const uintptr_t addr, ? session->page_size : write_left); //if error or EOF before writing len bytes if (write_bytes == -1 || (write_bytes == 0 && write_done < buf_sz)) { - ln_errno = LN_ERR_READ_WRITE; + mc_errno = MC_ERR_READ_WRITE; return -1; } write_done += write_bytes; diff --git a/src/lib/procfs_iface.h b/src/lib/procfs_iface.h index 4ecb5ec..364b701 100644 --- a/src/lib/procfs_iface.h +++ b/src/lib/procfs_iface.h @@ -1,19 +1,31 @@ #ifndef PROC_IFACE_H #define PROC_IFACE_H -#include +//external libraries +#include -#include "liblain.h" +//local headers +#include "memcry.h" +#include "krncry.h" +#include "debug.h" +#define LINE_LEN PATH_MAX + 128 + + +#ifdef DEBUG //internal -int _procfs_open(ln_session * session, const int pid); -int _procfs_close(ln_session * session); -int _procfs_update_map(const ln_session * session, ln_vm_map * vm_map); -int _procfs_read(const ln_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -int _procfs_write(const ln_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +void _build_entry(struct vm_entry * entry, const char * line_buf); +#endif + +//interface +int procfs_open(mc_session * session, const int pid); +int procfs_close(mc_session * session); +int procfs_update_map(const mc_session * session, mc_vm_map * vm_map); +int procfs_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +int procfs_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); #endif diff --git a/src/lib/util.c b/src/lib/util.c index 6eb4232..4d60cd8 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -1,33 +1,38 @@ +//standard libraries #include #include #include #include +//system headers #include #include - #include - #include - -#include "liblain.h" +//local headers +#include "memcry.h" #include "util.h" +#include "debug.h" + +#define _LINE_LEN PATH_MAX + 128 -#define LINE_LEN PATH_MAX + 128 -// --- INTERNAL +/* + * --- [INTERNAL] --- + */ //convert the first line of /proc/pid/status to a name (comm) -static inline void _line_to_name(const char * line_buf, char * name_buf) { +DBG_STATIC DBG_INLINE +void _line_to_name(const char * line_buf, char * name_buf) { line_buf += 5; char * name_str; //for every character in the line - for (int i = 0; i < LINE_LEN; ++i) { + for (int i = 0; i < _LINE_LEN; ++i) { if (line_buf[i] == ' ' || line_buf[i] == '\t') continue; name_str = (char *) line_buf + 1; @@ -41,8 +46,14 @@ static inline void _line_to_name(const char * line_buf, char * name_buf) { } -//use /proc/pid/status to get the name of a process (mimic utils like 'ps') -static int _get_status_name(char * name_buf, const pid_t pid) { + +/* + * Use `/proc/pid/status` to get the name of a process. This is + * how utilities like `ps` and `top` fetch process names. + */ + +DBG_STATIC +int _get_status_name(char * name_buf, const pid_t pid) { int ret; char * fret; @@ -50,7 +61,7 @@ static int _get_status_name(char * name_buf, const pid_t pid) { FILE * fs; char path_buf[PATH_MAX]; - char line_buf[LINE_LEN]; + char line_buf[_LINE_LEN]; //build path @@ -59,22 +70,22 @@ static int _get_status_name(char * name_buf, const pid_t pid) { //get name fs = fopen(path_buf, "r"); if (fs == NULL) { - ln_errno = LN_ERR_PROC_STATUS; + mc_errno = MC_ERR_PROC_STATUS; return -1; } //read top line containing name (comm) of process - fret = fgets(line_buf, LINE_LEN, fs); + fret = fgets(line_buf, _LINE_LEN, fs); if (fret == NULL) { fclose(fs); - ln_errno = LN_ERR_PROC_STATUS; + mc_errno = MC_ERR_PROC_STATUS; return -1; } //close file stream ret = fclose(fs); if (ret == -1) { - ln_errno = LN_ERR_PROC_STATUS; + mc_errno = MC_ERR_PROC_STATUS; return -1; } @@ -89,10 +100,11 @@ static int _get_status_name(char * name_buf, const pid_t pid) { -// --- EXTERNAL +/* + * --- EXTERNAL + */ -//returns basename -const char * ln_pathname_to_basename(const char * pathname) { +const char * mc_pathname_to_basename(const char * pathname) { char * basename = strrchr(pathname, (int) '/'); @@ -101,8 +113,8 @@ const char * ln_pathname_to_basename(const char * pathname) { } -//get vector of pids matching name, return first match -pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { + +pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { int ret; int first_recorded = 0; @@ -118,16 +130,16 @@ pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { //initialise vector - ret = cm_new_vector(pid_vector, sizeof(pid_t)); + ret = cm_new_vct(pid_vector, sizeof(pid_t)); if (ret) { - ln_errno = LN_ERR_LIBCMORE; + mc_errno = MC_ERR_LIBCMORE; return -1; } //open proc directory ds = opendir("/proc"); if (ds == NULL) { - ln_errno = LN_ERR_PROC_NAV; + mc_errno = MC_ERR_PROC_NAV; return -1; } @@ -141,8 +153,8 @@ pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { temp_pid = (pid_t) strtoul(d_ent->d_name, NULL, 10); if (errno == ERANGE) { closedir(ds); - cm_del_vector(pid_vector); - ln_errno = LN_ERR_PROC_NAV; + cm_del_vct(pid_vector); + mc_errno = MC_ERR_PROC_NAV; return -1; } @@ -150,7 +162,7 @@ pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { ret = _get_status_name(name_buf, temp_pid); if (ret) { closedir(ds); - cm_del_vector(pid_vector); + cm_del_vct(pid_vector); return -1; } @@ -159,11 +171,11 @@ pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { if (!ret) { //add pid_t to list of potential PIDs - ret = cm_vector_append(pid_vector, (cm_byte *) &temp_pid); + ret = cm_vct_apd(pid_vector, (cm_byte *) &temp_pid); if (ret) { closedir(ds); - cm_del_vector(pid_vector); - ln_errno = LN_ERR_LIBCMORE; + cm_del_vct(pid_vector); + mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -179,8 +191,8 @@ pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { ret = closedir(ds); if (ret) { - cm_del_vector(pid_vector); - ln_errno = LN_ERR_PROC_NAV; + cm_del_vct(pid_vector); + mc_errno = MC_ERR_PROC_NAV; return -1; } @@ -188,8 +200,8 @@ pid_t ln_pid_by_name(const char * comm, cm_vector * pid_vector) { } -//get name of a pid -int ln_name_by_pid(const pid_t pid, char * name_buf) { + +int mc_name_by_pid(const pid_t pid, char * name_buf) { int ret; @@ -201,8 +213,8 @@ int ln_name_by_pid(const pid_t pid, char * name_buf) { } -//give byte string, return char hex string, double the size -void ln_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out) { + +void mc_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out) { cm_byte nibble; int count; diff --git a/src/lib/util.h b/src/lib/util.h index 81422c3..f80c43b 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -1,14 +1,21 @@ #ifndef UTIL_H #define UTIL_H -#include +//external libraries +#include -//external -const char * ln_pathname_to_basename(const char * pathname); -int ln_pid_by_name(const char * comm, cm_vector * pid_vector); -int ln_name_by_pid(const pid_t pid, char * name_buf); -void ln_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); +#ifdef DEBUG +//internal +void _line_to_name(const char * line_buf, char * name_buf); +int _get_status_name(char * name_buf, const pid_t pid); +#endif +//external +const char * mc_pathname_to_basename(const char * pathname); +int mc_pid_by_name(const char * comm, cm_vct * pid_vector); +int mc_name_by_pid(const pid_t pid, char * name_buf); +void mc_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); + #endif diff --git a/src/test/Makefile b/src/test/Makefile index 35bfc66..65d18ba 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -2,35 +2,33 @@ # This makefile takes the following variables: # -# CC - Compiler. -# BUILD_DIR - Base build directory. - -CFLAGS=-O0 -ggdb -Wno-unused-but-set-variable +# CC - Compiler. +# BUILD_DIR - Unit test build directory. +# LIB_BIN_DIR - Library artifact directory. +# +# _CFLAGS - Compiler flags. +# _WARN_OPTS - Compiler warnings. -INCLUDE=-L${BUILD_DIR}/lib -Wl,-rpath=${BUILD_DIR}/lib -lcmore -llain -SOURCES_TEST=iface.c map.c main.c util.c -HEADERS_TEST=test.h -OBJECTS_TEST=${SOURCES_TEST:.c=.o} +CFLAGS=${_CFLAGS} -fsanitize=address +WARN_OPTS+=${_WARN_OPTS} -Wno-unused-variable -Wno-unused-but-set-variable +LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} -lcmore -lcheck -static-libasan -TEST=run_tests +SOURCES_TEST=main.c +OBJECTS_TEST=${SOURCES_TEST:%.c=${BUILD_DIR}/%.o} -test:${TEST} -> echo ${BUILD_DIR}/lib -> mkdir -p ${BUILD_DIR}/test -> mv ${TEST} ${BUILD_DIR}/test +TESTS=test -${TEST}: ${OBJECTS_TEST} -> ${CC} ${CFLAGS} ${OBJECTS_TEST} ${HEADERS_TEST} \ - -o ${TEST} ${INCLUDE} -${OBJECTS_LIB}: ${SOURCES_LIB} ${HEADERS_LIB} -> ${CC} ${CFLAGS} -c ${SOURCES_TEST} -o ${OBJECTS_TEST} ${INCLUDE} +tests: ${TESTS} +> mkdir -p ${BUILD_DIR} +> mv ${TESTS} ${BUILD_DIR} -clean_all: clean_src clean_build +${TESTS}: ${OBJECTS_TEST} +> ${CC} ${CFLAGS} ${WARN_OPTS} -o $@ $^ ${LDFLAGS} -clean_src: -> -rm -f *.o +${BUILD_DIR}/%.o: %.c +> ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ -clean_build: -> -rm ${BUILD_DIR}/test/${TEST} +clean: +> -rm -v ${BUILD_DIR}/{*.o,${TESTS}} diff --git a/src/test/check_map.c b/src/test/check_map.c new file mode 100644 index 0000000..dc867f3 --- /dev/null +++ b/src/test/check_map.c @@ -0,0 +1,255 @@ +//standard library +#include +#include +#include + +//system headers +#include +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" + +//test target headers +#include "../lib/memcry.h" +#include "../lib/map.h" + + + +/* + * --- [FIXTURES] --- + */ + +//globals +static mc_vm_map m; + + + +//empty virtual memory map setup +static void _setup_empty_vm_map() { + + mc_new_vm_map(&m); + + return; +} + + + +static void _teardown() { + + mc_del_vm_map(&m); + + return; +} + + + +/* + * --- [HELPERS] --- + */ + +//construct a vm_entry stub +static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, + unsigned long vm_end, unsigned long file_off, + krncry_pgprot_t prot, char * file_path) { + + entry->vm_start = vm_start; + entry->vm_end = vm_end; + entry->file_off = file_off; + entry->prot = prot; + strncpy(entry->file_path, file_path, PATH_MAX); + + return; +} + + + +//integration test for CMore +static void _assert_lst_len(cm_lst * list, int len) { + + ck_assert_int_eq(list->len, len); + + //if length is zero (0), ensure head is null + if (len == 0) { + ck_assert_ptr_null(list->head); + return; + } + + //if length is one (1), ensure head is not null + ck_assert_ptr_nonnull(list->head); + cm_lst_node * iter = list->head; + if (len == 1) return; + + //if length is greater than 1 (1), iterate over nodes to ensure length + ck_assert_ptr_nonnull(iter->next); + iter = iter->next; + + for (int i = 1; i < len; ++i) { + + ck_assert(iter != list->head); + iter = iter->next; + } + + return; +} + + + +static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, + uintptr_t start_addr, uintptr_t end_addr, + int vm_areas_len, int last_vm_areas_len, + int id, bool mapped) { + + //check names + ck_assert_str_eq(obj->pathname, pathname); + ck_assert_str_eq(obj->basename, basename); + + //check addresses + ck_assert_int_eq(obj->start_addr, start_addr); + ck_assert_int_eq(obj->end_addr, end_addr); + + //check area node lists are initialised + _assert_lst_len(&obj->vm_area_node_ps, vm_areas_len); + _assert_lst_len(&obj->last_vm_area_node_ps, last_vm_areas_len); + + //check the object id is correctly set + ck_assert_int_eq(obj->id, id); + + //check the object is set as mapped + ck_assert(obj->mapped == mapped); + + return; +} + + + +static void _assert_vm_area(mc_vm_area * area, char * pathname, char * basename, + uintptr_t start_addr, uintptr_t end_addr, + cm_byte access, cm_lst_node * obj_node_p, + cm_lst_node * last_obj_node_p, + int id, bool mapped) { + + //check names + ck_assert_str_eq(area->pathname, pathname); + ck_assert_str_eq(area->basename, basename); + + //check addresses + ck_assert_int_eq(area->start_addr, start_addr); + ck_assert_int_eq(area->end_addr, end_addr); + + //check access + ck_assert(area->access == access); + + //check object pointers + ck_assert_ptr_eq(area->obj_node_p, obj_node_p); + ck_assert_ptr_eq(area->last_obj_node_p, last_obj_node_p); + + //check the area id is correctly set + ck_assert_int_eq(area->id, id); + + //check the area is mapped + ck_assert(area->mapped == mapped); + + return; +} + + + +/* + * --- [UNIT TESTS] --- + */ + +//mc_new_vm_map() & mc_del_vm_map() [no fixture] +START_TEST(test_mc_new_del_vm_map) { + + mc_vm_obj * zero_obj; + + mc_new_vm_map(&m); + + //check list lengths + _assert_lst_len(&m.vm_areas, 0); + _assert_lst_len(&m.vm_objs, 1); + _assert_lst_len(&m.vm_areas_unmapped, 0); + _assert_lst_len(&m.vm_objs_unmapped, 0); + + //check zero object + zero_obj = MC_GET_NODE_OBJ(m.vm_objs.head); + _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, ZERO_OBJ_ID, true); + + mc_del_vm_map(&m); + + return; + +} END_TEST + + + +//_map_new_vm_obj() & _map_del_vm_obj() [empty fixture] +START_TEST(test__map_new_del_vm_obj) { + + mc_vm_obj obj, * obj_p; + + _map_new_vm_obj(&obj, &m, "/foo/bar"); + + //check object creation + obj_p = MC_GET_NODE_OBJ(m.vm_objs.head); + _assert_vm_obj(obj_p, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); + + //check the map's next_id_obj is incremented + ck_assert_int_eq(m.next_id_obj, 1); + + return; +} + + + +//_map_init_vm_area() [empty fixture] +START_TEST(test__map_init_vm_area) { + + mc_vm_obj obj; + mc_vm_area area; + struct vm_entry entry; + cm_lst_node obj_node; + + //create an object for the area to reference + _map_new_vm_obj(&obj, &m, "/foo/bar"); + + //create a stub list node to hold the object + obj_node.data = &obj; + obj_node.next = obj_node.prev = NULL; + + + //create a stub entry & initialise a new area + _init_vm_entry(&entry, 0x1000, 0x2000, 0x800, + MC_ACCESS_READ, "/foo/bar"); + _map_init_vm_area(&area, &m, &obj_node, NULL, &entry); + + //check area is correctly created + _assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, + MC_ACCESS_READ, &obj_node, NULL, 0, true); + + //check map's next id is incremented + ck_assert_int_eq(m.next_id_area, 1); + + + //create a stub entry & initialise another new area + _init_vm_entry(&entry, 0x2000, 0x4000, 0x800, + MC_ACCESS_READ | MC_ACCESS_WRITE, "/purr/meow"); + _map_init_vm_area(&area, &m, NULL, &obj_node, &entry); + + //check area is correctly created + _assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &obj_node, 1, true); + + //check map's next id is incremented + ck_assert_int_eq(m.next_id_area, 1); + + + //deallocate the object + _map_del_vm_obj(&obj); + +} END_TEST diff --git a/src/test/iface.c b/src/test/iface.c deleted file mode 100644 index 2dd7851..0000000 --- a/src/test/iface.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include - -#include - -#include - -#include "../lib/liblain.h" -#include "test.h" - - - -//run test for an interface specified by _iface_ -void test_iface(pid_t pid, int iface, ln_vm_map * vm_map, uintptr_t test_addr) { - - printf("\n\n --- [IFACE] --- \n\n"); - printf("notice: use a debugger\n"); - - int ret; - size_t bytes; - - ln_session session; - cm_byte buf[0x2000]; - - //prepare write buffer - memset(buf, (int) 'c', 0x2000); - - //open the map - ret = ln_open(&session, iface, pid); - - //initialise the map - ret = ln_update_map(&session, vm_map); - - //wait for target to change - printf("press enter when target's map has changed: "); - char cont[8]; - fgets(cont, 8, stdin); - fgets(cont, 8, stdin); - - //update the map - ret = ln_update_map(&session, vm_map); - - //read & write - //bytes = ln_write(&session, test_addr, buf, 0x2000); - //bytes = ln_read(&session, test_addr, buf, 0x2000); - - //close session - ret = ln_close(&session); - - return; -} diff --git a/src/test/main.c b/src/test/main.c index 31d0a61..2680438 100644 --- a/src/test/main.c +++ b/src/test/main.c @@ -1,42 +1,90 @@ +//standard library #include -#include +//system headers #include +#include -#include +//external libraries +#include -#include +//local headers +#include "suites.h" -#include "../lib/liblain.h" -#include "test.h" -int main() { +enum _test_mode {UNIT}; - int ret, iface; - uintptr_t test_addr; - pid_t pid; - char comm[NAME_MAX]; - ln_vm_map vm_map; +//determine which tests to run +static enum _test_mode _get_test_mode(int argc, char ** argv) { - //test utils - printf("target name: "); - scanf("%s", comm); - pid = test_utils(comm); + const struct option long_opts[] = { + {"unit-tests", no_argument, NULL, 'u'}, + {0,0,0,0} + }; - ln_new_vm_map(&vm_map); + int opt; + enum _test_mode test_mode = UNIT; - //test ifaces - printf("interface (0 - lainko, 1 - procfs): "); - scanf("%d", &iface); - printf("rw- buffer to read/write 0x2000 bytes from/to: "); - scanf("%lx", &test_addr); - test_iface(pid, iface, &vm_map, test_addr); + + while((opt = getopt_long(argc, argv, "u", long_opts, NULL)) != -1 + && opt != 0) { - //test map - test_map(&vm_map); + //determine parsed argument + switch (opt) { - ret = ln_del_vm_map(&vm_map); + case 'u': + test_mode = UNIT; + break; + } + } + return test_mode; +} + + + +//run unit tests +static void _run_unit_tests() { + + Suite * s_iface; + Suite * s_krncry_iface; + Suite * s_procfs_iface; + Suite * s_map; + Suite * s_map_util; + Suite * util; + + SRunner * sr; + + //initialise test suites + // TODO s_test1 = test1_suite(); + + //create suite runner + //sr = srunner_create(s_test1); + //srunner_add_suite(sr, s_test2); + + //run tests + srunner_run_all(sr, CK_VERBOSE); + + //cleanup + srunner_free(sr); + + return; +} + + + +//dispatch tests +int main(int argc, char ** argv) { + + enum _test_mode mode = _get_test_mode(argc, argv); + + switch (mode) { + + case UNIT: + _run_unit_tests(); + break; + } + return 0; } diff --git a/src/test/map.c b/src/test/map.c deleted file mode 100644 index 3b73219..0000000 --- a/src/test/map.c +++ /dev/null @@ -1,210 +0,0 @@ -#include - -#include - -#include "../lib/liblain.h" -#include "test.h" - - -void print_map_area(ln_vm_area * area, char * prefix) { - - fprintf(stderr, "%spathname: %s\n", prefix, area->pathname); - fprintf(stderr, "%sbasename: %s\n", prefix, area->basename); - - fprintf(stderr, "%sstart_addr: %lx\n", prefix, area->start_addr); - fprintf(stderr, "%send_addr: %lx\n", prefix, area->end_addr); - - int mask_iter = 0; - int mask_start = 1; - char perm[] = {'r', 'w', 'x', 's'}; - - //print permissions - fprintf(stderr, "%saccess: ", prefix); - do { - - if (area->access & mask_start) { - fprintf(stderr, "%c", perm[mask_iter]); - } else { - fprintf(stderr, "%c", '-'); - } - - mask_start *= 2; - ++mask_iter; - } while (mask_iter < 4); - fprintf(stderr, "%c", '\n'); - - fprintf(stderr, "%sid: %d\n", prefix, area->id); - fprintf(stderr, "%smapped: %s", prefix, area->mapped ? "true" : "false"); - fprintf(stderr, "\n"); - - return; -} - - -void print_map_obj(ln_vm_obj * obj) { - - int ret; - - cm_list_node * node; - ln_vm_area * area; - - fprintf(stderr, "\tpathname: %s\n", obj->pathname); - fprintf(stderr, "\tbasename: %s\n", obj->basename); - - fprintf(stderr, "\tstart_addr: %lx\n", obj->start_addr); - fprintf(stderr, "\tend_addr: %lx\n", obj->end_addr); - - fprintf(stderr, "\tid: %d\n", obj->id); - fprintf(stderr, "\tmapped: %s", obj->mapped ? "true" : "false"); - - fprintf(stderr, "\n\tnodes:\n"); - - for (int i = 0; i < obj->vm_area_node_ptrs.len; ++i) { - - ret = cm_list_get_val(&obj->vm_area_node_ptrs, i, (cm_byte *) &node); - area = LN_GET_NODE_AREA(node); - - fprintf(stderr, "\n\t\t[AREA %d]\n", i); - print_map_area(area, "\t\t"); - - } //end for - - fprintf(stderr, "\n"); - - return; -} - - -//print out map areas in chronological order to stdout -void print_areas(ln_vm_map * vm_map) { - - cm_list_node * node; - ln_vm_area * area; - - for (int i = 0; i < vm_map->vm_areas.len; ++i) { - - node = cm_list_get_node(&vm_map->vm_areas, i); - area = LN_GET_NODE_AREA(node); - - fprintf(stderr, "\n[AREA %d]\n", i); - print_map_area(area, "\t"); - - } //end for - - return; -} - - -void print_objs(ln_vm_map * vm_map) { - - cm_list_node * node; - ln_vm_obj * obj; - - for (int i = 0; i < vm_map->vm_objs.len; ++i) { - - node = cm_list_get_node(&vm_map->vm_objs, i); - obj = LN_GET_NODE_OBJ(node); - - fprintf(stderr, "\n[OBJ %d]\n", i); - print_map_obj(obj); - } - - return; -} - - -//print unmapped areas -void print_unmapped_areas(ln_vm_map * vm_map) { - - int ret; - cm_list_node * node; - ln_vm_area * area; - - int mask_start = 1; - int mask_iter = 0; - char perm[] = {'r', 'w', 'x', 's'}; - - for (int i = 0; i < vm_map->vm_areas_unmapped.len; ++i) { - - ret = cm_list_get_val(&vm_map->vm_areas_unmapped, i, (cm_byte *) &node); - area = LN_GET_NODE_AREA(node); - - fprintf(stderr, "\n[AREA %d]\n", i); - print_map_area(area, "\t"); - - } //end for - - return; -} - - -//print unmapped objs -void print_unmapped_objs(ln_vm_map * vm_map) { - - int ret; - cm_list_node * node; - ln_vm_obj * obj; - - for (int i = 0; i < vm_map->vm_objs_unmapped.len; ++i) { - - ret = cm_list_get_val(&vm_map->vm_objs_unmapped, i, (cm_byte *) &node); - obj = LN_GET_NODE_OBJ(node); - - fprintf(stderr, "\n[OBJ %d]\n", i); - print_map_obj(obj); - } - - return; -} - - -void test_map(ln_vm_map * vm_map) { - - printf("\n\n --- [MAP & MAP UTILS] --- \n\n"); - printf("note: use a debugger to test map utils.\n"); - - int ret; - off_t off; - char * name; - char * badname = "lain"; - - cm_list_node * node; - ln_vm_area * area; - ln_vm_obj * obj; - - //test obj by name - node = cm_list_get_node(&vm_map->vm_areas, 1); - area = LN_GET_NODE_AREA(node); - name = area->pathname; - - node = ln_get_vm_obj_by_pathname(vm_map, name); - obj = LN_GET_NODE_OBJ(node); - name = obj->basename; - - node = ln_get_vm_obj_by_basename(vm_map, name); - obj = LN_GET_NODE_OBJ(node); - - //test area/obj by address - node = ln_get_vm_area_by_addr(vm_map, obj->start_addr + 0x500, &off); - area = LN_GET_NODE_AREA(node); - - node = ln_get_vm_obj_by_addr(vm_map, area->start_addr + 0x500, &off); - obj = LN_GET_NODE_OBJ(node); - - //dump areas - print_areas(vm_map); - - //dump objs - print_objs(vm_map); - - //dump unmapped areas - print_unmapped_areas(vm_map); - - //dump unmapped objs - print_unmapped_objs(vm_map); - - //clean unmapped areas/objs - ret = ln_map_clean_unmapped(vm_map); - - return; -} diff --git a/src/test/suites.h b/src/test/suites.h new file mode 100644 index 0000000..160cce2 --- /dev/null +++ b/src/test/suites.h @@ -0,0 +1,19 @@ +#ifndef SUITES_H +#define SUITES_H + +//external libraries +#include + + +//unit test suites +Suite * iface_suite(); +Suite * krncry_iface_suite(); +Suite * procfs_iface_suite(); +Suite * map_suite(); +Suite * map_util_suite(); +Suite * util_suite(); + +//other tests +//TODO + +#endif diff --git a/src/test/test.h b/src/test/test.h deleted file mode 100644 index 36df017..0000000 --- a/src/test/test.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef TEST_H -#define TEST_H - -#include - -#include - -#include "../lib/liblain.h" - -//utils -pid_t test_utils(char * comm); - -//iface -void test_iface(pid_t pid, int iface, ln_vm_map * vm_map, uintptr_t test_addr); - -//map -void test_map(ln_vm_map * vm_map); - -//extras -void print_map_area(ln_vm_area * area, char * prefix); -void print_map_obj(ln_vm_obj * obj); -void print_map_areas(ln_vm_map * vm_map); -void print_map_objs(ln_vm_map * vm_map); - -#endif diff --git a/src/test/util.c b/src/test/util.c deleted file mode 100644 index 57793d6..0000000 --- a/src/test/util.c +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include - -#include - -#include - -#include "../lib/liblain.h" -#include "test.h" - - -pid_t test_utils(char * comm) { - - printf("\n\n --- [UTILS] --- \n\n"); - - int ret; - pid_t pid, pid_iter; - - char name_buf[NAME_MAX]; - cm_byte hex_buf[4] = {0xde, 0xad, 0xc0, 0xde}; - char hexstr_buf[10]; - - cm_vector pid_vector; - - //test pathname to basename conversion - char * path = "/everything/is/connected"; - char * base = ln_pathname_to_basename(path); - printf("basename of '%s': '%s'\n", path, base); - - //test converting process name to pid(s) - pid = ln_pid_by_name(comm, &pid_vector); - printf("comm: %s, pid: %d\n", comm, pid); - - for (int i = 0; i < pid_vector.len; ++i) { - ret = cm_vector_get_val(&pid_vector, i, (cm_byte *) &pid_iter); - printf("\npid %d: %d\n", i, pid_iter); - } //end for - cm_del_vector(&pid_vector); - - //test converting pid to process name - ret = ln_name_by_pid(pid, name_buf); - printf("pid: %d, comm: %s\n", pid, name_buf); - - //test bytes to hex string - - ln_bytes_to_hex(hex_buf, 4, hexstr_buf); - printf("should see 'deadcode': %s\n", hexstr_buf); - - return pid; -} diff --git a/src/tgt/Makefile b/src/tgt/Makefile deleted file mode 100644 index 8669016..0000000 --- a/src/tgt/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -.RECIPEPREFIX:=> - -# This makefile takes the following variables: -# -# CC - Compiler. -# BUILD_DIR - Base build directory. - -CFLAGS=-O0 -ggdb -Wno-unused-but-set-variable -L${BUILD_DIR}/lib -Wl,-rpath=${BUILD_DIR}/lib -lcmore -llain - -SOURCES_TGT=main.c -HEADERS_TGT= -OBJECTS_TGT=${SOURCES_TGT:.c=.o} - -TGT=target - -tgt:${TGT} -> mkdir -p ${BUILD_DIR}/tgt -> mv ${TGT} ${BUILD_DIR}/tgt - -${TGT}: ${OBJECTS_TGT} -> ${CC} ${CFLAGS} ${OBJECTS_TGT} ${HEADERS_TGT} \ - -o ${TGT} - -${OBJECTS_LIB}: ${SOURCES_LIB} ${HEADERS_LIB} -> ${CC} ${CFLAGS} -c ${SOURCES_TGT} -o ${OBJECTS_TGT} - -clean_all: clean_src clean_build - -clean_src: -> -rm -f *.o - -clean_build: -> -rm ${BUILD_DIR}/tgt/${TGT} diff --git a/src/tgt/main.c b/src/tgt/main.c deleted file mode 100644 index 01dd007..0000000 --- a/src/tgt/main.c +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define FILESIZE 4096 // 4 KB - -int main() { - - // Create a mapping before main executable - void * zero_mapping = mmap((void *) 0x100000, 0x2000, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, - -1, 0x0); - - // Open "/etc/fstab" file - int fd = open("/etc/fstab", O_RDONLY); - if (fd == -1) { - perror("Error opening file for reading"); - exit(EXIT_FAILURE); - } - - // Memory map empty 8KB - char * fillMePtr = mmap(0, 8196, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (fillMePtr == MAP_FAILED) { - close(fd); - perror("Error mmapping the file"); - exit(EXIT_FAILURE); - } - - // Memory map the first 4KB of the file - char *dataPtr = mmap(0, FILESIZE, PROT_READ, MAP_SHARED, fd, 0); - if (dataPtr == MAP_FAILED) { - close(fd); - perror("Error mmapping the file"); - exit(EXIT_FAILURE); - } - - // Load libutil.so.1 - void *libutil_handle = dlopen("libutil.so.1", RTLD_LAZY); - if (!libutil_handle) { - fprintf(stderr, "%s\n", dlerror()); - exit(EXIT_FAILURE); - } - - - // Do something with the memory mapped file - printf("Press enter to continue: "); - char cont; - scanf("%c", &cont); - - - // Unmap the file - if (munmap(zero_mapping, FILESIZE) == -1) { - perror("Error un-mmapping the file"); - exit(EXIT_FAILURE); - } - - // Unmap the file - if (munmap(dataPtr, FILESIZE) == -1) { - perror("Error un-mmapping the file"); - exit(EXIT_FAILURE); - } - - // Close the file descriptor - close(fd); - - - // Unload libutil.so.1 - if (dlclose(libutil_handle) != 0) { - fprintf(stderr, "%s\n", dlerror()); - exit(EXIT_FAILURE); - } - - // Load libdl.so.2 - void *lib_handle = dlopen("libdl.so.2", RTLD_LAZY); - if (!lib_handle) { - fprintf(stderr, "%s\n", dlerror()); - exit(EXIT_FAILURE); - } - - printf("Press enter to continue again: "); - char cont2; - scanf("%c", &cont2); - - // Unload libdl.so.2 - if (dlclose(lib_handle) != 0) { - fprintf(stderr, "%s\n", dlerror()); - exit(EXIT_FAILURE); - } - - // Unmap empty 8KB - if (munmap(fillMePtr, 8192) == -1) { - perror("Error un-mapping "); - exit(EXIT_FAILURE); - } - - return 0; -} - diff --git a/src/tgt/tgt b/src/tgt/tgt deleted file mode 100755 index 8704017af4eba43b093a7a4a90f8b36b2321d75e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19200 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 From 8847359daf23dde3bfcdad1f4aa42c6d39b5c074 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 22 Dec 2024 05:49:44 +0000 Subject: [PATCH 03/45] add unit tests --- src/lib/map.c | 147 +++++++----- src/lib/map.h | 18 +- src/test/check_map.c | 534 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 574 insertions(+), 125 deletions(-) diff --git a/src/lib/map.c b/src/lib/map.c index 1a8304e..757d9e8 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -21,6 +21,11 @@ * --- [INTERNAL] */ +/* + * Note that while the vm_area initialiser function does take a vm_obj node + * as a parameter, the state of this vm_obj is not changed. + */ + DBG_STATIC void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, const cm_lst_node * obj_node, @@ -62,6 +67,12 @@ void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, +/* + * Note that while the vm_obj constructor does take a vm_map pointer as a + * parameter, the vm_obj is not added to the vm_map in the constructor. Only + * the `next_id_obj` counter is incremented. + */ + DBG_STATIC void _map_new_vm_obj(mc_vm_obj * vm_obj, mc_vm_map * vm_map, const char * pathname) { @@ -110,10 +121,52 @@ void _map_make_zero_obj(mc_vm_obj * vm_obj) { +DBG_STATIC +int _map_obj_add_area_insert(cm_lst * obj_area_lst, + const cm_lst_node * area_node) { + + cm_lst_node * ret_node, * iter_node; + mc_vm_area * area = MC_GET_NODE_AREA(area_node); + + //insert the new area at an appropriate index + iter_node = obj_area_lst->head; + for (int i = 0; i < obj_area_lst->len; ++i) { + + //new area ends at a lower address than some existing area + if (area->end_addr < MC_GET_NODE_AREA(iter_node)->end_addr) { + + ret_node = cm_lst_ins_nb(obj_area_lst, iter_node, &area_node); + break; + } + + //new area ends later than any existing area + if ((iter_node->next == NULL) + || (iter_node->next == obj_area_lst->head)) { + + ret_node = cm_lst_ins_na(obj_area_lst, iter_node, &area_node); + break; + } + + //increment node iteration + iter_node = iter_node->next; + } + + //check the new area was inserted successfully + if (ret_node == NULL) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } + + return 0; +} + + + DBG_STATIC DBG_INLINE -int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node) { +int _map_obj_add_area(mc_vm_obj * obj, + const cm_lst_node * area_node) { - cm_lst_node * ret_node; + int ret; mc_vm_area * area = MC_GET_NODE_AREA(area_node); //if this object has no areas yet @@ -122,7 +175,7 @@ int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node) { //set new addr bounds obj->start_addr = area->start_addr; obj->end_addr = area->end_addr; - + //if this object has areas, only update the start and end addr if necessary } else { @@ -130,34 +183,29 @@ int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node) { if (area->start_addr < obj->start_addr) obj->start_addr = area->start_addr; if (area->end_addr > obj->end_addr) - obj->end_addr = area->end_addr; - } + obj->end_addr = area->end_addr; - //simply appending preserves chronological order; out of order vm_areas - //are guaranteed(?) to be removed - ret_node = cm_lst_apd(&obj->vm_area_node_ps, &area_node); - if (ret_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; - return -1; + } + //insert new area & ensure the area list remains sorted + ret = _map_obj_add_area_insert(&obj->vm_area_node_ps, area_node); + if (ret) return -1; + return 0; } DBG_STATIC DBG_INLINE -int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node) { - - cm_lst_node * ret_node; +int _map_obj_add_last_area(mc_vm_obj * obj, + const cm_lst_node * last_area_node) { - //simply appending preserves chronological order; out of order vm_areas - //are guaranteed(?) to be removed - ret_node = cm_lst_apd(&obj->last_vm_area_node_ps, &last_area_node); - if (ret_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } + int ret; + + //insert new last area & ensure the last area list remains sorted + ret = _map_obj_add_area_insert(&obj->last_vm_area_node_ps, last_area_node); + if (ret) return -1; return 0; } @@ -176,9 +224,6 @@ bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { -#define _MAP_OBJ_PREV 0 -#define _MAP_OBJ_NEW 1 -#define _MAP_OBJ_NEXT 2 DBG_STATIC DBG_INLINE int _map_find_obj_for_area(const _traverse_state * state, const struct vm_entry * entry) { @@ -187,10 +232,10 @@ int _map_find_obj_for_area(const _traverse_state * state, mc_vm_obj * prev_obj, * next_obj; //check for null - if (state->prev_obj == NULL) return _MAP_OBJ_NEW; + if (state->prev_obj_node == NULL) return _MAP_OBJ_NEW; //get prev obj - prev_node = state->prev_obj; + prev_node = state->prev_obj_node; prev_obj = MC_GET_NODE_OBJ(prev_node); if (prev_node->next == NULL) { @@ -201,38 +246,16 @@ int _map_find_obj_for_area(const _traverse_state * state, } //return appropriate index - if (_is_pathname_in_obj(entry->file_path, prev_obj)) return _MAP_OBJ_PREV; - if (_is_pathname_in_obj(entry->file_path, next_obj)) return _MAP_OBJ_NEXT; + if (_map_is_pathname_in_obj(entry->file_path, prev_obj)) + return _MAP_OBJ_PREV; + if (_map_is_pathname_in_obj(entry->file_path, next_obj)) + return _MAP_OBJ_NEXT; return _MAP_OBJ_NEW; } -DBG_STATIC DBG_INLINE -int _map_get_area_index(const mc_vm_area * area, const mc_vm_obj * obj) { - - int ret; - - cm_lst_node * temp_node; - mc_vm_area * temp_area; - - //for all vm areas in this object - for (int i = 0; i < obj->vm_area_node_ps.len; ++i) { - - ret = cm_lst_get(&obj->vm_area_node_ps, i, &temp_node); - if (ret) return -1; - temp_area = MC_GET_NODE_AREA(temp_node); - - if (temp_area->id == area->id) return i; - - } //end for - - return -1; -} - - - DBG_STATIC DBG_INLINE int _map_update_obj_addr_range(mc_vm_obj * obj) { @@ -241,6 +264,12 @@ int _map_update_obj_addr_range(mc_vm_obj * obj) { cm_lst_node * temp_node; mc_vm_area * temp_area; + if (obj->vm_area_node_ps.len == 0) { + + obj->end_addr = obj->start_addr = 0x0; + return 0; + } + //get start addr ret = cm_lst_get(&obj->vm_area_node_ps, 0, &temp_node); if (ret) { @@ -252,11 +281,7 @@ int _map_update_obj_addr_range(mc_vm_obj * obj) { obj->start_addr = temp_area->start_addr; //get end addr - if (obj->vm_area_node_ps.len == 1) { - end_index = 0; - } else { - end_index = -1; - } + end_index = obj->vm_area_node_ps.len == 1 ? 0 : -1; ret = cm_lst_get(&obj->vm_area_node_ps, end_index, &temp_node); if (ret) { @@ -274,7 +299,7 @@ int _map_update_obj_addr_range(mc_vm_obj * obj) { //called when deleting an object; moves `last_obj_node_p` back if needed DBG_STATIC DBG_INLINE -int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * node) { +int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { int ret; int iterations; @@ -321,7 +346,7 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * node) { //called when adding an object; moves `last_obj_node_p` forward if needed DBG_STATIC DBG_INLINE -int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * node) { +int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { int ret; @@ -479,7 +504,7 @@ int _map_unlink_unmapped_area(cm_lst_node * node, //else set the new start and end addr for this object } else { - ret = _update_obj_addr_range(temp_obj); + ret = _update_obj_addr_range(temp_obj); if (ret) return -1; } @@ -832,7 +857,7 @@ void mc_new_vm_map(mc_vm_map * vm_map) { cm_lst_apd(&vm_map->vm_objs, &zero_obj); - //reset id + //reset next object id back to zero vm_map->next_id_area = vm_map->next_id_obj = 0; return; diff --git a/src/lib/map.h b/src/lib/map.h index 29f9d6d..7678c13 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -10,6 +10,11 @@ #define DEBUG + +#define _MAP_OBJ_PREV 0 +#define _MAP_OBJ_NEW 1 +#define _MAP_OBJ_NEXT 2 + /* * Initialise _traverse_state manually on the first call to * send_map_node() for a map generated by a memory interface. @@ -20,8 +25,8 @@ typedef struct { - cm_lst_node * next_area; //mc_vm_area - cm_lst_node * prev_obj; //mc_vm_obj + cm_lst_node * next_area_node; //mc_vm_area + cm_lst_node * prev_obj_node; //mc_vm_obj } _traverse_state; @@ -38,15 +43,18 @@ void _map_del_vm_obj(mc_vm_obj * vm_obj); void _map_make_zero_obj(mc_vm_obj * vm_obj); +int _map_obj_add_area_insert(cm_lst * obj_area_lst, + const cm_lst_node * area_node); int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node); int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node); bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj); -int _map_get_area_index(const mc_vm_area * area, const mc_vm_obj * obj); +int _map_find_obj_for_area(const _traverse_state * state, + const struct vm_entry * entry); int _map_update_obj_addr_range(mc_vm_obj * obj); -int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * node); -int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * node); +int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); +int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); int _map_unlink_unmapped_obj(cm_lst_node * node, const _traverse_state * state, diff --git a/src/test/check_map.c b/src/test/check_map.c index dc867f3..1af0f54 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -21,30 +21,24 @@ /* - * --- [FIXTURES] --- + * -- [UNTESTED FUNCTIONS] --- + * + * _map_obj_add_area_insert(): + * + * Tested through _map_obj_add_area() and _map_obj_add_last_area() + * */ + //globals static mc_vm_map m; +static mc_vm_obj o; +static cm_lst_node o_n; - -//empty virtual memory map setup -static void _setup_empty_vm_map() { - - mc_new_vm_map(&m); - - return; -} - - - -static void _teardown() { - - mc_del_vm_map(&m); - - return; -} +#define AREAS_NUM 4 +static mc_vm_area a[AREAS_NUM]; +static cm_lst_node a_n[AREAS_NUM]; @@ -52,7 +46,7 @@ static void _teardown() { * --- [HELPERS] --- */ -//construct a vm_entry stub +//initialise a vm_entry stub static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, unsigned long vm_end, unsigned long file_off, krncry_pgprot_t prot, char * file_path) { @@ -68,6 +62,30 @@ static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, +//initialise a _traverse_state +static void _init__traverse_state(_traverse_state * state, + cm_lst_node * next_area_node, + cm_lst_node * prev_obj_node) { + + state->next_area_node = next_area_node; + state->prev_obj_node = prev_obj_node; + + return; +} + + + +//initialise a cm_lst_node stub wrapper +static void _create_lst_wrapper(cm_lst_node * node, void * data) { + + node->data = data; + node->next = node->prev = NULL; + + return; +} + + + //integration test for CMore static void _assert_lst_len(cm_lst * list, int len) { @@ -99,6 +117,27 @@ static void _assert_lst_len(cm_lst * list, int len) { +static void _assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, + int vm_areas_unmapped_len, int vm_objs_unmapped_len, + int next_id_area, int next_id_obj) { + + //check mapped lists + _assert_lst_len(&map->vm_areas, vm_areas_len); + _assert_lst_len(&map->vm_objs, vm_objs_len); + + //check unmapped lists + _assert_lst_len(&map->vm_areas_unmapped, vm_areas_unmapped_len); + _assert_lst_len(&map->vm_objs_unmapped, vm_objs_unmapped_len); + + //check next IDs + ck_assert_int_eq(map->next_id_area, next_id_area); + ck_assert_int_eq(map->next_id_obj, next_id_obj); + + return; +} + + + static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, uintptr_t start_addr, uintptr_t end_addr, int vm_areas_len, int last_vm_areas_len, @@ -116,7 +155,7 @@ static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, _assert_lst_len(&obj->vm_area_node_ps, vm_areas_len); _assert_lst_len(&obj->last_vm_area_node_ps, last_vm_areas_len); - //check the object id is correctly set + //check the object ID is correctly set ck_assert_int_eq(obj->id, id); //check the object is set as mapped @@ -148,7 +187,7 @@ static void _assert_vm_area(mc_vm_area * area, char * pathname, char * basename, ck_assert_ptr_eq(area->obj_node_p, obj_node_p); ck_assert_ptr_eq(area->last_obj_node_p, last_obj_node_p); - //check the area id is correctly set + //check the area ID is correctly set ck_assert_int_eq(area->id, id); //check the area is mapped @@ -159,6 +198,93 @@ static void _assert_vm_area(mc_vm_area * area, char * pathname, char * basename, +/* + * --- [FIXTURES] --- + */ + +//empty map fixture +static void _setup_empty_vm_map() { + + mc_new_vm_map(&m); + + return; +} + + + +static void _teardown_vm_map() { + + mc_del_vm_map(&m); + + return; +} + + + +//empty object fixture +static void _setup_empty_vm_obj() { + + //super of _setup_empty_vm_map + _setup_empty_vm_map(); + + //construct the new object + _map_new_vm_obj(&o, &m, "/foo/bar"); + + //populate the object node + _create_lst_wrapper(&o_n, &o); + + return; +} + + + +//stub object fixture +static void _setup_stub_vm_obj() { + + /* + * Object will have an address range of 0x1000 - 0x5000. + */ + + struct vm_entry entry; + uintptr_t addr = 0x1000; + uintptr_t file_off = 0x200; + + //super of _setup_empty_vm_obj + _setup_empty_vm_obj(); + + + //initialise areas + for (int i = 0; i < AREAS_NUM; ++i) { + + //setup area + _init_vm_entry(&entry, addr, addr + 0x1000, + file_off, MC_ACCESS_READ, "/foo/bar"); + _map_init_vm_area(&a[i], &m, &o_n, NULL, &entry); + _map_obj_add_area(&o, &a_n[i]); + + //advance iteration + addr += 0x1000; + file_off += 0x200; + } + + return; +} + + + +static void _teardown_vm_obj() { + + //destroy the object + _map_del_vm_obj(&o); + + //super of _teardown_vm_map() + _teardown_vm_map(); + + return; +} + + + /* * --- [UNIT TESTS] --- */ @@ -168,18 +294,18 @@ START_TEST(test_mc_new_del_vm_map) { mc_vm_obj * zero_obj; + + //construct the map mc_new_vm_map(&m); - //check list lengths - _assert_lst_len(&m.vm_areas, 0); - _assert_lst_len(&m.vm_objs, 1); - _assert_lst_len(&m.vm_areas_unmapped, 0); - _assert_lst_len(&m.vm_objs_unmapped, 0); + //assert state + _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); - //check zero object + //check the zero is present object zero_obj = MC_GET_NODE_OBJ(m.vm_objs.head); _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, ZERO_OBJ_ID, true); + //delete the map mc_del_vm_map(&m); return; @@ -188,68 +314,358 @@ START_TEST(test_mc_new_del_vm_map) { -//_map_new_vm_obj() & _map_del_vm_obj() [empty fixture] +//_map_new_vm_obj() & _map_del_vm_obj() [empty map fixture] START_TEST(test__map_new_del_vm_obj) { - mc_vm_obj obj, * obj_p; + mc_vm_obj obj; + //construct the object _map_new_vm_obj(&obj, &m, "/foo/bar"); - //check object creation - obj_p = MC_GET_NODE_OBJ(m.vm_objs.head); - _assert_vm_obj(obj_p, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); + //assert state + _assert_vm_obj(&obj, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); + _assert_vm_map(&m, 0, 1, 0, 0, 0, 1); + + return; +} + + + +//_map_make_zero_obj() [empty map fixture] +START_TEST(test__map_make_zero_obj) { + + mc_vm_obj zero_obj; - //check the map's next_id_obj is incremented - ck_assert_int_eq(m.next_id_obj, 1); + + //create new object + _map_new_vm_obj(&zero_obj, &m, "0x0"); + + //convert new object to pseudo object + _map_make_zero_obj(&zero_obj); + + //assert state + _assert_vm_obj(&zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, ZERO_OBJ_ID, true); + _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); + + //destroy pseudo object + _map_del_vm_obj(&zero_obj); return; } -//_map_init_vm_area() [empty fixture] +//_map_init_vm_area() [empty object fixture] START_TEST(test__map_init_vm_area) { - mc_vm_obj obj; mc_vm_area area; struct vm_entry entry; - cm_lst_node obj_node; - - //create an object for the area to reference - _map_new_vm_obj(&obj, &m, "/foo/bar"); - - //create a stub list node to hold the object - obj_node.data = &obj; - obj_node.next = obj_node.prev = NULL; - //create a stub entry & initialise a new area + //create a stub entry & initialise the new area _init_vm_entry(&entry, 0x1000, 0x2000, 0x800, MC_ACCESS_READ, "/foo/bar"); - _map_init_vm_area(&area, &m, &obj_node, NULL, &entry); + _map_init_vm_area(&area, &m, &o_n, NULL, &entry); - //check area is correctly created + //assert state _assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, - MC_ACCESS_READ, &obj_node, NULL, 0, true); - - //check map's next id is incremented - ck_assert_int_eq(m.next_id_area, 1); + MC_ACCESS_READ, &o_n, NULL, 0, true); + _assert_vm_map(&m, 0, 1, 0, 0, 1, 0); //create a stub entry & initialise another new area _init_vm_entry(&entry, 0x2000, 0x4000, 0x800, MC_ACCESS_READ | MC_ACCESS_WRITE, "/purr/meow"); - _map_init_vm_area(&area, &m, NULL, &obj_node, &entry); + _map_init_vm_area(&area, &m, NULL, &o_n, &entry); - //check area is correctly created + //assert state _assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, - MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &obj_node, 1, true); + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &o_n, 1, true); + _assert_vm_map(&m, 0, 1, 0, 0, 2, 0); - //check map's next id is incremented - ck_assert_int_eq(m.next_id_area, 1); + return; + +} END_TEST - //deallocate the object - _map_del_vm_obj(&obj); - + +//_map_obj_add_area() [empty object fixture] +START_TEST(test__map_obj_add_area) { + + int ret; + + mc_vm_area area[4]; + cm_lst_node area_node[4], * area_node_ptr; + + struct vm_entry entry; + + + //initialise first area + _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "/foo/bar"); + _map_init_vm_area(&area[0], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&area_node[0], &area[0]); + + //add first area to the backing object + _map_obj_add_area(&o, &area_node[0]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x2000, 0x3000, 1, 0, 0, true); + area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); + _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); + + + //initialise lower area + _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/foo/bar"); + _map_init_vm_area(&area[1], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&area_node[1], &area); + + //add lower area to the backing object + _map_obj_add_area(&o, &area_node[1]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x3000, 2, 0, 0, true); + area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); + _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 1, true); + + + //initialise higher area + _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/foo/bar"); + _map_init_vm_area(&area[2], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&area_node[2], &area); + + //add lower area to the backing object + _map_obj_add_area(&o, &area_node[2]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); + area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); + _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); + + + //initialise middle area + _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "/foo/bar"); + _map_init_vm_area(&area[3], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&area_node[3], &area); + + //add middle area to the backing object + _map_obj_add_area(&o, &area_node[3]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); + _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); + + return; + +} END_TEST + + + +//_map_obj_add_last_area() [empty object fixture] +START_TEST(test__map_obj_add_last_area) { + + int ret; + + mc_vm_area last_area[4]; + cm_lst_node last_area_node[4], * last_area_node_ptr; + + struct vm_entry entry; + + + //initialise first area + _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "anonmap"); + _map_init_vm_area(&last_area[0], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&last_area_node[0], &last_area[0]); + + //add first area to the backing object + _map_obj_add_area(&o, &last_area_node[0]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); + _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "anonmap", "anonmap", + 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); + + + //initialise lower area + _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/bin/cat"); + _map_init_vm_area(&last_area[1], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&last_area_node[1], &last_area); + + //add lower area to the backing object + _map_obj_add_area(&o, &last_area_node[1]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); + _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), + "/bin/cat", "cat", 0x1000, 0x2000, MC_ACCESS_READ, + &o_n, NULL, 1, true); + + + //initialise higher area + _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/lib/std"); + _map_init_vm_area(&last_area[2], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&last_area_node[2], &last_area); + + //add lower area to the backing object + _map_obj_add_area(&o, &last_area_node[2]); + + //assert state + _assert_vm_obj(&o, "/lib/std", "std", 0x0, 0x0, 0, 3, 0, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); + _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "/foo/bar", "bar", + 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); + + + //initialise middle area + _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "io"); + _map_init_vm_area(&last_area[3], &m, &o_n, NULL, &entry); + _create_lst_wrapper(&last_area_node[3], &last_area); + + //add middle area to the backing object + _map_obj_add_area(&o, &last_area_node[3]); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); + _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "io", "io", + 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); + + return; + +} END_TEST + + + +//_map_is_pathname_in_obj() [empty object fixture] +START_TEST(test__map_is_pathname_in_obj) { + + bool ret; + + //path is in the object + ret = _map_is_pathname_in_obj("/foo/bar", &o); + ck_assert(ret); + + //path is not in the object + ret = _map_is_pathname_in_obj("anonmap", &o); + ck_assert(!ret); + + return; + +} END_TEST + + + +//_map_find_obj_for_area [empty map fixture] +START_TEST(test__map_find_obj_for_area) { + + int ret; + + mc_vm_obj objs[3]; + cm_lst_node obj_nodes[3]; + char * obj_paths[3] = {"/lib/libc", "/lib/libpthread", "anonmap"}; + + struct vm_entry entry; + _traverse_state state; + + + //construct objects + for (int i = 0; i < 3; ++i) { + _map_new_vm_obj(&objs[i], &m, obj_paths[i]); + _create_lst_wrapper(&obj_nodes[i], &objs[i]); + } + + + //new object, state empty + _init__traverse_state(&state, NULL, NULL); + _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, + MC_ACCESS_READ, "/lib/libpthread"); + + ret = _map_find_obj_for_area(&state, &entry); + ck_assert_int_eq(ret, _MAP_OBJ_NEW); + + + //new object, state full + _init__traverse_state(&state, NULL, &obj_nodes[1]); + _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, + MC_ACCESS_READ, "/lib/libpthread"); + + ret = _map_find_obj_for_area(&state, &entry); + ck_assert_int_eq(ret, _MAP_OBJ_NEW); + + + //previous object + _init__traverse_state(&state, NULL, &obj_nodes[1]); + _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, + MC_ACCESS_READ, "/lib/libc"); + + ret = _map_find_obj_for_area(&state, &entry); + ck_assert_int_eq(ret, _MAP_OBJ_PREV); + + + //next object + _init__traverse_state(&state, NULL, &obj_nodes[1]); + _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, + MC_ACCESS_READ, "anonmap"); + + ret = _map_find_obj_for_area(&state, &entry); + ck_assert_int_eq(ret, _MAP_OBJ_NEXT); + + + //destruct objects + for (int i = 0; i < 3; ++i) { + _map_del_vm_obj(&objs[i]); + } + + return; + +} END_TEST + + + +//_map_update_obj_addr_range [stub object fixture] +START_TEST(test__map_update_obj_addr_range) { + + int ret; + + + //middle + ret = cm_lst_rem(&o.vm_area_node_ps, 1); + ck_assert_int_eq(ret, 0); + + ret = _map_update_obj_addr_range(&o); + ck_assert_int_eq(ret, 0); + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); + + //highest + ret = cm_lst_rem(&o.vm_area_node_ps, -1); + ck_assert_int_eq(ret, 0); + + ret = _map_update_obj_addr_range(&o); + ck_assert_int_eq(ret, 0); + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x4000, 2, 0, 0, true); + + //lowest + ret = cm_lst_rem(&o.vm_area_node_ps, 0); + ck_assert_int_eq(ret, 0); + + ret = _map_update_obj_addr_range(&o); + ck_assert_int_eq(ret, 0); + _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x4000, 1, 0, 0, true); + + //last (highest & lowest) + ret = cm_lst_rem(&o.vm_area_node_ps, 0); + ck_assert_int_eq(ret, 0); + + ret = _map_update_obj_addr_range(&o); + ck_assert_int_eq(ret, 0); + _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); + + return; + } END_TEST From 47ba8a1188fa7c125ccd54d9b0cd948d7541798e Mon Sep 17 00:00:00 2001 From: vykt Date: Tue, 24 Dec 2024 06:24:41 +0000 Subject: [PATCH 04/45] more tests --- src/lib/error.c | 12 ++ src/lib/map.c | 224 ++++++++++++++++------- src/lib/map.h | 7 +- src/lib/memcry.h | 29 +-- src/test/check_map.c | 425 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 579 insertions(+), 118 deletions(-) diff --git a/src/lib/error.c b/src/lib/error.c index bab9355..c4d9ef0 100644 --- a/src/lib/error.c +++ b/src/lib/error.c @@ -9,6 +9,11 @@ __thread int mc_errno; +/* + * TODO: Find a metaprogramming way to do this. + */ + + void mc_perror(const char * prefix) { switch(mc_errno) { @@ -30,6 +35,10 @@ void mc_perror(const char * prefix) { case MC_ERR_INTERNAL_INDEX: fprintf(stderr, "%s: %s", prefix, MC_ERR_INTERNAL_INDEX_MSG); break; + + case MC_ERR_AREA_IN_OBJ: + fprintf(stderr, "%s: %s", prefix, MC_ERR_AREA_IN_OBJ_MSG); + break; case MC_ERR_UNEXPECTED_NULL: fprintf(stderr, "%s: %s", prefix, MC_ERR_UNEXPECTED_NULL_MSG); @@ -108,6 +117,9 @@ const char * mc_strerror(const int mc_errnum) { // 2xx - internal errors case MC_ERR_INTERNAL_INDEX: return MC_ERR_INTERNAL_INDEX_MSG; + + case MC_ERR_AREA_IN_OBJ: + return MC_ERR_AREA_IN_OBJ_MSG; case MC_ERR_UNEXPECTED_NULL: return MC_ERR_UNEXPECTED_NULL_MSG; diff --git a/src/lib/map.c b/src/lib/map.c index 757d9e8..75a971b 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -114,7 +114,7 @@ void _map_del_vm_obj(mc_vm_obj * vm_obj) { DBG_STATIC DBG_INLINE void _map_make_zero_obj(mc_vm_obj * vm_obj) { - vm_obj->id = ZERO_OBJ_ID; + vm_obj->id = MC_ZERO_OBJ_ID; return; } @@ -128,33 +128,42 @@ int _map_obj_add_area_insert(cm_lst * obj_area_lst, cm_lst_node * ret_node, * iter_node; mc_vm_area * area = MC_GET_NODE_AREA(area_node); - //insert the new area at an appropriate index - iter_node = obj_area_lst->head; - for (int i = 0; i < obj_area_lst->len; ++i) { + //if list is empty, append + if (obj_area_lst->len == 0) { + + cm_lst_apd(obj_area_lst, &area_node); + + //list is not empty, find appropriate insert point + } else { - //new area ends at a lower address than some existing area - if (area->end_addr < MC_GET_NODE_AREA(iter_node)->end_addr) { + //setup iteration + iter_node = obj_area_lst->head; + for (int i = 0; i < obj_area_lst->len; ++i) { + + //new area ends at a lower address than some existing area + if (area->end_addr < MC_GET_NODE_AREA(iter_node)->end_addr) { - ret_node = cm_lst_ins_nb(obj_area_lst, iter_node, &area_node); - break; - } + ret_node = cm_lst_ins_nb(obj_area_lst, iter_node, &area_node); + break; + } - //new area ends later than any existing area - if ((iter_node->next == NULL) - || (iter_node->next == obj_area_lst->head)) { + //new area ends later than any existing area + if ((iter_node->next == NULL) + || (iter_node->next == obj_area_lst->head)) { - ret_node = cm_lst_ins_na(obj_area_lst, iter_node, &area_node); - break; - } + ret_node = cm_lst_ins_na(obj_area_lst, iter_node, &area_node); + break; + } - //increment node iteration - iter_node = iter_node->next; - } + //increment node iteration + iter_node = iter_node->next; + } - //check the new area was inserted successfully - if (ret_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; - return -1; + //check the new area was inserted successfully + if (ret_node == NULL) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } } return 0; @@ -162,6 +171,34 @@ int _map_obj_add_area_insert(cm_lst * obj_area_lst, +DBG_STATIC +cm_lst_node * _map_obj_find_area_outer_node(cm_lst * obj_area_lst, + cm_lst_node * area_node) { + + cm_lst_node * iter_node; + + + //list should never be empty + if (obj_area_lst->len == 0) { + + mc_errno = MC_ERR_AREA_IN_OBJ; + return NULL; + } + + //setup iteration + iter_node = obj_area_lst->head; + for (int i = 0; i < obj_area_lst->len; ++i) { + + if (area_node == MC_GET_NODE_PTR(iter_node)) return iter_node; + iter_node = iter_node->next; + } + + mc_errno = MC_ERR_AREA_IN_OBJ; + return NULL; +} + + + DBG_STATIC DBG_INLINE int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node) { @@ -169,6 +206,7 @@ int _map_obj_add_area(mc_vm_obj * obj, int ret; mc_vm_area * area = MC_GET_NODE_AREA(area_node); + //if this object has no areas yet if (obj->start_addr == -1 || obj->start_addr == 0x0) { @@ -212,6 +250,93 @@ int _map_obj_add_last_area(mc_vm_obj * obj, +DBG_STATIC DBG_INLINE +int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node) { + + int ret, index; + cm_lst_node * area_outer_node, * temp_node; + + + //remove area node from the object + + //find index of area in object + area_outer_node = _map_obj_find_area_outer_node(&obj->vm_area_node_ps, + area_node); + if (area_outer_node == NULL) return -1; + + //remove area node from object + ret = cm_lst_rmv_n(&obj->vm_area_node_ps, area_outer_node); + if (ret != 0) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } + + + //update object's address range + + //if no area nodes left, address range is now zero (unmapped) + if (obj->vm_area_node_ps.len == 0) { + + obj->end_addr = obj->start_addr = 0x0; + return 0; + } + + //get start addr + ret = cm_lst_get(&obj->vm_area_node_ps, 0, &temp_node); + if (ret) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } + + obj->start_addr = MC_GET_NODE_AREA(temp_node)->start_addr; + + + //get end addr + index = obj->vm_area_node_ps.len == 1 ? 0 : -1; + + if (index != 0) { + ret = cm_lst_get(&obj->vm_area_node_ps, index, &temp_node); + if (ret) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } + } + + obj->end_addr = MC_GET_NODE_AREA(temp_node)->end_addr; + + + return 0; +} + + + +DBG_STATIC DBG_INLINE +int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node) { + + int ret, index; + cm_lst_node * last_area_outer_node, * temp_node; + + + //remove area node from the object + + //find index of area in object + last_area_outer_node = _map_obj_find_area_outer_node(&obj-> + last_vm_area_node_ps, + last_area_node); + if (last_area_outer_node == NULL) return -1; + + //remove area node from object + ret = cm_lst_rmv_n(&obj->last_vm_area_node_ps, last_area_outer_node); + if (ret != 0) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } + + return 0; +} + + + DBG_STATIC bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { @@ -256,47 +381,6 @@ int _map_find_obj_for_area(const _traverse_state * state, -DBG_STATIC DBG_INLINE -int _map_update_obj_addr_range(mc_vm_obj * obj) { - - int ret, end_index; - - cm_lst_node * temp_node; - mc_vm_area * temp_area; - - if (obj->vm_area_node_ps.len == 0) { - - obj->end_addr = obj->start_addr = 0x0; - return 0; - } - - //get start addr - ret = cm_lst_get(&obj->vm_area_node_ps, 0, &temp_node); - if (ret) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } - - temp_area = MC_GET_NODE_AREA(temp_node); - obj->start_addr = temp_area->start_addr; - - //get end addr - end_index = obj->vm_area_node_ps.len == 1 ? 0 : -1; - - ret = cm_lst_get(&obj->vm_area_node_ps, end_index, &temp_node); - if (ret) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } - - temp_area = MC_GET_NODE_AREA(temp_node); - obj->end_addr = temp_area->end_addr; - - return 0; -} - - - //called when deleting an object; moves `last_obj_node_p` back if needed DBG_STATIC DBG_INLINE int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { @@ -311,7 +395,7 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { //setup iteration - temp_obj = MC_GET_NODE_OBJ(node); + temp_obj = MC_GET_NODE_OBJ(obj_node); last_area_node = temp_obj->last_vm_area_node_ps.head; if (last_area_node == NULL) return 0; @@ -323,13 +407,13 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { for (int i = 0; i < iterations; ++i) { //update pointer - last_area->last_obj_node_p = node->prev; + last_area->last_obj_node_p = obj_node->prev; //advance iteration (part 1) last_area_node = last_area_node->next; //remove this area from the object's last_vm_area_node_ps list - ret = cm_lst_rem(&temp_obj->last_vm_area_node_ps, 0); + ret = cm_lst_rmv(&temp_obj->last_vm_area_node_ps, 0); if (ret == -1) { mc_errno = MC_ERR_LIBCMORE; return -1; @@ -393,7 +477,9 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { //remove this area from the previous //last object's last_vm_area_node_ps - ret = cm_lst_rem(&temp_prev_obj->last_vm_area_node_ps, i); + + //TODO CHANGE TO _map_obj_rmv_area + ret = cm_lst_rmv(&temp_prev_obj->last_vm_area_node_ps, i); if (ret == -1) { mc_errno = MC_ERR_LIBCMORE; return -1; @@ -425,7 +511,7 @@ int _map_unlink_unmapped_obj(cm_lst_node * node, //if this is the pseudo object, just reset it temp_obj = MC_GET_NODE_OBJ(node); - if (temp_obj->id == ZERO_OBJ_ID) { + if (temp_obj->id == MC_ZERO_OBJ_ID) { temp_obj->start_addr = 0x0; temp_obj->end_addr = 0x0; return 0; @@ -486,7 +572,7 @@ int _map_unlink_unmapped_area(cm_lst_node * node, //find the index for this area in the obj index = _get_area_index(temp_area, temp_obj); if (index != -1) { - ret = cm_lst_rem(&temp_obj->vm_area_node_ps, index); + ret = cm_lst_rmv(&temp_obj->vm_area_node_ps, index); if (ret) { mc_errno = MC_ERR_LIBCMORE; return -1; diff --git a/src/lib/map.h b/src/lib/map.h index 7678c13..ea9bc8e 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -45,14 +45,19 @@ void _map_make_zero_obj(mc_vm_obj * vm_obj); int _map_obj_add_area_insert(cm_lst * obj_area_lst, const cm_lst_node * area_node); +cm_lst_node * _map_obj_find_area_outer_node(cm_lst * obj_area_lst, + cm_lst_node * area_node); + int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node); int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node); +int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node); +int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node); + bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj); int _map_find_obj_for_area(const _traverse_state * state, const struct vm_entry * entry); -int _map_update_obj_addr_range(mc_vm_obj * obj); int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); diff --git a/src/lib/memcry.h b/src/lib/memcry.h index d60dcf8..06db6e6 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -34,7 +34,7 @@ extern "C"{ #define MC_IFACE_PROCFS 1 //pseudo object id -#define ZERO_OBJ_ID -1 +#define MC_ZERO_OBJ_ID -1 @@ -213,14 +213,15 @@ extern __thread int mc_errno; // 2XX - internal errors #define MC_ERR_INTERNAL_INDEX 2200 -#define MC_ERR_UNEXPECTED_NULL 2201 -#define MC_ERR_LIBCMORE 2202 -#define MC_ERR_READ_WRITE 2203 -#define MC_ERR_MEMU_TARGET 2204 -#define MC_ERR_MEMU_MAP_SZ 2205 -#define MC_ERR_MEMU_MAP_GET 2206 -#define MC_ERR_PROC_STATUS 2207 -#define MC_ERR_PROC_NAV 2208 +#define MC_ERR_AREA_IN_OBJ 2201 +#define MC_ERR_UNEXPECTED_NULL 2202 +#define MC_ERR_LIBCMORE 2203 +#define MC_ERR_READ_WRITE 2204 +#define MC_ERR_MEMU_TARGET 2205 +#define MC_ERR_MEMU_MAP_SZ 2206 +#define MC_ERR_MEMU_MAP_GET 2207 +#define MC_ERR_PROC_STATUS 2208 +#define MC_ERR_PROC_NAV 2209 // 3XX - environmental errors #define MC_ERR_MEM 2300 @@ -242,18 +243,20 @@ extern __thread int mc_errno; // 2XX - internal errors #define MC_ERR_INTERNAL_INDEX_MSG \ "Internal: Indexing error.\n" +#define MC_ERR_AREA_IN_OBJ_MSG \ + "Internal: Area is not in object when it should be.\n" #define MC_ERR_UNEXPECTED_NULL_MSG \ "Internal: Unexpected NULL pointer.\n" #define MC_ERR_LIBCMORE_MSG \ - "Internal: Libcmore error. See cm_perror().\n" + "Internal: CMore error. See cm_perror().\n" #define MC_ERR_READ_WRITE_MSG \ "Internal: Read/write failed.\n" #define MC_ERR_MEMU_TARGET_MSG \ - "Internal: Lainko target open failed.\n" + "Internal: Krncry target open failed.\n" #define MC_ERR_MEMU_MAP_SZ_MSG \ - "Internal: Lainko map size fetch failed..\n" + "Internal: Krncry map size fetch failed..\n" #define MC_ERR_MEMU_MAP_GET_MSG \ - "Internal: Lainko map transfer failed.\n" + "Internal: Krncry map transfer failed.\n" #define MC_ERR_PROC_STATUS_MSG \ "Internal: Failed to use /proc//status.\n" #define MC_ERR_PROC_NAV_MSG \ diff --git a/src/test/check_map.c b/src/test/check_map.c index 1af0f54..1b5aa69 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -4,6 +4,7 @@ #include //system headers +#include #include #include @@ -20,6 +21,13 @@ +/* + * Functions are not tested in the same order as they appear in the map.c + * source file. This assists with bootstrapping many tests. The order of + * testing remains close. + */ + + /* * -- [UNTESTED FUNCTIONS] --- * @@ -27,18 +35,41 @@ * * Tested through _map_obj_add_area() and _map_obj_add_last_area() * + * _map_obj_find_area_outer_node(): + * + * Tested through _map_obj_rmv_area() and _map_obj_rmv_last_area(); + * */ -//globals +//globals - map + +/* + * Contains all structures inside a map, artificially allocated statically. + */ + +#define STUB_MAP_AREA_NUM 10 +#define STUB_MAP_OBJ_NUM 4 static mc_vm_map m; +static mc_vm_area m_a[STUB_MAP_AREA_NUM]; +static cm_lst_node m_a_n[STUB_MAP_AREA_NUM]; + +static mc_vm_obj m_o[STUB_MAP_OBJ_NUM]; +static cm_lst_node m_o_n[STUB_MAP_OBJ_NUM]; + + +//globals - standalone object +#define AREAS_NUM 4 +#define LAST_AREAS_NUM 2 static mc_vm_obj o; static cm_lst_node o_n; -#define AREAS_NUM 4 -static mc_vm_area a[AREAS_NUM]; -static cm_lst_node a_n[AREAS_NUM]; +static mc_vm_area o_a[AREAS_NUM]; +static cm_lst_node o_a_n[AREAS_NUM]; + +static mc_vm_area o_a_l[LAST_AREAS_NUM]; +static cm_lst_node o_a_l_n[LAST_AREAS_NUM]; @@ -51,12 +82,20 @@ static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, unsigned long vm_end, unsigned long file_off, krncry_pgprot_t prot, char * file_path) { + //set entry vars entry->vm_start = vm_start; entry->vm_end = vm_end; entry->file_off = file_off; entry->prot = prot; - strncpy(entry->file_path, file_path, PATH_MAX); + //set file path + if (file_path != NULL) { + strncpy(entry->file_path, file_path, PATH_MAX); + + } else { + entry->file_path[0] = '\0'; + } + return; } @@ -166,6 +205,34 @@ static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, +static void _assert_vm_obj_list(cm_lst * outer_node_lst, + uintptr_t * start_addrs, int start_addrs_len) { + + mc_vm_area * area; + cm_lst_node * area_node, * iter_node; + + + //setup iteration + iter_node = outer_node_lst->head; + + //if provided lst is empty, return + if (outer_node_lst->len == 0 && outer_node_lst->head == NULL) return; + + //otherwise iterate over area starting addresses + for (int i = 0; i < start_addrs_len; ++i) { + + //check starting address + area_node = MC_GET_NODE_PTR(iter_node); + area = MC_GET_NODE_AREA(area_node); + ck_assert_int_eq(area->start_addr, start_addrs[i]); + + //advance iteration + iter_node = iter_node->next; + } +} + + + static void _assert_vm_area(mc_vm_area * area, char * pathname, char * basename, uintptr_t start_addr, uintptr_t end_addr, cm_byte access, cm_lst_node * obj_node_p, @@ -205,8 +272,132 @@ static void _assert_vm_area(mc_vm_area * area, char * pathname, char * basename, //empty map fixture static void _setup_empty_vm_map() { + //construct the map + mc_new_vm_map(&m); + + return; +} + + + +#define STUB_MAP_LEN 10 +//stub map fixture +static void _stub_vm_map() { + + /* + * Stub map: + * + * 0) 0x1000 - 0x2000 /bin/cat r-- + * 1) 0x2000 - 0x3000 /bin/cat rw- + * 2) 0x3000 - 0x4000 /bin/cat r-x + * 3) 0x4000 - 0x5000 [heap] rw- <- gap + * 4) 0x6000 - 0x7000 rw- <- gap + * 5) 0x8000 - 0x9000 /lib/foo rw- + * 6) 0x9000 - 0xA000 /lib/foo rw- + * 7) 0xA000 - 0xB000 /lib/foo r-x <- gap + * 8) 0xC000 - 0xD000 rw- <- gap + * 9) 0xE000 - 0xF000 [stack] rw- + */ + + int ret; + struct vm_entry entry; + + + //construct the map mc_new_vm_map(&m); + //construct objects + _map_new_vm_obj(&m_o[0], &m, "/bin/cat"); + _map_new_vm_obj(&m_o[1], &m, "[heap]"); + _map_new_vm_obj(&m_o[2], &m, "/lib/foo"); + _map_new_vm_obj(&m_o[3], &m, "[stack]"); + + //construct object nodes + for (int i = 0; i < STUB_MAP_OBJ_NUM; ++i) { + + _create_lst_wrapper(&m_o_n[i], &m_o[i]); + } + + + //construct areas + _init_vm_entry(&entry, 0x1000, 0x2000, 0x100, + MC_ACCESS_READ, "/bin/cat"); + _map_init_vm_area(&m_a[0], &m, &m_o_n[0], NULL, &entry); + + _init_vm_entry(&entry, 0x2000, 0x3000, 0x200, + MC_ACCESS_READ | MC_ACCESS_WRITE, "/bin/cat"); + _map_init_vm_area(&m_a[1], &m, &m_o_n[0], NULL, &entry); + + _init_vm_entry(&entry, 0x3000, 0x4000, 0x300, + MC_ACCESS_READ | MC_ACCESS_EXEC, "/bin/cat"); + _map_init_vm_area(&m_a[2], &m, &m_o_n[0], NULL, &entry); + + _init_vm_entry(&entry, 0x4000, 0x5000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, "[heap]"); + _map_init_vm_area(&m_a[3], &m, &m_o_n[1], NULL, &entry); + + _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); + _map_init_vm_area(&m_a[4], &m, NULL, &m_o_n[1], &entry); + + _init_vm_entry(&entry, 0x8000, 0x9000, 0x100, + MC_ACCESS_READ, "/lib/foo"); + _map_init_vm_area(&m_a[5], &m, &m_o_n[2], NULL, &entry); + + _init_vm_entry(&entry, 0x9000, 0xA000, 0x200, + MC_ACCESS_READ | MC_ACCESS_WRITE, "/lib/foo"); + _map_init_vm_area(&m_a[6], &m, &m_o_n[2], NULL, &entry); + + _init_vm_entry(&entry, 0xA000, 0xB000, 0x300, + MC_ACCESS_READ | MC_ACCESS_EXEC, "/lib/foo"); + _map_init_vm_area(&m_a[7], &m, &m_o_n[2], NULL, &entry); + + _init_vm_entry(&entry, 0xC000, 0xD000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); + _map_init_vm_area(&m_a[8], &m, NULL, &m_o_n[2], &entry); + + _init_vm_entry(&entry, 0xE000, 0xF000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, "[stack]"); + _map_init_vm_area(&m_a[9], &m, &m_o_n[3], NULL, &entry); + + //construct area nodes + for (int i = 0; i < STUB_MAP_AREA_NUM; ++i) { + + _create_lst_wrapper(&m_a_n[i], &m_a[i]); + } + + + //connect areas to objects + ret = _map_obj_add_area(&m_o[0], &m_a_n[0]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[0], &m_a_n[1]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[0], &m_a_n[2]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[1], &m_a_n[3]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_last_area(&m_o[1], &m_a_n[4]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[2], &m_a_n[5]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[2], &m_a_n[6]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[2], &m_a_n[7]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_last_area(&m_o[2], &m_a_n[8]); + ck_assert_int_eq(ret, 0); + + ret = _map_obj_add_area(&m_o[3], &m_a_n[9]); + ck_assert_int_eq(ret, 0); + return; } @@ -247,6 +438,7 @@ static void _setup_stub_vm_obj() { struct vm_entry entry; uintptr_t addr = 0x1000; + uintptr_t last_addr = 0x5000; uintptr_t file_off = 0x200; //super of _setup_empty_vm_obj @@ -259,14 +451,28 @@ static void _setup_stub_vm_obj() { //setup area _init_vm_entry(&entry, addr, addr + 0x1000, file_off, MC_ACCESS_READ, "/foo/bar"); - _map_init_vm_area(&a[i], &m, &o_n, NULL, &entry); - _map_obj_add_area(&o, &a_n[i]); + _map_init_vm_area(&o_a[i], &m, &o_n, NULL, &entry); + _map_obj_add_area(&o, &o_a_n[i]); //advance iteration addr += 0x1000; file_off += 0x200; } + + //initialise last areas + for (int i = 0; i < LAST_AREAS_NUM; ++i) { + + //setup last area + _init_vm_entry(&entry, last_addr, last_addr + 0x1000, + 0x0, MC_ACCESS_READ, NULL); + _map_init_vm_area(&o_a_l[i], &m, NULL, &o_n, &entry); + _map_obj_add_last_area(&o, &o_a_l_n[i]); + + //advance iteration + last_addr += 0x1000; + } + return; } @@ -395,10 +601,14 @@ START_TEST(test__map_obj_add_area) { int ret; mc_vm_area area[4]; - cm_lst_node area_node[4], * area_node_ptr; - + cm_lst_node area_node[4], * area_node_ptr; struct vm_entry entry; + uintptr_t state_first[1] = {0x2000}; + uintptr_t state_lower[2] = {0x1000, 0x2000}; + uintptr_t state_higher[3] = {0x1000, 0x2000, 0x4000}; + uintptr_t state_middle[4] = {0x1000, 0x2000, 0x3000, 0x4000}; + //initialise first area _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "/foo/bar"); @@ -410,6 +620,7 @@ START_TEST(test__map_obj_add_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x2000, 0x3000, 1, 0, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_first, 1); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); @@ -425,6 +636,7 @@ START_TEST(test__map_obj_add_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x3000, 2, 0, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_lower, 2); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 1, true); @@ -440,6 +652,7 @@ START_TEST(test__map_obj_add_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_higher, 3); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); @@ -455,6 +668,7 @@ START_TEST(test__map_obj_add_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 4); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); @@ -472,9 +686,13 @@ START_TEST(test__map_obj_add_last_area) { mc_vm_area last_area[4]; cm_lst_node last_area_node[4], * last_area_node_ptr; - struct vm_entry entry; + uintptr_t state_first[1] = {0x2000}; + uintptr_t state_lower[2] = {0x1000, 0x2000}; + uintptr_t state_higher[3] = {0x1000, 0x2000, 0x4000}; + uintptr_t state_middle[4] = {0x1000, 0x2000, 0x3000, 0x4000}; + //initialise first area _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "anonmap"); @@ -486,6 +704,7 @@ START_TEST(test__map_obj_add_last_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); + _assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "anonmap", "anonmap", 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); @@ -501,6 +720,7 @@ START_TEST(test__map_obj_add_last_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + _assert_vm_obj_list(&o.last_vm_area_node_ps, state_lower, 2); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "/bin/cat", "cat", 0x1000, 0x2000, MC_ACCESS_READ, @@ -517,6 +737,7 @@ START_TEST(test__map_obj_add_last_area) { //assert state _assert_vm_obj(&o, "/lib/std", "std", 0x0, 0x0, 0, 3, 0, true); + _assert_vm_obj_list(&o.last_vm_area_node_ps, state_higher, 3); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "/foo/bar", "bar", 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); @@ -532,6 +753,7 @@ START_TEST(test__map_obj_add_last_area) { //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + _assert_vm_obj_list(&o.last_vm_area_node_ps, state_middle, 4); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "io", "io", 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); @@ -542,6 +764,88 @@ START_TEST(test__map_obj_add_last_area) { +//_map_obj_rmv_area() [stub object fixture] +START_TEST(test__map_obj_rmv_area) { + + int ret; + + uintptr_t state_middle[3] = {0x1000, 0x3000, 0x4000}; + uintptr_t state_first[2] = {0x3000, 0x4000}; + uintptr_t state_last[1] = {0x3000}; + + + + //remove middle area + ret = _map_obj_rmv_area(&o, &o_a_n[1]); + ck_assert_int_eq(ret, 0); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 2, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 3); + + + //remove first area + ret = _map_obj_rmv_area(&o, &o_a_n[0]); + ck_assert_int_eq(ret, 0); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x5000, 2, 2, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_first, 2); + + + //remove last area + ret = _map_obj_rmv_area(&o, &o_a_n[3]); + ck_assert_int_eq(ret, 0); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x4000, 1, 2, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, state_last, 1); + + + //remove only remaining area + ret = _map_obj_rmv_area(&o, &o_a_n[2]); + ck_assert_int_eq(ret, 0); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + _assert_vm_obj_list(&o.vm_area_node_ps, NULL, 0); + + return; + +} END_TEST + + + +//_map_obj_rmv_last_area [stub object fixture] +START_TEST(test__map_obj_rmv_last_area) { + + int ret; + + uintptr_t state_first[1] = {0x6000}; + + + //remove first last area + ret = _map_obj_rmv_last_area(&o, &o_a_l_n[0]); + ck_assert_int_eq(ret, 0); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 1, 0, true); + _assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); + + + //remove only remaining last area + ret = _map_obj_rmv_last_area(&o, &o_a_l_n[1]); + ck_assert_int_eq(ret, 0); + + //assert state + _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + _assert_vm_obj_list(&o.last_vm_area_node_ps, NULL, 0); + + return; +} + + + //_map_is_pathname_in_obj() [empty object fixture] START_TEST(test__map_is_pathname_in_obj) { @@ -628,43 +932,94 @@ START_TEST(test__map_find_obj_for_area) { -//_map_update_obj_addr_range [stub object fixture] -START_TEST(test__map_update_obj_addr_range) { +//_map_backtrack_unmapped_obj_last_vm_areas() +START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { + + /* + * Stub map: + * + * 0) 0x1000 - 0x2000 /bin/cat r-- + * 1) 0x2000 - 0x3000 /bin/cat rw- + * 2) 0x3000 - 0x4000 /bin/cat r-x + * 3) 0x4000 - 0x5000 [heap] rw- <- gap + * 4) 0x6000 - 0x7000 rw- <- gap + * 5) 0x8000 - 0x9000 /lib/foo rw- + * 6) 0x9000 - 0xA000 /lib/foo rw- + * 7) 0xA000 - 0xB000 /lib/foo r-x <- gap + * 8) 0xC000 - 0xD000 rw- <- gap + * 9) 0xE000 - 0xF000 [stack] rw- + */ int ret; + mc_vm_area * area; + mc_vm_obj * obj; + cm_lst_node * area_node, * zero_node; - //middle - ret = cm_lst_rem(&o.vm_area_node_ps, 1); - ck_assert_int_eq(ret, 0); - - ret = _map_update_obj_addr_range(&o); - ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); + uintptr_t first_state[2] = {0x6000, 0xC000}; + uintptr_t second_state[2] = {0x6000, 0xC000}; - //highest - ret = cm_lst_rem(&o.vm_area_node_ps, -1); + + //backtrack `/lib/foo`'s last area (index: 8) + ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[2]); ck_assert_int_eq(ret, 0); - - ret = _map_update_obj_addr_range(&o); + + //assert state + + //check `/lib/foo` no longer has any last areas associated with it + obj = MC_GET_NODE_OBJ(((cm_lst_node *) &m_o_n[2])); + _assert_vm_obj(obj, "/lib/foo", "/lib/foo", 0x8000, 0xA000, 3, 0, 3, true); + + //check `[heap]` now has two last areas associated with it + obj_node = cm_lst_get_n(&m.vm_objs, 2); + obj = MC_GET_NODE_OBJ(obj_node); + _assert_vm_obj(obj, "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 2, true); + _assert_vm_obj_list(&obj->last_vm_area_node_ps, first_state, 2); + + //check the transfered last area (index: 8) now points to `[heap]` + area_node = cm_lst_get_n(&m.vm_areas, 8); + area = MC_GET_NODE_AREA(area_node); + _assert_vm_area(area, NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, obj_node, 8, true); + + + //backtrack `[heap]` and then further backtrack `/bin/cat` (index: 8, 4) + obj_node = cm_lst_get_n(&m.vm_objs, 2); + ret = _map_backtrack_unmapped_obj_last_vm_areas(obj_node); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x4000, 2, 0, 0, true); - //lowest - ret = cm_lst_rem(&o.vm_area_node_ps, 0); + obj_node = cm_lst_get_n(&m.vm_objs, 1); + ret = _map_backtrack_unmapped_obj_last_vm_areas(obj_node); ck_assert_int_eq(ret, 0); + + //assert state + + //check + obj_node = cm_lst_get_n(&m.vm_objs, 0); + obj = MC_GET_NODE_OBJ(obj_node); + _assert_vm_obj(obj, "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); + _assert_vm_obj_list(&obj->last_vm_area_node_ps, second_state, 2); + + area_node = cm_lst_get_n(&m.vm_areas, 8); + area = MC_GET_NODE_AREA(area_node); + _assert_vm_area(area, NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, obj_node, 8, true); + + area_node = cm_lst_get_n(&m.vm_areas, 4); + area = MC_GET_NODE_AREA(area_node); + _assert_vm_area(area, NULL, NULL, 0x6000, 0x7000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, obj_node, 4, true); - ret = _map_update_obj_addr_range(&o); - ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x4000, 1, 0, 0, true); - //last (highest & lowest) - ret = cm_lst_rem(&o.vm_area_node_ps, 0); - ck_assert_int_eq(ret, 0); + return; - ret = _map_update_obj_addr_range(&o); - ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); +} END_TEST + + + +//_map_forward_unmapped_obj_last_vm_areas() +START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { + return; From 82f78c727f86db1c530688701aa560de720221c8 Mon Sep 17 00:00:00 2001 From: vykt <106453929+vykt@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:06:56 +0000 Subject: [PATCH 05/45] add temporary note to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 44cd3ba..fdf7efc 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@

+### NOTE: + +**liblain** is undergoing major testing and a minor refactor. Prebuilt binaries and static builds are coming. To get liblain working before the refactor is finished, install the latest release and [cmore v0.0.3](https://github.com/vykt/cmore/releases/tag/0.0.3). + + ### ABOUT: The Lain library (liblain) provides a programmatic interface to the memory and memory maps of processes on Linux. Liblain can be used for: From 18394c8002049fa1d2eec6c28e28e91e1271bac9 Mon Sep 17 00:00:00 2001 From: vykt Date: Fri, 27 Dec 2024 20:55:38 +0000 Subject: [PATCH 06/45] add more unit tests --- src/lib/map.c | 280 +++++++++--------- src/lib/map.h | 29 +- src/test/check_map.c | 670 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 733 insertions(+), 246 deletions(-) diff --git a/src/lib/map.c b/src/lib/map.c index 75a971b..e182373 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -8,6 +8,7 @@ //external libraries #include +#include //local headers #include "memcry.h" @@ -27,40 +28,39 @@ */ DBG_STATIC -void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, +void _map_init_vm_area(mc_vm_area * area, const struct vm_entry * entry, const cm_lst_node * obj_node, - const cm_lst_node * last_obj_node, - const struct vm_entry * entry) { + const cm_lst_node * last_obj_node, mc_vm_map * map) { - mc_vm_obj * vm_obj; + mc_vm_obj * obj; //set pathname for area if applicable if (obj_node != NULL) { - vm_obj = MC_GET_NODE_OBJ(obj_node); + obj = MC_GET_NODE_OBJ(obj_node); - vm_area->pathname = vm_obj->pathname; - vm_area->basename = vm_obj->basename; + area->pathname = obj->pathname; + area->basename = obj->basename; } else { - vm_area->pathname = NULL; - vm_area->basename = NULL; + area->pathname = NULL; + area->basename = NULL; } //end if - vm_area->obj_node_p = (cm_lst_node *) obj_node; - vm_area->last_obj_node_p = (cm_lst_node *) last_obj_node; + area->obj_node_p = (cm_lst_node *) obj_node; + area->last_obj_node_p = (cm_lst_node *) last_obj_node; - vm_area->start_addr = entry->vm_start; - vm_area->end_addr = entry->vm_end; + area->start_addr = entry->vm_start; + area->end_addr = entry->vm_end; - vm_area->access = (cm_byte) (entry->prot & 0x0000000F); + area->access = (cm_byte) (entry->prot & 0x0000000F); - vm_area->mapped = true; + area->mapped = true; //set id & increment map's next id - vm_area->id = vm_map->next_id_area; - ++vm_map->next_id_area; + area->id = map->next_id_area; + ++map->next_id_area; return; } @@ -74,26 +74,26 @@ void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, */ DBG_STATIC -void _map_new_vm_obj(mc_vm_obj * vm_obj, - mc_vm_map * vm_map, const char * pathname) { +void _map_new_vm_obj(mc_vm_obj * obj, + mc_vm_map * map, const char * pathname) { const char * basename = mc_pathname_to_basename(pathname); - strncpy(vm_obj->pathname, pathname, PATH_MAX); - strncpy(vm_obj->basename, basename, NAME_MAX); + strncpy(obj->pathname, pathname, PATH_MAX); + strncpy(obj->basename, basename, NAME_MAX); - vm_obj->start_addr = 0x0; - vm_obj->end_addr = 0x0; + obj->start_addr = 0x0; + obj->end_addr = 0x0; //initialise area list - cm_new_lst(&vm_obj->vm_area_node_ps, sizeof(cm_lst_node *)); - cm_new_lst(&vm_obj->last_vm_area_node_ps, sizeof(cm_lst_node *)); + cm_new_lst(&obj->vm_area_node_ps, sizeof(cm_lst_node *)); + cm_new_lst(&obj->last_vm_area_node_ps, sizeof(cm_lst_node *)); - vm_obj->mapped = true; + obj->mapped = true; //set id & increment map's next id - vm_obj->id = vm_map->next_id_obj; - ++vm_map->next_id_obj; + obj->id = map->next_id_obj; + ++map->next_id_obj; return; } @@ -101,10 +101,10 @@ void _map_new_vm_obj(mc_vm_obj * vm_obj, DBG_STATIC -void _map_del_vm_obj(mc_vm_obj * vm_obj) { +void _map_del_vm_obj(mc_vm_obj * obj) { - cm_del_lst(&vm_obj->vm_area_node_ps); - cm_del_lst(&vm_obj->last_vm_area_node_ps); + cm_del_lst(&obj->vm_area_node_ps); + cm_del_lst(&obj->last_vm_area_node_ps); return; } @@ -112,9 +112,9 @@ void _map_del_vm_obj(mc_vm_obj * vm_obj) { DBG_STATIC DBG_INLINE -void _map_make_zero_obj(mc_vm_obj * vm_obj) { +void _map_make_zero_obj(mc_vm_obj * obj) { - vm_obj->id = MC_ZERO_OBJ_ID; + obj->id = MC_ZERO_OBJ_ID; return; } @@ -350,8 +350,8 @@ bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { DBG_STATIC DBG_INLINE -int _map_find_obj_for_area(const _traverse_state * state, - const struct vm_entry * entry) { +int _map_find_obj_for_area(const struct vm_entry * entry, + const _traverse_state * state) { cm_lst_node * prev_node, * next_node; mc_vm_obj * prev_obj, * next_obj; @@ -388,10 +388,10 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { int ret; int iterations; - mc_vm_obj * temp_obj; - - cm_lst_node * last_area_node; mc_vm_area * last_area; + cm_lst_node * last_area_node; + + mc_vm_obj * temp_obj; //setup iteration @@ -435,61 +435,52 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { int ret; //declarations - cm_lst_node * prev_node; - mc_vm_obj * temp_obj; - mc_vm_obj * temp_prev_obj; + mc_vm_obj * obj; + + cm_lst_node * prev_obj_node; + mc_vm_obj * prev_obj; cm_lst_node * last_area_node; mc_vm_area * last_area; - /* - * This is not that complicated, C syntax for generics is just awful - */ - //setup iteration - prev_node = node->prev; + obj = MC_GET_NODE_OBJ(obj_node); - temp_obj = MC_GET_NODE_OBJ(node); - temp_prev_obj = MC_GET_NODE_OBJ(prev_node); + prev_obj_node = obj_node->prev; + prev_obj = MC_GET_NODE_OBJ(prev_obj_node); - last_area_node = temp_prev_obj->last_vm_area_node_ps.head; + last_area_node = prev_obj->last_vm_area_node_ps.head; if (last_area_node == NULL) return 0; last_area = MC_GET_NODE_AREA(last_area_node); //for every area, move last object pointer forward if necessary - for (int i = 0; i < temp_prev_obj->last_vm_area_node_ps.len; ++i) { - - //advance iteration (part 1) - last_area_node = last_area_node->next; + for (int i = 0; i < prev_obj->last_vm_area_node_ps.len; ++i) { //if this area's address range comes completely after this object - if (last_area->start_addr >= temp_obj->end_addr) { + if (last_area->start_addr >= obj->end_addr) { //set this object as the new last object pointer - last_area->last_obj_node_p = node; + last_area->last_obj_node_p = obj_node; //add this area to this object's last_vm_area_node_ps list - cm_lst_apd(&temp_obj->last_vm_area_node_ps, - &last_area_node); + ret = _map_obj_add_last_area(obj, last_area_node); + if (ret == -1) return -1; //remove this area from the previous //last object's last_vm_area_node_ps - - //TODO CHANGE TO _map_obj_rmv_area - ret = cm_lst_rmv(&temp_prev_obj->last_vm_area_node_ps, i); - if (ret == -1) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } + ret =_map_obj_rmv_last_area(prev_obj, last_area_node); + if (ret == -1) return -1; + //correct iteration index i -= 1; - } //end if + } //end if area's address range comes completely after this object - //advance iteration (part 2) + //advance iteration + last_area_node = last_area_node->next; last_area = MC_GET_NODE_AREA(last_area_node); } //end for @@ -500,42 +491,45 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { DBG_STATIC DBG_INLINE -int _map_unlink_unmapped_obj(cm_lst_node * node, - const _traverse_state * state, - mc_vm_map * vm_map) { +int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { int ret; cm_lst_node * ret_node; - mc_vm_obj * temp_obj; + + mc_vm_obj * obj; - //if this is the pseudo object, just reset it - temp_obj = MC_GET_NODE_OBJ(node); - if (temp_obj->id == MC_ZERO_OBJ_ID) { - temp_obj->start_addr = 0x0; - temp_obj->end_addr = 0x0; + //fetch object + obj = MC_GET_NODE_OBJ(obj_node); + + //if this is the pseydo object, just reset it + if (obj->id == MC_ZERO_OBJ_ID) { + obj->start_addr = 0x0; + obj->end_addr = 0x0; return 0; } //correct last_obj_node_p of every vm_area - //using this object as its last object - ret = _backtrack_unmapped_obj_last_vm_areas(node); + //with this object as its last object + ret = _map_backtrack_unmapped_obj_last_vm_areas(obj_node); if (ret == -1) return -1; //unlink this node from the list of mapped vm areas - ret = cm_lst_uln(&vm_map->vm_objs, state->prev_obj_index + 1); - if (ret) { + ret_node = cm_lst_uln_n(&map->vm_objs, obj_node); + if (ret_node != obj_node) { mc_errno = MC_ERR_LIBCMORE; return -1; } - //set node's values to be unmapped - (MC_GET_NODE_OBJ(node))->mapped = false; - node->next = NULL; - node->prev = NULL; + //disconnect object node's attributes + obj->mapped = false; + obj->start_addr = 0x0; + obj->end_addr = 0x0; + obj_node->next = NULL; + obj_node->prev = NULL; - //add a pointer to this node to the list containing nodes to dealloc later - ret_node = cm_lst_apd(&vm_map->vm_objs_unmapped, &node); + //move this object node to the unmapped list + ret_node = cm_lst_apd(&map->vm_objs_unmapped, &obj_node); if (ret_node == NULL) { mc_errno = MC_ERR_LIBCMORE; return -1; @@ -547,71 +541,68 @@ int _map_unlink_unmapped_obj(cm_lst_node * node, DBG_STATIC DBG_INLINE -int _map_unlink_unmapped_area(cm_lst_node * node, - const _traverse_state * state, - mc_vm_map * vm_map) { +int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map) { - int ret, index; - - cm_lst_node * obj_node, * temp_node; - - mc_vm_area * temp_area; - mc_vm_obj * temp_obj; + int ret; + cm_lst_node * ret_node; + + mc_vm_area * area; + + mc_vm_obj * obj; + cm_lst_node * obj_node; //get vm area of node - temp_area = MC_GET_NODE_AREA(node); + area = MC_GET_NODE_AREA(area_node); - //remove this area from its parent object if necessary - if (temp_area->obj_node_p != NULL) { + if (area->obj_node_p != NULL) { - obj_node = temp_area->obj_node_p; - temp_obj = MC_GET_NODE_OBJ(obj_node); - - //find the index for this area in the obj - index = _get_area_index(temp_area, temp_obj); - if (index != -1) { - ret = cm_lst_rmv(&temp_obj->vm_area_node_ps, index); - if (ret) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } - } else { - mc_errno = MC_ERR_INTERNAL_INDEX; - return -1; - } + obj_node = area->obj_node_p; + obj = MC_GET_NODE_OBJ(obj_node); - //if the object now has no areas, set it to be unmapped - if (temp_obj->vm_area_node_ps.len == 0) { + //remove this area from the object + ret = _map_obj_rmv_area(obj, area_node); + if (ret == -1) return -1; + + //if this area's object now has no areas, unmap it + if (obj->vm_area_node_ps.len == 0) { - ret = _unlink_unmapped_obj(obj_node, state, vm_map); - if (ret) return -1; - - //else set the new start and end addr for this object - } else { - ret = _update_obj_addr_range(temp_obj); + ret = _map_unlink_unmapped_obj(obj_node, map); if (ret) return -1; } + } - } //end if + //remove this area from its last parent object if necessary + if (area->last_obj_node_p != NULL) { + + obj_node = area->last_obj_node_p; + obj = MC_GET_NODE_OBJ(obj_node); + + //remove this area from the last object + ret = _map_obj_rmv_last_area(obj, area_node); + if (ret == -1) return -1; + } + //unlink this node from the list of mapped vm areas - ret = cm_lst_uln(&vm_map->vm_areas, state->next_area_index); - if (ret) { + ret_node = cm_lst_uln_n(&map->vm_areas, area_node); + if (ret_node != area_node) { mc_errno = MC_ERR_LIBCMORE; return -1; } - //now safe to set node's values to be unmapped - temp_area->mapped = false; - node->next = NULL; - node->prev = NULL; + //disconnect area node's attributes + area->mapped = false; + area->obj_node_p = NULL; + area->last_obj_node_p = NULL; + area_node->next = NULL; + area_node->prev = NULL; - //add a pointer to this node to the list containing nodes to dealloc later - temp_node = cm_lst_apd(&vm_map->vm_areas_unmapped, &node); - if (temp_node == NULL) { + //move this area node to the unmapped list + ret_node = cm_lst_apd(&map->vm_areas_unmapped, &area_node); + if (ret_node == NULL) { mc_errno = MC_ERR_LIBCMORE; return -1; } @@ -644,36 +635,37 @@ int _map_check_area_eql(const struct vm_entry * entry, DBG_STATIC DBG_INLINE -int _map_resync_area(mc_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry) { +int _map_resync_area(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map) { int ret; - mc_vm_area * iter_area; - cm_lst_node * iter_node; + mc_vm_area * area; + cm_lst_node * area_node; - iter_node = state->next_area; - iter_area = MC_GET_NODE_AREA(iter_node); + //setup iteration + area_node = state->next_area_node; + area = MC_GET_NODE_AREA(area_node); //while there are vm areas left to discard - while (entry->vm_end > iter_area->start_addr) { + while (entry->vm_end > area->start_addr) { //make state point to the next node, and NULL if there is no next node - if (state->next_area->next == NULL) { - state->next_area = NULL; + if (state->next_area_node->next == map->vm_objs.head) { + state->next_area_node = NULL; } else { - state->next_area = state->next_area->next; + state->next_area_node = state->next_area_node->next; } //correctly handle removing this area node - ret = _unlink_unmapped_area(iter_node, state, vm_map); + ret = _map_unlink_unmapped_area(area_node, map); if (ret) return -1; //update iterator nodes - if (state->next_area == NULL) break; - iter_node = state->next_area; - iter_area = MC_GET_NODE_AREA(iter_node); + if (state->next_area_node == NULL) break; + area_node = state->next_area_node; + area = MC_GET_NODE_AREA(area_node); } //end while diff --git a/src/lib/map.h b/src/lib/map.h index ea9bc8e..054c015 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -33,15 +33,14 @@ typedef struct { #ifdef DEBUG //internal -void _map_init_vm_area(mc_vm_area * vm_area, mc_vm_map * vm_map, +void _map_init_vm_area(mc_vm_area * area, const struct vm_entry * entry, const cm_lst_node * obj_node, - const cm_lst_node * last_obj_node, - const struct vm_entry * entry); -void _map_new_vm_obj(mc_vm_obj * vm_obj, - mc_vm_map * vm_map, const char * pathname); -void _map_del_vm_obj(mc_vm_obj * vm_obj); + const cm_lst_node * last_obj_node, mc_vm_map * map); +void _map_new_vm_obj(mc_vm_obj * obj, + mc_vm_map * map, const char * pathname); +void _map_del_vm_obj(mc_vm_obj * obj); -void _map_make_zero_obj(mc_vm_obj * vm_obj); +void _map_make_zero_obj(mc_vm_obj * obj); int _map_obj_add_area_insert(cm_lst * obj_area_lst, const cm_lst_node * area_node); @@ -55,23 +54,19 @@ int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node); int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node); bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj); -int _map_find_obj_for_area(const _traverse_state * state, - const struct vm_entry * entry); +int _map_find_obj_for_area(const struct vm_entry * entry, + const _traverse_state * state); int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); -int _map_unlink_unmapped_obj(cm_lst_node * node, - const _traverse_state * state, - mc_vm_map * vm_map); -int _map_unlink_unmapped_area(cm_lst_node * node, - const _traverse_state * state, - mc_vm_map * vm_map); +int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map); +int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map); int _map_check_area_eql(const struct vm_entry * entry, const cm_lst_node * area_node); -int _map_resync_area(mc_vm_map * vm_map, - _traverse_state * state, const struct vm_entry * entry); +int _map_resync_area(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map); void _map_state_inc_area(mc_vm_map * vm_map, _traverse_state * state, const cm_lst_node * assign_node, const int inc_type); diff --git a/src/test/check_map.c b/src/test/check_map.c index 1b5aa69..5921774 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -42,6 +42,27 @@ */ + +//test structures + +struct obj_check { + + char basename[NAME_MAX]; + uintptr_t start_addr; + uintptr_t end_addr; +}; + + + +struct area_check { + + char basename[NAME_MAX]; + uintptr_t start_addr; + uintptr_t end_addr; +}; + + + //globals - map /* @@ -177,6 +198,48 @@ static void _assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, +static void _assert_vm_map_objs(cm_lst * obj_lst, + struct obj_check * obj_checks, + int obj_checks_len) { + + mc_vm_obj * obj; + + for (int i = 0; i < obj_checks_len; ++i) { + + obj = cm_lst_get_p(obj_lst, i); + ck_assert_ptr_nonnull(obj); + + ck_assert_str_eq(obj->basename, obj_checks[i].basename); + ck_assert_int_eq(obj->start_addr, obj_checks[i].start_addr); + ck_assert_int_eq(obj->end_addr, obj_checks[i].end_addr); + } + + return; +} + + + +static void _assert_vm_map_areas(cm_lst * area_lst, + struct area_check * area_checks, + int area_checks_len) { + + mc_vm_area * area; + + for (int i = 0; i < area_checks_len; ++i) { + + area = cm_lst_get_p(area_lst, i); + ck_assert_ptr_nonnull(area); + + ck_assert_str_eq(area->basename, area_checks[i].basename); + ck_assert_int_eq(area->start_addr, area_checks[i].start_addr); + ck_assert_int_eq(area->end_addr, area_checks[i].end_addr); + } + + return; +} + + + static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, uintptr_t start_addr, uintptr_t end_addr, int vm_areas_len, int last_vm_areas_len, @@ -205,6 +268,11 @@ static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, +/* + * Check state of the object by checking the starting addresses of each of + * its constituent areas. + */ + static void _assert_vm_obj_list(cm_lst * outer_node_lst, uintptr_t * start_addrs, int start_addrs_len) { @@ -279,7 +347,7 @@ static void _setup_empty_vm_map() { } - +#ifdef DEBUG #define STUB_MAP_LEN 10 //stub map fixture static void _stub_vm_map() { @@ -322,43 +390,43 @@ static void _stub_vm_map() { //construct areas _init_vm_entry(&entry, 0x1000, 0x2000, 0x100, MC_ACCESS_READ, "/bin/cat"); - _map_init_vm_area(&m_a[0], &m, &m_o_n[0], NULL, &entry); + _map_init_vm_area(&m_a[0], &entry, &m_o_n[0], NULL, &m); _init_vm_entry(&entry, 0x2000, 0x3000, 0x200, MC_ACCESS_READ | MC_ACCESS_WRITE, "/bin/cat"); - _map_init_vm_area(&m_a[1], &m, &m_o_n[0], NULL, &entry); + _map_init_vm_area(&m_a[1], &entry, &m_o_n[0], NULL, &m); _init_vm_entry(&entry, 0x3000, 0x4000, 0x300, MC_ACCESS_READ | MC_ACCESS_EXEC, "/bin/cat"); - _map_init_vm_area(&m_a[2], &m, &m_o_n[0], NULL, &entry); + _map_init_vm_area(&m_a[2], &entry, &m_o_n[0], NULL, &m); _init_vm_entry(&entry, 0x4000, 0x5000, 0x0, MC_ACCESS_READ | MC_ACCESS_WRITE, "[heap]"); - _map_init_vm_area(&m_a[3], &m, &m_o_n[1], NULL, &entry); + _map_init_vm_area(&m_a[3], &entry, &m_o_n[1], NULL, &m); _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); - _map_init_vm_area(&m_a[4], &m, NULL, &m_o_n[1], &entry); + _map_init_vm_area(&m_a[4], &entry, NULL, &m_o_n[1], &m); _init_vm_entry(&entry, 0x8000, 0x9000, 0x100, MC_ACCESS_READ, "/lib/foo"); - _map_init_vm_area(&m_a[5], &m, &m_o_n[2], NULL, &entry); + _map_init_vm_area(&m_a[5], &entry, &m_o_n[2], NULL, &m); _init_vm_entry(&entry, 0x9000, 0xA000, 0x200, MC_ACCESS_READ | MC_ACCESS_WRITE, "/lib/foo"); - _map_init_vm_area(&m_a[6], &m, &m_o_n[2], NULL, &entry); + _map_init_vm_area(&m_a[6], &entry, &m_o_n[2], NULL, &m); _init_vm_entry(&entry, 0xA000, 0xB000, 0x300, MC_ACCESS_READ | MC_ACCESS_EXEC, "/lib/foo"); - _map_init_vm_area(&m_a[7], &m, &m_o_n[2], NULL, &entry); + _map_init_vm_area(&m_a[7], &entry, &m_o_n[2], NULL, &m); _init_vm_entry(&entry, 0xC000, 0xD000, 0x0, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); - _map_init_vm_area(&m_a[8], &m, NULL, &m_o_n[2], &entry); + _map_init_vm_area(&m_a[8], &entry, NULL, &m_o_n[2], &m); _init_vm_entry(&entry, 0xE000, 0xF000, 0x0, MC_ACCESS_READ | MC_ACCESS_WRITE, "[stack]"); - _map_init_vm_area(&m_a[9], &m, &m_o_n[3], NULL, &entry); + _map_init_vm_area(&m_a[9], &entry, &m_o_n[3], NULL, &m); //construct area nodes for (int i = 0; i < STUB_MAP_AREA_NUM; ++i) { @@ -400,6 +468,7 @@ static void _stub_vm_map() { return; } +#endif @@ -411,7 +480,7 @@ static void _teardown_vm_map() { } - +#ifdef DEBUG //empty object fixture static void _setup_empty_vm_obj() { @@ -426,9 +495,11 @@ static void _setup_empty_vm_obj() { return; } +#endif +#ifdef DEBUG //stub object fixture static void _setup_stub_vm_obj() { @@ -466,7 +537,7 @@ static void _setup_stub_vm_obj() { //setup last area _init_vm_entry(&entry, last_addr, last_addr + 0x1000, 0x0, MC_ACCESS_READ, NULL); - _map_init_vm_area(&o_a_l[i], &m, NULL, &o_n, &entry); + _map_init_vm_area(&o_a_l[i], &entry, NULL, &o_n, &m); _map_obj_add_last_area(&o, &o_a_l_n[i]); //advance iteration @@ -475,9 +546,11 @@ static void _setup_stub_vm_obj() { return; } +#endif +#ifdef DEBUG static void _teardown_vm_obj() { //destroy the object @@ -488,6 +561,7 @@ static void _teardown_vm_obj() { return; } +#endif @@ -509,7 +583,8 @@ START_TEST(test_mc_new_del_vm_map) { //check the zero is present object zero_obj = MC_GET_NODE_OBJ(m.vm_objs.head); - _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, ZERO_OBJ_ID, true); + _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, + 0, 0, MC_ZERO_OBJ_ID, true); //delete the map mc_del_vm_map(&m); @@ -520,6 +595,7 @@ START_TEST(test_mc_new_del_vm_map) { +#ifdef DEBUG //_map_new_vm_obj() & _map_del_vm_obj() [empty map fixture] START_TEST(test__map_new_del_vm_obj) { @@ -550,7 +626,8 @@ START_TEST(test__map_make_zero_obj) { _map_make_zero_obj(&zero_obj); //assert state - _assert_vm_obj(&zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, ZERO_OBJ_ID, true); + _assert_vm_obj(&zero_obj, + "0x0", "0x0", 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); //destroy pseudo object @@ -571,7 +648,7 @@ START_TEST(test__map_init_vm_area) { //create a stub entry & initialise the new area _init_vm_entry(&entry, 0x1000, 0x2000, 0x800, MC_ACCESS_READ, "/foo/bar"); - _map_init_vm_area(&area, &m, &o_n, NULL, &entry); + _map_init_vm_area(&area, &entry, &o_n, NULL, &m); //assert state _assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, @@ -582,7 +659,7 @@ START_TEST(test__map_init_vm_area) { //create a stub entry & initialise another new area _init_vm_entry(&entry, 0x2000, 0x4000, 0x800, MC_ACCESS_READ | MC_ACCESS_WRITE, "/purr/meow"); - _map_init_vm_area(&area, &m, NULL, &o_n, &entry); + _map_init_vm_area(&area, &entry, NULL, &o_n, &m); //assert state _assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, @@ -612,7 +689,7 @@ START_TEST(test__map_obj_add_area) { //initialise first area _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "/foo/bar"); - _map_init_vm_area(&area[0], &m, &o_n, NULL, &entry); + _map_init_vm_area(&area[0], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[0], &area[0]); //add first area to the backing object @@ -628,7 +705,7 @@ START_TEST(test__map_obj_add_area) { //initialise lower area _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/foo/bar"); - _map_init_vm_area(&area[1], &m, &o_n, NULL, &entry); + _map_init_vm_area(&area[1], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[1], &area); //add lower area to the backing object @@ -644,7 +721,7 @@ START_TEST(test__map_obj_add_area) { //initialise higher area _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/foo/bar"); - _map_init_vm_area(&area[2], &m, &o_n, NULL, &entry); + _map_init_vm_area(&area[2], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[2], &area); //add lower area to the backing object @@ -660,7 +737,7 @@ START_TEST(test__map_obj_add_area) { //initialise middle area _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "/foo/bar"); - _map_init_vm_area(&area[3], &m, &o_n, NULL, &entry); + _map_init_vm_area(&area[3], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[3], &area); //add middle area to the backing object @@ -696,7 +773,7 @@ START_TEST(test__map_obj_add_last_area) { //initialise first area _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "anonmap"); - _map_init_vm_area(&last_area[0], &m, &o_n, NULL, &entry); + _map_init_vm_area(&last_area[0], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[0], &last_area[0]); //add first area to the backing object @@ -712,7 +789,7 @@ START_TEST(test__map_obj_add_last_area) { //initialise lower area _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/bin/cat"); - _map_init_vm_area(&last_area[1], &m, &o_n, NULL, &entry); + _map_init_vm_area(&last_area[1], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[1], &last_area); //add lower area to the backing object @@ -729,7 +806,7 @@ START_TEST(test__map_obj_add_last_area) { //initialise higher area _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/lib/std"); - _map_init_vm_area(&last_area[2], &m, &o_n, NULL, &entry); + _map_init_vm_area(&last_area[2], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[2], &last_area); //add lower area to the backing object @@ -745,7 +822,7 @@ START_TEST(test__map_obj_add_last_area) { //initialise middle area _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "io"); - _map_init_vm_area(&last_area[3], &m, &o_n, NULL, &entry); + _map_init_vm_area(&last_area[3], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[3], &last_area); //add middle area to the backing object @@ -890,7 +967,7 @@ START_TEST(test__map_find_obj_for_area) { _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libpthread"); - ret = _map_find_obj_for_area(&state, &entry); + ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_NEW); @@ -899,7 +976,7 @@ START_TEST(test__map_find_obj_for_area) { _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libpthread"); - ret = _map_find_obj_for_area(&state, &entry); + ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_NEW); @@ -908,7 +985,7 @@ START_TEST(test__map_find_obj_for_area) { _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libc"); - ret = _map_find_obj_for_area(&state, &entry); + ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_PREV); @@ -917,7 +994,7 @@ START_TEST(test__map_find_obj_for_area) { _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "anonmap"); - ret = _map_find_obj_for_area(&state, &entry); + ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_NEXT); @@ -932,95 +1009,518 @@ START_TEST(test__map_find_obj_for_area) { -//_map_backtrack_unmapped_obj_last_vm_areas() +//_map_backtrack_unmapped_obj_last_vm_areas() [stub map fixture] START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { - /* - * Stub map: - * - * 0) 0x1000 - 0x2000 /bin/cat r-- - * 1) 0x2000 - 0x3000 /bin/cat rw- - * 2) 0x3000 - 0x4000 /bin/cat r-x - * 3) 0x4000 - 0x5000 [heap] rw- <- gap - * 4) 0x6000 - 0x7000 rw- <- gap - * 5) 0x8000 - 0x9000 /lib/foo rw- - * 6) 0x9000 - 0xA000 /lib/foo rw- - * 7) 0xA000 - 0xB000 /lib/foo r-x <- gap - * 8) 0xC000 - 0xD000 rw- <- gap - * 9) 0xE000 - 0xF000 [stack] rw- - */ - int ret; - mc_vm_area * area; - mc_vm_obj * obj; - cm_lst_node * area_node, * zero_node; + mc_vm_obj * zero_obj; + cm_lst_node * zero_node; - uintptr_t first_state[2] = {0x6000, 0xC000}; + uintptr_t first_state[2] = {0x6000, 0xC000}; uintptr_t second_state[2] = {0x6000, 0xC000}; - + uintptr_t third_state[2] = {0x6000, 0xC000}; + //backtrack `/lib/foo`'s last area (index: 8) ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[2]); ck_assert_int_eq(ret, 0); - - //assert state - + //check `/lib/foo` no longer has any last areas associated with it - obj = MC_GET_NODE_OBJ(((cm_lst_node *) &m_o_n[2])); - _assert_vm_obj(obj, "/lib/foo", "/lib/foo", 0x8000, 0xA000, 3, 0, 3, true); + _assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, true); //check `[heap]` now has two last areas associated with it - obj_node = cm_lst_get_n(&m.vm_objs, 2); - obj = MC_GET_NODE_OBJ(obj_node); - _assert_vm_obj(obj, "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 2, true); - _assert_vm_obj_list(&obj->last_vm_area_node_ps, first_state, 2); + _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); + _assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, first_state, 2); //check the transfered last area (index: 8) now points to `[heap]` - area_node = cm_lst_get_n(&m.vm_areas, 8); - area = MC_GET_NODE_AREA(area_node); - _assert_vm_area(area, NULL, NULL, 0xC000, 0xD000, - MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, obj_node, 8, true); + _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, &m_o_n[1], 8, true); - //backtrack `[heap]` and then further backtrack `/bin/cat` (index: 8, 4) - obj_node = cm_lst_get_n(&m.vm_objs, 2); - ret = _map_backtrack_unmapped_obj_last_vm_areas(obj_node); - ck_assert_int_eq(ret, 0); - obj_node = cm_lst_get_n(&m.vm_objs, 1); - ret = _map_backtrack_unmapped_obj_last_vm_areas(obj_node); + //backtrack `[heap]`'s two last areas (indeces: 4, 8) + ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[1]); ck_assert_int_eq(ret, 0); - //assert state + //check `[heap]` no longer has any last areas associated with it + _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 0, 1, true); + + //check `/bin/cat` now has two last areas associated with it + _assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 2, 0, true); + _assert_vm_obj_list(&m_o[0].last_vm_area_node_ps, first_state, 2); + + //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` + _assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[0], 4, true); - //check - obj_node = cm_lst_get_n(&m.vm_objs, 0); - obj = MC_GET_NODE_OBJ(obj_node); - _assert_vm_obj(obj, "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); - _assert_vm_obj_list(&obj->last_vm_area_node_ps, second_state, 2); + _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[0], 8, true); + + - area_node = cm_lst_get_n(&m.vm_areas, 8); - area = MC_GET_NODE_AREA(area_node); - _assert_vm_area(area, NULL, NULL, 0xC000, 0xD000, - MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, obj_node, 8, true); - - area_node = cm_lst_get_n(&m.vm_areas, 4); - area = MC_GET_NODE_AREA(area_node); - _assert_vm_area(area, NULL, NULL, 0x6000, 0x7000, - MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, obj_node, 4, true); + //backtrack `/bin/cat`'s two lat areas (indeces: 4, 8) + ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[0]); + ck_assert_int_eq(ret, 0); + + //get the pseudo object + zero_node = cm_lst_get_n(&m.vm_objs, 0); + zero_obj = MC_GET_NODE_OBJ(zero_node); + //check `/bin/cat` no longer has any last areas associated with it + _assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 0, 0, true); + //check the pseudo object now has two last areas associated with it + _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, + 0, 2, MC_ZERO_OBJ_ID, true); + _assert_vm_obj_list(&zero_obj->last_vm_area_node_ps, third_state, 2); + + //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` + _assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, zero_node, 4, true); + + _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, zero_node, 8, true); + return; } END_TEST -//_map_forward_unmapped_obj_last_vm_areas() +//_map_forward_unmapped_obj_last_vm_areas() [stub map fixture] START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { + int ret; + + uintptr_t heap_state[2] = {0x6000}; + uintptr_t lib_foo_state[2] = {0xC000}; + + + //setup the test by backtracking `/lib/foo`'s last area. + ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[2]); + ck_assert_int_eq(ret, 0); + + + //pretend the `/lib/foo` object was just inserted + ret = _map_forward_unmapped_obj_last_vm_areas(&m_o_n[2]); + ck_assert_int_eq(ret, 0); + + + //check `[heap]` has only one last area associated with it + _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 1, 1, true); + _assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 1); + + //check `/lib/foo` now has one last area associated with it + _assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 2, 0, true); + _assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, lib_foo_state, 1); + + //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` + _assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[1], 4, true); + + _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[2], 8, true); + + return; + +} END_TEST + + + +//_map_unlink_unmapped_obj() +START_TEST(test__map_unlink_unmapped_obj) { + + int ret; + uintptr_t heap_state[2] = {0x6000, 0xC000}; + + struct obj_check obj_state[3] = { + {"cat", 0x1000, 0x4000}, + {"[heap]", 0x4000, 0x5000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct obj_check unmapped_obj_state[1] = { + {"foo", 0x8000, 0xB000} + }; + + + //unlink `/lib/foo` + ret = _map_unlink_unmapped_obj(&m_o_n[2], &m); + ck_assert_int_eq(ret, 0); + + //check `/lib/foo` has no last areas associated with it, and is unmapped + _assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, false); + _assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, NULL, 0); + + //check `[heap]` has no last areas associated with it + _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); + _assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 2); + + + //check state of mapped objects + _assert_vm_map_objs(&m.vm_objs, obj_state, 3); + + //check state of unmapped objects + _assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 1); + + //check removed object has no links to other objects anymore + ck_assert(m_o[2].mapped == false); + ck_assert_int_eq(m_o[2].start_addr, 0x0); + ck_assert_int_eq(m_o[2].end_addr, 0x0); + ck_assert_ptr_null(m_o_n[2].next); + ck_assert_ptr_null(m_o_n[2].prev); + + return; + +} END_TEST + + + +//_map_unlink_unmapped_area() [stub map fixture] +START_TEST(test__map_unlink_unmapped_area) { + + int ret; + + + //remove /lib/foo:1: object state + struct obj_check foo_obj_state[4] = { + {"cat", 0x1000, 0x4000}, + {"[heap]", 0x4000, 0x5000}, + {"foo", 0x9000, 0xB000}, + {"[stack]", 0xE000, 0xF000} + }; + + //remove /lib/foo:1: area state + struct area_check foo_area_state[9] = { + {"cat", 0x1000, 0x2000}, + {"cat", 0x2000, 0x3000}, + {"cat", 0x3000, 0x4000}, + {"[heap]", 0x4000, 0x5000}, + {NULL, 0x6000, 0x7000}, + {"foo", 0x9000, 0xA000}, + {"foo", 0xA000, 0xB000}, + {NULL, 0xC000, 0xD000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct area_check foo_unmapped_area_state[1] = { + {"foo", 0x8000, 0x9000} + }; + + + //remove [heap]: object state + struct obj_check heap_obj_state[3] = { + {"cat", 0x1000, 0x4000}, + {"foo", 0x9000, 0xB000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct obj_check heap_unmapped_obj_state[1] = { + {"[heap]", 0x0, 0x0} + }; + + //remove [heap]: area state: + struct area_check heap_area_state[8] = { + {"cat", 0x1000, 0x2000}, + {"cat", 0x2000, 0x3000}, + {"cat", 0x3000, 0x4000}, + {NULL, 0x6000, 0x7000}, + {"foo", 0x9000, 0xA000}, + {"foo", 0xA000, 0xB000}, + {NULL, 0xC000, 0xD000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct area_check heap_unmapped_area_state[2] = { + {"foo", 0x8000, 0x9000}, + {"[heap]", 0x4000, 0x5000} + }; + + + //remove `/lib/foo`'s first area + ret = _map_unlink_unmapped_area(&m_a_n[6], &m); + ck_assert_int_eq(ret, 0); + + //check state + _assert_vm_area(&m_a[5], "/lib/foo", "foo", + 0x8000, 0x9000, MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, NULL, 5, false); + + _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 4); + _assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0); + _assert_vm_map_areas(&m.vm_areas, foo_area_state, 9); + _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 1); + + + //remove '[heap]''s only area + ret = _map_unlink_unmapped_area(&m_a_n[3], &m); + ck_assert_int_eq(ret, 0); + + //check state + _assert_vm_area(&m_a[3], "[heap]", "[heap]", + 0x4000, 0x5000, MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, NULL, 5, false); + + _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 1); + _assert_vm_map_areas(&m.vm_areas, heap_area_state, 8); + _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 2); + + return; + +} END_TEST + + + +//_map_check_area_eql() [empty map fixture] +START_TEST(test__map_check_area_eql) { + + int ret; + + struct vm_entry entry; + mc_vm_area area; + cm_lst_node area_node; + + mc_vm_obj obj; + cm_lst_node obj_node; + + + //construct a vm_obj + _map_new_vm_obj(&obj, &m, "/bin/cat"); + _create_lst_wrapper(&obj_node, &obj); + + //create a vm_area + _init_vm_entry(&entry, 0x1000, 0x2000, 0x0, MC_ACCESS_READ, "/bin/cat"); + _map_init_vm_area(&area, &entry, &obj_node, NULL, &m); + _create_lst_wrapper(&area_node, &area); + + + //entry same as vm_area + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, 0); + + //entry start address is different + entry.vm_start = 0x500; + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, -1); + entry.vm_start = area.start_addr; + + //entry end address is different + entry.vm_end = 0x2500; + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, -1); + entry.vm_end = area.end_addr; + + //entry permissions are different + entry.prot = MC_ACCESS_READ | MC_ACCESS_WRITE; + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, -1); + entry.prot = area.access; + + //both entry and area don't have a path + entry.file_path[0] = '\0'; + area.pathname = NULL; + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, 0); + entry.file_path[0] = '/'; + area.pathname = obj.pathname; + + //entry has a path, area does not + area.pathname = NULL; + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, 0); + area.pathname = obj.pathname; + + //entry does not have a path, area does + entry.file_path[0] = '\0'; + ret = _map_check_area_eql(&entry, &area_node); + ck_assert_int_eq(ret, 0); + entry.file_path[0] = '/'; + + + //destroy pseudo object + _map_del_vm_obj(&obj); return; } END_TEST + + + +//_map_resync_area() [stub map fixture] +START_TEST(test__map_resync_area) { + + //remove [heap]: object state + struct obj_check heap_obj_state[3] = { + {"cat", 0x1000, 0x4000}, + {"foo", 0x9000, 0xB000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct obj_check heap_unmapped_obj_state[1] = { + {"[heap]", 0x4000, 0x5000} + }; + + //remove [heap]: area state + struct area_check heap_area_state[9] = { + {"cat", 0x1000, 0x2000}, + {"cat", 0x2000, 0x3000}, + {"cat", 0x3000, 0x4000}, + {NULL, 0x6000, 0x7000}, + {"foo", 0x8000, 0x9000}, + {"foo", 0x9000, 0xA000}, + {"foo", 0xA000, 0xB000}, + {NULL, 0xC000, 0xD000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct area_check heap_unmapped_area_state[1] = { + {"[heap]", 0x4000, 0x5000} + }; + + + + //remove /lib/foo:1,2: object state + struct obj_check foo_obj_state[3] = { + {"cat", 0x1000, 0x4000}, + {"foo", 0xA000, 0xB000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct obj_check foo_unmapped_obj_state[1] = { + {"[heap]", 0x4000, 0x5000} + }; + + //remove /lib/foo:1,2: area state + struct area_check foo_area_state[7] = { + {"cat", 0x1000, 0x2000}, + {"cat", 0x2000, 0x3000}, + {"cat", 0x3000, 0x4000}, + {NULL, 0x6000, 0x7000}, + {"foo", 0xA000, 0xB000}, + {NULL, 0xC000, 0xD000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct area_check foo_unmapped_area_state[3] = { + {"[heap]", 0x4000, 0x5000}, + {"foo", 0x8000, 0x9000}, + {"foo", 0x9000, 0xA000}, + }; + + + + //remove /bin/cat:1,2,3: object state + struct obj_check cat_obj_state[2] = { + {"foo", 0xA000, 0xB000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct obj_check cat_unmapped_obj_state[2] = { + {"cat", 0x0, 0x0}, + {"[heap]", 0x0, 0x0} + }; + + //remove /bin/cat:1,2,3: area state + struct area_check cat_area_state[4] = { + {NULL, 0x6000, 0x7000}, + {"foo", 0xA000, 0xB000}, + {NULL, 0xC000, 0xD000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct area_check cat_unmapped_area_state[6] = { + {"[heap]", 0x4000, 0x5000}, + {"foo", 0x8000, 0x9000}, + {"foo", 0x9000, 0xA000}, + {"cat", 0x1000, 0x2000}, + {"cat", 0x2000, 0x3000}, + {"cat", 0x3000, 0x4000}, + }; + + int ret; + + struct vm_entry entry; + _traverse_state state; + + + //correct by removing `[heap]` + _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); + state.next_area_node = &m_a_n[6]; + state.prev_obj_node = &m_o_n[0]; + + ret = _map_resync_area(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + //check state + _assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, NULL, 5, true); + _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), + "/bin/cat", "cat", 0x1000, 0x3000, 3, 1, 0, true); + + _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 1); + _assert_vm_map_areas(&m.vm_areas, heap_area_state, 9); + _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 1); + + + + //correct by removing first 2 areas of `/lib/foo` + _init_vm_entry(&entry, 0xA000, 0xB000, 0x0, + MC_ACCESS_READ | MC_ACCESS_EXEC, "/lib/foo"); + state.next_area_node = &m_a_n[5]; + state.prev_obj_node = &m_o_n[2]; + + ret = _map_resync_area(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + //check state + _assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, &m_o_n[2], 8, true); + + _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", + 0xA000, 0xB000, 1, 1, 2, true); + + _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, foo_unmapped_obj_state, 1); + _assert_vm_map_areas(&m.vm_areas, foo_area_state, 7); + _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 3); + + + + //correct by removing `/bin/cat`, check last areas are correctly + //transferred to the pseudo object + _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, + MC_ACCESS_READ | MC_ACCESS_EXEC, NULL); + state.next_area_node = &m_a_n[0]; + state.prev_obj_node = &m_o_n[0]; + + ret = _map_resync_area(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + //check state + _assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, m.vm_objs.head, 4, true); + + _assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", + 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); + + _assert_vm_map_objs(&m.vm_objs, cat_obj_state, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, cat_unmapped_obj_state, 2); + _assert_vm_map_areas(&m.vm_areas, cat_area_state, 4); + _assert_vm_map_areas(&m.vm_areas_unmapped, cat_unmapped_area_state, 6); + + return; + +} END_TEST + + + + +#endif From 351f9d9038014dd30b0869f1033419671ceb78ef Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 29 Dec 2024 05:11:46 +0000 Subject: [PATCH 07/45] finish internal unit tests (not run) --- src/lib/map.c | 205 ++++++++-------- src/lib/map.h | 20 +- src/test/check_map.c | 556 +++++++++++++++++++++++++++---------------- 3 files changed, 463 insertions(+), 318 deletions(-) diff --git a/src/lib/map.c b/src/lib/map.c index e182373..00e1fc4 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -634,103 +634,57 @@ int _map_check_area_eql(const struct vm_entry * entry, -DBG_STATIC DBG_INLINE -int _map_resync_area(const struct vm_entry * entry, - _traverse_state * state, mc_vm_map * map) { - - int ret; - - mc_vm_area * area; - cm_lst_node * area_node; - - - //setup iteration - area_node = state->next_area_node; - area = MC_GET_NODE_AREA(area_node); - - //while there are vm areas left to discard - while (entry->vm_end > area->start_addr) { - - //make state point to the next node, and NULL if there is no next node - if (state->next_area_node->next == map->vm_objs.head) { - state->next_area_node = NULL; - } else { - state->next_area_node = state->next_area_node->next; - } - - //correctly handle removing this area node - ret = _map_unlink_unmapped_area(area_node, map); - if (ret) return -1; - - //update iterator nodes - if (state->next_area_node == NULL) break; - area_node = state->next_area_node; - area = MC_GET_NODE_AREA(area_node); - - } //end while - - return 0; -} - - - -#define _STATE_AREA_NODE_KEEP 0 -#define _STATE_AREA_NODE_ADVANCE 1 -#define _STATE_AREA_NODE_REASSIGN 2 - DBG_STATIC -void _map_state_inc_area(mc_vm_map * vm_map, _traverse_state * state, - const cm_lst_node * assign_node, const int inc_type) { +void _map_state_inc_area(_traverse_state * state, const int inc_type, + const cm_lst_node * assign_node, mc_vm_map * map) { switch (inc_type) { case _STATE_AREA_NODE_KEEP: + break; case _STATE_AREA_NODE_ADVANCE: + //advance next area if we haven't reached the end //& dont circle back to the start - if (state->next_area != NULL - && state->next_area_index < vm_map->vm_areas.len) { - - state->next_area = state->next_area->next; + if (state->next_area_node != NULL + && state->next_area_node != map->vm_areas.head) { + state->next_area_node = state->next_area_node->next; } else { - state->next_area = NULL; - } + state->next_area_node = NULL; + + } break; case _STATE_AREA_NODE_REASSIGN: - state->next_area = (cm_lst_node *) assign_node; + + state->next_area_node = (cm_lst_node *) assign_node; break; } //end switch - //always increment state index - ++state->next_area_index; - return; } DBG_STATIC -void _map_state_inc_obj(mc_vm_map * vm_map, _traverse_state * state) { +void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map) { //if there is no prev obj, initialise it - if (state->prev_obj == NULL) { + if (state->prev_obj_node == NULL) { - state->prev_obj = vm_map->vm_objs.head; - state->prev_obj_index = 0; + state->prev_obj_node = map->vm_objs.head; //if there is a prev obj } else { //only advance next object if we won't circle back to start - if (state->prev_obj_index < vm_map->vm_objs.len) {; + if (state->prev_obj_node->next != map->vm_objs.head) { - state->prev_obj = state->prev_obj->next; - ++state->prev_obj_index; + state->prev_obj_node = state->prev_obj_node->next; } } @@ -739,39 +693,71 @@ void _map_state_inc_obj(mc_vm_map * vm_map, _traverse_state * state) { -DBG_STATIC -cm_lst_node * _map_add_obj(mc_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry) { +DBG_STATIC DBG_INLINE +int _map_resync_area(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map) { - int index; + int ret; + + mc_vm_area * area; + cm_lst_node * area_node; + + + //setup iteration + area_node = state->next_area_node; + area = MC_GET_NODE_AREA(area_node); + + //while there are vm areas left to discard + while (entry->vm_end > area->start_addr) { + + //advance state + _map_state_inc_area(state, _STATE_AREA_NODE_ADVANCE, NULL, map); + + //remove this area node + ret = _map_unlink_unmapped_area(area_node, map); + if (ret) return -1; + + //update iterator nodes + if (state->next_area_node == NULL) break; + area_node = state->next_area_node; + area = MC_GET_NODE_AREA(area_node); + + } //end while + + return 0; +} + + + +DBG_STATIC +cm_lst_node * _map_add_obj(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map) { mc_vm_obj vm_obj; cm_lst_node * obj_node; - //create new object - _new_vm_obj(&vm_obj, vm_map, entry->file_path); - //figure out which insertion index to use - index = (vm_map->vm_objs.len == 0) ? 0 : state->prev_obj_index + 1; + //create new object + _map_new_vm_obj(&vm_obj, map, entry->file_path); - //insert obj into map at state's index - obj_node = cm_lst_ins(&vm_map->vm_objs, index, &vm_obj); + //insert obj into map + obj_node = cm_lst_ins_na(&map->vm_objs, state->prev_obj_node, &vm_obj); if (obj_node == NULL) { mc_errno = MC_ERR_LIBCMORE; return NULL; } /* - * With the insertion of this object, vm_areas in the previous object's - * last_vm_area_node_ps list may now incorrectly treat the previous object - * as the last object, when in fact this newly inserted object should be - * their new last object. This needs to now be corrected. + * With the insertion of this object, it may now be closer to some + * memory areas without a backing object. For such memory areas, their + * `last_obj_node_p` pointer must be updated. + * */ - _forward_unmapped_obj_last_vm_areas(obj_node); + _map_forward_unmapped_obj_last_vm_areas(obj_node); //advance state - _state_inc_obj(vm_map, state); + _map_state_inc_obj(state, map); return obj_node; } @@ -779,76 +765,85 @@ cm_lst_node * _map_add_obj(mc_vm_map * vm_map, _traverse_state * state, DBG_STATIC -int _map_add_area(mc_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry, const int inc_type) { +int _map_add_area(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map) { int ret; - bool use_obj = false; - - mc_vm_area vm_area; - mc_vm_obj * vm_obj; + bool use_obj; + mc_vm_area area; cm_lst_node * area_node; + + mc_vm_obj * obj; cm_lst_node * obj_node; - //if no obj for this area - if (entry->file_path[0] != '\0') use_obj = true; + //determine if this area belongs to a backing object + use_obj = (entry->file_path[0] == '\0') ? false : true; + + + //if this area does not belong to a backing object, create a new area if (!use_obj) { /* * It should never be possible for prev_obj * to point at/ahead of this vm_area. */ - _new_vm_area(&vm_area, vm_map, NULL, state->prev_obj, entry); + _map_init_vm_area(&area, entry, NULL, state->prev_obj_node, map); + - //else there is an obj for this area + //else there is a backing object for this area } else { - ret = _find_obj_for_area(state, entry); - + //determine which of the adjascent backing objects this area belongs to + ret = _map_find_obj_for_area(entry, state); + + //dispatch case switch (ret) { - //area belongs to previous obj + //area belongs to the previous object case _MAP_OBJ_PREV: - _new_vm_area(&vm_area, vm_map, state->prev_obj, NULL, entry); break; - //area is part of a new object + //area is the start of a new object case _MAP_OBJ_NEW: - obj_node = _map_add_obj(vm_map, state, entry); + obj_node = _map_add_obj(entry, state, map); if (obj_node == NULL) return -1; - _new_vm_area(&vm_area, vm_map, state->prev_obj, NULL, entry); break; - //area belongs to the next obj + //area belongs to the next object case _MAP_OBJ_NEXT: - _state_inc_obj(vm_map, state); - _new_vm_area(&vm_area, vm_map, state->prev_obj, NULL, entry); + _map_state_inc_obj(state, map); break; } //end switch - } + + //initialise the area + _map_init_vm_area(&area, entry, state->prev_obj_node, NULL, map); + + } //end if-else + //add area to the map list - area_node = cm_lst_ins(&vm_map->vm_areas, state->next_area_index, &vm_area); + area_node = cm_lst_ins_nb(&map->vm_areas, state->next_area_node, &area); if (area_node == NULL) { mc_errno = MC_ERR_LIBCMORE; return -1; } //add area to the object pointer list - vm_obj = MC_GET_NODE_OBJ(state->prev_obj); + obj = MC_GET_NODE_OBJ(state->prev_obj_node); if (use_obj) { - ret = _obj_add_area(vm_obj, area_node); + ret = _map_obj_add_area(obj, area_node); if (ret == -1) return -1; + } else { - ret = _obj_add_last_area(vm_obj, area_node); + ret = _map_obj_add_last_area(obj, area_node); if (ret == -1) return -1; } //increment area state - _state_inc_area(vm_map, state, area_node, inc_type); + _map_state_inc_area(state, _STATE_AREA_NODE_REASSIGN, area_node->next, map); return 0; } diff --git a/src/lib/map.h b/src/lib/map.h index 054c015..1ed1527 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -15,6 +15,10 @@ #define _MAP_OBJ_NEW 1 #define _MAP_OBJ_NEXT 2 +#define _STATE_AREA_NODE_KEEP 0 +#define _STATE_AREA_NODE_ADVANCE 1 +#define _STATE_AREA_NODE_REASSIGN 2 + /* * Initialise _traverse_state manually on the first call to * send_map_node() for a map generated by a memory interface. @@ -65,17 +69,17 @@ int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map); int _map_check_area_eql(const struct vm_entry * entry, const cm_lst_node * area_node); + +void _map_state_inc_area(_traverse_state * state, const int inc_type, + const cm_lst_node * assign_node, mc_vm_map * map); +void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map); int _map_resync_area(const struct vm_entry * entry, _traverse_state * state, mc_vm_map * map); -void _map_state_inc_area(mc_vm_map * vm_map, _traverse_state * state, - const cm_lst_node * assign_node, const int inc_type); -void _map_state_inc_obj(mc_vm_map * vm_map, _traverse_state * state); - -cm_lst_node * _map_add_obj(mc_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry); -int _map_add_area(mc_vm_map * vm_map, _traverse_state * state, - const struct vm_entry * entry, const int inc_type); +cm_lst_node * _map_add_obj(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map); +int _map_add_area(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map); #endif diff --git a/src/test/check_map.c b/src/test/check_map.c index 5921774..7857221 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -22,29 +22,36 @@ /* - * Functions are not tested in the same order as they appear in the map.c - * source file. This assists with bootstrapping many tests. The order of - * testing remains close. - */ - - -/* - * -- [UNTESTED FUNCTIONS] --- + * [ADVANCED TEST] + * + * The virtuam memory map management code is complicated and as such, + * almost every internal function has independent tests. For these tests + * to run, the debug target must be built. + * + * The following functions do not have unit tests: * - * _map_obj_add_area_insert(): + * _map_obj_add_area_insert(): * - * Tested through _map_obj_add_area() and _map_obj_add_last_area() + * > Tested through `_map_obj_add_area()` + * and `_map_obj_add_last_area()` * - * _map_obj_find_area_outer_node(): + * _map_obj_find_area_outer_node(): + * + * > Tested through `_map_obj_rmv_area()` + * and `_map_obj_rmv_last_area()` * - * Tested through _map_obj_rmv_area() and _map_obj_rmv_last_area(); * */ +/* + * Functions are not tested in the same order as they appear in the map.c + * source file. This assists with bootstrapping many tests. The order of + * testing remains close. + */ + -//test structures - +//map test structures struct obj_check { char basename[NAME_MAX]; @@ -146,7 +153,7 @@ static void _create_lst_wrapper(cm_lst_node * node, void * data) { -//integration test for CMore +//assert the length of a list, also works as an integration test for CMore static void _assert_lst_len(cm_lst * list, int len) { ck_assert_int_eq(list->len, len); @@ -177,6 +184,7 @@ static void _assert_lst_len(cm_lst * list, int len) { +//basic assertion of state for a mc_vm_map static void _assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, int vm_areas_unmapped_len, int vm_objs_unmapped_len, int next_id_area, int next_id_obj) { @@ -198,15 +206,16 @@ static void _assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, +//assert the state of all [unmapped] objects inside a mc_vm_map static void _assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, - int obj_checks_len) { + int start_index, int len) { mc_vm_obj * obj; - for (int i = 0; i < obj_checks_len; ++i) { + for (int i = 0; i < len; ++i) { - obj = cm_lst_get_p(obj_lst, i); + obj = cm_lst_get_p(obj_lst, start_index + i); ck_assert_ptr_nonnull(obj); ck_assert_str_eq(obj->basename, obj_checks[i].basename); @@ -219,15 +228,16 @@ static void _assert_vm_map_objs(cm_lst * obj_lst, +//assert the state of all [unmapped] memory areas inside a mc_vm_map static void _assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, - int area_checks_len) { + int start_index, int len) { mc_vm_area * area; - for (int i = 0; i < area_checks_len; ++i) { + for (int i = 0; i < len; ++i) { - area = cm_lst_get_p(area_lst, i); + area = cm_lst_get_p(area_lst, start_index + i); ck_assert_ptr_nonnull(area); ck_assert_str_eq(area->basename, area_checks[i].basename); @@ -522,7 +532,7 @@ static void _setup_stub_vm_obj() { //setup area _init_vm_entry(&entry, addr, addr + 0x1000, file_off, MC_ACCESS_READ, "/foo/bar"); - _map_init_vm_area(&o_a[i], &m, &o_n, NULL, &entry); + _map_init_vm_area(&o_a[i], &entry, &o_n, NULL, &m); _map_obj_add_area(&o, &o_a_n[i]); //advance iteration @@ -575,18 +585,16 @@ START_TEST(test_mc_new_del_vm_map) { mc_vm_obj * zero_obj; - //construct the map + //only test: construct the map mc_new_vm_map(&m); - //assert state _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); - //check the zero is present object + //check the pseudo object is present zero_obj = MC_GET_NODE_OBJ(m.vm_objs.head); _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); - //delete the map mc_del_vm_map(&m); return; @@ -601,10 +609,9 @@ START_TEST(test__map_new_del_vm_obj) { mc_vm_obj obj; - //construct the object + //only test: construct the object _map_new_vm_obj(&obj, &m, "/foo/bar"); - //assert state _assert_vm_obj(&obj, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); _assert_vm_map(&m, 0, 1, 0, 0, 0, 1); @@ -622,10 +629,9 @@ START_TEST(test__map_make_zero_obj) { //create new object _map_new_vm_obj(&zero_obj, &m, "0x0"); - //convert new object to pseudo object + //only test: convert new object to pseudo object _map_make_zero_obj(&zero_obj); - - //assert state + _assert_vm_obj(&zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); @@ -645,23 +651,21 @@ START_TEST(test__map_init_vm_area) { struct vm_entry entry; - //create a stub entry & initialise the new area + //first test: create a stub entry & initialise the new area _init_vm_entry(&entry, 0x1000, 0x2000, 0x800, MC_ACCESS_READ, "/foo/bar"); _map_init_vm_area(&area, &entry, &o_n, NULL, &m); - //assert state _assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 0, true); _assert_vm_map(&m, 0, 1, 0, 0, 1, 0); - //create a stub entry & initialise another new area + //second test: create a stub entry & initialise another new area _init_vm_entry(&entry, 0x2000, 0x4000, 0x800, MC_ACCESS_READ | MC_ACCESS_WRITE, "/purr/meow"); _map_init_vm_area(&area, &entry, NULL, &o_n, &m); - //assert state _assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &o_n, 1, true); _assert_vm_map(&m, 0, 1, 0, 0, 2, 0); @@ -692,10 +696,10 @@ START_TEST(test__map_obj_add_area) { _map_init_vm_area(&area[0], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[0], &area[0]); - //add first area to the backing object + + //first test: add first area to the backing object _map_obj_add_area(&o, &area_node[0]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x2000, 0x3000, 1, 0, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_first, 1); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); @@ -708,10 +712,9 @@ START_TEST(test__map_obj_add_area) { _map_init_vm_area(&area[1], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[1], &area); - //add lower area to the backing object + //second test: add lower area to the backing object _map_obj_add_area(&o, &area_node[1]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x3000, 2, 0, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_lower, 2); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); @@ -724,10 +727,9 @@ START_TEST(test__map_obj_add_area) { _map_init_vm_area(&area[2], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[2], &area); - //add lower area to the backing object + //third test: add lower area to the backing object _map_obj_add_area(&o, &area_node[2]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_higher, 3); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); @@ -740,10 +742,9 @@ START_TEST(test__map_obj_add_area) { _map_init_vm_area(&area[3], &entry, &o_n, NULL, &m); _create_lst_wrapper(&area_node[3], &area); - //add middle area to the backing object + //fourth test: add middle area to the backing object _map_obj_add_area(&o, &area_node[3]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 4); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); @@ -776,10 +777,9 @@ START_TEST(test__map_obj_add_last_area) { _map_init_vm_area(&last_area[0], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[0], &last_area[0]); - //add first area to the backing object + //first test: add first area to the backing object _map_obj_add_area(&o, &last_area_node[0]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); _assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); @@ -792,10 +792,9 @@ START_TEST(test__map_obj_add_last_area) { _map_init_vm_area(&last_area[1], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[1], &last_area); - //add lower area to the backing object + //second test: add lower area to the backing object _map_obj_add_area(&o, &last_area_node[1]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); _assert_vm_obj_list(&o.last_vm_area_node_ps, state_lower, 2); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); @@ -809,10 +808,9 @@ START_TEST(test__map_obj_add_last_area) { _map_init_vm_area(&last_area[2], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[2], &last_area); - //add lower area to the backing object + //third test: add lower area to the backing object _map_obj_add_area(&o, &last_area_node[2]); - //assert state _assert_vm_obj(&o, "/lib/std", "std", 0x0, 0x0, 0, 3, 0, true); _assert_vm_obj_list(&o.last_vm_area_node_ps, state_higher, 3); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); @@ -825,10 +823,9 @@ START_TEST(test__map_obj_add_last_area) { _map_init_vm_area(&last_area[3], &entry, &o_n, NULL, &m); _create_lst_wrapper(&last_area_node[3], &last_area); - //add middle area to the backing object + //fourth test: add middle area to the backing object _map_obj_add_area(&o, &last_area_node[3]); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); _assert_vm_obj_list(&o.last_vm_area_node_ps, state_middle, 4); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); @@ -851,39 +848,34 @@ START_TEST(test__map_obj_rmv_area) { uintptr_t state_last[1] = {0x3000}; - - //remove middle area + //first test: remove middle area ret = _map_obj_rmv_area(&o, &o_a_n[1]); ck_assert_int_eq(ret, 0); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 2, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 3); - //remove first area + //second test: remove first area ret = _map_obj_rmv_area(&o, &o_a_n[0]); ck_assert_int_eq(ret, 0); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x5000, 2, 2, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_first, 2); - //remove last area + //third test: remove last area ret = _map_obj_rmv_area(&o, &o_a_n[3]); ck_assert_int_eq(ret, 0); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x4000, 1, 2, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, state_last, 1); - //remove only remaining area + //fourth test: remove only remaining area ret = _map_obj_rmv_area(&o, &o_a_n[2]); ck_assert_int_eq(ret, 0); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); _assert_vm_obj_list(&o.vm_area_node_ps, NULL, 0); @@ -901,20 +893,18 @@ START_TEST(test__map_obj_rmv_last_area) { uintptr_t state_first[1] = {0x6000}; - //remove first last area + //first test: remove first last area ret = _map_obj_rmv_last_area(&o, &o_a_l_n[0]); ck_assert_int_eq(ret, 0); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 1, 0, true); _assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); - //remove only remaining last area + //second test: remove only remaining last area ret = _map_obj_rmv_last_area(&o, &o_a_l_n[1]); ck_assert_int_eq(ret, 0); - //assert state _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); _assert_vm_obj_list(&o.last_vm_area_node_ps, NULL, 0); @@ -928,11 +918,12 @@ START_TEST(test__map_is_pathname_in_obj) { bool ret; - //path is in the object + + //first test: path is in the object ret = _map_is_pathname_in_obj("/foo/bar", &o); ck_assert(ret); - //path is not in the object + //second test: path is not in the object ret = _map_is_pathname_in_obj("anonmap", &o); ck_assert(!ret); @@ -955,14 +946,14 @@ START_TEST(test__map_find_obj_for_area) { _traverse_state state; - //construct objects + //construct test objects for (int i = 0; i < 3; ++i) { _map_new_vm_obj(&objs[i], &m, obj_paths[i]); _create_lst_wrapper(&obj_nodes[i], &objs[i]); } - //new object, state empty + //first test: new object, state empty _init__traverse_state(&state, NULL, NULL); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libpthread"); @@ -971,7 +962,7 @@ START_TEST(test__map_find_obj_for_area) { ck_assert_int_eq(ret, _MAP_OBJ_NEW); - //new object, state full + //second test: new object, state full _init__traverse_state(&state, NULL, &obj_nodes[1]); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libpthread"); @@ -980,7 +971,7 @@ START_TEST(test__map_find_obj_for_area) { ck_assert_int_eq(ret, _MAP_OBJ_NEW); - //previous object + //third test: previous object _init__traverse_state(&state, NULL, &obj_nodes[1]); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libc"); @@ -989,7 +980,7 @@ START_TEST(test__map_find_obj_for_area) { ck_assert_int_eq(ret, _MAP_OBJ_PREV); - //next object + //fourth test: next object _init__traverse_state(&state, NULL, &obj_nodes[1]); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "anonmap"); @@ -1022,7 +1013,7 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { uintptr_t third_state[2] = {0x6000, 0xC000}; - //backtrack `/lib/foo`'s last area (index: 8) + //first test: backtrack `/lib/foo`'s last area (index: 8) ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[2]); ck_assert_int_eq(ret, 0); @@ -1039,8 +1030,7 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { NULL, &m_o_n[1], 8, true); - - //backtrack `[heap]`'s two last areas (indeces: 4, 8) + //second test: backtrack `[heap]`'s two last areas (indeces: 4, 8) ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[1]); ck_assert_int_eq(ret, 0); @@ -1058,9 +1048,8 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[0], 8, true); - - //backtrack `/bin/cat`'s two lat areas (indeces: 4, 8) + //third test: backtrack `/bin/cat`'s two lat areas (indeces: 4, 8) ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[0]); ck_assert_int_eq(ret, 0); @@ -1103,10 +1092,9 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { ck_assert_int_eq(ret, 0); - //pretend the `/lib/foo` object was just inserted + //only test: pretend the `/lib/foo` object was just inserted ret = _map_forward_unmapped_obj_last_vm_areas(&m_o_n[2]); ck_assert_int_eq(ret, 0); - //check `[heap]` has only one last area associated with it _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 1, 1, true); @@ -1135,18 +1123,19 @@ START_TEST(test__map_unlink_unmapped_obj) { int ret; uintptr_t heap_state[2] = {0x6000, 0xC000}; - struct obj_check obj_state[3] = { - {"cat", 0x1000, 0x4000}, - {"[heap]", 0x4000, 0x5000}, - {"[stack]", 0xE000, 0xF000} - }; + struct obj_check obj_state[4] = { //start index: 0 + {"0x0", 0x0, 0x0}, + {"cat", 0x1000, 0x4000}, + {"[heap]", 0x4000, 0x5000}, + {"[stack]", 0xE000, 0xF000} + }; - struct obj_check unmapped_obj_state[1] = { - {"foo", 0x8000, 0xB000} - }; + struct obj_check unmapped_obj_state[1] = { //start index: 0 + {"foo", 0x8000, 0xB000} + }; - //unlink `/lib/foo` + //only test: unlink `/lib/foo` ret = _map_unlink_unmapped_obj(&m_o_n[2], &m); ck_assert_int_eq(ret, 0); @@ -1160,10 +1149,10 @@ START_TEST(test__map_unlink_unmapped_obj) { //check state of mapped objects - _assert_vm_map_objs(&m.vm_objs, obj_state, 3); + _assert_vm_map_objs(&m.vm_objs, obj_state, 0, 4); //check state of unmapped objects - _assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 1); + _assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 0, 1); //check removed object has no links to other objects anymore ck_assert(m_o[2].mapped == false); @@ -1185,88 +1174,72 @@ START_TEST(test__map_unlink_unmapped_area) { //remove /lib/foo:1: object state - struct obj_check foo_obj_state[4] = { - {"cat", 0x1000, 0x4000}, - {"[heap]", 0x4000, 0x5000}, - {"foo", 0x9000, 0xB000}, + struct obj_check foo_obj_state[3] = { //start index: 2 + {"[heap]", 0x4000, 0x5000}, + {"foo", 0x9000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; //remove /lib/foo:1: area state - struct area_check foo_area_state[9] = { - {"cat", 0x1000, 0x2000}, - {"cat", 0x2000, 0x3000}, - {"cat", 0x3000, 0x4000}, - {"[heap]", 0x4000, 0x5000}, - {NULL, 0x6000, 0x7000}, - {"foo", 0x9000, 0xA000}, - {"foo", 0xA000, 0xB000}, - {NULL, 0xC000, 0xD000}, - {"[stack]", 0xE000, 0xF000} + struct area_check foo_area_state[3] = { //start index: 4 + {NULL, 0x6000, 0x7000}, + {"foo", 0x9000, 0xA000}, + {"foo", 0xA000, 0xB000} }; - struct area_check foo_unmapped_area_state[1] = { - {"foo", 0x8000, 0x9000} + struct area_check foo_unmapped_area_state[1] = { //start index: 0 + {"foo", 0x8000, 0x9000} }; //remove [heap]: object state - struct obj_check heap_obj_state[3] = { - {"cat", 0x1000, 0x4000}, - {"foo", 0x9000, 0xB000}, - {"[stack]", 0xE000, 0xF000} + struct obj_check heap_obj_state[2] = { //start index: 1 + {"cat", 0x1000, 0x4000}, + {"foo", 0x9000, 0xB000} }; - struct obj_check heap_unmapped_obj_state[1] = { - {"[heap]", 0x0, 0x0} + struct obj_check heap_unmapped_obj_state[1] = { //start index: 0 + {"[heap]", 0x0, 0x0} }; //remove [heap]: area state: - struct area_check heap_area_state[8] = { - {"cat", 0x1000, 0x2000}, - {"cat", 0x2000, 0x3000}, - {"cat", 0x3000, 0x4000}, - {NULL, 0x6000, 0x7000}, - {"foo", 0x9000, 0xA000}, - {"foo", 0xA000, 0xB000}, - {NULL, 0xC000, 0xD000}, - {"[stack]", 0xE000, 0xF000} + struct area_check heap_area_state[2] = { //start index: 2 + {"cat", 0x3000, 0x4000}, + {NULL, 0x6000, 0x7000} }; - struct area_check heap_unmapped_area_state[2] = { - {"foo", 0x8000, 0x9000}, - {"[heap]", 0x4000, 0x5000} + struct area_check heap_unmapped_area_state[2] = { //start index: 0 + {"foo", 0x8000, 0x9000}, + {"[heap]", 0x4000, 0x5000} }; - //remove `/lib/foo`'s first area + //first test: remove first area of `/lib/foo` ret = _map_unlink_unmapped_area(&m_a_n[6], &m); ck_assert_int_eq(ret, 0); - //check state _assert_vm_area(&m_a[5], "/lib/foo", "foo", 0x8000, 0x9000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 4); - _assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0); - _assert_vm_map_areas(&m.vm_areas, foo_area_state, 9); - _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 1); + _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 2, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0, 0); + _assert_vm_map_areas(&m.vm_areas, foo_area_state, 4, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 0, 1); - //remove '[heap]''s only area + //second test: remove only area of '[heap]' ret = _map_unlink_unmapped_area(&m_a_n[3], &m); ck_assert_int_eq(ret, 0); - //check state _assert_vm_area(&m_a[3], "[heap]", "[heap]", 0x4000, 0x5000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 1); - _assert_vm_map_areas(&m.vm_areas, heap_area_state, 8); - _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 2); + _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 1, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 0, 1); + _assert_vm_map_areas(&m.vm_areas, heap_area_state, 2, 2); + _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 0, 2); return; @@ -1297,29 +1270,33 @@ START_TEST(test__map_check_area_eql) { _create_lst_wrapper(&area_node, &area); - //entry same as vm_area + //first test: entry same as vm_area ret = _map_check_area_eql(&entry, &area_node); ck_assert_int_eq(ret, 0); - //entry start address is different + + //second test: entry start address is different entry.vm_start = 0x500; ret = _map_check_area_eql(&entry, &area_node); ck_assert_int_eq(ret, -1); entry.vm_start = area.start_addr; - //entry end address is different + + //third test: entry end address is different entry.vm_end = 0x2500; ret = _map_check_area_eql(&entry, &area_node); ck_assert_int_eq(ret, -1); entry.vm_end = area.end_addr; - //entry permissions are different + + //fourth test: entry permissions are different entry.prot = MC_ACCESS_READ | MC_ACCESS_WRITE; ret = _map_check_area_eql(&entry, &area_node); ck_assert_int_eq(ret, -1); entry.prot = area.access; - //both entry and area don't have a path + + //fifth test: both entry and area don't have a path entry.file_path[0] = '\0'; area.pathname = NULL; ret = _map_check_area_eql(&entry, &area_node); @@ -1327,13 +1304,15 @@ START_TEST(test__map_check_area_eql) { entry.file_path[0] = '/'; area.pathname = obj.pathname; - //entry has a path, area does not + + //sixth test: entry has a path, area does not area.pathname = NULL; ret = _map_check_area_eql(&entry, &area_node); ck_assert_int_eq(ret, 0); area.pathname = obj.pathname; - //entry does not have a path, area does + + //seventh test: entry does not have a path, area does entry.file_path[0] = '\0'; ret = _map_check_area_eql(&entry, &area_node); ck_assert_int_eq(ret, 0); @@ -1349,95 +1328,135 @@ START_TEST(test__map_check_area_eql) { +//_map_state_inc_area() [stub map fixture] +START_TEST(test__map_state_inc_area) { + + _traverse_state state; + + + //first test: keep + state.next_area_node = &m_a_n[0]; + _map_state_inc_area(&state, _STATE_AREA_NODE_KEEP, NULL, &m); + ck_assert_int_eq(MC_GET_NODE_AREA(state.next_area_node)->id, 0); + + //second test: advance - success + state.next_area_node = &m_a_n[0]; + _map_state_inc_area(&state, _STATE_AREA_NODE_ADVANCE, NULL, &m); + ck_assert_int_eq(MC_GET_NODE_AREA(state.next_area_node)->id, 1); + + //third test: advance - refuse (reached the end) + state.next_area_node = &m_a_n[9]; + _map_state_inc_area(&state, _STATE_AREA_NODE_ADVANCE, NULL, &m); + ck_assert_int_eq(MC_GET_NODE_AREA(state.next_area_node)->id, 9); + + //fourth test: reassign + state.next_area_node = &m_a_n[0]; + _map_state_inc_area(&state, _STATE_AREA_NODE_REASSIGN, &m_a_n[2], &m); + ck_assert_int_eq(MC_GET_NODE_AREA(state.next_area_node)->id, 2); + + return; + +} END_TEST + + + +//_map_state_inc_obj() [stub map fixture] +START_TEST(test__map_state_inc_obj) { + + _traverse_state state; + + + //first test: advance from pseudo object + state.prev_obj_node = m.vm_objs.head; + _map_state_inc_obj(&state, &m); + ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node), 0); + + //second test: advance from regular object + state.prev_obj_node = &m_a_n[0]; + _map_state_inc_obj(&state, &m); + ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node), 1); + + return; + +} END_TEST + + + //_map_resync_area() [stub map fixture] START_TEST(test__map_resync_area) { //remove [heap]: object state - struct obj_check heap_obj_state[3] = { - {"cat", 0x1000, 0x4000}, - {"foo", 0x9000, 0xB000}, - {"[stack]", 0xE000, 0xF000} + struct obj_check heap_obj_state[2] = { //start index: 1 + {"cat", 0x1000, 0x4000}, + {"foo", 0x9000, 0xB000} }; - struct obj_check heap_unmapped_obj_state[1] = { - {"[heap]", 0x4000, 0x5000} + struct obj_check heap_unmapped_obj_state[1] = { //start index: 0 + {"[heap]", 0x4000, 0x5000} }; //remove [heap]: area state - struct area_check heap_area_state[9] = { - {"cat", 0x1000, 0x2000}, - {"cat", 0x2000, 0x3000}, - {"cat", 0x3000, 0x4000}, - {NULL, 0x6000, 0x7000}, - {"foo", 0x8000, 0x9000}, - {"foo", 0x9000, 0xA000}, - {"foo", 0xA000, 0xB000}, - {NULL, 0xC000, 0xD000}, - {"[stack]", 0xE000, 0xF000} + struct area_check heap_area_state[2] = { //start index: 2 + {"cat", 0x3000, 0x4000}, + {NULL, 0x6000, 0x7000} }; - struct area_check heap_unmapped_area_state[1] = { - {"[heap]", 0x4000, 0x5000} + struct area_check heap_unmapped_area_state[1] = { //start index 0 + {"[heap]", 0x4000, 0x5000} }; //remove /lib/foo:1,2: object state - struct obj_check foo_obj_state[3] = { - {"cat", 0x1000, 0x4000}, - {"foo", 0xA000, 0xB000}, + struct obj_check foo_obj_state[3] = { //start index: 1 + {"cat", 0x1000, 0x4000}, + {"foo", 0xA000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; - struct obj_check foo_unmapped_obj_state[1] = { - {"[heap]", 0x4000, 0x5000} + struct obj_check foo_unmapped_obj_state[1] = { //start index 0 + {"[heap]", 0x4000, 0x5000} }; //remove /lib/foo:1,2: area state - struct area_check foo_area_state[7] = { - {"cat", 0x1000, 0x2000}, - {"cat", 0x2000, 0x3000}, - {"cat", 0x3000, 0x4000}, - {NULL, 0x6000, 0x7000}, - {"foo", 0xA000, 0xB000}, - {NULL, 0xC000, 0xD000}, - {"[stack]", 0xE000, 0xF000} + struct area_check foo_area_state[3] = { //start index 3 + {NULL, 0x6000, 0x7000}, + {"foo", 0xA000, 0xB000}, + {NULL, 0xC000, 0xD000} }; - struct area_check foo_unmapped_area_state[3] = { - {"[heap]", 0x4000, 0x5000}, - {"foo", 0x8000, 0x9000}, - {"foo", 0x9000, 0xA000}, + struct area_check foo_unmapped_area_state[3] = { //start index 0 + {"[heap]", 0x4000, 0x5000}, + {"foo", 0x8000, 0x9000}, + {"foo", 0x9000, 0xA000}, }; //remove /bin/cat:1,2,3: object state - struct obj_check cat_obj_state[2] = { + struct obj_check cat_obj_state[2] = { //start index: 1 {"foo", 0xA000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; - struct obj_check cat_unmapped_obj_state[2] = { + struct obj_check cat_unmapped_obj_state[2] = { //start index: 0 {"cat", 0x0, 0x0}, {"[heap]", 0x0, 0x0} }; //remove /bin/cat:1,2,3: area state - struct area_check cat_area_state[4] = { + struct area_check cat_area_state[2] = { //start index: 0 {NULL, 0x6000, 0x7000}, - {"foo", 0xA000, 0xB000}, - {NULL, 0xC000, 0xD000}, - {"[stack]", 0xE000, 0xF000} + {"foo", 0xA000, 0xB000} }; - struct area_check cat_unmapped_area_state[6] = { + struct area_check cat_unmapped_area_state[6] = { //start index: 0 {"[heap]", 0x4000, 0x5000}, {"foo", 0x8000, 0x9000}, {"foo", 0x9000, 0xA000}, {"cat", 0x1000, 0x2000}, {"cat", 0x2000, 0x3000}, - {"cat", 0x3000, 0x4000}, + {"cat", 0x3000, 0x4000} }; int ret; @@ -1449,8 +1468,7 @@ START_TEST(test__map_resync_area) { //correct by removing `[heap]` _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); - state.next_area_node = &m_a_n[6]; - state.prev_obj_node = &m_o_n[0]; + _init__traverse_state(&state, &m_a_n[6], &m_o_n[0]); ret = _map_resync_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); @@ -1462,18 +1480,17 @@ START_TEST(test__map_resync_area) { _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/bin/cat", "cat", 0x1000, 0x3000, 3, 1, 0, true); - _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 1); - _assert_vm_map_areas(&m.vm_areas, heap_area_state, 9); - _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 1); + _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 1, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 0, 1); + _assert_vm_map_areas(&m.vm_areas, heap_area_state, 3, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 0, 3); //correct by removing first 2 areas of `/lib/foo` _init_vm_entry(&entry, 0xA000, 0xB000, 0x0, MC_ACCESS_READ | MC_ACCESS_EXEC, "/lib/foo"); - state.next_area_node = &m_a_n[5]; - state.prev_obj_node = &m_o_n[2]; + _init__traverse_state(&state, &m_a_n[5], &m_o_n[2]); ret = _map_resync_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); @@ -1486,19 +1503,17 @@ START_TEST(test__map_resync_area) { _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", 0xA000, 0xB000, 1, 1, 2, true); - _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, foo_unmapped_obj_state, 1); - _assert_vm_map_areas(&m.vm_areas, foo_area_state, 7); - _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 3); + _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 1, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, foo_unmapped_obj_state, 0, 1); + _assert_vm_map_areas(&m.vm_areas, foo_area_state, 3, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 0, 3); - //correct by removing `/bin/cat`, check last areas are correctly - //transferred to the pseudo object + //correct by removing entirety of `/bin/cat` _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, MC_ACCESS_READ | MC_ACCESS_EXEC, NULL); - state.next_area_node = &m_a_n[0]; - state.prev_obj_node = &m_o_n[0]; + _init__traverse_state(&state, &m_a_n[0], &m_o_n[0]); ret = _map_resync_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); @@ -1511,10 +1526,10 @@ START_TEST(test__map_resync_area) { _assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); - _assert_vm_map_objs(&m.vm_objs, cat_obj_state, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, cat_unmapped_obj_state, 2); - _assert_vm_map_areas(&m.vm_areas, cat_area_state, 4); - _assert_vm_map_areas(&m.vm_areas_unmapped, cat_unmapped_area_state, 6); + _assert_vm_map_objs(&m.vm_objs, cat_obj_state, 1, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, cat_unmapped_obj_state, 0, 2); + _assert_vm_map_areas(&m.vm_areas, cat_area_state, 0, 2); + _assert_vm_map_areas(&m.vm_areas_unmapped, cat_unmapped_area_state, 0, 6); return; @@ -1522,5 +1537,136 @@ START_TEST(test__map_resync_area) { +//_map_add_obj() [stub map fixture] +START_TEST(test__map_add_obj) { + //test data + struct obj_check first_objs[3] = { //start index: 3 + {"foo", 0x8000, 0xB000}, + {"bar", 0xD000, 0xE000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct obj_check second_objs[3] = { //start index: 2 + {"[heap]", 0x4000, 0x5000}, + {"dog", 0x7000, 0x8000}, + {"foo", 0x8000, 0xB000} + }; + + + //test vars + cm_lst_node * ret_node; + + struct vm_entry entry; + _traverse_state state; + + + //first test: no forwarding last memory areas + _init_vm_entry(&entry, 0xD000, 0xE000, 0x0, MC_ACCESS_READ, "/lib/bar"); + _init__traverse_state(&state, NULL, &m_o_n[2]); + + ret_node = _map_add_obj(&entry, &state, &m); + ck_assert_ptr_nonnull(ret_node); + + _assert_vm_map_objs(&m.vm_objs, first_objs, 3, 3); + _assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 0); + + + //second test: forwarding last memory areas + _init_vm_entry(&entry, 0x5000, 0x6000, 0x0, MC_ACCESS_READ, "/bin/dog"); + _init__traverse_state(&state, NULL, &m_o_n[1]); + + ret_node = _map_add_obj(&entry, &state, &m); + ck_assert_ptr_nonnull(ret_node); + + _assert_vm_map_objs(&m.vm_objs, second_objs, 3, 2); + _assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 1); + + return; + +} END_TEST + + + +//_map_add_area() [stub map fixture] +START_TEST(test__map_add_area) { + + //test data + struct area_check first_areas[3] = { //start_index: 7 + {"foo", 0xA000, 0xB000}, + {"foo", 0xB000, 0xC000}, + {NULL, 0xC000, 0xD000} + }; + + struct obj_check first_objs[2] = { //start index: 3 + {"foo", 0x8000, 0xC000}, + {"[stack]", 0xE000, 0xF000} + }; + + struct area_check second_areas[3] = { //start_index: 6 + {NULL, 0x6000, 0x7000}, + {NULL, 0x7000, 0x8000}, + {"foo", 0x8000, 0x9000} + }; + + struct obj_check second_objs[2] = { //start index: 2 + {"[heap]", 0x4000, 0x5000}, + {"foo", 0x8000, 0xC000}, + }; + + struct area_check third_areas[3] = { //start_index: 3 + {"[heap]", 0x4000, 0x5000}, + {"bar", 0x5000, 0x6000}, + {NULL, 0x6000, 0x7000} + }; + + struct obj_check third_objs[3] = { //start index: 2 + {"[heap]", 0x4000, 0x5000}, + {"bar", 0x5000, 0x6000}, + {"foo", 0x8000, 0xC000} + }; + + + //test vars + int ret; + + struct vm_entry entry; + _traverse_state state; + + + //first test: add additional area to the end of `/lib/foo` + _init_vm_entry(&entry, 0xB000, 0xC000, 0x0, MC_ACCESS_READ, "/lib/foo"); + _init__traverse_state(&state, &m_a_n[8], &m_o_n[2]); + + ret = _map_add_area(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, first_areas, 7, 3); + _assert_vm_map_objs(&m.vm_objs, first_objs, 3, 2); + + + //second test: add area without a backing object before `/lib/foo` + _init_vm_entry(&entry, 0x7000, 0x8000, 0x0, MC_ACCESS_READ, NULL); + _init__traverse_state(&state, &m_a_n[5], &m_o_n[1]); + + ret = _map_add_area(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, first_areas, 6, 3); + _assert_vm_map_objs(&m.vm_objs, first_objs, 2, 2); + + + //third test: add an area that creates a new object `/lib/bar` + _init_vm_entry(&entry, 0x5000, 0x6000, 0x0, MC_ACCESS_READ, "/lib/bar"); + _init__traverse_state(&state, &m_a_n[4], &m_o_n[1]); + + ret = _map_add_area(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); + _assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); + + return; + +} END_TEST #endif From 7aa955fbd6b82db3e4aed8a0b1ac0db4ede374b9 Mon Sep 17 00:00:00 2001 From: vykt Date: Mon, 30 Dec 2024 05:17:44 +0000 Subject: [PATCH 08/45] finish map unit tests --- src/lib/map.c | 157 +++++++------ src/lib/map.h | 13 +- src/test/check_map.c | 516 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 547 insertions(+), 139 deletions(-) diff --git a/src/lib/map.c b/src/lib/map.c index 00e1fc4..80ad077 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -854,38 +854,39 @@ int _map_add_area(const struct vm_entry * entry, * --- [INTERFACE] --- */ -int map_send_entry(mc_vm_map * vm_map, - _traverse_state * state, const struct vm_entry * entry) { +int map_send_entry(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map) { int ret; - //if at end of old map - if (state->next_area == NULL) { + //if reached the end of the old map + if (state->next_area_node == NULL) { - ret = _map_add_area(vm_map, state, entry, false); + ret = _map_add_area(entry, state, map); if (ret == -1) return -1; - //if not at end of old map + //if not reached the end end of the old map } else { //if entry doesn't match next area (a change in the map) - if (_check_area_eql(entry, state->next_area)) { + if (_map_check_area_eql(entry, state->next_area_node)) { - ret = _resync_area(vm_map, state, entry); + ret = _map_resync_area(entry, state, map); if (ret) return -1; - ret = _map_add_area(vm_map, state, entry, false); + ret = _map_add_area(entry, state, map); if (ret == -1) return -1; // else entry matches next area } else { - _state_inc_area(vm_map, state, NULL, _STATE_AREA_NODE_ADVANCE); + _map_state_inc_area(state, + _STATE_AREA_NODE_ADVANCE, NULL, map); //check if area belongs to the next obj - if (_find_obj_for_area(state, entry) == _MAP_OBJ_NEXT) { - _state_inc_obj(vm_map, state); + if (_map_find_obj_for_area(entry, state) == _MAP_OBJ_NEXT) { + _map_state_inc_obj(state, map); } } //end if match @@ -897,11 +898,10 @@ int map_send_entry(mc_vm_map * vm_map, -void map_init_traverse_state(const mc_vm_map * vm_map, _traverse_state * state) { +void map_init_traverse_state(_traverse_state * state, const mc_vm_map * map) { - //set up next area node - state->next_area = vm_map->vm_areas.head; - state->prev_obj = vm_map->vm_objs.head; + state->next_area_node = map->vm_areas.head; + state->prev_obj_node = map->vm_objs.head; return; } @@ -912,128 +912,127 @@ void map_init_traverse_state(const mc_vm_map * vm_map, _traverse_state * state) * --- [EXTERNAL] --- */ -void mc_new_vm_map(mc_vm_map * vm_map) { +void mc_new_vm_map(mc_vm_map * map) { //pseudo object, will adopt leading parentless vm_areas mc_vm_obj zero_obj; //initialise lists - cm_new_lst(&vm_map->vm_areas, sizeof(mc_vm_area)); - cm_new_lst(&vm_map->vm_objs, sizeof(mc_vm_obj)); + cm_new_lst(&map->vm_areas, sizeof(mc_vm_area)); + cm_new_lst(&map->vm_objs, sizeof(mc_vm_obj)); - cm_new_lst(&vm_map->vm_areas_unmapped, sizeof(cm_lst_node *)); - cm_new_lst(&vm_map->vm_objs_unmapped, sizeof(cm_lst_node *)); + cm_new_lst(&map->vm_areas_unmapped, sizeof(cm_lst_node *)); + cm_new_lst(&map->vm_objs_unmapped, sizeof(cm_lst_node *)); //setup pseudo object at start of map - _map_new_vm_obj(&zero_obj, vm_map, "0x0"); + _map_new_vm_obj(&zero_obj, map, "0x0"); _map_make_zero_obj(&zero_obj); - cm_lst_apd(&vm_map->vm_objs, &zero_obj); + cm_lst_apd(&map->vm_objs, &zero_obj); - //reset next object id back to zero - vm_map->next_id_area = vm_map->next_id_obj = 0; + //set next IDs to 0 + map->next_id_area = map->next_id_obj = 0; return; } -int mc_del_vm_map(mc_vm_map * vm_map) { +int mc_del_vm_map(mc_vm_map * map) { - int ret; + int ret, len_obj; - //unallocate all unmapped nodes - ret = mc_map_clean_unmapped(vm_map); - if (ret) return -1; + cm_lst_node * obj_node; + mc_vm_obj * obj; - cm_lst_node * iter_node; - mc_vm_obj * iter_obj; + //unallocate all unmapped nodes + ret = mc_map_clean_unmapped(map); + if (ret) return -1; - int len_obj = vm_map->vm_objs.len; + //setup iteration + len_obj = map->vm_objs.len; + obj_node = map->vm_objs.head; //manually free all unmapped obj nodes for (int i = 0; i < len_obj; ++i) { - iter_node = cm_lst_get_n(&vm_map->vm_objs_unmapped, i); - if (iter_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } - - iter_obj = MC_GET_NODE_OBJ(iter_node); - if (iter_obj == NULL) { - mc_errno = MC_ERR_UNEXPECTED_NULL; - return -1; - } + //fetch & destroy the object + obj = MC_GET_NODE_OBJ(obj_node); + _map_del_vm_obj(obj); - _del_vm_obj(iter_obj); + //advance iteration + obj_node = obj_node->next; } //end for - - cm_del_lst(&vm_map->vm_areas); - cm_del_lst(&vm_map->vm_objs); + + + //destroy all lists + cm_del_lst(&map->vm_areas); + cm_del_lst(&map->vm_objs); + cm_del_lst(&map->vm_areas_unmapped); + cm_del_lst(&map->vm_objs_unmapped); return 0; } - -/* - * The nodes of 'vm_map->vm_{areas,objs}_unmapped' lists hold pointers - * to other nodes. These nodes must be freed as appropriate. - */ -int mc_map_clean_unmapped(mc_vm_map * vm_map) { +int mc_map_clean_unmapped(mc_vm_map * map) { - int ret; + int ret, len; + + mc_vm_obj * obj; + cm_lst_node * node, * del_node; - cm_lst_node * iter_node; - cm_lst_node * del_node; - int len_area = vm_map->vm_areas_unmapped.len; - int len_obj = vm_map->vm_objs_unmapped.len; + //setup unmapped area iteration + len = map->vm_areas_unmapped.len; + node = map->vm_areas_unmapped.head; //manually free all unmapped area nodes - for (int i = 0; i < len_area; ++i) { + for (int i = 0; i < len; ++i) { - iter_node = cm_lst_get_n(&vm_map->vm_areas_unmapped, i); - if (iter_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } + //delete the unmapped area node + del_node = MC_GET_NODE_PTR(node); + cm_del_lst_node(del_node); - del_node = *((cm_lst_node **) iter_node->data); - free(del_node->data); - free(del_node); + //advance iteration + node = node->next; } //end for + + + //setup unmapped object iteration + len = map->vm_objs_unmapped.len; + node = map->vm_objs_unmapped.head; //manually free all unmapped obj nodes - for (int i = 0; i < len_obj; ++i) { + for (int i = 0; i < len; ++i) { - iter_node = cm_lst_get_n(&vm_map->vm_objs_unmapped, i); - if (iter_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; - return -1; - } + //delete the unmapped object and its node + del_node = MC_GET_NODE_PTR(node); + obj = MC_GET_NODE_OBJ(del_node); + + _map_del_vm_obj(obj); + cm_del_lst_node(del_node); - del_node = *((cm_lst_node **) iter_node->data); - free(del_node->data); - free(del_node); + //advance iteration + node = node->next; } //end for + //empty out both unmapped lists - ret = cm_lst_emp(&vm_map->vm_areas_unmapped); + ret = cm_lst_emp(&map->vm_areas_unmapped); if (ret) { mc_errno = MC_ERR_LIBCMORE; return -1; } - ret = cm_lst_emp(&vm_map->vm_objs_unmapped); + ret = cm_lst_emp(&map->vm_objs_unmapped); if (ret) { mc_errno = MC_ERR_LIBCMORE; return -1; diff --git a/src/lib/map.h b/src/lib/map.h index 1ed1527..ae1df66 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -84,16 +84,15 @@ int _map_add_area(const struct vm_entry * entry, //interface -int map_send_entry(mc_vm_map * vm_map, - _traverse_state * state, const struct vm_entry * entry); -void map_init_traverse_state(const mc_vm_map * vm_map, - _traverse_state * state); +int map_send_entry(const struct vm_entry * entry, + _traverse_state * state, mc_vm_map * map); +void map_init_traverse_state(_traverse_state * state, const mc_vm_map * map); //external -void mc_new_vm_map(mc_vm_map * vm_map); -int mc_del_vm_map(mc_vm_map * vm_map); -int mc_map_clean_unmapped(mc_vm_map * vm_map); +void mc_new_vm_map(mc_vm_map * map); +int mc_del_vm_map(mc_vm_map * map); +int mc_map_clean_unmapped(mc_vm_map * map); #endif diff --git a/src/test/check_map.c b/src/test/check_map.c index 7857221..977d6c8 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -105,6 +105,19 @@ static cm_lst_node o_a_l_n[LAST_AREAS_NUM]; * --- [HELPERS] --- */ +//initialise a _traverse_state +static void _init__traverse_state(_traverse_state * state, + cm_lst_node * next_area_node, + cm_lst_node * prev_obj_node) { + + state->next_area_node = next_area_node; + state->prev_obj_node = prev_obj_node; + + return; +} + + + //initialise a vm_entry stub static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, unsigned long vm_end, unsigned long file_off, @@ -129,19 +142,6 @@ static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, -//initialise a _traverse_state -static void _init__traverse_state(_traverse_state * state, - cm_lst_node * next_area_node, - cm_lst_node * prev_obj_node) { - - state->next_area_node = next_area_node; - state->prev_obj_node = prev_obj_node; - - return; -} - - - //initialise a cm_lst_node stub wrapper static void _create_lst_wrapper(cm_lst_node * node, void * data) { @@ -360,7 +360,7 @@ static void _setup_empty_vm_map() { #ifdef DEBUG #define STUB_MAP_LEN 10 //stub map fixture -static void _stub_vm_map() { +static void _setup_stub_vm_map() { /* * Stub map: @@ -582,6 +582,10 @@ static void _teardown_vm_obj() { //mc_new_vm_map() & mc_del_vm_map() [no fixture] START_TEST(test_mc_new_del_vm_map) { + /* + * Using ASAN to check for memory leaks in the destructor. + */ + mc_vm_obj * zero_obj; @@ -1117,7 +1121,7 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { -//_map_unlink_unmapped_obj() +//_map_unlink_unmapped_obj() [stub map fixture] START_TEST(test__map_unlink_unmapped_obj) { int ret; @@ -1174,41 +1178,41 @@ START_TEST(test__map_unlink_unmapped_area) { //remove /lib/foo:1: object state - struct obj_check foo_obj_state[3] = { //start index: 2 + struct obj_check first_objs[3] = { //start index: 2 {"[heap]", 0x4000, 0x5000}, {"foo", 0x9000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; //remove /lib/foo:1: area state - struct area_check foo_area_state[3] = { //start index: 4 + struct area_check first_areas[3] = { //start index: 4 {NULL, 0x6000, 0x7000}, {"foo", 0x9000, 0xA000}, {"foo", 0xA000, 0xB000} }; - struct area_check foo_unmapped_area_state[1] = { //start index: 0 + struct area_check first_areas_unmapped[1] = { //start index: 0 {"foo", 0x8000, 0x9000} }; //remove [heap]: object state - struct obj_check heap_obj_state[2] = { //start index: 1 + struct obj_check second_objs[2] = { //start index: 1 {"cat", 0x1000, 0x4000}, {"foo", 0x9000, 0xB000} }; - struct obj_check heap_unmapped_obj_state[1] = { //start index: 0 + struct obj_check second_objs_unmapped[1] = { //start index: 0 {"[heap]", 0x0, 0x0} }; //remove [heap]: area state: - struct area_check heap_area_state[2] = { //start index: 2 + struct area_check second_areas[2] = { //start index: 2 {"cat", 0x3000, 0x4000}, {NULL, 0x6000, 0x7000} }; - struct area_check heap_unmapped_area_state[2] = { //start index: 0 + struct area_check second_areas_unmapped[2] = { //start index: 0 {"foo", 0x8000, 0x9000}, {"[heap]", 0x4000, 0x5000} }; @@ -1222,10 +1226,10 @@ START_TEST(test__map_unlink_unmapped_area) { 0x8000, 0x9000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 2, 3); + _assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); _assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0, 0); - _assert_vm_map_areas(&m.vm_areas, foo_area_state, 4, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 0, 1); + _assert_vm_map_areas(&m.vm_areas, first_areas, 4, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 1); //second test: remove only area of '[heap]' @@ -1236,10 +1240,10 @@ START_TEST(test__map_unlink_unmapped_area) { 0x4000, 0x5000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 1, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 0, 1); - _assert_vm_map_areas(&m.vm_areas, heap_area_state, 2, 2); - _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 0, 2); + _assert_vm_map_objs(&m.vm_objs, second_objs, 1, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); + _assert_vm_map_areas(&m.vm_areas, second_areas, 2, 2); + _assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 2); return; @@ -1386,46 +1390,46 @@ START_TEST(test__map_state_inc_obj) { START_TEST(test__map_resync_area) { //remove [heap]: object state - struct obj_check heap_obj_state[2] = { //start index: 1 + struct obj_check first_objs[2] = { //start index: 1 {"cat", 0x1000, 0x4000}, {"foo", 0x9000, 0xB000} }; - struct obj_check heap_unmapped_obj_state[1] = { //start index: 0 + struct obj_check first_objs_unmapped[1] = { //start index: 0 {"[heap]", 0x4000, 0x5000} }; //remove [heap]: area state - struct area_check heap_area_state[2] = { //start index: 2 + struct area_check first_areas[2] = { //start index: 2 {"cat", 0x3000, 0x4000}, {NULL, 0x6000, 0x7000} }; - struct area_check heap_unmapped_area_state[1] = { //start index 0 + struct area_check first_areas_unmapped[1] = { //start index 0 {"[heap]", 0x4000, 0x5000} }; //remove /lib/foo:1,2: object state - struct obj_check foo_obj_state[3] = { //start index: 1 + struct obj_check second_objs[3] = { //start index: 1 {"cat", 0x1000, 0x4000}, {"foo", 0xA000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; - struct obj_check foo_unmapped_obj_state[1] = { //start index 0 + struct obj_check second_objs_unmapped[1] = { //start index 0 {"[heap]", 0x4000, 0x5000} }; //remove /lib/foo:1,2: area state - struct area_check foo_area_state[3] = { //start index 3 + struct area_check second_areas[3] = { //start index 3 {NULL, 0x6000, 0x7000}, {"foo", 0xA000, 0xB000}, {NULL, 0xC000, 0xD000} }; - struct area_check foo_unmapped_area_state[3] = { //start index 0 + struct area_check second_areas_unmapped[3] = { //start index 0 {"[heap]", 0x4000, 0x5000}, {"foo", 0x8000, 0x9000}, {"foo", 0x9000, 0xA000}, @@ -1434,23 +1438,23 @@ START_TEST(test__map_resync_area) { //remove /bin/cat:1,2,3: object state - struct obj_check cat_obj_state[2] = { //start index: 1 + struct obj_check third_objs[2] = { //start index: 1 {"foo", 0xA000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; - struct obj_check cat_unmapped_obj_state[2] = { //start index: 0 + struct obj_check third_objs_unmapped[2] = { //start index: 0 {"cat", 0x0, 0x0}, {"[heap]", 0x0, 0x0} }; //remove /bin/cat:1,2,3: area state - struct area_check cat_area_state[2] = { //start index: 0 + struct area_check third_areas[2] = { //start index: 0 {NULL, 0x6000, 0x7000}, {"foo", 0xA000, 0xB000} }; - struct area_check cat_unmapped_area_state[6] = { //start index: 0 + struct area_check third_areas_unmapped[6] = { //start index: 0 {"[heap]", 0x4000, 0x5000}, {"foo", 0x8000, 0x9000}, {"foo", 0x9000, 0xA000}, @@ -1480,10 +1484,10 @@ START_TEST(test__map_resync_area) { _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/bin/cat", "cat", 0x1000, 0x3000, 3, 1, 0, true); - _assert_vm_map_objs(&m.vm_objs, heap_obj_state, 1, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, heap_unmapped_obj_state, 0, 1); - _assert_vm_map_areas(&m.vm_areas, heap_area_state, 3, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, heap_unmapped_area_state, 0, 3); + _assert_vm_map_objs(&m.vm_objs, first_objs, 1, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, first_objs_unmapped, 0, 1); + _assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 3); @@ -1503,10 +1507,10 @@ START_TEST(test__map_resync_area) { _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", 0xA000, 0xB000, 1, 1, 2, true); - _assert_vm_map_objs(&m.vm_objs, foo_obj_state, 1, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, foo_unmapped_obj_state, 0, 1); - _assert_vm_map_areas(&m.vm_areas, foo_area_state, 3, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, foo_unmapped_area_state, 0, 3); + _assert_vm_map_objs(&m.vm_objs, second_objs, 1, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); + _assert_vm_map_areas(&m.vm_areas, second_areas, 3, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 3); @@ -1526,10 +1530,10 @@ START_TEST(test__map_resync_area) { _assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); - _assert_vm_map_objs(&m.vm_objs, cat_obj_state, 1, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, cat_unmapped_obj_state, 0, 2); - _assert_vm_map_areas(&m.vm_areas, cat_area_state, 0, 2); - _assert_vm_map_areas(&m.vm_areas_unmapped, cat_unmapped_area_state, 0, 6); + _assert_vm_map_objs(&m.vm_objs, third_objs, 1, 2); + _assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 2); + _assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); + _assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 6); return; @@ -1669,4 +1673,410 @@ START_TEST(test__map_add_area) { return; } END_TEST + + + +//map_send_entry() [stub map fixture] +START_TEST(test_map_send_entry) { + + //test data + struct area_check first_areas[2] = { //start_index: 9 + {"[stack]", 0x0E000, 0x0F000}, + {"dog", 0x0F000, 0x10000}, + }; + + struct obj_check first_objs[2] = { //start index: 4 + {"[stack]", 0x0E000, 0x0F000}, + {"dog", 0x0F000, 0x10000} + }; + + + struct area_check second_areas[3] = { //start_index: 9 + {"[stack]", 0x0E000, 0x0F000}, + {"dog", 0x0F000, 0xA0000}, + {"dog", 0xA0000, 0xA1000} + }; + + struct obj_check second_objs[2] = { //start index: 4 + {"[stack]", 0x0E000, 0x0F000}, + {"dog", 0x0F000, 0xA1000} + }; + + + struct area_check third_areas[2] = { //start_index: 0 + {"[heap]", 0x04000, 0x05000}, + {NULL, 0x06000, 0x07000} + }; + + struct obj_check third_objs[3] = { //start index: 0 + {"0x0", 0x0, 0x0}, + {"[heap]", 0x04000, 0x05000}, + {"foo", 0x08000, 0x0B000} + }; + + struct area_check third_areas_unmapped[3] = { //start_index: 0 + {"cat", 0x01000, 0x02000}, + {"cat", 0x02000, 0x03000}, + {"cat", 0x03000, 0x04000} + }; + + struct obj_check third_objs_unmapped[1] = { //start index: 0 + {"cat", 0x0, 0x0}, + }; + + + struct area_check fourth_areas[3] = { //start_index: 0 + {"[heap]", 0x04000, 0x05000}, + {NULL, 0x06000, 0x07000}, + {"foo", 0x08000, 0x09000}, + }; + + struct obj_check fourth_objs[3] = { //start index: 0 + {"0x0", 0x0, 0x0}, + {"[heap]", 0x04000, 0x05000}, + {"foo", 0x08000, 0x0B000} + }; + + struct area_check fourth_areas_unmapped[3] = { //start_index: 0 + {"cat", 0x01000, 0x02000}, + {"cat", 0x02000, 0x03000}, + {"cat", 0x03000, 0x04000} + }; + + struct obj_check fourth_objs_unmapped[1] = { //start index: 0 + {"cat", 0x0, 0x0}, + }; + + int ret; + + struct vm_entry entry; + _traverse_state state; + + + //first test: send a "/bin/dog" entry to the end of the map + _init_vm_entry(&entry, 0x0F000, 0x10000, 0x0, MC_ACCESS_READ, "/bin/dog"); + _init__traverse_state(&state, m.vm_areas.head->prev, m.vm_objs.head->prev); + + ret = map_send_entry(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, first_areas, 9, 2); + _assert_vm_map_objs(&m.vm_objs, first_objs, 4, 2); + + + //second test: send another "/bin/dog" area to the end of the map + _init_vm_entry(&entry, 0x10000, 0x11000, 0x0, MC_ACCESS_READ, "/bin/dog"); + _init__traverse_state(&state, m.vm_areas.head->prev, m.vm_objs.head->prev); + + ret = map_send_entry(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, second_areas, 9, 3); + _assert_vm_map_objs(&m.vm_objs, second_objs, 4, 2); + + + //third test: send a "[heap]" entry to the start of the map + _init_vm_entry(&entry, 0x04000, 0x05000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, "[heap]"); + _init__traverse_state(&state, m.vm_areas.head, m.vm_objs.head); + + ret = map_send_entry(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); + _assert_vm_map_objs(&m.vm_objs, third_objs, 0, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 1); + + + //fourth test: send a correct entry after the "[heap]" entry + _init_vm_entry(&entry, 0x06000, 0x07000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); + _init__traverse_state(&state, m.vm_areas.head->next, m.vm_objs.head->next); + + ret = map_send_entry(&entry, &state, &m); + ck_assert_int_eq(ret, 0); + + _assert_vm_map_areas(&m.vm_areas, fourth_areas, 0, 3); + _assert_vm_map_objs(&m.vm_objs, fourth_objs, 0, 3); + _assert_vm_map_areas(&m.vm_areas_unmapped, fourth_areas_unmapped, 0, 3); + _assert_vm_map_objs(&m.vm_objs_unmapped, fourth_objs_unmapped, 0, 1); + + return; + +} END_TEST + + + +//map_init_traverse_state() [empty map fixture] +START_TEST(test_map_init_traverse_state) { + + struct vm_entry entry; + _traverse_state state; + + + //setup an empty map + mc_new_vm_map(&m); + + //first test: empty map + map_init_traverse_state(&state, &m); + ck_assert_ptr_null(state.next_area_node); + ck_assert_ptr_eq(state.prev_obj_node, m.vm_objs.head); + + + //add an area + _init_vm_entry(&entry, 0x1000, 0x2000, 0x0, MC_ACCESS_READ, "/bin/cat"); + _map_add_area(&entry, &state, &m); + + + //second test: existing map + map_init_traverse_state(&state, &m); + ck_assert_ptr_eq(state.next_area_node, m.vm_areas.head); + ck_assert_ptr_eq(state.prev_obj_node, m.vm_objs.head); + + //destroy the empty map + mc_del_vm_map(&m); + + return; + +} END_TEST + + + +//mc_map_clean_unmapped() [stub map fixture] +START_TEST(test_mc_map_clean_unmapped) { + + int ret; + + + //first test: unlink /lib/foo and clean its unmapped areas + ret = _map_unlink_unmapped_obj(&m_o_n[3], &m); + ck_assert_int_eq(ret, 0); + + ret = _map_unlink_unmapped_area(&m_a_n[5], &m); + ck_assert_int_eq(ret, 0); + ret = _map_unlink_unmapped_area(&m_a_n[6], &m); + ck_assert_int_eq(ret, 0); + ret = _map_unlink_unmapped_area(&m_a_n[7], &m); + ck_assert_int_eq(ret, 0); + + ret = mc_map_clean_unmapped(&m); + ck_assert_int_eq(ret, 0); + + _assert_lst_len(&m.vm_areas_unmapped, 0); + _assert_lst_len(&m.vm_objs_unmapped, 0); + + + //second test: try cleaning unmapped areas when none are present + ret = mc_map_clean_unmapped(&m); + ck_assert_int_eq(ret, 0); + + return; + +} END_TEST #endif + + + +/* + * --- [SUITE] --- + */ + + Suite * map_suite() { + + //test cases + TCase * tc_new_del_vm_map; + + #ifdef DEBUG + TCase * tc__new_del_vm_obj; + TCase * tc__make_zero_obj; + TCase * tc__init_vm_area; + TCase * tc__obj_add_area; + TCase * tc__obj_add_last_area; + TCase * tc__obj_rmv_area; + TCase * tc__obj_rmv_last_area; + TCase * tc__is_pathname_in_obj; + TCase * tc__find_obj_for_area; + TCase * tc__backtrack_unmapped_obj_last_vm_areas; + TCase * tc__forward_unmapped_obj_last_vm_areas; + TCase * tc__unlink_unmapped_obj; + TCase * tc__unlink_unmapped_area; + TCase * tc__check_area_eql; + TCase * tc__state_inc_area; + TCase * tc__state_inc_obj; + TCase * tc__resync_area; + TCase * tc__add_obj; + TCase * tc__add_area; + TCase * tc_send_entry; + TCase * tc_init_traverse_state; + TCase * tc_clean_unmapped; + #endif + + Suite * s = suite_create("map"); + + + //tc_new_del_vm_map() + tc_new_del_vm_map = tcase_create("new_del_vm_map"); + tcase_add_test(tc_new_del_vm_map, test_mc_new_del_vm_map); + + //tc__make_zero_obj() + tc__new_del_vm_obj = tcase_create("_new_del_vm_obj"); + tcase_add_checked_fixture(tc__new_del_vm_obj, + _setup_empty_vm_map, _teardown_vm_map); + tcase_add_test(tc__new_del_vm_obj, test__map_new_del_vm_obj); + + //tc__init_vm_area() + tc__init_vm_area = tcase_create("_init_vm_area"); + tcase_add_checked_fixture(tc__init_vm_area, + _setup_empty_vm_map, _teardown_vm_map); + tcase_add_test(tc__init_vm_area, test__map_init_vm_area); + + //tc__obj_add_area() + tc__obj_add_area = tcase_create("_obj_add_area"); + tcase_add_checked_fixture(tc__obj_add_area, + _setup_empty_vm_obj, _teardown_vm_obj); + tcase_add_test(tc__obj_add_area, test__map_obj_add_area); + + //tc__obj_add_last_area() + tc__obj_add_last_area = tcase_create("_obj_add_last_area"); + tcase_add_checked_fixture(tc__obj_add_last_area, + _setup_empty_vm_obj, _teardown_vm_obj); + tcase_add_test(tc__obj_add_last_area, test__map_obj_add_last_area); + + //tc__obj_rmv_area() + tc__obj_rmv_area = tcase_create("_obj_rmv_area"); + tcase_add_checked_fixture(tc__obj_rmv_area, + _setup_stub_vm_obj, _teardown_vm_obj); + tcase_add_test(tc__obj_rmv_area, test__map_obj_rmv_area); + + //tc__obj_rmv_last_area() + tc__obj_rmv_last_area = tcase_create("_obj_rmv_last_area"); + tcase_add_checked_fixture(tc__obj_rmv_last_area, + _setup_stub_vm_obj, _teardown_vm_obj); + tcase_add_test(tc__obj_rmv_last_area, test__map_obj_rmv_area); + + //tc__is_pathname_in_obj() + tc__is_pathname_in_obj = tcase_create("_is_pathname_in_obj"); + tcase_add_checked_fixture(tc__is_pathname_in_obj, + _setup_empty_vm_obj, _teardown_vm_obj); + tcase_add_test(tc__is_pathname_in_obj, test__map_is_pathname_in_obj); + + //tc__find_obj_for_area() + tc__find_obj_for_area = tcase_create("_find_obj_for_area"); + tcase_add_checked_fixture(tc__find_obj_for_area, + _setup_empty_vm_map, _teardown_vm_map); + tcase_add_test(tc__find_obj_for_area, test__map_find_obj_for_area); + + //tc__backtrack_unmapped_obj_last_vm_areas() + tc__backtrack_unmapped_obj_last_vm_areas + = tcase_create("_backtrack_unmapped_obj_last_vm_areas"); + tcase_add_checked_fixture(tc__backtrack_unmapped_obj_last_vm_areas, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__backtrack_unmapped_obj_last_vm_areas, + test__map_backtrack_unmapped_obj_last_vm_areas); + + //tc__forward_unmapped_obj_last_vm_areas() + tc__forward_unmapped_obj_last_vm_areas + = tcase_create("_forward_unmapped_obj_last_vm_areas"); + tcase_add_checked_fixture(tc__forward_unmapped_obj_last_vm_areas, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__forward_unmapped_obj_last_vm_areas, + test__map_forward_unmapped_obj_last_vm_areas); + + //tc__unlink_unmapped_obj() + tc__unlink_unmapped_obj = tcase_create("_unlink_unmapped_obj"); + tcase_add_checked_fixture(tc__unlink_unmapped_obj, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__unlink_unmapped_obj, test__map_unlink_unmapped_obj); + + //tc__unlink_unmapped_area() + tc__unlink_unmapped_area = tcase_create("_unlink_unmapped_area"); + tcase_add_checked_fixture(tc__unlink_unmapped_area, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__unlink_unmapped_area, test__map_unlink_unmapped_area); + + //tc__check_area_eql() + tc__check_area_eql = tcase_create("_check_area_eql"); + tcase_add_checked_fixture(tc__check_area_eql, + _setup_empty_vm_map, _teardown_vm_map); + tcase_add_test(tc__check_area_eql, test__map_check_area_eql); + + //tc__state_inc_area() + tc__state_inc_area = tcase_create("_state_inc_area"); + tcase_add_checked_fixture(tc__state_inc_area, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__state_inc_area, test__map_state_inc_area); + + //tc__state_inc_obj() + tc__state_inc_obj = tcase_create("_state_inc_obj"); + tcase_add_checked_fixture(tc__state_inc_obj, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__state_inc_obj, test__map_state_inc_obj); + + //tc__resync_area() + tc__resync_area = tcase_create("_resync_area"); + tcase_add_checked_fixture(tc__resync_area, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__resync_area, test__map_resync_area); + + //tc__add_obj() + tc__add_obj = tcase_create("_add_obj"); + tcase_add_checked_fixture(tc__add_obj, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__add_obj, test__map_add_obj); + + //tc__add_area() + tc__add_area = tcase_create("_add_area"); + tcase_add_checked_fixture(tc__add_area, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc__add_area, test__map_add_area); + + //tc_send_entry() + tc_send_entry = tcase_create("send_entry"); + tcase_add_checked_fixture(tc_send_entry, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc_send_entry, test_map_send_entry); + + //tc_init_traverse_state() + tc_init_traverse_state = tcase_create("init_traverse_state"); + tcase_add_checked_fixture(tc_init_traverse_state, + _setup_empty_vm_map, _teardown_vm_map); + tcase_add_test(tc_init_traverse_state, test_map_init_traverse_state); + + //tc_clean_unmapped() + tc_clean_unmapped = tcase_create("clean_unmapped"); + tcase_add_checked_fixture(tc_clean_unmapped, + _setup_stub_vm_map, _teardown_vm_map); + tcase_add_test(tc_clean_unmapped, test_mc_map_clean_unmapped); + + + //add test cases to map test suite + suite_add_tcase(s, tc_new_del_vm_map); + + #ifdef DEBUG + suite_add_tcase(s, tc__new_del_vm_obj); + suite_add_tcase(s, tc__make_zero_obj); + suite_add_tcase(s, tc__init_vm_area); + suite_add_tcase(s, tc__obj_add_area); + suite_add_tcase(s, tc__obj_add_last_area); + suite_add_tcase(s, tc__obj_rmv_area); + suite_add_tcase(s, tc__obj_rmv_last_area); + suite_add_tcase(s, tc__is_pathname_in_obj); + suite_add_tcase(s, tc__find_obj_for_area); + suite_add_tcase(s, tc__backtrack_unmapped_obj_last_vm_areas); + suite_add_tcase(s, tc__forward_unmapped_obj_last_vm_areas); + suite_add_tcase(s, tc__unlink_unmapped_obj); + suite_add_tcase(s, tc__unlink_unmapped_area); + suite_add_tcase(s, tc__check_area_eql); + suite_add_tcase(s, tc__state_inc_area); + suite_add_tcase(s, tc__state_inc_obj); + suite_add_tcase(s, tc__resync_area); + suite_add_tcase(s, tc__add_obj); + suite_add_tcase(s, tc__add_area); + suite_add_tcase(s, tc_send_entry); + suite_add_tcase(s, tc_init_traverse_state); + suite_add_tcase(s, tc_clean_unmapped); + #endif + + return s; + } From 8b4f751e17bd41bd2d8f42cf6b4122b61e76cf51 Mon Sep 17 00:00:00 2001 From: vykt Date: Mon, 30 Dec 2024 05:18:53 +0000 Subject: [PATCH 09/45] fix main header --- src/lib/memcry.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 06db6e6..55a265c 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -164,9 +164,9 @@ extern int mc_write(const mc_session * session, const uintptr_t addr, // --- [map] //all return 0 = success, -1 = fail/error -extern void mc_new_vm_map(mc_vm_map * vm_map); -extern int mc_del_vm_map(mc_vm_map * vm_map); -extern int mc_map_clean_unmapped(mc_vm_map * vm_map); +extern void mc_new_vm_map(mc_vm_map * map); +extern int mc_del_vm_map(mc_vm_map * map); +extern int mc_map_clean_unmapped(mc_vm_map * map); // --- [map util] From b1fbc8d3703c6dec82af1e2d22089faf55d0ad66 Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 1 Feb 2025 19:59:08 +0000 Subject: [PATCH 10/45] add local changes (unstable) --- README.md | 2 +- lain.png => memcry.png | Bin src/lib/iface.c | 26 +- src/lib/iface.h | 14 +- src/lib/krncry_iface.c | 2 + src/lib/memcry.h | 34 +- src/lib/procfs_iface.c | 9 +- src/lib/procfs_iface.h | 8 +- src/lib/util.c | 2 + src/test/Makefile | 13 +- src/test/check_iface.c | 150 +++++++++ src/test/check_map.c | 530 ++++++++++---------------------- src/test/check_procfs_iface.c | 145 +++++++++ src/test/iface_helper.c | 235 ++++++++++++++ src/test/iface_helper.h | 25 ++ src/test/map_helper.c | 255 +++++++++++++++ src/test/map_helper.h | 68 ++++ src/test/target/Makefile | 39 +++ src/test/target/manual_target.c | 92 ++++++ src/test/target/unit_target | Bin 0 -> 18768 bytes src/test/target/unit_target.c | 90 ++++++ src/test/target_helper.c | 292 ++++++++++++++++++ src/test/target_helper.h | 52 ++++ 23 files changed, 1666 insertions(+), 417 deletions(-) rename lain.png => memcry.png (100%) create mode 100644 src/test/check_iface.c create mode 100644 src/test/check_procfs_iface.c create mode 100644 src/test/iface_helper.c create mode 100644 src/test/iface_helper.h create mode 100644 src/test/map_helper.c create mode 100644 src/test/map_helper.h create mode 100644 src/test/target/Makefile create mode 100644 src/test/target/manual_target.c create mode 100755 src/test/target/unit_target create mode 100644 src/test/target/unit_target.c create mode 100644 src/test/target_helper.c create mode 100644 src/test/target_helper.h diff --git a/README.md b/README.md index fdf7efc..d749cd9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Lain

- +

### NOTE: diff --git a/lain.png b/memcry.png similarity index 100% rename from lain.png rename to memcry.png diff --git a/src/lib/iface.c b/src/lib/iface.c index c3a5cc6..71bc011 100644 --- a/src/lib/iface.c +++ b/src/lib/iface.c @@ -50,12 +50,14 @@ void _set_krncry_session(mc_session * session) { * --- [EXTERNAL] --- */ -int mc_open(mc_session * session, const int iface, const pid_t pid) { +int mc_open(mc_session * session, + const enum mc_iface_type iface, const pid_t pid) { + int ret; //if requesting procfs interface - if (iface == MC_IFACE_PROCFS) { + if (iface == PROCFS) { _set_procfs_session(session); } else { _set_krncry_session(session); @@ -93,26 +95,26 @@ int mc_update_map(const mc_session * session, mc_vm_map * vm_map) { -int mc_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz) { +ssize_t mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz) { - int ret; + size_t read_bytes; - ret = session->iface.read(session, addr, buf, buf_sz); - if (ret) return -1; + read_bytes = session->iface.read(session, addr, buf, buf_sz); + if (read_bytes == -1) return -1; return 0; } -int mc_write(const mc_session * session, uintptr_t addr, - const cm_byte * buf, const size_t buf_sz) { +ssize_t mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz) { - int ret; + size_t write_bytes; - ret = session->iface.write(session, addr, buf, buf_sz); - if (ret) return -1; + write_bytes = session->iface.write(session, addr, buf, buf_sz); + if (write_bytes) return -1; return 0; } diff --git a/src/lib/iface.h b/src/lib/iface.h index 1ac1465..e4e5a59 100644 --- a/src/lib/iface.h +++ b/src/lib/iface.h @@ -4,6 +4,9 @@ //standard library #include +//system headers +#include + //local headers #include "memcry.h" @@ -16,12 +19,13 @@ void _set_krncry_session(mc_session * session); //external -int mc_open(mc_session * session, const int iface, const pid_t pid); +int mc_open(mc_session * session, + const enum mc_iface_type iface, const pid_t pid); int mc_close(mc_session * session); int mc_update_map(const mc_session * session, mc_vm_map * vm_map); -int mc_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -int mc_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +ssize_t mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +ssize_t mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); #endif diff --git a/src/lib/krncry_iface.c b/src/lib/krncry_iface.c index f2b246e..fde45a9 100644 --- a/src/lib/krncry_iface.c +++ b/src/lib/krncry_iface.c @@ -6,8 +6,10 @@ //system headers #include #include + #include #include + #include //external libraries diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 55a265c..da3ef31 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -29,13 +29,18 @@ extern "C"{ #define MC_ACCESS_EXEC 0x04 #define MC_ACCESS_SHARED 0x08 -//interface types -#define MC_IFACE_KRNCRY 0 -#define MC_IFACE_PROCFS 1 - //pseudo object id #define MC_ZERO_OBJ_ID -1 +//do not seek when reading/writing + + +//interface types +enum mc_iface_type { + PROCFS = 0, + KRNCRY = 1 +}; + /* @@ -105,13 +110,13 @@ struct _mc_session; typedef struct { - int (*open)(struct _mc_session *, int); + int (*open)(struct _mc_session *, const int); int (*close)(struct _mc_session *); int (*update_map)(const struct _mc_session *, mc_vm_map *); - int (*read)(const struct _mc_session *, const uintptr_t, - cm_byte *, const size_t); - int (*write)(const struct _mc_session *, const uintptr_t, - const cm_byte *, const size_t); + ssize_t (*read)(const struct _mc_session *, + const uintptr_t, cm_byte *, const size_t); + ssize_t (*write)(const struct _mc_session *, + const uintptr_t, const cm_byte *, const size_t); } mc_iface; @@ -154,13 +159,14 @@ extern void mc_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); // [virtual interface] //all return 0 = success, -1 = fail/error -extern int mc_open(mc_session * session, const int iface, const pid_t pid); +extern int mc_open(mc_session * session, + const enum mc_iface_type iface, const pid_t pid); extern int mc_close(mc_session * session); extern int mc_update_map(const mc_session * session, mc_vm_map * vm_map); -extern int mc_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -extern int mc_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +extern ssize_t mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +extern ssize_t mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); // --- [map] //all return 0 = success, -1 = fail/error diff --git a/src/lib/procfs_iface.c b/src/lib/procfs_iface.c index dcf6ff4..f210d0c 100644 --- a/src/lib/procfs_iface.c +++ b/src/lib/procfs_iface.c @@ -7,6 +7,7 @@ //system headers #include + #include //external libraries @@ -97,7 +98,7 @@ void _build_entry(struct vm_entry * entry, const char * line_buf) { } //end for every char - //fill entry (prot already done in switch statement) + //fill entry (`prot` already filled inside switch statement) entry->vm_start = (unsigned long) strtol(start_str, NULL, 16); entry->vm_end = (unsigned long) strtol(end_str, NULL, 16); entry->file_off = (unsigned long) strtol(offset_str, NULL, 16); @@ -173,7 +174,7 @@ int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { } //init traverse state for this map - map_init_traverse_state(vm_map, &state); + map_init_traverse_state(&state, vm_map); //while there are entries left while (fgets(line_buf, LINE_LEN, fs) != NULL) { @@ -181,7 +182,7 @@ int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { memset(&new_entry, 0, sizeof(new_entry)); _build_entry(&new_entry, line_buf); - ret = map_send_entry(vm_map, &state, &new_entry); + ret = map_send_entry(&new_entry, &state, vm_map); if (ret != 0) return -1; } //end while @@ -195,7 +196,7 @@ int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { -int procfs_read(const mc_session * session, const uintptr_t addr, +ssize_t procfs_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { off_t off_ret; diff --git a/src/lib/procfs_iface.h b/src/lib/procfs_iface.h index 364b701..a142158 100644 --- a/src/lib/procfs_iface.h +++ b/src/lib/procfs_iface.h @@ -23,9 +23,9 @@ void _build_entry(struct vm_entry * entry, const char * line_buf); int procfs_open(mc_session * session, const int pid); int procfs_close(mc_session * session); int procfs_update_map(const mc_session * session, mc_vm_map * vm_map); -int procfs_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -int procfs_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +ssize_t procfs_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +ssize_t procfs_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); #endif diff --git a/src/lib/util.c b/src/lib/util.c index 4d60cd8..c14ec2d 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -7,7 +7,9 @@ //system headers #include #include + #include + #include //local headers diff --git a/src/test/Makefile b/src/test/Makefile index 65d18ba..99c18c2 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -10,17 +10,22 @@ # _WARN_OPTS - Compiler warnings. +#[parameters] CFLAGS=${_CFLAGS} -fsanitize=address WARN_OPTS+=${_WARN_OPTS} -Wno-unused-variable -Wno-unused-but-set-variable LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} -lcmore -lcheck -static-libasan + +#[build constants] SOURCES_TEST=main.c OBJECTS_TEST=${SOURCES_TEST:%.c=${BUILD_DIR}/%.o} +TARGET_DIR=${shell pwd}/target -TESTS=test +#[targets] +TESTS=test -tests: ${TESTS} +tests: tgt ${TESTS} > mkdir -p ${BUILD_DIR} > mv ${TESTS} ${BUILD_DIR} @@ -30,5 +35,9 @@ ${TESTS}: ${OBJECTS_TEST} ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ +tgt: +> $(MAKE) -C ${TARGET_DIR} targets CC='${CC}' BUILD_DIR='${BUILD_DIR}' + clean: +> ${MAKE} -C ${TARGET_DIR} clean BUILD_DIR='${BUILD_DIR}' > -rm -v ${BUILD_DIR}/{*.o,${TESTS}} diff --git a/src/test/check_iface.c b/src/test/check_iface.c new file mode 100644 index 0000000..ecec713 --- /dev/null +++ b/src/test/check_iface.c @@ -0,0 +1,150 @@ +//TODO make open() & close() perform do the test + +//standard library +#include +#include +#include + +//system headers +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" + +//test target headers +#include "../lib/memcry.h" +#include "../lib/iface.h" +#include "../lib/procfs_iface.h" +#include "../lib/krncry_iface.h" + + + +/* + * [BASIC TEST] + * + * Virtual interface code is simple; only external functions are tested. + * + * The following functions do not have unit tests: + * + * mc_open() & mc_close(): + * + * > Orchestrating the test in a safe manner is too complicated + * considering the simplicity of these functions. + */ + + + +//globals +mc_session s; + + + +/* + * --- [HELPERS] --- + */ + +//interface stub function return codes +#define STUB_OPEN_RET 0x1111 +#define STUB_CLOSE_RET 0x2222 +#define STUB_UPDATE_MAP_RET 0x3333 +#define STUB_READ_RET 0x4444 +#define STUB_WRITE_RET 0x5555 + + + +//interface stub functions +int stub_open(struct _mc_session * s, int pid) { + + return STUB_OPEN_RET; +} + + + +int stub_close(struct _mc_session * s) { + + return STUB_CLOSE_RET; +} + + + +int stub_update_map(const struct _mc_session * s, mc_vm_map * m){ + + return STUB_UPDATE_MAP_RET; +} + + + +int stub_read(const struct _mc_session * s, + const uintptr_t off, cm_byte * buf, const size_t sz) { + + return STUB_READ_RET; +} + + + +int stub_write(const struct _mc_session * s, + const uintptr_t off, const cm_byte * buf, const size_t sz) { + + return STUB_WRITE_RET; +} + + + +/* + * --- [FIXTURES] --- + */ + +static void _set_stub_session(mc_session * s) { + + s->iface.open = stub_open; + s->iface.close = stub_close; + s->iface.update_map = stub_update_map; + s->iface.read = stub_read; + s->iface.write = stub_write; + + return; + } + + + +/* + * --- [UNIT TESTS] --- + */ + +//mc_update_map() [stub fixture] +START_TEST(test_mc_update_map) { + + int ret = mc_update_map(&s, 0x0); + ck_assert_int_eq(ret, STUB_UPDATE_MAP_RET); + + return; + +} END_TEST + + + +//mc_read() [stub fixture] +START_TEST(test_mc_read) { + + int ret = mc_read(&s, 0x0, 0x0, 0); + ck_assert_int_eq(ret, STUB_READ_RET); + + return; + +} END_TEST + + + +//mc_write() [stub fixture] +START_TEST(test_mc_write) { + + int ret = mc_write(&s, 0x0, 0x0, 0); + ck_assert_int_eq(ret, STUB_WRITE_RET); + + return; + +} END_TEST diff --git a/src/test/check_map.c b/src/test/check_map.c index 977d6c8..d7fc139 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -4,8 +4,9 @@ #include //system headers -#include #include +#include + #include //external libraries @@ -14,6 +15,7 @@ //local headers #include "suites.h" +#include "map_helper.h" //test target headers #include "../lib/memcry.h" @@ -24,7 +26,7 @@ /* * [ADVANCED TEST] * - * The virtuam memory map management code is complicated and as such, + * The virtual memory map management code is complicated and as such, * almost every internal function has independent tests. For these tests * to run, the debug target must be built. * @@ -39,8 +41,6 @@ * * > Tested through `_map_obj_rmv_area()` * and `_map_obj_rmv_last_area()` - * - * */ /* @@ -51,25 +51,6 @@ -//map test structures -struct obj_check { - - char basename[NAME_MAX]; - uintptr_t start_addr; - uintptr_t end_addr; -}; - - - -struct area_check { - - char basename[NAME_MAX]; - uintptr_t start_addr; - uintptr_t end_addr; -}; - - - //globals - map /* @@ -142,207 +123,6 @@ static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, -//initialise a cm_lst_node stub wrapper -static void _create_lst_wrapper(cm_lst_node * node, void * data) { - - node->data = data; - node->next = node->prev = NULL; - - return; -} - - - -//assert the length of a list, also works as an integration test for CMore -static void _assert_lst_len(cm_lst * list, int len) { - - ck_assert_int_eq(list->len, len); - - //if length is zero (0), ensure head is null - if (len == 0) { - ck_assert_ptr_null(list->head); - return; - } - - //if length is one (1), ensure head is not null - ck_assert_ptr_nonnull(list->head); - cm_lst_node * iter = list->head; - if (len == 1) return; - - //if length is greater than 1 (1), iterate over nodes to ensure length - ck_assert_ptr_nonnull(iter->next); - iter = iter->next; - - for (int i = 1; i < len; ++i) { - - ck_assert(iter != list->head); - iter = iter->next; - } - - return; -} - - - -//basic assertion of state for a mc_vm_map -static void _assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, - int vm_areas_unmapped_len, int vm_objs_unmapped_len, - int next_id_area, int next_id_obj) { - - //check mapped lists - _assert_lst_len(&map->vm_areas, vm_areas_len); - _assert_lst_len(&map->vm_objs, vm_objs_len); - - //check unmapped lists - _assert_lst_len(&map->vm_areas_unmapped, vm_areas_unmapped_len); - _assert_lst_len(&map->vm_objs_unmapped, vm_objs_unmapped_len); - - //check next IDs - ck_assert_int_eq(map->next_id_area, next_id_area); - ck_assert_int_eq(map->next_id_obj, next_id_obj); - - return; -} - - - -//assert the state of all [unmapped] objects inside a mc_vm_map -static void _assert_vm_map_objs(cm_lst * obj_lst, - struct obj_check * obj_checks, - int start_index, int len) { - - mc_vm_obj * obj; - - for (int i = 0; i < len; ++i) { - - obj = cm_lst_get_p(obj_lst, start_index + i); - ck_assert_ptr_nonnull(obj); - - ck_assert_str_eq(obj->basename, obj_checks[i].basename); - ck_assert_int_eq(obj->start_addr, obj_checks[i].start_addr); - ck_assert_int_eq(obj->end_addr, obj_checks[i].end_addr); - } - - return; -} - - - -//assert the state of all [unmapped] memory areas inside a mc_vm_map -static void _assert_vm_map_areas(cm_lst * area_lst, - struct area_check * area_checks, - int start_index, int len) { - - mc_vm_area * area; - - for (int i = 0; i < len; ++i) { - - area = cm_lst_get_p(area_lst, start_index + i); - ck_assert_ptr_nonnull(area); - - ck_assert_str_eq(area->basename, area_checks[i].basename); - ck_assert_int_eq(area->start_addr, area_checks[i].start_addr); - ck_assert_int_eq(area->end_addr, area_checks[i].end_addr); - } - - return; -} - - - -static void _assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, - uintptr_t start_addr, uintptr_t end_addr, - int vm_areas_len, int last_vm_areas_len, - int id, bool mapped) { - - //check names - ck_assert_str_eq(obj->pathname, pathname); - ck_assert_str_eq(obj->basename, basename); - - //check addresses - ck_assert_int_eq(obj->start_addr, start_addr); - ck_assert_int_eq(obj->end_addr, end_addr); - - //check area node lists are initialised - _assert_lst_len(&obj->vm_area_node_ps, vm_areas_len); - _assert_lst_len(&obj->last_vm_area_node_ps, last_vm_areas_len); - - //check the object ID is correctly set - ck_assert_int_eq(obj->id, id); - - //check the object is set as mapped - ck_assert(obj->mapped == mapped); - - return; -} - - - -/* - * Check state of the object by checking the starting addresses of each of - * its constituent areas. - */ - -static void _assert_vm_obj_list(cm_lst * outer_node_lst, - uintptr_t * start_addrs, int start_addrs_len) { - - mc_vm_area * area; - cm_lst_node * area_node, * iter_node; - - - //setup iteration - iter_node = outer_node_lst->head; - - //if provided lst is empty, return - if (outer_node_lst->len == 0 && outer_node_lst->head == NULL) return; - - //otherwise iterate over area starting addresses - for (int i = 0; i < start_addrs_len; ++i) { - - //check starting address - area_node = MC_GET_NODE_PTR(iter_node); - area = MC_GET_NODE_AREA(area_node); - ck_assert_int_eq(area->start_addr, start_addrs[i]); - - //advance iteration - iter_node = iter_node->next; - } -} - - - -static void _assert_vm_area(mc_vm_area * area, char * pathname, char * basename, - uintptr_t start_addr, uintptr_t end_addr, - cm_byte access, cm_lst_node * obj_node_p, - cm_lst_node * last_obj_node_p, - int id, bool mapped) { - - //check names - ck_assert_str_eq(area->pathname, pathname); - ck_assert_str_eq(area->basename, basename); - - //check addresses - ck_assert_int_eq(area->start_addr, start_addr); - ck_assert_int_eq(area->end_addr, end_addr); - - //check access - ck_assert(area->access == access); - - //check object pointers - ck_assert_ptr_eq(area->obj_node_p, obj_node_p); - ck_assert_ptr_eq(area->last_obj_node_p, last_obj_node_p); - - //check the area ID is correctly set - ck_assert_int_eq(area->id, id); - - //check the area is mapped - ck_assert(area->mapped == mapped); - - return; -} - - - /* * --- [FIXTURES] --- */ @@ -393,7 +173,7 @@ static void _setup_stub_vm_map() { //construct object nodes for (int i = 0; i < STUB_MAP_OBJ_NUM; ++i) { - _create_lst_wrapper(&m_o_n[i], &m_o[i]); + create_lst_wrapper(&m_o_n[i], &m_o[i]); } @@ -441,7 +221,7 @@ static void _setup_stub_vm_map() { //construct area nodes for (int i = 0; i < STUB_MAP_AREA_NUM; ++i) { - _create_lst_wrapper(&m_a_n[i], &m_a[i]); + create_lst_wrapper(&m_a_n[i], &m_a[i]); } @@ -501,7 +281,7 @@ static void _setup_empty_vm_obj() { _map_new_vm_obj(&o, &m, "/foo/bar"); //populate the object node - _create_lst_wrapper(&o_n, &o); + create_lst_wrapper(&o_n, &o); return; } @@ -592,11 +372,11 @@ START_TEST(test_mc_new_del_vm_map) { //only test: construct the map mc_new_vm_map(&m); - _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); + assert_vm_map(&m, 0, 1, 0, 0, 0, 0); //check the pseudo object is present zero_obj = MC_GET_NODE_OBJ(m.vm_objs.head); - _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, + assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); mc_del_vm_map(&m); @@ -616,8 +396,8 @@ START_TEST(test__map_new_del_vm_obj) { //only test: construct the object _map_new_vm_obj(&obj, &m, "/foo/bar"); - _assert_vm_obj(&obj, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); - _assert_vm_map(&m, 0, 1, 0, 0, 0, 1); + assert_vm_obj(&obj, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); + assert_vm_map(&m, 0, 1, 0, 0, 0, 1); return; } @@ -636,9 +416,9 @@ START_TEST(test__map_make_zero_obj) { //only test: convert new object to pseudo object _map_make_zero_obj(&zero_obj); - _assert_vm_obj(&zero_obj, + assert_vm_obj(&zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); - _assert_vm_map(&m, 0, 1, 0, 0, 0, 0); + assert_vm_map(&m, 0, 1, 0, 0, 0, 0); //destroy pseudo object _map_del_vm_obj(&zero_obj); @@ -660,9 +440,9 @@ START_TEST(test__map_init_vm_area) { MC_ACCESS_READ, "/foo/bar"); _map_init_vm_area(&area, &entry, &o_n, NULL, &m); - _assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, + assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 0, true); - _assert_vm_map(&m, 0, 1, 0, 0, 1, 0); + assert_vm_map(&m, 0, 1, 0, 0, 1, 0); //second test: create a stub entry & initialise another new area @@ -670,9 +450,9 @@ START_TEST(test__map_init_vm_area) { MC_ACCESS_READ | MC_ACCESS_WRITE, "/purr/meow"); _map_init_vm_area(&area, &entry, NULL, &o_n, &m); - _assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, + assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &o_n, 1, true); - _assert_vm_map(&m, 0, 1, 0, 0, 2, 0); + assert_vm_map(&m, 0, 1, 0, 0, 2, 0); return; @@ -698,62 +478,62 @@ START_TEST(test__map_obj_add_area) { //initialise first area _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "/foo/bar"); _map_init_vm_area(&area[0], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&area_node[0], &area[0]); + create_lst_wrapper(&area_node[0], &area[0]); //first test: add first area to the backing object _map_obj_add_area(&o, &area_node[0]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x2000, 0x3000, 1, 0, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_first, 1); + assert_vm_obj(&o, "/foo/bar", "bar", 0x2000, 0x3000, 1, 0, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_first, 1); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); - _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); //initialise lower area _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/foo/bar"); _map_init_vm_area(&area[1], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&area_node[1], &area); + create_lst_wrapper(&area_node[1], &area); //second test: add lower area to the backing object _map_obj_add_area(&o, &area_node[1]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x3000, 2, 0, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_lower, 2); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x3000, 2, 0, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_lower, 2); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); - _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", - 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 1, true); + assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 1, true); //initialise higher area _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/foo/bar"); _map_init_vm_area(&area[2], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&area_node[2], &area); + create_lst_wrapper(&area_node[2], &area); //third test: add lower area to the backing object _map_obj_add_area(&o, &area_node[2]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_higher, 3); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 0, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_higher, 3); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); - _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", - 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); + assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); //initialise middle area _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "/foo/bar"); _map_init_vm_area(&area[3], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&area_node[3], &area); + create_lst_wrapper(&area_node[3], &area); //fourth test: add middle area to the backing object _map_obj_add_area(&o, &area_node[3]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 4); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 4); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); - _assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", - 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); + assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", + 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); return; @@ -779,61 +559,61 @@ START_TEST(test__map_obj_add_last_area) { //initialise first area _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "anonmap"); _map_init_vm_area(&last_area[0], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&last_area_node[0], &last_area[0]); + create_lst_wrapper(&last_area_node[0], &last_area[0]); //first test: add first area to the backing object _map_obj_add_area(&o, &last_area_node[0]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); - _assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); + assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); + assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); - _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "anonmap", "anonmap", - 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "anonmap", "anonmap", + 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); //initialise lower area _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/bin/cat"); _map_init_vm_area(&last_area[1], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&last_area_node[1], &last_area); + create_lst_wrapper(&last_area_node[1], &last_area); //second test: add lower area to the backing object _map_obj_add_area(&o, &last_area_node[1]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); - _assert_vm_obj_list(&o.last_vm_area_node_ps, state_lower, 2); + assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + assert_vm_obj_list(&o.last_vm_area_node_ps, state_lower, 2); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); - _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), - "/bin/cat", "cat", 0x1000, 0x2000, MC_ACCESS_READ, - &o_n, NULL, 1, true); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), + "/bin/cat", "cat", 0x1000, 0x2000, MC_ACCESS_READ, + &o_n, NULL, 1, true); //initialise higher area _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/lib/std"); _map_init_vm_area(&last_area[2], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&last_area_node[2], &last_area); + create_lst_wrapper(&last_area_node[2], &last_area); //third test: add lower area to the backing object _map_obj_add_area(&o, &last_area_node[2]); - _assert_vm_obj(&o, "/lib/std", "std", 0x0, 0x0, 0, 3, 0, true); - _assert_vm_obj_list(&o.last_vm_area_node_ps, state_higher, 3); + assert_vm_obj(&o, "/lib/std", "std", 0x0, 0x0, 0, 3, 0, true); + assert_vm_obj_list(&o.last_vm_area_node_ps, state_higher, 3); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); - _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "/foo/bar", "bar", - 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "/foo/bar", "bar", + 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); //initialise middle area _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "io"); _map_init_vm_area(&last_area[3], &entry, &o_n, NULL, &m); - _create_lst_wrapper(&last_area_node[3], &last_area); + create_lst_wrapper(&last_area_node[3], &last_area); //fourth test: add middle area to the backing object _map_obj_add_area(&o, &last_area_node[3]); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); - _assert_vm_obj_list(&o.last_vm_area_node_ps, state_middle, 4); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + assert_vm_obj_list(&o.last_vm_area_node_ps, state_middle, 4); last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); - _assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "io", "io", + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "io", "io", 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); return; @@ -856,32 +636,32 @@ START_TEST(test__map_obj_rmv_area) { ret = _map_obj_rmv_area(&o, &o_a_n[1]); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 2, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 3); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 3, 2, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 3); //second test: remove first area ret = _map_obj_rmv_area(&o, &o_a_n[0]); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x5000, 2, 2, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_first, 2); + assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x5000, 2, 2, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_first, 2); //third test: remove last area ret = _map_obj_rmv_area(&o, &o_a_n[3]); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x4000, 1, 2, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, state_last, 1); + assert_vm_obj(&o, "/foo/bar", "bar", 0x3000, 0x4000, 1, 2, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, state_last, 1); //fourth test: remove only remaining area ret = _map_obj_rmv_area(&o, &o_a_n[2]); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); - _assert_vm_obj_list(&o.vm_area_node_ps, NULL, 0); + assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + assert_vm_obj_list(&o.vm_area_node_ps, NULL, 0); return; @@ -901,16 +681,16 @@ START_TEST(test__map_obj_rmv_last_area) { ret = _map_obj_rmv_last_area(&o, &o_a_l_n[0]); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 1, 0, true); - _assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 1, 0, true); + assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); //second test: remove only remaining last area ret = _map_obj_rmv_last_area(&o, &o_a_l_n[1]); ck_assert_int_eq(ret, 0); - _assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); - _assert_vm_obj_list(&o.last_vm_area_node_ps, NULL, 0); + assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + assert_vm_obj_list(&o.last_vm_area_node_ps, NULL, 0); return; } @@ -953,7 +733,7 @@ START_TEST(test__map_find_obj_for_area) { //construct test objects for (int i = 0; i < 3; ++i) { _map_new_vm_obj(&objs[i], &m, obj_paths[i]); - _create_lst_wrapper(&obj_nodes[i], &objs[i]); + create_lst_wrapper(&obj_nodes[i], &objs[i]); } @@ -1022,14 +802,14 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { ck_assert_int_eq(ret, 0); //check `/lib/foo` no longer has any last areas associated with it - _assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, true); + assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, true); //check `[heap]` now has two last areas associated with it - _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); - _assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, first_state, 2); + assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); + assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, first_state, 2); //check the transfered last area (index: 8) now points to `[heap]` - _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[1], 8, true); @@ -1039,17 +819,17 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { ck_assert_int_eq(ret, 0); //check `[heap]` no longer has any last areas associated with it - _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 0, 1, true); + assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 0, 1, true); //check `/bin/cat` now has two last areas associated with it - _assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 2, 0, true); - _assert_vm_obj_list(&m_o[0].last_vm_area_node_ps, first_state, 2); + assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 2, 0, true); + assert_vm_obj_list(&m_o[0].last_vm_area_node_ps, first_state, 2); //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` - _assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[0], 4, true); - _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[0], 8, true); @@ -1062,18 +842,18 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { zero_obj = MC_GET_NODE_OBJ(zero_node); //check `/bin/cat` no longer has any last areas associated with it - _assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 0, 0, true); + assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 0, 0, true); //check the pseudo object now has two last areas associated with it - _assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, + assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); - _assert_vm_obj_list(&zero_obj->last_vm_area_node_ps, third_state, 2); + assert_vm_obj_list(&zero_obj->last_vm_area_node_ps, third_state, 2); //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` - _assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, zero_node, 4, true); - _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, zero_node, 8, true); return; @@ -1101,18 +881,18 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { ck_assert_int_eq(ret, 0); //check `[heap]` has only one last area associated with it - _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 1, 1, true); - _assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 1); + assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 1, 1, true); + assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 1); //check `/lib/foo` now has one last area associated with it - _assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 2, 0, true); - _assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, lib_foo_state, 1); + assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 2, 0, true); + assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, lib_foo_state, 1); //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` - _assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[1], 4, true); - _assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[2], 8, true); return; @@ -1144,19 +924,19 @@ START_TEST(test__map_unlink_unmapped_obj) { ck_assert_int_eq(ret, 0); //check `/lib/foo` has no last areas associated with it, and is unmapped - _assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, false); - _assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, NULL, 0); + assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, false); + assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, NULL, 0); //check `[heap]` has no last areas associated with it - _assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); - _assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 2); + assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); + assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 2); //check state of mapped objects - _assert_vm_map_objs(&m.vm_objs, obj_state, 0, 4); + assert_vm_map_objs(&m.vm_objs, obj_state, 0, 4); //check state of unmapped objects - _assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 0, 1); + assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 0, 1); //check removed object has no links to other objects anymore ck_assert(m_o[2].mapped == false); @@ -1222,28 +1002,28 @@ START_TEST(test__map_unlink_unmapped_area) { ret = _map_unlink_unmapped_area(&m_a_n[6], &m); ck_assert_int_eq(ret, 0); - _assert_vm_area(&m_a[5], "/lib/foo", "foo", + assert_vm_area(&m_a[5], "/lib/foo", "foo", 0x8000, 0x9000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - _assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0, 0); - _assert_vm_map_areas(&m.vm_areas, first_areas, 4, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 1); + assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); + assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0, 0); + assert_vm_map_areas(&m.vm_areas, first_areas, 4, 3); + assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 1); //second test: remove only area of '[heap]' ret = _map_unlink_unmapped_area(&m_a_n[3], &m); ck_assert_int_eq(ret, 0); - _assert_vm_area(&m_a[3], "[heap]", "[heap]", + assert_vm_area(&m_a[3], "[heap]", "[heap]", 0x4000, 0x5000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - _assert_vm_map_objs(&m.vm_objs, second_objs, 1, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); - _assert_vm_map_areas(&m.vm_areas, second_areas, 2, 2); - _assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 2); + assert_vm_map_objs(&m.vm_objs, second_objs, 1, 2); + assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, second_areas, 2, 2); + assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 2); return; @@ -1266,12 +1046,12 @@ START_TEST(test__map_check_area_eql) { //construct a vm_obj _map_new_vm_obj(&obj, &m, "/bin/cat"); - _create_lst_wrapper(&obj_node, &obj); + create_lst_wrapper(&obj_node, &obj); //create a vm_area _init_vm_entry(&entry, 0x1000, 0x2000, 0x0, MC_ACCESS_READ, "/bin/cat"); _map_init_vm_area(&area, &entry, &obj_node, NULL, &m); - _create_lst_wrapper(&area_node, &area); + create_lst_wrapper(&area_node, &area); //first test: entry same as vm_area @@ -1478,16 +1258,16 @@ START_TEST(test__map_resync_area) { ck_assert_int_eq(ret, 0); //check state - _assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, true); - _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), + assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/bin/cat", "cat", 0x1000, 0x3000, 3, 1, 0, true); - _assert_vm_map_objs(&m.vm_objs, first_objs, 1, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, first_objs_unmapped, 0, 1); - _assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 3); + assert_vm_map_objs(&m.vm_objs, first_objs, 1, 2); + assert_vm_map_objs(&m.vm_objs_unmapped, first_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); + assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 3); @@ -1500,17 +1280,17 @@ START_TEST(test__map_resync_area) { ck_assert_int_eq(ret, 0); //check state - _assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[2], 8, true); - _assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", + assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", 0xA000, 0xB000, 1, 1, 2, true); - _assert_vm_map_objs(&m.vm_objs, second_objs, 1, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); - _assert_vm_map_areas(&m.vm_areas, second_areas, 3, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 3); + assert_vm_map_objs(&m.vm_objs, second_objs, 1, 3); + assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, second_areas, 3, 3); + assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 3); @@ -1523,17 +1303,17 @@ START_TEST(test__map_resync_area) { ck_assert_int_eq(ret, 0); //check state - _assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, m.vm_objs.head, 4, true); - _assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", + assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); - _assert_vm_map_objs(&m.vm_objs, third_objs, 1, 2); - _assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 2); - _assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); - _assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 6); + assert_vm_map_objs(&m.vm_objs, third_objs, 1, 2); + assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 2); + assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); + assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 6); return; @@ -1572,8 +1352,8 @@ START_TEST(test__map_add_obj) { ret_node = _map_add_obj(&entry, &state, &m); ck_assert_ptr_nonnull(ret_node); - _assert_vm_map_objs(&m.vm_objs, first_objs, 3, 3); - _assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 0); + assert_vm_map_objs(&m.vm_objs, first_objs, 3, 3); + assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 0); //second test: forwarding last memory areas @@ -1583,8 +1363,8 @@ START_TEST(test__map_add_obj) { ret_node = _map_add_obj(&entry, &state, &m); ck_assert_ptr_nonnull(ret_node); - _assert_vm_map_objs(&m.vm_objs, second_objs, 3, 2); - _assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 1); + assert_vm_map_objs(&m.vm_objs, second_objs, 3, 2); + assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 1); return; @@ -1599,7 +1379,7 @@ START_TEST(test__map_add_area) { struct area_check first_areas[3] = { //start_index: 7 {"foo", 0xA000, 0xB000}, {"foo", 0xB000, 0xC000}, - {NULL, 0xC000, 0xD000} + {"", 0xC000, 0xD000} }; struct obj_check first_objs[2] = { //start index: 3 @@ -1608,8 +1388,8 @@ START_TEST(test__map_add_area) { }; struct area_check second_areas[3] = { //start_index: 6 - {NULL, 0x6000, 0x7000}, - {NULL, 0x7000, 0x8000}, + {"", 0x6000, 0x7000}, + {"", 0x7000, 0x8000}, {"foo", 0x8000, 0x9000} }; @@ -1621,7 +1401,7 @@ START_TEST(test__map_add_area) { struct area_check third_areas[3] = { //start_index: 3 {"[heap]", 0x4000, 0x5000}, {"bar", 0x5000, 0x6000}, - {NULL, 0x6000, 0x7000} + {"", 0x6000, 0x7000} }; struct obj_check third_objs[3] = { //start index: 2 @@ -1645,8 +1425,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, first_areas, 7, 3); - _assert_vm_map_objs(&m.vm_objs, first_objs, 3, 2); + assert_vm_map_areas(&m.vm_areas, first_areas, 7, 3); + assert_vm_map_objs(&m.vm_objs, first_objs, 3, 2); //second test: add area without a backing object before `/lib/foo` @@ -1656,8 +1436,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, first_areas, 6, 3); - _assert_vm_map_objs(&m.vm_objs, first_objs, 2, 2); + assert_vm_map_areas(&m.vm_areas, first_areas, 6, 3); + assert_vm_map_objs(&m.vm_objs, first_objs, 2, 2); //third test: add an area that creates a new object `/lib/bar` @@ -1667,8 +1447,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); - _assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); + assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); + assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); return; @@ -1705,7 +1485,7 @@ START_TEST(test_map_send_entry) { struct area_check third_areas[2] = { //start_index: 0 {"[heap]", 0x04000, 0x05000}, - {NULL, 0x06000, 0x07000} + {"", 0x06000, 0x07000} }; struct obj_check third_objs[3] = { //start index: 0 @@ -1727,7 +1507,7 @@ START_TEST(test_map_send_entry) { struct area_check fourth_areas[3] = { //start_index: 0 {"[heap]", 0x04000, 0x05000}, - {NULL, 0x06000, 0x07000}, + {"", 0x06000, 0x07000}, {"foo", 0x08000, 0x09000}, }; @@ -1760,8 +1540,8 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, first_areas, 9, 2); - _assert_vm_map_objs(&m.vm_objs, first_objs, 4, 2); + assert_vm_map_areas(&m.vm_areas, first_areas, 9, 2); + assert_vm_map_objs(&m.vm_objs, first_objs, 4, 2); //second test: send another "/bin/dog" area to the end of the map @@ -1771,8 +1551,8 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, second_areas, 9, 3); - _assert_vm_map_objs(&m.vm_objs, second_objs, 4, 2); + assert_vm_map_areas(&m.vm_areas, second_areas, 9, 3); + assert_vm_map_objs(&m.vm_objs, second_objs, 4, 2); //third test: send a "[heap]" entry to the start of the map @@ -1783,10 +1563,10 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); - _assert_vm_map_objs(&m.vm_objs, third_objs, 0, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); + assert_vm_map_objs(&m.vm_objs, third_objs, 0, 3); + assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 3); + assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 1); //fourth test: send a correct entry after the "[heap]" entry @@ -1797,10 +1577,10 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - _assert_vm_map_areas(&m.vm_areas, fourth_areas, 0, 3); - _assert_vm_map_objs(&m.vm_objs, fourth_objs, 0, 3); - _assert_vm_map_areas(&m.vm_areas_unmapped, fourth_areas_unmapped, 0, 3); - _assert_vm_map_objs(&m.vm_objs_unmapped, fourth_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, fourth_areas, 0, 3); + assert_vm_map_objs(&m.vm_objs, fourth_objs, 0, 3); + assert_vm_map_areas(&m.vm_areas_unmapped, fourth_areas_unmapped, 0, 3); + assert_vm_map_objs(&m.vm_objs_unmapped, fourth_objs_unmapped, 0, 1); return; @@ -1863,8 +1643,8 @@ START_TEST(test_mc_map_clean_unmapped) { ret = mc_map_clean_unmapped(&m); ck_assert_int_eq(ret, 0); - _assert_lst_len(&m.vm_areas_unmapped, 0); - _assert_lst_len(&m.vm_objs_unmapped, 0); + assert_lst_len(&m.vm_areas_unmapped, 0); + assert_lst_len(&m.vm_objs_unmapped, 0); //second test: try cleaning unmapped areas when none are present diff --git a/src/test/check_procfs_iface.c b/src/test/check_procfs_iface.c new file mode 100644 index 0000000..9576a5d --- /dev/null +++ b/src/test/check_procfs_iface.c @@ -0,0 +1,145 @@ +//standard library +#include +#include + +//system headers +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" +#include "target_helper.h" + +//test target headers +#include "../lib/memcry.h" +#include "../lib/procfs_iface.h" + + + +/* + * [BASIC TEST] + * + * Procfs interface code is simple; only external functions are tested. + */ + + +//globals +pid_t pid; + + + +/* + * --- [HELPERS] --- + */ + +static void _assert_session(mc_session * se, pid_t pid) { + + ck_assert_int_ne(se->fd_mem, -1); + ck_assert_int_eq(se->pid, pid); + + return; +} + + + +/* + * --- [UNIT TESTS] --- + */ + +//procfs_open() & procfs_close() [no fixture] +START_TEST(test_mc_open_close) { + + assert_ + + return; + +} END_TEST + + + +//procfs_update_map() [no fixture] +START_TEST(test_mc_update_map) { + + int ret; + mc_vm_map m; + + + //setup tests + pid = start_target(); + ck_assert_int_ne(pid, -1); + + ret = procfs_open(&s, pid); + ck_assert_int_eq(ret, 0); + + mc_new_vm_map(&m); + + + //first test: update empty map + ret = procfs_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + assert_target_map(pid, &m); + + + //second test: update filled map (map new areas) + change_target_map(pid); + + ret = procfs_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + assert_target_map(pid, &m); + + + //third test: update filled map (unmap old areas) + change_target_map(pid); + + ret = procfs_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + assert_target_map(pid, &m); + + + //fourth test: process exited + end_target(pid); + + ret = procfs_update_map(&s, &m); + ck_assert_int_eq(ret, -1); + + + //cleanup + ret = procfs_close(&s); + ck_assert_int_eq(ret, 0); + + return; + +} END_TEST + + + +//procfs_read() & procfs_write() [no fixture] +START_TEST(test_mc_read_write) { + + int ret; + mc_vm_map m; + + + //setup tests + pid = start_target(); + ck_assert_int_ne(pid, -1); + + ret = procfs_open(&s, pid); + ck_assert_int_eq(ret, 0); + + mc_new_vm_map(&m); + + + + + + + return; + +} END_TEST diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c new file mode 100644 index 0000000..0e419f8 --- /dev/null +++ b/src/test/iface_helper.c @@ -0,0 +1,235 @@ +//standard library +#include +#include +#include + +//system headers +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" +#include "iface_helper.h" +#include "target_helper.h" + +//test target headers +#include "../lib/memcry.h" +#include "../lib/procfs_iface.h" + + + +/* + * Interface helper is a generic interface tester. It requires the test suite + * of an interface to implement a custom `assert_session()` function that + * verifies the validity of an open session. + */ + + + +//get address for testing a read / write in a PIE process +static inline uintptr_t _get_addr(mc_vm_map * m, + int obj_index, int area_index, off_t off) { + + int ret; + + mc_vm_area * a; + cm_lst_node * a_p; + mc_vm_obj * o; + cm_lst_node * o_p; + + + //get relevant object + o_p = cm_lst_get_n(&m->vm_objs, obj_index); + ck_assert_ptr_nonnull(o_p); + o = MC_GET_NODE_OBJ(o_p); + + //get relevant area + ret = cm_lst_get(&o->vm_area_node_ps, area_index, &a_p); + ck_assert_int_eq(ret, 0); + a = MC_GET_NODE_AREA(a_p); + + return (a->start_addr + off); +} + + + +//assert open() and close() methods of an interface +void assert_iface_open_close(enum mc_iface_type iface, + void (* assert_session)(mc_session *, pid_t)) { + + int ret; + + pid_t pid; + mc_session s; + + + //setup tests + pid = start_target(); + ck_assert_int_ne(pid, -1); + + + //first test: open the target & close the target + ret = mc_open(&s, iface, pid); + ck_assert_int_eq(ret, 0); + + assert_session(&s, pid); + + ret = mc_close(&s); + ck_assert_int_eq(ret, 0); + + + //second test: re-attach to existing target and + // target exits before session is closed + ret = mc_open(&s, iface, pid); + ck_assert_int_eq(ret, 0); + + assert_session(&s, pid); + end_target(pid); + + ret = mc_close(&s); + ck_assert_int_eq(ret, 0); + + return; + +} + + + +//procfs_update_map() [no fixture] +void assert_iface_update_map(enum mc_iface_type iface) { + + int ret; + + pid_t pid; + mc_session s; + mc_vm_map m; + + + //setup tests + pid = start_target(); + ck_assert_int_ne(pid, -1); + + ret = mc_open(&s, iface, pid); + ck_assert_int_eq(ret, 0); + + mc_new_vm_map(&m); + + + //first test: update empty map + ret = mc_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + assert_target_map(pid, &m); + + + //second test: update filled map (map new areas) + change_target_map(pid); + + ret = mc_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + assert_target_map(pid, &m); + + + //third test: update filled map (unmap old areas) + change_target_map(pid); + + ret = mc_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + assert_target_map(pid, &m); + + + //fourth test: process exited + end_target(pid); + + ret = mc_update_map(&s, &m); + ck_assert_int_eq(ret, -1); + + + //cleanup + ret = mc_close(&s); + ck_assert_int_eq(ret, 0); + + ret = mc_del_vm_map(&m); + ck_assert_int_eq(ret, 0); + + return; +} + + + +//procfs_read() & procfs_write() [no fixture] +void assert_iface_read_write(enum mc_iface_type iface) { + + /* + * NOTE: These tests work with hardcoded offsets extracted from + * a compiled `unit_target` binary with rizin. If you find + * that these tests are failing for you, verify these + * offsets are correct with gdb & `/proc//maps` + */ + + int ret; + ssize_t bytes; + + cm_byte rw_buf[16]; + uintptr_t rw_buf_addr; + + pid_t pid; + mc_session s; + mc_vm_map m; + + const char * w_buf = "buffer written "; + + + //setup tests + pid = start_target(); + ck_assert_int_ne(pid, -1); + + ret = mc_open(&s, iface, pid); + ck_assert_int_eq(ret, 0); + + mc_new_vm_map(&m); + ret = mc_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + + //first test: read & write predefined rw- segment + rw_buf_addr = _get_addr(&m, IFACE_RW_BUF_OBJ_INDEX, + IFACE_RW_BUF_AREA_INDEX, IFACE_RW_BUF_OFF); + //read foreign buffer + bytes = mc_read(&s, rw_buf_addr, rw_buf, TARGET_BUF_SZ); + ck_assert_int_ne(bytes, -1); + + //check foreign buffer was read correctly + ret = strncmp((char *) rw_buf, IFACE_RW_BUF_STR, TARGET_BUF_SZ); + ck_assert_int_eq(ret, 0); + + + //write to foreign buffer + bytes = mc_write(&s, rw_buf_addr, (cm_byte *) w_buf, TARGET_BUF_SZ); + ck_assert_int_ne(bytes, -1); + + //re-read foreign buffer + bytes = mc_read(&s, rw_buf_addr, rw_buf, TARGET_BUF_SZ); + ck_assert_int_ne(bytes, -1); + + //check the write was performed correctly + ret = strncmp((char *) rw_buf, w_buf, TARGET_BUF_SZ); + ck_assert_int_eq(ret, 0); + + + + //second test: read & write to region with no read & write permissions + + + + //third test: read & write to unmapped memory + + + return; + +} END_TEST diff --git a/src/test/iface_helper.h b/src/test/iface_helper.h new file mode 100644 index 0000000..0b28f60 --- /dev/null +++ b/src/test/iface_helper.h @@ -0,0 +1,25 @@ +#ifndef IFACE_HELPER_H +#define IFACE_HELPER_H + +//standard library +#include + +//system headers +#include + +//external libraries +#include + +//test target headers +#include "../lib/memcry.h" + + + +//map helper functions +void assert_iface_open_close(enum mc_iface_type iface, + void (* assert_session)(mc_session *, pid_t)); +void assert_iface_update_map(enum mc_iface_type iface); +void assert_iface_read_write(enum mc_iface_type iface); + + +#endif diff --git a/src/test/map_helper.c b/src/test/map_helper.c new file mode 100644 index 0000000..0671c70 --- /dev/null +++ b/src/test/map_helper.c @@ -0,0 +1,255 @@ +//standard library +#include +#include + +//system headers +#include + +#include + +//external libraries +#include +#include + +//local headers +#include "map_helper.h" +#include "suites.h" + +//test target headers +#include "../lib/memcry.h" +#include "../lib/map.h" + + + +//initialise a cm_lst_node stub wrapper +void create_lst_wrapper(cm_lst_node * node, void * data) { + + node->data = data; + node->next = node->prev = NULL; + + return; +} + + + +//assert the length of a list, also works as an integration test for CMore +void assert_lst_len(cm_lst * list, int len) { + + ck_assert_int_eq(list->len, len); + + //if length is zero (0), ensure head is null + if (len == 0) { + ck_assert_ptr_null(list->head); + return; + } + + //if length is one (1), ensure head is not null + ck_assert_ptr_nonnull(list->head); + cm_lst_node * iter = list->head; + if (len == 1) return; + + //if length is greater than 1 (1), iterate over nodes to ensure length + ck_assert_ptr_nonnull(iter->next); + iter = iter->next; + + for (int i = 1; i < len; ++i) { + + ck_assert(iter != list->head); + iter = iter->next; + } + + return; +} + + + +//basic assertion of state for a mc_vm_map +void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, + int vm_areas_unmapped_len, int vm_objs_unmapped_len, + int next_id_area, int next_id_obj) { + + //check mapped lists + assert_lst_len(&map->vm_areas, vm_areas_len); + assert_lst_len(&map->vm_objs, vm_objs_len); + + //check unmapped lists + assert_lst_len(&map->vm_areas_unmapped, vm_areas_unmapped_len); + assert_lst_len(&map->vm_objs_unmapped, vm_objs_unmapped_len); + + //check next IDs + ck_assert_int_eq(map->next_id_area, next_id_area); + ck_assert_int_eq(map->next_id_obj, next_id_obj); + + return; +} + + + +//assert the state of all [unmapped] objects inside a mc_vm_map +void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, + int start_index, int len) { + + mc_vm_obj * obj; + + for (int i = 0; i < len; ++i) { + + obj = cm_lst_get_p(obj_lst, start_index + i); + ck_assert_ptr_nonnull(obj); + + ck_assert_str_eq(obj->basename, obj_checks[i].basename); + ck_assert_int_eq(obj->start_addr, obj_checks[i].start_addr); + ck_assert_int_eq(obj->end_addr, obj_checks[i].end_addr); + } + + return; +} + + + +//assert only pathnames, not mapped address ranges +void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[PATH_MAX], + int start_index, int len) { + + mc_vm_obj * obj; + + for (int i = 0; i < len; ++i) { + + obj = cm_lst_get_p(obj_lst, start_index + i); + ck_assert_ptr_nonnull(obj); + + ck_assert_str_eq(obj->basename, basenames[i]); + } + + return; +} + + + +//assert the state of all [unmapped] memory areas inside a mc_vm_map +void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, + int start_index, int len) { + + mc_vm_area * area; + + for (int i = 0; i < len; ++i) { + + area = cm_lst_get_p(area_lst, start_index + i); + ck_assert_ptr_nonnull(area); + + ck_assert_str_eq(area->basename, area_checks[i].basename); + ck_assert_int_eq(area->start_addr, area_checks[i].start_addr); + ck_assert_int_eq(area->end_addr, area_checks[i].end_addr); + } + + return; +} + + + +//assert only pathnames, not mapped address ranges +void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[PATH_MAX], + int start_index, int len) { + + mc_vm_area * area; + + for (int i = 0; i < len; ++i) { + + area = cm_lst_get_p(area_lst, start_index + i); + ck_assert_ptr_nonnull(area); + + ck_assert_str_eq(area->basename, basenames[i]); + } + + return; +} + + + +void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, + uintptr_t start_addr, uintptr_t end_addr, int vm_areas_len, + int last_vm_areas_len, int id, bool mapped) { + + //check names + ck_assert_str_eq(obj->pathname, pathname); + ck_assert_str_eq(obj->basename, basename); + + //check addresses + ck_assert_int_eq(obj->start_addr, start_addr); + ck_assert_int_eq(obj->end_addr, end_addr); + + //check area node lists are initialised + assert_lst_len(&obj->vm_area_node_ps, vm_areas_len); + assert_lst_len(&obj->last_vm_area_node_ps, last_vm_areas_len); + + //check the object ID is correctly set + ck_assert_int_eq(obj->id, id); + + //check the object is set as mapped + ck_assert(obj->mapped == mapped); + + return; +} + + + +/* + * Check state of the object by checking the starting addresses of each of + * its constituent areas. + */ + +void assert_vm_obj_list(cm_lst * outer_node_lst, + uintptr_t * start_addrs, int start_addrs_len) { + + mc_vm_area * area; + cm_lst_node * area_node, * iter_node; + + + //setup iteration + iter_node = outer_node_lst->head; + + //if provided lst is empty, return + if (outer_node_lst->len == 0 && outer_node_lst->head == NULL) return; + + //otherwise iterate over area starting addresses + for (int i = 0; i < start_addrs_len; ++i) { + + //check starting address + area_node = MC_GET_NODE_PTR(iter_node); + area = MC_GET_NODE_AREA(area_node); + ck_assert_int_eq(area->start_addr, start_addrs[i]); + + //advance iteration + iter_node = iter_node->next; + } +} + + + +void assert_vm_area(mc_vm_area * area, char * pathname, char * basename, + uintptr_t start_addr, uintptr_t end_addr, + cm_byte access, cm_lst_node * obj_node_p, + cm_lst_node * last_obj_node_p, int id, bool mapped) { + + //check names + ck_assert_str_eq(area->pathname, pathname); + ck_assert_str_eq(area->basename, basename); + + //check addresses + ck_assert_int_eq(area->start_addr, start_addr); + ck_assert_int_eq(area->end_addr, end_addr); + + //check access + ck_assert(area->access == access); + + //check object pointers + ck_assert_ptr_eq(area->obj_node_p, obj_node_p); + ck_assert_ptr_eq(area->last_obj_node_p, last_obj_node_p); + + //check the area ID is correctly set + ck_assert_int_eq(area->id, id); + + //check the area is mapped + ck_assert(area->mapped == mapped); + + return; +} diff --git a/src/test/map_helper.h b/src/test/map_helper.h new file mode 100644 index 0000000..056cee1 --- /dev/null +++ b/src/test/map_helper.h @@ -0,0 +1,68 @@ +#ifndef MAP_HELPER_H +#define MAP_HELPER_H + +//standard library +#include + +//system headers +#include + +#include + +//external libraries +#include + +//test target headers +#include "../lib/memcry.h" + + + +//map test structures +struct obj_check { + + char basename[NAME_MAX]; + uintptr_t start_addr; + uintptr_t end_addr; +}; + + + +struct area_check { + + char basename[NAME_MAX]; + uintptr_t start_addr; + uintptr_t end_addr; +}; + + + +//map helper functions +void create_lst_wrapper(cm_lst_node * node, void * data); +void assert_lst_len(cm_lst * list, int len); + +void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, + int vm_areas_unmapped_len, int vm_objs_unmapped_len, + int next_id_area, int next_id_obj); + +void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, + int start_index, int len); +void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[PATH_MAX], + int start_index, int len); + +void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, + int start_index, int len); +void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[PATH_MAX], + int start_index, int len); + +void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, + uintptr_t start_addr, uintptr_t end_addr, int vm_areas_len, + int last_vm_areas_len, int id, bool mapped); +void assert_vm_obj_list(cm_lst * outer_node_lst, + uintptr_t * start_addrs, int start_addrs_len); + +void assert_vm_area(mc_vm_area * area, char * pathname, char * basename, + uintptr_t start_addr, uintptr_t end_addr, + cm_byte access, cm_lst_node * obj_node_p, + cm_lst_node * last_obj_node_p, int id, bool mapped); + +#endif diff --git a/src/test/target/Makefile b/src/test/target/Makefile new file mode 100644 index 0000000..7539c62 --- /dev/null +++ b/src/test/target/Makefile @@ -0,0 +1,39 @@ +.RECIPEPREFIX:=> + +# This makefile takes the following variables: +# +# CC - Compiler. +# BUILD_DIR - Unit test build directory. + + +CFLAGS=-O0 -ggdb +WARN_OPTS+=${_WARN_OPTS} -Wno-unused-variable -Wno-unused-but-set-variable +LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} -lcmore -lcheck + +SOURCES_MANUAL_TARGET=manual_target.c +OBJECTS_MANUAL_TARGET=${SOURCES_MANUAL_TARGET:%.c=${BUILD_DIR}/%.o} + +SOURCES_UNIT_TARGET=unit_target.c +OBJECTS_UNIT_TARGET=${SOURCES_UNIT_TARGET}:%.c=${BUILD_IR}/%.o} + +MANUAL_TARGET=manual_target +UNIT_TARGET=unit_target + +TARGETS=${MANUAL_TARGET} ${UNIT_TARGET} + + +targets: ${TARGETS} +> mkdir -p ${BUILD_DIR} +> mv ${TARGETS} ${BUILD_DIR} + +${MANUAL_TARGET}: ${OBJECTS_MANUAL_TARGET} +> ${CC} ${CFLAGS} ${WARN_OPTS} -o $@ $^ ${LDFLAGS} + +${UNIT_TARGET}: ${OBJECTS_UNIT_TARGET} +> ${CC} ${CFLAGS} ${WARN_OPTS} -o $@ $^ ${LDFLAGS} + +${BUILD_DIR}/%.o: %.c +> ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ + +clean: +> -rm -v ${BUILD_DIR}/{*.o,${MANUAL_TARGET},${UNIT_TARGET}} diff --git a/src/test/target/manual_target.c b/src/test/target/manual_target.c new file mode 100644 index 0000000..4f30ea3 --- /dev/null +++ b/src/test/target/manual_target.c @@ -0,0 +1,92 @@ +//standard library +#include +#include +#include +#include +#include +#include + +//system headers +#include +#include +#include + + + +/* + * This program will continue counting up to confirm that it is running. + * Press `ENTER` at any point to make the program dlopen() an additional + * library, changing it's memory map. + */ + + + +//globals +struct termios old_term, new_term; + + + +//Ctrl+C signal handler +void sigint_handler(int signum) { + + tcsetattr(STDIN_FILENO, TCSANOW, &old_term); + _exit(signum); +} + + + +//set terminal to non-blocking, non-canon, non-echo mode +void setup_terminal() { + + //clone old terminal attributes + new_term = old_term; + + //disable canonical mode & input echo + new_term.c_lflag &= ~(ICANON | ECHO); + + //set new terminal attributes + tcsetattr(STDIN_FILENO, TCSANOW, &new_term); + + //disable blocking on STDIN + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + + return; +} + + + +int main() { + + int ch; + bool map_changed = false; + + + //get old terminal attributes + tcgetattr(STDIN_FILENO, &old_term); + + //register SIGINT handler + signal(SIGINT, sigint_handler); + + //setup terminal + setup_terminal(); + + for (int i = 0; ++i; ) { + + printf("Target running: %d\n", i); + ch = getchar(); + + //if `ENTER` key is pressed, dlopen() a library + if (ch == '\n' && map_changed == false) { + + //change process maps + dlopen("libelf.so.1", RTLD_LAZY); + map_changed = true; + + puts("Target memory map changed."); + } + + sleep(1); + } + + return 0; +} diff --git a/src/test/target/unit_target b/src/test/target/unit_target new file mode 100755 index 0000000000000000000000000000000000000000..2700e130466e7e49edcd49732e1ea915b479e8fb GIT binary patch literal 18768 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 RUS0g-yQIb4E`W;_zW`tA6_@}3 literal 0 HcmV?d00001 diff --git a/src/test/target/unit_target.c b/src/test/target/unit_target.c new file mode 100644 index 0000000..9c9a5cf --- /dev/null +++ b/src/test/target/unit_target.c @@ -0,0 +1,90 @@ +//standard library +#include +#include +#include +#include +#include + +//system headers +#include +#include +#include + +#include +#include + +//local headers +#include "../target_helper.h" + + + +/* + * Unit target is tied directly to `../target_helper.{c,h}`. Changing this + * target likely necessitates also changing the target helper. + */ + + +/* + * This program sleeps indefinitely, awaiting a signal from its parent, + * which will cause it to dlopen() an additional library and hence change + * its memory map. This is done from inside unit tests. + */ + + + +//globals +void * libelf; +enum target_map_state state = UNCHANGED; + +/* + * These buffers are used in read/write tests for interfaces. + */ + + char rw_buf[TARGET_BUF_SZ] = IFACE_RW_BUF_STR; + + + +//unit test signal handler +void sigusr1_handler(int signum) { + + if (state == UNCHANGED) { + + libelf = dlopen("libelf.so.1", RTLD_LAZY); + state = MAPPED; + + } else if (state == MAPPED) { + + dlclose(libelf); + state = UNMAPPED; + } + + return; +} + + + +int main(int argc, char ** argv) { + + int ch; + pid_t parent_pid; + void * protected_area; + + + //recover parent pid + parent_pid = atoi(argv[1]); + + //map an area that can't be accessed + protected_area = mmap((void *) 0x10000, 0x1000, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + //register unit test handler + signal(SIGUSR1, sigusr1_handler); + + for (int i = 0; ++i; ) { + + //sleep for 10ms to not hoard the CPU + usleep(100000); + } + + return 0; +} diff --git a/src/test/target_helper.c b/src/test/target_helper.c new file mode 100644 index 0000000..440322e --- /dev/null +++ b/src/test/target_helper.c @@ -0,0 +1,292 @@ +//standard library +#include +#include + +//system headers +#include +#include + +#include + +#include + +//external libraries +#include + +//local headers +#include "target_helper.h" +#include "map_helper.h" + +//test target headers +#include "../lib/memcry.h" + + + +//globals +static enum target_map_state target_state; + +#define TARGET_AREAS_UNCHANGED 22 +char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX] = { + "", + "unit_target", + "unit_target", + "unit_target", + "unit_target", + "unit_target", + "", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "", + "", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "[stack]", + "[vvar]", + "[vdso]" +}; + +#define TARGET_OBJS_UNCHANGED 7 +char objs_unchanged[TARGET_OBJS_UNCHANGED][NAME_MAX] = { + "0x0", + "unit_target", + "libc.so.6", + "ld-linux-x86-64.so.2", + "[stack]", + "[vvar]", + "[vdso]" +}; + +#define TARGET_AREAS_MAPPED 33 +char areas_mapped[TARGET_AREAS_MAPPED][NAME_MAX] = { + "", + "unit_target", + "unit_target", + "unit_target", + "unit_target", + "unit_target", + "[heap]", + "libz.so.1.2.13", + "libz.so.1.2.13", + "libz.so.1.2.13", + "libz.so.1.2.13", + "libz.so.1.2.13", + "libelf-0.188.so", + "libelf-0.188.so", + "libelf-0.188.so", + "libelf-0.188.so", + "libelf-0.188.so", + "", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "", + "", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "[stack]", + "[vvar]", + "[vdso]" +}; + +#define TARGET_OBJS_MAPPED 10 +char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { + "0x0", + "unit_target", + "[heap]", + "libz.so.1.2.13", + "libelf-0.188.so", + "libc.so.6", + "ld-linux-x86-64.so.2", + "[stack]", + "[vvar]", + "[vdso]" +}; + +#define TARGET_AREAS_UNMAPPED 23 +char areas_unmapped[TARGET_AREAS_UNMAPPED][NAME_MAX] = { + "", + "unit_target", + "unit_target", + "unit_target", + "unit_target", + "unit_target", + "[heap]", + "", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "libc.so.6", + "", + "", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "ld-linux-x86-64.so.2", + "[stack]", + "[vvar]", + "[vdso]" +}; + +#define TARGET_OBJS_UNMAPPED 8 +char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { + "0x0", + "unit_target", + "[heap]", + "libc.so.6", + "ld-linux-x86-64.so.2", + "[stack]", + "[vvar]", + "[vdso]" +}; + + + +//signal handlers +static void _sigusr1_handler() { + + if (target_state == UNCHANGED) { + target_state = MAPPED; + + } else if (target_state == MAPPED) { + target_state = UNMAPPED; + + } + + return; +} + + + +//helpers +pid_t start_target() { + + int ret; + __sighandler_t ret_s; + + pid_t target_pid, parent_pid; + char pid_buf[8]; + + char * argv[3] = {TARGET_PATH, 0, 0}; + target_state = UNCHANGED; + + + //get current pid to pass to target + parent_pid = getpid(); + snprintf(pid_buf, 8, "%d", parent_pid); + argv[1] = pid_buf; + + //fork a new process + target_pid = fork(); + ck_assert_int_ne(target_pid, -1); + + //change image to target in child + if (target_pid == 0) { + + ret = execve(TARGET_PATH, argv, NULL); + ck_assert_int_ne(ret, -1); + + //if parent, register signal handler for child + } else { + + ret_s = signal(SIGUSR1, _sigusr1_handler); + ck_assert(ret_s != SIG_ERR); + } + + return target_pid; +} + + + +void end_target(pid_t pid) { + + int ret; + __sighandler_t ret_s; + + pid_t ret_p; + + + //unregister signal handler + ret_s = signal(SIGUSR1, SIG_DFL); + + //terminate target process + ret = kill(pid, SIGTERM); + ck_assert_int_eq(ret, 0); + + //wait for it to terminate + ret_p = waitpid(pid, NULL, 0); + ck_assert_int_eq(ret_p, pid); + + return; +} + + + +void change_target_map(pid_t pid) { + + int ret; + __sighandler_t ret_s; + + enum target_map_state old_state = target_state; + + + //assert the target hasn't performed all map transformations already + ck_assert(target_state != UNMAPPED); + + //send SIGUSR1 to the target process to request a change in its memory map + ret = kill(pid, SIGUSR1); + ck_assert_int_eq(ret, 0); + + //busy-wait for target to change its memory map + while(target_state == old_state) {} + + return; +} + + + +void assert_target_map(pid_t pid, mc_vm_map * map) { + + switch(target_state) { + + case UNCHANGED: + + assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unchanged, + 0, TARGET_AREAS_UNCHANGED); + assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unchanged, + 0, TARGET_OBJS_UNCHANGED); + break; + + case MAPPED: + + assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_mapped, + 0, TARGET_AREAS_MAPPED); + assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_mapped, + 0, TARGET_OBJS_MAPPED); + break; + + case UNMAPPED: + + assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unmapped, + 0, TARGET_AREAS_UNMAPPED); + assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unmapped, + 0, TARGET_OBJS_UNMAPPED); + break; + + + } //end switch + + return; +} diff --git a/src/test/target_helper.h b/src/test/target_helper.h new file mode 100644 index 0000000..3e4a5fb --- /dev/null +++ b/src/test/target_helper.h @@ -0,0 +1,52 @@ +#ifndef TARGET_H +#define TARGET_H + +//system headers +#include + +//test target headers +#include "../lib/memcry.h" + + + +/* + * The target helper is directly tied to `target/unit_target.c`, changing + * the target helper means you likely have to change the unit target and + * vice versa. + */ + + + +//the state of the target's memory map +enum target_map_state { + UNCHANGED, //default state + MAPPED, //new areas mapped + UNMAPPED //newly mapped areas now unmapped +}; + + + +//target metadata +#define TARGET_PATH "target" +#define TARGET_BUF_SZ 16 + +#define IFACE_RW_BUF_STR "read & write me " + +#define IFACE_RW_BUF_OBJ_INDEX 1 +#define IFACE_RW_BUF_AREA_INDEX 4 +#define IFACE_RW_BUF_OFF 0x40 + +#define IFACE_NONE_OBJ_INDEX 0 +#define IFACE_NONE_AREA_INDEX 0 +#define IFACE_NONE_OFF 0x0 + + + +//target helpers +pid_t start_target(); +void end_target(pid_t pid); +void change_target_map(pid_t pid); +void assert_target_map(pid_t pid, mc_vm_map * map); + + +#endif From 31697072f1747c21c9ffd99cdb9bdb2435057e96 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Feb 2025 02:12:19 +0000 Subject: [PATCH 11/45] add map util unit tests --- src/lib/iface.h | 8 +- src/lib/map_util.c | 32 ++-- src/lib/map_util.h | 26 +-- src/lib/memcry.h | 34 ++-- src/lib/procfs_iface.c | 6 +- src/lib/procfs_iface.h | 8 +- src/test/check_iface.c | 150 ----------------- src/test/check_krncry_iface.c | 75 +++++++++ src/test/check_map.c | 30 ++-- src/test/check_map_util.c | 308 ++++++++++++++++++++++++++++++++++ src/test/check_procfs_iface.c | 90 ++-------- src/test/check_util.c | 107 ++++++++++++ src/test/iface_helper.c | 77 +++++++-- src/test/info.c | 18 ++ src/test/info.h | 21 +++ src/test/target/unit_target | Bin 18768 -> 0 bytes src/test/target_helper.c | 8 +- src/test/target_helper.h | 22 ++- 18 files changed, 701 insertions(+), 319 deletions(-) delete mode 100644 src/test/check_iface.c create mode 100644 src/test/check_krncry_iface.c create mode 100644 src/test/check_map_util.c create mode 100644 src/test/check_util.c create mode 100644 src/test/info.c create mode 100644 src/test/info.h delete mode 100755 src/test/target/unit_target diff --git a/src/lib/iface.h b/src/lib/iface.h index e4e5a59..5d7da07 100644 --- a/src/lib/iface.h +++ b/src/lib/iface.h @@ -23,9 +23,9 @@ int mc_open(mc_session * session, const enum mc_iface_type iface, const pid_t pid); int mc_close(mc_session * session); int mc_update_map(const mc_session * session, mc_vm_map * vm_map); -ssize_t mc_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -ssize_t mc_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +int mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +int mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); #endif diff --git a/src/lib/map_util.c b/src/lib/map_util.c index 6c1b1ad..5aa59d9 100644 --- a/src/lib/map_util.c +++ b/src/lib/map_util.c @@ -63,12 +63,12 @@ cm_lst_node * _get_obj_last_area(const mc_vm_obj * vm_obj) { cm_lst_node * last_node; //if this object has multiple areas - if (vm_obj->vm_area_node_ptrs.len > 1) { - last_node = MC_GET_NODE_PTR(vm_obj->vm_area_node_ptrs.head->prev); + if (vm_obj->vm_area_node_ps.len > 1) { + last_node = MC_GET_NODE_PTR(vm_obj->vm_area_node_ps.head->prev); //else this object only has one area } else { - last_node = MC_GET_NODE_PTR(vm_obj->vm_area_node_ptrs.head); + last_node = MC_GET_NODE_PTR(vm_obj->vm_area_node_ps.head); } return last_node; @@ -96,7 +96,7 @@ cm_lst_node * _fast_addr_find(const mc_vm_map * vm_map, iter_obj_node = _get_starting_obj(vm_map); iter_vm_obj = MC_GET_NODE_OBJ(iter_obj_node); - iter_area_node = MC_GET_NODE_PTR(iter_vm_obj->vm_area_node_ptrs.head); + iter_area_node = MC_GET_NODE_PTR(iter_vm_obj->vm_area_node_ps.head); iter_vm_area = MC_GET_NODE_AREA(iter_area_node); @@ -205,7 +205,8 @@ cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, * --- [EXTERNAL] --- */ -off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr) { +off_t mc_get_area_offset(const cm_lst_node * area_node, + const uintptr_t addr) { mc_vm_area * vm_area = MC_GET_NODE_AREA(area_node); @@ -214,7 +215,8 @@ off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr) { -off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr) { +off_t mc_get_obj_offset(const cm_lst_node * obj_node, + const uintptr_t addr) { mc_vm_obj * vm_obj = MC_GET_NODE_OBJ(obj_node); @@ -245,8 +247,9 @@ off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, -cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, off_t * offset) { +cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, + off_t * offset) { cm_lst_node * area_node; @@ -260,8 +263,9 @@ cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, -cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, off_t * offset) { +cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, + off_t * offset) { cm_lst_node * obj_node; @@ -275,8 +279,8 @@ cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, -cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, - const char * pathname) { +cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, + const char * pathname) { cm_lst_node * obj_node; @@ -288,8 +292,8 @@ cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, -cm_lst_node * mc_get_vm_obj_by_basename(const mc_vm_map * vm_map, - const char * basename) { +cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, + const char * basename) { cm_lst_node * obj_node; diff --git a/src/lib/map_util.h b/src/lib/map_util.h index d42032b..f1b9176 100644 --- a/src/lib/map_util.h +++ b/src/lib/map_util.h @@ -21,22 +21,26 @@ cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, //external -off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr); -off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr); +off_t mc_get_area_offset(const cm_lst_node * area_node, + const uintptr_t addr); +off_t mc_get_obj_offset(const cm_lst_node * obj_node, + const uintptr_t addr); off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, const uintptr_t addr); off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, const uintptr_t addr); -cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, off_t * offset); -cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, off_t * offset); - -cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, - const char * pathname); -cm_lst_node * mc_get_vm_obj_by_basename(const mc_vm_map * vm_map, - const char * basename); +cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, + off_t * offset); +cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, + off_t * offset); + +cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, + const char * pathname); +cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, + const char * basename); #endif diff --git a/src/lib/memcry.h b/src/lib/memcry.h index da3ef31..54d52e8 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -32,7 +32,6 @@ extern "C"{ //pseudo object id #define MC_ZERO_OBJ_ID -1 -//do not seek when reading/writing //interface types @@ -163,10 +162,10 @@ extern int mc_open(mc_session * session, const enum mc_iface_type iface, const pid_t pid); extern int mc_close(mc_session * session); extern int mc_update_map(const mc_session * session, mc_vm_map * vm_map); -extern ssize_t mc_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -extern ssize_t mc_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +extern int mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +extern int mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); // --- [map] //all return 0 = success, -1 = fail/error @@ -177,23 +176,28 @@ extern int mc_map_clean_unmapped(mc_vm_map * map); // --- [map util] //return: offset from start of area/object -extern off_t mc_get_area_offset(const cm_lst_node * area_node, const uintptr_t addr); -extern off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr); +extern off_t mc_get_area_offset(const cm_lst_node * area_node, + const uintptr_t addr); +extern off_t mc_get_obj_offset(const cm_lst_node * obj_node, + const uintptr_t addr); //return: offset from start of area/object = success, -1 = not in area/object extern off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, const uintptr_t addr); extern off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, const uintptr_t addr); + //return area node * = success, NULL = fail/error -extern cm_lst_node * mc_get_vm_area_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, off_t * offset); +extern cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, + off_t * offset); //return obj node * = success, NULL = fail/error -extern cm_lst_node * mc_get_vm_obj_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, off_t * offset); -extern cm_lst_node * mc_get_vm_obj_by_pathname(const mc_vm_map * vm_map, - const char * pathname); -extern cm_lst_node * mc_get_vm_obj_by_basename(const mc_vm_map * vm_map, - const char * basename); +extern cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, + off_t * offset); +extern cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, + const char * pathname); +extern cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, + const char * basename); // --- [error handling] diff --git a/src/lib/procfs_iface.c b/src/lib/procfs_iface.c index f210d0c..36eb250 100644 --- a/src/lib/procfs_iface.c +++ b/src/lib/procfs_iface.c @@ -196,8 +196,8 @@ int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { -ssize_t procfs_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz) { +int procfs_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz) { off_t off_ret; ssize_t read_bytes, read_done, read_left; @@ -236,7 +236,7 @@ ssize_t procfs_read(const mc_session * session, const uintptr_t addr, int procfs_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz) { + const cm_byte * buf, const size_t buf_sz) { off_t off_ret; ssize_t write_bytes, write_done, write_left; diff --git a/src/lib/procfs_iface.h b/src/lib/procfs_iface.h index a142158..364b701 100644 --- a/src/lib/procfs_iface.h +++ b/src/lib/procfs_iface.h @@ -23,9 +23,9 @@ void _build_entry(struct vm_entry * entry, const char * line_buf); int procfs_open(mc_session * session, const int pid); int procfs_close(mc_session * session); int procfs_update_map(const mc_session * session, mc_vm_map * vm_map); -ssize_t procfs_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -ssize_t procfs_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); +int procfs_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz); +int procfs_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz); #endif diff --git a/src/test/check_iface.c b/src/test/check_iface.c deleted file mode 100644 index ecec713..0000000 --- a/src/test/check_iface.c +++ /dev/null @@ -1,150 +0,0 @@ -//TODO make open() & close() perform do the test - -//standard library -#include -#include -#include - -//system headers -#include - -//external libraries -#include -#include - -//local headers -#include "suites.h" - -//test target headers -#include "../lib/memcry.h" -#include "../lib/iface.h" -#include "../lib/procfs_iface.h" -#include "../lib/krncry_iface.h" - - - -/* - * [BASIC TEST] - * - * Virtual interface code is simple; only external functions are tested. - * - * The following functions do not have unit tests: - * - * mc_open() & mc_close(): - * - * > Orchestrating the test in a safe manner is too complicated - * considering the simplicity of these functions. - */ - - - -//globals -mc_session s; - - - -/* - * --- [HELPERS] --- - */ - -//interface stub function return codes -#define STUB_OPEN_RET 0x1111 -#define STUB_CLOSE_RET 0x2222 -#define STUB_UPDATE_MAP_RET 0x3333 -#define STUB_READ_RET 0x4444 -#define STUB_WRITE_RET 0x5555 - - - -//interface stub functions -int stub_open(struct _mc_session * s, int pid) { - - return STUB_OPEN_RET; -} - - - -int stub_close(struct _mc_session * s) { - - return STUB_CLOSE_RET; -} - - - -int stub_update_map(const struct _mc_session * s, mc_vm_map * m){ - - return STUB_UPDATE_MAP_RET; -} - - - -int stub_read(const struct _mc_session * s, - const uintptr_t off, cm_byte * buf, const size_t sz) { - - return STUB_READ_RET; -} - - - -int stub_write(const struct _mc_session * s, - const uintptr_t off, const cm_byte * buf, const size_t sz) { - - return STUB_WRITE_RET; -} - - - -/* - * --- [FIXTURES] --- - */ - -static void _set_stub_session(mc_session * s) { - - s->iface.open = stub_open; - s->iface.close = stub_close; - s->iface.update_map = stub_update_map; - s->iface.read = stub_read; - s->iface.write = stub_write; - - return; - } - - - -/* - * --- [UNIT TESTS] --- - */ - -//mc_update_map() [stub fixture] -START_TEST(test_mc_update_map) { - - int ret = mc_update_map(&s, 0x0); - ck_assert_int_eq(ret, STUB_UPDATE_MAP_RET); - - return; - -} END_TEST - - - -//mc_read() [stub fixture] -START_TEST(test_mc_read) { - - int ret = mc_read(&s, 0x0, 0x0, 0); - ck_assert_int_eq(ret, STUB_READ_RET); - - return; - -} END_TEST - - - -//mc_write() [stub fixture] -START_TEST(test_mc_write) { - - int ret = mc_write(&s, 0x0, 0x0, 0); - ck_assert_int_eq(ret, STUB_WRITE_RET); - - return; - -} END_TEST diff --git a/src/test/check_krncry_iface.c b/src/test/check_krncry_iface.c new file mode 100644 index 0000000..e62a2a5 --- /dev/null +++ b/src/test/check_krncry_iface.c @@ -0,0 +1,75 @@ +//standard library +#include +#include + +//system headers +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" +#include "iface_helper.c" + +//test target headers +#include "../lib/memcry.h" + + + +/* + * [BASIC TEST] + */ + +/* + * NOTE: Unit tests for interfaces are standardised through the iface helper. + */ + + + +/* + * --- [HELPERS] --- + */ + +static void _assert_session(mc_session * se, pid_t pid) { + + ck_assert_int_ne(se->major, -1); + ck_assert_int_eq(se->fd_dev_krncry, pid); + + return; +} + + + +/* + * --- [UNIT TESTS] --- + */ + +//krncry_open() & krncry_close() [no fixture] +START_TEST(test_krncry_mc_open_close) { + + assert_iface_open_close(KRNCRY, _assert_session); + return; + +} END_TEST + + + +//krncry_update_map() [no fixture] +START_TEST(test_krncry_mc_update_map) { + + assert_iface_update_map(KRNCRY); + return; + +} END_TEST + + + +//krncry_read() & krncry_write() [no fixture] +START_TEST(test_krncry_mc_read_write) { + + assert_iface_read_write(KRNCRY); + return; + +} END_TEST diff --git a/src/test/check_map.c b/src/test/check_map.c index d7fc139..72aec5a 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -25,28 +25,30 @@ /* * [ADVANCED TEST] + */ + +/* + * NOTE: The virtual memory map management code is complicated and as such, + * almost every internal function has independent tests. For these + * tests to run, the debug target must be built. * - * The virtual memory map management code is complicated and as such, - * almost every internal function has independent tests. For these tests - * to run, the debug target must be built. - * - * The following functions do not have unit tests: + * The following functions do not have unit tests: * - * _map_obj_add_area_insert(): + * _map_obj_add_area_insert(): * - * > Tested through `_map_obj_add_area()` - * and `_map_obj_add_last_area()` + * > Tested through `_map_obj_add_area()` + * and `_map_obj_add_last_area()` * - * _map_obj_find_area_outer_node(): + * _map_obj_find_area_outer_node(): * - * > Tested through `_map_obj_rmv_area()` - * and `_map_obj_rmv_last_area()` + * > Tested through `_map_obj_rmv_area()` + * and `_map_obj_rmv_last_area()` */ /* - * Functions are not tested in the same order as they appear in the map.c - * source file. This assists with bootstrapping many tests. The order of - * testing remains close. + * NOTE: Functions are not tested in the same order as they appear in + * the map.c source file. This assists with bootstrapping many tests. + * The order of testing remains close. */ diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c new file mode 100644 index 0000000..4bc87f9 --- /dev/null +++ b/src/test/check_map_util.c @@ -0,0 +1,308 @@ +//standard library +#include +#include + +//system headers +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" +#include "target_helper.h" + +//test target headers +#include "../lib/memcry.h" + + + +/* + * [BASIC TEST] + */ + + + +//globals +mc_vm_map m; +mc_session s; +pid_t pid; + + + +/* + * --- [FIXTURES] --- + */ + +//initialise the target +static void _setup_target() { + + int ret; + + + pid = start_target(); + ck_assert_int_ne(pid, -1); + + ret = mc_open(&s, PROCFS, pid); + ck_assert_int_eq(ret, 0); + + mc_new_vm_map(&m); + ret = mc_update_map(&s, &m); + ck_assert_int_eq(ret, 0); + + return; +} + + + +//teardown the target +static void _teardown_target() { + + int ret; + + + ret = mc_del_vm_map(&m); + ck_assert_int_eq(ret, 0); + + ret = mc_close(&s); + ck_assert_int_eq(ret, 0); + + end_target(pid); + + return; +} + + + +/* + * --- [UNIT TESTS] --- + */ + +//mc_get_area_offset() [target fixture] +START_TEST(test_mc_get_area_offset) { + + off_t off; + + mc_vm_obj * o; + cm_lst_node * a_n; + + + //first test: typical offset + o = MC_GET_NODE_OBJ(m.vm_objs.head); + a_n = MC_GET_NODE_PTR(o->vm_area_node_ps.head); + + off = mc_get_area_offset(a_n, 0x10800); + ck_assert_int_eq(off, 0x800); + + + //second test: address is lower than area's starting address + off = mc_get_area_offset(a_n, 0x9800); + ck_assert_int_eq(off, -0x800); + + return; + +} END_TEST + + + +//mc_get_obj_offset() [target fixture] +START_TEST(test_mc_get_obj_offset) { + + off_t off; + + cm_lst_node * o_n; + + + //first test: typical offset + o_n = m.vm_objs.head; + + off = mc_get_obj_offset(o_n, 0x10800); + ck_assert_int_eq(off, 0x800); + + + //second test: address is lower than obj's starting address + off = mc_get_obj_offset(o_n, 0x9800); + ck_assert_int_eq(off, -0x800); + + return; + +} END_TEST + + + +//mc_get_area_offset_bnd() [target fixture] +START_TEST(test_mc_get_area_offset_bnd) { + + off_t off; + + mc_vm_obj * o; + cm_lst_node * a_n; + + + //first test: typical offset + o = MC_GET_NODE_OBJ(m.vm_objs.head); + a_n = MC_GET_NODE_PTR(o->vm_area_node_ps.head); + + off = mc_get_area_offset(a_n, 0x10800); + ck_assert_int_eq(off, 0x800); + + + //second test: address is lower than area's starting address + off = mc_get_area_offset(a_n, 0x9800); + ck_assert_int_eq(off, -1); + + return; + +} END_TEST + + + +//mc_get_obj_offset_bnd() [target fixture] +START_TEST(test_mc_get_obj_offset_bnd) { + + off_t off; + + cm_lst_node * o_n; + + + //first test: typical offset + o_n = m.vm_objs.head; + + off = mc_get_obj_offset(o_n, 0x10800); + ck_assert_int_eq(off, 0x800); + + + //second test: address is lower than obj's starting address + off = mc_get_obj_offset(o_n, 0x9800); + ck_assert_int_eq(off, -1); + + return; + +} END_TEST + + + +//mc_get_vm_area_by_addr [target fixture] +START_TEST(test_mc_get_vm_area_by_addr) { + + cm_lst_node * ret_n; + off_t off; + + mc_vm_obj * o; + mc_vm_area * a; + cm_lst_node * a_n; + + + //first test: mapped address + a_n = m.vm_areas.head->next->next; + a = MC_GET_NODE_AREA(a_n); + + ret_n = mc_get_area_node_by_addr(&m, a->start_addr + 0x200, &off); + ck_assert_ptr_eq(ret_n, a_n); + ck_assert_int_eq(off, 0x200); + + + //second test: unmapped address + off = 0x0; + + ret_n = mc_get_area_node_by_addr(&m, 0x1337, &off); + ck_assert_ptr_null(ret_n); + ck_assert_int_eq(off, 0); + + return; + +} END_TEST + + + +//mc_get_vm_obj_by_addr [target fixture] +START_TEST(test_mc_get_vm_obj_by_addr) { + + cm_lst_node * ret_n; + off_t off; + + mc_vm_obj * o; + cm_lst_node * o_n; + + + //first test: mapped address + o_n = m.vm_objs.head->next->next; + o = MC_GET_NODE_OBJ(o_n); + + ret_n = mc_get_obj_node_by_addr(&m, o->start_addr + 0x200, &off); + ck_assert_ptr_eq(ret_n, o_n); + ck_assert_int_eq(off, 0x200); + + + //second test: unmapped address + off = 0x0; + + ret_n = mc_get_obj_node_by_addr(&m, 0x1337, &off); + ck_assert_ptr_null(ret_n); + ck_assert_int_eq(off, 0); + + return; + +} END_TEST + + + +//mc_get_obj_node_by_pathname() [target fixture] +START_TEST(test_mc_get_obj_node_by_pathname) { + + cm_lst_node * ret_n; + + mc_vm_obj * o; + cm_lst_node * o_n; + char * pathname; + + + //first test: pathname exists + o_n = m.vm_objs.head->next->next; + o = MC_GET_NODE_OBJ(o_n); + + pathname = o->pathname; + ret_n = mc_get_obj_node_by_pathname(&m, pathname); + ck_assert_ptr_eq(ret_n, o_n); + + + //second test: pathname doesn't exist + pathname = "/foo/bar"; + ret_n = mc_get_obj_node_by_pathname(&m, pathname); + ck_assert_ptr_null(ret_n); + + return; + +} END_TEST + + + +//mc_get_obj_node_by_basename() [target_fixture] +START_TEST(test_mc_get_obj_node_by_basename) { + + + cm_lst_node * ret_n; + + mc_vm_obj * o; + cm_lst_node * o_n; + char * basename; + + + //first test: pathname exists + o_n = m.vm_objs.head->next->next; + o = MC_GET_NODE_OBJ(o_n); + + basename = o->pathname; + ret_n = mc_get_obj_node_by_basename(&m, basename); + ck_assert_ptr_eq(ret_n, o_n); + + + //second test: pathname doesn't exist + basename = "1337"; + ret_n = mc_get_obj_node_by_basename(&m, basename); + ck_assert_ptr_null(ret_n); + + return; + +} END_TEST diff --git a/src/test/check_procfs_iface.c b/src/test/check_procfs_iface.c index 9576a5d..5578203 100644 --- a/src/test/check_procfs_iface.c +++ b/src/test/check_procfs_iface.c @@ -11,23 +11,20 @@ //local headers #include "suites.h" -#include "target_helper.h" +#include "iface_helper.c" //test target headers #include "../lib/memcry.h" -#include "../lib/procfs_iface.h" /* * [BASIC TEST] - * - * Procfs interface code is simple; only external functions are tested. */ - -//globals -pid_t pid; +/* + * NOTE: Unit tests for interfaces are standardised through the iface helper. + */ @@ -50,10 +47,9 @@ static void _assert_session(mc_session * se, pid_t pid) { */ //procfs_open() & procfs_close() [no fixture] -START_TEST(test_mc_open_close) { - - assert_ +START_TEST(test_procfs_mc_open_close) { + assert_iface_open_close(PROCFS, _assert_session); return; } END_TEST @@ -61,58 +57,9 @@ START_TEST(test_mc_open_close) { //procfs_update_map() [no fixture] -START_TEST(test_mc_update_map) { - - int ret; - mc_vm_map m; - - - //setup tests - pid = start_target(); - ck_assert_int_ne(pid, -1); - - ret = procfs_open(&s, pid); - ck_assert_int_eq(ret, 0); - - mc_new_vm_map(&m); - - - //first test: update empty map - ret = procfs_update_map(&s, &m); - ck_assert_int_eq(ret, 0); - - assert_target_map(pid, &m); - - - //second test: update filled map (map new areas) - change_target_map(pid); - - ret = procfs_update_map(&s, &m); - ck_assert_int_eq(ret, 0); - - assert_target_map(pid, &m); - - - //third test: update filled map (unmap old areas) - change_target_map(pid); - - ret = procfs_update_map(&s, &m); - ck_assert_int_eq(ret, 0); - - assert_target_map(pid, &m); - - - //fourth test: process exited - end_target(pid); - - ret = procfs_update_map(&s, &m); - ck_assert_int_eq(ret, -1); - - - //cleanup - ret = procfs_close(&s); - ck_assert_int_eq(ret, 0); +START_TEST(test_procfs_mc_update_map) { + assert_iface_update_map(PROCFS); return; } END_TEST @@ -120,26 +67,9 @@ START_TEST(test_mc_update_map) { //procfs_read() & procfs_write() [no fixture] -START_TEST(test_mc_read_write) { - - int ret; - mc_vm_map m; - - - //setup tests - pid = start_target(); - ck_assert_int_ne(pid, -1); - - ret = procfs_open(&s, pid); - ck_assert_int_eq(ret, 0); - - mc_new_vm_map(&m); - - - - - +START_TEST(test_procfs_mc_read_write) { + assert_iface_read_write(PROCFS); return; } END_TEST diff --git a/src/test/check_util.c b/src/test/check_util.c new file mode 100644 index 0000000..eb84fdf --- /dev/null +++ b/src/test/check_util.c @@ -0,0 +1,107 @@ +//standard library +#include +#include + +//system headers +#include + +//external libraries +#include +#include + +//local headers +#include "suites.h" +#include "target_helper.h" + +//test target headers +#include "../lib/memcry.h" + + + +/* + * [BASIC TEST] + */ + + + +//globals +mc_session s; +pid_t pid; + + +/* + * --- [HELPERS] --- + */ + + + +/* + * --- [FIXTURES] --- + */ + +//initialise the target +static void _setup_target() { + + int ret; + + + pid = start_target(); + ck_assert_int_ne(pid, -1); + + ret = mc_open(&s, PROCFS, pid); + ck_assert_int_eq(ret, 0); + + return; +} + + + +//teardown the target +static void _teardown_target() { + + int ret; + + + ret = mc_close(&s); + ck_assert_int_eq(ret, 0); + + end_target(pid); + + return; +} + + + +/* + * --- [UNIT TESTS] --- + */ + +//mc_pathname_to_basename() [target fixture] +START_TEST(test_mc_pathname_to_basename) { + + +} END_TEST + + + +//mc_pid_by_name() [target fixture] +START_TEST(test_mc_pid_by_name) { + + +} END_TEST + + + +//mc_name_by_pid() [target fixture] +START_TEST(test_mc_name_by_pid) { + + +} END_TEST + + + +//mc_bytes_to_hex() [target fixture] +START_TEST(test_mc_bytes_to_hex) { + + +} END_TEST diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c index 0e419f8..f0f6721 100644 --- a/src/test/iface_helper.c +++ b/src/test/iface_helper.c @@ -11,7 +11,7 @@ #include //local headers -#include "suites.h" +#include "info.h" #include "iface_helper.h" #include "target_helper.h" @@ -22,9 +22,9 @@ /* - * Interface helper is a generic interface tester. It requires the test suite - * of an interface to implement a custom `assert_session()` function that - * verifies the validity of an open session. + * NOTE: Interface helper is a generic interface tester. It requires the + * test suite of an interface to implement a custom `assert_session()` + * function that verifies the validity of an open session. */ @@ -92,8 +92,8 @@ void assert_iface_open_close(enum mc_iface_type iface, ret = mc_close(&s); ck_assert_int_eq(ret, 0); - return; - + + return; } @@ -157,6 +157,8 @@ void assert_iface_update_map(enum mc_iface_type iface) { ret = mc_del_vm_map(&m); ck_assert_int_eq(ret, 0); + end_target(pid); + return; } @@ -173,10 +175,9 @@ void assert_iface_read_write(enum mc_iface_type iface) { */ int ret; - ssize_t bytes; cm_byte rw_buf[16]; - uintptr_t rw_buf_addr; + uintptr_t tgt_buf_addr; pid_t pid; mc_session s; @@ -197,25 +198,26 @@ void assert_iface_read_write(enum mc_iface_type iface) { ck_assert_int_eq(ret, 0); - //first test: read & write predefined rw- segment - rw_buf_addr = _get_addr(&m, IFACE_RW_BUF_OBJ_INDEX, + //first test: read & write predefined rw- segment, seek & no seek + tgt_buf_addr = _get_addr(&m, IFACE_RW_BUF_OBJ_INDEX, IFACE_RW_BUF_AREA_INDEX, IFACE_RW_BUF_OFF); + //read foreign buffer - bytes = mc_read(&s, rw_buf_addr, rw_buf, TARGET_BUF_SZ); - ck_assert_int_ne(bytes, -1); + ret = mc_read(&s, tgt_buf_addr, rw_buf, TARGET_BUF_SZ); + ck_assert_int_eq(ret, 0); //check foreign buffer was read correctly ret = strncmp((char *) rw_buf, IFACE_RW_BUF_STR, TARGET_BUF_SZ); ck_assert_int_eq(ret, 0); - //write to foreign buffer - bytes = mc_write(&s, rw_buf_addr, (cm_byte *) w_buf, TARGET_BUF_SZ); - ck_assert_int_ne(bytes, -1); + //write to foreign buffer (first half) + ret = mc_write(&s, tgt_buf_addr, (cm_byte *) w_buf, TARGET_BUF_SZ); + ck_assert_int_eq(ret, 0); //re-read foreign buffer - bytes = mc_read(&s, rw_buf_addr, rw_buf, TARGET_BUF_SZ); - ck_assert_int_ne(bytes, -1); + ret = mc_read(&s, tgt_buf_addr, rw_buf, TARGET_BUF_SZ); + ck_assert_int_eq(ret, -1); //check the write was performed correctly ret = strncmp((char *) rw_buf, w_buf, TARGET_BUF_SZ); @@ -224,11 +226,52 @@ void assert_iface_read_write(enum mc_iface_type iface) { //second test: read & write to region with no read & write permissions + tgt_buf_addr = _get_addr(&m, IFACE_NONE_OBJ_INDEX, + IFACE_NONE_AREA_INDEX, IFACE_NONE_OFF); + + //write to foreign buffer + ret = mc_write(&s, tgt_buf_addr, (cm_byte *) w_buf, TARGET_BUF_SZ); + INFO_PRINT("[%s][no perm]: returned %d\n", + get_iface_name(iface), ret); + + //read foreign buffer + ret = mc_read(&s, tgt_buf_addr, rw_buf, TARGET_BUF_SZ); + INFO_PRINT("[%s][no perm]: returned %d\n", + get_iface_name(iface), ret); + + //check if write succeeded + ret = strncmp((char *) rw_buf, IFACE_RW_BUF_STR, TARGET_BUF_SZ); + INFO_PRINT("[%s][none perm]: returned %d\n", + get_iface_name(iface), ret); //third test: read & write to unmapped memory + tgt_buf_addr = 0x1337; + //write to foreign buffer + ret = mc_write(&s, tgt_buf_addr, (cm_byte *) w_buf, TARGET_BUF_SZ); + INFO_PRINT("[%s][unmapped]: returned %d\n", + get_iface_name(iface), ret); + + //read foreign buffer + ret = mc_read(&s, tgt_buf_addr, rw_buf, TARGET_BUF_SZ); + INFO_PRINT("[%s][unmapped]: returned %d\n", + get_iface_name(iface), ret); + + ret = strncmp((char *) rw_buf, IFACE_RW_BUF_STR, TARGET_BUF_SZ); + INFO_PRINT("[%s][none perm]: returned %d\n", + get_iface_name(iface), ret); + + + //cleanup + ret = mc_close(&s); + ck_assert_int_eq(ret, 0); + + ret = mc_del_vm_map(&m); + ck_assert_int_eq(ret, 0); + + end_target(pid); return; diff --git a/src/test/info.c b/src/test/info.c new file mode 100644 index 0000000..5d1bc77 --- /dev/null +++ b/src/test/info.c @@ -0,0 +1,18 @@ +//local includes +#include "../lib/memcry.h" + + + +//mc_iface_type names +static char * _iface_names[2] = { + "PROCFS", + "KRNCRY" +}; + + + +//convert enum to name +char * get_iface_name(enum mc_iface_type iface) { + + return _iface_names[(int) iface]; +} diff --git a/src/test/info.h b/src/test/info.h new file mode 100644 index 0000000..83adfd8 --- /dev/null +++ b/src/test/info.h @@ -0,0 +1,21 @@ +#ifndef INFO_H +#define INFO_H + +//standard library +#include + +//local includes +#include "../lib/memcry.h" + + + +#define INFO_PRINT(format, ...)\ + printf("[INFO]: " format, ##__VA_ARGS__) + + + +char * get_iface_name(enum mc_iface_type iface); + + + +#endif diff --git a/src/test/target/unit_target b/src/test/target/unit_target deleted file mode 100755 index 2700e130466e7e49edcd49732e1ea915b479e8fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18768 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 RUS0g-yQIb4E`W;_zW`tA6_@}3 diff --git a/src/test/target_helper.c b/src/test/target_helper.c index 440322e..fc63fc6 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -25,7 +25,6 @@ //globals static enum target_map_state target_state; -#define TARGET_AREAS_UNCHANGED 22 char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX] = { "", "unit_target", @@ -51,7 +50,6 @@ char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX] = { "[vdso]" }; -#define TARGET_OBJS_UNCHANGED 7 char objs_unchanged[TARGET_OBJS_UNCHANGED][NAME_MAX] = { "0x0", "unit_target", @@ -62,7 +60,7 @@ char objs_unchanged[TARGET_OBJS_UNCHANGED][NAME_MAX] = { "[vdso]" }; -#define TARGET_AREAS_MAPPED 33 + char areas_mapped[TARGET_AREAS_MAPPED][NAME_MAX] = { "", "unit_target", @@ -99,7 +97,6 @@ char areas_mapped[TARGET_AREAS_MAPPED][NAME_MAX] = { "[vdso]" }; -#define TARGET_OBJS_MAPPED 10 char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { "0x0", "unit_target", @@ -113,7 +110,7 @@ char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { "[vdso]" }; -#define TARGET_AREAS_UNMAPPED 23 + char areas_unmapped[TARGET_AREAS_UNMAPPED][NAME_MAX] = { "", "unit_target", @@ -140,7 +137,6 @@ char areas_unmapped[TARGET_AREAS_UNMAPPED][NAME_MAX] = { "[vdso]" }; -#define TARGET_OBJS_UNMAPPED 8 char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { "0x0", "unit_target", diff --git a/src/test/target_helper.h b/src/test/target_helper.h index 3e4a5fb..e6fd1f9 100644 --- a/src/test/target_helper.h +++ b/src/test/target_helper.h @@ -17,6 +17,26 @@ +#define TARGET_AREAS_UNCHANGED 22 +extern char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX]; + +#define TARGET_OBJS_UNCHANGED 7 +extern char objs_unchanged[TARGET_OBJS_UNCHANGED][NAME_MAX]; + +#define TARGET_AREAS_MAPPED 33 +extern char areas_mapped[TARGET_AREAS_MAPPED][NAME_MAX]; + +#define TARGET_OBJS_MAPPED 10 +extern char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX]; + +#define TARGET_AREAS_UNMAPPED 23 +extern char areas_unmapped[TARGET_AREAS_UNMAPPED][NAME_MAX]; + +#define TARGET_OBJS_UNMAPPED 8 +extern char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX]; + + + //the state of the target's memory map enum target_map_state { UNCHANGED, //default state @@ -28,7 +48,7 @@ enum target_map_state { //target metadata #define TARGET_PATH "target" -#define TARGET_BUF_SZ 16 +#define TARGET_BUF_SZ 16 /* must be even */ #define IFACE_RW_BUF_STR "read & write me " From 440488ee8e7eeaa786a0a6a8edfa729880554668 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Feb 2025 23:14:15 +0000 Subject: [PATCH 12/45] finish untested suite --- src/lib/memcry.h | 2 +- src/lib/util.c | 2 +- src/lib/util.h | 2 +- src/test/check_krncry_iface.c | 37 ++++++++++ src/test/check_map.c | 44 +++++------ src/test/check_map_util.c | 93 +++++++++++++++++++++++- src/test/check_procfs_iface.c | 37 ++++++++++ src/test/check_util.c | 133 +++++++++++++++++++++++++++++++++- src/test/suites.h | 1 - src/test/target_helper.h | 2 +- 10 files changed, 319 insertions(+), 34 deletions(-) diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 54d52e8..5930d85 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -147,7 +147,7 @@ typedef struct _mc_session mc_session; // [util] //return: basename = success, NULL = fail/error -extern const char * mc_pathname_to_basename(const char * pathname); +extern char * mc_pathname_to_basename(const char * pathname); //must destroy 'pid_vector' manually on success | pid = success, -1 = fail/error extern pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector); //return: 0 = success, -1 = fail/error diff --git a/src/lib/util.c b/src/lib/util.c index c14ec2d..4b3bc39 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -106,7 +106,7 @@ int _get_status_name(char * name_buf, const pid_t pid) { * --- EXTERNAL */ -const char * mc_pathname_to_basename(const char * pathname) { +char * mc_pathname_to_basename(const char * pathname) { char * basename = strrchr(pathname, (int) '/'); diff --git a/src/lib/util.h b/src/lib/util.h index f80c43b..4a55cac 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -13,7 +13,7 @@ int _get_status_name(char * name_buf, const pid_t pid); //external -const char * mc_pathname_to_basename(const char * pathname); +char * mc_pathname_to_basename(const char * pathname); int mc_pid_by_name(const char * comm, cm_vct * pid_vector); int mc_name_by_pid(const pid_t pid, char * name_buf); void mc_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); diff --git a/src/test/check_krncry_iface.c b/src/test/check_krncry_iface.c index e62a2a5..36c8b99 100644 --- a/src/test/check_krncry_iface.c +++ b/src/test/check_krncry_iface.c @@ -73,3 +73,40 @@ START_TEST(test_krncry_mc_read_write) { return; } END_TEST + + + +/* + * --- [SUITE] --- + */ + +Suite * krncry_iface_suite() { + + //test cases + TCase * tc_krncry_mc_open_close; + TCase * tc_krncry_mc_update_map; + TCase * tc_krncry_mc_read_write; + + Suite * s = suite_create("krncry_iface"); + + + //tc_krncry_mc_open_close + tc_krncry_mc_open_close = tcase_create("krncry_mc_open_close"); + tcase_add_test(tc_krncry_mc_open_close, test_krncry_mc_open_close); + + //tc_krncry_mc_update_map + tc_krncry_mc_update_map = tcase_create("krncry_mc_update_map"); + tcase_add_test(tc_krncry_mc_update_map, test_krncry_mc_update_map); + + //tc_krncry_mc_read_write + tc_krncry_mc_read_write = tcase_create("krncry_mc_read_write"); + tcase_add_test(tc_krncry_mc_read_write, test_krncry_mc_read_write); + + + //add test cases to krncry interface test suite + suite_add_tcase(s, tc_krncry_mc_open_close); + suite_add_tcase(s, tc_krncry_mc_update_map); + suite_add_tcase(s, tc_krncry_mc_read_write); + + return s; +} diff --git a/src/test/check_map.c b/src/test/check_map.c index 72aec5a..3874caf 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -1697,59 +1697,59 @@ START_TEST(test_mc_map_clean_unmapped) { Suite * s = suite_create("map"); - //tc_new_del_vm_map() + //tc_new_del_vm_map tc_new_del_vm_map = tcase_create("new_del_vm_map"); tcase_add_test(tc_new_del_vm_map, test_mc_new_del_vm_map); - //tc__make_zero_obj() + //tc__make_zero_obj tc__new_del_vm_obj = tcase_create("_new_del_vm_obj"); tcase_add_checked_fixture(tc__new_del_vm_obj, _setup_empty_vm_map, _teardown_vm_map); tcase_add_test(tc__new_del_vm_obj, test__map_new_del_vm_obj); - //tc__init_vm_area() + //tc__init_vm_area tc__init_vm_area = tcase_create("_init_vm_area"); tcase_add_checked_fixture(tc__init_vm_area, _setup_empty_vm_map, _teardown_vm_map); tcase_add_test(tc__init_vm_area, test__map_init_vm_area); - //tc__obj_add_area() + //tc__obj_add_area tc__obj_add_area = tcase_create("_obj_add_area"); tcase_add_checked_fixture(tc__obj_add_area, _setup_empty_vm_obj, _teardown_vm_obj); tcase_add_test(tc__obj_add_area, test__map_obj_add_area); - //tc__obj_add_last_area() + //tc__obj_add_last_area tc__obj_add_last_area = tcase_create("_obj_add_last_area"); tcase_add_checked_fixture(tc__obj_add_last_area, _setup_empty_vm_obj, _teardown_vm_obj); tcase_add_test(tc__obj_add_last_area, test__map_obj_add_last_area); - //tc__obj_rmv_area() + //tc__obj_rmv_area tc__obj_rmv_area = tcase_create("_obj_rmv_area"); tcase_add_checked_fixture(tc__obj_rmv_area, _setup_stub_vm_obj, _teardown_vm_obj); tcase_add_test(tc__obj_rmv_area, test__map_obj_rmv_area); - //tc__obj_rmv_last_area() + //tc__obj_rmv_last_area tc__obj_rmv_last_area = tcase_create("_obj_rmv_last_area"); tcase_add_checked_fixture(tc__obj_rmv_last_area, _setup_stub_vm_obj, _teardown_vm_obj); tcase_add_test(tc__obj_rmv_last_area, test__map_obj_rmv_area); - //tc__is_pathname_in_obj() + //tc__is_pathname_in_obj tc__is_pathname_in_obj = tcase_create("_is_pathname_in_obj"); tcase_add_checked_fixture(tc__is_pathname_in_obj, _setup_empty_vm_obj, _teardown_vm_obj); tcase_add_test(tc__is_pathname_in_obj, test__map_is_pathname_in_obj); - //tc__find_obj_for_area() + //tc__find_obj_for_area tc__find_obj_for_area = tcase_create("_find_obj_for_area"); tcase_add_checked_fixture(tc__find_obj_for_area, _setup_empty_vm_map, _teardown_vm_map); tcase_add_test(tc__find_obj_for_area, test__map_find_obj_for_area); - //tc__backtrack_unmapped_obj_last_vm_areas() + //tc__backtrack_unmapped_obj_last_vm_areas tc__backtrack_unmapped_obj_last_vm_areas = tcase_create("_backtrack_unmapped_obj_last_vm_areas"); tcase_add_checked_fixture(tc__backtrack_unmapped_obj_last_vm_areas, @@ -1757,7 +1757,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_test(tc__backtrack_unmapped_obj_last_vm_areas, test__map_backtrack_unmapped_obj_last_vm_areas); - //tc__forward_unmapped_obj_last_vm_areas() + //tc__forward_unmapped_obj_last_vm_areas tc__forward_unmapped_obj_last_vm_areas = tcase_create("_forward_unmapped_obj_last_vm_areas"); tcase_add_checked_fixture(tc__forward_unmapped_obj_last_vm_areas, @@ -1765,67 +1765,67 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_test(tc__forward_unmapped_obj_last_vm_areas, test__map_forward_unmapped_obj_last_vm_areas); - //tc__unlink_unmapped_obj() + //tc__unlink_unmapped_obj tc__unlink_unmapped_obj = tcase_create("_unlink_unmapped_obj"); tcase_add_checked_fixture(tc__unlink_unmapped_obj, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__unlink_unmapped_obj, test__map_unlink_unmapped_obj); - //tc__unlink_unmapped_area() + //tc__unlink_unmapped_area tc__unlink_unmapped_area = tcase_create("_unlink_unmapped_area"); tcase_add_checked_fixture(tc__unlink_unmapped_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__unlink_unmapped_area, test__map_unlink_unmapped_area); - //tc__check_area_eql() + //tc__check_area_eql tc__check_area_eql = tcase_create("_check_area_eql"); tcase_add_checked_fixture(tc__check_area_eql, _setup_empty_vm_map, _teardown_vm_map); tcase_add_test(tc__check_area_eql, test__map_check_area_eql); - //tc__state_inc_area() + //tc__state_inc_area tc__state_inc_area = tcase_create("_state_inc_area"); tcase_add_checked_fixture(tc__state_inc_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__state_inc_area, test__map_state_inc_area); - //tc__state_inc_obj() + //tc__state_inc_obj tc__state_inc_obj = tcase_create("_state_inc_obj"); tcase_add_checked_fixture(tc__state_inc_obj, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__state_inc_obj, test__map_state_inc_obj); - //tc__resync_area() + //tc__resync_area tc__resync_area = tcase_create("_resync_area"); tcase_add_checked_fixture(tc__resync_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__resync_area, test__map_resync_area); - //tc__add_obj() + //tc__add_obj tc__add_obj = tcase_create("_add_obj"); tcase_add_checked_fixture(tc__add_obj, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__add_obj, test__map_add_obj); - //tc__add_area() + //tc__add_area tc__add_area = tcase_create("_add_area"); tcase_add_checked_fixture(tc__add_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__add_area, test__map_add_area); - //tc_send_entry() + //tc_send_entry tc_send_entry = tcase_create("send_entry"); tcase_add_checked_fixture(tc_send_entry, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc_send_entry, test_map_send_entry); - //tc_init_traverse_state() + //tc_init_traverse_state tc_init_traverse_state = tcase_create("init_traverse_state"); tcase_add_checked_fixture(tc_init_traverse_state, _setup_empty_vm_map, _teardown_vm_map); tcase_add_test(tc_init_traverse_state, test_map_init_traverse_state); - //tc_clean_unmapped() + //tc_clean_unmapped tc_clean_unmapped = tcase_create("clean_unmapped"); tcase_add_checked_fixture(tc_clean_unmapped, _setup_stub_vm_map, _teardown_vm_map); diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index 4bc87f9..2d2d2f9 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -183,8 +183,8 @@ START_TEST(test_mc_get_obj_offset_bnd) { -//mc_get_vm_area_by_addr [target fixture] -START_TEST(test_mc_get_vm_area_by_addr) { +//mc_get_area_node_by_addr [target fixture] +START_TEST(test_mc_get_area_node_by_addr) { cm_lst_node * ret_n; off_t off; @@ -216,8 +216,8 @@ START_TEST(test_mc_get_vm_area_by_addr) { -//mc_get_vm_obj_by_addr [target fixture] -START_TEST(test_mc_get_vm_obj_by_addr) { +//mc_get_obj_node_by_addr [target fixture] +START_TEST(test_mc_get_obj_node_by_addr) { cm_lst_node * ret_n; off_t off; @@ -306,3 +306,88 @@ START_TEST(test_mc_get_obj_node_by_basename) { return; } END_TEST + + + +/* + * --- [SUITE] --- + */ + +Suite * map_util_suite() { + + //test cases + TCase * tc_get_area_offset; + TCase * tc_get_obj_offset; + TCase * tc_get_area_offset_bnd; + TCase * tc_get_obj_offset_bnd; + TCase * tc_get_area_node_by_addr; + TCase * tc_get_obj_node_by_addr; + TCase * tc_get_obj_node_by_pathname; + TCase * tc_get_obj_node_by_basename; + + Suite * s = suite_create("map_util"); + + + //tc_get_area_offset + tc_get_area_offset = tcase_create("get_area_offset"); + tcase_add_checked_fixture(tc_get_area_offset, + _setup_target, _teardown_target); + tcase_add_test(tc_get_area_offset, test_mc_get_area_offset); + + //tc_get_obj_offset + tc_get_obj_offset = tcase_create("get_obj_offset"); + tcase_add_checked_fixture(tc_get_obj_offset, + _setup_target, _teardown_target); + tcase_add_test(tc_get_obj_offset, test_mc_get_obj_offset); + + //tc_get_area_offset_bnd + tc_get_area_offset_bnd = tcase_create("get_area_offset_bnd"); + tcase_add_checked_fixture(tc_get_area_offset_bnd, + _setup_target, _teardown_target); + tcase_add_test(tc_get_area_offset_bnd, test_mc_get_area_offset_bnd); + + //tc_get_obj_offset_bnd + tc_get_obj_offset_bnd = tcase_create("get_obj_offset_bnd"); + tcase_add_checked_fixture(tc_get_obj_offset_bnd, + _setup_target, _teardown_target); + tcase_add_test(tc_get_obj_offset_bnd, test_mc_get_obj_offset_bnd); + + //tc_get_vm_area_by_addr + tc_get_area_node_by_addr = tcase_create("get_area_node_by_addr"); + tcase_add_checked_fixture(tc_get_area_node_by_addr, + _setup_target, _teardown_target); + tcase_add_test(tc_get_area_node_by_addr, test_mc_get_area_node_by_addr); + + //tc_get_vm_area_by_addr + tc_get_obj_node_by_addr = tcase_create("get_obj_node_by_addr"); + tcase_add_checked_fixture(tc_get_obj_node_by_addr, + _setup_target, _teardown_target); + tcase_add_test(tc_get_obj_node_by_addr, test_mc_get_obj_node_by_addr); + + //tc_get_obj_node_by_pathname + tc_get_obj_node_by_pathname = tcase_create("get_obj_node_by_pathname"); + tcase_add_checked_fixture(tc_get_obj_node_by_pathname, + _setup_target, _teardown_target); + tcase_add_test(tc_get_obj_node_by_pathname, + test_mc_get_obj_node_by_pathname); + + //tc_get_obj_node_by_basename + tc_get_obj_node_by_basename = tcase_create("get_obj_node_by_basename"); + tcase_add_checked_fixture(tc_get_obj_node_by_basename, + _setup_target, _teardown_target); + tcase_add_test(tc_get_obj_node_by_basename, + test_mc_get_obj_node_by_basename); + + + //add test cases to map util test suite + suite_add_tcase(s, tc_get_area_offset); + suite_add_tcase(s, tc_get_obj_offset); + suite_add_tcase(s, tc_get_area_offset_bnd); + suite_add_tcase(s, tc_get_obj_offset_bnd); + suite_add_tcase(s, tc_get_area_node_by_addr); + suite_add_tcase(s, tc_get_obj_node_by_addr); + suite_add_tcase(s, tc_get_obj_node_by_pathname); + suite_add_tcase(s, tc_get_obj_node_by_basename); + + return s; +} diff --git a/src/test/check_procfs_iface.c b/src/test/check_procfs_iface.c index 5578203..623a0b6 100644 --- a/src/test/check_procfs_iface.c +++ b/src/test/check_procfs_iface.c @@ -73,3 +73,40 @@ START_TEST(test_procfs_mc_read_write) { return; } END_TEST + + + +/* + * --- [SUITE] --- + */ + +Suite * procfs_iface_suite() { + + //test cases + TCase * tc_procfs_mc_open_close; + TCase * tc_procfs_mc_update_map; + TCase * tc_procfs_mc_read_write; + + Suite * s = suite_create("procfs_iface"); + + + //tc_procfs_mc_open_close + tc_procfs_mc_open_close = tcase_create("procfs_mc_open_close"); + tcase_add_test(tc_procfs_mc_open_close, test_procfs_mc_open_close); + + //tc_procfs_mc_update_map + tc_procfs_mc_update_map = tcase_create("procfs_mc_update_map"); + tcase_add_test(tc_procfs_mc_update_map, test_procfs_mc_update_map); + + //tc_procfs_mc_read_write + tc_procfs_mc_read_write = tcase_create("procfs_mc_read_write"); + tcase_add_test(tc_procfs_mc_read_write, test_procfs_mc_read_write); + + + //add test cases to procfs interface test suite + suite_add_tcase(s, tc_procfs_mc_open_close); + suite_add_tcase(s, tc_procfs_mc_update_map); + suite_add_tcase(s, tc_procfs_mc_read_write); + + return s; +} diff --git a/src/test/check_util.c b/src/test/check_util.c index eb84fdf..ae401f1 100644 --- a/src/test/check_util.c +++ b/src/test/check_util.c @@ -1,10 +1,13 @@ //standard library #include #include +#include //system headers #include +#include + //external libraries #include #include @@ -76,9 +79,26 @@ static void _teardown_target() { * --- [UNIT TESTS] --- */ -//mc_pathname_to_basename() [target fixture] +//mc_pathname_to_basename() [no fixture] START_TEST(test_mc_pathname_to_basename) { + char * pathname, * basename; + + + //first test: pathname is a path + pathname = "/foo/bar"; + basename = mc_pathname_to_basename(pathname); + + ck_assert_str_eq(basename, "bar"); + + + //second test: basename is not a path + pathname = "baz"; + basename = mc_pathname_to_basename(pathname); + + ck_assert_str_eq(basename, "baz"); + + return; } END_TEST @@ -87,21 +107,128 @@ START_TEST(test_mc_pathname_to_basename) { //mc_pid_by_name() [target fixture] START_TEST(test_mc_pid_by_name) { + pid_t pid; + cm_vct v; + + + //first test: target exists + pid = mc_pid_by_name(TARGET_NAME, &v); + + ck_assert_int_ne(pid, -1); + ck_assert_int_eq(v.len, 1); + + cm_del_vct(&v); + + + //second test: target does not exist + pid = mc_pid_by_name("foo", &v); + + ck_assert_int_eq(pid, -1); + ck_assert_int_eq(v.len, 0); + + cm_del_vct(&v); + + return; } END_TEST -//mc_name_by_pid() [target fixture] +//mc_name_by_pid() [no fixture] START_TEST(test_mc_name_by_pid) { + int ret; + + pid_t pid; + char name_buf[NAME_MAX]; + + + //setup test + pid = start_target(); + ck_assert_int_ne(pid, -1); + + + //first test: target exists + ret = mc_name_by_pid(pid, name_buf); + ck_assert_int_eq(ret, 0); + ck_assert_str_eq(name_buf, TARGET_NAME); + + + //second test: target does not exist + memset(name_buf, 0, NAME_MAX); + ret = mc_name_by_pid(pow(2, 32) - 1, name_buf); + ck_assert_int_eq(ret, -1); + ck_assert_str_eq(name_buf, "\0"); + + + //cleanup + end_target(pid); + + return; } END_TEST -//mc_bytes_to_hex() [target fixture] +//mc_bytes_to_hex() [no fixture] START_TEST(test_mc_bytes_to_hex) { + int ret; + + + //only test: convert ascii "foo" to hex representation + cm_byte str[3] = "foo"; + char hex_str[6] = {0}; + + mc_bytes_to_hex(str, 3, hex_str); + ret = memcmp(hex_str, "666f6f", 6); + ck_assert_int_eq(ret, 0); + + return; } END_TEST + + + +/* + * --- [SUITE] --- + */ + +Suite * util_suite() { + + //test cases + TCase * tc_pathname_to_basename; + TCase * tc_pid_by_name; + TCase * tc_name_by_pid; + TCase * tc_bytes_to_hex; + + Suite * s = suite_create("util"); + + + //tc_pathname_to_basename + tc_pathname_to_basename = tcase_create("pathname_to_basename"); + tcase_add_test(tc_pathname_to_basename, test_mc_pathname_to_basename); + + //tc_pid_by_name + tc_pid_by_name = tcase_create("pid_by_name"); + tcase_add_checked_fixture(tc_pid_by_name, _setup_target, _teardown_target); + tcase_add_test(tc_pid_by_name, test_mc_pid_by_name); + + //tc_name_by_pid + tc_name_by_pid = tcase_create("name_by_pid"); + tcase_add_checked_fixture(tc_name_by_pid, _setup_target, _teardown_target); + tcase_add_test(tc_name_by_pid, test_mc_name_by_pid); + + //tc_bytes_to_hex + tc_bytes_to_hex = tcase_create("bytes_to_hex"); + tcase_add_test(tc_bytes_to_hex, test_mc_bytes_to_hex); + + + //add test cases to util test suite + suite_add_tcase(s, tc_pathname_to_basename); + suite_add_tcase(s, tc_pid_by_name); + suite_add_tcase(s, tc_name_by_pid); + suite_add_tcase(s, tc_bytes_to_hex); + + return s; +} diff --git a/src/test/suites.h b/src/test/suites.h index 160cce2..6642fcb 100644 --- a/src/test/suites.h +++ b/src/test/suites.h @@ -6,7 +6,6 @@ //unit test suites -Suite * iface_suite(); Suite * krncry_iface_suite(); Suite * procfs_iface_suite(); Suite * map_suite(); diff --git a/src/test/target_helper.h b/src/test/target_helper.h index e6fd1f9..932025c 100644 --- a/src/test/target_helper.h +++ b/src/test/target_helper.h @@ -47,7 +47,7 @@ enum target_map_state { //target metadata -#define TARGET_PATH "target" +#define TARGET_NAME "unit_target" #define TARGET_BUF_SZ 16 /* must be even */ #define IFACE_RW_BUF_STR "read & write me " From ee45323b0de13cc752ce0d05c96eee6e91ae9eed Mon Sep 17 00:00:00 2001 From: vykt Date: Fri, 7 Feb 2025 01:24:21 +0000 Subject: [PATCH 13/45] Make test suite compile & fix build system --- Makefile | 35 ++++++++++++++++++--------------- TODO | 8 ++------ src/lib/Makefile | 8 +++++--- src/lib/iface.c | 20 +++++++++---------- src/lib/krncry_iface.c | 26 +++++++++++++++--------- src/lib/map.c | 6 +++--- src/lib/map.h | 5 ----- src/lib/map_util.c | 2 +- src/lib/memcry.h | 13 ++++++------ src/lib/procfs_iface.c | 12 ++++++++---- src/lib/util.c | 2 +- src/test/Makefile | 10 +++++++--- src/test/check_krncry_iface.c | 2 +- src/test/check_map.c | 29 ++++++++++++++++----------- src/test/check_map_util.c | 6 +++--- src/test/check_procfs_iface.c | 2 +- src/test/check_util.c | 6 +++--- src/test/iface_helper.c | 6 +++--- src/test/main.c | 37 +++++++++++++++++++++++++---------- src/test/map_helper.c | 13 ++---------- src/test/map_helper.h | 6 ++---- src/test/target/Makefile | 5 +++-- src/test/target/unit_target.c | 4 +++- src/test/target_helper.c | 10 +++------- src/test/target_helper.h | 6 +++--- 25 files changed, 152 insertions(+), 127 deletions(-) diff --git a/Makefile b/Makefile index 1f19eff..211e33e 100644 --- a/Makefile +++ b/Makefile @@ -5,34 +5,34 @@ INSTALL_DIR=/usr/local/lib INCLUDE_INSTALL_DIR=/usr/local/include MAN_INSTALL_DIR=/usr/local/share/man -MD_INSTALL_DIR=/usr/local/share/doc/lain +MD_INSTALL_DIR=/usr/local/share/doc/memcry LD_DIR=/etc/ld.so.conf.d CC=gcc CFLAGS= -CFLAGS_DBG=-ggdb -O0 +CFLAGS_TEST=-ggdb -O0 WARN_OPTS=-Wall -Wextra -LDFLAGS= +LDFLAGS=-lcmore #[build constants] -LIB_DIR="./src/lib" -TEST_DIR="./src/test" +LIB_DIR=./src/lib +TEST_DIR=./src/test DOC_DIR=./doc BUILD_DIR=${shell pwd}/build #[installation constants] -SHARED=liblain.so -STATIC=liblain.a -HEADER=lain.h +SHARED=libmcry.so +STATIC=libmcry.a +HEADER=memcry.h #[set build options] ifeq ($(build),debug) - CFLAGS += -O0 -ggdb -fsanitize=address -DDEBUG - CFLAGS_DBG += -DDEBUG - LDFLAGS += -static-libasan + CFLAGS += -O0 -ggdb -fsanitize=address -DDEBUG + CFLAGS_TEST += -DDEBUG + LDFLAGS += -static-libasan else CFLAGS += -O3 endif @@ -45,10 +45,13 @@ endif #[process targets] +.PHONY prepare: +> mkdir -p ${BUILD_DIR}/test ${BUILD_DIR}/lib + test: shared -> $(MAKE) -C ${TEST_DIR} tests CC='${CC}' _CFLAGS='${CFLAGS_DBG}' \ +> $(MAKE) -C ${TEST_DIR} tests CC='${CC}' _CFLAGS='${CFLAGS_TEST}' \ _WARN_OPTS='${WARN_OPTS}' \ - BUILD_DIR='${BUILD_DIR}/test' \ + BUILD_DIR='${BUILD_DIR}/test' \ LIB_BIN_DIR='${BUILD_DIR}/lib' all: shared static @@ -79,7 +82,7 @@ install: > cp -v ${LIB_DIR}/${HEADER} ${INCLUDE_INSTALL_DIR} > mkdir -pv ${MAN_INSTALL_DIR} > cp -Rv ${DOC_DIR}/groff/man/* ${MAN_INSTALL_DIR} -> echo "${INSTALL_DIR}" > ${LD_DIR}/90cmore.conf +> echo "${INSTALL_DIR}" > ${LD_DIR}/90memcry.conf > ldconfig install_docs: @@ -89,8 +92,8 @@ install_docs: uninstall: > -rm -v ${INSTALL_DIR}/{${SHARED},${STATIC}} > -rm -v ${INCLUDE_INSTALL_DIR}/${HEADER} -> -rm -v ${MAN_INSTALL_DIR}/man7/cmore_*.7 +> -rm -v ${MAN_INSTALL_DIR}/man7/memcry_*.7 > -rm -v ${MD_INSTALL_DIR}/*.md > -rmdir ${MD_INSTALL_DIR} -> -rm ${LD_DIR}/90cmore.conf +> -rm ${LD_DIR}/90memcry.conf > ldconfig diff --git a/TODO b/TODO index 2ad6197..64b3a6b 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,6 @@ [bugs]: --make mc_vm_area link to mc_vm_obj list nodes, instead of objects directly - -- when the map is updated, vm_area's last_obj pointers need to be intelligently - updated. this is quite difficult. implement tests to check this is handled - correctly. - +- DRY up interface read & writes, almost the same block of code repeated + 4 times in reading/writing loop. [features]: - add 3rd interface that uses process_vm_readv/process_vm_writev diff --git a/src/lib/Makefile b/src/lib/Makefile index b89aabc..5893ecf 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -18,8 +18,8 @@ SOURCES_LIB=error.c iface.c krncry_iface.c \ map.c map_util.c procfs_iface.c util.c OBJECTS_LIB=${SOURCES_LIB:%.c=${BUILD_DIR}/%.o} -SHARED=libcmore.so -STATIC=libcmore.a +SHARED=libmcry.so +STATIC=libmcry.a shared: ${SHARED} @@ -40,4 +40,6 @@ ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ clean: -> -rm -v ${BUILD_DIR}/{*.o,${SHARED},${STATIC}} +> -rm -v ${BUILD_DIR}/*.o +> -rm -v ${BUILD_DIR}/${SHARED} +> -rm -v ${BUILD_DIR}/${STATIC} diff --git a/src/lib/iface.c b/src/lib/iface.c index 71bc011..9f3bdb3 100644 --- a/src/lib/iface.c +++ b/src/lib/iface.c @@ -95,26 +95,26 @@ int mc_update_map(const mc_session * session, mc_vm_map * vm_map) { -ssize_t mc_read(const mc_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz) { +int mc_read(const mc_session * session, const uintptr_t addr, + cm_byte * buf, const size_t buf_sz) { - size_t read_bytes; + int ret; - read_bytes = session->iface.read(session, addr, buf, buf_sz); - if (read_bytes == -1) return -1; + ret = session->iface.read(session, addr, buf, buf_sz); + if (ret == -1) return -1; return 0; } -ssize_t mc_write(const mc_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz) { +int mc_write(const mc_session * session, const uintptr_t addr, + const cm_byte * buf, const size_t buf_sz) { - size_t write_bytes; + int ret; - write_bytes = session->iface.write(session, addr, buf, buf_sz); - if (write_bytes) return -1; + ret = session->iface.write(session, addr, buf, buf_sz); + if (ret == -1) return -1; return 0; } diff --git a/src/lib/krncry_iface.c b/src/lib/krncry_iface.c index fde45a9..62e5307 100644 --- a/src/lib/krncry_iface.c +++ b/src/lib/krncry_iface.c @@ -34,7 +34,7 @@ char _krncry_iface_get_major() { int fd; - size_t read_bytes; + ssize_t read_bytes; char major, read_buf[8]; @@ -122,6 +122,10 @@ int krncry_close(mc_session * session) { ioctl_call = KRNCRY_APPLY_TEMPLATE((char) session->major, KRNCRY_TEMPLATE_RELEASE_TGT); ret = ioctl(session->fd_dev_krncry, ioctl_call, &arg); + if (ret == -1) { + + return -1; + } //FIXME check this is correct //close device close(session->fd_dev_krncry); @@ -172,13 +176,13 @@ int krncry_update_map(const mc_session * session, mc_vm_map * vm_map) { //update the map with each received segment - _map_init_traverse_state(vm_map, &state); + map_init_traverse_state(&state, vm_map); for (int i = 0; i < count; ++i) { - ret = _map_send_entry(vm_map, &state, - (struct vm_entry *) - (arg.u_buf + (i * sizeof(struct vm_entry)))); + ret = map_send_entry((struct vm_entry *) + (arg.u_buf + (i * sizeof(struct vm_entry))), + &state, vm_map); if (ret) { munmap(arg.u_buf, u_buf_sz); return -1; @@ -221,13 +225,15 @@ int krncry_read(const mc_session * session, const uintptr_t addr, read_left > session->page_size ? session->page_size : read_left); //if error or EOF before reading len bytes - if (read_bytes == -1 || (read_bytes == 0 && read_done < buf_sz)) { + if (read_bytes == -1 || (read_bytes == 0 + && read_done < (ssize_t) buf_sz)) { + mc_errno = MC_ERR_READ_WRITE; return -1; } read_done += read_bytes; - } while (read_done < buf_sz); + } while (read_done < (ssize_t) buf_sz); return 0; } @@ -262,13 +268,15 @@ int krncry_write(const mc_session * session, const uintptr_t addr, write_left > session->page_size ? session->page_size : write_left); //if error or EOF before writing len bytes - if (write_bytes == -1 || (write_bytes == 0 && write_done < buf_sz)) { + if (write_bytes == -1 || (write_bytes == 0 + && write_done < (ssize_t) buf_sz)) { + mc_errno = MC_ERR_READ_WRITE; return -1; } write_done += write_bytes; - } while (write_done < buf_sz); + } while (write_done < (ssize_t) buf_sz); return 0; } diff --git a/src/lib/map.c b/src/lib/map.c index 80ad077..cd7551c 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -208,7 +208,7 @@ int _map_obj_add_area(mc_vm_obj * obj, //if this object has no areas yet - if (obj->start_addr == -1 || obj->start_addr == 0x0) { + if (obj->start_addr == MC_UNDEF_ADDR || obj->start_addr == 0x0) { //set new addr bounds obj->start_addr = area->start_addr; @@ -313,8 +313,8 @@ int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node) { DBG_STATIC DBG_INLINE int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node) { - int ret, index; - cm_lst_node * last_area_outer_node, * temp_node; + int ret; + cm_lst_node * last_area_outer_node; //remove area node from the object diff --git a/src/lib/map.h b/src/lib/map.h index ae1df66..daa45af 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -6,11 +6,6 @@ #include "krncry.h" -//TODO REMOVE -#define DEBUG - - - #define _MAP_OBJ_PREV 0 #define _MAP_OBJ_NEW 1 #define _MAP_OBJ_NEXT 2 diff --git a/src/lib/map_util.c b/src/lib/map_util.c index 5aa59d9..4826b95 100644 --- a/src/lib/map_util.c +++ b/src/lib/map_util.c @@ -48,7 +48,7 @@ cm_lst_node * _get_starting_obj(const mc_vm_map * vm_map) { vm_obj = MC_GET_NODE_OBJ(obj_node); //if pseudo object has no areas - if (vm_obj->start_addr == -1 || vm_obj->start_addr == 0x0) { + if (vm_obj->start_addr == MC_UNDEF_ADDR || vm_obj->start_addr == 0x0) { obj_node = obj_node->next; } diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 5930d85..3df86f7 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -18,7 +18,7 @@ extern "C"{ #include -//these macros take a cm_list_node pointer +//easy extract from a cm_list_node pointer #define MC_GET_NODE_AREA(node) ((mc_vm_area *) (node->data)) #define MC_GET_NODE_OBJ(node) ((mc_vm_obj *) (node->data)) #define MC_GET_NODE_PTR(node) *((cm_lst_node **) (node->data)) @@ -32,6 +32,8 @@ extern "C"{ //pseudo object id #define MC_ZERO_OBJ_ID -1 +//-Wsign-compare '-1' +#define MC_UNDEF_ADDR UINTPTR_MAX //interface types @@ -41,7 +43,6 @@ enum mc_iface_type { }; - /* * --- [DATA TYPES] --- */ @@ -112,10 +113,10 @@ typedef struct { int (*open)(struct _mc_session *, const int); int (*close)(struct _mc_session *); int (*update_map)(const struct _mc_session *, mc_vm_map *); - ssize_t (*read)(const struct _mc_session *, - const uintptr_t, cm_byte *, const size_t); - ssize_t (*write)(const struct _mc_session *, - const uintptr_t, const cm_byte *, const size_t); + int (*read)(const struct _mc_session *, + const uintptr_t, cm_byte *, const size_t); + int (*write)(const struct _mc_session *, + const uintptr_t, const cm_byte *, const size_t); } mc_iface; diff --git a/src/lib/procfs_iface.c b/src/lib/procfs_iface.c index 36eb250..0424377 100644 --- a/src/lib/procfs_iface.c +++ b/src/lib/procfs_iface.c @@ -222,13 +222,15 @@ int procfs_read(const mc_session * session, const uintptr_t addr, read_left > session->page_size ? session->page_size : read_left); //if error or EOF before reading len bytes - if (read_bytes == -1 || (read_bytes == 0 && read_done < buf_sz)) { + if (read_bytes == -1 || (read_bytes == 0 + && read_done < (ssize_t) buf_sz)) { + mc_errno = MC_ERR_READ_WRITE; return -1; } read_done += read_bytes; - } while (read_done < buf_sz); + } while (read_done < (ssize_t) buf_sz); return 0; } @@ -261,13 +263,15 @@ int procfs_write(const mc_session * session, const uintptr_t addr, write_left > session->page_size ? session->page_size : write_left); //if error or EOF before writing len bytes - if (write_bytes == -1 || (write_bytes == 0 && write_done < buf_sz)) { + if (write_bytes == -1 || (write_bytes == 0 + && write_done < (ssize_t) buf_sz)) { + mc_errno = MC_ERR_READ_WRITE; return -1; } write_done += write_bytes; - } while (write_done < buf_sz); + } while (write_done < (ssize_t) buf_sz); return 0; } diff --git a/src/lib/util.c b/src/lib/util.c index 4b3bc39..f383a06 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -110,7 +110,7 @@ char * mc_pathname_to_basename(const char * pathname) { char * basename = strrchr(pathname, (int) '/'); - if (basename == NULL) return pathname; + if (basename == NULL) return (char *) pathname; return basename + 1; } diff --git a/src/test/Makefile b/src/test/Makefile index 99c18c2..5a1e7ec 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -13,11 +13,14 @@ #[parameters] CFLAGS=${_CFLAGS} -fsanitize=address WARN_OPTS+=${_WARN_OPTS} -Wno-unused-variable -Wno-unused-but-set-variable -LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} -lcmore -lcheck -static-libasan +LDFLAGS=-L${LIB_BIN_DIR} \ + -Wl,-rpath=${LIB_BIN_DIR} -lmcry -lcmore -lcheck -lsubunit -static-libasan #[build constants] -SOURCES_TEST=main.c +SOURCES_TEST=check_map.c check_procfs_iface.c check_krncry_iface.c \ + check_map_util.c check_util.c map_helper.c target_helper.c \ + iface_helper.c info.c main.c OBJECTS_TEST=${SOURCES_TEST:%.c=${BUILD_DIR}/%.o} TARGET_DIR=${shell pwd}/target @@ -40,4 +43,5 @@ tgt: clean: > ${MAKE} -C ${TARGET_DIR} clean BUILD_DIR='${BUILD_DIR}' -> -rm -v ${BUILD_DIR}/{*.o,${TESTS}} +> -rm -v ${BUILD_DIR}/*.o +> -rm -v ${BUILD_DIR}/${TESTS} diff --git a/src/test/check_krncry_iface.c b/src/test/check_krncry_iface.c index 36c8b99..f9b5423 100644 --- a/src/test/check_krncry_iface.c +++ b/src/test/check_krncry_iface.c @@ -11,7 +11,7 @@ //local headers #include "suites.h" -#include "iface_helper.c" +#include "iface_helper.h" //test target headers #include "../lib/memcry.h" diff --git a/src/test/check_map.c b/src/test/check_map.c index 3874caf..661837c 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -968,7 +968,7 @@ START_TEST(test__map_unlink_unmapped_area) { //remove /lib/foo:1: area state struct area_check first_areas[3] = { //start index: 4 - {NULL, 0x6000, 0x7000}, + {"", 0x6000, 0x7000}, {"foo", 0x9000, 0xA000}, {"foo", 0xA000, 0xB000} }; @@ -991,7 +991,7 @@ START_TEST(test__map_unlink_unmapped_area) { //remove [heap]: area state: struct area_check second_areas[2] = { //start index: 2 {"cat", 0x3000, 0x4000}, - {NULL, 0x6000, 0x7000} + {"", 0x6000, 0x7000} }; struct area_check second_areas_unmapped[2] = { //start index: 0 @@ -1155,12 +1155,12 @@ START_TEST(test__map_state_inc_obj) { //first test: advance from pseudo object state.prev_obj_node = m.vm_objs.head; _map_state_inc_obj(&state, &m); - ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node), 0); + ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node)->id, 0); //second test: advance from regular object state.prev_obj_node = &m_a_n[0]; _map_state_inc_obj(&state, &m); - ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node), 1); + ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node)->id, 1); return; @@ -1184,7 +1184,7 @@ START_TEST(test__map_resync_area) { //remove [heap]: area state struct area_check first_areas[2] = { //start index: 2 {"cat", 0x3000, 0x4000}, - {NULL, 0x6000, 0x7000} + {"", 0x6000, 0x7000} }; struct area_check first_areas_unmapped[1] = { //start index 0 @@ -1206,9 +1206,9 @@ START_TEST(test__map_resync_area) { //remove /lib/foo:1,2: area state struct area_check second_areas[3] = { //start index 3 - {NULL, 0x6000, 0x7000}, + {"", 0x6000, 0x7000}, {"foo", 0xA000, 0xB000}, - {NULL, 0xC000, 0xD000} + {"", 0xC000, 0xD000} }; struct area_check second_areas_unmapped[3] = { //start index 0 @@ -1232,7 +1232,7 @@ START_TEST(test__map_resync_area) { //remove /bin/cat:1,2,3: area state struct area_check third_areas[2] = { //start index: 0 - {NULL, 0x6000, 0x7000}, + {"", 0x6000, 0x7000}, {"foo", 0xA000, 0xB000} }; @@ -1692,7 +1692,7 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc_send_entry; TCase * tc_init_traverse_state; TCase * tc_clean_unmapped; - #endif + #endif Suite * s = suite_create("map"); @@ -1701,12 +1701,19 @@ START_TEST(test_mc_map_clean_unmapped) { tc_new_del_vm_map = tcase_create("new_del_vm_map"); tcase_add_test(tc_new_del_vm_map, test_mc_new_del_vm_map); - //tc__make_zero_obj + #ifdef DEBUG + //tc__new_del_vm_obj tc__new_del_vm_obj = tcase_create("_new_del_vm_obj"); tcase_add_checked_fixture(tc__new_del_vm_obj, _setup_empty_vm_map, _teardown_vm_map); tcase_add_test(tc__new_del_vm_obj, test__map_new_del_vm_obj); + //tc__make_zero_obj + tc__make_zero_obj = tcase_create("_make_zero_obj"); + tcase_add_checked_fixture(tc__make_zero_obj, + _setup_empty_vm_map, _teardown_vm_map); + tcase_add_test(tc__make_zero_obj, test__map_make_zero_obj); + //tc__init_vm_area tc__init_vm_area = tcase_create("_init_vm_area"); tcase_add_checked_fixture(tc__init_vm_area, @@ -1830,7 +1837,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_checked_fixture(tc_clean_unmapped, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc_clean_unmapped, test_mc_map_clean_unmapped); - + #endif //add test cases to map test suite suite_add_tcase(s, tc_new_del_vm_map); diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index 2d2d2f9..5eb99c0 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -25,9 +25,9 @@ //globals -mc_vm_map m; -mc_session s; -pid_t pid; +static mc_vm_map m; +static mc_session s; +static pid_t pid; diff --git a/src/test/check_procfs_iface.c b/src/test/check_procfs_iface.c index 623a0b6..5b6f42f 100644 --- a/src/test/check_procfs_iface.c +++ b/src/test/check_procfs_iface.c @@ -11,7 +11,7 @@ //local headers #include "suites.h" -#include "iface_helper.c" +#include "iface_helper.h" //test target headers #include "../lib/memcry.h" diff --git a/src/test/check_util.c b/src/test/check_util.c index ae401f1..d4f49e7 100644 --- a/src/test/check_util.c +++ b/src/test/check_util.c @@ -28,8 +28,8 @@ //globals -mc_session s; -pid_t pid; +static mc_session s; +static pid_t pid; /* @@ -156,7 +156,7 @@ START_TEST(test_mc_name_by_pid) { //second test: target does not exist memset(name_buf, 0, NAME_MAX); - ret = mc_name_by_pid(pow(2, 32) - 1, name_buf); + ret = mc_name_by_pid((int) (pow(2, 32)) - 1, name_buf); ck_assert_int_eq(ret, -1); ck_assert_str_eq(name_buf, "\0"); diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c index f0f6721..bd9378c 100644 --- a/src/test/iface_helper.c +++ b/src/test/iface_helper.c @@ -122,7 +122,7 @@ void assert_iface_update_map(enum mc_iface_type iface) { ret = mc_update_map(&s, &m); ck_assert_int_eq(ret, 0); - assert_target_map(pid, &m); + assert_target_map(&m); //second test: update filled map (map new areas) @@ -131,7 +131,7 @@ void assert_iface_update_map(enum mc_iface_type iface) { ret = mc_update_map(&s, &m); ck_assert_int_eq(ret, 0); - assert_target_map(pid, &m); + assert_target_map(&m); //third test: update filled map (unmap old areas) @@ -140,7 +140,7 @@ void assert_iface_update_map(enum mc_iface_type iface) { ret = mc_update_map(&s, &m); ck_assert_int_eq(ret, 0); - assert_target_map(pid, &m); + assert_target_map(&m); //fourth test: process exited diff --git a/src/test/main.c b/src/test/main.c index 2680438..ddfb935 100644 --- a/src/test/main.c +++ b/src/test/main.c @@ -1,5 +1,6 @@ //standard library #include +#include //system headers #include @@ -13,13 +14,14 @@ -enum _test_mode {UNIT}; +enum _test_mode {UNIT, EXPL}; //determine which tests to run static enum _test_mode _get_test_mode(int argc, char ** argv) { const struct option long_opts[] = { {"unit-tests", no_argument, NULL, 'u'}, + {"explore", no_argument, NULL, 'e'}, {0,0,0,0} }; @@ -27,7 +29,7 @@ static enum _test_mode _get_test_mode(int argc, char ** argv) { enum _test_mode test_mode = UNIT; - while((opt = getopt_long(argc, argv, "u", long_opts, NULL)) != -1 + while((opt = getopt_long(argc, argv, "ue", long_opts, NULL)) != -1 && opt != 0) { //determine parsed argument @@ -36,6 +38,10 @@ static enum _test_mode _get_test_mode(int argc, char ** argv) { case 'u': test_mode = UNIT; break; + + case 'e': + test_mode = EXPL; + break; } } @@ -47,21 +53,28 @@ static enum _test_mode _get_test_mode(int argc, char ** argv) { //run unit tests static void _run_unit_tests() { - Suite * s_iface; - Suite * s_krncry_iface; - Suite * s_procfs_iface; Suite * s_map; + Suite * s_procfs_iface; + //Suite * s_krncry_iface; Suite * s_map_util; - Suite * util; + Suite * s_util; SRunner * sr; + //initialise test suites - // TODO s_test1 = test1_suite(); - + s_map = map_suite(); + s_procfs_iface = procfs_iface_suite(); + //s_krncry_iface = krncry_iface_suite(); + s_map_util = map_util_suite(); + s_util = util_suite(); + //create suite runner - //sr = srunner_create(s_test1); - //srunner_add_suite(sr, s_test2); + sr = srunner_create(s_map); + srunner_add_suite(sr, s_procfs_iface); + //srunner_add_suite(sr, s_krncry_iface); + srunner_add_suite(sr, s_map_util); + srunner_add_suite(sr, s_util); //run tests srunner_run_all(sr, CK_VERBOSE); @@ -84,6 +97,10 @@ int main(int argc, char ** argv) { case UNIT: _run_unit_tests(); break; + + case EXPL: + fprintf(stderr, "[ERR] `-e, --expore` not implemented.\n"); + break; } return 0; diff --git a/src/test/map_helper.c b/src/test/map_helper.c index 0671c70..58a28b1 100644 --- a/src/test/map_helper.c +++ b/src/test/map_helper.c @@ -31,7 +31,6 @@ void create_lst_wrapper(cm_lst_node * node, void * data) { } - //assert the length of a list, also works as an integration test for CMore void assert_lst_len(cm_lst * list, int len) { @@ -62,7 +61,6 @@ void assert_lst_len(cm_lst * list, int len) { } - //basic assertion of state for a mc_vm_map void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, int vm_areas_unmapped_len, int vm_objs_unmapped_len, @@ -84,7 +82,6 @@ void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, } - //assert the state of all [unmapped] objects inside a mc_vm_map void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, int start_index, int len) { @@ -105,9 +102,8 @@ void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, } - //assert only pathnames, not mapped address ranges -void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[PATH_MAX], +void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[NAME_MAX], int start_index, int len) { mc_vm_obj * obj; @@ -124,7 +120,6 @@ void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[PATH_MAX], } - //assert the state of all [unmapped] memory areas inside a mc_vm_map void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, int start_index, int len) { @@ -145,9 +140,8 @@ void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, } - //assert only pathnames, not mapped address ranges -void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[PATH_MAX], +void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[NAME_MAX], int start_index, int len) { mc_vm_area * area; @@ -164,7 +158,6 @@ void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[PATH_MAX], } - void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, uintptr_t start_addr, uintptr_t end_addr, int vm_areas_len, int last_vm_areas_len, int id, bool mapped) { @@ -191,7 +184,6 @@ void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, } - /* * Check state of the object by checking the starting addresses of each of * its constituent areas. @@ -224,7 +216,6 @@ void assert_vm_obj_list(cm_lst * outer_node_lst, } - void assert_vm_area(mc_vm_area * area, char * pathname, char * basename, uintptr_t start_addr, uintptr_t end_addr, cm_byte access, cm_lst_node * obj_node_p, diff --git a/src/test/map_helper.h b/src/test/map_helper.h index 056cee1..9616eba 100644 --- a/src/test/map_helper.h +++ b/src/test/map_helper.h @@ -26,7 +26,6 @@ struct obj_check { }; - struct area_check { char basename[NAME_MAX]; @@ -35,7 +34,6 @@ struct area_check { }; - //map helper functions void create_lst_wrapper(cm_lst_node * node, void * data); void assert_lst_len(cm_lst * list, int len); @@ -46,12 +44,12 @@ void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, int start_index, int len); -void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[PATH_MAX], +void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[NAME_MAX], int start_index, int len); void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, int start_index, int len); -void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[PATH_MAX], +void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[NAME_MAX], int start_index, int len); void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, diff --git a/src/test/target/Makefile b/src/test/target/Makefile index 7539c62..7d49a11 100644 --- a/src/test/target/Makefile +++ b/src/test/target/Makefile @@ -14,7 +14,7 @@ SOURCES_MANUAL_TARGET=manual_target.c OBJECTS_MANUAL_TARGET=${SOURCES_MANUAL_TARGET:%.c=${BUILD_DIR}/%.o} SOURCES_UNIT_TARGET=unit_target.c -OBJECTS_UNIT_TARGET=${SOURCES_UNIT_TARGET}:%.c=${BUILD_IR}/%.o} +OBJECTS_UNIT_TARGET=${SOURCES_UNIT_TARGET:%.c=${BUILD_DIR}/%.o} MANUAL_TARGET=manual_target UNIT_TARGET=unit_target @@ -36,4 +36,5 @@ ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ clean: -> -rm -v ${BUILD_DIR}/{*.o,${MANUAL_TARGET},${UNIT_TARGET}} +> -rm -v ${BUILD_DIR}/${MANUAL_TARGET} +> -rm -v ${BUILD_DIR}/${UNIT_TARGET} diff --git a/src/test/target/unit_target.c b/src/test/target/unit_target.c index 9c9a5cf..92f4d83 100644 --- a/src/test/target/unit_target.c +++ b/src/test/target/unit_target.c @@ -45,7 +45,7 @@ enum target_map_state state = UNCHANGED; //unit test signal handler -void sigusr1_handler(int signum) { +void sigusr1_handler() { if (state == UNCHANGED) { @@ -69,6 +69,8 @@ int main(int argc, char ** argv) { pid_t parent_pid; void * protected_area; + //check correct number of args is provided (quiet -Wunused-parameter) + if (argc != 2) return -1; //recover parent pid parent_pid = atoi(argv[1]); diff --git a/src/test/target_helper.c b/src/test/target_helper.c index fc63fc6..a64914e 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -175,7 +175,7 @@ pid_t start_target() { pid_t target_pid, parent_pid; char pid_buf[8]; - char * argv[3] = {TARGET_PATH, 0, 0}; + char * argv[3] = {TARGET_NAME, 0, 0}; target_state = UNCHANGED; @@ -191,7 +191,7 @@ pid_t start_target() { //change image to target in child if (target_pid == 0) { - ret = execve(TARGET_PATH, argv, NULL); + ret = execve(TARGET_NAME, argv, NULL); ck_assert_int_ne(ret, -1); //if parent, register signal handler for child @@ -253,12 +253,11 @@ void change_target_map(pid_t pid) { -void assert_target_map(pid_t pid, mc_vm_map * map) { +void assert_target_map(mc_vm_map * map) { switch(target_state) { case UNCHANGED: - assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unchanged, 0, TARGET_AREAS_UNCHANGED); assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unchanged, @@ -266,7 +265,6 @@ void assert_target_map(pid_t pid, mc_vm_map * map) { break; case MAPPED: - assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_mapped, 0, TARGET_AREAS_MAPPED); assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_mapped, @@ -274,13 +272,11 @@ void assert_target_map(pid_t pid, mc_vm_map * map) { break; case UNMAPPED: - assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unmapped, 0, TARGET_AREAS_UNMAPPED); assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unmapped, 0, TARGET_OBJS_UNMAPPED); break; - } //end switch diff --git a/src/test/target_helper.h b/src/test/target_helper.h index 932025c..c8d5aec 100644 --- a/src/test/target_helper.h +++ b/src/test/target_helper.h @@ -1,5 +1,5 @@ -#ifndef TARGET_H -#define TARGET_H +#ifndef TARGET_HELPER_H +#define TARGET_HELPER_H //system headers #include @@ -66,7 +66,7 @@ enum target_map_state { pid_t start_target(); void end_target(pid_t pid); void change_target_map(pid_t pid); -void assert_target_map(pid_t pid, mc_vm_map * map); +void assert_target_map(mc_vm_map * map); #endif From 9b805bd0c43a31fea6b6b379b6f3ca1784cb974c Mon Sep 17 00:00:00 2001 From: vykt Date: Fri, 14 Feb 2025 03:08:36 +0000 Subject: [PATCH 14/45] Change formatting & debug tests --- Makefile | 4 +- build/test/debug.sh | 2 + build/test/init.gdb | 67 +++++++++++++++++ src/lib/error.c | 6 +- src/lib/iface.c | 5 -- src/lib/krncry.h | 3 - src/lib/krncry_iface.c | 4 - src/lib/map.c | 33 ++------ src/lib/map.h | 2 +- src/lib/map_util.c | 11 --- src/lib/map_util.h | 1 - src/lib/memcry.h | 6 +- src/lib/procfs_iface.c | 4 - src/lib/util.c | 4 - src/test/check_krncry_iface.c | 2 - src/test/check_map.c | 128 +++++++++++++------------------- src/test/check_map_util.c | 9 --- src/test/check_procfs_iface.c | 2 - src/test/check_util.c | 9 --- src/test/iface_helper.c | 4 - src/test/iface_helper.h | 2 - src/test/info.c | 2 - src/test/info.h | 4 - src/test/main.c | 22 +++--- src/test/map_helper.c | 41 +++++++--- src/test/map_helper.h | 2 +- src/test/target/manual_target.c | 5 -- src/test/target/unit_target.c | 5 -- src/test/target_helper.c | 6 -- src/test/target_helper.h | 5 -- 30 files changed, 172 insertions(+), 228 deletions(-) create mode 100755 build/test/debug.sh create mode 100644 build/test/init.gdb diff --git a/Makefile b/Makefile index 211e33e..df6c2f5 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ LD_DIR=/etc/ld.so.conf.d CC=gcc CFLAGS= -CFLAGS_TEST=-ggdb -O0 +CFLAGS_TEST=-ggdb3 -O0 WARN_OPTS=-Wall -Wextra LDFLAGS=-lcmore @@ -30,7 +30,7 @@ HEADER=memcry.h #[set build options] ifeq ($(build),debug) - CFLAGS += -O0 -ggdb -fsanitize=address -DDEBUG + CFLAGS += -O0 -ggdb3 -fsanitize=address -DDEBUG CFLAGS_TEST += -DDEBUG LDFLAGS += -static-libasan else diff --git a/build/test/debug.sh b/build/test/debug.sh new file mode 100755 index 0000000..7ae82f9 --- /dev/null +++ b/build/test/debug.sh @@ -0,0 +1,2 @@ +#!/bin/sh +gdb -x init.gdb --args ./test "$@" diff --git a/build/test/init.gdb b/build/test/init.gdb new file mode 100644 index 0000000..0123e2d --- /dev/null +++ b/build/test/init.gdb @@ -0,0 +1,67 @@ +set environment CK_FORK=no + +# print vm_area at a node +define parean + if $argc != 1 + printf "Use: parean <*area_node>\n" + else + p *((mc_vm_area *) ($arg0->data)) + end +end + +# hex print vm_area at a node +define pxarean + if $argc != 1 + printf "Use: parean <*area_node>\n" + else + p/x *((mc_vm_area *) ($arg0->data)) + end +end + +# print vm_obj at a node +define pobjn + if $argc != 1 + printf "Use: pobjn <*obj_node>\n" + else + p *((mc_vm_obj *) ($arg0->data)) + end +end + +# hex print vm_obj at a node +define pxobjn + if $argc != 1 + printf "Use: pobjn <*obj_node>\n" + else + p/x *((mc_vm_obj *) ($arg0->data)) + end +end + +# print a node at the specified node +define pnode + if $argc != 1 + printf "Use: pnode <*obj_area_node>\n" + else + p *(*((cm_lst_node **) ($arg0->data))) + end +end + +# print the area at a node at the specified node: +define pnodea + if $argc != 1 + printf "Use: pnodea <*obj_area_node>\n" + else + p *((mc_vm_area *) ((*((cm_lst_node **) ($arg0->data)))->data)) + end +end + +# hex print the are aat a node at the specified node: +define pxnodea + if $argc != 1 + printf "Use: pnodea <*obj_area_node>\n" + else + p/x *((mc_vm_area *) ((*((cm_lst_node **) ($arg0->data)))->data)) + end +end + +tb test__map_obj_add_last_area_fn +run diff --git a/src/lib/error.c b/src/lib/error.c index c4d9ef0..3f7b1ae 100644 --- a/src/lib/error.c +++ b/src/lib/error.c @@ -10,14 +10,13 @@ __thread int mc_errno; /* - * TODO: Find a metaprogramming way to do this. + * TODO: Find a better way to do this. */ void mc_perror(const char * prefix) { switch(mc_errno) { - // 1XX - user errors case MC_ERR_PROC_MEM: fprintf(stderr, "%s: %s", prefix, MC_ERR_PROC_MEM_MSG); @@ -72,7 +71,6 @@ void mc_perror(const char * prefix) { fprintf(stderr, "%s: %s", prefix, MC_ERR_PROC_NAV_MSG); break; - // 3XX - environmental errors case MC_ERR_MEM: fprintf(stderr, "%s: %s", prefix, MC_ERR_MEM_MSG); @@ -99,11 +97,9 @@ void mc_perror(const char * prefix) { } - const char * mc_strerror(const int mc_errnum) { switch (mc_errnum) { - // 1XX - user errors case MC_ERR_PROC_MEM: return MC_ERR_PROC_MEM_MSG; diff --git a/src/lib/iface.c b/src/lib/iface.c index 9f3bdb3..23d833d 100644 --- a/src/lib/iface.c +++ b/src/lib/iface.c @@ -31,7 +31,6 @@ void _set_procfs_session(mc_session * session) { } - DBG_STATIC DBG_INLINE void _set_krncry_session(mc_session * session) { @@ -70,7 +69,6 @@ int mc_open(mc_session * session, } - int mc_close(mc_session * session) { int ret; @@ -82,7 +80,6 @@ int mc_close(mc_session * session) { } - int mc_update_map(const mc_session * session, mc_vm_map * vm_map) { int ret; @@ -94,7 +91,6 @@ int mc_update_map(const mc_session * session, mc_vm_map * vm_map) { } - int mc_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { @@ -107,7 +103,6 @@ int mc_read(const mc_session * session, const uintptr_t addr, } - int mc_write(const mc_session * session, const uintptr_t addr, const cm_byte * buf, const size_t buf_sz) { diff --git a/src/lib/krncry.h b/src/lib/krncry.h index c8b6eb3..95e1c34 100644 --- a/src/lib/krncry.h +++ b/src/lib/krncry.h @@ -9,7 +9,6 @@ extern "C" { #include - /* * This header comes from the krncry project. */ @@ -43,7 +42,6 @@ typedef unsigned long krncry_pgprot_t; #define VM_SHARED 0x00000008 - /* * --- [DATA TYPES] --- */ @@ -52,7 +50,6 @@ typedef unsigned long krncry_pgprot_t; typedef unsigned char kc_byte; - // [ioctl argument] struct ioctl_arg { kc_byte * u_buf; diff --git a/src/lib/krncry_iface.c b/src/lib/krncry_iface.c index 62e5307..0179188 100644 --- a/src/lib/krncry_iface.c +++ b/src/lib/krncry_iface.c @@ -110,7 +110,6 @@ int krncry_open(mc_session * session, const pid_t pid) { } - int krncry_close(mc_session * session) { int ret; @@ -134,7 +133,6 @@ int krncry_close(mc_session * session) { } - int krncry_update_map(const mc_session * session, mc_vm_map * vm_map) { int ret, count; @@ -196,7 +194,6 @@ int krncry_update_map(const mc_session * session, mc_vm_map * vm_map) { } - int krncry_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { @@ -239,7 +236,6 @@ int krncry_read(const mc_session * session, const uintptr_t addr, } - int krncry_write(const mc_session * session, const uintptr_t addr, const cm_byte * buf, const size_t buf_sz) { diff --git a/src/lib/map.c b/src/lib/map.c index cd7551c..118ba00 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -66,7 +66,6 @@ void _map_init_vm_area(mc_vm_area * area, const struct vm_entry * entry, } - /* * Note that while the vm_obj constructor does take a vm_map pointer as a * parameter, the vm_obj is not added to the vm_map in the constructor. Only @@ -99,7 +98,6 @@ void _map_new_vm_obj(mc_vm_obj * obj, } - DBG_STATIC void _map_del_vm_obj(mc_vm_obj * obj) { @@ -110,7 +108,6 @@ void _map_del_vm_obj(mc_vm_obj * obj) { } - DBG_STATIC DBG_INLINE void _map_make_zero_obj(mc_vm_obj * obj) { @@ -120,12 +117,11 @@ void _map_make_zero_obj(mc_vm_obj * obj) { } - DBG_STATIC int _map_obj_add_area_insert(cm_lst * obj_area_lst, const cm_lst_node * area_node) { - cm_lst_node * ret_node, * iter_node; + cm_lst_node * ret_node, * iter_node, * inner_node; mc_vm_area * area = MC_GET_NODE_AREA(area_node); //if list is empty, append @@ -140,8 +136,11 @@ int _map_obj_add_area_insert(cm_lst * obj_area_lst, iter_node = obj_area_lst->head; for (int i = 0; i < obj_area_lst->len; ++i) { + //get area node that iter_node points to + inner_node = MC_GET_NODE_PTR(iter_node); + //new area ends at a lower address than some existing area - if (area->end_addr < MC_GET_NODE_AREA(iter_node)->end_addr) { + if (area->end_addr < MC_GET_NODE_AREA(inner_node)->end_addr) { ret_node = cm_lst_ins_nb(obj_area_lst, iter_node, &area_node); break; @@ -170,7 +169,6 @@ int _map_obj_add_area_insert(cm_lst * obj_area_lst, } - DBG_STATIC cm_lst_node * _map_obj_find_area_outer_node(cm_lst * obj_area_lst, cm_lst_node * area_node) { @@ -198,7 +196,7 @@ cm_lst_node * _map_obj_find_area_outer_node(cm_lst * obj_area_lst, } - +// This function only updates the vm_obj structure itself, not the area list DBG_STATIC DBG_INLINE int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node) { @@ -234,7 +232,6 @@ int _map_obj_add_area(mc_vm_obj * obj, } - DBG_STATIC DBG_INLINE int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node) { @@ -249,7 +246,6 @@ int _map_obj_add_last_area(mc_vm_obj * obj, } - DBG_STATIC DBG_INLINE int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node) { @@ -304,12 +300,10 @@ int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node) { obj->end_addr = MC_GET_NODE_AREA(temp_node)->end_addr; - return 0; } - DBG_STATIC DBG_INLINE int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node) { @@ -336,7 +330,6 @@ int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node) { } - DBG_STATIC bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { @@ -348,7 +341,6 @@ bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { } - DBG_STATIC DBG_INLINE int _map_find_obj_for_area(const struct vm_entry * entry, const _traverse_state * state) { @@ -380,7 +372,6 @@ int _map_find_obj_for_area(const struct vm_entry * entry, } - //called when deleting an object; moves `last_obj_node_p` back if needed DBG_STATIC DBG_INLINE int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { @@ -427,7 +418,6 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { } - //called when adding an object; moves `last_obj_node_p` forward if needed DBG_STATIC DBG_INLINE int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { @@ -489,7 +479,6 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { } - DBG_STATIC DBG_INLINE int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { @@ -539,7 +528,6 @@ int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { } - DBG_STATIC DBG_INLINE int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map) { @@ -611,7 +599,6 @@ int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map) { } - DBG_STATIC DBG_INLINE int _map_check_area_eql(const struct vm_entry * entry, const cm_lst_node * area_node) { @@ -633,7 +620,6 @@ int _map_check_area_eql(const struct vm_entry * entry, } - DBG_STATIC void _map_state_inc_area(_traverse_state * state, const int inc_type, const cm_lst_node * assign_node, mc_vm_map * map) { @@ -669,7 +655,6 @@ void _map_state_inc_area(_traverse_state * state, const int inc_type, } - DBG_STATIC void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map) { @@ -692,7 +677,6 @@ void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map) { } - DBG_STATIC DBG_INLINE int _map_resync_area(const struct vm_entry * entry, _traverse_state * state, mc_vm_map * map) { @@ -728,7 +712,6 @@ int _map_resync_area(const struct vm_entry * entry, } - DBG_STATIC cm_lst_node * _map_add_obj(const struct vm_entry * entry, _traverse_state * state, mc_vm_map * map) { @@ -763,7 +746,6 @@ cm_lst_node * _map_add_obj(const struct vm_entry * entry, } - DBG_STATIC int _map_add_area(const struct vm_entry * entry, _traverse_state * state, mc_vm_map * map) { @@ -897,7 +879,6 @@ int map_send_entry(const struct vm_entry * entry, } - void map_init_traverse_state(_traverse_state * state, const mc_vm_map * map) { state->next_area_node = map->vm_areas.head; @@ -937,7 +918,6 @@ void mc_new_vm_map(mc_vm_map * map) { } - int mc_del_vm_map(mc_vm_map * map) { int ret, len_obj; @@ -977,7 +957,6 @@ int mc_del_vm_map(mc_vm_map * map) { return 0; } - int mc_map_clean_unmapped(mc_vm_map * map) { diff --git a/src/lib/map.h b/src/lib/map.h index daa45af..4a07f68 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -14,6 +14,7 @@ #define _STATE_AREA_NODE_ADVANCE 1 #define _STATE_AREA_NODE_REASSIGN 2 + /* * Initialise _traverse_state manually on the first call to * send_map_node() for a map generated by a memory interface. @@ -89,5 +90,4 @@ void mc_new_vm_map(mc_vm_map * map); int mc_del_vm_map(mc_vm_map * map); int mc_map_clean_unmapped(mc_vm_map * map); - #endif diff --git a/src/lib/map_util.c b/src/lib/map_util.c index 4826b95..fbd599b 100644 --- a/src/lib/map_util.c +++ b/src/lib/map_util.c @@ -32,7 +32,6 @@ bool _is_map_empty(const mc_vm_map * vm_map) { } - /* * Determines starting object for iteration. * Will skip the pseudo-object if it is empty. @@ -56,7 +55,6 @@ cm_lst_node * _get_starting_obj(const mc_vm_map * vm_map) { } - DBG_STATIC DBG_INLINE cm_lst_node * _get_obj_last_area(const mc_vm_obj * vm_obj) { @@ -75,7 +73,6 @@ cm_lst_node * _get_obj_last_area(const mc_vm_obj * vm_obj) { } - DBG_STATIC cm_lst_node * _fast_addr_find(const mc_vm_map * vm_map, const uintptr_t addr, const int mode) { @@ -158,7 +155,6 @@ cm_lst_node * _fast_addr_find(const mc_vm_map * vm_map, } - DBG_STATIC cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, const char * name, const int mode) { @@ -214,7 +210,6 @@ off_t mc_get_area_offset(const cm_lst_node * area_node, } - off_t mc_get_obj_offset(const cm_lst_node * obj_node, const uintptr_t addr) { @@ -224,7 +219,6 @@ off_t mc_get_obj_offset(const cm_lst_node * obj_node, } - off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, const uintptr_t addr) { @@ -235,7 +229,6 @@ off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, } - off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, const uintptr_t addr) { @@ -246,7 +239,6 @@ off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, } - cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset) { @@ -262,7 +254,6 @@ cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, } - cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset) { @@ -278,7 +269,6 @@ cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, } - cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, const char * pathname) { @@ -291,7 +281,6 @@ cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, } - cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, const char * basename) { diff --git a/src/lib/map_util.h b/src/lib/map_util.h index f1b9176..544e27e 100644 --- a/src/lib/map_util.h +++ b/src/lib/map_util.h @@ -42,5 +42,4 @@ cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, const char * basename); - #endif diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 3df86f7..3086088 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -71,9 +71,6 @@ typedef struct { // [backing object] typedef struct { - char pathname[PATH_MAX]; - char basename[NAME_MAX]; - uintptr_t start_addr; uintptr_t end_addr; @@ -83,6 +80,9 @@ typedef struct { int id; bool mapped; //set to false when a map update discovers obj. to be unmapped + char basename[NAME_MAX]; + char pathname[PATH_MAX]; + } mc_vm_obj; diff --git a/src/lib/procfs_iface.c b/src/lib/procfs_iface.c index 0424377..c23f6a7 100644 --- a/src/lib/procfs_iface.c +++ b/src/lib/procfs_iface.c @@ -142,7 +142,6 @@ int procfs_open(mc_session * session, const int pid) { } - int procfs_close(mc_session * session) { //close procfs mem file @@ -152,7 +151,6 @@ int procfs_close(mc_session * session) { } - int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { int ret; @@ -195,7 +193,6 @@ int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { } - int procfs_read(const mc_session * session, const uintptr_t addr, cm_byte * buf, const size_t buf_sz) { @@ -236,7 +233,6 @@ int procfs_read(const mc_session * session, const uintptr_t addr, } - int procfs_write(const mc_session * session, const uintptr_t addr, const cm_byte * buf, const size_t buf_sz) { diff --git a/src/lib/util.c b/src/lib/util.c index f383a06..12b446c 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -48,7 +48,6 @@ void _line_to_name(const char * line_buf, char * name_buf) { } - /* * Use `/proc/pid/status` to get the name of a process. This is * how utilities like `ps` and `top` fetch process names. @@ -115,7 +114,6 @@ char * mc_pathname_to_basename(const char * pathname) { } - pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { int ret; @@ -202,7 +200,6 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { } - int mc_name_by_pid(const pid_t pid, char * name_buf) { int ret; @@ -215,7 +212,6 @@ int mc_name_by_pid(const pid_t pid, char * name_buf) { } - void mc_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out) { cm_byte nibble; diff --git a/src/test/check_krncry_iface.c b/src/test/check_krncry_iface.c index f9b5423..4a7618e 100644 --- a/src/test/check_krncry_iface.c +++ b/src/test/check_krncry_iface.c @@ -55,7 +55,6 @@ START_TEST(test_krncry_mc_open_close) { } END_TEST - //krncry_update_map() [no fixture] START_TEST(test_krncry_mc_update_map) { @@ -65,7 +64,6 @@ START_TEST(test_krncry_mc_update_map) { } END_TEST - //krncry_read() & krncry_write() [no fixture] START_TEST(test_krncry_mc_read_write) { diff --git a/src/test/check_map.c b/src/test/check_map.c index 661837c..341a491 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -52,7 +52,6 @@ */ - //globals - map /* @@ -100,7 +99,6 @@ static void _init__traverse_state(_traverse_state * state, } - //initialise a vm_entry stub static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, unsigned long vm_end, unsigned long file_off, @@ -263,7 +261,6 @@ static void _setup_stub_vm_map() { #endif - static void _teardown_vm_map() { mc_del_vm_map(&m); @@ -290,7 +287,6 @@ static void _setup_empty_vm_obj() { #endif - #ifdef DEBUG //stub object fixture static void _setup_stub_vm_obj() { @@ -341,7 +337,6 @@ static void _setup_stub_vm_obj() { #endif - #ifdef DEBUG static void _teardown_vm_obj() { @@ -388,7 +383,6 @@ START_TEST(test_mc_new_del_vm_map) { } END_TEST - #ifdef DEBUG //_map_new_vm_obj() & _map_del_vm_obj() [empty map fixture] START_TEST(test__map_new_del_vm_obj) { @@ -405,7 +399,6 @@ START_TEST(test__map_new_del_vm_obj) { } - //_map_make_zero_obj() [empty map fixture] START_TEST(test__map_make_zero_obj) { @@ -418,9 +411,9 @@ START_TEST(test__map_make_zero_obj) { //only test: convert new object to pseudo object _map_make_zero_obj(&zero_obj); - assert_vm_obj(&zero_obj, - "0x0", "0x0", 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); - assert_vm_map(&m, 0, 1, 0, 0, 0, 0); + assert_vm_obj(&zero_obj, "0x0", "0x0", + 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); + assert_vm_map(&m, 0, 1, 0, 0, 0, 1); //destroy pseudo object _map_del_vm_obj(&zero_obj); @@ -429,7 +422,6 @@ START_TEST(test__map_make_zero_obj) { } - //_map_init_vm_area() [empty object fixture] START_TEST(test__map_init_vm_area) { @@ -444,7 +436,7 @@ START_TEST(test__map_init_vm_area) { assert_vm_area(&area, "/foo/bar", "bar", 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 0, true); - assert_vm_map(&m, 0, 1, 0, 0, 1, 0); + assert_vm_map(&m, 0, 1, 0, 0, 1, 1); //second test: create a stub entry & initialise another new area @@ -454,14 +446,13 @@ START_TEST(test__map_init_vm_area) { assert_vm_area(&area, NULL, NULL, 0x2000, 0x4000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &o_n, 1, true); - assert_vm_map(&m, 0, 1, 0, 0, 2, 0); + assert_vm_map(&m, 0, 1, 0, 0, 2, 1); return; } END_TEST - //_map_obj_add_area() [empty object fixture] START_TEST(test__map_obj_add_area) { @@ -496,7 +487,7 @@ START_TEST(test__map_obj_add_area) { //initialise lower area _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/foo/bar"); _map_init_vm_area(&area[1], &entry, &o_n, NULL, &m); - create_lst_wrapper(&area_node[1], &area); + create_lst_wrapper(&area_node[1], &area[1]); //second test: add lower area to the backing object _map_obj_add_area(&o, &area_node[1]); @@ -505,13 +496,13 @@ START_TEST(test__map_obj_add_area) { assert_vm_obj_list(&o.vm_area_node_ps, state_lower, 2); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", - 0x1000, 0x2000, MC_ACCESS_READ, &o_n, NULL, 1, true); + 0x1000, 0x2000, MC_ACCESS_WRITE, &o_n, NULL, 1, true); //initialise higher area _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/foo/bar"); _map_init_vm_area(&area[2], &entry, &o_n, NULL, &m); - create_lst_wrapper(&area_node[2], &area); + create_lst_wrapper(&area_node[2], &area[2]); //third test: add lower area to the backing object _map_obj_add_area(&o, &area_node[2]); @@ -526,7 +517,7 @@ START_TEST(test__map_obj_add_area) { //initialise middle area _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "/foo/bar"); _map_init_vm_area(&area[3], &entry, &o_n, NULL, &m); - create_lst_wrapper(&area_node[3], &area); + create_lst_wrapper(&area_node[3], &area[3]); //fourth test: add middle area to the backing object _map_obj_add_area(&o, &area_node[3]); @@ -535,14 +526,13 @@ START_TEST(test__map_obj_add_area) { assert_vm_obj_list(&o.vm_area_node_ps, state_middle, 4); area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); assert_vm_area(MC_GET_NODE_AREA(area_node_ptr), "/foo/bar", "bar", - 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); + 0x3000, 0x4000, MC_ACCESS_READ, &o_n, NULL, 3, true); return; } END_TEST - //_map_obj_add_last_area() [empty object fixture] START_TEST(test__map_obj_add_last_area) { @@ -559,71 +549,70 @@ START_TEST(test__map_obj_add_last_area) { //initialise first area - _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, "anonmap"); - _map_init_vm_area(&last_area[0], &entry, &o_n, NULL, &m); + _init_vm_entry(&entry, 0x2000, 0x3000, 0x800, MC_ACCESS_READ, NULL); + _map_init_vm_area(&last_area[0], &entry, NULL, &o_n, &m); create_lst_wrapper(&last_area_node[0], &last_area[0]); //first test: add first area to the backing object - _map_obj_add_area(&o, &last_area_node[0]); + _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[0]); assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); - last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); - assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "anonmap", "anonmap", - 0x2000, 0x3000, MC_ACCESS_READ, &o_n, NULL, 0, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, + 0x2000, 0x3000, MC_ACCESS_READ, NULL, &o_n, 0, true); //initialise lower area - _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, "/bin/cat"); - _map_init_vm_area(&last_area[1], &entry, &o_n, NULL, &m); - create_lst_wrapper(&last_area_node[1], &last_area); + _init_vm_entry(&entry, 0x1000, 0x2000, 0x600, MC_ACCESS_WRITE, NULL); + _map_init_vm_area(&last_area[1], &entry, NULL, &o_n, &m); + create_lst_wrapper(&last_area_node[1], &last_area[1]); //second test: add lower area to the backing object - _map_obj_add_area(&o, &last_area_node[1]); + _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[1]); assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_lower, 2); - last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head); - assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), - "/bin/cat", "cat", 0x1000, 0x2000, MC_ACCESS_READ, - &o_n, NULL, 1, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, + 0x1000, 0x2000, MC_ACCESS_WRITE, NULL, &o_n, 1, true); //initialise higher area - _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, "/lib/std"); - _map_init_vm_area(&last_area[2], &entry, &o_n, NULL, &m); - create_lst_wrapper(&last_area_node[2], &last_area); + _init_vm_entry(&entry, 0x4000, 0x5000, 0x900, MC_ACCESS_EXEC, NULL); + _map_init_vm_area(&last_area[2], &entry, NULL, &o_n, &m); + create_lst_wrapper(&last_area_node[2], &last_area[2]); //third test: add lower area to the backing object - _map_obj_add_area(&o, &last_area_node[2]); + _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[2]); - assert_vm_obj(&o, "/lib/std", "std", 0x0, 0x0, 0, 3, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 3, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_higher, 3); - last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->prev); - assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "/foo/bar", "bar", - 0x4000, 0x5000, MC_ACCESS_EXEC, &o_n, NULL, 2, true); + last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head->prev); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, + 0x4000, 0x5000, MC_ACCESS_EXEC, NULL, &o_n, 2, true); //initialise middle area - _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, "io"); - _map_init_vm_area(&last_area[3], &entry, &o_n, NULL, &m); - create_lst_wrapper(&last_area_node[3], &last_area); + _init_vm_entry(&entry, 0x3000, 0x4000, 0x880, MC_ACCESS_READ, NULL); + _map_init_vm_area(&last_area[3], &entry, NULL, &o_n, &m); + create_lst_wrapper(&last_area_node[3], &last_area[3]); //fourth test: add middle area to the backing object - _map_obj_add_area(&o, &last_area_node[3]); + _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[3]); - assert_vm_obj(&o, "/foo/bar", "bar", 0x1000, 0x5000, 4, 0, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 4, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_middle, 4); - last_area_node_ptr = MC_GET_NODE_PTR(o.vm_area_node_ps.head->next->next); - assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), "io", "io", - 0x3000, 0x4000, MC_ACCESS_EXEC, &o_n, NULL, 3, true); + last_area_node_ptr + = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head->next->next); + assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, + 0x3000, 0x4000, MC_ACCESS_READ, NULL, &o_n, 3, true); return; } END_TEST - - +#endif/* //_map_obj_rmv_area() [stub object fixture] START_TEST(test__map_obj_rmv_area) { @@ -670,7 +659,6 @@ START_TEST(test__map_obj_rmv_area) { } END_TEST - //_map_obj_rmv_last_area [stub object fixture] START_TEST(test__map_obj_rmv_last_area) { @@ -698,7 +686,6 @@ START_TEST(test__map_obj_rmv_last_area) { } - //_map_is_pathname_in_obj() [empty object fixture] START_TEST(test__map_is_pathname_in_obj) { @@ -718,7 +705,6 @@ START_TEST(test__map_is_pathname_in_obj) { } END_TEST - //_map_find_obj_for_area [empty map fixture] START_TEST(test__map_find_obj_for_area) { @@ -785,7 +771,6 @@ START_TEST(test__map_find_obj_for_area) { } END_TEST - //_map_backtrack_unmapped_obj_last_vm_areas() [stub map fixture] START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { @@ -863,7 +848,6 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { } END_TEST - //_map_forward_unmapped_obj_last_vm_areas() [stub map fixture] START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { @@ -902,7 +886,6 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { } END_TEST - //_map_unlink_unmapped_obj() [stub map fixture] START_TEST(test__map_unlink_unmapped_obj) { @@ -952,7 +935,6 @@ START_TEST(test__map_unlink_unmapped_obj) { } END_TEST - //_map_unlink_unmapped_area() [stub map fixture] START_TEST(test__map_unlink_unmapped_area) { @@ -1032,7 +1014,6 @@ START_TEST(test__map_unlink_unmapped_area) { } END_TEST - //_map_check_area_eql() [empty map fixture] START_TEST(test__map_check_area_eql) { @@ -1113,7 +1094,6 @@ START_TEST(test__map_check_area_eql) { } END_TEST - //_map_state_inc_area() [stub map fixture] START_TEST(test__map_state_inc_area) { @@ -1145,7 +1125,6 @@ START_TEST(test__map_state_inc_area) { } END_TEST - //_map_state_inc_obj() [stub map fixture] START_TEST(test__map_state_inc_obj) { @@ -1167,7 +1146,6 @@ START_TEST(test__map_state_inc_obj) { } END_TEST - //_map_resync_area() [stub map fixture] START_TEST(test__map_resync_area) { @@ -1322,7 +1300,6 @@ START_TEST(test__map_resync_area) { } END_TEST - //_map_add_obj() [stub map fixture] START_TEST(test__map_add_obj) { @@ -1373,7 +1350,6 @@ START_TEST(test__map_add_obj) { } END_TEST - //_map_add_area() [stub map fixture] START_TEST(test__map_add_area) { @@ -1457,7 +1433,6 @@ START_TEST(test__map_add_area) { } END_TEST - //map_send_entry() [stub map fixture] START_TEST(test_map_send_entry) { @@ -1589,7 +1564,6 @@ START_TEST(test_map_send_entry) { } END_TEST - //map_init_traverse_state() [empty map fixture] START_TEST(test_map_init_traverse_state) { @@ -1624,7 +1598,6 @@ START_TEST(test_map_init_traverse_state) { } END_TEST - //mc_map_clean_unmapped() [stub map fixture] START_TEST(test_mc_map_clean_unmapped) { @@ -1658,8 +1631,7 @@ START_TEST(test_mc_map_clean_unmapped) { } END_TEST #endif - - +*/ //TODO DEBUG /* * --- [SUITE] --- */ @@ -1675,7 +1647,7 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc__init_vm_area; TCase * tc__obj_add_area; TCase * tc__obj_add_last_area; - TCase * tc__obj_rmv_area; + #endif/*TCase * tc__obj_rmv_area; TCase * tc__obj_rmv_last_area; TCase * tc__is_pathname_in_obj; TCase * tc__find_obj_for_area; @@ -1693,7 +1665,7 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc_init_traverse_state; TCase * tc_clean_unmapped; #endif - + */ Suite * s = suite_create("map"); @@ -1717,7 +1689,7 @@ START_TEST(test_mc_map_clean_unmapped) { //tc__init_vm_area tc__init_vm_area = tcase_create("_init_vm_area"); tcase_add_checked_fixture(tc__init_vm_area, - _setup_empty_vm_map, _teardown_vm_map); + _setup_empty_vm_obj, _teardown_vm_obj); tcase_add_test(tc__init_vm_area, test__map_init_vm_area); //tc__obj_add_area @@ -1731,7 +1703,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_checked_fixture(tc__obj_add_last_area, _setup_empty_vm_obj, _teardown_vm_obj); tcase_add_test(tc__obj_add_last_area, test__map_obj_add_last_area); - + #endif/* //tc__obj_rmv_area tc__obj_rmv_area = tcase_create("_obj_rmv_area"); tcase_add_checked_fixture(tc__obj_rmv_area, @@ -1838,17 +1810,17 @@ START_TEST(test_mc_map_clean_unmapped) { _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc_clean_unmapped, test_mc_map_clean_unmapped); #endif - + */ //add test cases to map test suite suite_add_tcase(s, tc_new_del_vm_map); - + #ifdef DEBUG suite_add_tcase(s, tc__new_del_vm_obj); suite_add_tcase(s, tc__make_zero_obj); suite_add_tcase(s, tc__init_vm_area); suite_add_tcase(s, tc__obj_add_area); suite_add_tcase(s, tc__obj_add_last_area); - suite_add_tcase(s, tc__obj_rmv_area); + #endif/*suite_add_tcase(s, tc__obj_rmv_area); suite_add_tcase(s, tc__obj_rmv_last_area); suite_add_tcase(s, tc__is_pathname_in_obj); suite_add_tcase(s, tc__find_obj_for_area); @@ -1866,6 +1838,6 @@ START_TEST(test_mc_map_clean_unmapped) { suite_add_tcase(s, tc_init_traverse_state); suite_add_tcase(s, tc_clean_unmapped); #endif - + */ return s; } diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index 5eb99c0..14d6fdc 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -23,7 +23,6 @@ */ - //globals static mc_vm_map m; static mc_session s; @@ -55,7 +54,6 @@ static void _setup_target() { } - //teardown the target static void _teardown_target() { @@ -105,7 +103,6 @@ START_TEST(test_mc_get_area_offset) { } END_TEST - //mc_get_obj_offset() [target fixture] START_TEST(test_mc_get_obj_offset) { @@ -130,7 +127,6 @@ START_TEST(test_mc_get_obj_offset) { } END_TEST - //mc_get_area_offset_bnd() [target fixture] START_TEST(test_mc_get_area_offset_bnd) { @@ -157,7 +153,6 @@ START_TEST(test_mc_get_area_offset_bnd) { } END_TEST - //mc_get_obj_offset_bnd() [target fixture] START_TEST(test_mc_get_obj_offset_bnd) { @@ -182,7 +177,6 @@ START_TEST(test_mc_get_obj_offset_bnd) { } END_TEST - //mc_get_area_node_by_addr [target fixture] START_TEST(test_mc_get_area_node_by_addr) { @@ -215,7 +209,6 @@ START_TEST(test_mc_get_area_node_by_addr) { } END_TEST - //mc_get_obj_node_by_addr [target fixture] START_TEST(test_mc_get_obj_node_by_addr) { @@ -247,7 +240,6 @@ START_TEST(test_mc_get_obj_node_by_addr) { } END_TEST - //mc_get_obj_node_by_pathname() [target fixture] START_TEST(test_mc_get_obj_node_by_pathname) { @@ -277,7 +269,6 @@ START_TEST(test_mc_get_obj_node_by_pathname) { } END_TEST - //mc_get_obj_node_by_basename() [target_fixture] START_TEST(test_mc_get_obj_node_by_basename) { diff --git a/src/test/check_procfs_iface.c b/src/test/check_procfs_iface.c index 5b6f42f..06f04eb 100644 --- a/src/test/check_procfs_iface.c +++ b/src/test/check_procfs_iface.c @@ -55,7 +55,6 @@ START_TEST(test_procfs_mc_open_close) { } END_TEST - //procfs_update_map() [no fixture] START_TEST(test_procfs_mc_update_map) { @@ -65,7 +64,6 @@ START_TEST(test_procfs_mc_update_map) { } END_TEST - //procfs_read() & procfs_write() [no fixture] START_TEST(test_procfs_mc_read_write) { diff --git a/src/test/check_util.c b/src/test/check_util.c index d4f49e7..84c197f 100644 --- a/src/test/check_util.c +++ b/src/test/check_util.c @@ -32,11 +32,6 @@ static mc_session s; static pid_t pid; -/* - * --- [HELPERS] --- - */ - - /* * --- [FIXTURES] --- @@ -58,7 +53,6 @@ static void _setup_target() { } - //teardown the target static void _teardown_target() { @@ -103,7 +97,6 @@ START_TEST(test_mc_pathname_to_basename) { } END_TEST - //mc_pid_by_name() [target fixture] START_TEST(test_mc_pid_by_name) { @@ -133,7 +126,6 @@ START_TEST(test_mc_pid_by_name) { } END_TEST - //mc_name_by_pid() [no fixture] START_TEST(test_mc_name_by_pid) { @@ -169,7 +161,6 @@ START_TEST(test_mc_name_by_pid) { } END_TEST - //mc_bytes_to_hex() [no fixture] START_TEST(test_mc_bytes_to_hex) { diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c index bd9378c..5c8f69b 100644 --- a/src/test/iface_helper.c +++ b/src/test/iface_helper.c @@ -28,7 +28,6 @@ */ - //get address for testing a read / write in a PIE process static inline uintptr_t _get_addr(mc_vm_map * m, int obj_index, int area_index, off_t off) { @@ -55,7 +54,6 @@ static inline uintptr_t _get_addr(mc_vm_map * m, } - //assert open() and close() methods of an interface void assert_iface_open_close(enum mc_iface_type iface, void (* assert_session)(mc_session *, pid_t)) { @@ -97,7 +95,6 @@ void assert_iface_open_close(enum mc_iface_type iface, } - //procfs_update_map() [no fixture] void assert_iface_update_map(enum mc_iface_type iface) { @@ -163,7 +160,6 @@ void assert_iface_update_map(enum mc_iface_type iface) { } - //procfs_read() & procfs_write() [no fixture] void assert_iface_read_write(enum mc_iface_type iface) { diff --git a/src/test/iface_helper.h b/src/test/iface_helper.h index 0b28f60..f0ebe84 100644 --- a/src/test/iface_helper.h +++ b/src/test/iface_helper.h @@ -14,12 +14,10 @@ #include "../lib/memcry.h" - //map helper functions void assert_iface_open_close(enum mc_iface_type iface, void (* assert_session)(mc_session *, pid_t)); void assert_iface_update_map(enum mc_iface_type iface); void assert_iface_read_write(enum mc_iface_type iface); - #endif diff --git a/src/test/info.c b/src/test/info.c index 5d1bc77..013e873 100644 --- a/src/test/info.c +++ b/src/test/info.c @@ -2,7 +2,6 @@ #include "../lib/memcry.h" - //mc_iface_type names static char * _iface_names[2] = { "PROCFS", @@ -10,7 +9,6 @@ static char * _iface_names[2] = { }; - //convert enum to name char * get_iface_name(enum mc_iface_type iface) { diff --git a/src/test/info.h b/src/test/info.h index 83adfd8..670a6c0 100644 --- a/src/test/info.h +++ b/src/test/info.h @@ -8,14 +8,10 @@ #include "../lib/memcry.h" - #define INFO_PRINT(format, ...)\ printf("[INFO]: " format, ##__VA_ARGS__) - char * get_iface_name(enum mc_iface_type iface); - - #endif diff --git a/src/test/main.c b/src/test/main.c index ddfb935..9d4acb0 100644 --- a/src/test/main.c +++ b/src/test/main.c @@ -13,7 +13,6 @@ #include "suites.h" - enum _test_mode {UNIT, EXPL}; //determine which tests to run @@ -49,32 +48,31 @@ static enum _test_mode _get_test_mode(int argc, char ** argv) { } - //run unit tests static void _run_unit_tests() { Suite * s_map; - Suite * s_procfs_iface; + //Suite * s_procfs_iface; //Suite * s_krncry_iface; - Suite * s_map_util; - Suite * s_util; + //Suite * s_map_util; + //Suite * s_util; SRunner * sr; //initialise test suites s_map = map_suite(); - s_procfs_iface = procfs_iface_suite(); + //s_procfs_iface = procfs_iface_suite(); //s_krncry_iface = krncry_iface_suite(); - s_map_util = map_util_suite(); - s_util = util_suite(); + //s_map_util = map_util_suite(); + //s_util = util_suite(); //create suite runner sr = srunner_create(s_map); - srunner_add_suite(sr, s_procfs_iface); + //srunner_add_suite(sr, s_procfs_iface); //srunner_add_suite(sr, s_krncry_iface); - srunner_add_suite(sr, s_map_util); - srunner_add_suite(sr, s_util); + //srunner_add_suite(sr, s_map_util); + //srunner_add_suite(sr, s_util); //run tests srunner_run_all(sr, CK_VERBOSE); @@ -86,14 +84,12 @@ static void _run_unit_tests() { } - //dispatch tests int main(int argc, char ** argv) { enum _test_mode mode = _get_test_mode(argc, argv); switch (mode) { - case UNIT: _run_unit_tests(); break; diff --git a/src/test/map_helper.c b/src/test/map_helper.c index 58a28b1..e332d7a 100644 --- a/src/test/map_helper.c +++ b/src/test/map_helper.c @@ -20,7 +20,6 @@ #include "../lib/map.h" - //initialise a cm_lst_node stub wrapper void create_lst_wrapper(cm_lst_node * node, void * data) { @@ -45,14 +44,18 @@ void assert_lst_len(cm_lst * list, int len) { //if length is one (1), ensure head is not null ck_assert_ptr_nonnull(list->head); cm_lst_node * iter = list->head; - if (len == 1) return; + + if (len == 1) { + ck_assert_ptr_null(iter->next); + ck_assert_ptr_null(iter->prev); + return; + } //if length is greater than 1 (1), iterate over nodes to ensure length ck_assert_ptr_nonnull(iter->next); iter = iter->next; for (int i = 1; i < len; ++i) { - ck_assert(iter != list->head); iter = iter->next; } @@ -61,6 +64,22 @@ void assert_lst_len(cm_lst * list, int len) { } +//properly assert a potentially NULL string pair +void assert_names(char * a, char * b) { + + //if one is NULL, both must be NULL + if (a == NULL || b == NULL) { + ck_assert_ptr_eq(a, b); + + //otherwise, assert strings + } else { + ck_assert_str_eq(a, b); + } + + return; +} + + //basic assertion of state for a mc_vm_map void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, int vm_areas_unmapped_len, int vm_objs_unmapped_len, @@ -93,7 +112,7 @@ void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, obj = cm_lst_get_p(obj_lst, start_index + i); ck_assert_ptr_nonnull(obj); - ck_assert_str_eq(obj->basename, obj_checks[i].basename); + assert_names(obj->basename, obj_checks[i].basename); ck_assert_int_eq(obj->start_addr, obj_checks[i].start_addr); ck_assert_int_eq(obj->end_addr, obj_checks[i].end_addr); } @@ -113,7 +132,7 @@ void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[NAME_MAX], obj = cm_lst_get_p(obj_lst, start_index + i); ck_assert_ptr_nonnull(obj); - ck_assert_str_eq(obj->basename, basenames[i]); + assert_names(obj->basename, basenames[i]); } return; @@ -131,7 +150,7 @@ void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, area = cm_lst_get_p(area_lst, start_index + i); ck_assert_ptr_nonnull(area); - ck_assert_str_eq(area->basename, area_checks[i].basename); + assert_names(area->basename, area_checks[i].basename); ck_assert_int_eq(area->start_addr, area_checks[i].start_addr); ck_assert_int_eq(area->end_addr, area_checks[i].end_addr); } @@ -151,7 +170,7 @@ void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[NAME_MAX], area = cm_lst_get_p(area_lst, start_index + i); ck_assert_ptr_nonnull(area); - ck_assert_str_eq(area->basename, basenames[i]); + assert_names(area->basename, basenames[i]); } return; @@ -163,8 +182,8 @@ void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, int last_vm_areas_len, int id, bool mapped) { //check names - ck_assert_str_eq(obj->pathname, pathname); - ck_assert_str_eq(obj->basename, basename); + assert_names(obj->pathname, pathname); + assert_names(obj->basename, basename); //check addresses ck_assert_int_eq(obj->start_addr, start_addr); @@ -222,8 +241,8 @@ void assert_vm_area(mc_vm_area * area, char * pathname, char * basename, cm_lst_node * last_obj_node_p, int id, bool mapped) { //check names - ck_assert_str_eq(area->pathname, pathname); - ck_assert_str_eq(area->basename, basename); + assert_names(area->pathname, pathname); + assert_names(area->basename, basename); //check addresses ck_assert_int_eq(area->start_addr, start_addr); diff --git a/src/test/map_helper.h b/src/test/map_helper.h index 9616eba..cd97820 100644 --- a/src/test/map_helper.h +++ b/src/test/map_helper.h @@ -16,7 +16,6 @@ #include "../lib/memcry.h" - //map test structures struct obj_check { @@ -38,6 +37,7 @@ struct area_check { void create_lst_wrapper(cm_lst_node * node, void * data); void assert_lst_len(cm_lst * list, int len); +void assert_names(char * name_1, char * name_2); void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, int vm_areas_unmapped_len, int vm_objs_unmapped_len, int next_id_area, int next_id_obj); diff --git a/src/test/target/manual_target.c b/src/test/target/manual_target.c index 4f30ea3..1f56f53 100644 --- a/src/test/target/manual_target.c +++ b/src/test/target/manual_target.c @@ -12,7 +12,6 @@ #include - /* * This program will continue counting up to confirm that it is running. * Press `ENTER` at any point to make the program dlopen() an additional @@ -20,12 +19,10 @@ */ - //globals struct termios old_term, new_term; - //Ctrl+C signal handler void sigint_handler(int signum) { @@ -34,7 +31,6 @@ void sigint_handler(int signum) { } - //set terminal to non-blocking, non-canon, non-echo mode void setup_terminal() { @@ -54,7 +50,6 @@ void setup_terminal() { } - int main() { int ch; diff --git a/src/test/target/unit_target.c b/src/test/target/unit_target.c index 92f4d83..f790f55 100644 --- a/src/test/target/unit_target.c +++ b/src/test/target/unit_target.c @@ -17,13 +17,11 @@ #include "../target_helper.h" - /* * Unit target is tied directly to `../target_helper.{c,h}`. Changing this * target likely necessitates also changing the target helper. */ - /* * This program sleeps indefinitely, awaiting a signal from its parent, * which will cause it to dlopen() an additional library and hence change @@ -31,7 +29,6 @@ */ - //globals void * libelf; enum target_map_state state = UNCHANGED; @@ -43,7 +40,6 @@ enum target_map_state state = UNCHANGED; char rw_buf[TARGET_BUF_SZ] = IFACE_RW_BUF_STR; - //unit test signal handler void sigusr1_handler() { @@ -62,7 +58,6 @@ void sigusr1_handler() { } - int main(int argc, char ** argv) { int ch; diff --git a/src/test/target_helper.c b/src/test/target_helper.c index a64914e..93f2e07 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -21,7 +21,6 @@ #include "../lib/memcry.h" - //globals static enum target_map_state target_state; @@ -149,7 +148,6 @@ char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { }; - //signal handlers static void _sigusr1_handler() { @@ -165,7 +163,6 @@ static void _sigusr1_handler() { } - //helpers pid_t start_target() { @@ -205,7 +202,6 @@ pid_t start_target() { } - void end_target(pid_t pid) { int ret; @@ -229,7 +225,6 @@ void end_target(pid_t pid) { } - void change_target_map(pid_t pid) { int ret; @@ -252,7 +247,6 @@ void change_target_map(pid_t pid) { } - void assert_target_map(mc_vm_map * map) { switch(target_state) { diff --git a/src/test/target_helper.h b/src/test/target_helper.h index c8d5aec..b25595b 100644 --- a/src/test/target_helper.h +++ b/src/test/target_helper.h @@ -8,7 +8,6 @@ #include "../lib/memcry.h" - /* * The target helper is directly tied to `target/unit_target.c`, changing * the target helper means you likely have to change the unit target and @@ -16,7 +15,6 @@ */ - #define TARGET_AREAS_UNCHANGED 22 extern char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX]; @@ -45,7 +43,6 @@ enum target_map_state { }; - //target metadata #define TARGET_NAME "unit_target" #define TARGET_BUF_SZ 16 /* must be even */ @@ -61,12 +58,10 @@ enum target_map_state { #define IFACE_NONE_OFF 0x0 - //target helpers pid_t start_target(); void end_target(pid_t pid); void change_target_map(pid_t pid); void assert_target_map(mc_vm_map * map); - #endif From e1ba879e3a6312854c1946f9a0b4fb49efc115fd Mon Sep 17 00:00:00 2001 From: vykt Date: Mon, 17 Feb 2025 03:06:49 +0000 Subject: [PATCH 15/45] Bugfix map updates & fix unit tests --- build/test/debug.sh | 2 +- build/test/init.gdb | 2 +- src/lib/map.c | 157 +++++++++++++++++++----------- src/lib/map.h | 6 +- src/test/check_map.c | 222 +++++++++++++++++++++++++++++++++++++------ 5 files changed, 304 insertions(+), 85 deletions(-) diff --git a/build/test/debug.sh b/build/test/debug.sh index 7ae82f9..a9f8aad 100755 --- a/build/test/debug.sh +++ b/build/test/debug.sh @@ -1,2 +1,2 @@ #!/bin/sh -gdb -x init.gdb --args ./test "$@" +gdb -x init.gdb --args ./test -u "$@" diff --git a/build/test/init.gdb b/build/test/init.gdb index 0123e2d..a90bcff 100644 --- a/build/test/init.gdb +++ b/build/test/init.gdb @@ -63,5 +63,5 @@ define pxnodea end end -tb test__map_obj_add_last_area_fn +tb test__map_forward_unmapped_obj_last_vm_areas_fn run diff --git a/src/lib/map.c b/src/lib/map.c index 118ba00..d2de4bc 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -219,9 +219,7 @@ int _map_obj_add_area(mc_vm_obj * obj, if (area->start_addr < obj->start_addr) obj->start_addr = area->start_addr; if (area->end_addr > obj->end_addr) - obj->end_addr = area->end_addr; - - + obj->end_addr = area->end_addr; } //insert new area & ensure the area list remains sorted @@ -246,22 +244,15 @@ int _map_obj_add_last_area(mc_vm_obj * obj, } -DBG_STATIC DBG_INLINE -int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node) { +DBG_STATIC +int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node) { int ret, index; - cm_lst_node * area_outer_node, * temp_node; + cm_lst_node * temp_node; - //remove area node from the object - - //find index of area in object - area_outer_node = _map_obj_find_area_outer_node(&obj->vm_area_node_ps, - area_node); - if (area_outer_node == NULL) return -1; - //remove area node from object - ret = cm_lst_rmv_n(&obj->vm_area_node_ps, area_outer_node); + ret = cm_lst_rmv_n(&obj->vm_area_node_ps, outer_area_node); if (ret != 0) { mc_errno = MC_ERR_LIBCMORE; return -1; @@ -305,22 +296,33 @@ int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node) { DBG_STATIC DBG_INLINE -int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node) { +int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * inner_area_node) { int ret; - cm_lst_node * last_area_outer_node; + cm_lst_node * outer_area_node; + //find index of area in object + outer_area_node = _map_obj_find_area_outer_node(&obj->vm_area_node_ps, + inner_area_node); + if (outer_area_node == NULL) { + mc_errno = MC_ERR_AREA_IN_OBJ; + return -1; + } - //remove area node from the object + ret = _map_obj_rmv_area_fast(obj, outer_area_node); + if (ret != 0) return -1; - //find index of area in object - last_area_outer_node = _map_obj_find_area_outer_node(&obj-> - last_vm_area_node_ps, - last_area_node); - if (last_area_outer_node == NULL) return -1; + return 0; +} - //remove area node from object - ret = cm_lst_rmv_n(&obj->last_vm_area_node_ps, last_area_outer_node); + +DBG_STATIC +int _map_obj_rmv_last_area_fast(mc_vm_obj * obj, + cm_lst_node * outer_area_node) { + + int ret; + + ret = cm_lst_rmv_n(&obj->last_vm_area_node_ps, outer_area_node); if (ret != 0) { mc_errno = MC_ERR_LIBCMORE; return -1; @@ -330,6 +332,31 @@ int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node) { } +DBG_STATIC DBG_INLINE +int _map_obj_rmv_last_area(mc_vm_obj * obj, + cm_lst_node * inner_area_node) { + + int ret; + cm_lst_node * outer_area_node; + + + //remove area node from the object + + //find index of area in object + outer_area_node = _map_obj_find_area_outer_node(&obj->last_vm_area_node_ps, + inner_area_node); + if (outer_area_node == NULL) { + mc_errno = MC_ERR_AREA_IN_OBJ; + return -1; + } + + ret = _map_obj_rmv_last_area_fast(obj, outer_area_node); + if (ret != 0) return -1; + + return 0; +} + + DBG_STATIC bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { @@ -377,20 +404,27 @@ DBG_STATIC DBG_INLINE int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { int ret; + cm_lst_node * ret_p; + int iterations; mc_vm_area * last_area; - cm_lst_node * last_area_node; + cm_lst_node * last_area_node, * temp_area_node; - mc_vm_obj * temp_obj; + mc_vm_obj * temp_obj, * temp_prev_obj; //setup iteration - temp_obj = MC_GET_NODE_OBJ(obj_node); + temp_obj = MC_GET_NODE_OBJ(obj_node); + temp_prev_obj = MC_GET_NODE_OBJ(obj_node->prev); + + //get first node last_area_node = temp_obj->last_vm_area_node_ps.head; if (last_area_node == NULL) return 0; - last_area = MC_GET_NODE_AREA(last_area_node); + //get last area from first node + temp_area_node = MC_GET_NODE_PTR(last_area_node); + last_area = MC_GET_NODE_AREA(temp_area_node); //for every area, backtrack last object pointer @@ -399,9 +433,18 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { //update pointer last_area->last_obj_node_p = obj_node->prev; - + ret_p = cm_lst_apd(&temp_prev_obj->last_vm_area_node_ps, + &temp_area_node); + if (ret_p == NULL) { + mc_errno = MC_ERR_LIBCMORE; + return -1; + } + //advance iteration (part 1) last_area_node = last_area_node->next; + if (last_area_node != NULL) { + temp_area_node = MC_GET_NODE_PTR(last_area_node); + } //remove this area from the object's last_vm_area_node_ps list ret = cm_lst_rmv(&temp_obj->last_vm_area_node_ps, 0); @@ -411,7 +454,9 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { } //advance iteration (part 2) - last_area = MC_GET_NODE_AREA(last_area_node); + if (last_area_node != NULL) { + last_area = MC_GET_NODE_AREA(temp_area_node); + } } return 0; @@ -424,27 +469,23 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { int ret; - //declarations - mc_vm_obj * obj; - - cm_lst_node * prev_obj_node; - mc_vm_obj * prev_obj; - - cm_lst_node * last_area_node; + cm_lst_node * last_area_node, * temp_area_node, * prev_obj_node; mc_vm_area * last_area; + mc_vm_obj * obj, * prev_obj; - //setup iteration + //setup objects obj = MC_GET_NODE_OBJ(obj_node); - prev_obj_node = obj_node->prev; prev_obj = MC_GET_NODE_OBJ(prev_obj_node); + + //setup iteration last_area_node = prev_obj->last_vm_area_node_ps.head; if (last_area_node == NULL) return 0; - - last_area = MC_GET_NODE_AREA(last_area_node); + temp_area_node = MC_GET_NODE_PTR(last_area_node); + last_area = MC_GET_NODE_AREA(temp_area_node); //for every area, move last object pointer forward if necessary for (int i = 0; i < prev_obj->last_vm_area_node_ps.len; ++i) { @@ -456,22 +497,32 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { last_area->last_obj_node_p = obj_node; //add this area to this object's last_vm_area_node_ps list - ret = _map_obj_add_last_area(obj, last_area_node); + ret = _map_obj_add_last_area(obj, temp_area_node); if (ret == -1) return -1; + //advance iteration (part 1) + temp_area_node = last_area_node->next; + //remove this area from the previous //last object's last_vm_area_node_ps - ret =_map_obj_rmv_last_area(prev_obj, last_area_node); + ret =_map_obj_rmv_last_area_fast(prev_obj, last_area_node); if (ret == -1) return -1; //correct iteration index i -= 1; - } //end if area's address range comes completely after this object + //otherwise just update iteration + } else { + temp_area_node = last_area_node->next; + } //advance iteration - last_area_node = last_area_node->next; - last_area = MC_GET_NODE_AREA(last_area_node); + if (temp_area_node != NULL) { + + last_area_node = temp_area_node; + temp_area_node = MC_GET_NODE_PTR(last_area_node); + last_area = MC_GET_NODE_AREA(temp_area_node); + } } //end for @@ -920,9 +971,9 @@ void mc_new_vm_map(mc_vm_map * map) { int mc_del_vm_map(mc_vm_map * map) { - int ret, len_obj; + int ret, len_unmapped_obj; - cm_lst_node * obj_node; + cm_lst_node * unmapped_obj_node; mc_vm_obj * obj; @@ -932,18 +983,18 @@ int mc_del_vm_map(mc_vm_map * map) { //setup iteration - len_obj = map->vm_objs.len; - obj_node = map->vm_objs.head; + len_unmapped_obj = map->vm_objs_unmapped.len; + unmapped_obj_node = map->vm_objs_unmapped.head; //manually free all unmapped obj nodes - for (int i = 0; i < len_obj; ++i) { + for (int i = 0; i < len_unmapped_obj; ++i) { //fetch & destroy the object - obj = MC_GET_NODE_OBJ(obj_node); + obj = MC_GET_NODE_OBJ(unmapped_obj_node); _map_del_vm_obj(obj); //advance iteration - obj_node = obj_node->next; + unmapped_obj_node = unmapped_obj_node->next; } //end for diff --git a/src/lib/map.h b/src/lib/map.h index 4a07f68..a1fc7c8 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -50,8 +50,10 @@ cm_lst_node * _map_obj_find_area_outer_node(cm_lst * obj_area_lst, int _map_obj_add_area(mc_vm_obj * obj, const cm_lst_node * area_node); int _map_obj_add_last_area(mc_vm_obj * obj, const cm_lst_node * last_area_node); -int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * area_node); -int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * last_area_node); +int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node); +int _map_obj_rmv_area(mc_vm_obj * obj, cm_lst_node * inner_area_node); +int _map_obj_rmv_last_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node); +int _map_obj_rmv_last_area(mc_vm_obj * obj, cm_lst_node * inner_area_node); bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj); int _map_find_obj_for_area(const struct vm_entry * entry, diff --git a/src/test/check_map.c b/src/test/check_map.c index 341a491..8a10e9d 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -43,6 +43,16 @@ * * > Tested through `_map_obj_rmv_area()` * and `_map_obj_rmv_last_area()` + * + * _map_obj_rmv_area_fast(): + * + * > Tested through `map_obj_rmv_area()` + * which calls this function. + * + * _map_obj_rmv_last_area_fast(): + * + * > Tested through `map_obj_rmv_last_area()` + * which calls this function. */ /* @@ -122,6 +132,62 @@ static void _init_vm_entry(struct vm_entry * entry, unsigned long vm_start, } +//connect nodes +static void _connect_nodes(cm_lst_node * node_1, cm_lst_node * node_2) { + + if (node_1 != NULL) { + node_1->next = node_2; + } + + if (node_2 != NULL) { + node_2->prev = node_1; + } + + return; +} + + +//check if a pointer points to a static allocation +static bool _map_check_static(void * ptr, void * arr, int len, size_t ent_sz) { + + for (int i = 0; i < len; ++i) { + if ((cm_byte *) ptr == (((cm_byte *) arr) + (i * ent_sz))) return true; + } + + return false; +} + + +//remove static pointers from a list +static void _map_remove_static(cm_lst * lst, + void * arr, int len, size_t ent_sz) { + + cm_lst_node * temp_node; + cm_lst_node * node = lst->head; + + + for (int i = 0; i < lst->len; ++i) { + + //static node + if (_map_check_static(node, arr, len, ent_sz)) { + + //adjust iteration & unlink static node + temp_node = node->next; + cm_lst_uln_n(lst, node); + node = temp_node; + i -= 1; + + //dynamic node + } else { + node = node->next; + + } //end if + + } //end for + + return; +} + /* * --- [FIXTURES] --- @@ -138,7 +204,13 @@ static void _setup_empty_vm_map() { #ifdef DEBUG -#define STUB_MAP_LEN 10 + +/* + * NOTE: I recognise setting this up and tearing it down is an enormous pain, + * and a time sink, however without it the majority of internal + * functions can't be tested. + */ + //stub map fixture static void _setup_stub_vm_map() { @@ -256,13 +328,73 @@ static void _setup_stub_vm_map() { ret = _map_obj_add_area(&m_o[3], &m_a_n[9]); ck_assert_int_eq(ret, 0); + + //connect area nodes + for (int i = 0; i < STUB_MAP_AREA_NUM - 1; ++i) { + _connect_nodes(&m_a_n[i], &m_a_n[i+1]); + } + _connect_nodes(&m_a_n[STUB_MAP_AREA_NUM-1], &m_a_n[0]); + + //connect object nodes + for (int i = 0; i < STUB_MAP_OBJ_NUM - 1; ++i) { + _connect_nodes(&m_o_n[i], &m_o_n[i+1]); + } + _connect_nodes(m.vm_objs.head, &m_o_n[0]); + _connect_nodes(&m_o_n[STUB_MAP_OBJ_NUM-1], m.vm_objs.head); + + + //connect area nodes and object nodes to the map + m.vm_areas.head = &m_a_n[0]; + + m.vm_areas.len += STUB_MAP_AREA_NUM; + m.vm_objs.len += STUB_MAP_OBJ_NUM; + return; } #endif +/* + * NOTE: The map starts out containing only statically allocated areas, + * objects, and nodes. After tests are carried out, it can contain + * a mix of statically and dynamically allocated data. The map + * destructor expects exclusively dynamically allocated data. As such, + * it is important to remove all statically allocated data from the map + * before calling the destructor. + */ + static void _teardown_vm_map() { + cm_lst_node * node; + mc_vm_obj * obj; + mc_vm_area * area; + + + //setup iteration + node = m.vm_objs.head; + + //empty all object lists + for (int i = 0; i < m.vm_objs.len; ++i) { + + //empty lists of this object (always dynamic) + obj = MC_GET_NODE_OBJ(node); + cm_lst_emp(&obj->vm_area_node_ps); + cm_lst_emp(&obj->last_vm_area_node_ps); + node = node->next; + } + + //remove static objects & areas + _map_remove_static(&m.vm_objs, m_o_n, + STUB_MAP_OBJ_NUM, sizeof(cm_lst_node)); + _map_remove_static(&m.vm_areas, m_a_n, + STUB_MAP_AREA_NUM, sizeof(cm_lst_node)); + _map_remove_static(&m.vm_objs_unmapped, m_o_n, + STUB_MAP_OBJ_NUM, sizeof(cm_lst_node)); + _map_remove_static(&m.vm_areas_unmapped, m_a_n, + STUB_MAP_AREA_NUM, sizeof(cm_lst_node)); + + + //call regular destructor mc_del_vm_map(&m); return; @@ -311,6 +443,7 @@ static void _setup_stub_vm_obj() { _init_vm_entry(&entry, addr, addr + 0x1000, file_off, MC_ACCESS_READ, "/foo/bar"); _map_init_vm_area(&o_a[i], &entry, &o_n, NULL, &m); + create_lst_wrapper(&o_a_n[i], &o_a[i]); _map_obj_add_area(&o, &o_a_n[i]); //advance iteration @@ -326,6 +459,7 @@ static void _setup_stub_vm_obj() { _init_vm_entry(&entry, last_addr, last_addr + 0x1000, 0x0, MC_ACCESS_READ, NULL); _map_init_vm_area(&o_a_l[i], &entry, NULL, &o_n, &m); + create_lst_wrapper(&o_a_l_n[i], &o_a_l[i]); _map_obj_add_last_area(&o, &o_a_l_n[i]); //advance iteration @@ -612,7 +746,7 @@ START_TEST(test__map_obj_add_last_area) { } END_TEST -#endif/* + //_map_obj_rmv_area() [stub object fixture] START_TEST(test__map_obj_rmv_area) { @@ -724,27 +858,31 @@ START_TEST(test__map_find_obj_for_area) { create_lst_wrapper(&obj_nodes[i], &objs[i]); } + //connect test objects + _connect_nodes(&obj_nodes[0], &obj_nodes[1]); + _connect_nodes(&obj_nodes[1], &obj_nodes[2]); + //first test: new object, state empty _init__traverse_state(&state, NULL, NULL); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, - MC_ACCESS_READ, "/lib/libpthread"); + MC_ACCESS_READ, "/lib/libc"); ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_NEW); //second test: new object, state full - _init__traverse_state(&state, NULL, &obj_nodes[1]); + _init__traverse_state(&state, NULL, &obj_nodes[0]); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, - MC_ACCESS_READ, "/lib/libpthread"); + MC_ACCESS_READ, "anonmap"); ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_NEW); //third test: previous object - _init__traverse_state(&state, NULL, &obj_nodes[1]); + _init__traverse_state(&state, NULL, &obj_nodes[0]); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, MC_ACCESS_READ, "/lib/libc"); @@ -753,15 +891,15 @@ START_TEST(test__map_find_obj_for_area) { //fourth test: next object - _init__traverse_state(&state, NULL, &obj_nodes[1]); + _init__traverse_state(&state, NULL, &obj_nodes[0]); _init_vm_entry(&entry, 0x1000, 0x2000, 0x500, - MC_ACCESS_READ, "anonmap"); + MC_ACCESS_READ, "/lib/libpthread"); ret = _map_find_obj_for_area(&entry, &state); ck_assert_int_eq(ret, _MAP_OBJ_NEXT); - //destruct objects + //destruct test objects for (int i = 0; i < 3; ++i) { _map_del_vm_obj(&objs[i]); } @@ -774,6 +912,13 @@ START_TEST(test__map_find_obj_for_area) { //_map_backtrack_unmapped_obj_last_vm_areas() [stub map fixture] START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { + /* + * NOTE: For this test, `m_o{_n}` arrays are used to refer to objects + * in the stub map. Note `m_o{_n}[0]` is not the first object in + * in the map; the map contains a pseudo object that is not in + * the `m_o{_n}` arrays. + */ + int ret; mc_vm_obj * zero_obj; @@ -813,7 +958,7 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { assert_vm_obj_list(&m_o[0].last_vm_area_node_ps, first_state, 2); //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` - assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[4], NULL, NULL, 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[0], 4, true); assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, @@ -837,7 +982,7 @@ START_TEST(test__map_backtrack_unmapped_obj_last_vm_areas) { assert_vm_obj_list(&zero_obj->last_vm_area_node_ps, third_state, 2); //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` - assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + assert_vm_area(&m_a[4], NULL, NULL, 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, zero_node, 4, true); assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, @@ -853,29 +998,50 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { int ret; - uintptr_t heap_state[2] = {0x6000}; - uintptr_t lib_foo_state[2] = {0xC000}; + uintptr_t first_heap_state[2] = {0x6000, 0xC000}; + + uintptr_t second_heap_state[1] = {0x6000}; + uintptr_t second_lib_foo_state[1] = {0xC000}; - //setup the test by backtracking `/lib/foo`'s last area. + //setup the test by backtracking `/lib/foo`'s and `[heap]`'s last areas. ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[2]); ck_assert_int_eq(ret, 0); + ret = _map_backtrack_unmapped_obj_last_vm_areas(&m_o_n[1]); + ck_assert_int_eq(ret, 0); + - //only test: pretend the `/lib/foo` object was just inserted + //first test: pretend `[heap]` object was just inserted + ret = _map_forward_unmapped_obj_last_vm_areas(&m_o_n[1]); + ck_assert_int_eq(ret, 0); + + //check `/bin/cat` has no last areas associated with it + assert_vm_obj(&m_o[0], "/bin/cat", "cat", 0x1000, 0x4000, 3, 0, 0, true); + assert_vm_obj_list(&m_o[0].last_vm_area_node_ps, NULL, 0); + + //check the transferred last areas (indeces 4, 8) now point to correct objs + assert_vm_area(&m_a[4], NULL, NULL, 0x6000, 0x7000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[1], 4, true); + + assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[1], 8, true); + + + //second test: pretend the `/lib/foo` object was just inserted ret = _map_forward_unmapped_obj_last_vm_areas(&m_o_n[2]); ck_assert_int_eq(ret, 0); //check `[heap]` has only one last area associated with it assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 1, 1, true); - assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 1); + assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, second_heap_state, 1); //check `/lib/foo` now has one last area associated with it - assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 2, 0, true); - assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, lib_foo_state, 1); + assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 1, 2, true); + assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, second_lib_foo_state, 1); - //check the transfered last areas (indeces: 4, 8) now point to `/bin/cat` - assert_vm_area(&m_a[4], NULL, NULL, 0xC000, 0xD000, + //check the transferred last areas (indeces: 4, 8) now point to correct objs + assert_vm_area(&m_a[4], NULL, NULL, 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, &m_o_n[1], 4, true); assert_vm_area(&m_a[8], NULL, NULL, 0xC000, 0xD000, @@ -885,7 +1051,7 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { } END_TEST - +#endif/* //_map_unlink_unmapped_obj() [stub map fixture] START_TEST(test__map_unlink_unmapped_obj) { @@ -1647,13 +1813,13 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc__init_vm_area; TCase * tc__obj_add_area; TCase * tc__obj_add_last_area; - #endif/*TCase * tc__obj_rmv_area; + TCase * tc__obj_rmv_area; TCase * tc__obj_rmv_last_area; TCase * tc__is_pathname_in_obj; TCase * tc__find_obj_for_area; TCase * tc__backtrack_unmapped_obj_last_vm_areas; TCase * tc__forward_unmapped_obj_last_vm_areas; - TCase * tc__unlink_unmapped_obj; + #endif/*TCase * tc__unlink_unmapped_obj; TCase * tc__unlink_unmapped_area; TCase * tc__check_area_eql; TCase * tc__state_inc_area; @@ -1703,7 +1869,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_checked_fixture(tc__obj_add_last_area, _setup_empty_vm_obj, _teardown_vm_obj); tcase_add_test(tc__obj_add_last_area, test__map_obj_add_last_area); - #endif/* + //tc__obj_rmv_area tc__obj_rmv_area = tcase_create("_obj_rmv_area"); tcase_add_checked_fixture(tc__obj_rmv_area, @@ -1714,7 +1880,7 @@ START_TEST(test_mc_map_clean_unmapped) { tc__obj_rmv_last_area = tcase_create("_obj_rmv_last_area"); tcase_add_checked_fixture(tc__obj_rmv_last_area, _setup_stub_vm_obj, _teardown_vm_obj); - tcase_add_test(tc__obj_rmv_last_area, test__map_obj_rmv_area); + tcase_add_test(tc__obj_rmv_last_area, test__map_obj_rmv_last_area); //tc__is_pathname_in_obj tc__is_pathname_in_obj = tcase_create("_is_pathname_in_obj"); @@ -1743,7 +1909,7 @@ START_TEST(test_mc_map_clean_unmapped) { _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__forward_unmapped_obj_last_vm_areas, test__map_forward_unmapped_obj_last_vm_areas); - + #endif/* //tc__unlink_unmapped_obj tc__unlink_unmapped_obj = tcase_create("_unlink_unmapped_obj"); tcase_add_checked_fixture(tc__unlink_unmapped_obj, @@ -1820,13 +1986,13 @@ START_TEST(test_mc_map_clean_unmapped) { suite_add_tcase(s, tc__init_vm_area); suite_add_tcase(s, tc__obj_add_area); suite_add_tcase(s, tc__obj_add_last_area); - #endif/*suite_add_tcase(s, tc__obj_rmv_area); + suite_add_tcase(s, tc__obj_rmv_area); suite_add_tcase(s, tc__obj_rmv_last_area); suite_add_tcase(s, tc__is_pathname_in_obj); suite_add_tcase(s, tc__find_obj_for_area); suite_add_tcase(s, tc__backtrack_unmapped_obj_last_vm_areas); suite_add_tcase(s, tc__forward_unmapped_obj_last_vm_areas); - suite_add_tcase(s, tc__unlink_unmapped_obj); + #endif/*suite_add_tcase(s, tc__unlink_unmapped_obj); suite_add_tcase(s, tc__unlink_unmapped_area); suite_add_tcase(s, tc__check_area_eql); suite_add_tcase(s, tc__state_inc_area); From 47265c3c71b1a70bc64a1ceade0889dd6b2a9243 Mon Sep 17 00:00:00 2001 From: vykt Date: Tue, 18 Feb 2025 01:05:59 +0000 Subject: [PATCH 16/45] Fix assertions of unmapped nodes --- build/test/init.gdb | 22 ++++- src/lib/map.c | 19 ++--- src/test/check_map.c | 193 ++++++++++++++++++++++++------------------ src/test/map_helper.c | 80 +++++++++++++---- src/test/map_helper.h | 18 ++-- 5 files changed, 212 insertions(+), 120 deletions(-) diff --git a/build/test/init.gdb b/build/test/init.gdb index a90bcff..6c48086 100644 --- a/build/test/init.gdb +++ b/build/test/init.gdb @@ -54,7 +54,7 @@ define pnodea end end -# hex print the are aat a node at the specified node: +# hex print the area at a node at the specified node: define pxnodea if $argc != 1 printf "Use: pnodea <*obj_area_node>\n" @@ -63,5 +63,23 @@ define pxnodea end end -tb test__map_forward_unmapped_obj_last_vm_areas_fn +# print the object at a node at the specified node: +define pnodeo + if $argc != 1 + printf "Use: pnodea <*obj_area_node>\n" + else + p *((mc_vm_obj *) ((*((cm_lst_node **) ($arg0->data)))->data)) + end +end + +# hex print the object at a node at the specified node: +define pxnodeo + if $argc != 1 + printf "Use: pnodea <*obj_area_node>\n" + else + p/x *((mc_vm_obj *) ((*((cm_lst_node **) ($arg0->data)))->data)) + end +end + +tb test__map_unlink_unmapped_obj_fn run diff --git a/src/lib/map.c b/src/lib/map.c index d2de4bc..74bd4ac 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -81,8 +81,8 @@ void _map_new_vm_obj(mc_vm_obj * obj, strncpy(obj->pathname, pathname, PATH_MAX); strncpy(obj->basename, basename, NAME_MAX); - obj->start_addr = 0x0; - obj->end_addr = 0x0; + obj->start_addr = MC_UNDEF_ADDR; + obj->end_addr = MC_UNDEF_ADDR; //initialise area list cm_new_lst(&obj->vm_area_node_ps, sizeof(cm_lst_node *)); @@ -112,6 +112,7 @@ DBG_STATIC DBG_INLINE void _map_make_zero_obj(mc_vm_obj * obj) { obj->id = MC_ZERO_OBJ_ID; + obj->start_addr = obj->end_addr = 0x0; return; } @@ -206,15 +207,13 @@ int _map_obj_add_area(mc_vm_obj * obj, //if this object has no areas yet - if (obj->start_addr == MC_UNDEF_ADDR || obj->start_addr == 0x0) { - + if (obj->start_addr == MC_UNDEF_ADDR) { //set new addr bounds obj->start_addr = area->start_addr; obj->end_addr = area->end_addr; //if this object has areas, only update the start and end addr if necessary } else { - //set new addr bounds if necessary if (area->start_addr < obj->start_addr) obj->start_addr = area->start_addr; @@ -261,10 +260,9 @@ int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node) { //update object's address range - //if no area nodes left, address range is now zero (unmapped) + //if no area nodes left, address range is now undefined (unmapped) if (obj->vm_area_node_ps.len == 0) { - - obj->end_addr = obj->start_addr = 0x0; + obj->end_addr = obj->start_addr = MC_UNDEF_ADDR; return 0; } @@ -277,7 +275,6 @@ int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node) { obj->start_addr = MC_GET_NODE_AREA(temp_node)->start_addr; - //get end addr index = obj->vm_area_node_ps.len == 1 ? 0 : -1; @@ -563,8 +560,8 @@ int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { //disconnect object node's attributes obj->mapped = false; - obj->start_addr = 0x0; - obj->end_addr = 0x0; + obj->start_addr = MC_UNDEF_ADDR; + obj->end_addr = MC_UNDEF_ADDR; obj_node->next = NULL; obj_node->prev = NULL; diff --git a/src/test/check_map.c b/src/test/check_map.c index 8a10e9d..da5e970 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -159,21 +159,29 @@ static bool _map_check_static(void * ptr, void * arr, int len, size_t ent_sz) { //remove static pointers from a list -static void _map_remove_static(cm_lst * lst, - void * arr, int len, size_t ent_sz) { - - cm_lst_node * temp_node; - cm_lst_node * node = lst->head; +static void _map_remove_static(cm_lst * lst, void * arr, + int len, size_t ent_sz, bool unmapped) { + cm_lst_node * temp_node, * node, * cmp_node; + node = lst->head; + for (int i = 0; i < lst->len; ++i) { + //determine if comparing node or inner node + cmp_node = unmapped ? MC_GET_NODE_PTR(node) : node; + //static node - if (_map_check_static(node, arr, len, ent_sz)) { + if (_map_check_static(cmp_node, arr, len, ent_sz)) { - //adjust iteration & unlink static node + //adjust iteration (part 1) temp_node = node->next; - cm_lst_uln_n(lst, node); + + //handle node + if (unmapped) cm_lst_rmv_n(lst, node); + if (!unmapped) cm_lst_uln_n(lst, node); + + //adjust iteration (part 2) node = temp_node; i -= 1; @@ -385,13 +393,13 @@ static void _teardown_vm_map() { //remove static objects & areas _map_remove_static(&m.vm_objs, m_o_n, - STUB_MAP_OBJ_NUM, sizeof(cm_lst_node)); + STUB_MAP_OBJ_NUM, sizeof(cm_lst_node), false); _map_remove_static(&m.vm_areas, m_a_n, - STUB_MAP_AREA_NUM, sizeof(cm_lst_node)); + STUB_MAP_AREA_NUM, sizeof(cm_lst_node), false); _map_remove_static(&m.vm_objs_unmapped, m_o_n, - STUB_MAP_OBJ_NUM, sizeof(cm_lst_node)); + STUB_MAP_OBJ_NUM, sizeof(cm_lst_node), true); _map_remove_static(&m.vm_areas_unmapped, m_a_n, - STUB_MAP_AREA_NUM, sizeof(cm_lst_node)); + STUB_MAP_AREA_NUM, sizeof(cm_lst_node), true); //call regular destructor @@ -507,8 +515,8 @@ START_TEST(test_mc_new_del_vm_map) { //check the pseudo object is present zero_obj = MC_GET_NODE_OBJ(m.vm_objs.head); - assert_vm_obj(zero_obj, "0x0", "0x0", 0x0, 0x0, - 0, 0, MC_ZERO_OBJ_ID, true); + assert_vm_obj(zero_obj, "0x0", "0x0", + 0x0, 0x0, 0, 0, MC_ZERO_OBJ_ID, true); mc_del_vm_map(&m); @@ -526,7 +534,8 @@ START_TEST(test__map_new_del_vm_obj) { //only test: construct the object _map_new_vm_obj(&obj, &m, "/foo/bar"); - assert_vm_obj(&obj, "/foo/bar", "bar", 0x0, 0x0, 0, 0, 0, true); + assert_vm_obj(&obj, "/foo/bar", "bar", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 0, 0, true); assert_vm_map(&m, 0, 1, 0, 0, 0, 1); return; @@ -690,7 +699,8 @@ START_TEST(test__map_obj_add_last_area) { //first test: add first area to the backing object _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[0]); - assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 1, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 1, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_first, 1); last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head); assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, @@ -705,7 +715,8 @@ START_TEST(test__map_obj_add_last_area) { //second test: add lower area to the backing object _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[1]); - assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 2, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_lower, 2); last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head); assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, @@ -720,7 +731,8 @@ START_TEST(test__map_obj_add_last_area) { //third test: add lower area to the backing object _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[2]); - assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 3, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 3, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_higher, 3); last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head->prev); assert_vm_area(MC_GET_NODE_AREA(last_area_node_ptr), NULL, NULL, @@ -735,7 +747,8 @@ START_TEST(test__map_obj_add_last_area) { //fourth test: add middle area to the backing object _map_obj_add_area_insert(&o.last_vm_area_node_ps, &last_area_node[3]); - assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 4, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 4, 0, true); assert_vm_obj_list(&o.last_vm_area_node_ps, state_middle, 4); last_area_node_ptr = MC_GET_NODE_PTR(o.last_vm_area_node_ps.head->next->next); @@ -785,7 +798,8 @@ START_TEST(test__map_obj_rmv_area) { ret = _map_obj_rmv_area(&o, &o_a_n[2]); ck_assert_int_eq(ret, 0); - assert_vm_obj(&o, "/foo/bar", "bar", 0x0, 0x0, 0, 2, 0, true); + assert_vm_obj(&o, "/foo/bar", "bar", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 2, 0, true); assert_vm_obj_list(&o.vm_area_node_ps, NULL, 0); return; @@ -1051,7 +1065,7 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { } END_TEST -#endif/* + //_map_unlink_unmapped_obj() [stub map fixture] START_TEST(test__map_unlink_unmapped_obj) { @@ -1066,7 +1080,7 @@ START_TEST(test__map_unlink_unmapped_obj) { }; struct obj_check unmapped_obj_state[1] = { //start index: 0 - {"foo", 0x8000, 0xB000} + {"foo", MC_UNDEF_ADDR, MC_UNDEF_ADDR} }; @@ -1075,24 +1089,25 @@ START_TEST(test__map_unlink_unmapped_obj) { ck_assert_int_eq(ret, 0); //check `/lib/foo` has no last areas associated with it, and is unmapped - assert_vm_obj(&m_o[2], "/lib/foo", "foo", 0x8000, 0xB000, 3, 0, 2, false); + assert_vm_obj(&m_o[2], "/lib/foo", "foo", + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 3, 0, 2, false); assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, NULL, 0); - //check `[heap]` has no last areas associated with it + //check `[heap]` has 2 last areas associated with it assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 2); //check state of mapped objects - assert_vm_map_objs(&m.vm_objs, obj_state, 0, 4); + assert_vm_map_objs(&m.vm_objs, obj_state, 0, 4, true); //check state of unmapped objects - assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 0, 1); + assert_vm_map_objs(&m.vm_objs_unmapped, unmapped_obj_state, 0, 1, false); //check removed object has no links to other objects anymore ck_assert(m_o[2].mapped == false); - ck_assert_int_eq(m_o[2].start_addr, 0x0); - ck_assert_int_eq(m_o[2].end_addr, 0x0); + ck_assert_int_eq(m_o[2].start_addr, MC_UNDEF_ADDR); + ck_assert_int_eq(m_o[2].end_addr, MC_UNDEF_ADDR); ck_assert_ptr_null(m_o_n[2].next); ck_assert_ptr_null(m_o_n[2].prev); @@ -1133,7 +1148,7 @@ START_TEST(test__map_unlink_unmapped_area) { }; struct obj_check second_objs_unmapped[1] = { //start index: 0 - {"[heap]", 0x0, 0x0} + {"[heap]", MC_UNDEF_ADDR, MC_UNDEF_ADDR} }; //remove [heap]: area state: @@ -1156,10 +1171,12 @@ START_TEST(test__map_unlink_unmapped_area) { 0x8000, 0x9000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); - assert_vm_map_objs(&m.vm_objs_unmapped, NULL, 0, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 4, 3); - assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 1); + assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + NULL, 0, 0, false); + assert_vm_map_areas(&m.vm_areas, first_areas, 4, 3, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + first_areas_unmapped, 0, 1, false); //second test: remove only area of '[heap]' @@ -1170,16 +1187,18 @@ START_TEST(test__map_unlink_unmapped_area) { 0x4000, 0x5000, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL, NULL, 5, false); - assert_vm_map_objs(&m.vm_objs, second_objs, 1, 2); - assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); - assert_vm_map_areas(&m.vm_areas, second_areas, 2, 2); - assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 2); + assert_vm_map_objs(&m.vm_objs, second_objs, 1, 2, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + second_objs_unmapped, 0, 1, false); + assert_vm_map_areas(&m.vm_areas, second_areas, 2, 2, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + second_areas_unmapped, 0, 2, false); return; } END_TEST - +#endif/* //_map_check_area_eql() [empty map fixture] START_TEST(test__map_check_area_eql) { @@ -1365,18 +1384,18 @@ START_TEST(test__map_resync_area) { //remove /bin/cat:1,2,3: object state struct obj_check third_objs[2] = { //start index: 1 - {"foo", 0xA000, 0xB000}, + {"foo", 0xA000, 0xB000}, {"[stack]", 0xE000, 0xF000} }; struct obj_check third_objs_unmapped[2] = { //start index: 0 - {"cat", 0x0, 0x0}, - {"[heap]", 0x0, 0x0} + {"cat", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, + {"[heap]", MC_UNDEF_ADDR, MC_UNDEF_ADDR} }; //remove /bin/cat:1,2,3: area state struct area_check third_areas[2] = { //start index: 0 - {"", 0x6000, 0x7000}, + {"", 0x6000, 0x7000}, {"foo", 0xA000, 0xB000} }; @@ -1410,10 +1429,12 @@ START_TEST(test__map_resync_area) { assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/bin/cat", "cat", 0x1000, 0x3000, 3, 1, 0, true); - assert_vm_map_objs(&m.vm_objs, first_objs, 1, 2); - assert_vm_map_objs(&m.vm_objs_unmapped, first_objs_unmapped, 0, 1); - assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); - assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 3); + assert_vm_map_objs(&m.vm_objs, first_objs, 1, 2, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + first_objs_unmapped, 0, 1, false); + assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + first_areas_unmapped, 0, 3, false); @@ -1433,10 +1454,12 @@ START_TEST(test__map_resync_area) { assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", 0xA000, 0xB000, 1, 1, 2, true); - assert_vm_map_objs(&m.vm_objs, second_objs, 1, 3); - assert_vm_map_objs(&m.vm_objs_unmapped, second_objs_unmapped, 0, 1); - assert_vm_map_areas(&m.vm_areas, second_areas, 3, 3); - assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 3); + assert_vm_map_objs(&m.vm_objs, second_objs, 1, 3, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + second_objs_unmapped, 0, 1, false); + assert_vm_map_areas(&m.vm_areas, second_areas, 3, 3, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + second_areas_unmapped, 0, 3, false); @@ -1456,10 +1479,12 @@ START_TEST(test__map_resync_area) { assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); - assert_vm_map_objs(&m.vm_objs, third_objs, 1, 2); - assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 2); - assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); - assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 6); + assert_vm_map_objs(&m.vm_objs, third_objs, 1, 2, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + third_objs_unmapped, 0, 2, false); + assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + third_areas_unmapped, 0, 6, false); return; @@ -1497,7 +1522,7 @@ START_TEST(test__map_add_obj) { ret_node = _map_add_obj(&entry, &state, &m); ck_assert_ptr_nonnull(ret_node); - assert_vm_map_objs(&m.vm_objs, first_objs, 3, 3); + assert_vm_map_objs(&m.vm_objs, first_objs, 3, 3, true); assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 0); @@ -1508,7 +1533,7 @@ START_TEST(test__map_add_obj) { ret_node = _map_add_obj(&entry, &state, &m); ck_assert_ptr_nonnull(ret_node); - assert_vm_map_objs(&m.vm_objs, second_objs, 3, 2); + assert_vm_map_objs(&m.vm_objs, second_objs, 3, 2, true); assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 1); return; @@ -1569,8 +1594,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 7, 3); - assert_vm_map_objs(&m.vm_objs, first_objs, 3, 2); + assert_vm_map_areas(&m.vm_areas, first_areas, 7, 3, true); + assert_vm_map_objs(&m.vm_objs, first_objs, 3, 2, true); //second test: add area without a backing object before `/lib/foo` @@ -1580,8 +1605,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 6, 3); - assert_vm_map_objs(&m.vm_objs, first_objs, 2, 2); + assert_vm_map_areas(&m.vm_areas, first_areas, 6, 3, true); + assert_vm_map_objs(&m.vm_objs, first_objs, 2, 2, true); //third test: add an area that creates a new object `/lib/bar` @@ -1591,8 +1616,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3); - assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3); + assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3, true); + assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3, true); return; @@ -1644,7 +1669,7 @@ START_TEST(test_map_send_entry) { }; struct obj_check third_objs_unmapped[1] = { //start index: 0 - {"cat", 0x0, 0x0}, + {"cat", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, }; @@ -1667,7 +1692,7 @@ START_TEST(test_map_send_entry) { }; struct obj_check fourth_objs_unmapped[1] = { //start index: 0 - {"cat", 0x0, 0x0}, + {"cat", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, }; int ret; @@ -1683,8 +1708,8 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 9, 2); - assert_vm_map_objs(&m.vm_objs, first_objs, 4, 2); + assert_vm_map_areas(&m.vm_areas, first_areas, 9, 2, true); + assert_vm_map_objs(&m.vm_objs, first_objs, 4, 2, true); //second test: send another "/bin/dog" area to the end of the map @@ -1694,8 +1719,8 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, second_areas, 9, 3); - assert_vm_map_objs(&m.vm_objs, second_objs, 4, 2); + assert_vm_map_areas(&m.vm_areas, second_areas, 9, 3, true); + assert_vm_map_objs(&m.vm_objs, second_objs, 4, 2, true); //third test: send a "[heap]" entry to the start of the map @@ -1706,10 +1731,12 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2); - assert_vm_map_objs(&m.vm_objs, third_objs, 0, 3); - assert_vm_map_areas(&m.vm_areas_unmapped, third_areas_unmapped, 0, 3); - assert_vm_map_objs(&m.vm_objs_unmapped, third_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, third_areas, 0, 2, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + third_areas_unmapped, 0, 3, false); + assert_vm_map_objs(&m.vm_objs, third_objs, 0, 3, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + third_objs_unmapped, 0, 1, false); //fourth test: send a correct entry after the "[heap]" entry @@ -1720,10 +1747,12 @@ START_TEST(test_map_send_entry) { ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, fourth_areas, 0, 3); - assert_vm_map_objs(&m.vm_objs, fourth_objs, 0, 3); - assert_vm_map_areas(&m.vm_areas_unmapped, fourth_areas_unmapped, 0, 3); - assert_vm_map_objs(&m.vm_objs_unmapped, fourth_objs_unmapped, 0, 1); + assert_vm_map_areas(&m.vm_areas, fourth_areas, 0, 3, true); + assert_vm_map_areas(&m.vm_areas_unmapped, + fourth_areas_unmapped, 0, 3, false); + assert_vm_map_objs(&m.vm_objs, fourth_objs, 0, 3, true); + assert_vm_map_objs(&m.vm_objs_unmapped, + fourth_objs_unmapped, 0, 1, false); return; @@ -1819,9 +1848,9 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc__find_obj_for_area; TCase * tc__backtrack_unmapped_obj_last_vm_areas; TCase * tc__forward_unmapped_obj_last_vm_areas; - #endif/*TCase * tc__unlink_unmapped_obj; + TCase * tc__unlink_unmapped_obj; TCase * tc__unlink_unmapped_area; - TCase * tc__check_area_eql; + #endif/*TCase * tc__check_area_eql; TCase * tc__state_inc_area; TCase * tc__state_inc_obj; TCase * tc__resync_area; @@ -1909,7 +1938,7 @@ START_TEST(test_mc_map_clean_unmapped) { _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__forward_unmapped_obj_last_vm_areas, test__map_forward_unmapped_obj_last_vm_areas); - #endif/* + //tc__unlink_unmapped_obj tc__unlink_unmapped_obj = tcase_create("_unlink_unmapped_obj"); tcase_add_checked_fixture(tc__unlink_unmapped_obj, @@ -1921,7 +1950,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_checked_fixture(tc__unlink_unmapped_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__unlink_unmapped_area, test__map_unlink_unmapped_area); - + #endif/* //tc__check_area_eql tc__check_area_eql = tcase_create("_check_area_eql"); tcase_add_checked_fixture(tc__check_area_eql, @@ -1992,9 +2021,9 @@ START_TEST(test_mc_map_clean_unmapped) { suite_add_tcase(s, tc__find_obj_for_area); suite_add_tcase(s, tc__backtrack_unmapped_obj_last_vm_areas); suite_add_tcase(s, tc__forward_unmapped_obj_last_vm_areas); - #endif/*suite_add_tcase(s, tc__unlink_unmapped_obj); + suite_add_tcase(s, tc__unlink_unmapped_obj); suite_add_tcase(s, tc__unlink_unmapped_area); - suite_add_tcase(s, tc__check_area_eql); + #endif/*suite_add_tcase(s, tc__check_area_eql); suite_add_tcase(s, tc__state_inc_area); suite_add_tcase(s, tc__state_inc_obj); suite_add_tcase(s, tc__resync_area); diff --git a/src/test/map_helper.c b/src/test/map_helper.c index e332d7a..2e11ceb 100644 --- a/src/test/map_helper.c +++ b/src/test/map_helper.c @@ -101,16 +101,33 @@ void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, } +/* + * NOTE: The mapped area & object lists store areas and objects directly. + * Unmapped area & object lists store unmapped nodes instead, which + * means an additional pointer dereference is required. + */ + //assert the state of all [unmapped] objects inside a mc_vm_map -void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, - int start_index, int len) { +void assert_vm_map_objs(cm_lst * lst, struct obj_check * obj_checks, + int start_index, int len, bool mapped) { + + int ret; + cm_lst_node * node; mc_vm_obj * obj; + for (int i = 0; i < len; ++i) { - obj = cm_lst_get_p(obj_lst, start_index + i); - ck_assert_ptr_nonnull(obj); + if (!mapped) { + ret = cm_lst_get(lst, start_index + i, &node); + ck_assert_int_eq(ret, 0); + obj = MC_GET_NODE_OBJ(node); + + } else { + obj = cm_lst_get_p(lst, start_index + i); + ck_assert_ptr_nonnull(obj); + } assert_names(obj->basename, obj_checks[i].basename); ck_assert_int_eq(obj->start_addr, obj_checks[i].start_addr); @@ -122,15 +139,26 @@ void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, //assert only pathnames, not mapped address ranges -void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[NAME_MAX], - int start_index, int len) { +void assert_vm_map_objs_aslr(cm_lst * lst, char * basenames[NAME_MAX], + int start_index, int len, bool mapped) { + + int ret; + cm_lst_node * node; mc_vm_obj * obj; + for (int i = 0; i < len; ++i) { - obj = cm_lst_get_p(obj_lst, start_index + i); - ck_assert_ptr_nonnull(obj); + if (!mapped) { + ret = cm_lst_get(lst, start_index + i, &node); + ck_assert_int_eq(ret, 0); + obj = MC_GET_NODE_OBJ(node); + + } else { + obj = cm_lst_get_p(lst, start_index + i); + ck_assert_ptr_nonnull(obj); + } assert_names(obj->basename, basenames[i]); } @@ -140,15 +168,25 @@ void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[NAME_MAX], //assert the state of all [unmapped] memory areas inside a mc_vm_map -void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, - int start_index, int len) { +void assert_vm_map_areas(cm_lst * lst, struct area_check * area_checks, + int start_index, int len, bool mapped) { + + int ret; + cm_lst_node * node; mc_vm_area * area; for (int i = 0; i < len; ++i) { - area = cm_lst_get_p(area_lst, start_index + i); - ck_assert_ptr_nonnull(area); + if (!mapped) { + ret = cm_lst_get(lst, start_index + i, &node); + ck_assert_int_eq(ret, 0); + area = MC_GET_NODE_AREA(node); + + } else { + area = cm_lst_get_p(lst, start_index + i); + ck_assert_ptr_nonnull(area); + } assert_names(area->basename, area_checks[i].basename); ck_assert_int_eq(area->start_addr, area_checks[i].start_addr); @@ -160,15 +198,25 @@ void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, //assert only pathnames, not mapped address ranges -void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[NAME_MAX], - int start_index, int len) { +void assert_vm_map_areas_aslr(cm_lst * lst, char * basenames[NAME_MAX], + int start_index, int len, bool mapped) { + + int ret; + cm_lst_node * node; mc_vm_area * area; for (int i = 0; i < len; ++i) { - area = cm_lst_get_p(area_lst, start_index + i); - ck_assert_ptr_nonnull(area); + if (!mapped) { + ret = cm_lst_get(lst, start_index + i, &node); + ck_assert_int_eq(ret, 0); + area = MC_GET_NODE_AREA(node); + + } else { + area = cm_lst_get_p(lst, start_index + i); + ck_assert_ptr_nonnull(area); + } assert_names(area->basename, basenames[i]); } diff --git a/src/test/map_helper.h b/src/test/map_helper.h index cd97820..b49c904 100644 --- a/src/test/map_helper.h +++ b/src/test/map_helper.h @@ -42,15 +42,15 @@ void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, int vm_areas_unmapped_len, int vm_objs_unmapped_len, int next_id_area, int next_id_obj); -void assert_vm_map_objs(cm_lst * obj_lst, struct obj_check * obj_checks, - int start_index, int len); -void assert_vm_map_objs_aslr(cm_lst * obj_lst, char * basenames[NAME_MAX], - int start_index, int len); - -void assert_vm_map_areas(cm_lst * area_lst, struct area_check * area_checks, - int start_index, int len); -void assert_vm_map_areas_aslr(cm_lst * area_lst, char * basenames[NAME_MAX], - int start_index, int len); +void assert_vm_map_objs(cm_lst * lst, struct obj_check * obj_checks, + int start_index, int len, bool mapped); +void assert_vm_map_objs_aslr(cm_lst * lst, char * basenames[NAME_MAX], + int start_index, int len, bool mapped); + +void assert_vm_map_areas(cm_lst * lst, struct area_check * area_checks, + int start_index, int len, bool mapped); +void assert_vm_map_areas_aslr(cm_lst * lst, char * basenames[NAME_MAX], + int start_index, int len, bool mapped); void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, uintptr_t start_addr, uintptr_t end_addr, int vm_areas_len, From e31571cb577ef2b85c1304a777dfcbfe7550727a Mon Sep 17 00:00:00 2001 From: vykt Date: Tue, 25 Feb 2025 00:55:15 +0000 Subject: [PATCH 17/45] Add GDB scripts & make map tests pass --- TODO | 4 +- build/test/init.gdb | 255 ++++++++++++++++++++++++++++++++++++++- src/lib/map.c | 73 +++++++---- src/lib/map.h | 6 +- src/lib/memcry.h | 4 +- src/test/check_map.c | 174 ++++++++++++++------------ src/test/map_helper.c | 26 ++-- src/test/target_helper.c | 12 +- 8 files changed, 432 insertions(+), 122 deletions(-) diff --git a/TODO b/TODO index 64b3a6b..b1cbe3b 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ [bugs]: -- DRY up interface read & writes, almost the same block of code repeated - 4 times in reading/writing loop. +- Change error code `LIBCMORE` -> `CMORE` + [features]: - add 3rd interface that uses process_vm_readv/process_vm_writev diff --git a/build/test/init.gdb b/build/test/init.gdb index 6c48086..3fb9638 100644 --- a/build/test/init.gdb +++ b/build/test/init.gdb @@ -1,3 +1,4 @@ +# ask libcheck to not fork each unit test when GDB is attached set environment CK_FORK=no # print vm_area at a node @@ -81,5 +82,257 @@ define pxnodeo end end -tb test__map_unlink_unmapped_obj_fn +# print areas of a map +define pmapa + if $argc != 1 + printf "Use: pmapa <*map>\n" + else + # print header + printf " --- [AREAS] ---\n" + + # bootstrap iteration + set $iter = 0 + set $iter_node = $arg0->vm_areas.head + + # for every area + while $iter != $arg0->vm_areas.len + + # fetch & typecast next area + set $area = ((mc_vm_area *) ($iter_node->data)) + + # fetch relevant entries for this area + set $id = $area->id + set $basename = $area->basename + set $start_addr = $area->start_addr + set $end_addr = $area->end_addr + + # fetch area's object id if one is present + if $area->obj_node_p != 0 + set $obj_id = ((mc_vm_obj *) ($area->obj_node_p->data))->id + else + set $obj_id = -1337 + end + + # fetch area's last object i if one is present + if $area->last_obj_node_p != 0 + set $last_obj_id = ((mc_vm_obj *) ($area->last_obj_node_p->data))->id + else + set $last_obj_id = -1337 + end + + # print relevant entries of this area + printf "%-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $obj_id, $last_obj_id, $basename + + # advance iteration + set $iter = $iter + 1 + set $iter_node = $iter_node->next + end + end +end + +# print unmapped areas of a map +define pmapua + if $argc != 1 + printf "Use: pmapua <*map>\n" + else + # print header + printf " --- [UNMAPPED AREAS] ---\n" + + # bootstrap iteration + set $iter = 0 + set $iter_node = $arg0->vm_areas_unmapped.head + + # for every area + while $iter != $arg0->vm_areas_unmapped.len + + # fetch & typecast next area + set $area = ((mc_vm_area *) ((*((cm_lst_node **) ($iter_node->data)))->data)) + + # fetch relevant entries for this area + set $id = $area->id + set $basename = $area->basename + set $start_addr = $area->start_addr + set $end_addr = $area->end_addr + + # fetch area's object id if one is present + if $area->obj_node_p != 0 + set $obj_id = ((mc_vm_obj *) ($area->obj_node_p->data))->id + else + set $obj_id = -1337 + end + + # fetch area's last object i if one is present + if $area->last_obj_node_p != 0 + set $last_obj_id = ((mc_vm_obj *) ($area->last_obj_node_p->data))->id + else + set $last_obj_id = -1337 + end + + # print relevant entries of this area + printf "%-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $obj_id, $last_obj_id, $basename + + # advance iteration + set $iter = $iter + 1 + set $iter_node = $iter_node->next + end + end +end + +# print objs of a map +define pmapo + if $argc != 1 + printf "Use: pmapo <*map>\n" + else + # print header + printf " --- [OBJS] ---\n" + + # bootstrap iteration over objects + set $iter = 0 + set $iter_node = $arg0->vm_objs.head + + # for every object + while $iter != $arg0->vm_objs.len + + # fetch & typecast next object + set $obj = ((mc_vm_obj *) ($iter_node->data)) + + # fetch relevant entries of this object + set $id = $obj->id + set $basename = $obj->basename + set $start_addr = $obj->start_addr + set $end_addr = $obj->end_addr + + # print relevant entries of this object + printf "<%-3d: 0x%lx - 0x%lx | \"%s\">\n", $id, $start_addr, $end_addr, $basename + + # setup iteration over areas belonging to this object + set $inner_iter = 0 + set $inner_iter_node = ((mc_vm_obj *) ($iter_node->data))->vm_area_node_ps.head + + # for every area that is part of this object + printf " [areas]:\n" + while $inner_iter != ((mc_vm_obj *) ($iter_node->data))->vm_area_node_ps.len + + # fetch and typecast next area + set $inner_area = ((mc_vm_area *) ((*((cm_lst_node **) ($inner_iter_node->data)))->data)) + + # fetch relevant entries of this area + set $id = $inner_area->id + set $basename = $inner_area->basename + set $start_addr = $inner_area->start_addr + set $end_addr = $inner_area->end_addr + + #fetch area's object id if one is present + if $inner_area->obj_node_p != 0 + set $inner_obj_id = ((mc_vm_obj *) ($inner_area->obj_node_p->data))->id + else + set $inner_obj_id = -1337 + end + + #fetch area's last object i if one is present + if $inner_area->last_obj_node_p != 0 + set $inner_last_obj_id = ((mc_vm_obj *) ($inner_area->last_obj_node_p->data))->id + else + set $inner_last_obj_id = -1337 + end + + # print relevant entries of this area + printf " %-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $inner_obj_id, $inner_last_obj_id, $basename + + # advance iteration over areas + set $inner_iter = $inner_iter + 1 + set $inner_iter_node = $inner_iter_node->next + end + + # setup iteration over last areas belonging to this object + set $inner_iter = 0 + set $inner_iter_node = ((mc_vm_obj *) ($iter_node->data))->last_vm_area_node_ps.head + + # for every area that is part of this object + printf " [last areas]:\n" + while $inner_iter != ((mc_vm_obj *) ($iter_node->data))->last_vm_area_node_ps.len + + # fetch and typecast next area + set $inner_area = ((mc_vm_area *) ((*((cm_lst_node **) ($inner_iter_node->data)))->data)) + + # fetch relevant entries of this area + set $id = $inner_area->id + set $basename = $inner_area->basename + set $start_addr = $inner_area->start_addr + set $end_addr = $inner_area->end_addr + + #fetch area's object id if one is present + if $inner_area->obj_node_p != 0 + set $inner_obj_id = ((mc_vm_obj *) ($inner_area->obj_node_p->data))->id + else + set $inner_obj_id = -1337 + end + + #fetch last area's object i if one is present + if $inner_area->last_obj_node_p != 0 + set $inner_last_obj_id = ((mc_vm_obj *) ($inner_area->last_obj_node_p->data))->id + else + set $inner_last_obj_id = -1337 + end + + # print relevant entries of this area + printf " %-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $inner_obj_id, $inner_last_obj_id, $basename + + # advance iteration over areas + set $inner_iter = $inner_iter + 1 + set $inner_iter_node = $inner_iter_node->next + end + + # advance iteration over objects + set $iter = $iter + 1 + set $iter_node = $iter_node->next + end + end +end + +# print unmapped objs of a map +define pmapuo + if $argc != 1 + printf "Use: pmapuo <*map>\n" + else + # print header + printf " --- [UNMAPPED OBJS] ---\n" + + # bootstrap iteration over objects + set $iter = 0 + set $iter_node = $arg0->vm_objs_unmapped.head + + # for every object + while $iter != $arg0->vm_objs_unmapped.len + + # fetch & typecast next object + set $obj = ((mc_vm_obj *) ((*((cm_lst_node **) ($iter_node->data)))->data)) + + # fetch relevant entries of this object + set $id = $obj->id + set $basename = $obj->basename + set $start_addr = $obj->start_addr + set $end_addr = $obj->end_addr + set $num_area = $obj->vm_area_node_ps.len + set $num_last_area = $obj->last_vm_area_node_ps.len + + # print relevant entries with conversion to MC_UNDEF_ADDR + if ($start_addr == -1) && ($end_addr == -1) + printf "<%-3d: MC_UNDEF_ADDR - MC_UNDEF_ADDR | areas: %d - last areas: %d | \"%s\">\n", $id, $num_area, $num_last_area, $basename + else + printf "<%-3d: 0x%lx - 0x%lx | areas: %d - last areas: %d | \"%s\">\n", $id, $start_addr, $end_addr, $num_area, $num_last_area, $basename + end + + # advance iteration over objects + set $iter = $iter + 1 + set $iter_node = $iter_node->next + end + end +end + + +# session dependent +tb main +layout src + run diff --git a/src/lib/map.c b/src/lib/map.c index 74bd4ac..15bd93d 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -528,7 +528,8 @@ int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { DBG_STATIC DBG_INLINE -int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { +int _map_unlink_unmapped_obj(cm_lst_node * obj_node, + _traverse_state * state, mc_vm_map * map) { int ret; cm_lst_node * ret_node; @@ -536,6 +537,11 @@ int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { mc_vm_obj * obj; + //backtrack traverse state if it is currently on this object + if (obj_node == state->prev_obj_node) { + state->prev_obj_node = state->prev_obj_node->prev; + } + //fetch object obj = MC_GET_NODE_OBJ(obj_node); @@ -577,7 +583,8 @@ int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map) { DBG_STATIC DBG_INLINE -int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map) { +int _map_unlink_unmapped_area(cm_lst_node * area_node, + _traverse_state * state, mc_vm_map * map) { int ret; cm_lst_node * ret_node; @@ -604,7 +611,7 @@ int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map) { //if this area's object now has no areas, unmap it if (obj->vm_area_node_ps.len == 0) { - ret = _map_unlink_unmapped_obj(obj_node, map); + ret = _map_unlink_unmapped_obj(obj_node, state, map); if (ret) return -1; } } @@ -683,12 +690,11 @@ void _map_state_inc_area(_traverse_state * state, const int inc_type, //advance next area if we haven't reached the end //& dont circle back to the start if (state->next_area_node != NULL - && state->next_area_node != map->vm_areas.head) { + && state->next_area_node->next != map->vm_areas.head) { state->next_area_node = state->next_area_node->next; } else { state->next_area_node = NULL; - } break; @@ -708,15 +714,12 @@ void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map) { //if there is no prev obj, initialise it if (state->prev_obj_node == NULL) { - state->prev_obj_node = map->vm_objs.head; //if there is a prev obj } else { - //only advance next object if we won't circle back to start if (state->prev_obj_node->next != map->vm_objs.head) { - state->prev_obj_node = state->prev_obj_node->next; } } @@ -725,6 +728,7 @@ void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map) { } +//return: 0 - do not re-add, 1 - do re-add, -1 - error DBG_STATIC DBG_INLINE int _map_resync_area(const struct vm_entry * entry, _traverse_state * state, mc_vm_map * map) { @@ -740,13 +744,14 @@ int _map_resync_area(const struct vm_entry * entry, area = MC_GET_NODE_AREA(area_node); //while there are vm areas left to discard - while (entry->vm_end > area->start_addr) { + while (entry->vm_end > area->start_addr + && _map_check_area_eql(entry, area_node) == -1) { //advance state _map_state_inc_area(state, _STATE_AREA_NODE_ADVANCE, NULL, map); //remove this area node - ret = _map_unlink_unmapped_area(area_node, map); + ret = _map_unlink_unmapped_area(area_node, state, map); if (ret) return -1; //update iterator nodes @@ -756,6 +761,7 @@ int _map_resync_area(const struct vm_entry * entry, } //end while + if (entry->vm_end > area->start_addr) return 1; return 0; } @@ -778,15 +784,6 @@ cm_lst_node * _map_add_obj(const struct vm_entry * entry, return NULL; } - /* - * With the insertion of this object, it may now be closer to some - * memory areas without a backing object. For such memory areas, their - * `last_obj_node_p` pointer must be updated. - * - */ - - _map_forward_unmapped_obj_last_vm_areas(obj_node); - //advance state _map_state_inc_obj(state, map); @@ -800,6 +797,7 @@ int _map_add_area(const struct vm_entry * entry, int ret; bool use_obj; + bool forward_obj = false; mc_vm_area area; cm_lst_node * area_node; @@ -839,6 +837,7 @@ int _map_add_area(const struct vm_entry * entry, case _MAP_OBJ_NEW: obj_node = _map_add_obj(entry, state, map); if (obj_node == NULL) return -1; + forward_obj = true; break; //area belongs to the next object @@ -855,7 +854,19 @@ int _map_add_area(const struct vm_entry * entry, //add area to the map list - area_node = cm_lst_ins_nb(&map->vm_areas, state->next_area_node, &area); + if (state->next_area_node == NULL) { + + //if map is empty, insert at start + if (map->vm_areas.head == NULL) { + area_node = cm_lst_ins(&map->vm_areas, 0, &area); + } else { + area_node = cm_lst_ins(&map->vm_areas, -1, &area); + } + + } else { + area_node = cm_lst_ins_nb(&map->vm_areas, + state->next_area_node, &area); + } if (area_node == NULL) { mc_errno = MC_ERR_LIBCMORE; return -1; @@ -872,6 +883,15 @@ int _map_add_area(const struct vm_entry * entry, if (ret == -1) return -1; } + /* + * Only now that the new object has an area associated with it (and hence + * a valid address range) can areas be forwarded to it. + */ + if (forward_obj) { + ret = _map_forward_unmapped_obj_last_vm_areas(state->prev_obj_node); + if (ret == -1) return -1; + } + //increment area state _map_state_inc_area(state, _STATE_AREA_NODE_REASSIGN, area_node->next, map); @@ -891,7 +911,8 @@ int map_send_entry(const struct vm_entry * entry, //if reached the end of the old map - if (state->next_area_node == NULL) { + if (state->next_area_node == NULL + || state->next_area_node->next == map->vm_areas.head) { ret = _map_add_area(entry, state, map); if (ret == -1) return -1; @@ -900,14 +921,16 @@ int map_send_entry(const struct vm_entry * entry, } else { //if entry doesn't match next area (a change in the map) - if (_map_check_area_eql(entry, state->next_area_node)) { + if (_map_check_area_eql(entry, state->next_area_node) == -1) { ret = _map_resync_area(entry, state, map); - if (ret) return -1; - - ret = _map_add_area(entry, state, map); if (ret == -1) return -1; + if (ret == 0) { + ret = _map_add_area(entry, state, map); + if (ret == -1) return -1; + } + // else entry matches next area } else { diff --git a/src/lib/map.h b/src/lib/map.h index a1fc7c8..1ac4bb5 100644 --- a/src/lib/map.h +++ b/src/lib/map.h @@ -62,8 +62,10 @@ int _map_find_obj_for_area(const struct vm_entry * entry, int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); int _map_forward_unmapped_obj_last_vm_areas(cm_lst_node * obj_node); -int _map_unlink_unmapped_obj(cm_lst_node * obj_node, mc_vm_map * map); -int _map_unlink_unmapped_area(cm_lst_node * area_node, mc_vm_map * map); +int _map_unlink_unmapped_obj(cm_lst_node * obj_node, + _traverse_state * state, mc_vm_map * map); +int _map_unlink_unmapped_area(cm_lst_node * area_node, + _traverse_state * state, mc_vm_map * map); int _map_check_area_eql(const struct vm_entry * entry, const cm_lst_node * area_node); diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 3086088..5cc4794 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -74,8 +74,8 @@ typedef struct { uintptr_t start_addr; uintptr_t end_addr; - cm_lst vm_area_node_ps; //STORES: cm_list_node * of mc_vm_area - cm_lst last_vm_area_node_ps; //STORES: cm_list_node * of mc_vm_area + cm_lst vm_area_node_ps; //STORES: cm_lst_node * of mc_vm_area + cm_lst last_vm_area_node_ps; //STORES: cm_lst_node * of mc_vm_area int id; bool mapped; //set to false when a map update discovers obj. to be unmapped diff --git a/src/test/check_map.c b/src/test/check_map.c index da5e970..739d2c5 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -230,7 +230,7 @@ static void _setup_stub_vm_map() { * 2) 0x3000 - 0x4000 /bin/cat r-x * 3) 0x4000 - 0x5000 [heap] rw- <- gap * 4) 0x6000 - 0x7000 rw- <- gap - * 5) 0x8000 - 0x9000 /lib/foo rw- + * 5) 0x8000 - 0x9000 /lib/foo r-- * 6) 0x9000 - 0xA000 /lib/foo rw- * 7) 0xA000 - 0xB000 /lib/foo r-x <- gap * 8) 0xC000 - 0xD000 rw- <- gap @@ -1070,6 +1070,8 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { START_TEST(test__map_unlink_unmapped_obj) { int ret; + _traverse_state state; + uintptr_t heap_state[2] = {0x6000, 0xC000}; struct obj_check obj_state[4] = { //start index: 0 @@ -1085,7 +1087,9 @@ START_TEST(test__map_unlink_unmapped_obj) { //only test: unlink `/lib/foo` - ret = _map_unlink_unmapped_obj(&m_o_n[2], &m); + state.prev_obj_node = &m_o_n[2]; + + ret = _map_unlink_unmapped_obj(&m_o_n[2], &state, &m); ck_assert_int_eq(ret, 0); //check `/lib/foo` has no last areas associated with it, and is unmapped @@ -1097,7 +1101,6 @@ START_TEST(test__map_unlink_unmapped_obj) { assert_vm_obj(&m_o[1], "[heap]", "[heap]", 0x4000, 0x5000, 1, 2, 1, true); assert_vm_obj_list(&m_o[1].last_vm_area_node_ps, heap_state, 2); - //check state of mapped objects assert_vm_map_objs(&m.vm_objs, obj_state, 0, 4, true); @@ -1111,6 +1114,9 @@ START_TEST(test__map_unlink_unmapped_obj) { ck_assert_ptr_null(m_o_n[2].next); ck_assert_ptr_null(m_o_n[2].prev); + //check previous object state has been backtracked + ck_assert_ptr_eq(state.prev_obj_node, &m_o_n[1]); + return; } END_TEST @@ -1120,7 +1126,7 @@ START_TEST(test__map_unlink_unmapped_obj) { START_TEST(test__map_unlink_unmapped_area) { int ret; - + _traverse_state state; //remove /lib/foo:1: object state struct obj_check first_objs[3] = { //start index: 2 @@ -1164,12 +1170,14 @@ START_TEST(test__map_unlink_unmapped_area) { //first test: remove first area of `/lib/foo` - ret = _map_unlink_unmapped_area(&m_a_n[6], &m); + state.prev_obj_node = &m_o_n[1]; + + ret = _map_unlink_unmapped_area(&m_a_n[5], &state, &m); ck_assert_int_eq(ret, 0); assert_vm_area(&m_a[5], "/lib/foo", "foo", - 0x8000, 0x9000, MC_ACCESS_READ | MC_ACCESS_WRITE, - NULL, NULL, 5, false); + 0x8000, 0x9000, MC_ACCESS_READ, + NULL, NULL, 5, false); assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3, true); assert_vm_map_objs(&m.vm_objs_unmapped, @@ -1178,14 +1186,18 @@ START_TEST(test__map_unlink_unmapped_area) { assert_vm_map_areas(&m.vm_areas_unmapped, first_areas_unmapped, 0, 1, false); + ck_assert_ptr_eq(state.prev_obj_node, &m_o_n[1]); + //second test: remove only area of '[heap]' - ret = _map_unlink_unmapped_area(&m_a_n[3], &m); + state.prev_obj_node = &m_o_n[0]; + + ret = _map_unlink_unmapped_area(&m_a_n[3], &state, &m); ck_assert_int_eq(ret, 0); assert_vm_area(&m_a[3], "[heap]", "[heap]", 0x4000, 0x5000, MC_ACCESS_READ | MC_ACCESS_WRITE, - NULL, NULL, 5, false); + NULL, NULL, 3, false); assert_vm_map_objs(&m.vm_objs, second_objs, 1, 2, true); assert_vm_map_objs(&m.vm_objs_unmapped, @@ -1194,11 +1206,13 @@ START_TEST(test__map_unlink_unmapped_area) { assert_vm_map_areas(&m.vm_areas_unmapped, second_areas_unmapped, 0, 2, false); + ck_assert_ptr_eq(state.prev_obj_node, &m_o_n[0]); + return; } END_TEST -#endif/* + //_map_check_area_eql() [empty map fixture] START_TEST(test__map_check_area_eql) { @@ -1260,14 +1274,14 @@ START_TEST(test__map_check_area_eql) { //sixth test: entry has a path, area does not area.pathname = NULL; ret = _map_check_area_eql(&entry, &area_node); - ck_assert_int_eq(ret, 0); + ck_assert_int_eq(ret, -1); area.pathname = obj.pathname; //seventh test: entry does not have a path, area does entry.file_path[0] = '\0'; ret = _map_check_area_eql(&entry, &area_node); - ck_assert_int_eq(ret, 0); + ck_assert_int_eq(ret, -1); entry.file_path[0] = '/'; @@ -1298,7 +1312,7 @@ START_TEST(test__map_state_inc_area) { //third test: advance - refuse (reached the end) state.next_area_node = &m_a_n[9]; _map_state_inc_area(&state, _STATE_AREA_NODE_ADVANCE, NULL, &m); - ck_assert_int_eq(MC_GET_NODE_AREA(state.next_area_node)->id, 9); + ck_assert_ptr_null(state.next_area_node); //fourth test: reassign state.next_area_node = &m_a_n[0]; @@ -1322,10 +1336,15 @@ START_TEST(test__map_state_inc_obj) { ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node)->id, 0); //second test: advance from regular object - state.prev_obj_node = &m_a_n[0]; + state.prev_obj_node = &m_o_n[0]; _map_state_inc_obj(&state, &m); ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node)->id, 1); + //third test: advance from last object + state.prev_obj_node = &m_o_n[3]; + _map_state_inc_obj(&state, &m); + ck_assert_int_eq(MC_GET_NODE_OBJ(state.prev_obj_node)->id, 3); + return; } END_TEST @@ -1337,11 +1356,11 @@ START_TEST(test__map_resync_area) { //remove [heap]: object state struct obj_check first_objs[2] = { //start index: 1 {"cat", 0x1000, 0x4000}, - {"foo", 0x9000, 0xB000} + {"foo", 0x8000, 0xB000} }; struct obj_check first_objs_unmapped[1] = { //start index: 0 - {"[heap]", 0x4000, 0x5000} + {"[heap]", MC_UNDEF_ADDR, MC_UNDEF_ADDR} }; //remove [heap]: area state @@ -1364,7 +1383,7 @@ START_TEST(test__map_resync_area) { }; struct obj_check second_objs_unmapped[1] = { //start index 0 - {"[heap]", 0x4000, 0x5000} + {"[heap]", MC_UNDEF_ADDR, MC_UNDEF_ADDR} }; //remove /lib/foo:1,2: area state @@ -1389,8 +1408,8 @@ START_TEST(test__map_resync_area) { }; struct obj_check third_objs_unmapped[2] = { //start index: 0 - {"cat", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, - {"[heap]", MC_UNDEF_ADDR, MC_UNDEF_ADDR} + {"[heap]", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, + {"cat", MC_UNDEF_ADDR, MC_UNDEF_ADDR} }; //remove /bin/cat:1,2,3: area state @@ -1417,42 +1436,42 @@ START_TEST(test__map_resync_area) { //correct by removing `[heap]` _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); - _init__traverse_state(&state, &m_a_n[6], &m_o_n[0]); + _init__traverse_state(&state, &m_a_n[3], &m_o_n[0]); ret = _map_resync_area(&entry, &state, &m); - ck_assert_int_eq(ret, 0); + ck_assert_int_eq(ret, 1); //check state - assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, - 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, - NULL, NULL, 5, true); + assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, + 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, &m_o_n[0], 4, true); assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), - "/bin/cat", "cat", 0x1000, 0x3000, 3, 1, 0, true); + "/bin/cat", "cat", 0x1000, 0x4000, 3, 1, 0, true); assert_vm_map_objs(&m.vm_objs, first_objs, 1, 2, true); assert_vm_map_objs(&m.vm_objs_unmapped, first_objs_unmapped, 0, 1, false); - assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3, true); + assert_vm_map_areas(&m.vm_areas, first_areas, 2, 2, true); assert_vm_map_areas(&m.vm_areas_unmapped, - first_areas_unmapped, 0, 3, false); + first_areas_unmapped, 0, 1, false); //correct by removing first 2 areas of `/lib/foo` _init_vm_entry(&entry, 0xA000, 0xB000, 0x0, MC_ACCESS_READ | MC_ACCESS_EXEC, "/lib/foo"); - _init__traverse_state(&state, &m_a_n[5], &m_o_n[2]); + _init__traverse_state(&state, &m_a_n[5], &m_o_n[0]); ret = _map_resync_area(&entry, &state, &m); - ck_assert_int_eq(ret, 0); + ck_assert_int_eq(ret, 1); //check state - assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, - 0xC000, 0xD000, MC_ACCESS_READ | MC_ACCESS_WRITE, - NULL, &m_o_n[2], 8, true); + assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), "/lib/foo", "foo", + 0xA000, 0xB000, MC_ACCESS_READ | MC_ACCESS_EXEC, + &m_o_n[2], NULL, 7, true); - assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/lib/foo", "foo", - 0xA000, 0xB000, 1, 1, 2, true); + assert_vm_obj(MC_GET_NODE_OBJ(state.prev_obj_node), "/bin/cat", "cat", + 0x1000, 0x4000, 3, 1, 0, true); assert_vm_map_objs(&m.vm_objs, second_objs, 1, 3, true); assert_vm_map_objs(&m.vm_objs_unmapped, @@ -1465,19 +1484,20 @@ START_TEST(test__map_resync_area) { //correct by removing entirety of `/bin/cat` _init_vm_entry(&entry, 0x6000, 0x7000, 0x0, - MC_ACCESS_READ | MC_ACCESS_EXEC, NULL); - _init__traverse_state(&state, &m_a_n[0], &m_o_n[0]); + MC_ACCESS_READ | MC_ACCESS_WRITE, NULL); + _init__traverse_state(&state, &m_a_n[0], m.vm_objs.head); ret = _map_resync_area(&entry, &state, &m); - ck_assert_int_eq(ret, 0); + ck_assert_int_eq(ret, 1); //check state - assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), NULL, NULL, - 0x6000, 0x7000, MC_ACCESS_READ | MC_ACCESS_WRITE, - NULL, m.vm_objs.head, 4, true); + assert_vm_area(MC_GET_NODE_AREA(state.next_area_node), + NULL, NULL, 0x6000, 0x7000, + MC_ACCESS_READ | MC_ACCESS_WRITE, + NULL, m.vm_objs.head, 4, true); assert_vm_obj(MC_GET_NODE_OBJ(m.vm_objs.head), "0x0", "0x0", - 0x0, 0x0, 0, 2, MC_ZERO_OBJ_ID, true); + 0x0, 0x0, 0, 1, MC_ZERO_OBJ_ID, true); assert_vm_map_objs(&m.vm_objs, third_objs, 1, 2, true); assert_vm_map_objs(&m.vm_objs_unmapped, @@ -1497,13 +1517,13 @@ START_TEST(test__map_add_obj) { //test data struct obj_check first_objs[3] = { //start index: 3 {"foo", 0x8000, 0xB000}, - {"bar", 0xD000, 0xE000}, + {"bar", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, {"[stack]", 0xE000, 0xF000} }; - struct obj_check second_objs[3] = { //start index: 2 + struct obj_check second_objs[3] = { //start index: 1 {"[heap]", 0x4000, 0x5000}, - {"dog", 0x7000, 0x8000}, + {"dog", MC_UNDEF_ADDR, MC_UNDEF_ADDR}, {"foo", 0x8000, 0xB000} }; @@ -1533,8 +1553,8 @@ START_TEST(test__map_add_obj) { ret_node = _map_add_obj(&entry, &state, &m); ck_assert_ptr_nonnull(ret_node); - assert_vm_map_objs(&m.vm_objs, second_objs, 3, 2, true); - assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 1); + assert_vm_map_objs(&m.vm_objs, second_objs, 2, 3, true); + assert_lst_len(&MC_GET_NODE_OBJ(ret_node)->last_vm_area_node_ps, 0); return; @@ -1556,7 +1576,7 @@ START_TEST(test__map_add_area) { {"[stack]", 0xE000, 0xF000} }; - struct area_check second_areas[3] = { //start_index: 6 + struct area_check second_areas[3] = { //start_index: 4 {"", 0x6000, 0x7000}, {"", 0x7000, 0x8000}, {"foo", 0x8000, 0x9000} @@ -1605,8 +1625,8 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 6, 3, true); - assert_vm_map_objs(&m.vm_objs, first_objs, 2, 2, true); + assert_vm_map_areas(&m.vm_areas, second_areas, 4, 3, true); + assert_vm_map_objs(&m.vm_objs, second_objs, 2, 2, true); //third test: add an area that creates a new object `/lib/bar` @@ -1616,14 +1636,15 @@ START_TEST(test__map_add_area) { ret = _map_add_area(&entry, &state, &m); ck_assert_int_eq(ret, 0); - assert_vm_map_areas(&m.vm_areas, first_areas, 3, 3, true); - assert_vm_map_objs(&m.vm_objs, first_objs, 2, 3, true); + assert_vm_map_areas(&m.vm_areas, third_areas, 3, 3, true); + assert_vm_map_objs(&m.vm_objs, third_objs, 2, 3, true); return; } END_TEST + //map_send_entry() [stub map fixture] START_TEST(test_map_send_entry) { @@ -1641,13 +1662,13 @@ START_TEST(test_map_send_entry) { struct area_check second_areas[3] = { //start_index: 9 {"[stack]", 0x0E000, 0x0F000}, - {"dog", 0x0F000, 0xA0000}, - {"dog", 0xA0000, 0xA1000} + {"dog", 0x0F000, 0x10000}, + {"dog", 0x10000, 0x11000} }; struct obj_check second_objs[2] = { //start index: 4 {"[stack]", 0x0E000, 0x0F000}, - {"dog", 0x0F000, 0xA1000} + {"dog", 0x0F000, 0x11000} }; @@ -1703,7 +1724,7 @@ START_TEST(test_map_send_entry) { //first test: send a "/bin/dog" entry to the end of the map _init_vm_entry(&entry, 0x0F000, 0x10000, 0x0, MC_ACCESS_READ, "/bin/dog"); - _init__traverse_state(&state, m.vm_areas.head->prev, m.vm_objs.head->prev); + _init__traverse_state(&state, NULL, m.vm_objs.head->prev); ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); @@ -1714,7 +1735,7 @@ START_TEST(test_map_send_entry) { //second test: send another "/bin/dog" area to the end of the map _init_vm_entry(&entry, 0x10000, 0x11000, 0x0, MC_ACCESS_READ, "/bin/dog"); - _init__traverse_state(&state, m.vm_areas.head->prev, m.vm_objs.head->prev); + _init__traverse_state(&state, NULL, m.vm_objs.head->prev); ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); @@ -1766,9 +1787,6 @@ START_TEST(test_map_init_traverse_state) { _traverse_state state; - //setup an empty map - mc_new_vm_map(&m); - //first test: empty map map_init_traverse_state(&state, &m); ck_assert_ptr_null(state.next_area_node); @@ -1777,7 +1795,7 @@ START_TEST(test_map_init_traverse_state) { //add an area _init_vm_entry(&entry, 0x1000, 0x2000, 0x0, MC_ACCESS_READ, "/bin/cat"); - _map_add_area(&entry, &state, &m); + map_send_entry(&entry, &state, &m); //second test: existing map @@ -1785,9 +1803,6 @@ START_TEST(test_map_init_traverse_state) { ck_assert_ptr_eq(state.next_area_node, m.vm_areas.head); ck_assert_ptr_eq(state.prev_obj_node, m.vm_objs.head); - //destroy the empty map - mc_del_vm_map(&m); - return; } END_TEST @@ -1798,16 +1813,21 @@ START_TEST(test_mc_map_clean_unmapped) { int ret; + struct vm_entry entry; + _traverse_state state; - //first test: unlink /lib/foo and clean its unmapped areas - ret = _map_unlink_unmapped_obj(&m_o_n[3], &m); - ck_assert_int_eq(ret, 0); - ret = _map_unlink_unmapped_area(&m_a_n[5], &m); - ck_assert_int_eq(ret, 0); - ret = _map_unlink_unmapped_area(&m_a_n[6], &m); + //first test: add /bin/dog and unmap it + _init_vm_entry(&entry, 0x0000, 0x1000, 0x0, + MC_ACCESS_READ | MC_ACCESS_WRITE, "/bin/dog"); + _init__traverse_state(&state, m.vm_areas.head, m.vm_objs.head); + ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); - ret = _map_unlink_unmapped_area(&m_a_n[7], &m); + + _init_vm_entry(&entry, 0x1000, 0x2000, 0x0, + MC_ACCESS_READ, "/bin/cat"); + _init__traverse_state(&state, m.vm_areas.head, m.vm_objs.head->next); + ret = map_send_entry(&entry, &state, &m); ck_assert_int_eq(ret, 0); ret = mc_map_clean_unmapped(&m); @@ -1826,7 +1846,7 @@ START_TEST(test_mc_map_clean_unmapped) { } END_TEST #endif -*/ //TODO DEBUG + /* * --- [SUITE] --- */ @@ -1850,7 +1870,7 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc__forward_unmapped_obj_last_vm_areas; TCase * tc__unlink_unmapped_obj; TCase * tc__unlink_unmapped_area; - #endif/*TCase * tc__check_area_eql; + TCase * tc__check_area_eql; TCase * tc__state_inc_area; TCase * tc__state_inc_obj; TCase * tc__resync_area; @@ -1860,7 +1880,7 @@ START_TEST(test_mc_map_clean_unmapped) { TCase * tc_init_traverse_state; TCase * tc_clean_unmapped; #endif - */ + Suite * s = suite_create("map"); @@ -1950,7 +1970,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_checked_fixture(tc__unlink_unmapped_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__unlink_unmapped_area, test__map_unlink_unmapped_area); - #endif/* + //tc__check_area_eql tc__check_area_eql = tcase_create("_check_area_eql"); tcase_add_checked_fixture(tc__check_area_eql, @@ -2005,7 +2025,7 @@ START_TEST(test_mc_map_clean_unmapped) { _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc_clean_unmapped, test_mc_map_clean_unmapped); #endif - */ + //add test cases to map test suite suite_add_tcase(s, tc_new_del_vm_map); @@ -2023,7 +2043,7 @@ START_TEST(test_mc_map_clean_unmapped) { suite_add_tcase(s, tc__forward_unmapped_obj_last_vm_areas); suite_add_tcase(s, tc__unlink_unmapped_obj); suite_add_tcase(s, tc__unlink_unmapped_area); - #endif/*suite_add_tcase(s, tc__check_area_eql); + suite_add_tcase(s, tc__check_area_eql); suite_add_tcase(s, tc__state_inc_area); suite_add_tcase(s, tc__state_inc_obj); suite_add_tcase(s, tc__resync_area); @@ -2033,6 +2053,6 @@ START_TEST(test_mc_map_clean_unmapped) { suite_add_tcase(s, tc_init_traverse_state); suite_add_tcase(s, tc_clean_unmapped); #endif - */ + return s; } diff --git a/src/test/map_helper.c b/src/test/map_helper.c index 2e11ceb..ee7143f 100644 --- a/src/test/map_helper.c +++ b/src/test/map_helper.c @@ -67,14 +67,26 @@ void assert_lst_len(cm_lst * list, int len) { //properly assert a potentially NULL string pair void assert_names(char * a, char * b) { - //if one is NULL, both must be NULL - if (a == NULL || b == NULL) { - ck_assert_ptr_eq(a, b); + //determine comparison type + int cmp_type = (a == NULL ? 0 : 1) + (b == NULL ? 0 : 2); + + + switch (cmp_type) { + + case 0: + ck_assert_ptr_eq(a, b); + break; + case 1: + ck_assert(*a == '\0' && b == NULL); + break; + case 2: + ck_assert(a == NULL && *b == '\0'); + break; + case 3: + ck_assert_str_eq(a, b); + break; - //otherwise, assert strings - } else { - ck_assert_str_eq(a, b); - } + } //end switch return; } diff --git a/src/test/target_helper.c b/src/test/target_helper.c index 93f2e07..f362675 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -253,23 +253,23 @@ void assert_target_map(mc_vm_map * map) { case UNCHANGED: assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unchanged, - 0, TARGET_AREAS_UNCHANGED); + 0, TARGET_AREAS_UNCHANGED, true); assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unchanged, - 0, TARGET_OBJS_UNCHANGED); + 0, TARGET_OBJS_UNCHANGED, true); break; case MAPPED: assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_mapped, - 0, TARGET_AREAS_MAPPED); + 0, TARGET_AREAS_MAPPED, true); assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_mapped, - 0, TARGET_OBJS_MAPPED); + 0, TARGET_OBJS_MAPPED, true); break; case UNMAPPED: assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unmapped, - 0, TARGET_AREAS_UNMAPPED); + 0, TARGET_AREAS_UNMAPPED, true); assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unmapped, - 0, TARGET_OBJS_UNMAPPED); + 0, TARGET_OBJS_UNMAPPED, true); break; } //end switch From 9fe8e98bcddc7b782ea73c774b68063704e0000d Mon Sep 17 00:00:00 2001 From: vykt Date: Tue, 25 Feb 2025 23:58:34 +0000 Subject: [PATCH 18/45] Make map tests pass --- TODO | 9 +++++ build/test/debug.sh | 3 +- build/test/init.gdb | 20 ++++++---- src/lib/map.c | 33 ++++++++++++++--- src/test/iface_helper.c | 2 - src/test/main.c | 80 ++++++++++++++++++++++------------------ src/test/map_helper.c | 4 +- src/test/map_helper.h | 4 +- src/test/suites.h | 7 ++++ src/test/target_helper.c | 58 ++++++++++++++++------------- 10 files changed, 138 insertions(+), 82 deletions(-) diff --git a/TODO b/TODO index b1cbe3b..4dcf596 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,14 @@ +[developing]: +- While there are unit tests, they catch far from + everything. To ensure your changes work, inspect + state with a gdb using the provided macros. + + [bugs]: - Change error code `LIBCMORE` -> `CMORE` +- Modify GDB scripts to pretty print areas, objs and nodes + with `printf` instead of crudely using `p`. Maybe add a + 'p' prefix, for "pretty"? e.g.: `epparean`? [features]: diff --git a/build/test/debug.sh b/build/test/debug.sh index a9f8aad..9d7588f 100755 --- a/build/test/debug.sh +++ b/build/test/debug.sh @@ -1,2 +1,3 @@ #!/bin/sh -gdb -x init.gdb --args ./test -u "$@" +kill $(pidof unit_target) +gdb -x init.gdb --args ./test -p -m "$@" diff --git a/build/test/init.gdb b/build/test/init.gdb index 3fb9638..36da0c7 100644 --- a/build/test/init.gdb +++ b/build/test/init.gdb @@ -121,7 +121,7 @@ define pmapa end # print relevant entries of this area - printf "%-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $obj_id, $last_obj_id, $basename + printf "%-3d: 0x%-12lx - 0x%-12lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $obj_id, $last_obj_id, $basename # advance iteration set $iter = $iter + 1 @@ -169,7 +169,7 @@ define pmapua end # print relevant entries of this area - printf "%-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $obj_id, $last_obj_id, $basename + printf "%-3d: 0x%-12lx - 0x%-12lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $obj_id, $last_obj_id, $basename # advance iteration set $iter = $iter + 1 @@ -237,7 +237,7 @@ define pmapo end # print relevant entries of this area - printf " %-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $inner_obj_id, $inner_last_obj_id, $basename + printf " %-3d: 0x%-12lx - 0x%-12lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $inner_obj_id, $inner_last_obj_id, $basename # advance iteration over areas set $inner_iter = $inner_iter + 1 @@ -276,7 +276,7 @@ define pmapo end # print relevant entries of this area - printf " %-3d: 0x%lx - 0x%lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $inner_obj_id, $inner_last_obj_id, $basename + printf " %-3d: 0x%-12lx - 0x%-12lx | obj: %-5d - last_obj: %-5d | \"%s\"\n", $id, $start_addr, $end_addr, $inner_obj_id, $inner_last_obj_id, $basename # advance iteration over areas set $inner_iter = $inner_iter + 1 @@ -320,7 +320,7 @@ define pmapuo if ($start_addr == -1) && ($end_addr == -1) printf "<%-3d: MC_UNDEF_ADDR - MC_UNDEF_ADDR | areas: %d - last areas: %d | \"%s\">\n", $id, $num_area, $num_last_area, $basename else - printf "<%-3d: 0x%lx - 0x%lx | areas: %d - last areas: %d | \"%s\">\n", $id, $start_addr, $end_addr, $num_area, $num_last_area, $basename + printf "<%-3d: 0x%-12lx - 0x%-12lx | areas: %d - last areas: %d | \"%s\">\n", $id, $start_addr, $end_addr, $num_area, $num_last_area, $basename end # advance iteration over objects @@ -331,8 +331,12 @@ define pmapuo end -# session dependent +# setup session tb main -layout src - run +set _DEBUG_ACTIVE = true + +# session dependent (modify from here onwards) +tb test_procfs_mc_update_map_fn +cont +layout src diff --git a/src/lib/map.c b/src/lib/map.c index 15bd93d..9ead23d 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -359,6 +359,13 @@ bool _map_is_pathname_in_obj(const char * pathname, const mc_vm_obj * obj) { if (obj == NULL) return false; + //if looking at pseudo object, '\0' pathname is a match + if (obj->id == MC_ZERO_OBJ_ID) { + if (*pathname == '\0') return true; + return false; + } + + //else require a string match bool ret = (strncmp(pathname, obj->pathname, PATH_MAX) ? false : true); return ret; @@ -728,7 +735,8 @@ void _map_state_inc_obj(_traverse_state * state, mc_vm_map * map) { } -//return: 0 - do not re-add, 1 - do re-add, -1 - error +//return: 0: removed all areas up to entry->end_addr +// 1: stopped iterating when matching area found DBG_STATIC DBG_INLINE int _map_resync_area(const struct vm_entry * entry, _traverse_state * state, mc_vm_map * map) { @@ -799,7 +807,7 @@ int _map_add_area(const struct vm_entry * entry, bool use_obj; bool forward_obj = false; - mc_vm_area area; + mc_vm_area area, * area_p; cm_lst_node * area_node; mc_vm_obj * obj; @@ -892,8 +900,17 @@ int _map_add_area(const struct vm_entry * entry, if (ret == -1) return -1; } - //increment area state - _map_state_inc_area(state, _STATE_AREA_NODE_REASSIGN, area_node->next, map); + /* + * Increment area state if this new area is higher than the state's area. + * It should be impossible for areas to have address overlap due to + * _map_resync_area() eliminating old areas that overlap with new areas. + * Because of this, comparing just end addresses should suffice. + */ + if ((state->next_area_node != NULL) + && (entry->vm_end + >= MC_GET_NODE_AREA(state->next_area_node)->end_addr)) { + _map_state_inc_area(state, _STATE_AREA_NODE_ADVANCE, NULL, map); + } return 0; } @@ -911,8 +928,7 @@ int map_send_entry(const struct vm_entry * entry, //if reached the end of the old map - if (state->next_area_node == NULL - || state->next_area_node->next == map->vm_areas.head) { + if (state->next_area_node == NULL) { ret = _map_add_area(entry, state, map); if (ret == -1) return -1; @@ -926,9 +942,14 @@ int map_send_entry(const struct vm_entry * entry, ret = _map_resync_area(entry, state, map); if (ret == -1) return -1; + //replace area if (ret == 0) { ret = _map_add_area(entry, state, map); if (ret == -1) return -1; + //area match found, advance state + } else { + _map_state_inc_area(state, + _STATE_AREA_NODE_ADVANCE, NULL, map); } // else entry matches next area diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c index 5c8f69b..9b33ffe 100644 --- a/src/test/iface_helper.c +++ b/src/test/iface_helper.c @@ -154,8 +154,6 @@ void assert_iface_update_map(enum mc_iface_type iface) { ret = mc_del_vm_map(&m); ck_assert_int_eq(ret, 0); - end_target(pid); - return; } diff --git a/src/test/main.c b/src/test/main.c index 9d4acb0..30d9b72 100644 --- a/src/test/main.c +++ b/src/test/main.c @@ -7,72 +7,91 @@ #include //external libraries +#include #include //local headers #include "suites.h" -enum _test_mode {UNIT, EXPL}; +//manipulated by debug.sh; see `suites.h` +bool _DEBUG_ACTIVE = false; + +//tests bitmask +#define PROCFS_TEST 0x1 +#define KRNCRY_TEST 0x2 +#define MAP_UTIL_TEST 0x4 +#define UTIL_TEST 0x8 + //determine which tests to run -static enum _test_mode _get_test_mode(int argc, char ** argv) { +static cm_byte _get_test_mode(int argc, char ** argv) { const struct option long_opts[] = { - {"unit-tests", no_argument, NULL, 'u'}, - {"explore", no_argument, NULL, 'e'}, + {"procfs", no_argument, NULL, 'p'}, + {"krncry", no_argument, NULL, 'k'}, + {"map-util", no_argument, NULL, 'm'}, + {"util", no_argument, NULL, 'u'}, {0,0,0,0} }; int opt; - enum _test_mode test_mode = UNIT; + cm_byte test_mask = 0; - while((opt = getopt_long(argc, argv, "ue", long_opts, NULL)) != -1 + while((opt = getopt_long(argc, argv, "pkmu", long_opts, NULL)) != -1 && opt != 0) { //determine parsed argument switch (opt) { - case 'u': - test_mode = UNIT; + case 'p': + test_mask |= PROCFS_TEST; break; - case 'e': - test_mode = EXPL; + case 'k': + test_mask |= KRNCRY_TEST; + break; + + case 'm': + test_mask |= MAP_UTIL_TEST; + break; + + case 'u': + test_mask |= UTIL_TEST; break; } } - return test_mode; + return test_mask; } //run unit tests -static void _run_unit_tests() { +static void _run_unit_tests(cm_byte test_mask) { Suite * s_map; - //Suite * s_procfs_iface; - //Suite * s_krncry_iface; - //Suite * s_map_util; - //Suite * s_util; + Suite * s_procfs_iface; + Suite * s_krncry_iface; + Suite * s_map_util; + Suite * s_util; SRunner * sr; //initialise test suites s_map = map_suite(); - //s_procfs_iface = procfs_iface_suite(); - //s_krncry_iface = krncry_iface_suite(); - //s_map_util = map_util_suite(); - //s_util = util_suite(); + if (test_mask & PROCFS_TEST) s_procfs_iface = procfs_iface_suite(); + if (test_mask & KRNCRY_TEST) s_krncry_iface = krncry_iface_suite(); + if (test_mask & MAP_UTIL_TEST) s_map_util = map_util_suite(); + if (test_mask & UTIL_TEST) s_util = util_suite(); //create suite runner sr = srunner_create(s_map); - //srunner_add_suite(sr, s_procfs_iface); - //srunner_add_suite(sr, s_krncry_iface); - //srunner_add_suite(sr, s_map_util); - //srunner_add_suite(sr, s_util); + if (test_mask & PROCFS_TEST) srunner_add_suite(sr, s_procfs_iface); + if (test_mask & KRNCRY_TEST) srunner_add_suite(sr, s_krncry_iface); + if (test_mask & MAP_UTIL_TEST) srunner_add_suite(sr, s_map_util); + if (test_mask & UTIL_TEST) srunner_add_suite(sr, s_util); //run tests srunner_run_all(sr, CK_VERBOSE); @@ -87,17 +106,8 @@ static void _run_unit_tests() { //dispatch tests int main(int argc, char ** argv) { - enum _test_mode mode = _get_test_mode(argc, argv); - - switch (mode) { - case UNIT: - _run_unit_tests(); - break; - - case EXPL: - fprintf(stderr, "[ERR] `-e, --expore` not implemented.\n"); - break; - } + cm_byte test_mask = _get_test_mode(argc, argv); + _run_unit_tests(test_mask); return 0; } diff --git a/src/test/map_helper.c b/src/test/map_helper.c index ee7143f..494906c 100644 --- a/src/test/map_helper.c +++ b/src/test/map_helper.c @@ -151,7 +151,7 @@ void assert_vm_map_objs(cm_lst * lst, struct obj_check * obj_checks, //assert only pathnames, not mapped address ranges -void assert_vm_map_objs_aslr(cm_lst * lst, char * basenames[NAME_MAX], +void assert_vm_map_objs_aslr(cm_lst * lst, char (* basenames)[NAME_MAX], int start_index, int len, bool mapped) { int ret; @@ -210,7 +210,7 @@ void assert_vm_map_areas(cm_lst * lst, struct area_check * area_checks, //assert only pathnames, not mapped address ranges -void assert_vm_map_areas_aslr(cm_lst * lst, char * basenames[NAME_MAX], +void assert_vm_map_areas_aslr(cm_lst * lst, char (* basenames)[NAME_MAX], int start_index, int len, bool mapped) { int ret; diff --git a/src/test/map_helper.h b/src/test/map_helper.h index b49c904..bfd75c7 100644 --- a/src/test/map_helper.h +++ b/src/test/map_helper.h @@ -44,12 +44,12 @@ void assert_vm_map(mc_vm_map * map, int vm_areas_len, int vm_objs_len, void assert_vm_map_objs(cm_lst * lst, struct obj_check * obj_checks, int start_index, int len, bool mapped); -void assert_vm_map_objs_aslr(cm_lst * lst, char * basenames[NAME_MAX], +void assert_vm_map_objs_aslr(cm_lst * lst, char (* basenames)[NAME_MAX], int start_index, int len, bool mapped); void assert_vm_map_areas(cm_lst * lst, struct area_check * area_checks, int start_index, int len, bool mapped); -void assert_vm_map_areas_aslr(cm_lst * lst, char * basenames[NAME_MAX], +void assert_vm_map_areas_aslr(cm_lst * lst, char (* basenames)[NAME_MAX], int start_index, int len, bool mapped); void assert_vm_obj(mc_vm_obj * obj, char * pathname, char * basename, diff --git a/src/test/suites.h b/src/test/suites.h index 6642fcb..5099ab7 100644 --- a/src/test/suites.h +++ b/src/test/suites.h @@ -1,10 +1,17 @@ #ifndef SUITES_H #define SUITES_H +//standard library +#include + //external libraries #include +//modify behaviour a debugger breaks +extern bool _DEBUG_ACTIVE; + + //unit test suites Suite * krncry_iface_suite(); Suite * procfs_iface_suite(); diff --git a/src/test/target_helper.c b/src/test/target_helper.c index f362675..9ae1acf 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -16,6 +16,7 @@ //local headers #include "target_helper.h" #include "map_helper.h" +#include "suites.h" //test target headers #include "../lib/memcry.h" @@ -39,24 +40,24 @@ char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX] = { "libc.so.6", "", "", + "[vvar]", + "[vdso]", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", - "[stack]", - "[vvar]", - "[vdso]" + "[stack]" }; char objs_unchanged[TARGET_OBJS_UNCHANGED][NAME_MAX] = { "0x0", "unit_target", "libc.so.6", - "ld-linux-x86-64.so.2", - "[stack]", "[vvar]", - "[vdso]" + "[vdso]", + "ld-linux-x86-64.so.2", + "[stack]" }; @@ -86,14 +87,14 @@ char areas_mapped[TARGET_AREAS_MAPPED][NAME_MAX] = { "libc.so.6", "", "", + "[vvar]", + "[vdso]", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", - "[stack]", - "[vvar]", - "[vdso]" + "[stack]" }; char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { @@ -103,10 +104,10 @@ char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { "libz.so.1.2.13", "libelf-0.188.so", "libc.so.6", - "ld-linux-x86-64.so.2", - "[stack]", "[vvar]", - "[vdso]" + "[vdso]", + "ld-linux-x86-64.so.2", + "[stack]" }; @@ -126,14 +127,14 @@ char areas_unmapped[TARGET_AREAS_UNMAPPED][NAME_MAX] = { "libc.so.6", "", "", + "[vvar]", + "[vdso]", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", - "[stack]", - "[vvar]", - "[vdso]" + "[stack]" }; char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { @@ -141,10 +142,10 @@ char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { "unit_target", "[heap]", "libc.so.6", - "ld-linux-x86-64.so.2", - "[stack]", "[vvar]", - "[vdso]" + "[vdso]", + "ld-linux-x86-64.so.2", + "[stack]" }; @@ -241,8 +242,13 @@ void change_target_map(pid_t pid) { ck_assert_int_eq(ret, 0); //busy-wait for target to change its memory map - while(target_state == old_state) {} - + if (_DEBUG_ACTIVE) { + if (target_state == MAPPED) target_state = UNMAPPED; + if (target_state == UNCHANGED) target_state = MAPPED; + } else { + while (target_state == old_state) {} + } + return; } @@ -252,23 +258,23 @@ void assert_target_map(mc_vm_map * map) { switch(target_state) { case UNCHANGED: - assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unchanged, + assert_vm_map_areas_aslr(&map->vm_areas, areas_unchanged, 0, TARGET_AREAS_UNCHANGED, true); - assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unchanged, + assert_vm_map_objs_aslr(&map->vm_objs, objs_unchanged, 0, TARGET_OBJS_UNCHANGED, true); break; case MAPPED: - assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_mapped, + assert_vm_map_areas_aslr(&map->vm_areas, areas_mapped, 0, TARGET_AREAS_MAPPED, true); - assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_mapped, + assert_vm_map_objs_aslr(&map->vm_objs, objs_mapped, 0, TARGET_OBJS_MAPPED, true); break; case UNMAPPED: - assert_vm_map_areas_aslr(&map->vm_areas, (char **) areas_unmapped, + assert_vm_map_areas_aslr(&map->vm_areas, areas_unmapped, 0, TARGET_AREAS_UNMAPPED, true); - assert_vm_map_objs_aslr(&map->vm_objs, (char **) objs_unmapped, + assert_vm_map_objs_aslr(&map->vm_objs, objs_unmapped, 0, TARGET_OBJS_UNMAPPED, true); break; From bd913c06e16223c58e363e33ad37165ba30b7a3a Mon Sep 17 00:00:00 2001 From: vykt Date: Thu, 27 Feb 2025 01:39:15 +0000 Subject: [PATCH 19/45] Make most tests pass --- build/test/debug.sh | 3 +-- build/test/init.gdb | 2 ++ src/test/check_map_util.c | 31 ++++++++++++++++++++----------- src/test/check_util.c | 4 ++-- src/test/iface_helper.c | 37 +++++++++++++++++++++++++++---------- src/test/target_helper.c | 28 +++++++++++++++++++++++----- src/test/target_helper.h | 7 ++++++- 7 files changed, 81 insertions(+), 31 deletions(-) diff --git a/build/test/debug.sh b/build/test/debug.sh index 9d7588f..5dc46e1 100755 --- a/build/test/debug.sh +++ b/build/test/debug.sh @@ -1,3 +1,2 @@ #!/bin/sh -kill $(pidof unit_target) -gdb -x init.gdb --args ./test -p -m "$@" +gdb -x init.gdb --args ./test -p "$@" diff --git a/build/test/init.gdb b/build/test/init.gdb index 36da0c7..6c3e51d 100644 --- a/build/test/init.gdb +++ b/build/test/init.gdb @@ -337,6 +337,8 @@ run set _DEBUG_ACTIVE = true # session dependent (modify from here onwards) +tb test_procfs_mc_open_close_fn tb test_procfs_mc_update_map_fn +tb test_procfs_mc_read_write_fn cont layout src diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index 14d6fdc..561adce 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -46,6 +46,9 @@ static void _setup_target() { ret = mc_open(&s, PROCFS, pid); ck_assert_int_eq(ret, 0); + //wait for ldlinux.so in the target + sleep(1); + mc_new_vm_map(&m); ret = mc_update_map(&s, &m); ck_assert_int_eq(ret, 0); @@ -88,14 +91,14 @@ START_TEST(test_mc_get_area_offset) { //first test: typical offset o = MC_GET_NODE_OBJ(m.vm_objs.head); - a_n = MC_GET_NODE_PTR(o->vm_area_node_ps.head); + a_n = MC_GET_NODE_PTR(o->last_vm_area_node_ps.head); off = mc_get_area_offset(a_n, 0x10800); ck_assert_int_eq(off, 0x800); //second test: address is lower than area's starting address - off = mc_get_area_offset(a_n, 0x9800); + off = mc_get_area_offset(a_n, 0xF800); ck_assert_int_eq(off, -0x800); return; @@ -109,17 +112,20 @@ START_TEST(test_mc_get_obj_offset) { off_t off; cm_lst_node * o_n; + mc_vm_obj * o; //first test: typical offset o_n = m.vm_objs.head; + o = MC_GET_NODE_OBJ(o_n); - off = mc_get_obj_offset(o_n, 0x10800); + off = mc_get_obj_offset(o_n, 0x800); ck_assert_int_eq(off, 0x800); //second test: address is lower than obj's starting address - off = mc_get_obj_offset(o_n, 0x9800); + o->end_addr = o->start_addr = 0x1000; + off = mc_get_obj_offset(o_n, 0x800); ck_assert_int_eq(off, -0x800); return; @@ -138,14 +144,14 @@ START_TEST(test_mc_get_area_offset_bnd) { //first test: typical offset o = MC_GET_NODE_OBJ(m.vm_objs.head); - a_n = MC_GET_NODE_PTR(o->vm_area_node_ps.head); + a_n = MC_GET_NODE_PTR(o->last_vm_area_node_ps.head); - off = mc_get_area_offset(a_n, 0x10800); + off = mc_get_area_offset_bnd(a_n, 0x10800); ck_assert_int_eq(off, 0x800); //second test: address is lower than area's starting address - off = mc_get_area_offset(a_n, 0x9800); + off = mc_get_area_offset_bnd(a_n, 0xF800); ck_assert_int_eq(off, -1); return; @@ -159,17 +165,20 @@ START_TEST(test_mc_get_obj_offset_bnd) { off_t off; cm_lst_node * o_n; - + mc_vm_obj * o; + //first test: typical offset o_n = m.vm_objs.head; + o = MC_GET_NODE_OBJ(o_n); - off = mc_get_obj_offset(o_n, 0x10800); + off = mc_get_obj_offset_bnd(o_n, 0x800); ck_assert_int_eq(off, 0x800); //second test: address is lower than obj's starting address - off = mc_get_obj_offset(o_n, 0x9800); + o->end_addr = o->start_addr = 0x1000; + off = mc_get_obj_offset_bnd(o_n, 0x800); ck_assert_int_eq(off, -1); return; @@ -284,7 +293,7 @@ START_TEST(test_mc_get_obj_node_by_basename) { o_n = m.vm_objs.head->next->next; o = MC_GET_NODE_OBJ(o_n); - basename = o->pathname; + basename = o->basename; ret_n = mc_get_obj_node_by_basename(&m, basename); ck_assert_ptr_eq(ret_n, o_n); diff --git a/src/test/check_util.c b/src/test/check_util.c index 84c197f..31b1c60 100644 --- a/src/test/check_util.c +++ b/src/test/check_util.c @@ -168,8 +168,8 @@ START_TEST(test_mc_bytes_to_hex) { //only test: convert ascii "foo" to hex representation - cm_byte str[3] = "foo"; - char hex_str[6] = {0}; + cm_byte str[4] = "foo"; + char hex_str[8] = {0}; mc_bytes_to_hex(str, 3, hex_str); ret = memcmp(hex_str, "666f6f", 6); diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c index 9b33ffe..da886cb 100644 --- a/src/test/iface_helper.c +++ b/src/test/iface_helper.c @@ -29,7 +29,7 @@ //get address for testing a read / write in a PIE process -static inline uintptr_t _get_addr(mc_vm_map * m, +static inline uintptr_t _get_addr(mc_vm_map * m, bool is_last_area, int obj_index, int area_index, off_t off) { int ret; @@ -46,7 +46,11 @@ static inline uintptr_t _get_addr(mc_vm_map * m, o = MC_GET_NODE_OBJ(o_p); //get relevant area - ret = cm_lst_get(&o->vm_area_node_ps, area_index, &a_p); + if (is_last_area == true) { + ret = cm_lst_get(&o->last_vm_area_node_ps, area_index, &a_p); + } else { + ret = cm_lst_get(&o->vm_area_node_ps, area_index, &a_p); + } ck_assert_int_eq(ret, 0); a = MC_GET_NODE_AREA(a_p); @@ -64,6 +68,10 @@ void assert_iface_open_close(enum mc_iface_type iface, mc_session s; + //kill existing targets + ret = clean_targets(); + ck_assert_int_eq(ret, 0); + //setup tests pid = start_target(); ck_assert_int_ne(pid, -1); @@ -90,7 +98,6 @@ void assert_iface_open_close(enum mc_iface_type iface, ret = mc_close(&s); ck_assert_int_eq(ret, 0); - return; } @@ -105,6 +112,10 @@ void assert_iface_update_map(enum mc_iface_type iface) { mc_vm_map m; + //kill existing targets + ret = clean_targets(); + ck_assert_int_eq(ret, 0); + //setup tests pid = start_target(); ck_assert_int_ne(pid, -1); @@ -177,9 +188,13 @@ void assert_iface_read_write(enum mc_iface_type iface) { mc_session s; mc_vm_map m; - const char * w_buf = "buffer written "; + const char * w_buf = IFACE_W_BUF_STR; + //kill existing targets + ret = clean_targets(); + ck_assert_int_eq(ret, 0); + //setup tests pid = start_target(); ck_assert_int_ne(pid, -1); @@ -193,7 +208,7 @@ void assert_iface_read_write(enum mc_iface_type iface) { //first test: read & write predefined rw- segment, seek & no seek - tgt_buf_addr = _get_addr(&m, IFACE_RW_BUF_OBJ_INDEX, + tgt_buf_addr = _get_addr(&m, false, IFACE_RW_BUF_OBJ_INDEX, IFACE_RW_BUF_AREA_INDEX, IFACE_RW_BUF_OFF); //read foreign buffer @@ -211,16 +226,17 @@ void assert_iface_read_write(enum mc_iface_type iface) { //re-read foreign buffer ret = mc_read(&s, tgt_buf_addr, rw_buf, TARGET_BUF_SZ); - ck_assert_int_eq(ret, -1); + ck_assert_int_eq(ret, 0); //check the write was performed correctly - ret = strncmp((char *) rw_buf, w_buf, TARGET_BUF_SZ); + ret = strncmp((char *) rw_buf, IFACE_W_BUF_STR, TARGET_BUF_SZ); ck_assert_int_eq(ret, 0); //second test: read & write to region with no read & write permissions - tgt_buf_addr = _get_addr(&m, IFACE_NONE_OBJ_INDEX, + memset(rw_buf, 0, 16); + tgt_buf_addr = _get_addr(&m, true, IFACE_NONE_OBJ_INDEX, IFACE_NONE_AREA_INDEX, IFACE_NONE_OFF); //write to foreign buffer @@ -234,13 +250,14 @@ void assert_iface_read_write(enum mc_iface_type iface) { get_iface_name(iface), ret); //check if write succeeded - ret = strncmp((char *) rw_buf, IFACE_RW_BUF_STR, TARGET_BUF_SZ); + ret = strncmp((char *) rw_buf, IFACE_W_BUF_STR, TARGET_BUF_SZ); INFO_PRINT("[%s][none perm]: returned %d\n", get_iface_name(iface), ret); //third test: read & write to unmapped memory + memset(rw_buf, 0, 16); tgt_buf_addr = 0x1337; //write to foreign buffer @@ -253,7 +270,7 @@ void assert_iface_read_write(enum mc_iface_type iface) { INFO_PRINT("[%s][unmapped]: returned %d\n", get_iface_name(iface), ret); - ret = strncmp((char *) rw_buf, IFACE_RW_BUF_STR, TARGET_BUF_SZ); + ret = strncmp((char *) rw_buf, IFACE_W_BUF_STR, TARGET_BUF_SZ); INFO_PRINT("[%s][none perm]: returned %d\n", get_iface_name(iface), ret); diff --git a/src/test/target_helper.c b/src/test/target_helper.c index 9ae1acf..7eaf92d 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -1,6 +1,8 @@ //standard library #include +#include #include +#include //system headers #include @@ -165,6 +167,24 @@ static void _sigusr1_handler() { //helpers +int clean_targets() { + + int ret; + char command_buf[TARGET_KILL_CMD_LEN]; + + + //build command string + snprintf(command_buf, TARGET_KILL_CMD_LEN, + TARGET_KILL_CMD_FMT, TARGET_NAME); + + //use system() to kill all existing targets + ret = system(command_buf); + if (ret == -1) return -1; + + return 0; +} + + pid_t start_target() { int ret; @@ -175,7 +195,7 @@ pid_t start_target() { char * argv[3] = {TARGET_NAME, 0, 0}; target_state = UNCHANGED; - + //get current pid to pass to target parent_pid = getpid(); @@ -188,13 +208,11 @@ pid_t start_target() { //change image to target in child if (target_pid == 0) { - ret = execve(TARGET_NAME, argv, NULL); ck_assert_int_ne(ret, -1); - //if parent, register signal handler for child + //parent registers signal handler for child } else { - ret_s = signal(SIGUSR1, _sigusr1_handler); ck_assert(ret_s != SIG_ERR); } @@ -210,7 +228,7 @@ void end_target(pid_t pid) { pid_t ret_p; - + //unregister signal handler ret_s = signal(SIGUSR1, SIG_DFL); diff --git a/src/test/target_helper.h b/src/test/target_helper.h index b25595b..e9799a9 100644 --- a/src/test/target_helper.h +++ b/src/test/target_helper.h @@ -45,9 +45,13 @@ enum target_map_state { //target metadata #define TARGET_NAME "unit_target" -#define TARGET_BUF_SZ 16 /* must be even */ +#define TARGET_KILL_CMD_FMT "kill $(pidof %s)" +#define TARGET_KILL_CMD_LEN NAME_MAX + 64 + +#define TARGET_BUF_SZ 16 /* must be even */ #define IFACE_RW_BUF_STR "read & write me " +#define IFACE_W_BUF_STR "buffer written " #define IFACE_RW_BUF_OBJ_INDEX 1 #define IFACE_RW_BUF_AREA_INDEX 4 @@ -59,6 +63,7 @@ enum target_map_state { //target helpers +int clean_targets(); pid_t start_target(); void end_target(pid_t pid); void change_target_map(pid_t pid); From 1defd2a17101f679e370e3781ae587c5a84e4aa7 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 13:30:51 +0000 Subject: [PATCH 20/45] Transfer WIP files --- README.md | 6 +-- README.txt | 51 +++++++++++++++++++++ TESTING.txt | 84 +++++++++++++++++++++++++++++++++++ TODO | 6 --- build/test/init.gdb | 9 +--- src/lib/error.c | 8 ++-- src/lib/map.c | 61 +++++++++++++------------ src/lib/memcry.h | 4 +- src/lib/procfs_iface.c | 4 +- src/lib/util.c | 4 +- src/test/Makefile | 6 +-- src/test/check_map.c | 15 +++++-- src/test/check_map_util.c | 14 +++--- src/test/check_util.c | 2 + src/test/iface_helper.c | 4 +- src/test/main.c | 27 +++++++---- src/test/suites.c | 19 ++++++++ src/test/suites.h | 2 + src/test/target/unit_target.c | 9 ++-- src/test/target_helper.c | 61 +++++++++++++++---------- src/test/target_helper.h | 5 ++- 21 files changed, 291 insertions(+), 110 deletions(-) create mode 100644 README.txt create mode 100644 TESTING.txt create mode 100644 src/test/suites.c diff --git a/README.md b/README.md index d749cd9..cb1d931 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,9 @@ -# Lain +# Memcry

-### NOTE: - -**liblain** is undergoing major testing and a minor refactor. Prebuilt binaries and static builds are coming. To get liblain working before the refactor is finished, install the latest release and [cmore v0.0.3](https://github.com/vykt/cmore/releases/tag/0.0.3). - ### ABOUT: diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..6620de8 --- /dev/null +++ b/README.txt @@ -0,0 +1,51 @@ +## TL;DR + + + + + +Memcry is a memory manipulation library. It's des + +Memcry provides two views of a target's memory: areas + + +`mc_vm_map` can be updated at any time by calling `mc_update_map()`. +Most importantly, updating the map does not invalidate any existing +pointers. Instead any areas discovered to no longer be mapped are +moved to unmapped lists inside `mc_vm_map`, and their `mapped` flags +are set to `false`. + + +## Interfaces: + +As an obfuscation measure your target may be watching for memory +accesses through some system APIs. Memcry performs all operations +through _interfaces_. An interface provides read & write primitives, and +a method to acquire the target's memory map. The library comes with support +for two interfaces: + +- procfs (included) +- krncry (WIP kernel module, get [here](https://github.com/vykt/krncry)) + + +## Utils: + +Memcry also offers some QoL utils: + +- Finding the PID of a target by name the exact way ps & top do. +- A fast address -> area/object search. +- Offset & bound offset getters. + + + + + + + +For any questions, contact me on discord (@vykt), by +email (vykt[at]disroot[dot]org), or on LiberaIRC (@vykt). + + +The core memcry data structure (`mc_vm_map`) + +Searching diff --git a/TESTING.txt b/TESTING.txt new file mode 100644 index 0000000..42402ff --- /dev/null +++ b/TESTING.txt @@ -0,0 +1,84 @@ +[Summary]: + + TL;DR: Inspect failing unit tests with GDB. + + Build the unit tests with `make test [build=debug]`. This produces a + `test` executable in `$PROJROOT/build/test`. To run tests for a given + component, pass one (or more) of these flags: + + -m : Core map data structure. + -p : `procfs` interface. + -k : `krncry` interface. + -n : Map utilities. + -u : Generic utilities. + + When debugging with GDB (see whole document), use the `debug.sh` + script in `$PROJROOT/build/test/debug.sh`. + + +[Unit tests & ASAN]: + + Unit tests use the 'Check' library: + + https://libcheck.github.io/check/ + + Check forks off new processes for each unit test. This can be undesirable + because debug builds of Memcry compile with GCC's address sanitizer, + which will fail to report memory leaks if they occur in a child process. + + Check will not fork new processes if the environment's `CK_FORK` + variable is set to `no`. + + +[GDB scripts (essential)]: + + Memcry's datastructures are very tedious to navigate with raw gdb. + Instead of writing 50 character long casts, make use these gdb scripts + defined in `$PROJROOT/build/test/init.gdb`: + + pmapa + + Pretty print all areas inside `m`. + + pmapo + + Pretty print all objects of a map `m` and their constituent + areas. + + pmapua + + Pretty print all unmapped areas inside `m` + + pmapuo + + Pretty print all unmapped objects of a map `m` and their + constituent areas. + + parean * n> + + Dump a `mc_vm_area` held by a list node `cm_lst_node`. + + pxarean * n> + + Dump (hex) a `mc_vm_area` held by a list node `cm_lst_node` + + pobjn * n> + + Dump a `mc_vm_obj` held by a list node `cm_lst_node`. + + pxobjn * n> + + Dump (hex) a `mc_vm_obj` held by a list node `cm_lst_node`. + + pnode * n> + + Dump a nested list node held by another list node. + + pnodea *> * n> + + `mc_vm_obj` stores a list of pointers to its constituent area + nodes. Use `pnodea` to easily print areas in this list. + + pxnodea *> * n> + + See above. diff --git a/TODO b/TODO index 4dcf596..13cab38 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,3 @@ -[developing]: -- While there are unit tests, they catch far from - everything. To ensure your changes work, inspect - state with a gdb using the provided macros. - - [bugs]: - Change error code `LIBCMORE` -> `CMORE` - Modify GDB scripts to pretty print areas, objs and nodes diff --git a/build/test/init.gdb b/build/test/init.gdb index 6c3e51d..0b52b1e 100644 --- a/build/test/init.gdb +++ b/build/test/init.gdb @@ -331,14 +331,7 @@ define pmapuo end -# setup session +# session dependent (modify from here onwards) tb main run -set _DEBUG_ACTIVE = true - -# session dependent (modify from here onwards) -tb test_procfs_mc_open_close_fn -tb test_procfs_mc_update_map_fn -tb test_procfs_mc_read_write_fn -cont layout src diff --git a/src/lib/error.c b/src/lib/error.c index 3f7b1ae..2a16086 100644 --- a/src/lib/error.c +++ b/src/lib/error.c @@ -43,8 +43,8 @@ void mc_perror(const char * prefix) { fprintf(stderr, "%s: %s", prefix, MC_ERR_UNEXPECTED_NULL_MSG); break; - case MC_ERR_LIBCMORE: - fprintf(stderr, "%s: %s", prefix, MC_ERR_LIBCMORE_MSG); + case MC_ERR_CMORE: + fprintf(stderr, "%s: %s", prefix, MC_ERR_CMORE_MSG); break; case MC_ERR_READ_WRITE: @@ -120,8 +120,8 @@ const char * mc_strerror(const int mc_errnum) { case MC_ERR_UNEXPECTED_NULL: return MC_ERR_UNEXPECTED_NULL_MSG; - case MC_ERR_LIBCMORE: - return MC_ERR_LIBCMORE_MSG; + case MC_ERR_CMORE: + return MC_ERR_CMORE_MSG; case MC_ERR_READ_WRITE: return MC_ERR_READ_WRITE_MSG; diff --git a/src/lib/map.c b/src/lib/map.c index 9ead23d..1065704 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -133,6 +133,8 @@ int _map_obj_add_area_insert(cm_lst * obj_area_lst, //list is not empty, find appropriate insert point } else { + ret_node = NULL; + //setup iteration iter_node = obj_area_lst->head; for (int i = 0; i < obj_area_lst->len; ++i) { @@ -161,7 +163,7 @@ int _map_obj_add_area_insert(cm_lst * obj_area_lst, //check the new area was inserted successfully if (ret_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } } @@ -221,7 +223,7 @@ int _map_obj_add_area(mc_vm_obj * obj, obj->end_addr = area->end_addr; } - //insert new area & ensure the area list remains sorted + //insert new area & ensure the area list remains sorted ret = _map_obj_add_area_insert(&obj->vm_area_node_ps, area_node); if (ret) return -1; @@ -253,7 +255,7 @@ int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node) { //remove area node from object ret = cm_lst_rmv_n(&obj->vm_area_node_ps, outer_area_node); if (ret != 0) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -269,7 +271,7 @@ int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node) { //get start addr ret = cm_lst_get(&obj->vm_area_node_ps, 0, &temp_node); if (ret) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -281,7 +283,7 @@ int _map_obj_rmv_area_fast(mc_vm_obj * obj, cm_lst_node * outer_area_node) { if (index != 0) { ret = cm_lst_get(&obj->vm_area_node_ps, index, &temp_node); if (ret) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } } @@ -321,7 +323,7 @@ int _map_obj_rmv_last_area_fast(mc_vm_obj * obj, ret = cm_lst_rmv_n(&obj->last_vm_area_node_ps, outer_area_node); if (ret != 0) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -440,7 +442,7 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { ret_p = cm_lst_apd(&temp_prev_obj->last_vm_area_node_ps, &temp_area_node); if (ret_p == NULL) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -453,7 +455,7 @@ int _map_backtrack_unmapped_obj_last_vm_areas(cm_lst_node * obj_node) { //remove this area from the object's last_vm_area_node_ps list ret = cm_lst_rmv(&temp_obj->last_vm_area_node_ps, 0); if (ret == -1) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -564,10 +566,10 @@ int _map_unlink_unmapped_obj(cm_lst_node * obj_node, ret = _map_backtrack_unmapped_obj_last_vm_areas(obj_node); if (ret == -1) return -1; - //unlink this node from the list of mapped vm areas + //unlink this node from the list of mapped vm objs ret_node = cm_lst_uln_n(&map->vm_objs, obj_node); if (ret_node != obj_node) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -581,7 +583,7 @@ int _map_unlink_unmapped_obj(cm_lst_node * obj_node, //move this object node to the unmapped list ret_node = cm_lst_apd(&map->vm_objs_unmapped, &obj_node); if (ret_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -639,7 +641,7 @@ int _map_unlink_unmapped_area(cm_lst_node * area_node, //unlink this node from the list of mapped vm areas ret_node = cm_lst_uln_n(&map->vm_areas, area_node); if (ret_node != area_node) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -653,7 +655,7 @@ int _map_unlink_unmapped_area(cm_lst_node * area_node, //move this area node to the unmapped list ret_node = cm_lst_apd(&map->vm_areas_unmapped, &area_node); if (ret_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -788,7 +790,7 @@ cm_lst_node * _map_add_obj(const struct vm_entry * entry, //insert obj into map obj_node = cm_lst_ins_na(&map->vm_objs, state->prev_obj_node, &vm_obj); if (obj_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return NULL; } @@ -807,7 +809,7 @@ int _map_add_area(const struct vm_entry * entry, bool use_obj; bool forward_obj = false; - mc_vm_area area, * area_p; + mc_vm_area area; cm_lst_node * area_node; mc_vm_obj * obj; @@ -876,7 +878,7 @@ int _map_add_area(const struct vm_entry * entry, state->next_area_node, &area); } if (area_node == NULL) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -1012,9 +1014,9 @@ void mc_new_vm_map(mc_vm_map * map) { int mc_del_vm_map(mc_vm_map * map) { - int ret, len_unmapped_obj; + int ret, len_obj; - cm_lst_node * unmapped_obj_node; + cm_lst_node * obj_node; mc_vm_obj * obj; @@ -1024,18 +1026,21 @@ int mc_del_vm_map(mc_vm_map * map) { //setup iteration - len_unmapped_obj = map->vm_objs_unmapped.len; - unmapped_obj_node = map->vm_objs_unmapped.head; + len_obj = map->vm_objs.len; + obj_node = map->vm_objs.head; - //manually free all unmapped obj nodes - for (int i = 0; i < len_unmapped_obj; ++i) { + //manually free all object lists + for (int i = 0; i < len_obj; ++i) { - //fetch & destroy the object - obj = MC_GET_NODE_OBJ(unmapped_obj_node); - _map_del_vm_obj(obj); + //fetch the object + obj = MC_GET_NODE_OBJ(obj_node); + + //destroy the object's list + cm_del_lst(&obj->vm_area_node_ps); + cm_del_lst(&obj->last_vm_area_node_ps); //advance iteration - unmapped_obj_node = unmapped_obj_node->next; + obj_node = obj_node->next; } //end for @@ -1099,13 +1104,13 @@ int mc_map_clean_unmapped(mc_vm_map * map) { //empty out both unmapped lists ret = cm_lst_emp(&map->vm_areas_unmapped); if (ret) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } ret = cm_lst_emp(&map->vm_objs_unmapped); if (ret) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 5cc4794..5b174c8 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -226,7 +226,7 @@ extern __thread int mc_errno; #define MC_ERR_INTERNAL_INDEX 2200 #define MC_ERR_AREA_IN_OBJ 2201 #define MC_ERR_UNEXPECTED_NULL 2202 -#define MC_ERR_LIBCMORE 2203 +#define MC_ERR_CMORE 2203 #define MC_ERR_READ_WRITE 2204 #define MC_ERR_MEMU_TARGET 2205 #define MC_ERR_MEMU_MAP_SZ 2206 @@ -258,7 +258,7 @@ extern __thread int mc_errno; "Internal: Area is not in object when it should be.\n" #define MC_ERR_UNEXPECTED_NULL_MSG \ "Internal: Unexpected NULL pointer.\n" -#define MC_ERR_LIBCMORE_MSG \ +#define MC_ERR_CMORE_MSG \ "Internal: CMore error. See cm_perror().\n" #define MC_ERR_READ_WRITE_MSG \ "Internal: Read/write failed.\n" diff --git a/src/lib/procfs_iface.c b/src/lib/procfs_iface.c index c23f6a7..3baaa5a 100644 --- a/src/lib/procfs_iface.c +++ b/src/lib/procfs_iface.c @@ -178,8 +178,8 @@ int procfs_update_map(const mc_session * session, mc_vm_map * vm_map) { while (fgets(line_buf, LINE_LEN, fs) != NULL) { memset(&new_entry, 0, sizeof(new_entry)); - _build_entry(&new_entry, line_buf); - + _build_entry(&new_entry, line_buf); + ret = map_send_entry(&new_entry, &state, vm_map); if (ret != 0) return -1; diff --git a/src/lib/util.c b/src/lib/util.c index 12b446c..5452ca2 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -132,7 +132,7 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { //initialise vector ret = cm_new_vct(pid_vector, sizeof(pid_t)); if (ret) { - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } @@ -175,7 +175,7 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { if (ret) { closedir(ds); cm_del_vct(pid_vector); - mc_errno = MC_ERR_LIBCMORE; + mc_errno = MC_ERR_CMORE; return -1; } diff --git a/src/test/Makefile b/src/test/Makefile index 5a1e7ec..7e68314 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -11,7 +11,7 @@ #[parameters] -CFLAGS=${_CFLAGS} -fsanitize=address +CFLAGS=${_CFLAGS} -fsanitize=address -fsanitize-recover=address WARN_OPTS+=${_WARN_OPTS} -Wno-unused-variable -Wno-unused-but-set-variable LDFLAGS=-L${LIB_BIN_DIR} \ -Wl,-rpath=${LIB_BIN_DIR} -lmcry -lcmore -lcheck -lsubunit -static-libasan @@ -19,8 +19,8 @@ LDFLAGS=-L${LIB_BIN_DIR} \ #[build constants] SOURCES_TEST=check_map.c check_procfs_iface.c check_krncry_iface.c \ - check_map_util.c check_util.c map_helper.c target_helper.c \ - iface_helper.c info.c main.c + check_map_util.c check_util.c iface_helper.c info.c \ + map_helper.c suites.c target_helper.c main.c OBJECTS_TEST=${SOURCES_TEST:%.c=${BUILD_DIR}/%.o} TARGET_DIR=${shell pwd}/target diff --git a/src/test/check_map.c b/src/test/check_map.c index 739d2c5..467ddc1 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -1070,7 +1070,9 @@ START_TEST(test__map_forward_unmapped_obj_last_vm_areas) { START_TEST(test__map_unlink_unmapped_obj) { int ret; + _traverse_state state; + mc_vm_obj * obj; uintptr_t heap_state[2] = {0x6000, 0xC000}; @@ -1088,13 +1090,17 @@ START_TEST(test__map_unlink_unmapped_obj) { //only test: unlink `/lib/foo` state.prev_obj_node = &m_o_n[2]; + + //clear constituent areas + ret = cm_lst_emp(&m_o[2].vm_area_node_ps); + ck_assert_int_eq(ret, 0); ret = _map_unlink_unmapped_obj(&m_o_n[2], &state, &m); ck_assert_int_eq(ret, 0); //check `/lib/foo` has no last areas associated with it, and is unmapped assert_vm_obj(&m_o[2], "/lib/foo", "foo", - MC_UNDEF_ADDR, MC_UNDEF_ADDR, 3, 0, 2, false); + MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 0, 2, false); assert_vm_obj_list(&m_o[2].last_vm_area_node_ps, NULL, 0); //check `[heap]` has 2 last areas associated with it @@ -1190,7 +1196,7 @@ START_TEST(test__map_unlink_unmapped_area) { //second test: remove only area of '[heap]' - state.prev_obj_node = &m_o_n[0]; + /*state.prev_obj_node = &m_o_n[0]; ret = _map_unlink_unmapped_area(&m_a_n[3], &state, &m); ck_assert_int_eq(ret, 0); @@ -1207,7 +1213,7 @@ START_TEST(test__map_unlink_unmapped_area) { second_areas_unmapped, 0, 2, false); ck_assert_ptr_eq(state.prev_obj_node, &m_o_n[0]); - + */ return; } END_TEST @@ -1970,7 +1976,7 @@ START_TEST(test_mc_map_clean_unmapped) { tcase_add_checked_fixture(tc__unlink_unmapped_area, _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc__unlink_unmapped_area, test__map_unlink_unmapped_area); - + //tc__check_area_eql tc__check_area_eql = tcase_create("_check_area_eql"); tcase_add_checked_fixture(tc__check_area_eql, @@ -2025,6 +2031,7 @@ START_TEST(test_mc_map_clean_unmapped) { _setup_stub_vm_map, _teardown_vm_map); tcase_add_test(tc_clean_unmapped, test_mc_map_clean_unmapped); #endif + //add test cases to map test suite suite_add_tcase(s, tc_new_del_vm_map); diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index 561adce..18eb516 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -46,9 +46,6 @@ static void _setup_target() { ret = mc_open(&s, PROCFS, pid); ck_assert_int_eq(ret, 0); - //wait for ldlinux.so in the target - sleep(1); - mc_new_vm_map(&m); ret = mc_update_map(&s, &m); ck_assert_int_eq(ret, 0); @@ -142,10 +139,12 @@ START_TEST(test_mc_get_area_offset_bnd) { cm_lst_node * a_n; - //first test: typical offset + //setup test o = MC_GET_NODE_OBJ(m.vm_objs.head); a_n = MC_GET_NODE_PTR(o->last_vm_area_node_ps.head); + + //first test: typical offset off = mc_get_area_offset_bnd(a_n, 0x10800); ck_assert_int_eq(off, 0x800); @@ -168,16 +167,19 @@ START_TEST(test_mc_get_obj_offset_bnd) { mc_vm_obj * o; - //first test: typical offset + //setup test o_n = m.vm_objs.head; o = MC_GET_NODE_OBJ(o_n); + + //first test: typical offset + o->end_addr = 0x1000; off = mc_get_obj_offset_bnd(o_n, 0x800); ck_assert_int_eq(off, 0x800); //second test: address is lower than obj's starting address - o->end_addr = o->start_addr = 0x1000; + o->start_addr = 0x1000; off = mc_get_obj_offset_bnd(o_n, 0x800); ck_assert_int_eq(off, -1); diff --git a/src/test/check_util.c b/src/test/check_util.c index 31b1c60..bee3194 100644 --- a/src/test/check_util.c +++ b/src/test/check_util.c @@ -42,6 +42,8 @@ static void _setup_target() { int ret; + ret = clean_targets(); + ck_assert_int_eq(ret, 0); pid = start_target(); ck_assert_int_ne(pid, -1); diff --git a/src/test/iface_helper.c b/src/test/iface_helper.c index da886cb..1b21a5a 100644 --- a/src/test/iface_helper.c +++ b/src/test/iface_helper.c @@ -251,7 +251,7 @@ void assert_iface_read_write(enum mc_iface_type iface) { //check if write succeeded ret = strncmp((char *) rw_buf, IFACE_W_BUF_STR, TARGET_BUF_SZ); - INFO_PRINT("[%s][none perm]: returned %d\n", + INFO_PRINT("[%s][no perm]: returned %d\n", get_iface_name(iface), ret); @@ -271,7 +271,7 @@ void assert_iface_read_write(enum mc_iface_type iface) { get_iface_name(iface), ret); ret = strncmp((char *) rw_buf, IFACE_W_BUF_STR, TARGET_BUF_SZ); - INFO_PRINT("[%s][none perm]: returned %d\n", + INFO_PRINT("[%s][unmapped]: returned %d\n", get_iface_name(iface), ret); diff --git a/src/test/main.c b/src/test/main.c index 30d9b72..0cbe6d0 100644 --- a/src/test/main.c +++ b/src/test/main.c @@ -18,19 +18,21 @@ bool _DEBUG_ACTIVE = false; //tests bitmask -#define PROCFS_TEST 0x1 -#define KRNCRY_TEST 0x2 -#define MAP_UTIL_TEST 0x4 -#define UTIL_TEST 0x8 +#define MAP_TEST 0x1 +#define PROCFS_TEST 0x2 +#define KRNCRY_TEST 0x4 +#define MAP_UTIL_TEST 0x8 +#define UTIL_TEST 0x10 //determine which tests to run static cm_byte _get_test_mode(int argc, char ** argv) { const struct option long_opts[] = { + {"map", no_argument, NULL, 'm'}, {"procfs", no_argument, NULL, 'p'}, {"krncry", no_argument, NULL, 'k'}, - {"map-util", no_argument, NULL, 'm'}, + {"map-util", no_argument, NULL, 'n'}, {"util", no_argument, NULL, 'u'}, {0,0,0,0} }; @@ -39,12 +41,16 @@ static cm_byte _get_test_mode(int argc, char ** argv) { cm_byte test_mask = 0; - while((opt = getopt_long(argc, argv, "pkmu", long_opts, NULL)) != -1 + while((opt = getopt_long(argc, argv, "mpknu", long_opts, NULL)) != -1 && opt != 0) { //determine parsed argument switch (opt) { + case 'm': + test_mask |= MAP_TEST; + break; + case 'p': test_mask |= PROCFS_TEST; break; @@ -53,7 +59,7 @@ static cm_byte _get_test_mode(int argc, char ** argv) { test_mask |= KRNCRY_TEST; break; - case 'm': + case 'n': test_mask |= MAP_UTIL_TEST; break; @@ -70,6 +76,7 @@ static cm_byte _get_test_mode(int argc, char ** argv) { //run unit tests static void _run_unit_tests(cm_byte test_mask) { + Suite * s_fake; Suite * s_map; Suite * s_procfs_iface; Suite * s_krncry_iface; @@ -80,14 +87,16 @@ static void _run_unit_tests(cm_byte test_mask) { //initialise test suites - s_map = map_suite(); + s_fake = fake_suite(); + if (test_mask & MAP_TEST) s_map = map_suite(); if (test_mask & PROCFS_TEST) s_procfs_iface = procfs_iface_suite(); if (test_mask & KRNCRY_TEST) s_krncry_iface = krncry_iface_suite(); if (test_mask & MAP_UTIL_TEST) s_map_util = map_util_suite(); if (test_mask & UTIL_TEST) s_util = util_suite(); //create suite runner - sr = srunner_create(s_map); + sr = srunner_create(s_fake); + if (test_mask & MAP_TEST) srunner_add_suite(sr, s_map); if (test_mask & PROCFS_TEST) srunner_add_suite(sr, s_procfs_iface); if (test_mask & KRNCRY_TEST) srunner_add_suite(sr, s_krncry_iface); if (test_mask & MAP_UTIL_TEST) srunner_add_suite(sr, s_map_util); diff --git a/src/test/suites.c b/src/test/suites.c new file mode 100644 index 0000000..a1c3086 --- /dev/null +++ b/src/test/suites.c @@ -0,0 +1,19 @@ +//external libraries +#include + + + +/* + * `libcheck` requires that a test suite is constructed with at least + * one suite. However, all tests should be optional and be selected + * through command-line arguments. To circumvent this limitation, the + * test runner is constructed with a fake_suite. + */ + +Suite * fake_suite() { + + //create a placeholder suite + Suite * s = suite_create("fake suite"); + + return s; +} diff --git a/src/test/suites.h b/src/test/suites.h index 5099ab7..4c4a480 100644 --- a/src/test/suites.h +++ b/src/test/suites.h @@ -13,12 +13,14 @@ extern bool _DEBUG_ACTIVE; //unit test suites +Suite * fake_suite(); Suite * krncry_iface_suite(); Suite * procfs_iface_suite(); Suite * map_suite(); Suite * map_util_suite(); Suite * util_suite(); + //other tests //TODO diff --git a/src/test/target/unit_target.c b/src/test/target/unit_target.c index f790f55..2a3c8f8 100644 --- a/src/test/target/unit_target.c +++ b/src/test/target/unit_target.c @@ -30,6 +30,7 @@ //globals +pid_t parent_pid; void * libelf; enum target_map_state state = UNCHANGED; @@ -44,16 +45,16 @@ enum target_map_state state = UNCHANGED; void sigusr1_handler() { if (state == UNCHANGED) { - libelf = dlopen("libelf.so.1", RTLD_LAZY); state = MAPPED; } else if (state == MAPPED) { - dlclose(libelf); state = UNMAPPED; } + kill(parent_pid, SIGUSR1); + return; } @@ -61,7 +62,6 @@ void sigusr1_handler() { int main(int argc, char ** argv) { int ch; - pid_t parent_pid; void * protected_area; //check correct number of args is provided (quiet -Wunused-parameter) @@ -77,6 +77,9 @@ int main(int argc, char ** argv) { //register unit test handler signal(SIGUSR1, sigusr1_handler); + //signal parent that initialisation is finished + kill(parent_pid, SIGUSR1); + for (int i = 0; ++i; ) { //sleep for 10ms to not hoard the CPU diff --git a/src/test/target_helper.c b/src/test/target_helper.c index 7eaf92d..de65a74 100644 --- a/src/test/target_helper.c +++ b/src/test/target_helper.c @@ -42,24 +42,24 @@ char areas_unchanged[TARGET_AREAS_UNCHANGED][NAME_MAX] = { "libc.so.6", "", "", - "[vvar]", - "[vdso]", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", - "[stack]" + "[stack]", + "[vvar]", + "[vdso]" }; char objs_unchanged[TARGET_OBJS_UNCHANGED][NAME_MAX] = { "0x0", "unit_target", "libc.so.6", - "[vvar]", - "[vdso]", "ld-linux-x86-64.so.2", - "[stack]" + "[stack]", + "[vvar]", + "[vdso]" }; @@ -89,14 +89,14 @@ char areas_mapped[TARGET_AREAS_MAPPED][NAME_MAX] = { "libc.so.6", "", "", - "[vvar]", - "[vdso]", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", - "[stack]" + "[stack]", + "[vvar]", + "[vdso]" }; char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { @@ -106,10 +106,10 @@ char objs_mapped[TARGET_OBJS_MAPPED][NAME_MAX] = { "libz.so.1.2.13", "libelf-0.188.so", "libc.so.6", - "[vvar]", - "[vdso]", "ld-linux-x86-64.so.2", - "[stack]" + "[stack]", + "[vvar]", + "[vdso]" }; @@ -129,14 +129,14 @@ char areas_unmapped[TARGET_AREAS_UNMAPPED][NAME_MAX] = { "libc.so.6", "", "", - "[vvar]", - "[vdso]", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", "ld-linux-x86-64.so.2", - "[stack]" + "[stack]", + "[vvar]", + "[vdso]" }; char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { @@ -144,22 +144,26 @@ char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX] = { "unit_target", "[heap]", "libc.so.6", - "[vvar]", - "[vdso]", "ld-linux-x86-64.so.2", - "[stack]" + "[stack]", + "[vvar]", + "[vdso]" }; //signal handlers static void _sigusr1_handler() { - if (target_state == UNCHANGED) { + //printf(" received signal from unit_target.\n"); + + if (target_state == UNINIT) { + target_state = UNCHANGED; + + } else if (target_state == UNCHANGED) { target_state = MAPPED; } else if (target_state == MAPPED) { target_state = UNMAPPED; - } return; @@ -194,14 +198,21 @@ pid_t start_target() { char pid_buf[8]; char * argv[3] = {TARGET_NAME, 0, 0}; - target_state = UNCHANGED; + enum target_map_state old_state; + //setup initial state + target_state = old_state = UNINIT; + //get current pid to pass to target parent_pid = getpid(); snprintf(pid_buf, 8, "%d", parent_pid); argv[1] = pid_buf; + //register signal handler + ret_s = signal(SIGUSR1, _sigusr1_handler); + ck_assert(ret_s != SIG_ERR); + //fork a new process target_pid = fork(); ck_assert_int_ne(target_pid, -1); @@ -211,10 +222,9 @@ pid_t start_target() { ret = execve(TARGET_NAME, argv, NULL); ck_assert_int_ne(ret, -1); - //parent registers signal handler for child + //parent waits for child to complete initialisation } else { - ret_s = signal(SIGUSR1, _sigusr1_handler); - ck_assert(ret_s != SIG_ERR); + while (target_state == old_state) {} } return target_pid; @@ -295,6 +305,9 @@ void assert_target_map(mc_vm_map * map) { assert_vm_map_objs_aslr(&map->vm_objs, objs_unmapped, 0, TARGET_OBJS_UNMAPPED, true); break; + + default: + ck_assert(false); //invalid state } //end switch diff --git a/src/test/target_helper.h b/src/test/target_helper.h index e9799a9..308c173 100644 --- a/src/test/target_helper.h +++ b/src/test/target_helper.h @@ -37,6 +37,7 @@ extern char objs_unmapped[TARGET_OBJS_UNMAPPED][NAME_MAX]; //the state of the target's memory map enum target_map_state { + UNINIT, //waiting for child to start UNCHANGED, //default state MAPPED, //new areas mapped UNMAPPED //newly mapped areas now unmapped @@ -46,7 +47,7 @@ enum target_map_state { //target metadata #define TARGET_NAME "unit_target" -#define TARGET_KILL_CMD_FMT "kill $(pidof %s)" +#define TARGET_KILL_CMD_FMT "kill $(pidof %s) > /dev/null 2> /dev/null" #define TARGET_KILL_CMD_LEN NAME_MAX + 64 #define TARGET_BUF_SZ 16 /* must be even */ @@ -55,7 +56,7 @@ enum target_map_state { #define IFACE_RW_BUF_OBJ_INDEX 1 #define IFACE_RW_BUF_AREA_INDEX 4 -#define IFACE_RW_BUF_OFF 0x40 +#define IFACE_RW_BUF_OFF 0x60 #define IFACE_NONE_OBJ_INDEX 0 #define IFACE_NONE_AREA_INDEX 0 From 03919267c3963a84788bebbd777760dbac943537 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 16:29:35 +0000 Subject: [PATCH 21/45] prepare release --- README.md | 251 ++++++++++++++++++++++++++-------- README.txt | 51 ------- doc/convert_md_to_roff.sh | 62 --------- doc/md/error.md | 39 ------ doc/md/iface.md | 70 ---------- doc/md/map.md | 155 --------------------- doc/md/util.md | 43 ------ doc/roff/man3/liblain_error.3 | 70 ---------- doc/roff/man3/liblain_iface.3 | 115 ---------------- doc/roff/man3/liblain_map.3 | 232 ------------------------------- doc/roff/man3/liblain_util.3 | 75 ---------- overview.png | Bin 0 -> 142895 bytes src/lib/util.c | 39 +++--- src/test/check_util.c | 8 +- 14 files changed, 226 insertions(+), 984 deletions(-) delete mode 100644 README.txt delete mode 100755 doc/convert_md_to_roff.sh delete mode 100644 doc/md/error.md delete mode 100644 doc/md/iface.md delete mode 100644 doc/md/map.md delete mode 100644 doc/md/util.md delete mode 100644 doc/roff/man3/liblain_error.3 delete mode 100644 doc/roff/man3/liblain_iface.3 delete mode 100644 doc/roff/man3/liblain_map.3 delete mode 100644 doc/roff/man3/liblain_util.3 create mode 100644 overview.png diff --git a/README.md b/README.md index cb1d931..ef4d821 100644 --- a/README.md +++ b/README.md @@ -7,83 +7,224 @@ ### ABOUT: -The Lain library (liblain) provides a programmatic interface to the memory and memory maps of processes on Linux. Liblain can be used for: +

+ +

-- Code injection -- Memory analysis -- Anti-cheat bypass (see [lain.ko](https://github.com/vykt/lain.ko)) +**Memcry provides**: -liblain offers both a procfs and a [lain.ko](https://github.com/vykt/lain.ko) LKM backend. Both interfaces provide identical functionality and are interchangable. +- Sophisticated data structure for representing the memory map of a target process. +- A way to update the memory map as the target's mappings change without invalidating pointers. Your pointer to a `vm_area` will not suddenly read garbage after a map update. +- All `vm_areas` and "backing objects" (e.g.: `libc.so.6`) store pointers to each other; traversal is easy and fast. +- Nameless areas are assumed to belong to the previous backing object, while still being distinctly separate from the areas that 'truly' comprise a backing object. +- Support for **multiple uniform interfaces**; each interface provides a method for acquiring the memory maps of a target and provides a read & write primitive. Target catching procfs reads? Switch to an interface that uses a kernel module. +- Multiple convenient utilities. For example, a way to find the process id of a target by its name, using the exact same method as `ps` & `top`. -liblain stores both virtual memory areas and backing objects in nodes traversable as lists or trees. The use of nodes for storage means the internal memory map can be updated without invalidating any pointers. This makes development of complex tools much easier. +See the example below, and refer to `memcry.h`. Feel free to contact me on discord (*@vykt*), email (*vykt[at]disroot[dot]org*), and LiberaIRC (*@vykt*). -In addition to a memory interface liblain also provides several utilities including: +### DEPENDENCIES: -- Same method for resolving PID as ps/top. -- Fast address -> VM area search. +If you're not using a packaged release, you'll need to install: ---- +- [CMore](https://github.com/vykt/cmore) - Data structures for C. -### DEPENDENCIES: -liblain requires [libcmore](https://github.com/vykt/libcmore). +### EXAMPLE: +```c +#include +#include +#include -### INSTALLATION: +#include +#include -Fetch the repo: -``` -$ git clone https://github.com/vykt/liblain -``` -Build: -``` -$ make lib -``` +int main() { -Install: -``` -# make install -``` + int ret; + -Install additional markdown documentation: -``` -# make install_doc -``` + /* + * First, find the PID of the target based on the target's name. You can + * optionally pass a pointer to an uninitialised CMore vector if you want + * to find PIDs of multiple processes with the same name. + */ + pid_t pid; + pid = mc_pid_by_name("target_name", NULL); -To uninstall: -``` -# make uninstall -``` ---- + /* + * Open a session on your target. For the procfs interface, this will + * open file descriptors on /proc/pid/{mem,maps} + */ + mc_session s; + ret = mc_open(&s, PROCFS, pid); + if (ret != 0) { + /* on error, a perror() function is provided */ + mc_perror("[error]"); + } -### LINKING: -Ensure your linker searches for liblain in the install directory (adjust as required): -``` -# echo "/usr/local/lib" > /etc/ld.so.conf.d/liblain.conf -``` + /* + * Read the target's memory map for the first time. + */ + mc_vm_map m; + ret = mc_update_map(&s, &m); -Include `` in your sources: -``` -#include -``` -Ask your compiler to link liblain: -``` -$ gcc -o test test.c -llain + /* + * Find the "libfoo.so" object in the target. + */ + cm_lst_node * libfoo_node = NULL; + libfoo_node = mc_get_obj_node_by_basename(&m, "libfoo.so"); + if (libfoo_node == NULL) {/*...*/} + + + /* + * Print libfoo.so's starting address. + */ + mc_vm_obj * libfoo_obj = MC_GET_NODE_OBJ(libfoo_node); + printf("libfoo.so start addr: 0x%lx, end addr: 0x%lx\n", + libfoo_obj->start_addr, libfoo_obj->end_addr); + + + /* + * Print the full path of the object after libfoo.so. + */ + cm_lst_node * next_node = libfoo_node->next; + mc_vm_obj * next_obj = MC_GET_NODE_OBJ(next_node); + printf("after libfoo.so: %s\n", next_obj->pathname); + + + /* + * Get the first area of libfoo.so. The object of libfoo (libfoo_obj) + * stores pointers to area nodes. + */ + cm_lst_node * area_node_p = libfoo_obj->vm_area_node_ps.head; + cm_lst_node * area_node = MC_GET_NODE_PTR(area_node_p); + mc_vm_area * area = MC_GET_NODE_AREA(area_node); + printf("is first area writable?: %d\n, area->access & MC_ACCESS_WRITE); + + + /* + * Get the next area and print its address range. + */ + mc_vm_area * next_area = MC_GET_NODE_AREA(area_node->next); + printf("next area start addr: 0x%lx, end addr: 0x%lx\n", + next_area->start_addr, next_area->end_addr); + + + /* + * The target's (OS-wide) memory map may have been updated; we should update + * our local map. + */ + ret = mc_update_map(&s, &m); + if (ret != 0) {/*...*/} + + + /* + * Check if libfoo.so is still mapped. If not, fetch the next mapped + * object. Even if libfoo.so and its constituent areas have been unmapped, + * their nodes and object pointers will remain valid. + */ + cm_lst_node * iter_node; + mc_vm_obj * iter_obj; + if (libfoo_obj->mapped == false) { + iter_node = libfoo_node->next; + while (iter_node != m.vm_objs.head) { + iter_obj = MC_GET_NODE_OBJ(iter_node); + if (iter_obj->mapped == true) break; + } + } + + + /* + * Clean up unmapped objects & areas. This will cause `libfoo_node` and + * `libfoo_obj` pointers to become invalid. + */ + ret = mc_map_clean_unmapped(&m); + if (ret != 0) {/*...*/} + + + /* + * Clean up and exit. + */ + ret = mc_close(&s); + if (ret != 0) {/*...*/} + + ret = mc_del_vm_map(&m); + if (ret != 0) {/*...*/} + + return 0; +} + ``` ---- -### DOCUMENTATION: -See `./doc/md` for markdown documentation. After installing liblain the following manpages are available: -| Manpage | Description | -| --------------- | --------------------------- | -| `liblain_error` | Error handling | -| `liblain_map` | Memory map data structure | -| `liblain_iface` | Using interfaces | -| `liblain_util` | Utilities | + + + + + + + + + + + + + +## TL;DR + + + + + +Memcry is a memory manipulation library. It's des + +Memcry provides two views of a target's memory: areas + + +`mc_vm_map` can be updated at any time by calling `mc_update_map()`. +Most importantly, updating the map does not invalidate any existing +pointers. Instead any areas discovered to no longer be mapped are +moved to unmapped lists inside `mc_vm_map`, and their `mapped` flags +are set to `false`. + + +## Interfaces: + +As an obfuscation measure your target may be watching for memory +accesses through some system APIs. Memcry performs all operations +through _interfaces_. An interface provides read & write primitives, and +a method to acquire the target's memory map. The library comes with support +for two interfaces: + +- procfs (included) +- krncry (WIP kernel module, get [here](https://github.com/vykt/krncry)) + + +## Utils: + +Memcry also offers some QoL utils: + +- Finding the PID of a target by name the exact way ps & top do. +- A fast address -> area/object search. +- Offset & bound offset getters. + + + + + + + +For any questions, contact me on discord (@vykt), by +email (vykt[at]disroot[dot]org), or on LiberaIRC (@vykt). + + +The core memcry data structure (`mc_vm_map`) + +Searching diff --git a/README.txt b/README.txt deleted file mode 100644 index 6620de8..0000000 --- a/README.txt +++ /dev/null @@ -1,51 +0,0 @@ -## TL;DR - - - - - -Memcry is a memory manipulation library. It's des - -Memcry provides two views of a target's memory: areas - - -`mc_vm_map` can be updated at any time by calling `mc_update_map()`. -Most importantly, updating the map does not invalidate any existing -pointers. Instead any areas discovered to no longer be mapped are -moved to unmapped lists inside `mc_vm_map`, and their `mapped` flags -are set to `false`. - - -## Interfaces: - -As an obfuscation measure your target may be watching for memory -accesses through some system APIs. Memcry performs all operations -through _interfaces_. An interface provides read & write primitives, and -a method to acquire the target's memory map. The library comes with support -for two interfaces: - -- procfs (included) -- krncry (WIP kernel module, get [here](https://github.com/vykt/krncry)) - - -## Utils: - -Memcry also offers some QoL utils: - -- Finding the PID of a target by name the exact way ps & top do. -- A fast address -> area/object search. -- Offset & bound offset getters. - - - - - - - -For any questions, contact me on discord (@vykt), by -email (vykt[at]disroot[dot]org), or on LiberaIRC (@vykt). - - -The core memcry data structure (`mc_vm_map`) - -Searching diff --git a/doc/convert_md_to_roff.sh b/doc/convert_md_to_roff.sh deleted file mode 100755 index 53e4856..0000000 --- a/doc/convert_md_to_roff.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -PROJECT="liblain" -VERSION="v1.0.3" -DATE="Nov 2024" - -SRC_DIR="md" -DST_DIR="roff/man3" - -#check that pandoc is installed -if ! command -v pandoc &> /dev/null; then - echo "[error]: Pandoc is not installed." >&2 - exit 1 -fi - -#create the dst dir if it doesn't exist -mkdir -p ${DST_DIR} - -#clean previous conversions -rm ${DST_DIR}/* - -#convert all markdown files to roff -for file in "$SRC_DIR"/*.md; do - if [ -f "$file" ]; then - filename=$(basename "$file" .md) - dst_file="$DST_DIR/$filename.3" - - echo "Converting $file to $dst_file..." - pandoc -s -t man "$file" -o "$dst_file" - if [ $? -ne 0 ]; then - echo "[error]: Pandoc failed to convert $file to roff format." >&2 - fi - fi -done - -#remove incorrect formatting -for file in "$DST_DIR"/*; do - if [[ -f "$file" ]]; then - sed -i '/.TH "" "" "" ""/d' "$file" - fi -done - -#add title and name to each file -for file in "$DST_DIR"/*; do - - basefile=$(basename "$file") - namefile=${basefile%.*} - filename=$(echo $namefile | tr [A-Z] [a-z]) - FILENAME=$(echo $namefile | tr [a-z] [A-Z]) - - # add the filename to the beginning of the file in all uppercase - echo ".TH ${FILENAME} 3 \"${DATE}\" \"${PROJECT} ${VERSION}\" \"${filename}\"" | cat - $file > temp && mv temp $file - - # add the filename to the beginning of the file in all lowercase - echo ".IX Title \"${FILENAME} 3" | cat - $file > temp && mv temp $file - - new_filename="${PROJECT}_$basefile" - mv "$DST_DIR"/"$basefile" "$DST_DIR"/"$new_filename" - -done - -echo "Conversion complete." diff --git a/doc/md/error.md b/doc/md/error.md deleted file mode 100644 index 251d4ba..0000000 --- a/doc/md/error.md +++ /dev/null @@ -1,39 +0,0 @@ -### LIBRARY -Lain memory manipulation library (liblain, -llain) - - -### SYNOPSIS -```c -_Thread_local int ln_errno; - -void ln_perror(); -const char * ln_strerror(const int ln_errnum); -``` - - -### STRUCTURE -When a **liblain** function return a value indicating an error (typically -1 or NULL), the integer *ln_errno* is set to hold a unique error number that describes the error that occurred. Each error number has a corresponding textual description. - -There are 3 classes of error numbers: - -- *21XX* : Errors caused by the user of the library. -- *22XX* : Errors caused by an internal bug in the library. -- *23XX* : Errors caused by the environment (such as a failure to allocate memory). - -The *cm_errno* value is stored in thread-local storage. Setting it in one thread does not affect its value in any other thread. - - -### FUNCTIONS -The **ln_perror()** function outputs the textual description of the last error to occur, stored in *ln_errno*, to standard error. - -The **ln_strerror()** takes a error number and returns a pointer to the textual description for the said error number. - -The *ln_errno* value is stored in thread-local storage. Setting it in one thread does not affect its value in any other thread. - - -### EXAMPLES -None provided. See **perror**(3) and **strerror**(3) for identical functionality. - - -### SEE ALSO -**liblain_iface**(3), **liblain_map**(3), **liblain_util**(3) diff --git a/doc/md/iface.md b/doc/md/iface.md deleted file mode 100644 index 43beef5..0000000 --- a/doc/md/iface.md +++ /dev/null @@ -1,70 +0,0 @@ -### LIBRARY -Lain memory manipulation library (liblain, -llain) - - -### SYNOPSIS -```c -#define LN_IFACE_LAINKO 0 -#define LN_IFACE_PROCFS 1 - - -struct _ln_session { - - union { - struct { - int fd_mem; - int pid; - }; //procfs_data - struct { - char major; - int fd_dev_memu; - }; //lainko_data - }; - - ln_iface iface; - -}; -typedef struct _ln_session ln_session; - -int ln_open(ln_session * session, const int iface, const pid_t pid); -int ln_close(ln_session * session); -int ln_update_map(const ln_session * session, ln_vm_map * vm_map); -int ln_read(const ln_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -int ln_write(const ln_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); -``` - - -### STRUCTURE -**liblain** provides 2 interfaces for operating on targets: *procfs* and *lainko*. The *procfs* interface uses Linux's inbuilt */proc* pseudo-filesystem for accessing the target. The *lainko* interface uses the **lain.ko kernel module** provided separately from this library. Both interfaces provide identical functionality. If your target does not employ any countermeasures, it is easier to stick with the *procfs* interface. - -To operate on a target it must first be opened. The *ln_session* structure stores data relevant to a single open target. If you desire to operate on multiple targets at the same time, you must open multiple sessions. You can have multiple sessions utilising the same interface, and multiple sessions utilising different interfaces. - -A session does not include a memory map. You are free to maintain multiple memory maps associated with a single session. - - -### FUNCTIONS -The **ln_open()** function opens a *session* on a target with the specified *pid*. The interface to use should be specified with the *iface* argument and should take the value of *LN_IFACE_LAINKO* or *LN_IFACE_PROCFS*. - -The **ln_close()** function closes an opened *session*. - -The **ln_update_map()** function updates the passed memory map *vm_map*. This function can be called both to populate a map for the first time, and to update it. - -The **ln_read()** function reads *buf_sz* bytes at address *addr* into a buffer pointed to by *buf*, - -The **ln_write()** function writes *buf_sz* bytes at address *addr* from a buffer pointed to by *buf*. - - -### RETURN VALUES -**ln_open()**, **ln_close()**, **ln_update_map()**, **ln_read()**, and **ln_write()** functions return 0 on success and -1 on error. - -On error, *ln_errno* is set. See **liblain_error**(3). - - -### EXAMPLES -See *src/test/iface.c* for examples. - - -### SEE ALSO -**liblain_error**(3), **liblain_map**(3), **liblain_util**(3) diff --git a/doc/md/map.md b/doc/md/map.md deleted file mode 100644 index 6be0cfa..0000000 --- a/doc/md/map.md +++ /dev/null @@ -1,155 +0,0 @@ -### LIBRARY -Lain memory manipulation library (liblain, -llain) - - -### SYNOPSIS -```c -//these macros take a cm_list_node pointer -#define LN_GET_NODE_AREA(node) ((ln_vm_area *) (node->data)) -#define LN_GET_NODE_OBJ(node) ((ln_vm_obj *) (node->data)) -#define LN_GET_NODE_PTR(node) *((cm_list_node **) (node->data)) - -//ln_vm_area.access bitmasks -#define LN_ACCESS_READ 0x01 -#define LN_ACCESS_WRITE 0x02 -#define LN_ACCESS_EXEC 0x04 -#define LN_ACCESS_SHARED 0x08 - - -struct _ln_vm_obj; - -// --- [memory area] -typedef struct { - - char * pathname; - char * basename; - - uintptr_t start_addr; - uintptr_t end_addr; - - cm_byte access; - - cm_list_node * obj_node_ptr; //STORES: own vm_obj * - cm_list_node * last_obj_node_ptr; //STORES: last encountered vm_obj * - - int id; - bool mapped; //can be set to false with map update - -} ln_vm_area; - - -// --- ['backing' object] -struct _ln_vm_obj { - - char pathname[PATH_MAX]; - char basename[NAME_MAX]; - - uintptr_t start_addr; - uintptr_t end_addr; - - cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area - cm_list last_vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area - - int id; - bool mapped; //can be set to false with map update -}; -typedef struct _ln_vm_obj ln_vm_obj; - - -// --- [memory map] -typedef struct { - - //up to date entries - cm_list vm_areas; //STORES: ln_vm_area - cm_list vm_objs; //STORES: ln_vm_obj - - //unmapped entries (storage for future deallocation) - cm_list vm_areas_unmapped; //STORES: cm_list_node * of ln_vm_area - cm_list vm_objs_unmapped; //STORES: cm_list_node * of ln_vm_obj - - // [internal] - int next_id_area; - int next_id_obj; - -} ln_vm_map; - - -void ln_new_vm_map(ln_vm_map * vm_map); -int ln_del_vm_map(ln_vm_map * vm_map); -int ln_map_clean_unmapped(ln_vm_map * vm_map); - -off_t ln_get_area_offset(const cm_list_node * area_node, const uintptr_t addr); -off_t ln_get_obj_offset(const cm_list_node * obj_node, const uintptr_t addr); -cm_list_node * ln_get_vm_area_by_addr(const ln_vm_map * vm_map, - const uintptr_t addr, const off_t * offset); - -cm_list_node * ln_get_vm_obj_by_addr(const ln_vm_map * vm_map, - const uintptr_t addr, off_t * offset); -cm_list_node * ln_get_vm_obj_by_pathname(const ln_vm_map * vm_map, - const char * pathname); -cm_list_node * ln_get_vm_obj_by_basename(const ln_vm_map * vm_map, - const char * basename); -``` - - -### STRUCTURE -**liblain** heavily relies on the linked list implementation provided by **libcmore**. Have a look at **libcmore_list**(3) to understand their interface. - -A memory map represents the virtual memory area address mappings of a process. The memory map of a target is represented by a *ln_vm_map* structure. This structure consists of 2 main linked lists: *vm_areas* and *vm_objs*. The *vm_areas* list stores the virtual memory areas of a process. The *vm_objs* list stores the backing objects of a process. - -The *ln_vm_area* structure represents a single virtual memory area of a process (kernel: struct *vm_area_struct*). Access permissions of an area can be checked by applying the *LN_ACCESS_READ*, *LN_ACCESS_WRITE*, *LN_ACCESS_EXEC*, and *LN_ACCESS_SHARED* bitmasks to the *access* member. - -The *ln_vm_obj* structure represents a single 'backing file' of a set of virtual memory areas (kernel *vm_area_struct.vm_file). - -Traversal between an area and its object can be done in O(1). Each area stores a pointer to its corresponding object list node, *obj_node_ptr*, if one is present. Each area without a corresponding object stores a pointer to the last encountered object list node instead, *last_obj_node_ptr*. Each object contains a list of pointers to its corresponding area list nodes, *vm_area_node_ptrs*. - -The macros *LN_GET_NODE_AREA()*, *LN_GET_NODE_OBJ()*, and *LN_GET_NODE_PTR()* have been provided to easily fetch the data held by a linked list node. - -A memory map is populated and updated by calling **ln_update_map()** on an open session. See **liblain_iface**(3). - -Following an update to a map, some areas and objects may become unmapped. To prevent pointer invalidation, their list nodes will be moved to the *vm_areas_unmapped* and *vm_objs_unmapped* linked lists. The *mapped* values of their *ln_vm_area* and *ln_vm_obj* structures will be set to false, and the *next* and *prev* pointers of their nodes will be set to NULL. When ready, all unmapped nodes can be deallocated with **ln_map_clean_unmapped()**. - - -### FUNCTIONS -The **ln_new_vm_map()** function initialises a new map *vm_map*. - -The **ln_del_vm_map()** function deallocates all contents of a map *vm_map*. - -The **ln_map_clean_unmapped()** function deallocates all unmapped areas and objects of a map *vm_map*. - -The **ln_get_area_offset()** function returns the offset of *addr* from the start of the area *area_node*. - -The **ln_get_obj_offset()** function returns the offset of *addr* from the start of the obj *obj_node*. - -The **ln_get_area_offset_bnd()** function returns the offset of *addr* from the start of the area *area_node*, or -1 if the address is not in the area. - -The **ln_get_obj_offset_bnd()** function returns the offset of *addr* from the start of the obj *obj_node*, or -1 if the address is not in the area. - -The **ln_get_vm_area_by_addr()** functions returns a pointer to the area node that *addr* falls into. If *offset* is not NULL, it is set to the offset of *addr* from the beginning of the area. - -The **ln_get_vm_obj_by_addr()** function returns a pointer to the object node that *addr* falls into. If *offset* is not NULL, it is set to the offset of *addr* from the beginning of the object. - -The **ln_get_vm_obj_by_pathname()** function returns a pointer to the first object who's path matches *pathname*. - -The **ln_get_vm_obj_by_basename()** function returns a pointer to the first object who's name matches *basename*. - - - -### RETURN VALUES -**ln_new_vm_map()**, **ln_del_vm_map()**, and **ln_map_clean_unmapped()** functions return 0 on success and -1 on error. - -**ln_get_area_offset()**, and **ln_get_obj_offset()** return an offset on success, and -1 if *addr* does not belong in the area/object. - -**ln_get_vm_area_by_addr()** return a *cm_list_node \** holding a *ln_vm_area* on success, and NULL on error. - -**ln_get_vm_obj_by_addr()**, **ln_get_vm_obj_by_pathname()**, and **ln_get_vm_obj_by_basename()** return a *cm_list_node \** on success, and NULL on error. - -On error, *ln_errno* is set. See **liblain_error**(3). - - -### EXAMPLES -See *src/test/map.c* for examples. - - -### SEE ALSO -**liblain_error**(3), **liblain_iface**(3), **liblain_util**(3) diff --git a/doc/md/util.md b/doc/md/util.md deleted file mode 100644 index dd826f2..0000000 --- a/doc/md/util.md +++ /dev/null @@ -1,43 +0,0 @@ -### LIBRARY -Lain memory manipulation library (liblain, -llain) - - -### SYNOPSIS -```c -const char * ln_pathname_to_basename(const char * pathname); -pid_t ln_pid_by_name(const char * basename, cm_vector * pid_vector); -int ln_name_by_pid(const pid_t pid, char * name_buf); -void ln_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); -``` - - -### STRUCTURE -**liblain** provides some utility functions. - - -### FUNCTIONS -The **ln_pathname_to_basename()** function returns a pointer to the basename component of the provided *pathname*. - -The **ln_pid_by_name()** function returns an allocated vector *pid_vector* of process IDs with a name matching *basename*. On success, *pid_vector* must be manually deallocated with **cm_del_vector()**. See **libcmore_vector**(3). - -The **ln_name_by_pid()** function stores the basename (comm) inside *name_buf*. The name is fetched from */proc//status* to mimic behaviour of utilities like *top* and *ps*. - -The **ln_bytes_to_hex()** function converts a binary buffer *inp* into its hexadecimal string representation, prefixed by '0x' and stored in the *out* buffer. Note that the length of *out* must be *inp_len* \* 2 + 2. - - -### RETURN VALUES -**ln_pathname_to_basename()** returns a pointer to the basename on success, and NULL on error. - -**ln_pid_by_name()** returns the first pid in *pid_vector* on success, and -1 on error. - -**ln_name_by_pid()** returns 0 on success, and -1 on error. - -On error, *ln_errno* is set. See **liblain_error**(3). - - -### EXAMPLES -See *src/test/util.c* for examples. - - -### SEE ALSO -**liblain_error**(3), **liblain_iface**(3), **liblain_map**(3) diff --git a/doc/roff/man3/liblain_error.3 b/doc/roff/man3/liblain_error.3 deleted file mode 100644 index 54246b7..0000000 --- a/doc/roff/man3/liblain_error.3 +++ /dev/null @@ -1,70 +0,0 @@ -.IX Title "ERROR 3 -.TH ERROR 3 "Nov 2024" "liblain v1.0.3" "error" -.\" Automatically generated by Pandoc 3.1.2 -.\" -.\" Define V font for inline verbatim, using C font in formats -.\" that render this, and otherwise B font. -.ie "\f[CB]x\f[]"x" \{\ -. ftr V B -. ftr VI BI -. ftr VB B -. ftr VBI BI -.\} -.el \{\ -. ftr V CR -. ftr VI CI -. ftr VB CB -. ftr VBI CBI -.\} -.hy -.SS LIBRARY -.PP -Lain memory manipulation library (liblain, -llain) -.SS SYNOPSIS -.IP -.nf -\f[C] -_Thread_local int ln_errno; - -void ln_perror(); -const char * ln_strerror(const int ln_errnum); -\f[R] -.fi -.SS STRUCTURE -.PP -When a \f[B]liblain\f[R] function return a value indicating an error -(typically -1 or NULL), the integer \f[I]ln_errno\f[R] is set to hold a -unique error number that describes the error that occurred. -Each error number has a corresponding textual description. -.PP -There are 3 classes of error numbers: -.IP \[bu] 2 -\f[I]21XX\f[R] : Errors caused by the user of the library. -.IP \[bu] 2 -\f[I]22XX\f[R] : Errors caused by an internal bug in the library. -.IP \[bu] 2 -\f[I]23XX\f[R] : Errors caused by the environment (such as a failure to -allocate memory). -.PP -The \f[I]cm_errno\f[R] value is stored in thread-local storage. -Setting it in one thread does not affect its value in any other thread. -.SS FUNCTIONS -.PP -The \f[B]ln_perror()\f[R] function outputs the textual description of -the last error to occur, stored in \f[I]ln_errno\f[R], to standard -error. -.PP -The \f[B]ln_strerror()\f[R] takes a error number and returns a pointer -to the textual description for the said error number. -.PP -The \f[I]ln_errno\f[R] value is stored in thread-local storage. -Setting it in one thread does not affect its value in any other thread. -.SS EXAMPLES -.PP -None provided. -See \f[B]perror\f[R](3) and \f[B]strerror\f[R](3) for identical -functionality. -.SS SEE ALSO -.PP -\f[B]liblain_iface\f[R](3), \f[B]liblain_map\f[R](3), -\f[B]liblain_util\f[R](3) diff --git a/doc/roff/man3/liblain_iface.3 b/doc/roff/man3/liblain_iface.3 deleted file mode 100644 index 4e487f5..0000000 --- a/doc/roff/man3/liblain_iface.3 +++ /dev/null @@ -1,115 +0,0 @@ -.IX Title "IFACE 3 -.TH IFACE 3 "Nov 2024" "liblain v1.0.3" "iface" -.\" Automatically generated by Pandoc 3.1.2 -.\" -.\" Define V font for inline verbatim, using C font in formats -.\" that render this, and otherwise B font. -.ie "\f[CB]x\f[]"x" \{\ -. ftr V B -. ftr VI BI -. ftr VB B -. ftr VBI BI -.\} -.el \{\ -. ftr V CR -. ftr VI CI -. ftr VB CB -. ftr VBI CBI -.\} -.hy -.SS LIBRARY -.PP -Lain memory manipulation library (liblain, -llain) -.SS SYNOPSIS -.IP -.nf -\f[C] -#define LN_IFACE_LAINKO 0 -#define LN_IFACE_PROCFS 1 - - -struct _ln_session { - - union { - struct { - int fd_mem; - int pid; - }; //procfs_data - struct { - char major; - int fd_dev_memu; - }; //lainko_data - }; - - ln_iface iface; - -}; -typedef struct _ln_session ln_session; - -int ln_open(ln_session * session, const int iface, const pid_t pid); -int ln_close(ln_session * session); -int ln_update_map(const ln_session * session, ln_vm_map * vm_map); -int ln_read(const ln_session * session, const uintptr_t addr, - cm_byte * buf, const size_t buf_sz); -int ln_write(const ln_session * session, const uintptr_t addr, - const cm_byte * buf, const size_t buf_sz); -\f[R] -.fi -.SS STRUCTURE -.PP -\f[B]liblain\f[R] provides 2 interfaces for operating on targets: -\f[I]procfs\f[R] and \f[I]lainko\f[R]. -The \f[I]procfs\f[R] interface uses Linux\[cq]s inbuilt \f[I]/proc\f[R] -pseudo-filesystem for accessing the target. -The \f[I]lainko\f[R] interface uses the \f[B]lain.ko kernel module\f[R] -provided separately from this library. -Both interfaces provide identical functionality. -If your target does not employ any countermeasures, it is easier to -stick with the \f[I]procfs\f[R] interface. -.PP -To operate on a target it must first be opened. -The \f[I]ln_session\f[R] structure stores data relevant to a single open -target. -If you desire to operate on multiple targets at the same time, you must -open multiple sessions. -You can have multiple sessions utilising the same interface, and -multiple sessions utilising different interfaces. -.PP -A session does not include a memory map. -You are free to maintain multiple memory maps associated with a single -session. -.SS FUNCTIONS -.PP -The \f[B]ln_open()\f[R] function opens a \f[I]session\f[R] on a target -with the specified \f[I]pid\f[R]. -The interface to use should be specified with the \f[I]iface\f[R] -argument and should take the value of \f[I]LN_IFACE_LAINKO\f[R] or -\f[I]LN_IFACE_PROCFS\f[R]. -.PP -The \f[B]ln_close()\f[R] function closes an opened \f[I]session\f[R]. -.PP -The \f[B]ln_update_map()\f[R] function updates the passed memory map -\f[I]vm_map\f[R]. -This function can be called both to populate a map for the first time, -and to update it. -.PP -The \f[B]ln_read()\f[R] function reads \f[I]buf_sz\f[R] bytes at address -\f[I]addr\f[R] into a buffer pointed to by \f[I]buf\f[R], -.PP -The \f[B]ln_write()\f[R] function writes \f[I]buf_sz\f[R] bytes at -address \f[I]addr\f[R] from a buffer pointed to by \f[I]buf\f[R]. -.SS RETURN VALUES -.PP -\f[B]ln_open()\f[R], \f[B]ln_close()\f[R], \f[B]ln_update_map()\f[R], -\f[B]ln_read()\f[R], and \f[B]ln_write()\f[R] functions return 0 on -success and -1 on error. -.PP -On error, \f[I]ln_errno\f[R] is set. -See \f[B]liblain_error\f[R](3). -.SS EXAMPLES -.PP -See \f[I]src/test/iface.c\f[R] for examples. -.SS SEE ALSO -.PP -\f[B]liblain_error\f[R](3), \f[B]liblain_map\f[R](3), -\f[B]liblain_util\f[R](3) diff --git a/doc/roff/man3/liblain_map.3 b/doc/roff/man3/liblain_map.3 deleted file mode 100644 index a03e988..0000000 --- a/doc/roff/man3/liblain_map.3 +++ /dev/null @@ -1,232 +0,0 @@ -.IX Title "MAP 3 -.TH MAP 3 "Nov 2024" "liblain v1.0.3" "map" -.\" Automatically generated by Pandoc 3.1.2 -.\" -.\" Define V font for inline verbatim, using C font in formats -.\" that render this, and otherwise B font. -.ie "\f[CB]x\f[]"x" \{\ -. ftr V B -. ftr VI BI -. ftr VB B -. ftr VBI BI -.\} -.el \{\ -. ftr V CR -. ftr VI CI -. ftr VB CB -. ftr VBI CBI -.\} -.hy -.SS LIBRARY -.PP -Lain memory manipulation library (liblain, -llain) -.SS SYNOPSIS -.IP -.nf -\f[C] -//these macros take a cm_list_node pointer -#define LN_GET_NODE_AREA(node) ((ln_vm_area *) (node->data)) -#define LN_GET_NODE_OBJ(node) ((ln_vm_obj *) (node->data)) -#define LN_GET_NODE_PTR(node) *((cm_list_node **) (node->data)) - -//ln_vm_area.access bitmasks -#define LN_ACCESS_READ 0x01 -#define LN_ACCESS_WRITE 0x02 -#define LN_ACCESS_EXEC 0x04 -#define LN_ACCESS_SHARED 0x08 - - -struct _ln_vm_obj; - -// --- [memory area] -typedef struct { - - char * pathname; - char * basename; - - uintptr_t start_addr; - uintptr_t end_addr; - - cm_byte access; - - cm_list_node * obj_node_ptr; //STORES: own vm_obj * - cm_list_node * last_obj_node_ptr; //STORES: last encountered vm_obj * - - int id; - bool mapped; //can be set to false with map update - -} ln_vm_area; - - -// --- [\[aq]backing\[aq] object] -struct _ln_vm_obj { - - char pathname[PATH_MAX]; - char basename[NAME_MAX]; - - uintptr_t start_addr; - uintptr_t end_addr; - - cm_list vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area - cm_list last_vm_area_node_ptrs; //STORES: cm_list_node * of ln_vm_area - - int id; - bool mapped; //can be set to false with map update -}; -typedef struct _ln_vm_obj ln_vm_obj; - - -// --- [memory map] -typedef struct { - - //up to date entries - cm_list vm_areas; //STORES: ln_vm_area - cm_list vm_objs; //STORES: ln_vm_obj - - //unmapped entries (storage for future deallocation) - cm_list vm_areas_unmapped; //STORES: cm_list_node * of ln_vm_area - cm_list vm_objs_unmapped; //STORES: cm_list_node * of ln_vm_obj - - // [internal] - int next_id_area; - int next_id_obj; - -} ln_vm_map; - - -void ln_new_vm_map(ln_vm_map * vm_map); -int ln_del_vm_map(ln_vm_map * vm_map); -int ln_map_clean_unmapped(ln_vm_map * vm_map); - -off_t ln_get_area_offset(const cm_list_node * area_node, const uintptr_t addr); -off_t ln_get_obj_offset(const cm_list_node * obj_node, const uintptr_t addr); -cm_list_node * ln_get_vm_area_by_addr(const ln_vm_map * vm_map, - const uintptr_t addr, const off_t * offset); - -cm_list_node * ln_get_vm_obj_by_addr(const ln_vm_map * vm_map, - const uintptr_t addr, off_t * offset); -cm_list_node * ln_get_vm_obj_by_pathname(const ln_vm_map * vm_map, - const char * pathname); -cm_list_node * ln_get_vm_obj_by_basename(const ln_vm_map * vm_map, - const char * basename); -\f[R] -.fi -.SS STRUCTURE -.PP -\f[B]liblain\f[R] heavily relies on the linked list implementation -provided by \f[B]libcmore\f[R]. -Have a look at \f[B]libcmore_list\f[R](3) to understand their interface. -.PP -A memory map represents the virtual memory area address mappings of a -process. -The memory map of a target is represented by a \f[I]ln_vm_map\f[R] -structure. -This structure consists of 2 main linked lists: \f[I]vm_areas\f[R] and -\f[I]vm_objs\f[R]. -The \f[I]vm_areas\f[R] list stores the virtual memory areas of a -process. -The \f[I]vm_objs\f[R] list stores the backing objects of a process. -.PP -The \f[I]ln_vm_area\f[R] structure represents a single virtual memory -area of a process (kernel: struct \f[I]vm_area_struct\f[R]). -Access permissions of an area can be checked by applying the -\f[I]LN_ACCESS_READ\f[R], \f[I]LN_ACCESS_WRITE\f[R], -\f[I]LN_ACCESS_EXEC\f[R], and \f[I]LN_ACCESS_SHARED\f[R] bitmasks to the -\f[I]access\f[R] member. -.PP -The \f[I]ln_vm_obj\f[R] structure represents a single `backing file' of -a set of virtual memory areas (kernel *vm_area_struct.vm_file). -.PP -Traversal between an area and its object can be done in O(1). -Each area stores a pointer to its corresponding object list node, -\f[I]obj_node_ptr\f[R], if one is present. -Each area without a corresponding object stores a pointer to the last -encountered object list node instead, \f[I]last_obj_node_ptr\f[R]. -Each object contains a list of pointers to its corresponding area list -nodes, \f[I]vm_area_node_ptrs\f[R]. -.PP -The macros \f[I]LN_GET_NODE_AREA()\f[R], \f[I]LN_GET_NODE_OBJ()\f[R], -and \f[I]LN_GET_NODE_PTR()\f[R] have been provided to easily fetch the -data held by a linked list node. -.PP -A memory map is populated and updated by calling -\f[B]ln_update_map()\f[R] on an open session. -See \f[B]liblain_iface\f[R](3). -.PP -Following an update to a map, some areas and objects may become -unmapped. -To prevent pointer invalidation, their list nodes will be moved to the -\f[I]vm_areas_unmapped\f[R] and \f[I]vm_objs_unmapped\f[R] linked lists. -The \f[I]mapped\f[R] values of their \f[I]ln_vm_area\f[R] and -\f[I]ln_vm_obj\f[R] structures will be set to false, and the -\f[I]next\f[R] and \f[I]prev\f[R] pointers of their nodes will be set to -NULL. -When ready, all unmapped nodes can be deallocated with -\f[B]ln_map_clean_unmapped()\f[R]. -.SS FUNCTIONS -.PP -The \f[B]ln_new_vm_map()\f[R] function initialises a new map -\f[I]vm_map\f[R]. -.PP -The \f[B]ln_del_vm_map()\f[R] function deallocates all contents of a map -\f[I]vm_map\f[R]. -.PP -The \f[B]ln_map_clean_unmapped()\f[R] function deallocates all unmapped -areas and objects of a map \f[I]vm_map\f[R]. -.PP -The \f[B]ln_get_area_offset()\f[R] function returns the offset of -\f[I]addr\f[R] from the start of the area \f[I]area_node\f[R]. -.PP -The \f[B]ln_get_obj_offset()\f[R] function returns the offset of -\f[I]addr\f[R] from the start of the obj \f[I]obj_node\f[R]. -.PP -The \f[B]ln_get_area_offset_bnd()\f[R] function returns the offset of -\f[I]addr\f[R] from the start of the area \f[I]area_node\f[R], or -1 if -the address is not in the area. -.PP -The \f[B]ln_get_obj_offset_bnd()\f[R] function returns the offset of -\f[I]addr\f[R] from the start of the obj \f[I]obj_node\f[R], or -1 if -the address is not in the area. -.PP -The \f[B]ln_get_vm_area_by_addr()\f[R] functions returns a pointer to -the area node that \f[I]addr\f[R] falls into. -If \f[I]offset\f[R] is not NULL, it is set to the offset of -\f[I]addr\f[R] from the beginning of the area. -.PP -The \f[B]ln_get_vm_obj_by_addr()\f[R] function returns a pointer to the -object node that \f[I]addr\f[R] falls into. -If \f[I]offset\f[R] is not NULL, it is set to the offset of -\f[I]addr\f[R] from the beginning of the object. -.PP -The \f[B]ln_get_vm_obj_by_pathname()\f[R] function returns a pointer to -the first object who\[cq]s path matches \f[I]pathname\f[R]. -.PP -The \f[B]ln_get_vm_obj_by_basename()\f[R] function returns a pointer to -the first object who\[cq]s name matches \f[I]basename\f[R]. -.SS RETURN VALUES -.PP -\f[B]ln_new_vm_map()\f[R], \f[B]ln_del_vm_map()\f[R], and -\f[B]ln_map_clean_unmapped()\f[R] functions return 0 on success and -1 -on error. -.PP -\f[B]ln_get_area_offset()\f[R], and \f[B]ln_get_obj_offset()\f[R] return -an offset on success, and -1 if \f[I]addr\f[R] does not belong in the -area/object. -.PP -\f[B]ln_get_vm_area_by_addr()\f[R] return a \f[I]cm_list_node *\f[R] -holding a \f[I]ln_vm_area\f[R] on success, and NULL on error. -.PP -\f[B]ln_get_vm_obj_by_addr()\f[R], -\f[B]ln_get_vm_obj_by_pathname()\f[R], and -\f[B]ln_get_vm_obj_by_basename()\f[R] return a \f[I]cm_list_node *\f[R] -on success, and NULL on error. -.PP -On error, \f[I]ln_errno\f[R] is set. -See \f[B]liblain_error\f[R](3). -.SS EXAMPLES -.PP -See \f[I]src/test/map.c\f[R] for examples. -.SS SEE ALSO -.PP -\f[B]liblain_error\f[R](3), \f[B]liblain_iface\f[R](3), -\f[B]liblain_util\f[R](3) diff --git a/doc/roff/man3/liblain_util.3 b/doc/roff/man3/liblain_util.3 deleted file mode 100644 index 90da01c..0000000 --- a/doc/roff/man3/liblain_util.3 +++ /dev/null @@ -1,75 +0,0 @@ -.IX Title "UTIL 3 -.TH UTIL 3 "Nov 2024" "liblain v1.0.3" "util" -.\" Automatically generated by Pandoc 3.1.2 -.\" -.\" Define V font for inline verbatim, using C font in formats -.\" that render this, and otherwise B font. -.ie "\f[CB]x\f[]"x" \{\ -. ftr V B -. ftr VI BI -. ftr VB B -. ftr VBI BI -.\} -.el \{\ -. ftr V CR -. ftr VI CI -. ftr VB CB -. ftr VBI CBI -.\} -.hy -.SS LIBRARY -.PP -Lain memory manipulation library (liblain, -llain) -.SS SYNOPSIS -.IP -.nf -\f[C] -const char * ln_pathname_to_basename(const char * pathname); -pid_t ln_pid_by_name(const char * basename, cm_vector * pid_vector); -int ln_name_by_pid(const pid_t pid, char * name_buf); -void ln_bytes_to_hex(const cm_byte * inp, const int inp_len, char * out); -\f[R] -.fi -.SS STRUCTURE -.PP -\f[B]liblain\f[R] provides some utility functions. -.SS FUNCTIONS -.PP -The \f[B]ln_pathname_to_basename()\f[R] function returns a pointer to -the basename component of the provided \f[I]pathname\f[R]. -.PP -The \f[B]ln_pid_by_name()\f[R] function returns an allocated vector -\f[I]pid_vector\f[R] of process IDs with a name matching -\f[I]basename\f[R]. -On success, \f[I]pid_vector\f[R] must be manually deallocated with -\f[B]cm_del_vector()\f[R]. -See \f[B]libcmore_vector\f[R](3). -.PP -The \f[B]ln_name_by_pid()\f[R] function stores the basename (comm) -inside \f[I]name_buf\f[R]. -The name is fetched from \f[I]/proc//status\f[R] to mimic behaviour of -utilities like \f[I]top\f[R] and \f[I]ps\f[R]. -.PP -The \f[B]ln_bytes_to_hex()\f[R] function converts a binary buffer -\f[I]inp\f[R] into its hexadecimal string representation, prefixed by -`0x' and stored in the \f[I]out\f[R] buffer. -Note that the length of \f[I]out\f[R] must be \f[I]inp_len\f[R] * 2 + 2. -.SS RETURN VALUES -.PP -\f[B]ln_pathname_to_basename()\f[R] returns a pointer to the basename on -success, and NULL on error. -.PP -\f[B]ln_pid_by_name()\f[R] returns the first pid in \f[I]pid_vector\f[R] -on success, and -1 on error. -.PP -\f[B]ln_name_by_pid()\f[R] returns 0 on success, and -1 on error. -.PP -On error, \f[I]ln_errno\f[R] is set. -See \f[B]liblain_error\f[R](3). -.SS EXAMPLES -.PP -See \f[I]src/test/util.c\f[R] for examples. -.SS SEE ALSO -.PP -\f[B]liblain_error\f[R](3), \f[B]liblain_iface\f[R](3), -\f[B]liblain_map\f[R](3) diff --git a/overview.png b/overview.png new file mode 100644 index 0000000000000000000000000000000000000000..5e2195f1ad35b9e10b8904a62878ccbdf18e501d GIT binary patch literal 142895 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 literal 0 HcmV?d00001 diff --git a/src/lib/util.c b/src/lib/util.c index 5452ca2..7839d85 100644 --- a/src/lib/util.c +++ b/src/lib/util.c @@ -130,15 +130,18 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { //initialise vector - ret = cm_new_vct(pid_vector, sizeof(pid_t)); - if (ret) { - mc_errno = MC_ERR_CMORE; - return -1; + if (pid_vector != NULL) { + ret = cm_new_vct(pid_vector, sizeof(pid_t)); + if (ret) { + mc_errno = MC_ERR_CMORE; + return -1; + } } //open proc directory ds = opendir("/proc"); if (ds == NULL) { + if (pid_vector != NULL) cm_del_vct(pid_vector); mc_errno = MC_ERR_PROC_NAV; return -1; } @@ -153,7 +156,7 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { temp_pid = (pid_t) strtoul(d_ent->d_name, NULL, 10); if (errno == ERANGE) { closedir(ds); - cm_del_vct(pid_vector); + if (pid_vector != NULL) cm_del_vct(pid_vector); mc_errno = MC_ERR_PROC_NAV; return -1; } @@ -162,27 +165,31 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { ret = _get_status_name(name_buf, temp_pid); if (ret) { closedir(ds); - cm_del_vct(pid_vector); + if (pid_vector != NULL) cm_del_vct(pid_vector); return -1; } //if found a match ret = strcmp(name_buf, comm); if (!ret) { - - //add pid_t to list of potential PIDs - ret = cm_vct_apd(pid_vector, (cm_byte *) &temp_pid); - if (ret) { - closedir(ds); - cm_del_vct(pid_vector); - mc_errno = MC_ERR_CMORE; - return -1; - } + //add pid_t to list of potential PIDs if a vector is provided + if (pid_vector != NULL) { + + ret = cm_vct_apd(pid_vector, (cm_byte *) &temp_pid); + if (ret) { + closedir(ds); + cm_del_vct(pid_vector); + mc_errno = MC_ERR_CMORE; + return -1; + } + } + //save first pid if (!first_recorded) { first_pid = temp_pid; ++first_recorded; + if (pid_vector == NULL) break; } }//end if found process with matching name @@ -191,7 +198,7 @@ pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector) { ret = closedir(ds); if (ret) { - cm_del_vct(pid_vector); + if (pid_vector != NULL) cm_del_vct(pid_vector); mc_errno = MC_ERR_PROC_NAV; return -1; } diff --git a/src/test/check_util.c b/src/test/check_util.c index bee3194..e1f01aa 100644 --- a/src/test/check_util.c +++ b/src/test/check_util.c @@ -106,7 +106,7 @@ START_TEST(test_mc_pid_by_name) { cm_vct v; - //first test: target exists + //first test: target exists, provide vector pid = mc_pid_by_name(TARGET_NAME, &v); ck_assert_int_ne(pid, -1); @@ -115,6 +115,12 @@ START_TEST(test_mc_pid_by_name) { cm_del_vct(&v); + //second test: target exists, do not provide vector + pid = mc_pid_by_name(TARGET_NAME, NULL); + + ck_assert_int_ne(pid, -1); + + //second test: target does not exist pid = mc_pid_by_name("foo", &v); From 4cafd9c08292854b95cff253be764ec056811700 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 16:46:58 +0000 Subject: [PATCH 22/45] Cleanup repo --- Makefile | 13 ------------- README.md | 16 ++++++++-------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index df6c2f5..bd887d9 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,6 @@ LDFLAGS=-lcmore #[build constants] LIB_DIR=./src/lib TEST_DIR=./src/test -DOC_DIR=./doc BUILD_DIR=${shell pwd}/build @@ -68,9 +67,6 @@ static: _LDFLAGS='${LDFLAGS}' \ BUILD_DIR='${BUILD_DIR}/lib' -docs: -> $(MAKE) -C ${DOC_DIR} all - clean: > $(MAKE) -C ${TEST_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/test' > $(MAKE) -C ${LIB_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/lib' @@ -80,20 +76,11 @@ install: > cp -v ${BUILD_DIR}/lib/{${SHARED},${STATIC}} ${INSTALL_DIR} > mkdir -pv ${INCLUDE_INSTALL_DIR} > cp -v ${LIB_DIR}/${HEADER} ${INCLUDE_INSTALL_DIR} -> mkdir -pv ${MAN_INSTALL_DIR} -> cp -Rv ${DOC_DIR}/groff/man/* ${MAN_INSTALL_DIR} > echo "${INSTALL_DIR}" > ${LD_DIR}/90memcry.conf > ldconfig -install_docs: -> mkdir -pv ${MD_INSTALL_DIR} -> cp -v ${DOC_DIR}/md/* ${MD_INSTALL_DIR} - uninstall: > -rm -v ${INSTALL_DIR}/{${SHARED},${STATIC}} > -rm -v ${INCLUDE_INSTALL_DIR}/${HEADER} -> -rm -v ${MAN_INSTALL_DIR}/man7/memcry_*.7 -> -rm -v ${MD_INSTALL_DIR}/*.md -> -rmdir ${MD_INSTALL_DIR} > -rm ${LD_DIR}/90memcry.conf > ldconfig diff --git a/README.md b/README.md index ef4d821..e81dc4d 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,16 @@

-**Memcry provides**: +**The Memcry Library () provides**: -- Sophisticated data structure for representing the memory map of a target process. -- A way to update the memory map as the target's mappings change without invalidating pointers. Your pointer to a `vm_area` will not suddenly read garbage after a map update. -- All `vm_areas` and "backing objects" (e.g.: `libc.so.6`) store pointers to each other; traversal is easy and fast. -- Nameless areas are assumed to belong to the previous backing object, while still being distinctly separate from the areas that 'truly' comprise a backing object. -- Support for **multiple uniform interfaces**; each interface provides a method for acquiring the memory maps of a target and provides a read & write primitive. Target catching procfs reads? Switch to an interface that uses a kernel module. -- Multiple convenient utilities. For example, a way to find the process id of a target by its name, using the exact same method as `ps` & `top`. +- Graph-like data structure (*map*) for representing the memory map of a target process. +- The ability to update the *map* as the target's memory mappings change without invalidating pointers to the *map*. +- Tracking of (assumed) ownership of unnamed `vm_area`s. +- Support for **multiple interfaces** for acquiring the memory maps, reading and writing memory. +- Multiple convenient utilities. + +See the example below. Feel free to contact me on discord (*@vykt*), email (*vykt[at]disroot[dot]org*). -See the example below, and refer to `memcry.h`. Feel free to contact me on discord (*@vykt*), email (*vykt[at]disroot[dot]org*), and LiberaIRC (*@vykt*). ### DEPENDENCIES: From 12c840f4593e229a0a05dd825a98ad3239bf2dca Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 18:08:48 +0000 Subject: [PATCH 23/45] fix install script --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bd887d9..2c29f71 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,8 @@ clean: install: > mkdir -pv ${INSTALL_DIR} -> cp -v ${BUILD_DIR}/lib/{${SHARED},${STATIC}} ${INSTALL_DIR} +> cp -v ${BUILD_DIR}/lib/${SHARED} ${INSTALL_DIR} +> cp -v ${BUILD_DIR}/lib/${STATIC} ${INSTALL_DIR} > mkdir -pv ${INCLUDE_INSTALL_DIR} > cp -v ${LIB_DIR}/${HEADER} ${INCLUDE_INSTALL_DIR} > echo "${INSTALL_DIR}" > ${LD_DIR}/90memcry.conf From 94071ba900fa57a8c78a5c16235ce2c502aa4b79 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 18:52:46 +0000 Subject: [PATCH 24/45] add package make target --- Makefile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2c29f71..6d69827 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ LDFLAGS=-lcmore LIB_DIR=./src/lib TEST_DIR=./src/test BUILD_DIR=${shell pwd}/build +PACKAGE_DIR=./package #[installation constants] @@ -45,7 +46,7 @@ endif #[process targets] .PHONY prepare: -> mkdir -p ${BUILD_DIR}/test ${BUILD_DIR}/lib +> mkdir -p ${BUILD_DIR}/test ${BUILD_DIR}/lib ${PACKAGE_DIR} test: shared > $(MAKE) -C ${TEST_DIR} tests CC='${CC}' _CFLAGS='${CFLAGS_TEST}' \ @@ -70,6 +71,7 @@ static: clean: > $(MAKE) -C ${TEST_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/test' > $(MAKE) -C ${LIB_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/lib' +> -rm ${PACKAGE_DIR}/* install: > mkdir -pv ${INSTALL_DIR} @@ -85,3 +87,9 @@ uninstall: > -rm -v ${INCLUDE_INSTALL_DIR}/${HEADER} > -rm ${LD_DIR}/90memcry.conf > ldconfig + +package: all +> -cp ${BUILD_DIR}/lib/${SHARED} ${PACKAGE_DIR} +> -cp ${BUILD_DIR}/lib/${STATIC} ${PACKAGE_DIR} +> -cp ${LIB_DIR}/memcry.h ${PACKAGE_DIR} +> -tar cvjf ${PACKAGE_DIR}/memcry.tar.bz2 ${PACKAGE_DIR}/* From 225969fed824a31ef91c5d5600e294d7974ffdc1 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 18:54:45 +0000 Subject: [PATCH 25/45] Cleanup repo --- README.md | 2 +- TESTING.txt => TESTING | 0 TODO | 7 ------- 3 files changed, 1 insertion(+), 8 deletions(-) rename TESTING.txt => TESTING (100%) diff --git a/README.md b/README.md index e81dc4d..990d178 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ### ABOUT:

- +

**The Memcry Library () provides**: diff --git a/TESTING.txt b/TESTING similarity index 100% rename from TESTING.txt rename to TESTING diff --git a/TODO b/TODO index 13cab38..7d9955c 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,2 @@ -[bugs]: -- Change error code `LIBCMORE` -> `CMORE` -- Modify GDB scripts to pretty print areas, objs and nodes - with `printf` instead of crudely using `p`. Maybe add a - 'p' prefix, for "pretty"? e.g.: `epparean`? - - [features]: - add 3rd interface that uses process_vm_readv/process_vm_writev From 3c28009e8ef77417e9f8e3651d2ed953c3cb360f Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 19:00:37 +0000 Subject: [PATCH 26/45] add 'media' folder --- README.md | 10 +++++----- memcry.png => media/memcry.png | Bin media/overview.png | Bin 0 -> 128003 bytes overview.png | Bin 142895 -> 0 bytes 4 files changed, 5 insertions(+), 5 deletions(-) rename memcry.png => media/memcry.png (100%) create mode 100644 media/overview.png delete mode 100644 overview.png diff --git a/README.md b/README.md index 990d178..3518b51 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,12 @@ # Memcry

- +

### ABOUT: -

- -

- **The Memcry Library () provides**: - Graph-like data structure (*map*) for representing the memory map of a target process. @@ -19,6 +15,10 @@ - Support for **multiple interfaces** for acquiring the memory maps, reading and writing memory. - Multiple convenient utilities. +

+ +

+ See the example below. Feel free to contact me on discord (*@vykt*), email (*vykt[at]disroot[dot]org*). diff --git a/memcry.png b/media/memcry.png similarity index 100% rename from memcry.png rename to media/memcry.png diff --git a/media/overview.png b/media/overview.png new file mode 100644 index 0000000000000000000000000000000000000000..2327094eb7da952dcda9700935e2a6c4a03d7010 GIT binary patch literal 128003 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 z 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 From d05c36ce909ae6a338bbe4d87dfa027db196e891 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 19:11:55 +0000 Subject: [PATCH 27/45] Restructure media --- media/resources/COLOURS | 4 + media/resources/memcry.xcf | Bin 0 -> 51167 bytes media/resources/overview.drawio | 220 ++++++++++++++++++++++++++++++++ media/resources/phage.png | Bin 0 -> 12499 bytes 4 files changed, 224 insertions(+) create mode 100644 media/resources/COLOURS create mode 100644 media/resources/memcry.xcf create mode 100644 media/resources/overview.drawio create mode 100644 media/resources/phage.png diff --git a/media/resources/COLOURS b/media/resources/COLOURS new file mode 100644 index 0000000..9e2ce43 --- /dev/null +++ b/media/resources/COLOURS @@ -0,0 +1,4 @@ +#6e2a2c - red (border) +#342025 - grox red (background) +#a29d88 - sand (symbol) + diff --git a/media/resources/memcry.xcf b/media/resources/memcry.xcf new file mode 100644 index 0000000000000000000000000000000000000000..98271f10d7dac13cfa4ddbb25de344d778890225 GIT binary patch literal 51167 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

TwVL^?)9ADCx{vO(Df{zAkAZQEt}MJD|2} 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 literal 0 HcmV?d00001 diff --git a/media/resources/overview.drawio b/media/resources/overview.drawio new file mode 100644 index 0000000..157fa02 --- /dev/null +++ b/media/resources/overview.drawio @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/media/resources/phage.png b/media/resources/phage.png new file mode 100644 index 0000000000000000000000000000000000000000..e319d15867ee538fc6b453cb44a253320c76198a GIT binary patch literal 12499 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 i!v7!o%QV|O2UYYfgN}peJYQh5RoHrhuFP literal 0 HcmV?d00001 From 4634397e9baf25dd9f3daef7cd517109d05e0569 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 19:15:03 +0000 Subject: [PATCH 28/45] Fix README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3518b51..71dc9be 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ ### ABOUT: -**The Memcry Library () provides**: +**The Memcry Library provides**: - Graph-like data structure (*map*) for representing the memory map of a target process. -- The ability to update the *map* as the target's memory mappings change without invalidating pointers to the *map*. -- Tracking of (assumed) ownership of unnamed `vm_area`s. +- The ability to update the *map* as the target's memory mappings change without invalidating pointers to said map. +- Tracking of (assumed) ownership of unnamed *vm_area*s. - Support for **multiple interfaces** for acquiring the memory maps, reading and writing memory. - Multiple convenient utilities. @@ -19,7 +19,7 @@

-See the example below. Feel free to contact me on discord (*@vykt*), email (*vykt[at]disroot[dot]org*). +See the example below. Feel free to contact me on discord: *@vykt* or email: *vykt[at]disroot[dot]org* ### DEPENDENCIES: From a4f39a6972d384383f6dc73286e3324a0c4ef629 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 19:16:36 +0000 Subject: [PATCH 29/45] Remove notes from README.md --- README.md | 72 ++----------------------------------------------------- TESTING | 4 ++-- 2 files changed, 4 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 71dc9be..9437927 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Memcry +# MemCry

@@ -7,7 +7,7 @@ ### ABOUT: -**The Memcry Library provides**: +**The MemCry Library provides**: - Graph-like data structure (*map*) for representing the memory map of a target process. - The ability to update the *map* as the target's memory mappings change without invalidating pointers to said map. @@ -160,71 +160,3 @@ int main() { } ``` - - - - - - - - - - - - - - - - - -## TL;DR - - - - - -Memcry is a memory manipulation library. It's des - -Memcry provides two views of a target's memory: areas - - -`mc_vm_map` can be updated at any time by calling `mc_update_map()`. -Most importantly, updating the map does not invalidate any existing -pointers. Instead any areas discovered to no longer be mapped are -moved to unmapped lists inside `mc_vm_map`, and their `mapped` flags -are set to `false`. - - -## Interfaces: - -As an obfuscation measure your target may be watching for memory -accesses through some system APIs. Memcry performs all operations -through _interfaces_. An interface provides read & write primitives, and -a method to acquire the target's memory map. The library comes with support -for two interfaces: - -- procfs (included) -- krncry (WIP kernel module, get [here](https://github.com/vykt/krncry)) - - -## Utils: - -Memcry also offers some QoL utils: - -- Finding the PID of a target by name the exact way ps & top do. -- A fast address -> area/object search. -- Offset & bound offset getters. - - - - - - - -For any questions, contact me on discord (@vykt), by -email (vykt[at]disroot[dot]org), or on LiberaIRC (@vykt). - - -The core memcry data structure (`mc_vm_map`) - -Searching diff --git a/TESTING b/TESTING index 42402ff..09cab92 100644 --- a/TESTING +++ b/TESTING @@ -23,7 +23,7 @@ https://libcheck.github.io/check/ Check forks off new processes for each unit test. This can be undesirable - because debug builds of Memcry compile with GCC's address sanitizer, + because debug builds of MemCry compile with GCC's address sanitizer, which will fail to report memory leaks if they occur in a child process. Check will not fork new processes if the environment's `CK_FORK` @@ -32,7 +32,7 @@ [GDB scripts (essential)]: - Memcry's datastructures are very tedious to navigate with raw gdb. + MemCry's datastructures are very tedious to navigate with raw gdb. Instead of writing 50 character long casts, make use these gdb scripts defined in `$PROJROOT/build/test/init.gdb`: From e448d424b479ce2d0a4373c029a48bee433bb2b0 Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 19:28:13 +0000 Subject: [PATCH 30/45] Fix tabs in README.md --- README.md | 221 +++++++++++++++++++++++++++--------------------------- 1 file changed, 110 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 9437927..1cef044 100644 --- a/README.md +++ b/README.md @@ -45,118 +45,117 @@ int main() { int ret; - /* - * First, find the PID of the target based on the target's name. You can - * optionally pass a pointer to an uninitialised CMore vector if you want + /* + * First, find the PID of the target based on the target's name. You can + * optionally pass a pointer to an uninitialised CMore vector if you want * to find PIDs of multiple processes with the same name. - */ - pid_t pid; - pid = mc_pid_by_name("target_name", NULL); - - - /* - * Open a session on your target. For the procfs interface, this will - * open file descriptors on /proc/pid/{mem,maps} - */ - mc_session s; - ret = mc_open(&s, PROCFS, pid); - if (ret != 0) { - /* on error, a perror() function is provided */ - mc_perror("[error]"); - } - - - /* - * Read the target's memory map for the first time. - */ - mc_vm_map m; - ret = mc_update_map(&s, &m); - - - /* - * Find the "libfoo.so" object in the target. - */ - cm_lst_node * libfoo_node = NULL; - libfoo_node = mc_get_obj_node_by_basename(&m, "libfoo.so"); - if (libfoo_node == NULL) {/*...*/} - - - /* - * Print libfoo.so's starting address. - */ - mc_vm_obj * libfoo_obj = MC_GET_NODE_OBJ(libfoo_node); - printf("libfoo.so start addr: 0x%lx, end addr: 0x%lx\n", - libfoo_obj->start_addr, libfoo_obj->end_addr); - - - /* - * Print the full path of the object after libfoo.so. - */ - cm_lst_node * next_node = libfoo_node->next; - mc_vm_obj * next_obj = MC_GET_NODE_OBJ(next_node); - printf("after libfoo.so: %s\n", next_obj->pathname); - - - /* - * Get the first area of libfoo.so. The object of libfoo (libfoo_obj) - * stores pointers to area nodes. - */ - cm_lst_node * area_node_p = libfoo_obj->vm_area_node_ps.head; - cm_lst_node * area_node = MC_GET_NODE_PTR(area_node_p); - mc_vm_area * area = MC_GET_NODE_AREA(area_node); - printf("is first area writable?: %d\n, area->access & MC_ACCESS_WRITE); - - - /* - * Get the next area and print its address range. - */ - mc_vm_area * next_area = MC_GET_NODE_AREA(area_node->next); - printf("next area start addr: 0x%lx, end addr: 0x%lx\n", - next_area->start_addr, next_area->end_addr); - - - /* - * The target's (OS-wide) memory map may have been updated; we should update + */ + pid_t pid; + pid = mc_pid_by_name("target_name", NULL); + + + /* + * Open a session on your target. For the procfs interface, this will + * open file descriptors on /proc/pid/{mem,maps} + */ + mc_session s; + ret = mc_open(&s, PROCFS, pid); + if (ret != 0) { + /* on error, a perror() function is provided */ + mc_perror("[error]"); + } + + + /* + * Read the target's memory map for the first time. + */ + mc_vm_map m; + ret = mc_update_map(&s, &m); + + + /* + * Find the "libfoo.so" object in the target. + */ + cm_lst_node * libfoo_node = NULL; + libfoo_node = mc_get_obj_node_by_basename(&m, "libfoo.so"); + if (libfoo_node == NULL) {/*...*/} + + + /* + * Print libfoo.so's starting address. + */ + mc_vm_obj * libfoo_obj = MC_GET_NODE_OBJ(libfoo_node); + printf("libfoo.so start addr: 0x%lx, end addr: 0x%lx\n", + libfoo_obj->start_addr, libfoo_obj->end_addr); + + + /* + * Print the full path of the object after libfoo.so. + */ + cm_lst_node * next_node = libfoo_node->next; + mc_vm_obj * next_obj = MC_GET_NODE_OBJ(next_node); + printf("after libfoo.so: %s\n", next_obj->pathname); + + + /* + * Get the first area of libfoo.so. The object of libfoo (libfoo_obj) + * stores pointers to area nodes. + */ + cm_lst_node * area_node_p = libfoo_obj->vm_area_node_ps.head; + cm_lst_node * area_node = MC_GET_NODE_PTR(area_node_p); + mc_vm_area * area = MC_GET_NODE_AREA(area_node); + printf("is first area writable?: %d\n, area->access & MC_ACCESS_WRITE); + + + /* + * Get the next area and print its address range. + */ + mc_vm_area * next_area = MC_GET_NODE_AREA(area_node->next); + printf("next area start addr: 0x%lx, end addr: 0x%lx\n", + next_area->start_addr, next_area->end_addr); + + + /* + * The target's (OS-wide) memory map may have been updated; we should update * our local map. - */ - ret = mc_update_map(&s, &m); - if (ret != 0) {/*...*/} - - - /* - * Check if libfoo.so is still mapped. If not, fetch the next mapped - * object. Even if libfoo.so and its constituent areas have been unmapped, - * their nodes and object pointers will remain valid. - */ - cm_lst_node * iter_node; - mc_vm_obj * iter_obj; - if (libfoo_obj->mapped == false) { - iter_node = libfoo_node->next; - while (iter_node != m.vm_objs.head) { - iter_obj = MC_GET_NODE_OBJ(iter_node); - if (iter_obj->mapped == true) break; - } - } - - - /* - * Clean up unmapped objects & areas. This will cause `libfoo_node` and - * `libfoo_obj` pointers to become invalid. - */ - ret = mc_map_clean_unmapped(&m); - if (ret != 0) {/*...*/} - - - /* - * Clean up and exit. - */ - ret = mc_close(&s); - if (ret != 0) {/*...*/} - - ret = mc_del_vm_map(&m); - if (ret != 0) {/*...*/} - - return 0; + */ + ret = mc_update_map(&s, &m); + if (ret != 0) {/*...*/} + + + /* + * Check if libfoo.so is still mapped. If not, fetch the next mapped + * object. Even if libfoo.so and its constituent areas have been unmapped, + * their nodes and object pointers will remain valid. + */ + cm_lst_node * iter_node; + mc_vm_obj * iter_obj; + if (libfoo_obj->mapped == false) { + iter_node = libfoo_node->next; + while (iter_node != m.vm_objs.head) { + iter_obj = MC_GET_NODE_OBJ(iter_node); + if (iter_obj->mapped == true) break; + } + } + + + /* + * Clean up unmapped objects & areas. This will cause `libfoo_node` and + * `libfoo_obj` pointers to become invalid. + */ + ret = mc_map_clean_unmapped(&m); + if (ret != 0) {/*...*/} + + + /* + * Clean up and exit. + */ + ret = mc_close(&s); + if (ret != 0) {/*...*/} + + ret = mc_del_vm_map(&m); + if (ret != 0) {/*...*/} + + return 0; } - ``` From 51dabdaa1f734f22f3ac9be9ff10ae32ecf8b18e Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 2 Mar 2025 19:35:12 +0000 Subject: [PATCH 31/45] Fix README.md indents (again) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cef044..2d08b1a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you're not using a packaged release, you'll need to install: int main() { - int ret; + int ret; /* From 435c95c00311ece6cd9ba3afae440916c70cca94 Mon Sep 17 00:00:00 2001 From: vykt Date: Thu, 6 Mar 2025 23:18:52 +0000 Subject: [PATCH 32/45] fix comment --- src/lib/map.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/map.c b/src/lib/map.c index 1065704..97587ac 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -19,7 +19,7 @@ /* - * --- [INTERNAL] + * --- [INTERNAL] --- */ /* From a5bb1b2d3bb4c7c4654715b64c3bb138372b99eb Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 8 Mar 2025 15:14:27 +0000 Subject: [PATCH 33/45] Remove documentation remnants from Makefile. --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 6d69827..e93b1c0 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,6 @@ #[set as required] INSTALL_DIR=/usr/local/lib INCLUDE_INSTALL_DIR=/usr/local/include -MAN_INSTALL_DIR=/usr/local/share/man -MD_INSTALL_DIR=/usr/local/share/doc/memcry LD_DIR=/etc/ld.so.conf.d CC=gcc From a8adea5f37b4b8bbd8f2c1016c3934378eb3655b Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 8 Mar 2025 15:36:19 +0000 Subject: [PATCH 34/45] Remove unnecessary CC inter-Makefile parameter for clean targets --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e93b1c0..0e78e23 100644 --- a/Makefile +++ b/Makefile @@ -67,8 +67,8 @@ static: BUILD_DIR='${BUILD_DIR}/lib' clean: -> $(MAKE) -C ${TEST_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/test' -> $(MAKE) -C ${LIB_DIR} clean CC='${CC}' BUILD_DIR='${BUILD_DIR}/lib' +> $(MAKE) -C ${TEST_DIR} clean BUILD_DIR='${BUILD_DIR}/test' +> $(MAKE) -C ${LIB_DIR} clean BUILD_DIR='${BUILD_DIR}/lib' > -rm ${PACKAGE_DIR}/* install: From 17a17f3756d348befe43647c7aa7ea91ed6bcd20 Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 8 Mar 2025 15:50:57 +0000 Subject: [PATCH 35/45] Cleanup makefile --- src/lib/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Makefile b/src/lib/Makefile index 5893ecf..7038c0d 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -5,9 +5,9 @@ # CC - Compiler. # BUILD_DIR - Library build directory. # -# _LDFLAGS - Linker flags. # _CFLAGS - Compiler flags. # _WARN_OPTS - Compiler warnings. +# _LDFLAGS - Linker flags. CFLAGS=${_CFLAGS} From 0400069cdb7ad022e8ba7b474028725bc009a47a Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 8 Mar 2025 17:13:21 +0000 Subject: [PATCH 36/45] Cleanup testing Makefiles --- src/test/Makefile | 2 +- src/test/target/Makefile | 19 ++++--------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/test/Makefile b/src/test/Makefile index 7e68314..f46ad79 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -39,7 +39,7 @@ ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ tgt: -> $(MAKE) -C ${TARGET_DIR} targets CC='${CC}' BUILD_DIR='${BUILD_DIR}' +> $(MAKE) -C ${TARGET_DIR} target CC='${CC}' BUILD_DIR='${BUILD_DIR}' clean: > ${MAKE} -C ${TARGET_DIR} clean BUILD_DIR='${BUILD_DIR}' diff --git a/src/test/target/Makefile b/src/test/target/Makefile index 7d49a11..165f709 100644 --- a/src/test/target/Makefile +++ b/src/test/target/Makefile @@ -7,27 +7,17 @@ CFLAGS=-O0 -ggdb -WARN_OPTS+=${_WARN_OPTS} -Wno-unused-variable -Wno-unused-but-set-variable -LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} -lcmore -lcheck - -SOURCES_MANUAL_TARGET=manual_target.c -OBJECTS_MANUAL_TARGET=${SOURCES_MANUAL_TARGET:%.c=${BUILD_DIR}/%.o} +WARN_OPTS=-Wno-unused-variable -Wno-unused-but-set-variable +LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} SOURCES_UNIT_TARGET=unit_target.c OBJECTS_UNIT_TARGET=${SOURCES_UNIT_TARGET:%.c=${BUILD_DIR}/%.o} -MANUAL_TARGET=manual_target UNIT_TARGET=unit_target -TARGETS=${MANUAL_TARGET} ${UNIT_TARGET} - - -targets: ${TARGETS} +target: ${UNIT_TARGET} > mkdir -p ${BUILD_DIR} -> mv ${TARGETS} ${BUILD_DIR} - -${MANUAL_TARGET}: ${OBJECTS_MANUAL_TARGET} -> ${CC} ${CFLAGS} ${WARN_OPTS} -o $@ $^ ${LDFLAGS} +> mv ${UNIT_TARGET} ${BUILD_DIR} ${UNIT_TARGET}: ${OBJECTS_UNIT_TARGET} > ${CC} ${CFLAGS} ${WARN_OPTS} -o $@ $^ ${LDFLAGS} @@ -36,5 +26,4 @@ ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ clean: -> -rm -v ${BUILD_DIR}/${MANUAL_TARGET} > -rm -v ${BUILD_DIR}/${UNIT_TARGET} From d3628ed543c88ebedae79495ecaf02211b234de4 Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 8 Mar 2025 17:18:38 +0000 Subject: [PATCH 37/45] Cleanup target makefile v2 --- src/test/target/Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/target/Makefile b/src/test/target/Makefile index 165f709..e526e31 100644 --- a/src/test/target/Makefile +++ b/src/test/target/Makefile @@ -10,20 +10,20 @@ CFLAGS=-O0 -ggdb WARN_OPTS=-Wno-unused-variable -Wno-unused-but-set-variable LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} -SOURCES_UNIT_TARGET=unit_target.c -OBJECTS_UNIT_TARGET=${SOURCES_UNIT_TARGET:%.c=${BUILD_DIR}/%.o} +SOURCES_TARGET=unit_target.c +OBJECTS_TARGET=${SOURCES_TARGET:%.c=${BUILD_DIR}/%.o} -UNIT_TARGET=unit_target +TARGET=target -target: ${UNIT_TARGET} +target: ${TARGET} > mkdir -p ${BUILD_DIR} -> mv ${UNIT_TARGET} ${BUILD_DIR} +> mv ${TARGET} ${BUILD_DIR} -${UNIT_TARGET}: ${OBJECTS_UNIT_TARGET} +${TARGET}: ${OBJECTS_TARGET} > ${CC} ${CFLAGS} ${WARN_OPTS} -o $@ $^ ${LDFLAGS} ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ clean: -> -rm -v ${BUILD_DIR}/${UNIT_TARGET} +> -rm -v ${BUILD_DIR}/${TARGET} From 24dca834dc7a627226fdd983dbfdd9f6cda4611a Mon Sep 17 00:00:00 2001 From: vykt Date: Sat, 8 Mar 2025 17:24:48 +0000 Subject: [PATCH 38/45] uwu --- src/test/target/Makefile | 2 +- src/test/target/manual_target.c | 87 --------------------------------- 2 files changed, 1 insertion(+), 88 deletions(-) delete mode 100644 src/test/target/manual_target.c diff --git a/src/test/target/Makefile b/src/test/target/Makefile index e526e31..7bb4c87 100644 --- a/src/test/target/Makefile +++ b/src/test/target/Makefile @@ -13,7 +13,7 @@ LDFLAGS=-L${LIB_BIN_DIR} -Wl,-rpath=${LIB_BIN_DIR} SOURCES_TARGET=unit_target.c OBJECTS_TARGET=${SOURCES_TARGET:%.c=${BUILD_DIR}/%.o} -TARGET=target +TARGET=unit_target target: ${TARGET} > mkdir -p ${BUILD_DIR} diff --git a/src/test/target/manual_target.c b/src/test/target/manual_target.c deleted file mode 100644 index 1f56f53..0000000 --- a/src/test/target/manual_target.c +++ /dev/null @@ -1,87 +0,0 @@ -//standard library -#include -#include -#include -#include -#include -#include - -//system headers -#include -#include -#include - - -/* - * This program will continue counting up to confirm that it is running. - * Press `ENTER` at any point to make the program dlopen() an additional - * library, changing it's memory map. - */ - - -//globals -struct termios old_term, new_term; - - -//Ctrl+C signal handler -void sigint_handler(int signum) { - - tcsetattr(STDIN_FILENO, TCSANOW, &old_term); - _exit(signum); -} - - -//set terminal to non-blocking, non-canon, non-echo mode -void setup_terminal() { - - //clone old terminal attributes - new_term = old_term; - - //disable canonical mode & input echo - new_term.c_lflag &= ~(ICANON | ECHO); - - //set new terminal attributes - tcsetattr(STDIN_FILENO, TCSANOW, &new_term); - - //disable blocking on STDIN - fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); - - return; -} - - -int main() { - - int ch; - bool map_changed = false; - - - //get old terminal attributes - tcgetattr(STDIN_FILENO, &old_term); - - //register SIGINT handler - signal(SIGINT, sigint_handler); - - //setup terminal - setup_terminal(); - - for (int i = 0; ++i; ) { - - printf("Target running: %d\n", i); - ch = getchar(); - - //if `ENTER` key is pressed, dlopen() a library - if (ch == '\n' && map_changed == false) { - - //change process maps - dlopen("libelf.so.1", RTLD_LAZY); - map_changed = true; - - puts("Target memory map changed."); - } - - sleep(1); - } - - return 0; -} From 4951a283fcc7217e8500167f9cfbdee44d5ea10f Mon Sep 17 00:00:00 2001 From: vykt Date: Sun, 9 Mar 2025 12:58:26 +0000 Subject: [PATCH 39/45] Fix clean targets --- src/lib/Makefile | 2 +- src/test/Makefile | 2 +- src/test/target/Makefile | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/Makefile b/src/lib/Makefile index 7038c0d..c8caef6 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -40,6 +40,6 @@ ${BUILD_DIR}/%.o: %.c > ${CC} ${CFLAGS} ${WARN_OPTS} -c $< -o $@ clean: -> -rm -v ${BUILD_DIR}/*.o > -rm -v ${BUILD_DIR}/${SHARED} > -rm -v ${BUILD_DIR}/${STATIC} +> -rm -v ${OBJECTS_LIB} diff --git a/src/test/Makefile b/src/test/Makefile index f46ad79..59144b2 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -43,5 +43,5 @@ tgt: clean: > ${MAKE} -C ${TARGET_DIR} clean BUILD_DIR='${BUILD_DIR}' -> -rm -v ${BUILD_DIR}/*.o > -rm -v ${BUILD_DIR}/${TESTS} +> -rm -v ${OBJECTS_TEST} diff --git a/src/test/target/Makefile b/src/test/target/Makefile index 7bb4c87..56d5ce1 100644 --- a/src/test/target/Makefile +++ b/src/test/target/Makefile @@ -27,3 +27,4 @@ ${BUILD_DIR}/%.o: %.c clean: > -rm -v ${BUILD_DIR}/${TARGET} +> -rm -v ${OBJECTS_TARGET} From 53963bd8b907731e2a405071edb588f61aa7593f Mon Sep 17 00:00:00 2001 From: vykt Date: Fri, 14 Mar 2025 23:42:56 +0000 Subject: [PATCH 40/45] Rename gdb script file --- build/test/debug.sh | 2 +- build/test/{init.gdb => memcry.gdb} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename build/test/{init.gdb => memcry.gdb} (100%) diff --git a/build/test/debug.sh b/build/test/debug.sh index 5dc46e1..b60e60d 100755 --- a/build/test/debug.sh +++ b/build/test/debug.sh @@ -1,2 +1,2 @@ #!/bin/sh -gdb -x init.gdb --args ./test -p "$@" +gdb -x memcry.gdb --args ./test -p "$@" diff --git a/build/test/init.gdb b/build/test/memcry.gdb similarity index 100% rename from build/test/init.gdb rename to build/test/memcry.gdb From 531b92140a22b585c40adb15e37488c42a995270 Mon Sep 17 00:00:00 2001 From: vykt Date: Mon, 17 Mar 2025 23:20:24 +0000 Subject: [PATCH 41/45] Save 4.2Kb per mc_vm_obj --- src/lib/map.c | 20 ++++++++++++++------ src/lib/memcry.h | 4 ++-- src/test/check_map.c | 7 +++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/lib/map.c b/src/lib/map.c index 97587ac..731661f 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -76,10 +76,16 @@ DBG_STATIC void _map_new_vm_obj(mc_vm_obj * obj, mc_vm_map * map, const char * pathname) { - const char * basename = mc_pathname_to_basename(pathname); + size_t len; - strncpy(obj->pathname, pathname, PATH_MAX); - strncpy(obj->basename, basename, NAME_MAX); + + //allocate space for the pathname + len = strnlen(pathname, PATH_MAX); + obj->pathname = calloc(sizeof(char), len + 1); + + //construct pathname + strncpy(obj->pathname, pathname, len); + obj->basename = mc_pathname_to_basename(obj->pathname); obj->start_addr = MC_UNDEF_ADDR; obj->end_addr = MC_UNDEF_ADDR; @@ -101,6 +107,7 @@ void _map_new_vm_obj(mc_vm_obj * obj, DBG_STATIC void _map_del_vm_obj(mc_vm_obj * obj) { + free(obj->pathname); cm_del_lst(&obj->vm_area_node_ps); cm_del_lst(&obj->last_vm_area_node_ps); @@ -1036,8 +1043,9 @@ int mc_del_vm_map(mc_vm_map * map) { obj = MC_GET_NODE_OBJ(obj_node); //destroy the object's list - cm_del_lst(&obj->vm_area_node_ps); - cm_del_lst(&obj->last_vm_area_node_ps); + _map_del_vm_obj(obj); + //cm_del_lst(&obj->vm_area_node_ps); + //cm_del_lst(&obj->last_vm_area_node_ps); //advance iteration obj_node = obj_node->next; @@ -1091,7 +1099,7 @@ int mc_map_clean_unmapped(mc_vm_map * map) { //delete the unmapped object and its node del_node = MC_GET_NODE_PTR(node); obj = MC_GET_NODE_OBJ(del_node); - + _map_del_vm_obj(obj); cm_del_lst_node(del_node); diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 5b174c8..9932336 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -80,8 +80,8 @@ typedef struct { int id; bool mapped; //set to false when a map update discovers obj. to be unmapped - char basename[NAME_MAX]; - char pathname[PATH_MAX]; + char * basename; //[restrict .NAME_MAX] + char * pathname; //[restrict .PATH_MAX] } mc_vm_obj; diff --git a/src/test/check_map.c b/src/test/check_map.c index 467ddc1..7bbb879 100644 --- a/src/test/check_map.c +++ b/src/test/check_map.c @@ -391,6 +391,11 @@ static void _teardown_vm_map() { node = node->next; } + //deallocate names of objects + for (int i = 0; i < STUB_MAP_OBJ_NUM; ++i) { + free(m_o[i].pathname); + } + //remove static objects & areas _map_remove_static(&m.vm_objs, m_o_n, STUB_MAP_OBJ_NUM, sizeof(cm_lst_node), false); @@ -538,6 +543,8 @@ START_TEST(test__map_new_del_vm_obj) { MC_UNDEF_ADDR, MC_UNDEF_ADDR, 0, 0, 0, true); assert_vm_map(&m, 0, 1, 0, 0, 0, 1); + _map_del_vm_obj(&obj); + return; } From ced8fc0daf63d5e8babb93f8ff42921007a8744b Mon Sep 17 00:00:00 2001 From: vykt Date: Tue, 18 Mar 2025 02:44:23 +0000 Subject: [PATCH 42/45] Zero map structures in ctors --- src/lib/map.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/map.c b/src/lib/map.c index 731661f..c06c521 100644 --- a/src/lib/map.c +++ b/src/lib/map.c @@ -34,6 +34,10 @@ void _map_init_vm_area(mc_vm_area * area, const struct vm_entry * entry, mc_vm_obj * obj; + + //zero the area + memset(area, 0, sizeof(*area)); + //set pathname for area if applicable if (obj_node != NULL) { @@ -79,6 +83,9 @@ void _map_new_vm_obj(mc_vm_obj * obj, size_t len; + //zero the obj + memset(obj, 0, sizeof(*obj)); + //allocate space for the pathname len = strnlen(pathname, PATH_MAX); obj->pathname = calloc(sizeof(char), len + 1); @@ -982,8 +989,8 @@ int map_send_entry(const struct vm_entry * entry, void map_init_traverse_state(_traverse_state * state, const mc_vm_map * map) { - state->next_area_node = map->vm_areas.head; - state->prev_obj_node = map->vm_objs.head; + state->next_area_node = map->vm_areas.len == 0 ? NULL : map->vm_areas.head; + state->prev_obj_node = map->vm_objs.len == 0 ? NULL : map->vm_objs.head; return; } @@ -999,6 +1006,9 @@ void mc_new_vm_map(mc_vm_map * map) { //pseudo object, will adopt leading parentless vm_areas mc_vm_obj zero_obj; + //zero the map + memset(map, 0, sizeof(*map)); + //initialise lists cm_new_lst(&map->vm_areas, sizeof(mc_vm_area)); cm_new_lst(&map->vm_objs, sizeof(mc_vm_obj)); From 3c3ebc8d4784e2dce9942734a78222bc161a7153 Mon Sep 17 00:00:00 2001 From: vykt Date: Fri, 28 Mar 2025 15:20:43 +0000 Subject: [PATCH 43/45] Make map_util function names more compact --- src/lib/map_util.c | 38 +++++---- src/lib/map_util.h | 36 ++++----- src/lib/memcry.h | 34 ++++---- src/test/check_map_util.c | 164 +++++++++++++++++++------------------- 4 files changed, 133 insertions(+), 139 deletions(-) diff --git a/src/lib/map_util.c b/src/lib/map_util.c index fbd599b..d98c600 100644 --- a/src/lib/map_util.c +++ b/src/lib/map_util.c @@ -201,8 +201,8 @@ cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, * --- [EXTERNAL] --- */ -off_t mc_get_area_offset(const cm_lst_node * area_node, - const uintptr_t addr) { +off_t mc_get_area_off(const cm_lst_node * area_node, + const uintptr_t addr) { mc_vm_area * vm_area = MC_GET_NODE_AREA(area_node); @@ -210,8 +210,8 @@ off_t mc_get_area_offset(const cm_lst_node * area_node, } -off_t mc_get_obj_offset(const cm_lst_node * obj_node, - const uintptr_t addr) { +off_t mc_get_obj_off(const cm_lst_node * obj_node, + const uintptr_t addr) { mc_vm_obj * vm_obj = MC_GET_NODE_OBJ(obj_node); @@ -219,8 +219,8 @@ off_t mc_get_obj_offset(const cm_lst_node * obj_node, } -off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, - const uintptr_t addr) { +off_t mc_get_area_off_bnd(const cm_lst_node * area_node, + const uintptr_t addr) { mc_vm_area * vm_area = MC_GET_NODE_AREA(area_node); @@ -229,8 +229,8 @@ off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, } -off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, - const uintptr_t addr) { +off_t mc_get_obj_off_bnd(const cm_lst_node * obj_node, + const uintptr_t addr) { mc_vm_obj * vm_obj = MC_GET_NODE_OBJ(obj_node); @@ -239,38 +239,36 @@ off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, } -cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, - off_t * offset) { +cm_lst_node * mc_get_area_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset) { cm_lst_node * area_node; area_node = _fast_addr_find(vm_map, addr, _MAP_UTIL_GET_AREA); if (!area_node) return NULL; - if (offset != NULL) *offset = mc_get_area_offset(area_node, addr); + if (offset != NULL) *offset = mc_get_area_off(area_node, addr); return area_node; } -cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, - off_t * offset) { +cm_lst_node * mc_get_obj_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset) { cm_lst_node * obj_node; obj_node = _fast_addr_find(vm_map, addr, _MAP_UTIL_GET_OBJ); if (!obj_node) return NULL; - if (offset != NULL) *offset = mc_get_obj_offset(obj_node, addr); + if (offset != NULL) *offset = mc_get_obj_off(obj_node, addr); return obj_node; } -cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, - const char * pathname) { +cm_lst_node * mc_get_obj_by_pathname(const mc_vm_map * vm_map, + const char * pathname) { cm_lst_node * obj_node; @@ -281,8 +279,8 @@ cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, } -cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, - const char * basename) { +cm_lst_node * mc_get_obj_by_basename(const mc_vm_map * vm_map, + const char * basename) { cm_lst_node * obj_node; diff --git a/src/lib/map_util.h b/src/lib/map_util.h index 544e27e..b713054 100644 --- a/src/lib/map_util.h +++ b/src/lib/map_util.h @@ -21,25 +21,23 @@ cm_lst_node * _obj_name_find(const mc_vm_map * vm_map, //external -off_t mc_get_area_offset(const cm_lst_node * area_node, +off_t mc_get_area_off(const cm_lst_node * area_node, + const uintptr_t addr); +off_t mc_get_obj_off(const cm_lst_node * obj_node, + const uintptr_t addr); +off_t mc_get_area_off_bnd(const cm_lst_node * area_node, + const uintptr_t addr); +off_t mc_get_obj_off_bnd(const cm_lst_node * obj_node, const uintptr_t addr); -off_t mc_get_obj_offset(const cm_lst_node * obj_node, - const uintptr_t addr); -off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, - const uintptr_t addr); -off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, - const uintptr_t addr); - -cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, - off_t * offset); -cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, - off_t * offset); - -cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, - const char * pathname); -cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, - const char * basename); + +cm_lst_node * mc_get_area_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset); +cm_lst_node * mc_get_obj_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset); + +cm_lst_node * mc_get_obj_by_pathname(const mc_vm_map * vm_map, + const char * pathname); +cm_lst_node * mc_get_obj_by_basename(const mc_vm_map * vm_map, + const char * basename); #endif diff --git a/src/lib/memcry.h b/src/lib/memcry.h index 9932336..b72fea5 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -177,28 +177,26 @@ extern int mc_map_clean_unmapped(mc_vm_map * map); // --- [map util] //return: offset from start of area/object -extern off_t mc_get_area_offset(const cm_lst_node * area_node, - const uintptr_t addr); -extern off_t mc_get_obj_offset(const cm_lst_node * obj_node, - const uintptr_t addr); +extern off_t mc_get_area_off(const cm_lst_node * area_node, + const uintptr_t addr); +extern off_t mc_get_obj_off(const cm_lst_node * obj_node, + const uintptr_t addr); //return: offset from start of area/object = success, -1 = not in area/object -extern off_t mc_get_area_offset_bnd(const cm_lst_node * area_node, - const uintptr_t addr); -extern off_t mc_get_obj_offset_bnd(const cm_lst_node * obj_node, - const uintptr_t addr); +extern off_t mc_get_area_off_bnd(const cm_lst_node * area_node, + const uintptr_t addr); +extern off_t mc_get_obj_off_bnd(const cm_lst_node * obj_node, + const uintptr_t addr); //return area node * = success, NULL = fail/error -extern cm_lst_node * mc_get_area_node_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, - off_t * offset); +extern cm_lst_node * mc_get_area_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset); //return obj node * = success, NULL = fail/error -extern cm_lst_node * mc_get_obj_node_by_addr(const mc_vm_map * vm_map, - const uintptr_t addr, - off_t * offset); -extern cm_lst_node * mc_get_obj_node_by_pathname(const mc_vm_map * vm_map, - const char * pathname); -extern cm_lst_node * mc_get_obj_node_by_basename(const mc_vm_map * vm_map, - const char * basename); +extern cm_lst_node * mc_get_obj_by_addr(const mc_vm_map * vm_map, + const uintptr_t addr, off_t * offset); +extern cm_lst_node * mc_get_obj_by_pathname(const mc_vm_map * vm_map, + const char * pathname); +extern cm_lst_node * mc_get_obj_by_basename(const mc_vm_map * vm_map, + const char * basename); // --- [error handling] diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index 18eb516..e8adfbb 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -77,8 +77,8 @@ static void _teardown_target() { * --- [UNIT TESTS] --- */ -//mc_get_area_offset() [target fixture] -START_TEST(test_mc_get_area_offset) { +//mc_get_area_off() [target fixture] +START_TEST(test_mc_get_area_off) { off_t off; @@ -90,12 +90,12 @@ START_TEST(test_mc_get_area_offset) { o = MC_GET_NODE_OBJ(m.vm_objs.head); a_n = MC_GET_NODE_PTR(o->last_vm_area_node_ps.head); - off = mc_get_area_offset(a_n, 0x10800); + off = mc_get_area_off(a_n, 0x10800); ck_assert_int_eq(off, 0x800); //second test: address is lower than area's starting address - off = mc_get_area_offset(a_n, 0xF800); + off = mc_get_area_off(a_n, 0xF800); ck_assert_int_eq(off, -0x800); return; @@ -103,8 +103,8 @@ START_TEST(test_mc_get_area_offset) { } END_TEST -//mc_get_obj_offset() [target fixture] -START_TEST(test_mc_get_obj_offset) { +//mc_get_obj_off() [target fixture] +START_TEST(test_mc_get_obj_off) { off_t off; @@ -116,13 +116,13 @@ START_TEST(test_mc_get_obj_offset) { o_n = m.vm_objs.head; o = MC_GET_NODE_OBJ(o_n); - off = mc_get_obj_offset(o_n, 0x800); + off = mc_get_obj_off(o_n, 0x800); ck_assert_int_eq(off, 0x800); //second test: address is lower than obj's starting address o->end_addr = o->start_addr = 0x1000; - off = mc_get_obj_offset(o_n, 0x800); + off = mc_get_obj_off(o_n, 0x800); ck_assert_int_eq(off, -0x800); return; @@ -130,8 +130,8 @@ START_TEST(test_mc_get_obj_offset) { } END_TEST -//mc_get_area_offset_bnd() [target fixture] -START_TEST(test_mc_get_area_offset_bnd) { +//mc_get_area_off_bnd() [target fixture] +START_TEST(test_mc_get_area_off_bnd) { off_t off; @@ -145,12 +145,12 @@ START_TEST(test_mc_get_area_offset_bnd) { //first test: typical offset - off = mc_get_area_offset_bnd(a_n, 0x10800); + off = mc_get_area_off_bnd(a_n, 0x10800); ck_assert_int_eq(off, 0x800); //second test: address is lower than area's starting address - off = mc_get_area_offset_bnd(a_n, 0xF800); + off = mc_get_area_off_bnd(a_n, 0xF800); ck_assert_int_eq(off, -1); return; @@ -158,8 +158,8 @@ START_TEST(test_mc_get_area_offset_bnd) { } END_TEST -//mc_get_obj_offset_bnd() [target fixture] -START_TEST(test_mc_get_obj_offset_bnd) { +//mc_get_obj_off_bnd() [target fixture] +START_TEST(test_mc_get_obj_off_bnd) { off_t off; @@ -174,13 +174,13 @@ START_TEST(test_mc_get_obj_offset_bnd) { //first test: typical offset o->end_addr = 0x1000; - off = mc_get_obj_offset_bnd(o_n, 0x800); + off = mc_get_obj_off_bnd(o_n, 0x800); ck_assert_int_eq(off, 0x800); //second test: address is lower than obj's starting address o->start_addr = 0x1000; - off = mc_get_obj_offset_bnd(o_n, 0x800); + off = mc_get_obj_off_bnd(o_n, 0x800); ck_assert_int_eq(off, -1); return; @@ -188,8 +188,8 @@ START_TEST(test_mc_get_obj_offset_bnd) { } END_TEST -//mc_get_area_node_by_addr [target fixture] -START_TEST(test_mc_get_area_node_by_addr) { +//mc_get_area_by_addr [target fixture] +START_TEST(test_mc_get_area_by_addr) { cm_lst_node * ret_n; off_t off; @@ -203,7 +203,7 @@ START_TEST(test_mc_get_area_node_by_addr) { a_n = m.vm_areas.head->next->next; a = MC_GET_NODE_AREA(a_n); - ret_n = mc_get_area_node_by_addr(&m, a->start_addr + 0x200, &off); + ret_n = mc_get_area_by_addr(&m, a->start_addr + 0x200, &off); ck_assert_ptr_eq(ret_n, a_n); ck_assert_int_eq(off, 0x200); @@ -211,7 +211,7 @@ START_TEST(test_mc_get_area_node_by_addr) { //second test: unmapped address off = 0x0; - ret_n = mc_get_area_node_by_addr(&m, 0x1337, &off); + ret_n = mc_get_area_by_addr(&m, 0x1337, &off); ck_assert_ptr_null(ret_n); ck_assert_int_eq(off, 0); @@ -220,8 +220,8 @@ START_TEST(test_mc_get_area_node_by_addr) { } END_TEST -//mc_get_obj_node_by_addr [target fixture] -START_TEST(test_mc_get_obj_node_by_addr) { +//mc_get_obj_by_addr [target fixture] +START_TEST(test_mc_get_obj_by_addr) { cm_lst_node * ret_n; off_t off; @@ -234,7 +234,7 @@ START_TEST(test_mc_get_obj_node_by_addr) { o_n = m.vm_objs.head->next->next; o = MC_GET_NODE_OBJ(o_n); - ret_n = mc_get_obj_node_by_addr(&m, o->start_addr + 0x200, &off); + ret_n = mc_get_obj_by_addr(&m, o->start_addr + 0x200, &off); ck_assert_ptr_eq(ret_n, o_n); ck_assert_int_eq(off, 0x200); @@ -242,7 +242,7 @@ START_TEST(test_mc_get_obj_node_by_addr) { //second test: unmapped address off = 0x0; - ret_n = mc_get_obj_node_by_addr(&m, 0x1337, &off); + ret_n = mc_get_obj_by_addr(&m, 0x1337, &off); ck_assert_ptr_null(ret_n); ck_assert_int_eq(off, 0); @@ -251,8 +251,8 @@ START_TEST(test_mc_get_obj_node_by_addr) { } END_TEST -//mc_get_obj_node_by_pathname() [target fixture] -START_TEST(test_mc_get_obj_node_by_pathname) { +//mc_get_obj_by_pathname() [target fixture] +START_TEST(test_mc_get_obj_by_pathname) { cm_lst_node * ret_n; @@ -266,13 +266,13 @@ START_TEST(test_mc_get_obj_node_by_pathname) { o = MC_GET_NODE_OBJ(o_n); pathname = o->pathname; - ret_n = mc_get_obj_node_by_pathname(&m, pathname); + ret_n = mc_get_obj_by_pathname(&m, pathname); ck_assert_ptr_eq(ret_n, o_n); //second test: pathname doesn't exist pathname = "/foo/bar"; - ret_n = mc_get_obj_node_by_pathname(&m, pathname); + ret_n = mc_get_obj_by_pathname(&m, pathname); ck_assert_ptr_null(ret_n); return; @@ -280,8 +280,8 @@ START_TEST(test_mc_get_obj_node_by_pathname) { } END_TEST -//mc_get_obj_node_by_basename() [target_fixture] -START_TEST(test_mc_get_obj_node_by_basename) { +//mc_get_obj_by_basename() [target_fixture] +START_TEST(test_mc_get_obj_by_basename) { cm_lst_node * ret_n; @@ -296,13 +296,13 @@ START_TEST(test_mc_get_obj_node_by_basename) { o = MC_GET_NODE_OBJ(o_n); basename = o->basename; - ret_n = mc_get_obj_node_by_basename(&m, basename); + ret_n = mc_get_obj_by_basename(&m, basename); ck_assert_ptr_eq(ret_n, o_n); //second test: pathname doesn't exist basename = "1337"; - ret_n = mc_get_obj_node_by_basename(&m, basename); + ret_n = mc_get_obj_by_basename(&m, basename); ck_assert_ptr_null(ret_n); return; @@ -318,78 +318,78 @@ START_TEST(test_mc_get_obj_node_by_basename) { Suite * map_util_suite() { //test cases - TCase * tc_get_area_offset; - TCase * tc_get_obj_offset; - TCase * tc_get_area_offset_bnd; - TCase * tc_get_obj_offset_bnd; - TCase * tc_get_area_node_by_addr; - TCase * tc_get_obj_node_by_addr; - TCase * tc_get_obj_node_by_pathname; - TCase * tc_get_obj_node_by_basename; + TCase * tc_get_area_off; + TCase * tc_get_obj_off; + TCase * tc_get_area_off_bnd; + TCase * tc_get_obj_off_bnd; + TCase * tc_get_area_by_addr; + TCase * tc_get_obj_by_addr; + TCase * tc_get_obj_by_pathname; + TCase * tc_get_obj_by_basename; Suite * s = suite_create("map_util"); - //tc_get_area_offset - tc_get_area_offset = tcase_create("get_area_offset"); - tcase_add_checked_fixture(tc_get_area_offset, + //tc_get_area_off + tc_get_area_off = tcase_create("get_area_off"); + tcase_add_checked_fixture(tc_get_area_off, _setup_target, _teardown_target); - tcase_add_test(tc_get_area_offset, test_mc_get_area_offset); + tcase_add_test(tc_get_area_off, test_mc_get_area_off); - //tc_get_obj_offset - tc_get_obj_offset = tcase_create("get_obj_offset"); - tcase_add_checked_fixture(tc_get_obj_offset, + //tc_get_obj_off + tc_get_obj_off = tcase_create("get_obj_off"); + tcase_add_checked_fixture(tc_get_obj_off, _setup_target, _teardown_target); - tcase_add_test(tc_get_obj_offset, test_mc_get_obj_offset); + tcase_add_test(tc_get_obj_off, test_mc_get_obj_off); - //tc_get_area_offset_bnd - tc_get_area_offset_bnd = tcase_create("get_area_offset_bnd"); - tcase_add_checked_fixture(tc_get_area_offset_bnd, + //tc_get_area_off_bnd + tc_get_area_off_bnd = tcase_create("get_area_off_bnd"); + tcase_add_checked_fixture(tc_get_area_off_bnd, _setup_target, _teardown_target); - tcase_add_test(tc_get_area_offset_bnd, test_mc_get_area_offset_bnd); + tcase_add_test(tc_get_area_off_bnd, test_mc_get_area_off_bnd); - //tc_get_obj_offset_bnd - tc_get_obj_offset_bnd = tcase_create("get_obj_offset_bnd"); - tcase_add_checked_fixture(tc_get_obj_offset_bnd, + //tc_get_obj_off_bnd + tc_get_obj_off_bnd = tcase_create("get_obj_off_bnd"); + tcase_add_checked_fixture(tc_get_obj_off_bnd, _setup_target, _teardown_target); - tcase_add_test(tc_get_obj_offset_bnd, test_mc_get_obj_offset_bnd); + tcase_add_test(tc_get_obj_off_bnd, test_mc_get_obj_off_bnd); - //tc_get_vm_area_by_addr - tc_get_area_node_by_addr = tcase_create("get_area_node_by_addr"); - tcase_add_checked_fixture(tc_get_area_node_by_addr, + //tc_get_area_by_addr + tc_get_area_by_addr = tcase_create("get_area_by_addr"); + tcase_add_checked_fixture(tc_get_area_by_addr, _setup_target, _teardown_target); - tcase_add_test(tc_get_area_node_by_addr, test_mc_get_area_node_by_addr); + tcase_add_test(tc_get_area_by_addr, test_mc_get_area_by_addr); - //tc_get_vm_area_by_addr - tc_get_obj_node_by_addr = tcase_create("get_obj_node_by_addr"); - tcase_add_checked_fixture(tc_get_obj_node_by_addr, + //tc_get_area_by_addr + tc_get_obj_by_addr = tcase_create("get_obj_by_addr"); + tcase_add_checked_fixture(tc_get_obj_by_addr, _setup_target, _teardown_target); - tcase_add_test(tc_get_obj_node_by_addr, test_mc_get_obj_node_by_addr); + tcase_add_test(tc_get_obj_by_addr, test_mc_get_obj_by_addr); - //tc_get_obj_node_by_pathname - tc_get_obj_node_by_pathname = tcase_create("get_obj_node_by_pathname"); - tcase_add_checked_fixture(tc_get_obj_node_by_pathname, + //tc_get_obj_by_pathname + tc_get_obj_by_pathname = tcase_create("get_obj_by_pathname"); + tcase_add_checked_fixture(tc_get_obj_by_pathname, _setup_target, _teardown_target); - tcase_add_test(tc_get_obj_node_by_pathname, - test_mc_get_obj_node_by_pathname); + tcase_add_test(tc_get_obj_by_pathname, + test_mc_get_obj_by_pathname); - //tc_get_obj_node_by_basename - tc_get_obj_node_by_basename = tcase_create("get_obj_node_by_basename"); - tcase_add_checked_fixture(tc_get_obj_node_by_basename, + //tc_get_obj_by_basename + tc_get_obj_by_basename = tcase_create("get_obj_by_basename"); + tcase_add_checked_fixture(tc_get_obj_by_basename, _setup_target, _teardown_target); - tcase_add_test(tc_get_obj_node_by_basename, - test_mc_get_obj_node_by_basename); + tcase_add_test(tc_get_obj_by_basename, + test_mc_get_obj_by_basename); //add test cases to map util test suite - suite_add_tcase(s, tc_get_area_offset); - suite_add_tcase(s, tc_get_obj_offset); - suite_add_tcase(s, tc_get_area_offset_bnd); - suite_add_tcase(s, tc_get_obj_offset_bnd); - suite_add_tcase(s, tc_get_area_node_by_addr); - suite_add_tcase(s, tc_get_obj_node_by_addr); - suite_add_tcase(s, tc_get_obj_node_by_pathname); - suite_add_tcase(s, tc_get_obj_node_by_basename); + suite_add_tcase(s, tc_get_area_off); + suite_add_tcase(s, tc_get_obj_off); + suite_add_tcase(s, tc_get_area_off_bnd); + suite_add_tcase(s, tc_get_obj_off_bnd); + suite_add_tcase(s, tc_get_area_by_addr); + suite_add_tcase(s, tc_get_obj_by_addr); + suite_add_tcase(s, tc_get_obj_by_pathname); + suite_add_tcase(s, tc_get_obj_by_basename); return s; } From a8ad0c35e5bd9cbf4f28dba192c1d361e05a20f8 Mon Sep 17 00:00:00 2001 From: vykt Date: Fri, 16 May 2025 01:43:43 +0100 Subject: [PATCH 44/45] Add access -> string function --- Makefile | 2 +- src/lib/map_util.c | 20 ++++++++++++++++++++ src/lib/map_util.h | 2 ++ src/lib/memcry.h | 8 ++++++-- src/test/check_map_util.c | 39 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 0e78e23..351d60b 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ ifeq ($(build),debug) CFLAGS_TEST += -DDEBUG LDFLAGS += -static-libasan else - CFLAGS += -O3 + CFLAGS += -O3 -flto endif diff --git a/src/lib/map_util.c b/src/lib/map_util.c index d98c600..0544cc7 100644 --- a/src/lib/map_util.c +++ b/src/lib/map_util.c @@ -289,3 +289,23 @@ cm_lst_node * mc_get_obj_by_basename(const mc_vm_map * vm_map, return obj_node; } + + +void mc_access_to_str(const cm_byte access, char * str_buf) { + + cm_byte bit_mask = 1; + const char * on = "rwxs"; + const char * off = "---p"; + + //for every bit in the access mask + for (int i = 0; i < 4; ++i) { + + //convert the permission bit to its character representation + str_buf[i] = (bit_mask << i) & access ? on[i] : off[i]; + } + + //add a NULL terminator + str_buf[4] = '\0'; + + return; +} diff --git a/src/lib/map_util.h b/src/lib/map_util.h index b713054..51cb638 100644 --- a/src/lib/map_util.h +++ b/src/lib/map_util.h @@ -40,4 +40,6 @@ cm_lst_node * mc_get_obj_by_pathname(const mc_vm_map * vm_map, cm_lst_node * mc_get_obj_by_basename(const mc_vm_map * vm_map, const char * basename); +void mc_access_to_str(const cm_byte access, char * str_buf); + #endif diff --git a/src/lib/memcry.h b/src/lib/memcry.h index b72fea5..6755c89 100644 --- a/src/lib/memcry.h +++ b/src/lib/memcry.h @@ -151,6 +151,7 @@ typedef struct _mc_session mc_session; extern char * mc_pathname_to_basename(const char * pathname); //must destroy 'pid_vector' manually on success | pid = success, -1 = fail/error extern pid_t mc_pid_by_name(const char * comm, cm_vct * pid_vector); +//`name_buf` must be at least NAME_MAX in size. //return: 0 = success, -1 = fail/error extern int mc_name_by_pid(const pid_t pid, char * name_buf); //'out' must have space for double the length of 'inp' + 1 @@ -187,16 +188,19 @@ extern off_t mc_get_area_off_bnd(const cm_lst_node * area_node, extern off_t mc_get_obj_off_bnd(const cm_lst_node * obj_node, const uintptr_t addr); -//return area node * = success, NULL = fail/error +//return: area node * = success, NULL = fail/error extern cm_lst_node * mc_get_area_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset); -//return obj node * = success, NULL = fail/error +//return: obj node * = success, NULL = fail/error extern cm_lst_node * mc_get_obj_by_addr(const mc_vm_map * vm_map, const uintptr_t addr, off_t * offset); extern cm_lst_node * mc_get_obj_by_pathname(const mc_vm_map * vm_map, const char * pathname); extern cm_lst_node * mc_get_obj_by_basename(const mc_vm_map * vm_map, const char * basename); +//`str_buf` must be at least 5 bytes in size. +//return: 0 = success, -1 = fail/error +extern void mc_access_to_str(const cm_byte access, char * str_buf); // --- [error handling] diff --git a/src/test/check_map_util.c b/src/test/check_map_util.c index e8adfbb..1bcf653 100644 --- a/src/test/check_map_util.c +++ b/src/test/check_map_util.c @@ -283,7 +283,6 @@ START_TEST(test_mc_get_obj_by_pathname) { //mc_get_obj_by_basename() [target_fixture] START_TEST(test_mc_get_obj_by_basename) { - cm_lst_node * ret_n; mc_vm_obj * o; @@ -310,6 +309,38 @@ START_TEST(test_mc_get_obj_by_basename) { } END_TEST +//mc_access_to_str() [no fixture] +START_TEST(test_mc_access_to_str) { + + cm_byte accesses[16] = { + 0b0000, 0b0001, 0b0010, 0b0011, + 0b0100, 0b0101, 0b0110, 0b0111, + 0b1000, 0b1001, 0b1010, 0b1011, + 0b1100, 0b1101, 0b1110, 0b1111 + }; + + char * access_strings[16] = { + "---p", "r--p", "-w-p", "rw-p", + "--xp", "r-xp", "-wxp", "rwxp", + "---s", "r--s", "-w-s", "rw-s", + "--xs", "r-xs", "-wxs", "rwxs", + }; + + char str_buf[5]; + + + //only test: try every access combination + for (int i = 0; i < 16; ++i) { + + mc_access_to_str(accesses[i], str_buf); + ck_assert_str_eq(str_buf, access_strings[i]); + } + + return; + +} END_TEST + + /* * --- [SUITE] --- @@ -326,6 +357,7 @@ Suite * map_util_suite() { TCase * tc_get_obj_by_addr; TCase * tc_get_obj_by_pathname; TCase * tc_get_obj_by_basename; + TCase * tc_access_to_str; Suite * s = suite_create("map_util"); @@ -380,6 +412,10 @@ Suite * map_util_suite() { tcase_add_test(tc_get_obj_by_basename, test_mc_get_obj_by_basename); + //tc_access_to_str + tc_access_to_str = tcase_create("access_to_str"); + tcase_add_test(tc_access_to_str, test_mc_access_to_str); + //add test cases to map util test suite suite_add_tcase(s, tc_get_area_off); @@ -390,6 +426,7 @@ Suite * map_util_suite() { suite_add_tcase(s, tc_get_obj_by_addr); suite_add_tcase(s, tc_get_obj_by_pathname); suite_add_tcase(s, tc_get_obj_by_basename); + suite_add_tcase(s, tc_access_to_str); return s; } From 3e3f7373f7ef7629c984904b543565fa1813c480 Mon Sep 17 00:00:00 2001 From: vykt <106453929+vykt@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:28:31 +0100 Subject: [PATCH 45/45] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2d08b1a..44a07e4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ - Support for **multiple interfaces** for acquiring the memory maps, reading and writing memory. - Multiple convenient utilities. +**Planned**: + +- An interface for operating on coredumps instead of live processes. +