├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING ├── COPYING ├── README.rst └── src ├── CMakeLists.txt ├── README ├── api ├── CMakeLists.txt ├── acur │ └── CMakeLists.txt ├── address.c ├── allocate.c ├── darr │ ├── CMakeLists.txt │ ├── memory-support.c │ ├── memory-support.h │ └── tests │ │ ├── CMakeLists.txt │ │ ├── realloc-decrease-size-new-block.c │ │ ├── realloc-decrease-size-same-block.c │ │ ├── realloc-increase-size-new-block.c │ │ ├── realloc-null-argument.c │ │ └── util │ │ ├── CMakeLists.txt │ │ ├── block-size.c │ │ └── block-size.h ├── error-message.c.php ├── error.c ├── execute.c ├── global.c ├── handle.c ├── implementation.h ├── include │ └── proctal.h ├── linux │ ├── CMakeLists.txt │ ├── address.c │ ├── address.h │ ├── allocate.c │ ├── allocate.h │ ├── execute.h │ ├── execute │ │ ├── hijack-main.c │ │ ├── implementation.h │ │ ├── interface.c │ │ ├── syscall.c │ │ ├── unimplemented.c │ │ └── x86_64 │ │ │ ├── no-op-code.c │ │ │ ├── savestate.c │ │ │ ├── stackframe.c │ │ │ ├── syscall.c │ │ │ └── trap-code.c │ ├── implementation.c │ ├── mem.c │ ├── mem.h │ ├── proc-maps.c │ ├── proc.c │ ├── proc.h │ ├── proctal.c │ ├── proctal.h │ ├── ptrace.h │ ├── ptrace │ │ ├── error-checking.c │ │ ├── implementation.h │ │ ├── implementation │ │ │ ├── unsupported │ │ │ │ ├── cpu-state.c │ │ │ │ ├── instruction-pointer.c │ │ │ │ └── register.c │ │ │ └── x86_64 │ │ │ │ ├── cpu-state.c │ │ │ │ ├── instruction-pointer.c │ │ │ │ └── register.c │ │ ├── interface.c │ │ ├── internal.h │ │ └── user-register.c │ ├── region.c │ ├── region.h │ ├── tests │ │ ├── CMakeLists.txt │ │ ├── proc-maps │ │ │ ├── CMakeLists.txt │ │ │ ├── check.c │ │ │ ├── correct-properties.c │ │ │ ├── sample-region-heap │ │ │ ├── sample-region-program-code │ │ │ ├── sample-region-stack │ │ │ ├── sample-region-with-execute │ │ │ ├── sample-region-without-execute │ │ │ ├── sample-with-execute │ │ │ ├── sample-with-read │ │ │ ├── sample-with-write │ │ │ ├── sample-without-execute │ │ │ ├── sample-without-read │ │ │ └── sample-without-write │ │ └── proc-path-size.c │ ├── watch.h │ └── watch │ │ ├── implementation.h │ │ ├── interface.c │ │ ├── unimplemented.c │ │ └── x86_64 │ │ └── breakpoint.c ├── malloc.c ├── pause.c ├── proc.c ├── proctal.h ├── read.c ├── region.c ├── unimplemented.c ├── version.c ├── watch.c ├── windows │ ├── implementation.c │ ├── memory.c │ ├── memory.h │ ├── proctal.c │ └── proctal.h ├── write.c └── x86_64 │ ├── dr.c │ └── dr.h ├── chunk ├── CMakeLists.txt ├── chunk.c ├── chunk.h └── tests │ ├── CMakeLists.txt │ ├── evenly.c │ ├── finished.c │ └── leftover.c ├── cli ├── CMakeLists.txt ├── assembler │ ├── CMakeLists.txt │ ├── assembler.h │ ├── implementation.c │ ├── implementation.h │ ├── implementation │ │ ├── with-capstone.c │ │ ├── with-keystone.c │ │ ├── without-capstone.c │ │ └── without-keystone.c │ ├── interface.c │ ├── internal.c │ └── internal.h ├── cmd │ ├── allocate.c │ ├── allocate.h │ ├── deallocate.c │ ├── deallocate.h │ ├── dump.c │ ├── dump.h │ ├── execute.c │ ├── execute.h │ ├── measure.c │ ├── measure.h │ ├── pattern.c │ ├── pattern.h │ ├── pause.c │ ├── pause.h │ ├── read.c │ ├── read.h │ ├── search.c │ ├── search.h │ ├── watch.c │ ├── watch.h │ ├── write.c │ └── write.h ├── main.c ├── parser │ ├── CMakeLists.txt │ ├── c-types.c │ ├── hex.c │ ├── names.c.php │ ├── parser.h │ └── skip.c ├── pattern │ ├── CMakeLists.txt │ ├── pattern.c │ ├── pattern.h │ └── tests │ │ ├── CMakeLists.txt │ │ ├── invalid-patterns.c │ │ └── valid-patterns.c ├── printer.c ├── printer.h ├── scanner.c ├── scanner.h ├── tests │ ├── CMakeLists.txt │ ├── allocate-permissions-default.py │ ├── allocate-permissions.py │ ├── deallocate.py │ ├── dump-range.py │ ├── execute-code.py │ ├── invalid-type-arguments.py │ ├── pattern-range.py │ ├── pause-multiple-threads.py │ ├── read-binary.py │ ├── read-write.py │ ├── search-boundaries.py │ ├── search-permissions-default.py │ ├── search-range.py │ ├── search-review-range.py │ ├── util │ │ ├── CMakeLists.txt │ │ ├── algorithms.py │ │ ├── proctal_cli.py │ │ ├── read-mem-mt.c │ │ ├── read-mem.c │ │ ├── read_mem.py │ │ ├── read_mem_mt.py │ │ ├── sleeper.c │ │ ├── sleeper.py │ │ ├── spit-back-mt.c │ │ └── spit_back_mt.py │ ├── watch-multiple-threads.py │ ├── watch-range.py │ ├── watch-uncaught-trap-bug.py │ ├── watch-unique.py │ └── write-binary.py ├── val │ ├── CMakeLists.txt │ ├── address.c │ ├── address.h │ ├── arm.c │ ├── arm.h │ ├── assembler.c │ ├── assembler.h │ ├── byte.c │ ├── byte.h │ ├── filter.c │ ├── filter.h │ ├── ieee754.c │ ├── ieee754.h │ ├── integer-endianness.c │ ├── integer-sign-signed.c │ ├── integer-sign-unsigned.c │ ├── integer.c │ ├── integer.h │ ├── mips.c │ ├── mips.h │ ├── powerpc.c │ ├── powerpc.h │ ├── sparc.c │ ├── sparc.h │ ├── tests │ │ ├── CMakeLists.txt │ │ ├── add.c │ │ ├── cmp.c │ │ ├── filter-compare-prev.c │ │ ├── filter-compare.c │ │ ├── integer-endianness.c │ │ ├── parse-bin-invalid-ascii.c │ │ ├── parse-bin-valid-ascii.c │ │ ├── parse-invalid-ascii.c │ │ ├── parse-valid-ascii.c │ │ ├── parse.c │ │ ├── print.c │ │ └── sub.c │ ├── text-encoding-ascii.c │ ├── text.c │ ├── text.h │ ├── val.c │ ├── val.h │ ├── x86.c │ └── x86.h ├── vmagazine.c ├── vmagazine.h └── yuck │ ├── CMakeLists.txt │ ├── args.yuck.m4 │ ├── main.c │ └── main.h ├── config.h.in ├── doc ├── api │ ├── allocating-memory.xml │ ├── api-memory-management.xml │ ├── error-handling.xml │ ├── executing-code.xml │ ├── initialization-deinitialization.xml │ ├── overview.xml │ ├── pausing-execution.xml │ ├── reading-memory.xml │ ├── scanning-memory.xml │ ├── watching-memory.xml │ └── writing-memory.xml ├── cli │ ├── allocating-memory.xml │ ├── dumping-memory.xml │ ├── executing-code.xml │ ├── measuring-values.xml │ ├── overview.xml │ ├── pausing-execution.xml │ ├── reading-values.xml │ ├── searching-values.xml │ ├── type-options.xml │ ├── watching-memory.xml │ └── writing-values.xml ├── index.xml ├── installation.xml ├── introduction.xml ├── references.xml └── tutorial │ ├── linux-find-pid.xml │ ├── linux-x86-64-system-calls.xml │ ├── modding-games.xml │ ├── quick-command-tour.xml │ └── search-techniques.xml ├── magic ├── CMakeLists.txt ├── magic.h └── tests │ ├── CMakeLists.txt │ ├── array-size.c │ ├── compare.c │ └── deref.c ├── otrap ├── CMakeLists.txt ├── otrap.c ├── otrap.h └── tests │ ├── CMakeLists.txt │ ├── read-nothing.c │ ├── read-too-much.c │ ├── read-twice.c │ ├── read.c │ ├── skip-nothing.c │ ├── skip-too-much.c │ ├── skip-twice.c │ └── skip.c ├── pq ├── CMakeLists.txt ├── implementation.c ├── implementation.h ├── interface.c ├── posix │ └── implementation.c ├── pq.h ├── quit-state.c ├── quit-state.h ├── unimplemented.c └── windows-posix │ └── implementation.c ├── riter ├── CMakeLists.txt ├── riter.c ├── riter.h └── tests │ ├── CMakeLists.txt │ ├── init-defaults.c │ ├── init-errors.c │ ├── read-align.c │ ├── read-end.c │ ├── read-failure.c │ ├── read-redundancy.c │ ├── read.c │ └── reader-data.c └── swbuf ├── CMakeLists.txt ├── swbuf.c ├── swbuf.h └── tests ├── CMakeLists.txt ├── address-offset-arithmetic.c ├── init-fail-alloc.c ├── init-succeed-alloc.c ├── size.c └── swap.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Directory meant for developer builds. 2 | /build 3 | 4 | # Python cache directory. 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generates a header file that can contain values of CMake variables. 2 | configure_file(config.h.in config.h ESCAPE_QUOTES @ONLY) 3 | 4 | # So that generated files can be included. 5 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 6 | 7 | # The include statement will be relative to the src directory. 8 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 9 | 10 | # General compilation options 11 | set_property(DIRECTORY PROPERTY C_STANDARD 11) 12 | 13 | if (MSVC) 14 | # I have yet to test this on Windows. 15 | 16 | add_compile_options(/W4 /WX) 17 | 18 | add_compile_options( 19 | -DNTDDI_VERSION=NTDDI_WINXPSP2 20 | ) 21 | else() 22 | # Assuming to be GCC compatible. 23 | 24 | # Make the compiler less forgiving. 25 | add_compile_options( 26 | -Wall 27 | -Wextra 28 | -Wfatal-errors 29 | -Wpointer-arith 30 | ) 31 | 32 | # Turn warnings into error messages. 33 | add_compile_options( 34 | -Werror=incompatible-pointer-types 35 | ) 36 | 37 | # Suppress less desirable warning messages. 38 | add_compile_options( 39 | -Wno-unused-variable 40 | -Wno-unused-label 41 | -Wno-unused-function 42 | -Wno-unused-parameter 43 | ) 44 | 45 | # Make ssize_t available in C11 mode. 46 | add_definitions(-D_XOPEN_SOURCE=500) 47 | 48 | # Make usleep available. 49 | add_definitions(-D_POSIX_C_SOURCE=200112L) 50 | 51 | # Let glibc declare syscall. 52 | add_definitions(-D_DEFAULT_SOURCE) 53 | endif() 54 | 55 | # Let each module build their targets. 56 | add_subdirectory(api) 57 | add_subdirectory(chunk) 58 | add_subdirectory(cli) 59 | add_subdirectory(magic) 60 | add_subdirectory(otrap) 61 | add_subdirectory(pq) 62 | add_subdirectory(riter) 63 | add_subdirectory(swbuf) 64 | -------------------------------------------------------------------------------- /src/README: -------------------------------------------------------------------------------- 1 | How the source code for Proctal is organized: 2 | 3 | - api 4 | The C library. 5 | 6 | - cli 7 | The command line tool. 8 | 9 | - doc 10 | The official documentation. These XML files are suitable for 11 | converting to other formats, such as HTML. The official website makes 12 | use of these. 13 | 14 | - magic 15 | A collection of clever C constructs. 16 | 17 | - otrap 18 | Allows you to read data that was written to the C standard library's 19 | FILE handle. 20 | 21 | - swbuf 22 | A buffer made up of two sides. They are guaranteed to be adjacent in memory, 23 | so you can dereference values that live on both sides. Indexes are relative 24 | to the middle of the two. You can move the contents of one side to the 25 | other. 26 | 27 | - chunk 28 | A way to partition a large buffer into smaller chunks and iterating 29 | over them. 30 | 31 | - pq 32 | Checks whether the program receives any kind of message or signal to 33 | quit while also preventing it from exiting. 34 | 35 | - riter 36 | Efficiently iterates over many addressable values in memory by reading large 37 | chunks in advance. 38 | -------------------------------------------------------------------------------- /src/api/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(darr) 2 | add_subdirectory(acur) 3 | 4 | make_php_c(error-message.c.php) 5 | 6 | # The static version of the library is always built. It will be linked into 7 | # the shared version. 8 | add_library( 9 | proctal_api-static 10 | 11 | STATIC 12 | 13 | implementation.h 14 | proctal.h 15 | version.c 16 | error.c 17 | error-message.c 18 | watch.c 19 | pause.c 20 | write.c 21 | read.c 22 | address.c 23 | region.c 24 | allocate.c 25 | execute.c 26 | malloc.c 27 | global.c 28 | proc.c 29 | handle.c 30 | ) 31 | 32 | set_target_properties(proctal_api-static PROPERTIES OUTPUT_NAME "proctal") 33 | set_target_properties(proctal_api-static PROPERTIES PUBLIC_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/include/proctal.h") 34 | 35 | if(PROCTAL_SHARED) 36 | # Code needs to be compiled with position independent instructions. 37 | set_property(TARGET proctal_api-static PROPERTY POSITION_INDEPENDENT_CODE ON) 38 | endif() 39 | 40 | target_link_libraries(proctal_api-static proctal_api_darr) 41 | target_link_libraries(proctal_api-static proctal_api_acur) 42 | 43 | if(PROCTAL_PLATFORM_LINUX) 44 | add_subdirectory(linux) 45 | target_link_libraries(proctal_api-static proctal_api_linux) 46 | elseif(PROCTAL_PLATFORM_WINDOWS) 47 | add_subdirectory(windows) 48 | target_link_libraries(proctal_api-static proctal_api_windows) 49 | else() 50 | target_SOURCES(proctal_api-static PRIVATE unimplemented.c) 51 | endif() 52 | 53 | if(PROCTAL_CPU_ARCHITECTURE_X86 AND PROCTAL_CPU_ARCHITECTURE_X86_MODE_64) 54 | target_SOURCES(proctal_api-static PRIVATE x86_64/dr.c x86_64/dr.h) 55 | endif() 56 | 57 | if(PROCTAL_SHARED) 58 | # Libraries need at least 1 source file. 59 | file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/cmake-no-source-workaround.c "") 60 | add_library(proctal_api-shared SHARED ${CMAKE_CURRENT_BINARY_DIR}/cmake-no-source-workaround.c) 61 | set_target_properties(proctal_api-shared PROPERTIES OUTPUT_NAME "proctal") 62 | set_target_properties(proctal_api-shared PROPERTIES PUBLIC_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/include/proctal.h") 63 | target_link_libraries(proctal_api-shared proctal_api-static) 64 | endif() 65 | -------------------------------------------------------------------------------- /src/api/acur/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | naive_prefix_funcs(acur.h ${proctal_acur_project_SOURCE_DIR}/src/acur.h acur) 2 | naive_prefix_funcs(acur.c ${proctal_acur_project_SOURCE_DIR}/src/acur.c acur) 3 | 4 | add_library( 5 | proctal_api_acur 6 | 7 | OBJECT 8 | 9 | acur.h 10 | acur.c 11 | ) 12 | 13 | if(PROCTAL_SHARED) 14 | # Code needs to be compiled with position independent instructions. 15 | set_property(TARGET proctal_api_acur PROPERTY POSITION_INDEPENDENT_CODE ON) 16 | endif() 17 | -------------------------------------------------------------------------------- /src/api/address.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "api/proctal.h" 6 | #include "api/implementation.h" 7 | 8 | void proctal_scan_address_start(struct proctal *p) 9 | { 10 | proctal_implementation_scan_address_start(p); 11 | } 12 | 13 | void proctal_scan_address_stop(struct proctal *p) 14 | { 15 | proctal_implementation_scan_address_stop(p); 16 | } 17 | 18 | size_t proctal_scan_address_size(struct proctal *p) 19 | { 20 | return p->address.size; 21 | } 22 | 23 | void proctal_scan_address_size_set(struct proctal *p, size_t size) 24 | { 25 | p->address.size = size > 0 ? size : 1; 26 | } 27 | 28 | size_t proctal_scan_address_align(struct proctal *p) 29 | { 30 | return p->address.align; 31 | } 32 | 33 | void proctal_scan_address_align_set(struct proctal *p, size_t align) 34 | { 35 | p->address.align = align > 0 ? align : 1; 36 | } 37 | 38 | long proctal_scan_address_region(struct proctal *p) 39 | { 40 | return p->address.region_mask; 41 | } 42 | 43 | void proctal_scan_address_region_set(struct proctal *p, long mask) 44 | { 45 | p->address.region_mask = mask; 46 | } 47 | 48 | int proctal_scan_address_read(struct proctal *p) 49 | { 50 | return p->address.read; 51 | } 52 | 53 | void proctal_scan_address_read_set(struct proctal *p, int read) 54 | { 55 | p->address.read = read != 0; 56 | } 57 | 58 | int proctal_scan_address_write(struct proctal *p) 59 | { 60 | return p->address.write; 61 | } 62 | 63 | void proctal_scan_address_write_set(struct proctal *p, int write) 64 | { 65 | p->address.write = write != 0; 66 | } 67 | 68 | int proctal_scan_address_execute(struct proctal *p) 69 | { 70 | return p->address.execute; 71 | } 72 | 73 | void proctal_scan_address_execute_set(struct proctal *p, int execute) 74 | { 75 | p->address.execute = execute != 0; 76 | } 77 | 78 | int proctal_scan_address_next(struct proctal *p, void **address) 79 | { 80 | return proctal_implementation_scan_address_next(p, address); 81 | } 82 | -------------------------------------------------------------------------------- /src/api/allocate.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | void *proctal_allocate(struct proctal *p, size_t size) 5 | { 6 | return proctal_implementation_allocate(p, size); 7 | } 8 | 9 | int proctal_allocate_read(struct proctal *p) 10 | { 11 | return p->allocate.read; 12 | } 13 | 14 | void proctal_allocate_read_set(struct proctal *p, int read) 15 | { 16 | p->allocate.read = read != 0; 17 | } 18 | 19 | int proctal_allocate_write(struct proctal *p) 20 | { 21 | return p->allocate.write; 22 | } 23 | 24 | void proctal_allocate_write_set(struct proctal *p, int write) 25 | { 26 | p->allocate.write = write != 0; 27 | } 28 | 29 | int proctal_allocate_execute(struct proctal *p) 30 | { 31 | return p->allocate.execute; 32 | } 33 | 34 | void proctal_allocate_execute_set(struct proctal *p, int execute) 35 | { 36 | p->allocate.execute = execute != 0; 37 | } 38 | 39 | void proctal_deallocate(struct proctal *p, void *address) 40 | { 41 | proctal_implementation_deallocate(p, address); 42 | } 43 | -------------------------------------------------------------------------------- /src/api/darr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(tests) 2 | 3 | naive_prefix_funcs(darr.h ${proctal_darr_project_SOURCE_DIR}/src/darr.h darr) 4 | naive_prefix_funcs(darr.c ${proctal_darr_project_SOURCE_DIR}/src/darr.c darr) 5 | 6 | add_library( 7 | proctal_api_darr 8 | 9 | OBJECT 10 | 11 | memory-support.h 12 | memory-support.c 13 | darr.h 14 | darr.c 15 | ) 16 | 17 | if(PROCTAL_SHARED) 18 | # Code needs to be compiled with position independent instructions. 19 | set_property(TARGET proctal_api_darr PROPERTY POSITION_INDEPENDENT_CODE ON) 20 | endif() 21 | -------------------------------------------------------------------------------- /src/api/darr/memory-support.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/darr/memory-support.h" 4 | #include "api/proctal.h" 5 | #include "magic/magic.h" 6 | 7 | void *proctal_darr_global_realloc(void *address, size_t size) 8 | { 9 | if (address == NULL) { 10 | void *n = proctal_global_malloc(size + sizeof(size)); 11 | 12 | if (n == NULL) { 13 | return NULL; 14 | } 15 | 16 | DEREF(size_t, n) = size; 17 | 18 | return (size_t *) n + 1; 19 | } else { 20 | size_t old_size = *((size_t *) address - 1); 21 | 22 | if (size == old_size) { 23 | // Same size. No need to do anything. 24 | return address; 25 | } else if (size < old_size && size > (old_size - old_size / 3)) { 26 | // Not worth creating a smaller block. 27 | return address; 28 | } 29 | 30 | void *n = proctal_global_malloc(size + sizeof(size)); 31 | 32 | if (n == NULL) { 33 | return NULL; 34 | } 35 | 36 | DEREF(size_t, n) = size; 37 | memcpy((size_t *) n + 1, address, size > old_size ? old_size : size); 38 | 39 | proctal_darr_global_free(address); 40 | 41 | return (size_t *) n + 1; 42 | } 43 | } 44 | 45 | void proctal_darr_global_free(void *address) 46 | { 47 | proctal_global_free((size_t *) address - 1); 48 | } 49 | -------------------------------------------------------------------------------- /src/api/darr/memory-support.h: -------------------------------------------------------------------------------- 1 | #ifndef API_DARR_MEMORY_SUPPORT_H 2 | #define API_DARR_MEMORY_SUPPORT_H 3 | 4 | #include 5 | 6 | /* 7 | * Reallocates memory for darr using the user provided malloc function. 8 | */ 9 | void *proctal_darr_global_realloc(void *address, size_t size); 10 | 11 | /* 12 | * Frees memory for darr using the user provided free function. 13 | */ 14 | void proctal_darr_global_free(void *address); 15 | 16 | #endif /* API_DARR_MEMORY_SUPPORT_H */ 17 | -------------------------------------------------------------------------------- /src/api/darr/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(util) 2 | 3 | function(test_single_c_file file) 4 | add_executable(proctal_api_darr_${file} ${file}.c) 5 | target_link_libraries(proctal_api_darr_${file} PRIVATE proctal_api-static proctal_api_darr_block-size) 6 | add_test(NAME proctal_api_darr_${file} COMMAND proctal_api_darr_${file}) 7 | endfunction() 8 | 9 | test_single_c_file(realloc-decrease-size-new-block) 10 | test_single_c_file(realloc-decrease-size-same-block) 11 | test_single_c_file(realloc-increase-size-new-block) 12 | test_single_c_file(realloc-null-argument) 13 | -------------------------------------------------------------------------------- /src/api/darr/tests/realloc-decrease-size-new-block.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/darr/memory-support.h" 4 | #include "api/darr/tests/util/block-size.h" 5 | 6 | int main(void) 7 | { 8 | int *i = proctal_darr_global_realloc(NULL, sizeof(int) * 2); 9 | 10 | i[0] = 12345; 11 | i[1] = 54321; 12 | 13 | int *i2 = proctal_darr_global_realloc(i, sizeof(int)); 14 | 15 | if (i2 == NULL) { 16 | fprintf(stderr, "Unexpectedly returned NULL.\n"); 17 | return 1; 18 | } 19 | 20 | if (block_size(i2) != sizeof(int)) { 21 | fprintf(stderr, "Expected to create a new block.\n"); 22 | return 1; 23 | } 24 | 25 | if (*i2 != 12345) { 26 | fprintf(stderr, "Original value was lost.\n"); 27 | return 1; 28 | } 29 | 30 | proctal_darr_global_free(i2); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/api/darr/tests/realloc-decrease-size-same-block.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/darr/memory-support.h" 4 | #include "api/darr/tests/util/block-size.h" 5 | 6 | int main(void) 7 | { 8 | int *i = proctal_darr_global_realloc(NULL, sizeof(int) * 3); 9 | 10 | i[0] = 12345; 11 | i[1] = 54321; 12 | i[2] = 33333; 13 | 14 | int *i2 = proctal_darr_global_realloc(i, sizeof(int) * 2); 15 | 16 | if (i2 == NULL) { 17 | fprintf(stderr, "Unexpectedly returned NULL.\n"); 18 | return 1; 19 | } 20 | 21 | if (block_size(i2) != sizeof(int) * 2) { 22 | fprintf(stderr, "Did not expect to create a new block.\n"); 23 | return 1; 24 | } 25 | 26 | if (i2[0] != 12345 || i2[1] != 54321) { 27 | fprintf(stderr, "Original value was lost.\n"); 28 | return 1; 29 | } 30 | 31 | // Should be able to safely write an int to it. 32 | i2[0] = 0; 33 | i2[1] = 0; 34 | 35 | proctal_darr_global_free(i2); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/api/darr/tests/realloc-increase-size-new-block.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/darr/memory-support.h" 4 | #include "api/darr/tests/util/block-size.h" 5 | 6 | int main(void) 7 | { 8 | int *i = proctal_darr_global_realloc(NULL, sizeof(int)); 9 | 10 | *i = 12345; 11 | 12 | int *i2 = proctal_darr_global_realloc(i, sizeof(int) * 2); 13 | 14 | if (i2 == NULL) { 15 | // Under normal conditions this should allocate a new block. 16 | fprintf(stderr, "Failed to allocate a new block.\n"); 17 | return 1; 18 | } 19 | 20 | if (block_size(i2) != sizeof(int) * 2) { 21 | fprintf(stderr, "Wrong block size.\n"); 22 | return 1; 23 | } 24 | 25 | if (*i2 != 12345) { 26 | fprintf(stderr, "Original value was lost.\n"); 27 | return 1; 28 | } 29 | 30 | proctal_darr_global_free(i2); 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/api/darr/tests/realloc-null-argument.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/darr/memory-support.h" 4 | #include "api/darr/tests/util/block-size.h" 5 | 6 | int main(void) 7 | { 8 | int *i = proctal_darr_global_realloc(NULL, sizeof(int)); 9 | 10 | if (i == NULL) { 11 | // Under normal conditions this should allocate a new block. 12 | fprintf(stderr, "Failed to allocate a new block.\n"); 13 | return 1; 14 | } 15 | 16 | if (block_size(i) != sizeof(int)) { 17 | fprintf(stderr, "Wrong block size.\n"); 18 | return 1; 19 | } 20 | 21 | // Should be able to safely write an int to it. 22 | *i = 0; 23 | 24 | proctal_darr_global_free(i); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/api/darr/tests/util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(proctal_api_darr_block-size OBJECT block-size.c block-size.h) 2 | always_build(proctal_api_darr_block-size) 3 | -------------------------------------------------------------------------------- /src/api/darr/tests/util/block-size.c: -------------------------------------------------------------------------------- 1 | #include "magic/magic.h" 2 | #include "api/darr/tests/util/block-size.h" 3 | 4 | size_t block_size(void *address) 5 | { 6 | return *(((size_t *) address) - 1); 7 | } 8 | -------------------------------------------------------------------------------- /src/api/darr/tests/util/block-size.h: -------------------------------------------------------------------------------- 1 | #ifndef API_DARR_TESTS_REALLOC_UTIL_BLOCK_SIZE_H 2 | #define API_DARR_TESTS_REALLOC_UTIL_BLOCK_SIZE_H 3 | 4 | #include 5 | 6 | /** 7 | * Retrieves the size of the block returned by proctal_global_realloc. 8 | */ 9 | size_t block_size(void *address); 10 | 11 | #endif /* API_DARR_TESTS_REALLOC_UTIL_BLOCK_SIZE_H */ 12 | -------------------------------------------------------------------------------- /src/api/error.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/proctal.h" 4 | 5 | int proctal_error(struct proctal *p) 6 | { 7 | if (p == NULL) { 8 | return PROCTAL_ERROR_OUT_OF_MEMORY; 9 | } 10 | 11 | return p->error; 12 | } 13 | 14 | void proctal_error_set(struct proctal *p, int error) 15 | { 16 | if (p->error) { 17 | // Will not override. 18 | return; 19 | } 20 | 21 | p->error = error; 22 | } 23 | 24 | int proctal_error_recover(struct proctal *p) 25 | { 26 | if (p == NULL) { 27 | // No way to recover from that. 28 | return 0; 29 | } 30 | 31 | p->error = 0; 32 | return 1; 33 | } 34 | -------------------------------------------------------------------------------- /src/api/execute.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | void proctal_execute(struct proctal *p, const void *bytecode, size_t bytecode_length) 5 | { 6 | proctal_implementation_execute(p, bytecode, bytecode_length); 7 | } 8 | -------------------------------------------------------------------------------- /src/api/global.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/darr/darr.h" 4 | #include "api/proctal.h" 5 | #include "api/darr/memory-support.h" 6 | 7 | struct proctal_global proctal_global = { 8 | .malloc = malloc, 9 | .free = free 10 | }; 11 | 12 | void proctal_malloc_set(void *(*f)(size_t)) 13 | { 14 | if (f == NULL) { 15 | f = malloc; 16 | proctal_darr_global_realloc_set(realloc); 17 | } else { 18 | proctal_darr_global_realloc_set(proctal_darr_global_realloc); 19 | } 20 | 21 | proctal_global.malloc = f; 22 | } 23 | 24 | void proctal_free_set(void (*f)(void *)) 25 | { 26 | if (f == NULL) { 27 | f = free; 28 | proctal_darr_global_free_set(free); 29 | } else { 30 | proctal_darr_global_free_set(proctal_darr_global_free); 31 | } 32 | 33 | proctal_global.free = f; 34 | } 35 | 36 | extern inline void *proctal_global_malloc(size_t size); 37 | 38 | extern inline void proctal_global_free(const void *address); 39 | -------------------------------------------------------------------------------- /src/api/handle.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | void proctal_init(struct proctal *p) 5 | { 6 | p->error = 0; 7 | 8 | p->address.region_mask = 0; 9 | p->address.size = 1; 10 | p->address.align = 1; 11 | p->address.read = 1; 12 | p->address.write = 0; 13 | p->address.execute = 0; 14 | 15 | p->region.mask = 0; 16 | p->region.read = 1; 17 | p->region.write = 0; 18 | p->region.execute = 0; 19 | 20 | p->watch.address = NULL; 21 | p->watch.read = 1; 22 | p->watch.write = 1; 23 | p->watch.execute = 0; 24 | 25 | p->allocate.read = 1; 26 | p->allocate.write = 1; 27 | p->allocate.execute = 1; 28 | } 29 | 30 | void proctal_deinit(struct proctal *p) 31 | { 32 | } 33 | 34 | struct proctal *proctal_open(void) 35 | { 36 | return proctal_implementation_open(); 37 | } 38 | 39 | void proctal_close(struct proctal *p) 40 | { 41 | return proctal_implementation_close(p); 42 | } 43 | -------------------------------------------------------------------------------- /src/api/implementation.h: -------------------------------------------------------------------------------- 1 | #ifndef API_IMPLEMENTATION_H 2 | #define API_IMPLEMENTATION_H 3 | 4 | #include 5 | 6 | /* 7 | * These are the functions that an implementation must define. 8 | */ 9 | 10 | struct proctal *proctal_implementation_open(void); 11 | 12 | void proctal_implementation_close(struct proctal *p); 13 | 14 | void proctal_implementation_pid_set(struct proctal *p, int pid); 15 | 16 | int proctal_implementation_pid(struct proctal *p); 17 | 18 | size_t proctal_implementation_read(struct proctal *p, void *address, void *out, size_t size); 19 | 20 | size_t proctal_implementation_write(struct proctal *p, void *address, const void *in, size_t size); 21 | 22 | void proctal_implementation_pause(struct proctal *p); 23 | 24 | void proctal_implementation_resume(struct proctal *p); 25 | 26 | void proctal_implementation_scan_address_start(struct proctal *p); 27 | 28 | void proctal_implementation_scan_address_stop(struct proctal *p); 29 | 30 | int proctal_implementation_scan_address_next(struct proctal *p, void **address); 31 | 32 | void proctal_implementation_scan_region_start(struct proctal *p); 33 | 34 | void proctal_implementation_scan_region_stop(struct proctal *p); 35 | 36 | int proctal_implementation_scan_region_next(struct proctal *p, void **start, void **end); 37 | 38 | void proctal_implementation_watch_start(struct proctal *p); 39 | 40 | void proctal_implementation_watch_stop(struct proctal *p); 41 | 42 | int proctal_implementation_watch_next(struct proctal *p, void **address); 43 | 44 | void proctal_implementation_execute(struct proctal *p, const void *bytecode, size_t bytecode_length); 45 | 46 | void *proctal_implementation_allocate(struct proctal *p, size_t size); 47 | 48 | void proctal_implementation_deallocate(struct proctal *p, void *address); 49 | 50 | #endif /* API_IMPLEMENTATION_H */ 51 | -------------------------------------------------------------------------------- /src/api/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(tests) 2 | 3 | add_library( 4 | proctal_api_linux 5 | 6 | OBJECT 7 | 8 | implementation.c 9 | address.c 10 | address.h 11 | region.c 12 | region.h 13 | allocate.c 14 | allocate.h 15 | execute.h 16 | execute/implementation.h 17 | execute/interface.c 18 | mem.c 19 | mem.h 20 | proc-maps.c 21 | proc.c 22 | proc.h 23 | ptrace.h 24 | ptrace/error-checking.c 25 | ptrace/implementation.h 26 | ptrace/interface.c 27 | ptrace/internal.h 28 | proctal.c 29 | proctal.h 30 | watch/interface.c 31 | watch/implementation.h 32 | watch.h 33 | ) 34 | 35 | if(PROCTAL_SHARED) 36 | # Code needs to be compiled with position independent instructions. 37 | set_property(TARGET proctal_api_linux PROPERTY POSITION_INDEPENDENT_CODE ON) 38 | endif() 39 | 40 | target_link_libraries(proctal_api_linux proctal_api_darr) 41 | target_link_libraries(proctal_api_linux proctal_api_acur) 42 | 43 | if(PROCTAL_CPU_ARCHITECTURE_X86 AND PROCTAL_CPU_ARCHITECTURE_X86_MODE_64) 44 | target_SOURCES( 45 | proctal_api_linux 46 | 47 | PRIVATE 48 | 49 | ptrace/implementation/x86_64/cpu-state.c 50 | ptrace/implementation/x86_64/instruction-pointer.c 51 | ptrace/implementation/x86_64/register.c 52 | ptrace/user-register.c 53 | 54 | execute/x86_64/no-op-code.c 55 | execute/x86_64/syscall.c 56 | execute/x86_64/trap-code.c 57 | execute/x86_64/savestate.c 58 | execute/x86_64/stackframe.c 59 | execute/hijack-main.c 60 | 61 | watch/x86_64/breakpoint.c 62 | ) 63 | else() 64 | target_SOURCES( 65 | proctal_api_linux 66 | 67 | PRIVATE 68 | 69 | ptrace/implementation/unsupported/cpu-state.c 70 | ptrace/implementation/unsupported/instruction-pointer.c 71 | ptrace/implementation/unsupported/register.c 72 | 73 | execute/unimplemented.c 74 | 75 | watch/unimplemented.c 76 | ) 77 | endif() 78 | -------------------------------------------------------------------------------- /src/api/linux/address.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_ADDRESS_H 2 | #define API_LINUX_ADDRESS_H 3 | 4 | #include 5 | 6 | #include "api/linux/proctal.h" 7 | #include "api/linux/proc.h" 8 | 9 | void proctal_linux_scan_address_start(struct proctal_linux *pl); 10 | 11 | void proctal_linux_scan_address_stop(struct proctal_linux *pl); 12 | 13 | int proctal_linux_scan_address_next(struct proctal_linux *pl, void **address); 14 | 15 | #endif /* API_LINUX_ADDRESS_H */ 16 | -------------------------------------------------------------------------------- /src/api/linux/allocate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "api/linux/allocate.h" 5 | #include "api/linux/proc.h" 6 | #include "api/linux/mem.h" 7 | #include "api/linux/execute.h" 8 | 9 | struct mem_header { 10 | size_t size; 11 | }; 12 | 13 | static inline int make_prot(struct proctal_linux *pl) 14 | { 15 | int prot = 0; 16 | 17 | if (pl->p.allocate.read) { 18 | prot |= PROT_READ; 19 | } 20 | 21 | if (pl->p.allocate.write) { 22 | prot |= PROT_WRITE; 23 | } 24 | 25 | if (pl->p.allocate.execute) { 26 | prot |= PROT_EXEC; 27 | } 28 | 29 | if (prot == 0) { 30 | prot = PROT_NONE; 31 | } 32 | 33 | return prot; 34 | } 35 | 36 | static inline void *read_header(struct proctal_linux *pl, struct mem_header *header, void *address) 37 | { 38 | void *memory_location = (char *) address - sizeof(header); 39 | 40 | if (!proctal_linux_mem_read(pl, memory_location, (char *) header, sizeof(header))) { 41 | return NULL; 42 | } 43 | 44 | return memory_location; 45 | } 46 | 47 | static inline void *write_header(struct proctal_linux *pl, struct mem_header *header, void *memory_location) 48 | { 49 | if (!proctal_linux_mem_write(pl, memory_location, (char *) header, sizeof(header))) { 50 | return NULL; 51 | } 52 | 53 | return (char *) memory_location + sizeof(header); 54 | } 55 | 56 | void *proctal_linux_allocate(struct proctal_linux *pl, size_t size) 57 | { 58 | struct mem_header header; 59 | header.size = size + sizeof(header); 60 | 61 | void *ret = proctal_linux_execute_syscall_mmap( 62 | pl, 63 | NULL, 64 | header.size, 65 | make_prot(pl), 66 | 0x22, // MAP_PRIVATE | MAP_ANONYMOUS 67 | -1, 68 | 0); 69 | 70 | if (proctal_error(&pl->p)) { 71 | return NULL; 72 | } 73 | 74 | // TODO: Detect error codes from system call return values. 75 | 76 | return write_header(pl, &header, ret); 77 | } 78 | 79 | void proctal_linux_deallocate(struct proctal_linux *pl, void *address) 80 | { 81 | struct mem_header header; 82 | 83 | void *memory_location = read_header(pl, &header, address); 84 | 85 | int ret = proctal_linux_execute_syscall_munmap(pl, memory_location, header.size); 86 | 87 | if (proctal_error(&pl->p)) { 88 | return; 89 | } 90 | 91 | if (ret != 0) { 92 | // TODO: Detect error codes from system call return values. 93 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNKNOWN); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/api/linux/allocate.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_ALLOCATE_H 2 | #define API_LINUX_ALLOCATE_H 3 | 4 | #include "api/linux/proctal.h" 5 | 6 | void *proctal_linux_allocate(struct proctal_linux *pl, size_t size); 7 | 8 | void proctal_linux_deallocate(struct proctal_linux *pl, void *address); 9 | 10 | #endif /* API_LINUX_ALLOCATE_H */ 11 | -------------------------------------------------------------------------------- /src/api/linux/execute.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_EXECUTE_H 2 | #define API_LINUX_EXECUTE_H 3 | 4 | #include "api/linux/proctal.h" 5 | 6 | /* 7 | * Executes code in the context of the program. 8 | * 9 | * This function blocks for as long as the code is running. 10 | * 11 | * The code is allowed to modify any register. 12 | * 13 | * Returns 1 on success and 0 on failure. 14 | */ 15 | int proctal_linux_execute(struct proctal_linux *pl, const void *bytecode, size_t bytecode_length); 16 | 17 | /* 18 | * The following functions execute system calls in the context of the program. 19 | * 20 | * The signatures are identical to the wrappers defined in glibc, the only 21 | * difference is the addition of an extra argument to pass a proctal_linux 22 | * struct. 23 | * 24 | * Unlike the glibc versions, these do not extract error codes to an errno like 25 | * variable. The return values are untampered. 26 | * 27 | * These functions block for as long as the system call is running. 28 | * 29 | * If Proctal fails to dispatch the system call, an error code will be set. 30 | */ 31 | 32 | void *proctal_linux_execute_syscall_mmap(struct proctal_linux *pl, void *addr, size_t length, int prot, int flags, int fd, off_t offset); 33 | 34 | int proctal_linux_execute_syscall_munmap(struct proctal_linux *pl, void *addr, size_t length); 35 | 36 | #endif /* API_LINUX_EXECUTE_H */ 37 | -------------------------------------------------------------------------------- /src/api/linux/execute/implementation.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_EXECUTE_IMPLEMENTATION_H 2 | #define API_LINUX_EXECUTE_IMPLEMENTATION_H 3 | 4 | #include "api/linux/proctal.h" 5 | 6 | /* 7 | * Executes code in the context of the program. 8 | * 9 | * This function blocks for as long as the code is running. 10 | * 11 | * The code is allowed to modify any register. 12 | * 13 | * Returns 1 on success and 0 on failure. 14 | */ 15 | int proctal_linux_execute_implementation(struct proctal_linux *pl, const void *bytecode, size_t bytecode_length); 16 | 17 | /* 18 | * The following functions execute system calls in the context of the program. 19 | * 20 | * The signatures are identical to the wrappers defined in glibc, the only 21 | * difference is the addition of an extra argument to pass a proctal_linux 22 | * struct. 23 | * 24 | * Unlike the glibc versions, these do not extract error codes to an errno like 25 | * variable. The return values are untampered. 26 | * 27 | * These functions block for as long as the system call is running. 28 | * 29 | * If Proctal fails to dispatch the system call, an error code will be set. 30 | */ 31 | 32 | void *proctal_linux_execute_implementation_syscall_mmap(struct proctal_linux *pl, void *addr, size_t length, int prot, int flags, int fd, off_t offset); 33 | 34 | int proctal_linux_execute_implementation_syscall_munmap(struct proctal_linux *pl, void *addr, size_t length); 35 | 36 | #endif /* API_LINUX_EXECUTE_IMPLEMENTATION_H */ 37 | -------------------------------------------------------------------------------- /src/api/linux/execute/interface.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is the functionality that the rest of the code outside this module will 3 | * see. 4 | */ 5 | 6 | #include "api/linux/execute.h" 7 | #include "api/linux/execute/implementation.h" 8 | 9 | int proctal_linux_execute(struct proctal_linux *pl, const void *bytecode, size_t bytecode_length) 10 | { 11 | return proctal_linux_execute_implementation(pl, bytecode, bytecode_length); 12 | } 13 | 14 | void *proctal_linux_execute_syscall_mmap(struct proctal_linux *pl, void *addr, size_t length, int prot, int flags, int fd, off_t offset) 15 | { 16 | return proctal_linux_execute_implementation_syscall_mmap(pl, addr, length, prot, flags, fd, offset); 17 | } 18 | 19 | int proctal_linux_execute_syscall_munmap(struct proctal_linux *pl, void *addr, size_t length) 20 | { 21 | return proctal_linux_execute_implementation_syscall_munmap(pl, addr, length); 22 | } 23 | -------------------------------------------------------------------------------- /src/api/linux/execute/syscall.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/execute.h" 2 | #include "api/linux/execute/implementation.h" 3 | 4 | int proctal_linux_execute_syscall(struct proctal_linux *pl, int sysnum, void *ret, void *arg1, void *arg2, void *arg3, void *arg4, void *arg5, void *arg6, void *arg7) 5 | { 6 | return proctal_linux_implementation_execute_syscall(pl, sysnum, ret, arg1, arg2, arg3, arg4, arg5, arg6, arg7); 7 | } 8 | -------------------------------------------------------------------------------- /src/api/linux/execute/unimplemented.c: -------------------------------------------------------------------------------- 1 | /* 2 | * An implementation that defines the functions as always failing. 3 | */ 4 | #include 5 | 6 | #include "api/linux/proctal.h" 7 | 8 | int proctal_linux_execute_implementation(struct proctal_linux *pl, const char *bytecode, size_t bytecode_length) 9 | { 10 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 11 | return 0; 12 | } 13 | 14 | void *proctal_linux_execute_implementation_syscall_mmap(struct proctal_linux *pl, void *addr, size_t length, int prot, int flags, int fd, off_t offset) 15 | { 16 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 17 | return NULL; 18 | } 19 | 20 | int proctal_linux_execute_implementation_syscall_munmap(struct proctal_linux *pl, void *addr, size_t length) 21 | { 22 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/api/linux/execute/x86_64/no-op-code.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "magic/magic.h" 4 | 5 | const char proctal_linux_execute_implementation_no_op_code[] = { 6 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 7 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 8 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 9 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 10 | }; 11 | 12 | const size_t proctal_linux_execute_implementation_no_op_code_size = ARRAY_SIZE(proctal_linux_execute_implementation_no_op_code); 13 | -------------------------------------------------------------------------------- /src/api/linux/execute/x86_64/savestate.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | 4 | void *proctal_linux_execute_implementation_save_state(struct proctal_linux *pl, pid_t tid) 5 | { 6 | struct proctal_linux_ptrace_cpu_state *state = proctal_linux_ptrace_cpu_state_create(pl); 7 | 8 | if (state == NULL) { 9 | return NULL; 10 | } 11 | 12 | if (!proctal_linux_ptrace_cpu_state_save(pl, tid, state)) { 13 | proctal_linux_ptrace_cpu_state_destroy(pl, state); 14 | return NULL; 15 | } 16 | 17 | return state; 18 | } 19 | 20 | int proctal_linux_execute_implementation_load_state(struct proctal_linux *pl, pid_t tid, void *state) 21 | { 22 | if (!proctal_linux_ptrace_cpu_state_load(pl, tid, state)) { 23 | proctal_linux_ptrace_cpu_state_destroy(pl, state); 24 | return 0; 25 | } 26 | 27 | proctal_linux_ptrace_cpu_state_destroy(pl, state); 28 | return 1; 29 | } 30 | -------------------------------------------------------------------------------- /src/api/linux/execute/x86_64/stackframe.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | 4 | #define RED_ZONE_SIZE 128 5 | 6 | int proctal_linux_execute_implementation_create_stack_frame(struct proctal_linux *pl, pid_t tid) 7 | { 8 | unsigned long long stack_pointer; 9 | unsigned long long base_pointer; 10 | 11 | proctal_linux_ptrace_register(pl, tid, PROCTAL_LINUX_PTRACE_REGISTER_X86_64_RSP, &stack_pointer); 12 | 13 | stack_pointer -= RED_ZONE_SIZE; 14 | base_pointer = stack_pointer; 15 | 16 | if (!proctal_linux_ptrace_register_set(pl, tid, PROCTAL_LINUX_PTRACE_REGISTER_X86_64_RSP, &stack_pointer) 17 | || !proctal_linux_ptrace_register_set(pl, tid, PROCTAL_LINUX_PTRACE_REGISTER_X86_64_RBP, &base_pointer)) { 18 | return 0; 19 | } 20 | 21 | return 1; 22 | } 23 | 24 | int proctal_linux_execute_implementation_destroy_stack_frame(struct proctal_linux *pl, pid_t tid) 25 | { 26 | return 1; 27 | } 28 | -------------------------------------------------------------------------------- /src/api/linux/execute/x86_64/trap-code.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "magic/magic.h" 4 | 5 | const char proctal_linux_execute_implementation_trap_code[] = { 6 | // It's a trap. 7 | 0xcd, 0x03, 8 | }; 9 | 10 | const size_t proctal_linux_execute_implementation_trap_code_size = ARRAY_SIZE(proctal_linux_execute_implementation_trap_code); 11 | -------------------------------------------------------------------------------- /src/api/linux/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_MEM_H 2 | #define API_LINUX_MEM_H 3 | 4 | #include 5 | 6 | #include "api/linux/proctal.h" 7 | 8 | /* 9 | * Read from memory. 10 | * 11 | * Returns the number of bytes read. If that number does not match the 12 | * requested size, then it means that there was an error. 13 | */ 14 | size_t proctal_linux_mem_read(struct proctal_linux *pl, void *address, void *out, size_t size); 15 | 16 | /* 17 | * Write to memory. 18 | * 19 | * Returns the number of bytes written. If that number does not match the 20 | * given size, then it means that there was an error. 21 | */ 22 | size_t proctal_linux_mem_write(struct proctal_linux *pl, void *address, const void *in, size_t size); 23 | 24 | /* 25 | * Swaps the contents in memory at the given address with the contents pointed 26 | * to by src. 27 | * 28 | * Both dst and src may point to overlapped memory. 29 | * 30 | * Returns 1 on success and 0 on failure. 31 | */ 32 | int proctal_linux_mem_swap(struct proctal_linux *pl, void *address, void *dst, const void *src, size_t size); 33 | 34 | /* 35 | * Finds a suitable place in memory marked as executable where you can write 36 | * the given number of bytes. 37 | * 38 | * Returns NULL on failure. 39 | */ 40 | void *proctal_linux_mem_find_payload_location(struct proctal_linux *pl, size_t size); 41 | 42 | #endif /* API_LINUX_MEM_H */ 43 | -------------------------------------------------------------------------------- /src/api/linux/proctal.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | #include "api/linux/address.h" 4 | #include "api/linux/region.h" 5 | 6 | void proctal_linux_init(struct proctal_linux *pl) 7 | { 8 | proctal_init(&pl->p); 9 | 10 | pl->mem = NULL; 11 | 12 | pl->ptrace.count = 0; 13 | proctal_darr_init(&pl->ptrace.tasks, sizeof(struct proctal_linux_ptrace_task)); 14 | 15 | pl->address.started = 0; 16 | 17 | pl->region.started = 0; 18 | } 19 | 20 | void proctal_linux_deinit(struct proctal_linux *pl) 21 | { 22 | proctal_deinit(&pl->p); 23 | 24 | if (pl->mem) { 25 | fclose(pl->mem); 26 | } 27 | 28 | proctal_linux_ptrace_detach_force(pl); 29 | proctal_darr_deinit(&pl->ptrace.tasks); 30 | 31 | proctal_linux_scan_address_stop(pl); 32 | 33 | proctal_linux_scan_region_stop(pl); 34 | } 35 | 36 | void proctal_linux_pid_set(struct proctal_linux *pl, pid_t pid) 37 | { 38 | if (pl->mem) { 39 | fclose(pl->mem); 40 | pl->mem = NULL; 41 | } 42 | 43 | proctal_linux_ptrace_detach_force(pl); 44 | 45 | pl->pid = pid; 46 | } 47 | 48 | pid_t proctal_linux_pid(struct proctal_linux *pl) 49 | { 50 | return pl->pid; 51 | } 52 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/error-checking.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Error checking. 3 | */ 4 | 5 | #include 6 | 7 | #include "api/linux/proctal.h" 8 | #include "api/linux/ptrace/internal.h" 9 | #include "magic/magic.h" 10 | 11 | int proctal_linux_ptrace_check_run_state_errno(struct proctal_linux *pl) 12 | { 13 | if (errno == 0) { 14 | return 0; 15 | } 16 | 17 | switch (errno) { 18 | case EPERM: 19 | proctal_error_set(&pl->p, PROCTAL_ERROR_PERMISSION_DENIED); 20 | break; 21 | 22 | case ESRCH: 23 | proctal_error_set(&pl->p, PROCTAL_ERROR_PROGRAM_NOT_FOUND); 24 | break; 25 | 26 | default: 27 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNKNOWN); 28 | break; 29 | } 30 | 31 | return 1; 32 | } 33 | 34 | int proctal_linux_ptrace_check_stop_state_errno(struct proctal_linux *pl) 35 | { 36 | if (errno == 0) { 37 | return 0; 38 | } 39 | 40 | switch (errno) { 41 | case EACCES: 42 | proctal_error_set(&pl->p, PROCTAL_ERROR_PERMISSION_DENIED); 43 | break; 44 | 45 | case ESRCH: 46 | proctal_error_set(&pl->p, PROCTAL_ERROR_PROGRAM_UNTAMEABLE); 47 | break; 48 | 49 | default: 50 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNKNOWN); 51 | break; 52 | } 53 | 54 | return 1; 55 | } 56 | 57 | int proctal_linux_ptrace_check_waitpid_errno(struct proctal_linux *pl) 58 | { 59 | if (errno == 0) { 60 | return 0; 61 | } 62 | 63 | switch (errno) { 64 | case EPERM: 65 | proctal_error_set(&pl->p, PROCTAL_ERROR_PERMISSION_DENIED); 66 | break; 67 | 68 | case ESRCH: 69 | proctal_error_set(&pl->p, PROCTAL_ERROR_PROGRAM_NOT_FOUND); 70 | break; 71 | 72 | case EINTR: 73 | proctal_error_set(&pl->p, PROCTAL_ERROR_INTERRUPT); 74 | break; 75 | 76 | default: 77 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNKNOWN); 78 | break; 79 | } 80 | 81 | return 1; 82 | } 83 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/implementation/unsupported/cpu-state.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | #include "api/linux/ptrace/internal.h" 4 | 5 | struct proctal_linux_ptrace_cpu_state *proctal_linux_ptrace_implementation_cpu_state_create(struct proctal_linux *pl) 6 | { 7 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 8 | return NULL; 9 | } 10 | 11 | void proctal_linux_ptrace_implementation_cpu_state_destroy(struct proctal_linux *pl, struct proctal_linux_ptrace_cpu_state *state) 12 | { 13 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 14 | } 15 | 16 | int proctal_linux_ptrace_implementation_cpu_state_save(struct proctal_linux *pl, pid_t tid, struct proctal_linux_ptrace_cpu_state *state) 17 | { 18 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 19 | return 0; 20 | } 21 | 22 | int proctal_linux_ptrace_implementation_cpu_state_load(struct proctal_linux *pl, pid_t tid, struct proctal_linux_ptrace_cpu_state *state) 23 | { 24 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/implementation/unsupported/instruction-pointer.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | #include "api/linux/ptrace/internal.h" 4 | 5 | int proctal_linux_ptrace_implementation_instruction_pointer(struct proctal_linux *pl, pid_t tid, void **address) 6 | { 7 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 8 | return 0; 9 | } 10 | 11 | int proctal_linux_ptrace_implementation_instruction_pointer_set(struct proctal_linux *pl, pid_t tid, void *address) 12 | { 13 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/implementation/unsupported/register.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | #include "api/linux/ptrace/internal.h" 4 | 5 | int proctal_linux_ptrace_implementation_register(struct proctal_linux *pl, pid_t tid, int regid, void *dst) 6 | { 7 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 8 | return 0; 9 | } 10 | 11 | int proctal_linux_ptrace_implementation_register_set(struct proctal_linux *pl, pid_t tid, int regid, void *src) 12 | { 13 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/implementation/x86_64/instruction-pointer.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/ptrace.h" 3 | #include "api/linux/ptrace/internal.h" 4 | 5 | int proctal_linux_ptrace_implementation_instruction_pointer(struct proctal_linux *pl, pid_t tid, void **address) 6 | { 7 | return proctal_linux_ptrace_register(pl, tid, PROCTAL_LINUX_PTRACE_REGISTER_X86_64_RIP, address); 8 | } 9 | 10 | int proctal_linux_ptrace_implementation_instruction_pointer_set(struct proctal_linux *pl, pid_t tid, void *address) 11 | { 12 | return proctal_linux_ptrace_register_set(pl, tid, PROCTAL_LINUX_PTRACE_REGISTER_X86_64_RIP, &address); 13 | } 14 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/internal.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_PTRACE_INTERNAL_H 2 | #define API_LINUX_PTRACE_INTERNAL_H 3 | 4 | #include "api/linux/proctal.h" 5 | #include "api/linux/ptrace.h" 6 | 7 | /* 8 | * Checks whether ptrace reports an error through errno in run state. 9 | * 10 | * Automatically sets the appropriate error code for proctal if an error is 11 | * found. 12 | * 13 | * This function should only be called when either errno was set to 0 or when 14 | * ptrace's return value absolutely means an error occurred. 15 | * 16 | * Returns 1 if an error was found, 0 if not. 17 | */ 18 | int proctal_linux_ptrace_check_run_state_errno(struct proctal_linux *pl); 19 | 20 | /* 21 | * Checks whether ptrace reports an error through errno in stop state. 22 | * 23 | * Automatically sets the appropriate error code for proctal if an error is 24 | * found. 25 | * 26 | * This function should only be called when either errno was set to 0 or when 27 | * ptrace's return value absolutely means an error occurred. 28 | * 29 | * Returns 1 if an error was found, 0 if not. 30 | */ 31 | int proctal_linux_ptrace_check_stop_state_errno(struct proctal_linux *pl); 32 | 33 | /* 34 | * Checks whether waitpid reports an error when waiting for ptrace signals. 35 | * 36 | * Automatically sets the appropriate error code for proctal if an error is 37 | * found. 38 | * 39 | * This function should only be called when either errno was set to 0 or when 40 | * waitpid's return value absolutely means an error occurred. 41 | * 42 | * Returns 1 if an error was found, 0 if not. 43 | */ 44 | int proctal_linux_ptrace_check_waitpid_errno(struct proctal_linux *pl); 45 | 46 | #endif /* API_LINUX_PTRACE_INTERNAL_H */ 47 | -------------------------------------------------------------------------------- /src/api/linux/ptrace/user-register.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This implementation relies on an offset into the user struct to get the 3 | * values of the registers. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "api/darr/darr.h" 13 | #include "api/linux/proctal.h" 14 | #include "api/linux/ptrace/internal.h" 15 | #include "api/linux/ptrace/implementation.h" 16 | #include "magic/magic.h" 17 | 18 | /* 19 | * Returns an offset into the user struct. 20 | * 21 | * On failure it returns -1. 22 | */ 23 | int proctal_linux_ptrace_implementation_register_user_offset(int regid); 24 | 25 | int proctal_linux_ptrace_implementation_register(struct proctal_linux *pl, pid_t tid, int regid, void *dst) 26 | { 27 | int offset = proctal_linux_ptrace_implementation_register_user_offset(regid); 28 | 29 | if (offset == -1) { 30 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 31 | return 0; 32 | } 33 | 34 | errno = 0; 35 | 36 | // Assuming all registers are the size of a word. 37 | 38 | unsigned long long v = ptrace(PTRACE_PEEKUSER, tid, offset, 0); 39 | 40 | if (proctal_linux_ptrace_check_stop_state_errno(pl)) { 41 | return 0; 42 | } 43 | 44 | DEREF(unsigned long long, dst) = v; 45 | 46 | return 1; 47 | } 48 | 49 | int proctal_linux_ptrace_implementation_register_set(struct proctal_linux *pl, pid_t tid, int regid, void *src) 50 | { 51 | int offset = proctal_linux_ptrace_implementation_register_user_offset(regid); 52 | 53 | if (offset == -1) { 54 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 55 | return 0; 56 | } 57 | 58 | errno = 0; 59 | 60 | // Assuming all registers are the size of a word. 61 | 62 | ptrace(PTRACE_POKEUSER, tid, offset, DEREF(unsigned long long, src)); 63 | 64 | if (proctal_linux_ptrace_check_stop_state_errno(pl)) { 65 | return 0; 66 | } 67 | 68 | return 1; 69 | } 70 | -------------------------------------------------------------------------------- /src/api/linux/region.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "api/linux/region.h" 5 | #include "api/linux/proc.h" 6 | 7 | static struct proctal_linux_proc_maps_region *next(struct proctal_linux *pl) 8 | { 9 | struct proctal_linux_proc_maps_region *region; 10 | 11 | do { 12 | region = proctal_linux_proc_maps_read(&pl->region.maps); 13 | 14 | if (region == NULL) { 15 | // No more found. 16 | break; 17 | } 18 | } while (!proctal_linux_proc_maps_region_check(region, &pl->region.check)); 19 | 20 | return region; 21 | } 22 | 23 | void proctal_linux_scan_region_start(struct proctal_linux *pl) 24 | { 25 | if (!proctal_linux_proc_maps_open(&pl->region.maps, pl->pid)) { 26 | proctal_error_set(&pl->p, PROCTAL_ERROR_PERMISSION_DENIED); 27 | return; 28 | } 29 | 30 | pl->region.check.pid = pl->pid; 31 | pl->region.check.mask = pl->p.region.mask; 32 | pl->region.check.read = pl->p.region.read; 33 | pl->region.check.write = pl->p.region.write; 34 | pl->region.check.execute = pl->p.region.execute; 35 | 36 | pl->region.started = 1; 37 | } 38 | 39 | void proctal_linux_scan_region_stop(struct proctal_linux *pl) 40 | { 41 | if (pl->region.started) { 42 | proctal_linux_proc_maps_close(&pl->region.maps); 43 | } 44 | 45 | pl->region.started = 0; 46 | } 47 | 48 | int proctal_linux_scan_region_next(struct proctal_linux *pl, void **start, void **end) 49 | { 50 | struct proctal_linux_proc_maps_region *region = next(pl); 51 | 52 | if (region) { 53 | *start = region->start; 54 | *end = region->end; 55 | return 1; 56 | } else { 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/api/linux/region.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_REGION_H 2 | #define API_LINUX_REGION_H 3 | 4 | #include "api/linux/proctal.h" 5 | #include "api/linux/proc.h" 6 | 7 | void proctal_linux_scan_region_start(struct proctal_linux *pl); 8 | 9 | void proctal_linux_scan_region_stop(struct proctal_linux *pl); 10 | 11 | int proctal_linux_scan_region_next(struct proctal_linux *pl, void **start, void **end); 12 | 13 | #endif /* API_LINUX_REGION_H */ 14 | -------------------------------------------------------------------------------- /src/api/linux/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_api_linux_${file} ${file}.c) 3 | target_link_libraries(proctal_api_linux_${file} PRIVATE proctal_api-static) 4 | add_test(NAME proctal_api_linux_${file} COMMAND proctal_api_linux_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(proc-path-size) 8 | 9 | add_subdirectory(proc-maps) 10 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_api_linux_proc_maps_${file} ${file}.c) 3 | target_link_libraries(proctal_api_linux_proc_maps_${file} PRIVATE proctal_api-static) 4 | add_test(NAME proctal_api_linux_proc_maps_${file} COMMAND proctal_api_linux_proc_maps_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(check) 8 | test_single_c_file(correct-properties) 9 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-region-heap: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [heap] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-region-program-code: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 /path/to/binary 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-region-stack: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-region-with-execute: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 rwxp 00000000 00:00 0 [heap] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-region-without-execute: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [heap] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-with-execute: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 --xp 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-with-read: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 r--p 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-with-write: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 -w-p 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-without-execute: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-without-read: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 -wxp 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-maps/sample-without-write: -------------------------------------------------------------------------------- 1 | 7fffb2c0d000-7fffb2c2e000 r-xp 00000000 00:00 0 [stack] 2 | -------------------------------------------------------------------------------- /src/api/linux/tests/proc-path-size.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "magic/magic.h" 5 | #include "api/linux/proc.h" 6 | 7 | int main(void) 8 | { 9 | struct test { 10 | int pid; 11 | const char *file; 12 | const char *expected_path; 13 | }; 14 | 15 | struct test tests[] = { 16 | { 17 | .pid = 12345, 18 | .file = "maps", 19 | .expected_path = "/proc/12345/maps", 20 | }, 21 | { 22 | .pid = 1, 23 | .file = "maps", 24 | .expected_path = "/proc/1/maps", 25 | }, 26 | }; 27 | 28 | for (size_t i = 0; i < ARRAY_SIZE(tests); ++i) { 29 | struct test *test = &tests[i]; 30 | 31 | const struct proctal_darr *path = proctal_linux_proc_path(test->pid, test->file); 32 | 33 | size_t expected_size = strlen(test->expected_path); 34 | 35 | if (proctal_darr_size(path) != expected_size) { 36 | fprintf(stderr, "Unexpected size in test #%d.\n", (int) i); 37 | proctal_linux_proc_path_dispose(path); 38 | return 1; 39 | } 40 | 41 | if (strncmp(test->expected_path, proctal_darr_data_const(path), expected_size) != 0) { 42 | fprintf(stderr, "Unexpected path in test #%d.\n", (int) i); 43 | proctal_linux_proc_path_dispose(path); 44 | return 1; 45 | } 46 | 47 | proctal_linux_proc_path_dispose(path); 48 | } 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /src/api/linux/watch.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_WATCH_H 2 | #define API_LINUX_WATCH_H 3 | 4 | #include "api/linux/proctal.h" 5 | #include "api/linux/ptrace.h" 6 | 7 | void proctal_linux_watch_start(struct proctal_linux *pl); 8 | 9 | void proctal_linux_watch_stop(struct proctal_linux *pl); 10 | 11 | int proctal_linux_watch_next(struct proctal_linux *pl, void **address); 12 | 13 | #endif /* API_LINUX_WATCH_H */ 14 | -------------------------------------------------------------------------------- /src/api/linux/watch/implementation.h: -------------------------------------------------------------------------------- 1 | #ifndef API_LINUX_WATCH_IMPLEMENTATION_H 2 | #define API_LINUX_WATCH_IMPLEMENTATION_H 3 | 4 | #include "api/linux/proctal.h" 5 | 6 | /* 7 | * Turns on the breakpoint. 8 | * 9 | * Returns 1 on success, 0 on failure. 10 | */ 11 | int proctal_linux_watch_implementation_breakpoint_enable(struct proctal_linux *pl, pid_t tid); 12 | 13 | /* 14 | * Turns off the breakpoint. 15 | * 16 | * Returns 1 on success, 0 on failure. 17 | */ 18 | int proctal_linux_watch_implementation_breakpoint_disable(struct proctal_linux *pl, pid_t tid); 19 | 20 | #endif /* API_LINUX_WATCH_IMPLEMENTATION_H */ 21 | -------------------------------------------------------------------------------- /src/api/linux/watch/interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "api/linux/proctal.h" 10 | #include "api/linux/watch.h" 11 | #include "api/linux/watch/implementation.h" 12 | 13 | /* 14 | * Attempts to disable breakpoints on as many tasks as possible. 15 | */ 16 | static inline void try_disable_breakpoints(struct proctal_linux *pl, struct proctal_linux_ptrace_task *begin, struct proctal_linux_ptrace_task *end) 17 | { 18 | struct proctal_linux_ptrace_task *task; 19 | 20 | for (task = begin; task != end; ++task) { 21 | proctal_linux_watch_implementation_breakpoint_disable(pl, task->tid); 22 | } 23 | } 24 | 25 | void proctal_linux_watch_start(struct proctal_linux *pl) 26 | { 27 | if (!proctal_linux_ptrace_attach(pl)) { 28 | return; 29 | } 30 | 31 | for (struct proctal_linux_ptrace_task *task = proctal_darr_begin(&pl->ptrace.tasks); task != proctal_darr_end(&pl->ptrace.tasks); ++task) { 32 | if (!proctal_linux_watch_implementation_breakpoint_enable(pl, task->tid)) { 33 | try_disable_breakpoints(pl, proctal_darr_begin(&pl->ptrace.tasks), task); 34 | proctal_linux_ptrace_detach(pl); 35 | return; 36 | } 37 | } 38 | 39 | if (!proctal_linux_ptrace_cont(pl, 0)) { 40 | try_disable_breakpoints(pl, proctal_darr_begin(&pl->ptrace.tasks), proctal_darr_end(&pl->ptrace.tasks)); 41 | proctal_linux_ptrace_detach(pl); 42 | return; 43 | } 44 | } 45 | 46 | void proctal_linux_watch_stop(struct proctal_linux *pl) 47 | { 48 | for (struct proctal_linux_ptrace_task *task = proctal_darr_begin(&pl->ptrace.tasks); task != proctal_darr_end(&pl->ptrace.tasks); ++task) { 49 | proctal_linux_ptrace_stop(pl, task->tid); 50 | proctal_linux_watch_implementation_breakpoint_disable(pl, task->tid); 51 | } 52 | 53 | proctal_linux_ptrace_detach(pl); 54 | } 55 | 56 | int proctal_linux_watch_next(struct proctal_linux *pl, void **address) 57 | { 58 | pid_t tid = proctal_linux_ptrace_catch_trap(pl, 0); 59 | 60 | if (tid == 0) { 61 | return 0; 62 | } 63 | 64 | proctal_linux_ptrace_instruction_pointer(pl, tid, address); 65 | 66 | if (!proctal_linux_ptrace_cont(pl, tid)) { 67 | return 0; 68 | } 69 | 70 | return 1; 71 | } 72 | -------------------------------------------------------------------------------- /src/api/linux/watch/unimplemented.c: -------------------------------------------------------------------------------- 1 | #include "api/linux/proctal.h" 2 | #include "api/linux/watch.h" 3 | #include "api/linux/watch/implementation.h" 4 | 5 | int proctal_linux_watch_implementation_breakpoint_enable(struct proctal_linux *pl, pid_t tid) 6 | { 7 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 8 | return 0; 9 | } 10 | 11 | int proctal_linux_watch_implementation_breakpoint_disable(struct proctal_linux *pl, pid_t tid) 12 | { 13 | proctal_error_set(&pl->p, PROCTAL_ERROR_UNSUPPORTED); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/api/malloc.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | 3 | void *proctal_malloc(struct proctal *p, size_t size) 4 | { 5 | void *a = proctal_global_malloc(size); 6 | 7 | if (a == NULL) { 8 | proctal_error_set(p, PROCTAL_ERROR_OUT_OF_MEMORY); 9 | } 10 | 11 | return a; 12 | } 13 | 14 | void proctal_free(struct proctal *p, const void *address) 15 | { 16 | return proctal_global_free(address); 17 | } 18 | -------------------------------------------------------------------------------- /src/api/pause.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | void proctal_pause(struct proctal *p) 5 | { 6 | proctal_implementation_pause(p); 7 | } 8 | 9 | void proctal_resume(struct proctal *p) 10 | { 11 | proctal_implementation_resume(p); 12 | } 13 | -------------------------------------------------------------------------------- /src/api/proc.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | void proctal_pid_set(struct proctal *p, int pid) 5 | { 6 | proctal_implementation_pid_set(p, pid); 7 | } 8 | 9 | int proctal_pid(struct proctal *p) 10 | { 11 | return proctal_implementation_pid(p); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/api/read.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | /* 5 | * Calls proctal_read with a C value. 6 | */ 7 | #define FORWARD_NATIVE(P, ADDRESS, VAL) \ 8 | proctal_read(P, ADDRESS, VAL, sizeof(*VAL)) 9 | 10 | /* 11 | * Calls proctal_read with a C array. 12 | */ 13 | #define FORWARD_NATIVE_ARRAY(P, ADDRESS, VAL, SIZE) \ 14 | proctal_read(P, ADDRESS, VAL, SIZE * sizeof(*VAL)) 15 | 16 | /* 17 | * Defines variants of proctal_read that take C types. 18 | */ 19 | #define DEFINE_FORWARD_NATIVE(SUFFIX, TYPE) \ 20 | size_t proctal_read_##SUFFIX(struct proctal *p, void *address, TYPE *out) \ 21 | { \ 22 | return FORWARD_NATIVE(p, address, out) / sizeof(TYPE); \ 23 | } \ 24 | \ 25 | size_t proctal_read_##SUFFIX##_array(struct proctal *p, void *address, TYPE *out, size_t size) \ 26 | { \ 27 | return FORWARD_NATIVE_ARRAY(p, address, out, size) / sizeof(TYPE); \ 28 | } 29 | 30 | size_t proctal_read(struct proctal *p, void *address, void *out, size_t size) 31 | { 32 | return proctal_implementation_read(p, address, out, size); 33 | } 34 | 35 | // Defining versions of proctal_read that take C types. 36 | DEFINE_FORWARD_NATIVE(char, char) 37 | DEFINE_FORWARD_NATIVE(signed_char, signed char) 38 | DEFINE_FORWARD_NATIVE(unsigned_char, unsigned char) 39 | DEFINE_FORWARD_NATIVE(short, short) 40 | DEFINE_FORWARD_NATIVE(unsigned_short, unsigned short) 41 | DEFINE_FORWARD_NATIVE(int, int) 42 | DEFINE_FORWARD_NATIVE(unsigned_int, unsigned int) 43 | DEFINE_FORWARD_NATIVE(long, long) 44 | DEFINE_FORWARD_NATIVE(unsigned_long, unsigned long) 45 | DEFINE_FORWARD_NATIVE(long_long, long long) 46 | DEFINE_FORWARD_NATIVE(unsigned_long_long, unsigned long long) 47 | DEFINE_FORWARD_NATIVE(float, float) 48 | DEFINE_FORWARD_NATIVE(double, double) 49 | DEFINE_FORWARD_NATIVE(long_double, long double) 50 | DEFINE_FORWARD_NATIVE(address, void *) 51 | -------------------------------------------------------------------------------- /src/api/region.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "api/proctal.h" 6 | #include "api/implementation.h" 7 | 8 | void proctal_scan_region_start(struct proctal *p) 9 | { 10 | proctal_implementation_scan_region_start(p); 11 | } 12 | 13 | void proctal_scan_region_stop(struct proctal *p) 14 | { 15 | proctal_implementation_scan_region_stop(p); 16 | } 17 | 18 | long proctal_scan_region_mask(struct proctal *p) 19 | { 20 | return p->region.mask; 21 | } 22 | 23 | void proctal_scan_region_mask_set(struct proctal *p, long mask) 24 | { 25 | p->region.mask = mask; 26 | } 27 | 28 | int proctal_scan_region_read(struct proctal *p) 29 | { 30 | return p->region.read; 31 | } 32 | 33 | void proctal_scan_region_read_set(struct proctal *p, int read) 34 | { 35 | p->region.read = read != 0; 36 | } 37 | 38 | int proctal_scan_region_write(struct proctal *p) 39 | { 40 | return p->region.write; 41 | } 42 | 43 | void proctal_scan_region_write_set(struct proctal *p, int write) 44 | { 45 | p->region.write = write != 0; 46 | } 47 | 48 | int proctal_scan_region_execute(struct proctal *p) 49 | { 50 | return p->region.execute; 51 | } 52 | 53 | void proctal_scan_region_execute_set(struct proctal *p, int execute) 54 | { 55 | p->region.execute = execute != 0; 56 | } 57 | 58 | int proctal_scan_region_next(struct proctal *p, void **start, void **end) 59 | { 60 | return proctal_implementation_scan_region_next(p, start, end); 61 | } 62 | -------------------------------------------------------------------------------- /src/api/version.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "config.h" 6 | #include "api/proctal.h" 7 | 8 | unsigned int proctal_version(void) 9 | { 10 | return PROCTAL_VERSION; 11 | } 12 | -------------------------------------------------------------------------------- /src/api/watch.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | void *proctal_watch_address(struct proctal *p) 5 | { 6 | return p->watch.address; 7 | } 8 | 9 | void proctal_watch_address_set(struct proctal *p, void *address) 10 | { 11 | p->watch.address = address; 12 | } 13 | 14 | int proctal_watch_read(struct proctal *p) 15 | { 16 | return p->watch.read; 17 | } 18 | 19 | void proctal_watch_read_set(struct proctal *p, int read) 20 | { 21 | p->watch.read = read != 0; 22 | } 23 | 24 | int proctal_watch_write(struct proctal *p) 25 | { 26 | return p->watch.write; 27 | } 28 | 29 | void proctal_watch_write_set(struct proctal *p, int write) 30 | { 31 | p->watch.write = write != 0; 32 | } 33 | 34 | int proctal_watch_execute(struct proctal *p) 35 | { 36 | return p->watch.execute; 37 | } 38 | 39 | void proctal_watch_execute_set(struct proctal *p, int execute) 40 | { 41 | p->watch.execute = execute != 0; 42 | } 43 | 44 | void proctal_watch_start(struct proctal *p) 45 | { 46 | proctal_implementation_watch_start(p); 47 | } 48 | 49 | void proctal_watch_stop(struct proctal *p) 50 | { 51 | proctal_implementation_watch_stop(p); 52 | } 53 | 54 | int proctal_watch_next(struct proctal *p, void **address) 55 | { 56 | return proctal_implementation_watch_next(p, address); 57 | } 58 | -------------------------------------------------------------------------------- /src/api/windows/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "api/windows/proctal.h" 4 | 5 | size_t proctal_windows_memory_read(struct proctal_windows *pw, void *address, void *out, size_t size) 6 | { 7 | SIZE_T read = 0; 8 | 9 | ReadProcessMemory(pw->process_handle, address, out, size, &read); 10 | 11 | if (read != size) { 12 | proctal_error_set(&pw->p, PROCTAL_ERROR_READ_FAILURE); 13 | } 14 | 15 | return read; 16 | } 17 | 18 | size_t proctal_windows_memory_write(struct proctal_windows *pw, void *address, const void *in, size_t size) 19 | { 20 | SIZE_T written = 0; 21 | 22 | WriteProcessMemory(pw->process_handle, address, in, size, &written); 23 | 24 | if (written != size) { 25 | proctal_error_set(&pw->p, PROCTAL_ERROR_WRITE_FAILURE); 26 | } 27 | 28 | return written; 29 | } 30 | -------------------------------------------------------------------------------- /src/api/windows/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef API_WINDOWS_MEMORY_H 2 | #define API_WINDOWS_MEMORY_H 3 | 4 | #include 5 | 6 | /* 7 | * Read from memory. 8 | * 9 | * Returns the number of bytes read. If that number does not match the 10 | * requested size, then it means that there was an error. 11 | */ 12 | size_t proctal_windows_memory_read(struct proctal_windows *pw, void *address, void *out, size_t size); 13 | 14 | /* 15 | * Write to memory. 16 | * 17 | * Returns the number of bytes written. If that number does not match the 18 | * given size, then it means that there was an error. 19 | */ 20 | size_t proctal_windows_memory_write(struct proctal_windows *pw, void *address, const void *in, size_t size); 21 | 22 | #endif /* API_WINDOWS_MEMORY_H */ 23 | -------------------------------------------------------------------------------- /src/api/windows/proctal.c: -------------------------------------------------------------------------------- 1 | #include "api/windows/proctal.h" 2 | 3 | static int open_process(struct proctal_windows *pw) 4 | { 5 | if (pw->process_id == 0) { 6 | // Can't open this. 7 | proctal_error_set(&pw->p, PROCTAL_ERROR_PERMISSION_DENIED); 8 | return 0; 9 | } 10 | 11 | DWORD access = PROCESS_VM_READ | PROCESS_VM_WRITE; 12 | 13 | pw->process_handle = OpenProcess(access, FALSE, pw->process_id); 14 | 15 | if (pw->process_handle == NULL) { 16 | // TODO: Check error code. 17 | pw->process_id = 0; 18 | proctal_error_set(&pw->p, PROCTAL_ERROR_PROGRAM_UNTAMEABLE); 19 | return 0; 20 | } 21 | 22 | return 1; 23 | } 24 | 25 | static int close_process(struct proctal_windows *pw) 26 | { 27 | if (pw->process_id == 0) { 28 | // Nothing to close. 29 | return 1; 30 | } 31 | 32 | if (!CloseHandle(pw->process_handle)) { 33 | // TODO: Check error code. 34 | pw->process_id = 0; 35 | proctal_error_set(&pw->p, PROCTAL_ERROR_PROGRAM_UNTAMEABLE); 36 | return 0; 37 | } 38 | 39 | return 1; 40 | } 41 | 42 | void proctal_windows_init(struct proctal_windows *pw) 43 | { 44 | proctal_init(&pw->p); 45 | 46 | pw->process_id = 0; 47 | } 48 | 49 | void proctal_windows_deinit(struct proctal_windows *pw) 50 | { 51 | close_process(pw); 52 | 53 | proctal_deinit(&pw->p); 54 | } 55 | 56 | void proctal_windows_pid_set(struct proctal_windows *pw, DWORD process_id) 57 | { 58 | close_process(pw); 59 | 60 | pw->process_id = process_id; 61 | 62 | open_process(pw); 63 | } 64 | 65 | DWORD proctal_windows_pid(struct proctal_windows *pw) 66 | { 67 | return pw->process_id; 68 | } 69 | -------------------------------------------------------------------------------- /src/api/windows/proctal.h: -------------------------------------------------------------------------------- 1 | #ifndef API_WINDOWS_PROCTAL_H 2 | #define API_WINDOWS_PROCTAL_H 3 | 4 | #include 5 | 6 | #include "api/proctal.h" 7 | 8 | /* 9 | * Windows specific handle. 10 | */ 11 | struct proctal_windows { 12 | // Base structure. 13 | struct proctal p; 14 | 15 | // Process ID. This identifies the program we're going to muck with. 16 | DWORD process_id; 17 | 18 | // A handle to the program. 19 | HANDLE process_handle; 20 | }; 21 | 22 | /* 23 | * Initializes a Windows specific handle. 24 | */ 25 | void proctal_windows_init(struct proctal_windows *pw); 26 | 27 | /* 28 | * Deinitializes a Windows specific handle. 29 | */ 30 | void proctal_windows_deinit(struct proctal_windows *pw); 31 | 32 | /* 33 | * Sets the PID. 34 | */ 35 | void proctal_windows_pid_set(struct proctal_windows *pw, DWORD process_id); 36 | 37 | /* 38 | * Gets the PID. 39 | */ 40 | DWORD proctal_windows_pid(struct proctal_windows *pw); 41 | 42 | #endif /* API_WINDOWS_PROCTAL_H */ 43 | -------------------------------------------------------------------------------- /src/api/write.c: -------------------------------------------------------------------------------- 1 | #include "api/proctal.h" 2 | #include "api/implementation.h" 3 | 4 | /* 5 | * Calls proctal_write with a C value. 6 | */ 7 | #define FORWARD_NATIVE(P, ADDRESS, VAL) \ 8 | proctal_write(P, ADDRESS, &VAL, sizeof(VAL)) 9 | 10 | /* 11 | * Calls proctal_write with a C array. 12 | */ 13 | #define FORWARD_NATIVE_ARRAY(P, ADDRESS, VAL, SIZE) \ 14 | proctal_write(P, ADDRESS, VAL, SIZE * sizeof(*VAL)) 15 | 16 | /* 17 | * Defines variants of proctal_write that take C types. 18 | */ 19 | #define DEFINE_FORWARD_NATIVE(SUFFIX, TYPE) \ 20 | size_t proctal_write_##SUFFIX(struct proctal *p, void *address, TYPE in) \ 21 | { \ 22 | return FORWARD_NATIVE(p, address, in) / sizeof(TYPE); \ 23 | } \ 24 | \ 25 | size_t proctal_write_##SUFFIX##_array(struct proctal *p, void *address, const TYPE *in, size_t size) \ 26 | { \ 27 | return FORWARD_NATIVE_ARRAY(p, address, in, size) / sizeof(TYPE); \ 28 | } 29 | 30 | size_t proctal_write(struct proctal *p, void *address, const void *in, size_t size) 31 | { 32 | return proctal_implementation_write(p, address, in, size); 33 | } 34 | 35 | // Defining versions of proctal_write that take C types. 36 | DEFINE_FORWARD_NATIVE(char, char) 37 | DEFINE_FORWARD_NATIVE(signed_char, signed char) 38 | DEFINE_FORWARD_NATIVE(unsigned_char, unsigned char) 39 | DEFINE_FORWARD_NATIVE(short, short) 40 | DEFINE_FORWARD_NATIVE(unsigned_short, unsigned short) 41 | DEFINE_FORWARD_NATIVE(int, int) 42 | DEFINE_FORWARD_NATIVE(unsigned_int, unsigned int) 43 | DEFINE_FORWARD_NATIVE(long, long) 44 | DEFINE_FORWARD_NATIVE(unsigned_long, unsigned long) 45 | DEFINE_FORWARD_NATIVE(long_long, long long) 46 | DEFINE_FORWARD_NATIVE(unsigned_long_long, unsigned long long) 47 | DEFINE_FORWARD_NATIVE(float, float) 48 | DEFINE_FORWARD_NATIVE(double, double) 49 | DEFINE_FORWARD_NATIVE(long_double, long double) 50 | DEFINE_FORWARD_NATIVE(address, void *) 51 | -------------------------------------------------------------------------------- /src/chunk/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(proctal_chunk STATIC chunk.c chunk.h) 2 | 3 | add_subdirectory(tests) 4 | -------------------------------------------------------------------------------- /src/chunk/chunk.c: -------------------------------------------------------------------------------- 1 | #include "chunk/chunk.h" 2 | 3 | extern inline void chunk_init(struct chunk *c, void *start, void *end, size_t size); 4 | 5 | extern inline void chunk_deinit(struct chunk *c); 6 | 7 | extern inline int chunk_finished(struct chunk *c); 8 | 9 | extern inline void *chunk_offset(struct chunk *c); 10 | 11 | extern inline size_t chunk_size(struct chunk *c); 12 | 13 | extern inline int chunk_next(struct chunk *c); 14 | -------------------------------------------------------------------------------- /src/chunk/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNK_CHUNK_H 2 | #define CHUNK_CHUNK_H 3 | 4 | #include 5 | 6 | #include "magic/magic.h" 7 | 8 | /* 9 | * Chunk data structure. Call chunk_init to initialize the struct. 10 | */ 11 | struct chunk { 12 | size_t size; 13 | char *curr; 14 | char *end; 15 | }; 16 | 17 | /* 18 | * Initializes a chunk data structure. 19 | * 20 | * Takes the start and end addresses of a block of memory and a size for 21 | * partitioning it in chunks. 22 | */ 23 | inline void chunk_init(struct chunk *c, void *start, void *end, size_t size) 24 | { 25 | c->size = size; 26 | c->curr = start; 27 | c->end = end; 28 | } 29 | 30 | /* 31 | * Deinitializes a chunk data structure. 32 | */ 33 | inline void chunk_deinit(struct chunk *c) 34 | { 35 | } 36 | 37 | /* 38 | * Will return 1 if finished, 0 if there's still more chunks to iterate over. 39 | */ 40 | inline int chunk_finished(struct chunk *c) 41 | { 42 | return c->curr >= c->end; 43 | } 44 | 45 | /* 46 | * Returns the start address of the current chunk. 47 | * 48 | * The function chunk_size returns the size of the returned current chunk. 49 | * 50 | * Behavior is left undefined if chunk_finished returns true. 51 | */ 52 | inline void *chunk_offset(struct chunk *c) 53 | { 54 | return c->curr; 55 | } 56 | 57 | /* 58 | * Size of the current chunk. 59 | * 60 | * Behavior is left undefined if chunk_finished returns true. 61 | */ 62 | inline size_t chunk_size(struct chunk *c) 63 | { 64 | size_t curr_size = c->end - c->curr; 65 | 66 | if (curr_size > c->size) { 67 | curr_size = c->size; 68 | } 69 | 70 | return curr_size; 71 | } 72 | 73 | /* 74 | * Moves on to the next chunk. 75 | * 76 | * Returns 1 if successful, 0 when no more chunks are available. 77 | * 78 | * Behavior is left undefined if chunk_finished returns true. 79 | */ 80 | inline int chunk_next(struct chunk *c) 81 | { 82 | c->curr += c->size; 83 | 84 | return !chunk_finished(c); 85 | } 86 | 87 | #endif /* CHUNK_CHUNK_H */ 88 | -------------------------------------------------------------------------------- /src/chunk/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_chunk_${file} ${file}.c) 3 | target_link_libraries(proctal_chunk_${file} PRIVATE proctal_chunk) 4 | add_test(NAME proctal_chunk_${file} COMMAND proctal_chunk_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(leftover) 8 | test_single_c_file(evenly) 9 | test_single_c_file(finished) 10 | -------------------------------------------------------------------------------- /src/chunk/tests/evenly.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "chunk/chunk.h" 5 | 6 | int main(void) 7 | { 8 | char block[10]; 9 | 10 | struct chunk c; 11 | chunk_init(&c, block, block + 10, 5); 12 | 13 | char *offset; 14 | size_t size; 15 | 16 | offset = chunk_offset(&c); 17 | size = chunk_size(&c); 18 | 19 | if (offset != &block[0]) { 20 | fprintf(stderr, "First chunk was supposed to start at the beginning of the block.\n"); 21 | chunk_deinit(&c); 22 | return 1; 23 | } 24 | 25 | if (size != 5) { 26 | fprintf(stderr, "First chunk size is not correct.\n"); 27 | chunk_deinit(&c); 28 | return 1; 29 | } 30 | 31 | if (!chunk_next(&c)) { 32 | fprintf(stderr, "There's supposed to be another chunk.\n"); 33 | chunk_deinit(&c); 34 | return 1; 35 | } 36 | 37 | offset = chunk_offset(&c); 38 | size = chunk_size(&c); 39 | 40 | if (offset != &block[5]) { 41 | fprintf(stderr, "Last chunk was supposed to start at the 5th element of the block.\n"); 42 | chunk_deinit(&c); 43 | return 1; 44 | } 45 | 46 | if (size != 5) { 47 | fprintf(stderr, "Last chunk size is not correct.\n"); 48 | chunk_deinit(&c); 49 | return 1; 50 | } 51 | 52 | if (chunk_next(&c)) { 53 | fprintf(stderr, "There's not supposed to be any more chunks.\n"); 54 | chunk_deinit(&c); 55 | return 1; 56 | } 57 | 58 | chunk_deinit(&c); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/chunk/tests/finished.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "chunk/chunk.h" 4 | 5 | int main(void) 6 | { 7 | char block[3]; 8 | 9 | struct chunk c; 10 | chunk_init(&c, block, block + 3, 2); 11 | 12 | if (chunk_finished(&c)) { 13 | fprintf(stderr, "Incorrectly reporting to have finished after init.\n"); 14 | chunk_deinit(&c); 15 | return 1; 16 | } 17 | 18 | chunk_next(&c); 19 | 20 | if (chunk_finished(&c)) { 21 | fprintf(stderr, "Incorrectly reporting to have finished before the last chunk.\n"); 22 | chunk_deinit(&c); 23 | return 1; 24 | } 25 | 26 | chunk_next(&c); 27 | 28 | if (!chunk_finished(&c)) { 29 | fprintf(stderr, "Not reporting to have finished after last chunk.\n"); 30 | chunk_deinit(&c); 31 | return 1; 32 | } 33 | 34 | chunk_deinit(&c); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/chunk/tests/leftover.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "chunk/chunk.h" 5 | 6 | int main(void) 7 | { 8 | char block[10]; 9 | 10 | struct chunk c; 11 | chunk_init(&c, block, block + 10, 4); 12 | 13 | char *offset; 14 | size_t size; 15 | 16 | offset = chunk_offset(&c); 17 | size = chunk_size(&c); 18 | 19 | if (offset != &block[0]) { 20 | fprintf(stderr, "First chunk was supposed to start at the beginning of the block.\n"); 21 | chunk_deinit(&c); 22 | return 1; 23 | } 24 | 25 | if (size != 4) { 26 | fprintf(stderr, "First chunk size is not correct.\n"); 27 | chunk_deinit(&c); 28 | return 1; 29 | } 30 | 31 | if (!chunk_next(&c)) { 32 | fprintf(stderr, "There's supposed to be another chunk.\n"); 33 | chunk_deinit(&c); 34 | return 1; 35 | } 36 | 37 | offset = chunk_offset(&c); 38 | size = chunk_size(&c); 39 | 40 | if (offset != &block[4]) { 41 | fprintf(stderr, "Middle chunk was supposed to start at the 4th element of the block.\n"); 42 | chunk_deinit(&c); 43 | return 1; 44 | } 45 | 46 | if (size != 4) { 47 | fprintf(stderr, "Middle chunk size is not correct.\n"); 48 | chunk_deinit(&c); 49 | return 1; 50 | } 51 | 52 | if (!chunk_next(&c)) { 53 | fprintf(stderr, "Last chunk is missing.\n"); 54 | chunk_deinit(&c); 55 | return 1; 56 | } 57 | 58 | offset = chunk_offset(&c); 59 | size = chunk_size(&c); 60 | 61 | if (offset != &block[8]) { 62 | fprintf(stderr, "Last chunk was supposed to start at the 8th element of the block.\n"); 63 | chunk_deinit(&c); 64 | return 1; 65 | } 66 | 67 | if (size != 2) { 68 | fprintf(stderr, "Last chunk size is not correct.\n"); 69 | fprintf(stderr, "Expected 2, got %d.\n", (int) size); 70 | chunk_deinit(&c); 71 | return 1; 72 | } 73 | 74 | if (chunk_next(&c)) { 75 | fprintf(stderr, "There's not supposed to be any more chunks.\n"); 76 | chunk_deinit(&c); 77 | return 1; 78 | } 79 | 80 | chunk_deinit(&c); 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /src/cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(val) 2 | add_subdirectory(assembler) 3 | add_subdirectory(yuck) 4 | add_subdirectory(parser) 5 | add_subdirectory(pattern) 6 | add_subdirectory(tests) 7 | 8 | add_executable( 9 | proctal_cli 10 | 11 | cmd/allocate.c 12 | cmd/allocate.h 13 | cmd/deallocate.c 14 | cmd/deallocate.h 15 | cmd/execute.c 16 | cmd/execute.h 17 | cmd/watch.c 18 | cmd/watch.h 19 | cmd/pause.c 20 | cmd/pause.h 21 | cmd/read.c 22 | cmd/read.h 23 | cmd/write.c 24 | cmd/write.h 25 | cmd/search.c 26 | cmd/search.h 27 | cmd/pattern.c 28 | cmd/pattern.h 29 | cmd/measure.c 30 | cmd/measure.h 31 | cmd/dump.c 32 | cmd/dump.h 33 | scanner.h 34 | scanner.c 35 | printer.h 36 | printer.c 37 | vmagazine.h 38 | vmagazine.c 39 | main.c 40 | ) 41 | 42 | set_target_properties(proctal_cli PROPERTIES OUTPUT_NAME "proctal") 43 | 44 | target_link_libraries(proctal_cli proctal_api-static) 45 | target_link_libraries(proctal_cli proctal_pq) 46 | target_link_libraries(proctal_cli proctal_chunk) 47 | target_link_libraries(proctal_cli proctal_swbuf) 48 | target_link_libraries(proctal_cli proctal_riter) 49 | target_link_libraries(proctal_cli proctal_cli_val) 50 | target_link_libraries(proctal_cli proctal_cli_assembler) 51 | target_link_libraries(proctal_cli proctal_cli_yuck) 52 | target_link_libraries(proctal_cli proctal_cli_parser) 53 | target_link_libraries(proctal_cli proctal_cli_pattern) 54 | target_link_libraries(proctal_cli darr) 55 | -------------------------------------------------------------------------------- /src/cli/assembler/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library( 2 | proctal_cli_assembler 3 | 4 | OBJECT 5 | 6 | assembler.h 7 | internal.h 8 | internal.c 9 | interface.c 10 | implementation.h 11 | implementation.c 12 | ) 13 | 14 | if(PROCTAL_HAS_CAPSTONE) 15 | if(CAPSTONE_LIBRARY) 16 | target_link_libraries(proctal_cli_assembler capstone) 17 | else() 18 | # Has different name. 19 | target_link_libraries(proctal_cli_assembler capstone-static) 20 | endif() 21 | endif() 22 | 23 | if(PROCTAL_HAS_KEYSTONE) 24 | target_link_libraries(proctal_cli_assembler keystone) 25 | endif() 26 | -------------------------------------------------------------------------------- /src/cli/assembler/implementation.c: -------------------------------------------------------------------------------- 1 | #include "cli/assembler/implementation.h" 2 | #include "config.h" 3 | 4 | #ifdef PROCTAL_HAS_CAPSTONE 5 | 6 | #include "cli/assembler/implementation/with-capstone.c" 7 | 8 | #else 9 | 10 | #include "cli/assembler/implementation/without-capstone.c" 11 | 12 | #endif 13 | 14 | #ifdef PROCTAL_HAS_KEYSTONE 15 | 16 | #include "cli/assembler/implementation/with-keystone.c" 17 | 18 | #else 19 | 20 | #include "cli/assembler/implementation/without-keystone.c" 21 | 22 | #endif -------------------------------------------------------------------------------- /src/cli/assembler/implementation.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_ASSEMBLER_IMPLEMENTATION_H 2 | #define CLI_ASSEMBLER_IMPLEMENTATION_H 3 | 4 | #include "cli/assembler/assembler.h" 5 | 6 | int cli_assembler_implementation_decompile(struct cli_assembler *assembler, const char *bytecode, size_t bytecode_size, struct cli_assembler_decompile_result *result); 7 | 8 | void cli_assembler_implementation_decompile_dispose(struct cli_assembler_decompile_result *result); 9 | 10 | int cli_assembler_implementation_compile(struct cli_assembler *assembler, const char *assembly, size_t assembly_size, struct cli_assembler_compile_result *result); 11 | 12 | void cli_assembler_implementation_compile_dispose(struct cli_assembler_compile_result *result); 13 | 14 | #endif /* CLI_ASSEMBLER_IMPLEMENTATION_H */ 15 | -------------------------------------------------------------------------------- /src/cli/assembler/implementation/without-capstone.c: -------------------------------------------------------------------------------- 1 | #include "cli/assembler/internal.h" 2 | #include "cli/assembler/implementation.h" 3 | 4 | int cli_assembler_implementation_decompile(struct cli_assembler *assembler, const char *bytecode, size_t bytecode_size, struct cli_assembler_decompile_result *result) 5 | { 6 | cli_assembler_error_message_set(assembler, "This feature is not available because Proctal was not compiled with support for Capstone."); 7 | 8 | return 0; 9 | } 10 | 11 | void cli_assembler_implementation_decompile_dispose(struct cli_assembler_decompile_result *result) 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/cli/assembler/implementation/without-keystone.c: -------------------------------------------------------------------------------- 1 | #include "cli/assembler/internal.h" 2 | #include "cli/assembler/implementation.h" 3 | 4 | int cli_assembler_implementation_compile(struct cli_assembler *assembler, const char *assembly, size_t assembly_size, struct cli_assembler_compile_result *result) 5 | { 6 | cli_assembler_error_message_set(assembler, "This feature is not available because Proctal was not compiled with support for Keystone."); 7 | 8 | return 0; 9 | } 10 | 11 | void cli_assembler_implementation_compile_dispose(struct cli_assembler_compile_result *result) 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/cli/assembler/internal.c: -------------------------------------------------------------------------------- 1 | #include "cli/assembler/internal.h" 2 | #include "cli/parser/parser.h" 3 | 4 | void cli_assembler_error_message_set(struct cli_assembler *assembler, const char *message) 5 | { 6 | assembler->error_message = message; 7 | } 8 | 9 | size_t cli_assembler_limit_to_one_instruction(const char *assembly, size_t assembly_size) 10 | { 11 | if (assembly_size == 0) { 12 | return 0; 13 | } 14 | 15 | return cli_parse_skip_until_chars2(assembly, assembly_size, "\n;"); 16 | } 17 | -------------------------------------------------------------------------------- /src/cli/assembler/internal.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_ASSEMBLER_INTERNAL_H 2 | #define CLI_ASSEMBLER_INTERNAL_H 3 | 4 | #include "cli/assembler/assembler.h" 5 | 6 | void cli_assembler_error_message_set(struct cli_assembler *assembler, const char *message); 7 | 8 | size_t cli_assembler_limit_to_one_instruction(const char *assembly, size_t assembly_size); 9 | 10 | #endif /* CLI_ASSEMBLER_INTERNAL_H */ 11 | -------------------------------------------------------------------------------- /src/cli/cmd/allocate.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cli/cmd/allocate.h" 4 | #include "cli/printer.h" 5 | #include "api/include/proctal.h" 6 | 7 | int cli_cmd_allocate(struct cli_cmd_allocate_arg *arg) 8 | { 9 | proctal_t p = proctal_open(); 10 | 11 | if (proctal_error(p)) { 12 | cli_print_proctal_error(p); 13 | proctal_close(p); 14 | return 1; 15 | } 16 | 17 | proctal_pid_set(p, arg->pid); 18 | 19 | if (!arg->read && !arg->write && !arg->execute) { 20 | proctal_allocate_read_set(p, 1); 21 | proctal_allocate_write_set(p, 1); 22 | proctal_allocate_execute_set(p, 1); 23 | } else { 24 | proctal_allocate_read_set(p, arg->read); 25 | proctal_allocate_write_set(p, arg->write); 26 | proctal_allocate_execute_set(p, arg->execute); 27 | } 28 | 29 | void *addr = proctal_allocate(p, arg->size); 30 | 31 | if (proctal_error(p)) { 32 | cli_print_proctal_error(p); 33 | proctal_close(p); 34 | return 1; 35 | } 36 | 37 | cli_print_address(addr); 38 | printf("\n"); 39 | 40 | proctal_close(p); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /src/cli/cmd/allocate.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_ALLOCATE_H 2 | #define CLI_CMD_ALLOCATE_H 3 | 4 | #include 5 | 6 | struct cli_cmd_allocate_arg { 7 | int pid; 8 | 9 | // Requested size. 10 | size_t size; 11 | 12 | // Read permission. 13 | int read; 14 | 15 | // Write permission. 16 | int write; 17 | 18 | // Execute permission. 19 | int execute; 20 | }; 21 | 22 | int cli_cmd_allocate(struct cli_cmd_allocate_arg *arg); 23 | 24 | #endif /* CLI_CMD_ALLOCATE_H */ 25 | -------------------------------------------------------------------------------- /src/cli/cmd/deallocate.c: -------------------------------------------------------------------------------- 1 | #include "cli/cmd/deallocate.h" 2 | #include "cli/printer.h" 3 | #include "api/include/proctal.h" 4 | 5 | int cli_cmd_deallocate(struct cli_cmd_deallocate_arg *arg) 6 | { 7 | proctal_t p = proctal_open(); 8 | 9 | if (proctal_error(p)) { 10 | cli_print_proctal_error(p); 11 | proctal_close(p); 12 | return 1; 13 | } 14 | 15 | proctal_pid_set(p, arg->pid); 16 | 17 | proctal_deallocate(p, arg->address); 18 | 19 | if (proctal_error(p)) { 20 | cli_print_proctal_error(p); 21 | proctal_close(p); 22 | return 1; 23 | } 24 | 25 | proctal_close(p); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/cli/cmd/deallocate.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_DEALLOCATE_H 2 | #define CLI_CMD_DEALLOCATE_H 3 | 4 | struct cli_cmd_deallocate_arg { 5 | int pid; 6 | 7 | void *address; 8 | }; 9 | 10 | int cli_cmd_deallocate(struct cli_cmd_deallocate_arg *arg); 11 | 12 | #endif /* CLI_CMD_DEALLOCATE_H */ 13 | -------------------------------------------------------------------------------- /src/cli/cmd/dump.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_DUMP_H 2 | #define CLI_CMD_DUMP_H 3 | 4 | struct cli_cmd_dump_arg { 5 | int pid; 6 | 7 | // Where to start dumping. Pass NULL to ignore this. 8 | void *address_start; 9 | 10 | // Where to stop dumping. Pass NULL to ignore this. 11 | void *address_stop; 12 | 13 | // Regions to search. Set to 0 to search all. Choose regions by using 14 | // macros that start with PROCTAL_REGION. 15 | int region; 16 | 17 | // Whether to dump readable memory addresses. 18 | int read; 19 | 20 | // Whether to dump writable memory addresses. 21 | int write; 22 | 23 | // Whether to dump executable memory addresses. 24 | int execute; 25 | 26 | // Whether to keep the program paused while dumping. 27 | int pause; 28 | }; 29 | 30 | int cli_cmd_dump(struct cli_cmd_dump_arg *arg); 31 | 32 | #endif /* CLI_CMD_DUMP_H */ 33 | -------------------------------------------------------------------------------- /src/cli/cmd/execute.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_EXECUTE_H 2 | #define CLI_CMD_EXECUTE_H 3 | 4 | #include "cli/assembler/assembler.h" 5 | 6 | enum cli_cmd_execute_format { 7 | CLI_CMD_EXECUTE_FORMAT_ASSEMBLY, 8 | CLI_CMD_EXECUTE_FORMAT_BYTECODE, 9 | }; 10 | 11 | struct cli_cmd_execute_arg { 12 | int pid; 13 | 14 | // What format we're expecting the input to be. 15 | enum cli_cmd_execute_format format; 16 | 17 | // Architecture. 18 | enum cli_assembler_architecture architecture; 19 | 20 | // Endianness. 21 | enum cli_assembler_endianness endianness; 22 | 23 | // x86 mode. 24 | enum cli_assembler_x86_mode x86_mode; 25 | 26 | // x86 syntax. 27 | enum cli_assembler_x86_syntax x86_syntax; 28 | 29 | // ARM mode. 30 | enum cli_assembler_arm_mode arm_mode; 31 | 32 | // Sparc mode. 33 | enum cli_assembler_sparc_mode sparc_mode; 34 | 35 | // PowerPC mode. 36 | enum cli_assembler_powerpc_mode powerpc_mode; 37 | 38 | // Mips mode. 39 | enum cli_assembler_mips_mode mips_mode; 40 | }; 41 | 42 | int cli_cmd_execute(struct cli_cmd_execute_arg *arg); 43 | 44 | #endif /* CLI_CMD_EXECUTE_H */ 45 | -------------------------------------------------------------------------------- /src/cli/cmd/measure.c: -------------------------------------------------------------------------------- 1 | #include "cli/cmd/measure.h" 2 | #include "cli/vmagazine.h" 3 | #include "cli/printer.h" 4 | #include "api/include/proctal.h" 5 | 6 | int cli_cmd_measure(struct cli_cmd_measure_arg *arg) 7 | { 8 | int ret = 1; 9 | 10 | struct vmagazine vmagazine; 11 | if (vmagazine_init(&vmagazine) != VMAGAZINE_OK) { 12 | fprintf(stderr, "Initialization failure.\n"); 13 | goto exit0; 14 | } 15 | vmagazine_template_value_set(&vmagazine, arg->value); 16 | vmagazine_template_address_set(&vmagazine, arg->address); 17 | 18 | if (vmagazine_size(&vmagazine) == 0) { 19 | for (size_t i = 0; i < arg->values_size; ++i) { 20 | const char *value = arg->values[i]; 21 | 22 | switch (vmagazine_parse_text(&vmagazine, value, strlen(value))) { 23 | case VMAGAZINE_OK: 24 | break; 25 | 26 | case VMAGAZINE_PARSE_FAILURE: 27 | default: 28 | fprintf(stderr, "Failed to parse argument #%d.\n", (int) i + 1); 29 | goto exit1; 30 | } 31 | } 32 | 33 | if (vmagazine_size(&vmagazine) == 0) { 34 | fprintf(stderr, "No values found.\n"); 35 | goto exit1; 36 | } 37 | } 38 | 39 | size_t total_size = 0; 40 | size_t array = arg->array; 41 | 42 | if (array == 0) { 43 | array = vmagazine_size(&vmagazine); 44 | } 45 | 46 | for (size_t i = 0, j = 0; i < array; ++i, ++j) { 47 | if (j == vmagazine_size(&vmagazine)) { 48 | j = 0; 49 | } 50 | 51 | cli_val_t *v = vmagazine_value(&vmagazine, j); 52 | 53 | total_size += cli_val_sizeof(*v); 54 | } 55 | 56 | cli_print_size(total_size); 57 | cli_print_nl(); 58 | 59 | ret = 0; 60 | exit1: 61 | vmagazine_deinit(&vmagazine); 62 | exit0: 63 | return ret; 64 | } 65 | -------------------------------------------------------------------------------- /src/cli/cmd/measure.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_MEASURE_H 2 | #define CLI_CMD_MEASURE_H 3 | 4 | #include 5 | 6 | #include "cli/val/val.h" 7 | 8 | struct cli_cmd_measure_arg { 9 | // At which address the first value would be located. 10 | void *address; 11 | 12 | // Number of values that would be expected to write. 13 | size_t array; 14 | 15 | // For parsing values. 16 | cli_val_t value; 17 | 18 | // Values to write. 19 | const char **values; 20 | // 21 | // How many values were passed. 22 | size_t values_size; 23 | }; 24 | 25 | int cli_cmd_measure(struct cli_cmd_measure_arg *arg); 26 | 27 | #endif /* CLI_CMD_MEASURE_H */ 28 | -------------------------------------------------------------------------------- /src/cli/cmd/pattern.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_PATTERN_H 2 | #define CLI_CMD_PATTERN_H 3 | 4 | struct cli_cmd_pattern_arg { 5 | int pid; 6 | 7 | const char *pattern; 8 | 9 | // If not NULL, start searching from this address. 10 | void *address_start; 11 | 12 | // If not NULL, search up to this address. 13 | void *address_stop; 14 | 15 | // Regions to search. Set to 0 to search all. Choose regions by using 16 | // macros that start with PROCTAL_REGION. 17 | int region; 18 | 19 | // Whether to search readable memory addresses. 20 | int read; 21 | 22 | // Whether to search writable memory addresses. 23 | int write; 24 | 25 | // Whether to search executable memory addresses. 26 | int execute; 27 | 28 | // Whether to keep the program paused while searching. 29 | int pause; 30 | }; 31 | 32 | int cli_cmd_pattern(struct cli_cmd_pattern_arg *arg); 33 | 34 | #endif /* CLI_CMD_PATTERN_H */ 35 | -------------------------------------------------------------------------------- /src/cli/cmd/pause.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cli/cmd/pause.h" 5 | #include "cli/printer.h" 6 | #include "api/include/proctal.h" 7 | #include "pq/pq.h" 8 | 9 | int cli_cmd_pause(struct cli_cmd_pause_arg *arg) 10 | { 11 | int ret = 1; 12 | 13 | if (!pq_start()) { 14 | fprintf(stderr, "Failed to start tracking quit signals.\n"); 15 | goto exit0; 16 | } 17 | 18 | proctal_t p = proctal_open(); 19 | 20 | if (proctal_error(p)) { 21 | cli_print_proctal_error(p); 22 | goto exit2; 23 | } 24 | 25 | proctal_pid_set(p, arg->pid); 26 | 27 | proctal_pause(p); 28 | 29 | if (proctal_error(p)) { 30 | cli_print_proctal_error(p); 31 | goto exit2; 32 | } 33 | 34 | pq_wait(); 35 | 36 | proctal_resume(p); 37 | 38 | if (proctal_error(p)) { 39 | cli_print_proctal_error(p); 40 | goto exit2; 41 | } 42 | 43 | ret = 0; 44 | exit2: 45 | proctal_close(p); 46 | exit1: 47 | pq_stop(); 48 | exit0: 49 | return ret; 50 | } 51 | -------------------------------------------------------------------------------- /src/cli/cmd/pause.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_PAUSE_H 2 | #define CLI_CMD_PAUSE_H 3 | 4 | struct cli_cmd_pause_arg { 5 | int pid; 6 | }; 7 | 8 | int cli_cmd_pause(struct cli_cmd_pause_arg *arg); 9 | 10 | #endif /* CLI_CMD_PAUSE_H */ 11 | -------------------------------------------------------------------------------- /src/cli/cmd/read.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_READ_H 2 | #define CLI_CMD_READ_H 3 | 4 | #include "cli/val/val.h" 5 | 6 | struct cli_cmd_read_arg { 7 | int pid; 8 | 9 | void *address; 10 | 11 | // Number of values expected to read. 12 | size_t array; 13 | 14 | // How we're going to interpret values. 15 | cli_val_t value; 16 | 17 | // Whether to additionally print the value's address. Useful when 18 | // printing a lot of adjacent values of variable length. 19 | int show_address; 20 | 21 | // Additionally prints a sequence of numbers in hexadecimal that 22 | // represent the bytes of the value in memory. 23 | int show_bytes; 24 | 25 | // Whether to print exactly what's in memory. 26 | int binary; 27 | 28 | // Whether to keep the program paused while reading. 29 | int pause; 30 | }; 31 | 32 | int cli_cmd_read(struct cli_cmd_read_arg *arg); 33 | 34 | #endif /* CLI_CMD_READ_H */ 35 | -------------------------------------------------------------------------------- /src/cli/cmd/search.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_SEARCH_H 2 | #define CLI_CMD_SEARCH_H 3 | 4 | #include "cli/val/val.h" 5 | 6 | struct cli_cmd_search_arg { 7 | int pid; 8 | 9 | // If not NULL, start searching from this address. 10 | void *address_start; 11 | 12 | // If not NULL, search up to this address. 13 | void *address_stop; 14 | 15 | // Regions to search. Set to 0 to search all. Use macros that start 16 | // with PROCTAL_REGION to choose regions. 17 | int region; 18 | 19 | // How we're going to interpret values. 20 | cli_val_t value; 21 | 22 | // Whether to search readable memory addresses. 23 | int read; 24 | 25 | // Whether to search writable memory addresses. 26 | int write; 27 | 28 | // Whether to search executable memory addresses. 29 | int execute; 30 | 31 | // Whether to review the results of a previous search. 32 | int review; 33 | 34 | // Whether to perform an equality check. 35 | int eq; 36 | cli_val_t eq_value; 37 | 38 | // Whether to perform a not equals check. 39 | int ne; 40 | cli_val_t ne_value; 41 | 42 | // Whether to perform greather than. 43 | int gt; 44 | cli_val_t gt_value; 45 | 46 | // Whether to perform greather than equals. 47 | int gte; 48 | cli_val_t gte_value; 49 | 50 | // Whether to perform less than. 51 | int lt; 52 | cli_val_t lt_value; 53 | 54 | // Whether to perform less than equals. 55 | int lte; 56 | cli_val_t lte_value; 57 | 58 | // Whether to check if it was incremented. 59 | int inc; 60 | cli_val_t inc_value; 61 | 62 | // Whether to check if it was incremented up to and including value. 63 | int inc_up_to; 64 | cli_val_t inc_up_to_value; 65 | 66 | // Whether to check if it was decremented. 67 | int dec; 68 | cli_val_t dec_value; 69 | 70 | // Whether to check if it was decremented up to and including value. 71 | int dec_up_to; 72 | cli_val_t dec_up_to_value; 73 | 74 | // Whether to check if it was changed. 75 | int changed; 76 | 77 | // Whether to check if it was unchanged. 78 | int unchanged; 79 | 80 | // Whether to check if it was increased. 81 | int increased; 82 | 83 | // Whether to check if it was decreased. 84 | int decreased; 85 | 86 | // Whether to keep the program paused while writing. 87 | int pause; 88 | }; 89 | 90 | int cli_cmd_search(struct cli_cmd_search_arg *arg); 91 | 92 | #endif /* CLI_CMD_SEARCH_H */ 93 | -------------------------------------------------------------------------------- /src/cli/cmd/watch.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_WATCH_H 2 | #define CLI_CMD_WATCH_H 3 | 4 | struct cli_cmd_watch_arg { 5 | int pid; 6 | 7 | void *address; 8 | 9 | // If not NULL, watch after this address. 10 | void *address_start; 11 | 12 | // If not NULL, watch up to this address. 13 | void *address_stop; 14 | 15 | // Whether to watch for reads. 16 | int read; 17 | 18 | // Whether to watch for writes. 19 | int write; 20 | 21 | // Whether to watch for instruction execution. 22 | int execute; 23 | 24 | // Whether to print an address only once. 25 | int unique; 26 | }; 27 | 28 | int cli_cmd_watch(struct cli_cmd_watch_arg *arg); 29 | 30 | #endif /* CLI_CMD_WATCH_H */ 31 | -------------------------------------------------------------------------------- /src/cli/cmd/write.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_CMD_WRITE_H 2 | #define CLI_CMD_WRITE_H 3 | 4 | #include 5 | 6 | #include "cli/val/val.h" 7 | 8 | struct cli_cmd_write_arg { 9 | int pid; 10 | 11 | void *address; 12 | 13 | // Number of values expected to write. 14 | size_t array; 15 | 16 | // For parsing values. 17 | cli_val_t value; 18 | 19 | // Values to write. 20 | const char **values; 21 | 22 | // How many values were passed. 23 | size_t values_size; 24 | 25 | // Whether to write the same value repeatedly until the program is told 26 | // to shut down. 27 | int repeat; 28 | // A delay in milliseconds before writing to the address again. Without 29 | // a delay you could theoretically turn your CPU into a heater. 30 | int repeat_delay; 31 | 32 | // Whether to parse values in binary. 33 | int binary; 34 | 35 | // Whether to keep the program paused while writing. 36 | int pause; 37 | }; 38 | 39 | int cli_cmd_write(struct cli_cmd_write_arg *arg); 40 | 41 | #endif /* CLI_CMD_WRITE_H */ 42 | -------------------------------------------------------------------------------- /src/cli/main.c: -------------------------------------------------------------------------------- 1 | #include "cli/yuck/main.h" 2 | 3 | int main(int argc, char **argv) 4 | { 5 | return cli_yuck_main(argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /src/cli/parser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | make_php_c(names.c.php) 2 | 3 | add_library( 4 | proctal_cli_parser 5 | 6 | OBJECT 7 | 8 | parser.h 9 | c-types.c 10 | names.c 11 | skip.c 12 | hex.c 13 | ) 14 | -------------------------------------------------------------------------------- /src/cli/parser/c-types.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int cli_parse_char(const char *s, char *val) 5 | { 6 | return sscanf(s, "%c", val) == 1 ? 1 : 0; 7 | } 8 | 9 | int cli_parse_unsigned_char(const char *s, unsigned char *val) 10 | { 11 | return sscanf(s, "%hhd", val) == 1 ? 1 : 0; 12 | } 13 | 14 | int cli_parse_signed_char(const char *s, signed char *val) 15 | { 16 | return sscanf(s, "%hhu", val) == 1 ? 1 : 0; 17 | } 18 | 19 | int cli_parse_short(const char *s, short *val) 20 | { 21 | return sscanf(s, "%hd", val) == 1 ? 1 : 0; 22 | } 23 | 24 | int cli_parse_unsigned_short(const char *s, unsigned short *val) 25 | { 26 | return sscanf(s, "%hu", val) == 1 ? 1 : 0; 27 | } 28 | 29 | int cli_parse_int(const char *s, int *val) 30 | { 31 | return sscanf(s, "%d", val) == 1 ? 1 : 0; 32 | } 33 | 34 | int cli_parse_unsigned_int(const char *s, unsigned int *val) 35 | { 36 | return sscanf(s, "%u", val) == 1 ? 1 : 0; 37 | } 38 | 39 | int cli_parse_long(const char *s, long *val) 40 | { 41 | return sscanf(s, "%ld", val) == 1 ? 1 : 0; 42 | } 43 | 44 | int cli_parse_unsigned_long(const char *s, unsigned long *val) 45 | { 46 | return sscanf(s, "%lu", val) == 1 ? 1 : 0; 47 | } 48 | 49 | int cli_parse_long_long(const char *s, long long *val) 50 | { 51 | return sscanf(s, "%lld", val) == 1 ? 1 : 0; 52 | } 53 | 54 | int cli_parse_unsigned_long_long(const char *s, unsigned long long *val) 55 | { 56 | return sscanf(s, "%llu", val) == 1 ? 1 : 0; 57 | } 58 | 59 | int cli_parse_float(const char *s, float *val) 60 | { 61 | return sscanf(s, "%f", val) == 1 ? 1 : 0; 62 | } 63 | 64 | int cli_parse_double(const char *s, double *val) 65 | { 66 | return sscanf(s, "%lf", val) == 1 ? 1 : 0; 67 | } 68 | 69 | int cli_parse_long_double(const char *s, long double *val) 70 | { 71 | return sscanf(s, "%Lf", val) == 1 ? 1 : 0; 72 | } 73 | 74 | int cli_parse_size(const char *s, size_t *val) 75 | { 76 | return sscanf(s, "%zu", val) == 1 ? 1 : 0; 77 | } 78 | 79 | int cli_parse_address(const char *s, void **val) 80 | { 81 | return sscanf(s, "%" PRIXPTR, (uintptr_t *) val) == 1 ? 1 : 0; 82 | } 83 | -------------------------------------------------------------------------------- /src/cli/parser/hex.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int cli_parse_is_hex_digit(int s) 4 | { 5 | return isxdigit(s) != 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/cli/parser/skip.c: -------------------------------------------------------------------------------- 1 | #include "cli/parser/parser.h" 2 | 3 | static inline int match_char(const char *chars, char ch) 4 | { 5 | for (size_t i = 0; chars[i] != '\0'; ++i) { 6 | if (chars[i] == ch) { 7 | return 1; 8 | } 9 | } 10 | 11 | return 0; 12 | } 13 | 14 | size_t cli_parse_skip_chars(const char *s, const char *chars) 15 | { 16 | int ch; 17 | size_t skipped = 0; 18 | 19 | for (size_t i = 0; (ch = s[i]) && match_char(chars, ch); ++i) { 20 | ++skipped; 21 | } 22 | 23 | return skipped; 24 | } 25 | 26 | size_t cli_parse_skip_chars2(const char *s, size_t length, const char *chars) 27 | { 28 | size_t skipped = 0; 29 | 30 | for (const char *ch = s; ch != &s[length] && match_char(chars, *ch); ++ch) { 31 | ++skipped; 32 | } 33 | 34 | return skipped; 35 | } 36 | 37 | size_t cli_parse_skip_until_chars(const char *s, const char *chars) 38 | { 39 | int ch; 40 | size_t skipped = 0; 41 | 42 | for (size_t i = 0; (ch = s[i]) && !match_char(chars, ch); ++i) { 43 | ++skipped; 44 | } 45 | 46 | return skipped; 47 | } 48 | 49 | size_t cli_parse_skip_until_chars2(const char *s, size_t length, const char *chars) 50 | { 51 | size_t skipped = 0; 52 | 53 | for (const char *ch = s; ch != &s[length] && !match_char(chars, *ch); ++ch) { 54 | ++skipped; 55 | } 56 | 57 | return skipped; 58 | } 59 | -------------------------------------------------------------------------------- /src/cli/pattern/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(tests) 2 | 3 | add_library( 4 | proctal_cli_pattern 5 | 6 | OBJECT 7 | 8 | pattern.h 9 | pattern.c 10 | ) 11 | -------------------------------------------------------------------------------- /src/cli/pattern/pattern.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_PATTERN_H 2 | #define CLI_PATTERN_H 3 | 4 | #include 5 | 6 | #define CLI_PATTERN_ERROR_INVALID_PATTERN 1 7 | #define CLI_PATTERN_ERROR_OUT_OF_MEMORY 2 8 | #define CLI_PATTERN_ERROR_EMPTY_PATTERN 3 9 | #define CLI_PATTERN_ERROR_MISSING_WHITESPACE 4 10 | #define CLI_PATTERN_ERROR_COMPILE_PATTERN 5 11 | 12 | typedef struct cli_pattern *cli_pattern; 13 | 14 | cli_pattern cli_pattern_create(void); 15 | 16 | void cli_pattern_destroy(cli_pattern cp); 17 | 18 | int cli_pattern_compile(cli_pattern cp, const char *s); 19 | 20 | void cli_pattern_new(cli_pattern cp); 21 | 22 | int cli_pattern_ready(cli_pattern cp); 23 | 24 | int cli_pattern_input(cli_pattern cp, const char *data, size_t size); 25 | 26 | int cli_pattern_finished(cli_pattern cp); 27 | 28 | int cli_pattern_matched(cli_pattern cp); 29 | 30 | int cli_pattern_error(cli_pattern cp); 31 | 32 | int cli_pattern_error_compile_offset(cli_pattern cp); 33 | 34 | #endif /* CLI_PATTERN_H */ 35 | -------------------------------------------------------------------------------- /src/cli/pattern/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_cli_pattern_${file} ${file}.c) 3 | target_link_libraries(proctal_cli_pattern_${file} PRIVATE proctal_cli_pattern proctal_cli_parser) 4 | add_test(NAME proctal_cli_pattern_${file} COMMAND proctal_cli_pattern_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(invalid-patterns) 8 | test_single_c_file(valid-patterns) 9 | -------------------------------------------------------------------------------- /src/cli/pattern/tests/valid-patterns.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cli/pattern/pattern.h" 6 | 7 | int main(void) 8 | { 9 | struct test { 10 | const char *pattern; 11 | const char *expected_match; 12 | }; 13 | 14 | struct test tests[] = { 15 | { 16 | .pattern = "99", 17 | .expected_match = "\x99", 18 | }, 19 | { 20 | .pattern = "99 88", 21 | .expected_match = "\x99\x88", 22 | }, 23 | { 24 | .pattern = "??", 25 | .expected_match = "\xaa", 26 | }, 27 | { 28 | .pattern = "?? 88", 29 | .expected_match = "\xaa\x88", 30 | }, 31 | { 32 | .pattern = " ?? 88 ", 33 | .expected_match = "\xaa\x88", 34 | }, 35 | }; 36 | 37 | cli_pattern cp = cli_pattern_create(); 38 | 39 | for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) { 40 | struct test *test = &tests[i]; 41 | 42 | cli_pattern_new(cp); 43 | 44 | cli_pattern_compile(cp, test->pattern); 45 | 46 | int error = cli_pattern_error(cp); 47 | 48 | if (error) { 49 | fprintf( 50 | stderr, 51 | "Pattern \"%s\" fails with error code %d.\n", 52 | test->pattern, 53 | error); 54 | 55 | return 1; 56 | } 57 | 58 | cli_pattern_input(cp, test->expected_match, strlen(test->expected_match)); 59 | 60 | if (!cli_pattern_finished(cp)) { 61 | fprintf( 62 | stderr, 63 | "Pattern \"%s\" could not match the whole thing.\n", 64 | test->pattern); 65 | 66 | return 1; 67 | } 68 | 69 | if (!cli_pattern_matched(cp)) { 70 | fprintf( 71 | stderr, 72 | "Pattern \"%s\" failed to match.\n", 73 | test->pattern); 74 | 75 | return 1; 76 | } 77 | } 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /src/cli/printer.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_PRINTER_H 2 | #define CLI_PRINTER_H 3 | 4 | #include 5 | #include "api/include/proctal.h" 6 | #include "cli/pattern/pattern.h" 7 | #include "riter/riter.h" 8 | 9 | /* 10 | * Prints a generic error message about riter's failure. These will only be 11 | * useful to programmers. 12 | */ 13 | void cli_print_riter_error(struct riter *r); 14 | 15 | /* 16 | * Prints an error message 17 | */ 18 | void cli_print_proctal_error(proctal_t p); 19 | 20 | /* 21 | * Prints the error message of a pattern. 22 | */ 23 | void cli_print_pattern_error(cli_pattern cp); 24 | 25 | /* 26 | * Prints a memory address. 27 | */ 28 | void cli_print_address(void *address); 29 | 30 | /* 31 | * Prints a single byte. 32 | */ 33 | void cli_print_byte(unsigned char byte); 34 | 35 | /* 36 | * Prints a size in bytes. 37 | */ 38 | void cli_print_size(size_t size); 39 | 40 | /* 41 | * Prints a new line character. 42 | */ 43 | void cli_print_nl(void); 44 | 45 | #endif /* CLI_PRINTER_H */ 46 | -------------------------------------------------------------------------------- /src/cli/scanner.c: -------------------------------------------------------------------------------- 1 | #include "cli/scanner.h" 2 | 3 | static inline int match_char(const char *chars, int ch) 4 | { 5 | for (size_t i = 0; chars[i] != '\0'; ++i) { 6 | if (chars[i] == ch) { 7 | return 1; 8 | } 9 | } 10 | 11 | return 0; 12 | } 13 | 14 | size_t cli_scan_skip_chars(FILE *f, const char *chars) 15 | { 16 | int ch; 17 | size_t skipped = 0; 18 | 19 | do { 20 | ch = fgetc(f); 21 | 22 | if (ch == EOF) { 23 | return skipped; 24 | } 25 | 26 | ++skipped; 27 | } while (match_char(chars, ch)); 28 | 29 | ungetc(ch, f); 30 | 31 | return skipped; 32 | } 33 | 34 | size_t cli_scan_skip_until_chars(FILE *f, const char *chars) 35 | { 36 | int ch; 37 | size_t skipped = 0; 38 | 39 | do { 40 | ch = fgetc(f); 41 | 42 | if (ch == EOF) { 43 | return skipped; 44 | } 45 | 46 | ++skipped; 47 | } while (!match_char(chars, ch)); 48 | 49 | ungetc(ch, f); 50 | 51 | return skipped; 52 | } 53 | -------------------------------------------------------------------------------- /src/cli/scanner.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_SCANNER_H 2 | #define CLI_SCANNER_H 3 | 4 | #include 5 | 6 | size_t cli_scan_skip_chars(FILE *f, const char *chars); 7 | 8 | size_t cli_scan_skip_until_chars(FILE *f, const char *chars); 9 | 10 | #endif /* CLI_SCANNER_H */ 11 | -------------------------------------------------------------------------------- /src/cli/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(util) 2 | 3 | function(test_python_script file) 4 | set(target_name proctal_cli_${file}) 5 | 6 | relative_build_or_source(file ${file}.py) 7 | 8 | add_test(NAME ${target_name} COMMAND ${PYTHON} ${file}) 9 | endfunction() 10 | 11 | test_python_script(allocate-permissions-default) 12 | test_python_script(allocate-permissions) 13 | test_python_script(deallocate) 14 | test_python_script(dump-range) 15 | test_python_script(execute-code) 16 | test_python_script(invalid-type-arguments) 17 | test_python_script(pattern-range) 18 | test_python_script(pause-multiple-threads) 19 | test_python_script(read-binary) 20 | test_python_script(read-write) 21 | test_python_script(search-boundaries) 22 | test_python_script(search-permissions-default) 23 | test_python_script(search-range) 24 | test_python_script(search-review-range) 25 | test_python_script(watch-multiple-threads) 26 | test_python_script(watch-range) 27 | test_python_script(watch-uncaught-trap-bug) 28 | test_python_script(watch-unique) 29 | test_python_script(write-binary) -------------------------------------------------------------------------------- /src/cli/tests/allocate-permissions-default.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper, algorithms 2 | 3 | with sleeper.run() as guinea: 4 | default_flags = "rwx" 5 | 6 | int32 = proctal_cli.TypeInteger(32) 7 | test_value = proctal_cli.ValueInteger(int32) 8 | test_value.parse(0xFFAAFFAA) 9 | 10 | address = proctal_cli.allocate(guinea.pid(), test_value.size()) 11 | 12 | proctal_cli.write(guinea.pid(), address, int32, test_value) 13 | 14 | for flag in default_flags: 15 | searcher = proctal_cli.search(guinea.pid(), int32, eq=test_value, permission=flag) 16 | 17 | found = False 18 | 19 | for match in searcher.match_iterator(): 20 | if match.address.cmp(address) == 0: 21 | found = True 22 | break 23 | 24 | searcher.stop() 25 | 26 | if not found: 27 | exit("Default allocate permissions is missing the {flag} flag.".format(flag=flag)) 28 | -------------------------------------------------------------------------------- /src/cli/tests/allocate-permissions.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper, algorithms 2 | 3 | permission_flags = ["r", "w", "x"] 4 | 5 | # All possible combinations. 6 | possible_permissions = algorithms.all_combinations(permission_flags) 7 | possible_permissions = map(lambda x: ''.join(x), possible_permissions) 8 | 9 | with sleeper.run() as guinea: 10 | for flags in possible_permissions: 11 | int32 = proctal_cli.TypeInteger(32) 12 | test_value = proctal_cli.ValueInteger(int32) 13 | test_value.parse(0xFFAAFFAA) 14 | 15 | address = proctal_cli.allocate(guinea.pid(), test_value.size(), permission=flags) 16 | 17 | proctal_cli.write(guinea.pid(), address, int32, test_value) 18 | 19 | searcher = proctal_cli.search(guinea.pid(), int32, eq=test_value, permission=flags) 20 | 21 | found = False 22 | 23 | for match in searcher.match_iterator(): 24 | if match.address.cmp(address) == 0: 25 | found = True 26 | break 27 | 28 | searcher.stop() 29 | 30 | if not found: 31 | exit("Could not allocate memory with the following permission flags: {flags}.".format(flags=flags)) 32 | -------------------------------------------------------------------------------- /src/cli/tests/deallocate.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper 2 | 3 | with sleeper.run() as guinea: 4 | int32 = proctal_cli.TypeInteger(32) 5 | test_value = proctal_cli.ValueInteger(int32) 6 | test_value.parse(0xFFAAFFAA) 7 | 8 | address = proctal_cli.allocate(guinea.pid(), test_value.size()) 9 | 10 | proctal_cli.write(guinea.pid(), address, int32, test_value) 11 | 12 | proctal_cli.deallocate(guinea.pid(), address) 13 | 14 | searcher = proctal_cli.search(guinea.pid(), int32, eq=test_value) 15 | 16 | for match in searcher.match_iterator(): 17 | if match.address.cmp(address) == 0: 18 | searcher.stop() 19 | exit("Memory block still seems accessible.") 20 | break 21 | 22 | searcher.stop() 23 | -------------------------------------------------------------------------------- /src/cli/tests/execute-code.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper 2 | 3 | codes = { 4 | "x86-64": """ 5 | mov rax, 0x{address} 6 | mov DWORD PTR [rax], {value} 7 | """ 8 | } 9 | 10 | with sleeper.run() as guinea: 11 | address = proctal_cli.allocate(guinea.pid(), 14) 12 | 13 | type = proctal_cli.TypeInteger(bits=32) 14 | value = proctal_cli.ValueInteger(type) 15 | value.parse(0) 16 | 17 | proctal_cli.write(guinea.pid(), address, type, value) 18 | 19 | proctal_cli.execute(guinea.pid(), codes["x86-64"].format(address=str(address), value=1)) 20 | 21 | reader = proctal_cli.read(guinea.pid(), address, type) 22 | read = reader.next_value() 23 | reader.stop() 24 | 25 | if read.cmp(value) == 0: 26 | exit("Value was not overwritten.") 27 | -------------------------------------------------------------------------------- /src/cli/tests/pause-multiple-threads.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, spit_back_mt 2 | 3 | with spit_back_mt.run() as guinea: 4 | if not guinea.ping(): 5 | exit("Failed to communicate with guinea pig.") 6 | 7 | pauser = proctal_cli.pause(guinea.pid()) 8 | 9 | if guinea.ping(): 10 | pauser.stop() 11 | exit("Was not supposed to be able to communicate with guinea pig.") 12 | 13 | pauser.stop() 14 | -------------------------------------------------------------------------------- /src/cli/tests/read-binary.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper 2 | 3 | class Error(Exception): 4 | pass 5 | 6 | class TestSingleValue: 7 | def __init__(self, type, value): 8 | self.type = type 9 | self.value = value 10 | pass 11 | 12 | def run(self, guinea): 13 | address = proctal_cli.allocate(guinea.pid(), self.value.size()) 14 | 15 | try: 16 | proctal_cli.write(guinea.pid(), address, self.type, self.value) 17 | reader = proctal_cli.read(guinea.pid(), address, self.type, binary=True) 18 | 19 | try: 20 | value = reader.next_value() 21 | 22 | if self.value.cmp(value) != 0: 23 | raise Error("Expected {expected} but got {found}.".format(expected=self.value, found=value)) 24 | finally: 25 | reader.stop() 26 | finally: 27 | proctal_cli.deallocate(guinea.pid(), address) 28 | 29 | int32 = proctal_cli.TypeInteger(32); 30 | int32_test_val = proctal_cli.ValueInteger(int32) 31 | int32_test_val.parse(0x0ACC23AA) 32 | 33 | tests = [ 34 | TestSingleValue(int32, int32_test_val) 35 | ] 36 | 37 | with sleeper.run() as guinea: 38 | for test in tests: 39 | test.run(guinea) 40 | -------------------------------------------------------------------------------- /src/cli/tests/read-write.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper 2 | 3 | class Error(Exception): 4 | pass 5 | 6 | class TestSingleValue: 7 | def __init__(self, type, value): 8 | self.type = type 9 | self.value = value 10 | pass 11 | 12 | def run(self, guinea): 13 | address = proctal_cli.allocate(guinea.pid(), self.value.size()) 14 | proctal_cli.write(guinea.pid(), address, self.type, self.value) 15 | 16 | try: 17 | reader = proctal_cli.read(guinea.pid(), address, self.type) 18 | 19 | try: 20 | value = reader.next_value() 21 | 22 | if self.value.cmp(value) != 0: 23 | raise Error("Expected {expected} but got {found}.".format(expected=self.value, found=value)) 24 | finally: 25 | reader.stop() 26 | finally: 27 | proctal_cli.deallocate(guinea.pid(), address) 28 | 29 | int32 = proctal_cli.TypeInteger(32); 30 | int32_test_val = proctal_cli.ValueInteger(int32) 31 | int32_test_val.parse(0x0ACC23AA) 32 | 33 | tests = [ 34 | TestSingleValue(int32, int32_test_val) 35 | ] 36 | 37 | with sleeper.run() as guinea: 38 | for test in tests: 39 | test.run(guinea) 40 | -------------------------------------------------------------------------------- /src/cli/tests/search-permissions-default.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper 2 | 3 | with sleeper.run() as guinea: 4 | active_flags = "r" 5 | inactive_flags = "wx" 6 | 7 | int32 = proctal_cli.TypeInteger(32) 8 | test_value = proctal_cli.ValueInteger(int32) 9 | test_value.parse(0xFFAAFFAA) 10 | 11 | for flag in active_flags: 12 | address = proctal_cli.allocate(guinea.pid(), test_value.size(), permission=flag) 13 | 14 | proctal_cli.write(guinea.pid(), address, int32, test_value) 15 | 16 | searcher = proctal_cli.search(guinea.pid(), int32, eq=test_value) 17 | 18 | found = False 19 | 20 | for match in searcher.match_iterator(): 21 | if match.address.cmp(address) == 0: 22 | found = True 23 | 24 | if not found: 25 | searcher.stop() 26 | exit("Default search permissions is missing the {flag} flag.\n".format(flag=flag)) 27 | 28 | for flag in inactive_flags: 29 | address = proctal_cli.allocate(guinea.pid(), test_value.size(), permission=flag) 30 | 31 | proctal_cli.write(guinea.pid(), address, int32, test_value) 32 | 33 | searcher = proctal_cli.search(guinea.pid(), int32, eq=test_value) 34 | 35 | for match in searcher.match_iterator(): 36 | if match.address.cmp(address) == 0: 37 | searcher.stop() 38 | exit("Default search permissions should not have the {flag} flag.\n".format(flag=flag)) 39 | 40 | searcher.stop() 41 | -------------------------------------------------------------------------------- /src/cli/tests/util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(proctal_sleeper sleeper.c) 2 | always_build(proctal_sleeper) 3 | 4 | add_executable(proctal_spit-back-mt spit-back-mt.c) 5 | always_build(proctal_spit-back-mt) 6 | target_link_libraries(proctal_spit-back-mt pthread) 7 | 8 | add_executable(proctal_read-mem read-mem.c) 9 | always_build(proctal_read-mem) 10 | 11 | add_executable(proctal_read-mem-mt read-mem-mt.c) 12 | target_link_libraries(proctal_read-mem-mt pthread) 13 | always_build(proctal_read-mem-mt) 14 | -------------------------------------------------------------------------------- /src/cli/tests/util/algorithms.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | def all_combinations(iterable, length=None): 4 | """Similar to the combinations function in itertools but includes 5 | combinations up to and including the length. 6 | """ 7 | if length == None: 8 | length = len(iterable) 9 | 10 | return itertools.chain(*map(lambda x: itertools.combinations(iterable, x), range(1, length + 1))) 11 | -------------------------------------------------------------------------------- /src/cli/tests/util/read-mem-mt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static void *t2_fun(void *arg) 6 | { 7 | volatile unsigned long long var = 1; 8 | 9 | setvbuf(stdout, NULL, _IONBF, 0); 10 | 11 | fprintf(stdout, "%p\n", &var); 12 | 13 | for (; var;) { 14 | } 15 | 16 | return NULL; 17 | } 18 | 19 | int main(void) 20 | { 21 | pthread_t t2; 22 | 23 | pthread_create(&t2, NULL, t2_fun, NULL); 24 | 25 | pthread_join(t2, NULL); 26 | } 27 | -------------------------------------------------------------------------------- /src/cli/tests/util/read-mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) 5 | { 6 | volatile unsigned long long var = 1; 7 | 8 | setvbuf(stdout, NULL, _IONBF, 0); 9 | 10 | fprintf(stdout, "%p\n", &var); 11 | 12 | for (; var;) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cli/tests/util/read_mem.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import select 3 | 4 | exe = "./util/read-mem" 5 | 6 | class Proc: 7 | """Controls the program.""" 8 | 9 | def __init__(self, process): 10 | self.process = process 11 | self._address = self.process.stdout.readline().strip().decode("utf-8") 12 | 13 | def stop(self): 14 | """Stops the program.""" 15 | self.process.terminate() 16 | self.process.wait() 17 | 18 | def stopped(self): 19 | """Stops whether the program has stopped.""" 20 | self.process.poll() 21 | return self.process.returncode != None 22 | 23 | def wait_stop(self, timeout): 24 | """Waits for the program to stop.""" 25 | try: 26 | self.process.wait(timeout / 1000) 27 | except subprocess.TimeoutExpired: 28 | return 29 | 30 | def address(self): 31 | """Returns the address of the variable that is being read.""" 32 | return self._address 33 | 34 | def pid(self): 35 | """Returns the process id (PID) of the program.""" 36 | return self.process.pid 37 | 38 | def __enter__(self): 39 | return self 40 | 41 | def __exit__(self, exc_type, exc_value, traceback): 42 | self.stop() 43 | 44 | def run(): 45 | """Runs the program and returns an object that can communicate with it.""" 46 | process = subprocess.Popen([exe], stdout=subprocess.PIPE) 47 | return Proc(process) 48 | -------------------------------------------------------------------------------- /src/cli/tests/util/read_mem_mt.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import select 3 | 4 | exe = "./util/read-mem-mt" 5 | 6 | class Proc: 7 | """Controls the program.""" 8 | 9 | def __init__(self, process): 10 | self.process = process 11 | self._address = self.process.stdout.readline().strip().decode("utf-8") 12 | 13 | def stop(self): 14 | """Stops the program.""" 15 | self.process.terminate() 16 | self.process.wait() 17 | 18 | def address(self): 19 | """Returns the address of the variable that is being read.""" 20 | return self._address 21 | 22 | def pid(self): 23 | """Returns the process id (PID) of the program.""" 24 | return self.process.pid 25 | 26 | def __enter__(self): 27 | return self 28 | 29 | def __exit__(self, exc_type, exc_value, traceback): 30 | self.stop() 31 | 32 | def run(): 33 | """Runs the program and returns an object that can communicate with it.""" 34 | process = subprocess.Popen([exe], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 35 | return Proc(process) 36 | -------------------------------------------------------------------------------- /src/cli/tests/util/sleeper.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) 4 | { 5 | for (;;) { 6 | sleep(60); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/cli/tests/util/sleeper.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import select 3 | 4 | exe = "./util/sleeper" 5 | 6 | class Proc: 7 | """Controls the program.""" 8 | 9 | def __init__(self, process): 10 | self.process = process 11 | 12 | def stop(self): 13 | """Stops the program.""" 14 | self.process.terminate() 15 | self.process.wait() 16 | 17 | def pid(self): 18 | """Returns the process id (PID) of the program.""" 19 | return self.process.pid 20 | 21 | def read_line(self): 22 | """Reads the next available line.""" 23 | return self.process.stdout.readline().decode("utf-8") 24 | 25 | def __enter__(self): 26 | return self 27 | 28 | def __exit__(self, exc_type, exc_value, traceback): 29 | self.stop() 30 | 31 | def run(): 32 | """Runs the program and returns an object that can communicate with it.""" 33 | process = subprocess.Popen([exe], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 34 | return Proc(process) 35 | -------------------------------------------------------------------------------- /src/cli/tests/util/spit-back-mt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static void *t2_fun(void *arg) 6 | { 7 | char buf[1]; 8 | 9 | setvbuf(stdin, NULL, _IONBF, 0); 10 | setvbuf(stdout, NULL, _IONBF, 0); 11 | 12 | for (;;) { 13 | size_t read = fread(buf, 1, sizeof(buf), stdin); 14 | 15 | if (read) { 16 | fwrite(buf, 1, read, stdout); 17 | } 18 | 19 | if (feof(stdin)) { 20 | return NULL; 21 | } 22 | } 23 | } 24 | 25 | int main(void) 26 | { 27 | pthread_t t2; 28 | 29 | pthread_create(&t2, NULL, t2_fun, NULL); 30 | 31 | pthread_join(t2, NULL); 32 | } 33 | -------------------------------------------------------------------------------- /src/cli/tests/util/spit_back_mt.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import select 3 | 4 | exe = "./util/spit-back-mt" 5 | 6 | class Proc: 7 | """Controls the program.""" 8 | def __init__(self, process): 9 | self.process = process 10 | 11 | def stop(self): 12 | """Stops the program.""" 13 | self.process.terminate() 14 | self.process.wait() 15 | 16 | def ping(self): 17 | """Checks whether the program is responsive.""" 18 | expected_response = "ping\n".encode() 19 | 20 | self.process.stdin.write(expected_response) 21 | self.process.stdin.flush() 22 | 23 | poll = select.poll() 24 | poll.register(self.process.stdout, select.POLLIN) 25 | 26 | if poll.poll(33): 27 | response = self.process.stdout.read(len(expected_response)) 28 | return response == expected_response 29 | else: 30 | return False 31 | 32 | def pid(self): 33 | """Returns the process id (PID) of the program.""" 34 | return self.process.pid 35 | 36 | def __enter__(self): 37 | return self 38 | 39 | def __exit__(self, exc_type, exc_value, traceback): 40 | self.stop() 41 | 42 | def run(): 43 | """Runs the program and returns an object that can communicate with it.""" 44 | process = subprocess.Popen([exe], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 45 | return Proc(process) 46 | -------------------------------------------------------------------------------- /src/cli/tests/watch-multiple-threads.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, read_mem_mt 2 | 3 | with read_mem_mt.run() as guinea: 4 | watcher = proctal_cli.watch(guinea.pid(), guinea.address(), "rw") 5 | 6 | try: 7 | if not watcher.wait_match(100): 8 | exit("Was unable to watch for reads in a thread that is not the main one.") 9 | finally: 10 | watcher.stop() 11 | -------------------------------------------------------------------------------- /src/cli/tests/watch-range.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, read_mem 2 | 3 | class Error(Exception): 4 | pass 5 | 6 | class ExpectedMatch(Error): 7 | def __init__(self): 8 | super().__init__("Expected a match.") 9 | 10 | class UnepectedMatch(Error): 11 | def __init__(self): 12 | super().__init__("Did not expect a match.") 13 | 14 | with read_mem.run() as guinea: 15 | watch_address = guinea.address() 16 | 17 | try: 18 | watcher = proctal_cli.watch(guinea.pid(), watch_address, watch="rw") 19 | 20 | if not watcher.wait_match(100): 21 | exit("Was unable to watch for reads.") 22 | 23 | instruction_address = watcher.next_match().address; 24 | finally: 25 | watcher.stop() 26 | 27 | def make_smallest_range(): 28 | """Smallest range of addresses.""" 29 | start_address = instruction_address.clone() 30 | stop_address = instruction_address.clone() 31 | stop_address.add_address_offset(1) 32 | 33 | return [start_address, stop_address, True] 34 | 35 | tests = [ 36 | make_smallest_range(), 37 | ] 38 | 39 | def run(start_address, stop_address, expect_match): 40 | watcher = proctal_cli.watch( 41 | guinea.pid(), 42 | watch_address, 43 | address_start=start_address, 44 | address_stop=stop_address, 45 | watch="rw") 46 | 47 | try: 48 | if watcher.wait_match(100): 49 | if not expect_match: 50 | raise UnexpectedMatch() 51 | else: 52 | if expect_match: 53 | raise ExpectedMatch() 54 | finally: 55 | watcher.stop() 56 | 57 | for test in tests: 58 | run(test[0], test[1], test[2]) 59 | -------------------------------------------------------------------------------- /src/cli/tests/watch-uncaught-trap-bug.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, read_mem 2 | 3 | with read_mem.run() as guinea: 4 | watch_address = guinea.address() 5 | 6 | watcher = proctal_cli.watch(guinea.pid(), watch_address, watch="rw", unique=True) 7 | 8 | try: 9 | if not watcher.wait_match(100): 10 | exit("Was supposed to have at least 1 match.") 11 | finally: 12 | watcher.stop() 13 | 14 | # If a trap was not caught, the program will stop. 15 | guinea.wait_stop(200) 16 | 17 | if guinea.stopped(): 18 | exit("Program died.") 19 | -------------------------------------------------------------------------------- /src/cli/tests/watch-unique.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, read_mem 2 | 3 | with read_mem.run() as guinea: 4 | watch_address = guinea.address() 5 | 6 | watcher = proctal_cli.watch(guinea.pid(), watch_address, watch="rw", unique=True) 7 | 8 | try: 9 | if not watcher.wait_match(100): 10 | exit("Was supposed to have at least 1 match.") 11 | 12 | watcher.next_match() 13 | 14 | if watcher.wait_match(100): 15 | exit("Was not supposed to get more than 1 match.") 16 | finally: 17 | watcher.stop() 18 | -------------------------------------------------------------------------------- /src/cli/tests/write-binary.py: -------------------------------------------------------------------------------- 1 | from util import proctal_cli, sleeper 2 | 3 | class Error(Exception): 4 | pass 5 | 6 | class TestSingleValue: 7 | def __init__(self, type, value): 8 | self.type = type 9 | self.value = value 10 | pass 11 | 12 | def run(self, guinea): 13 | address = proctal_cli.allocate(guinea.pid(), self.value.size()) 14 | 15 | try: 16 | writer = proctal_cli.write(guinea.pid(), address, self.type, binary=True) 17 | 18 | try: 19 | writer.write_value(self.value) 20 | writer.stop() 21 | 22 | reader = proctal_cli.read(guinea.pid(), address, self.type) 23 | 24 | try: 25 | value = reader.next_value() 26 | 27 | if self.value.cmp(value) != 0: 28 | raise Error("Expected {expected} but got {found}.".format(expected=self.value, found=value)) 29 | finally: 30 | reader.stop() 31 | finally: 32 | writer.stop() 33 | finally: 34 | proctal_cli.deallocate(guinea.pid(), address) 35 | 36 | int32 = proctal_cli.TypeInteger(32); 37 | int32_test_val = proctal_cli.ValueInteger(int32) 38 | int32_test_val.parse(0x0ACC23AA) 39 | 40 | tests = [ 41 | TestSingleValue(int32, int32_test_val) 42 | ] 43 | 44 | with sleeper.run() as guinea: 45 | for test in tests: 46 | test.run(guinea) 47 | -------------------------------------------------------------------------------- /src/cli/val/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(tests) 2 | 3 | add_library( 4 | proctal_cli_val 5 | 6 | OBJECT 7 | 8 | integer.h 9 | integer.c 10 | integer-sign-unsigned.c 11 | integer-sign-signed.c 12 | integer-endianness.c 13 | ieee754.h 14 | ieee754.c 15 | address.h 16 | address.c 17 | byte.h 18 | byte.c 19 | text.h 20 | text.c 21 | text-encoding-ascii.c 22 | assembler.h 23 | assembler.c 24 | filter.h 25 | filter.c 26 | x86.h 27 | x86.c 28 | arm.h 29 | arm.c 30 | sparc.h 31 | sparc.c 32 | powerpc.h 33 | powerpc.c 34 | mips.h 35 | mips.c 36 | val.h 37 | val.c 38 | ) 39 | -------------------------------------------------------------------------------- /src/cli/val/address.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/address.h" 2 | 3 | extern inline size_t cli_val_address_alignof(void); 4 | 5 | extern inline size_t cli_val_address_sizeof(void); 6 | 7 | extern inline struct cli_val_address *cli_val_address_create(void); 8 | 9 | extern inline void cli_val_address_destroy(struct cli_val_address *v); 10 | 11 | extern inline void *cli_val_address_data(struct cli_val_address *v); 12 | 13 | extern inline int cli_val_address_parse_binary(struct cli_val_address *v, const void *b, size_t length); 14 | 15 | extern inline int cli_val_address_cmp(struct cli_val_address *v, struct cli_val_address *other_v); 16 | 17 | extern inline struct cli_val_address *cli_val_address_create_clone(struct cli_val_address *other_v); 18 | 19 | int cli_val_address_print(struct cli_val_address *v, FILE *f) 20 | { 21 | return fprintf(f, "%" PRIXPTR, v->address); 22 | } 23 | 24 | int cli_val_address_scan(struct cli_val_address *v, FILE *f) 25 | { 26 | return fscanf(f, "%" PRIXPTR, &v->address) == 1 ? 1 : 0; 27 | } 28 | 29 | int cli_val_address_parse_text(struct cli_val_address *v, const char *s) 30 | { 31 | return sscanf(s, "%" PRIXPTR, &v->address) == 1 ? 1 : 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/cli/val/arm.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/arm.h" 2 | 3 | extern inline void cli_val_arm_attr_init(struct cli_val_arm_attr *a); 4 | 5 | extern inline void cli_val_arm_attr_mode_set(struct cli_val_arm_attr *a, enum cli_val_arm_mode mode); 6 | 7 | extern inline void cli_val_arm_attr_endianness_set(struct cli_val_arm_attr *a, enum cli_val_arm_endianness endianness); 8 | 9 | extern inline void cli_val_arm_attr_deinit(struct cli_val_arm_attr *a); 10 | 11 | extern inline struct cli_val_arm *cli_val_arm_create(struct cli_val_arm_attr *a); 12 | 13 | extern inline void cli_val_arm_destroy(struct cli_val_arm *v); 14 | 15 | extern inline void cli_val_arm_address_set(struct cli_val_arm *v, void *address); 16 | 17 | extern inline void *cli_val_arm_address(struct cli_val_arm *v); 18 | 19 | extern inline void *cli_val_arm_data(struct cli_val_arm *v); 20 | 21 | extern inline size_t cli_val_arm_sizeof(struct cli_val_arm *v); 22 | 23 | extern inline struct cli_val_arm *cli_val_arm_create_clone(struct cli_val_arm *other_v); 24 | 25 | extern inline int cli_val_arm_print(struct cli_val_arm *v, FILE *f); 26 | 27 | extern inline int cli_val_arm_parse_binary(struct cli_val_arm *v, const void *b, size_t length); 28 | 29 | extern inline int cli_val_arm_parse_text(struct cli_val_arm *v, const char *s); 30 | -------------------------------------------------------------------------------- /src/cli/val/byte.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/byte.h" 2 | 3 | extern inline struct cli_val_byte *cli_val_byte_create(void); 4 | 5 | extern inline struct cli_val_byte *cli_val_byte_create_clone(struct cli_val_byte *v); 6 | 7 | extern inline void cli_val_byte_destroy(struct cli_val_byte *v); 8 | 9 | extern inline void *cli_val_byte_data(struct cli_val_byte *v); 10 | 11 | extern inline int cli_val_byte_parse_binary(struct cli_val_byte *v, const void *b, size_t length); 12 | 13 | extern inline int cli_val_byte_add(struct cli_val_byte *v, struct cli_val_byte *other_v); 14 | 15 | extern inline int cli_val_byte_sub(struct cli_val_byte *v, struct cli_val_byte *other_v); 16 | 17 | extern inline int cli_val_byte_cmp(struct cli_val_byte *v, struct cli_val_byte *other_v); 18 | 19 | extern inline struct cli_val_byte *cli_val_byte_create_clone(struct cli_val_byte *other_v); 20 | 21 | int cli_val_byte_print(struct cli_val_byte *v, FILE *f) 22 | { 23 | return fprintf(f, "%02X", v->byte); 24 | } 25 | 26 | int cli_val_byte_scan(struct cli_val_byte *v, FILE *f) 27 | { 28 | return fscanf(f, "%hhx", &v->byte) == 1 ? 1 : 0; 29 | } 30 | 31 | int cli_val_byte_parse_text(struct cli_val_byte *v, const char *s) 32 | { 33 | return sscanf(s, "%hhx", &v->byte) == 1 ? 1 : 0; 34 | } 35 | -------------------------------------------------------------------------------- /src/cli/val/filter.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/filter.h" 2 | 3 | extern inline int cli_val_filter_compare(struct cli_val_filter_compare_arg *arg, cli_val_t value); 4 | 5 | extern inline int cli_val_filter_compare_prev(struct cli_val_filter_compare_prev_arg *arg, cli_val_t curr, cli_val_t prev); 6 | -------------------------------------------------------------------------------- /src/cli/val/integer-endianness.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.h" 4 | #include "cli/val/integer.h" 5 | 6 | static void reverse_bytes(void *p, size_t n) 7 | { 8 | unsigned char tmp; 9 | 10 | char *low = p; 11 | char *high = low + n - 1; 12 | 13 | while (high > low) 14 | { 15 | tmp = *low; 16 | *low++ = *high; 17 | *high-- = tmp; 18 | } 19 | } 20 | 21 | void cli_val_integer_endianness_convert(struct cli_val_integer *v) 22 | { 23 | #ifdef PROCTAL_INTEGER_ENDIANNESS_LITTLE 24 | if (v->attr.endianness == CLI_VAL_INTEGER_ENDIANNESS_BIG) { 25 | reverse_bytes(v->data, cli_val_integer_sizeof(v)); 26 | } 27 | #elif defined PROCTAL_INTEGER_ENDIANNESS_BIG 28 | if (v->attr.endianness == CLI_VAL_INTEGER_ENDIANNESS_LITTLE) { 29 | reverse_bytes(v->data, cli_val_integer_sizeof(v)); 30 | } 31 | #else 32 | #error "Unknown integer endianness." 33 | #endif 34 | } 35 | 36 | void cli_val_integer_endianness_revert(struct cli_val_integer *v) 37 | { 38 | // Basically the same behavior. 39 | cli_val_integer_endianness_convert(v); 40 | } 41 | -------------------------------------------------------------------------------- /src/cli/val/mips.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/mips.h" 2 | 3 | extern inline void cli_val_mips_attr_init(struct cli_val_mips_attr *a); 4 | 5 | extern inline void cli_val_mips_attr_mode_set(struct cli_val_mips_attr *a, enum cli_val_mips_mode mode); 6 | 7 | extern inline void cli_val_mips_attr_endianness_set(struct cli_val_mips_attr *a, enum cli_val_mips_endianness endianness); 8 | 9 | extern inline void cli_val_mips_attr_deinit(struct cli_val_mips_attr *a); 10 | 11 | extern inline struct cli_val_mips *cli_val_mips_create(struct cli_val_mips_attr *a); 12 | 13 | extern inline void cli_val_mips_destroy(struct cli_val_mips *v); 14 | 15 | extern inline void cli_val_mips_address_set(struct cli_val_mips *v, void *address); 16 | 17 | extern inline void *cli_val_mips_address(struct cli_val_mips *v); 18 | 19 | extern inline void *cli_val_mips_data(struct cli_val_mips *v); 20 | 21 | extern inline size_t cli_val_mips_sizeof(struct cli_val_mips *v); 22 | 23 | extern inline struct cli_val_mips *cli_val_mips_create_clone(struct cli_val_mips *other_v); 24 | 25 | extern inline int cli_val_mips_print(struct cli_val_mips *v, FILE *f); 26 | 27 | extern inline int cli_val_mips_parse_binary(struct cli_val_mips *v, const void *b, size_t length); 28 | 29 | extern inline int cli_val_mips_parse_text(struct cli_val_mips *v, const char *s); 30 | -------------------------------------------------------------------------------- /src/cli/val/powerpc.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/powerpc.h" 2 | 3 | extern inline void cli_val_powerpc_attr_init(struct cli_val_powerpc_attr *a); 4 | 5 | extern inline void cli_val_powerpc_attr_mode_set(struct cli_val_powerpc_attr *a, enum cli_val_powerpc_mode mode); 6 | 7 | extern inline void cli_val_powerpc_attr_endianness_set(struct cli_val_powerpc_attr *a, enum cli_val_powerpc_endianness endianness); 8 | 9 | extern inline void cli_val_powerpc_attr_deinit(struct cli_val_powerpc_attr *a); 10 | 11 | extern inline struct cli_val_powerpc *cli_val_powerpc_create(struct cli_val_powerpc_attr *a); 12 | 13 | extern inline void cli_val_powerpc_destroy(struct cli_val_powerpc *v); 14 | 15 | extern inline void cli_val_powerpc_address_set(struct cli_val_powerpc *v, void *address); 16 | 17 | extern inline void *cli_val_powerpc_address(struct cli_val_powerpc *v); 18 | 19 | extern inline void *cli_val_powerpc_data(struct cli_val_powerpc *v); 20 | 21 | extern inline size_t cli_val_powerpc_sizeof(struct cli_val_powerpc *v); 22 | 23 | extern inline struct cli_val_powerpc *cli_val_powerpc_create_clone(struct cli_val_powerpc *other_v); 24 | 25 | extern inline int cli_val_powerpc_print(struct cli_val_powerpc *v, FILE *f); 26 | 27 | extern inline int cli_val_powerpc_parse_binary(struct cli_val_powerpc *v, const void *b, size_t length); 28 | 29 | extern inline int cli_val_powerpc_parse_text(struct cli_val_powerpc *v, const char *s); 30 | -------------------------------------------------------------------------------- /src/cli/val/sparc.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/sparc.h" 2 | 3 | extern inline void cli_val_sparc_attr_init(struct cli_val_sparc_attr *a); 4 | 5 | extern inline void cli_val_sparc_attr_mode_set(struct cli_val_sparc_attr *a, enum cli_val_sparc_mode mode); 6 | 7 | extern inline void cli_val_sparc_attr_endianness_set(struct cli_val_sparc_attr *a, enum cli_val_sparc_endianness endianness); 8 | 9 | extern inline void cli_val_sparc_attr_deinit(struct cli_val_sparc_attr *a); 10 | 11 | extern inline struct cli_val_sparc *cli_val_sparc_create(struct cli_val_sparc_attr *a); 12 | 13 | extern inline void cli_val_sparc_destroy(struct cli_val_sparc *v); 14 | 15 | extern inline void cli_val_sparc_address_set(struct cli_val_sparc *v, void *address); 16 | 17 | extern inline void *cli_val_sparc_address(struct cli_val_sparc *v); 18 | 19 | extern inline void *cli_val_sparc_data(struct cli_val_sparc *v); 20 | 21 | extern inline size_t cli_val_sparc_sizeof(struct cli_val_sparc *v); 22 | 23 | extern inline struct cli_val_sparc *cli_val_sparc_create_clone(struct cli_val_sparc *other_v); 24 | 25 | extern inline int cli_val_sparc_print(struct cli_val_sparc *v, FILE *f); 26 | 27 | extern inline int cli_val_sparc_parse_binary(struct cli_val_sparc *v, const void *b, size_t length); 28 | 29 | extern inline int cli_val_sparc_parse_text(struct cli_val_sparc *v, const char *s); 30 | -------------------------------------------------------------------------------- /src/cli/val/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_cli_val_${file} ${file}.c) 3 | target_link_libraries(proctal_cli_val_${file} PRIVATE proctal_cli_val proctal_cli_assembler proctal_cli_parser proctal_otrap) 4 | add_test(NAME proctal_cli_val_${file} COMMAND proctal_cli_val_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(add) 8 | test_single_c_file(cmp) 9 | test_single_c_file(filter-compare-prev) 10 | test_single_c_file(filter-compare) 11 | test_single_c_file(integer-endianness) 12 | test_single_c_file(parse-bin-invalid-ascii) 13 | test_single_c_file(parse-bin-valid-ascii) 14 | test_single_c_file(parse-invalid-ascii) 15 | test_single_c_file(parse-valid-ascii) 16 | test_single_c_file(parse) 17 | test_single_c_file(print) 18 | test_single_c_file(sub) 19 | -------------------------------------------------------------------------------- /src/cli/val/tests/integer-endianness.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "otrap/otrap.h" 6 | #include "cli/val/integer.h" 7 | 8 | static int expect_print(struct cli_val_integer *v, const char *expected) 9 | { 10 | int ret = 0; 11 | 12 | struct otrap otrap; 13 | otrap_init(&otrap); 14 | 15 | cli_val_integer_print(v, otrap_file(&otrap)); 16 | 17 | char output[255]; 18 | size_t output_size = otrap_read(&otrap, output, sizeof(output)); 19 | 20 | if (output_size != strlen(expected) 21 | || memcmp(output, expected, output_size) != 0) { 22 | fprintf(stderr, "Expected %s, but instead got ", expected); 23 | fwrite(output, output_size, 1, stderr); 24 | fprintf(stderr, ".\n"); 25 | goto exit1; 26 | } 27 | 28 | ret = 1; 29 | exit1: 30 | otrap_deinit(&otrap); 31 | exit0: 32 | return ret; 33 | } 34 | 35 | int main(void) 36 | { 37 | int ret = 1; 38 | 39 | uint16_t integer = 0; 40 | unsigned char *byte = (unsigned char *) &integer; 41 | 42 | struct cli_val_integer_attr a; 43 | cli_val_integer_attr_init(&a); 44 | cli_val_integer_attr_bits_set(&a, CLI_VAL_INTEGER_BITS_16); 45 | 46 | struct cli_val_integer *v; 47 | 48 | cli_val_integer_attr_endianness_set(&a, CLI_VAL_INTEGER_ENDIANNESS_LITTLE); 49 | v = cli_val_integer_create(&a); 50 | byte[0] = 0x01; 51 | byte[1] = 0x00; 52 | 53 | if (!cli_val_integer_parse_binary(v, (const char *) &integer, sizeof(integer)) != 0) { 54 | fprintf(stderr, "Failed to parse integer.\n"); 55 | goto exit2; 56 | } 57 | 58 | if (!expect_print(v, "1")) { 59 | goto exit2; 60 | } 61 | 62 | cli_val_integer_destroy(v); 63 | 64 | cli_val_integer_attr_endianness_set(&a, CLI_VAL_INTEGER_ENDIANNESS_BIG); 65 | v = cli_val_integer_create(&a); 66 | byte[0] = 0x00; 67 | byte[1] = 0x01; 68 | 69 | if (!cli_val_integer_parse_binary(v, (const char *) &integer, sizeof(integer)) != 0) { 70 | fprintf(stderr, "Failed to parse integer.\n"); 71 | goto exit2; 72 | } 73 | 74 | if (!expect_print(v, "1")) { 75 | goto exit2; 76 | } 77 | 78 | ret = 0; 79 | exit2: 80 | cli_val_integer_destroy(v); 81 | exit1: 82 | cli_val_integer_attr_deinit(&a); 83 | exit0: 84 | return ret; 85 | } 86 | -------------------------------------------------------------------------------- /src/cli/val/tests/parse-bin-invalid-ascii.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cli/val/text.h" 5 | 6 | int main(void) 7 | { 8 | char characters[128]; 9 | 10 | for (size_t i = 0; i < 128; ++i) { 11 | characters[i] = i + 128; 12 | } 13 | 14 | struct cli_val_text_attr a; 15 | cli_val_text_attr_init(&a); 16 | cli_val_text_attr_encoding_set(&a, CLI_VAL_TEXT_ENCODING_ASCII); 17 | struct cli_val_text *v = cli_val_text_create(&a); 18 | cli_val_text_attr_deinit(&a); 19 | 20 | for (size_t i = 0; i < 128; ++i) { 21 | if (cli_val_text_parse_binary(v, &characters[i], 1) != 0) { 22 | fprintf(stderr, "cli_val_text_parse_binary accepted character #%lu\n", i + 1); 23 | cli_val_text_destroy(v); 24 | return 1; 25 | } 26 | } 27 | 28 | cli_val_text_destroy(v); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/val/tests/parse-bin-valid-ascii.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cli/val/text.h" 5 | 6 | int main(void) 7 | { 8 | char characters[128]; 9 | 10 | for (size_t i = 0; i < 128; ++i) { 11 | characters[i] = i; 12 | } 13 | 14 | struct cli_val_text_attr a; 15 | cli_val_text_attr_init(&a); 16 | cli_val_text_attr_encoding_set(&a, CLI_VAL_TEXT_ENCODING_ASCII); 17 | struct cli_val_text *v = cli_val_text_create(&a); 18 | cli_val_text_attr_deinit(&a); 19 | 20 | for (size_t i = 0; i < 128; ++i) { 21 | if (cli_val_text_parse_binary(v, &characters[i], 1) != 1) { 22 | fprintf(stderr, "cli_val_text_parse_binary failed on character #%lu\n", i + 1); 23 | cli_val_text_destroy(v); 24 | return 1; 25 | } 26 | } 27 | 28 | cli_val_text_destroy(v); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/val/tests/parse-invalid-ascii.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cli/val/text.h" 5 | 6 | int main(void) 7 | { 8 | char strings[129 * 2] = { '\0' }; 9 | 10 | for (size_t i = 0; i < 128; ++i) { 11 | strings[(i + 1) * 2] = i + 128; 12 | } 13 | 14 | struct cli_val_text_attr a; 15 | cli_val_text_attr_init(&a); 16 | cli_val_text_attr_encoding_set(&a, CLI_VAL_TEXT_ENCODING_ASCII); 17 | struct cli_val_text *v = cli_val_text_create(&a); 18 | cli_val_text_attr_deinit(&a); 19 | 20 | for (size_t i = 0; i < 129; ++i) { 21 | if (cli_val_text_parse_text(v, &strings[i * 2]) != 0) { 22 | fprintf(stderr, "cli_val_text_parse accepted string #%lu\n", i + 1); 23 | cli_val_text_destroy(v); 24 | return 1; 25 | } 26 | } 27 | 28 | cli_val_text_destroy(v); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/val/tests/parse-valid-ascii.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cli/val/text.h" 5 | 6 | int main(void) 7 | { 8 | char strings[127 * 2] = { '\0' }; 9 | 10 | for (size_t i = 0; i < 127; ++i) { 11 | strings[i * 2] = i + 1; 12 | } 13 | 14 | struct cli_val_text_attr a; 15 | cli_val_text_attr_init(&a); 16 | cli_val_text_attr_encoding_set(&a, CLI_VAL_TEXT_ENCODING_ASCII); 17 | struct cli_val_text *v = cli_val_text_create(&a); 18 | cli_val_text_attr_deinit(&a); 19 | 20 | for (size_t i = 0; i < 127; ++i) { 21 | if (cli_val_text_parse_text(v, &strings[i * 2]) != 1) { 22 | fprintf(stderr, "cli_val_text_parse failed on string #%lu\n", i + 1); 23 | cli_val_text_destroy(v); 24 | return 1; 25 | } 26 | } 27 | 28 | cli_val_text_destroy(v); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/val/text-encoding-ascii.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "magic/magic.h" 4 | #include "cli/val/text.h" 5 | 6 | size_t cli_val_text_ascii_sizeof(struct cli_val_text *v) 7 | { 8 | return 1; 9 | } 10 | 11 | int cli_val_text_ascii_cmp(struct cli_val_text *v, struct cli_val_text *other_v) 12 | { 13 | return COMPARE_INT(DEREF(char, v->data), DEREF(char, other_v->data)); 14 | } 15 | 16 | int cli_val_text_ascii_print(struct cli_val_text *v, FILE *f) 17 | { 18 | return fprintf(f, "%c", DEREF(char, v->data)); 19 | } 20 | 21 | int cli_val_text_ascii_scan(struct cli_val_text *v, FILE *f) 22 | { 23 | return fscanf(f, "%c", v->data) == 1 ? 1 : 0; 24 | } 25 | 26 | int cli_val_text_ascii_parse_text(struct cli_val_text *v, const char *s) 27 | { 28 | if (*s == '\0') { 29 | // End of the string. 30 | return 0; 31 | } 32 | 33 | if ((unsigned char) *s > 127) { 34 | // Not a valid ASCII character. 35 | return 0; 36 | } 37 | 38 | DEREF(char, v->data) = *s; 39 | 40 | return 1; 41 | } 42 | 43 | int cli_val_text_ascii_parse_binary(struct cli_val_text *v, const void *b, size_t length) 44 | { 45 | if (length == 0) { 46 | return 0; 47 | } 48 | 49 | if (DEREF(unsigned char, b) > 127) { 50 | // Not a valid ASCII character. 51 | return 0; 52 | } 53 | 54 | DEREF(char, v->data) = DEREF(char, b); 55 | 56 | return 1; 57 | } 58 | -------------------------------------------------------------------------------- /src/cli/val/x86.c: -------------------------------------------------------------------------------- 1 | #include "cli/val/x86.h" 2 | 3 | extern inline void cli_val_x86_attr_init(struct cli_val_x86_attr *a); 4 | 5 | extern inline void cli_val_x86_attr_mode_set(struct cli_val_x86_attr *a, enum cli_val_x86_mode mode); 6 | 7 | extern inline void cli_val_x86_attr_syntax_set(struct cli_val_x86_attr *a, enum cli_val_x86_syntax syntax); 8 | 9 | extern inline void cli_val_x86_attr_deinit(struct cli_val_x86_attr *a); 10 | 11 | extern inline struct cli_val_x86 *cli_val_x86_create(struct cli_val_x86_attr *a); 12 | 13 | extern inline void cli_val_x86_destroy(struct cli_val_x86 *v); 14 | 15 | extern inline void cli_val_x86_address_set(struct cli_val_x86 *v, void *address); 16 | 17 | extern inline void *cli_val_x86_address(struct cli_val_x86 *v); 18 | 19 | extern inline void *cli_val_x86_data(struct cli_val_x86 *v); 20 | 21 | extern inline size_t cli_val_x86_sizeof(struct cli_val_x86 *v); 22 | 23 | extern inline struct cli_val_x86 *cli_val_x86_create_clone(struct cli_val_x86 *other_v); 24 | 25 | extern inline int cli_val_x86_print(struct cli_val_x86 *v, FILE *f); 26 | 27 | extern inline int cli_val_x86_parse_binary(struct cli_val_x86 *v, const void *b, size_t length); 28 | 29 | extern inline int cli_val_x86_parse_text(struct cli_val_x86 *v, const char *s); 30 | -------------------------------------------------------------------------------- /src/cli/vmagazine.c: -------------------------------------------------------------------------------- 1 | #include "cli/vmagazine.h" 2 | 3 | extern inline enum vmagazine_result vmagazine_init(struct vmagazine *this); 4 | 5 | extern inline void vmagazine_deinit(struct vmagazine *this); 6 | 7 | extern inline void vmagazine_template_address_set(struct vmagazine *this, void *address); 8 | 9 | extern inline void vmagazine_template_value_set(struct vmagazine *this, cli_val_t value); 10 | 11 | extern inline size_t vmagazine_size(struct vmagazine *this); 12 | 13 | extern inline cli_val_t *vmagazine_value(struct vmagazine *this, size_t index); 14 | 15 | extern inline enum vmagazine_result vmagazine_parse_text(struct vmagazine *this, const char *str, size_t length); 16 | 17 | extern inline enum vmagazine_result vmagazine_parse_binary(struct vmagazine *this, const void *binary, size_t size, size_t *read); 18 | -------------------------------------------------------------------------------- /src/cli/yuck/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | m4_preprocess(args.yuck.m4) 2 | yuck_compile(args.yuck) 3 | yuck_manpage(proctal.1 args.yuck) 4 | 5 | add_custom_target(proctal_compile_yuck ALL DEPENDS args.yucc proctal.1) 6 | 7 | add_library( 8 | proctal_cli_yuck 9 | 10 | OBJECT 11 | 12 | main.h 13 | main.c 14 | ) 15 | 16 | add_dependencies(proctal_cli_yuck proctal_compile_yuck) 17 | 18 | target_link_libraries(proctal_cli_yuck darr) 19 | -------------------------------------------------------------------------------- /src/cli/yuck/main.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_YUCK_MAIN 2 | #define CLI_YUCK_MAIN 3 | 4 | int cli_yuck_main(int argc, char **argv); 5 | 6 | #endif /* CLI_YUCK_MAIN */ 7 | -------------------------------------------------------------------------------- /src/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #define PROCTAL_VERSION @Proctal_VERSION@ 5 | #define PROCTAL_META_DIR_SRC "@CMAKE_SOURCE_DIR@/src" 6 | 7 | #cmakedefine PROCTAL_PLATFORM_LINUX 8 | #cmakedefine PROCTAL_PLATFORM_WINDOWS 9 | #cmakedefine PROCTAL_PLATFORM_UNKNOWN 10 | 11 | #cmakedefine PROCTAL_INTEGER_ENDIANNESS_BIG 12 | #cmakedefine PROCTAL_INTEGER_ENDIANNESS_LITTLE 13 | 14 | #cmakedefine PROCTAL_HAS_KEYSTONE 15 | 16 | #cmakedefine PROCTAL_HAS_CAPSTONE 17 | 18 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_X86 19 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_X86_MODE_32 20 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_X86_MODE_64 21 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_ARM 22 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_ARM_MODE_A32 23 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_ARM_MODE_A64 24 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_SPARC 25 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_POWERPC 26 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_MIPS 27 | #cmakedefine PROCTAL_CPU_ARCHITECTURE_UNKNOWN 28 | 29 | #endif /* CONFIG_H */ 30 | -------------------------------------------------------------------------------- /src/doc/api/allocating-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Allocating memory 4 | 5 | To allocate memory in another program you must call the 6 | proctal_allocate function. It takes a handle and the 7 | number of bytes as arguments and returns an address to the start of the 8 | new memory location. 9 | 10 | 11 | void *address = proctal_allocate(proctal, 8); 12 | 13 | 14 | On failure the address will have the value NULL. Check 15 | the Error handling page to learn 16 | how to deal with an error. 17 | 18 | While the function may allocate more space than you had specified, you 19 | must only consider the number of bytes you specified as usable. 20 | 21 | Access permissions can be defined by calling the 22 | proctal_allocate_read_set, 23 | proctal_allocate_write_set and 24 | proctal_allocate_execute_set functions before 25 | proctal_allocate. By default all access permissions are 26 | set. 27 | 28 | 29 | proctal_allocate_read_set(proctal, 1); 30 | proctal_allocate_write_set(proctal, 1); 31 | proctal_allocate_execute_set(proctal, 0); 32 | 33 | 34 | To deallocate you must call the proctal_deallocate 35 | function. It takes a handle and the address to the start of the memory 36 | location as arguments. 37 | 38 | 39 | proctal_deallocate(proctal, address); 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/doc/api/api-memory-management.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | API memory management 4 | 5 | The API makes calls to malloc and free to 6 | allocate and deallocate memory, respectively, for its internal data 7 | structures. 8 | 9 | If you're the kind of programmer that wants to have complete control 10 | over every memory allocation, you can provide your own versions of 11 | malloc and free. 12 | 13 | Here's how you would tell the API to use your custom 14 | malloc and free: 15 | 16 | 17 | void *my_malloc(size_t size) 18 | { 19 | // Allocate memory however you like. 20 | } 21 | 22 | void my_free(void *memory) 23 | { 24 | // Deallocate your chunk of memory. 25 | } 26 | 27 | proctal_malloc_set(&my_malloc); 28 | proctal_free_set(&my_free); 29 | 30 | 31 | Just make sure that you do this before calling any other function of 32 | the API. You wouldn't want your version of free being 33 | called with an address returned by the wrong malloc. 34 | 35 | -------------------------------------------------------------------------------- /src/doc/api/executing-code.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Executing code 4 | 5 | You can make a program start executing your code by calling 6 | proctal_execute. 7 | 8 | The given instructions will be embedded at some place in memory and 9 | executed in a new stack frame in the context of the main thread. The 10 | other threads will be paused. Your code is free to modify any registers 11 | because they will be restored to their original values. Control will 12 | be given back to the program after the last instruction is executed. 13 | 14 | The instructions cannot rely on where they will be placed in memory. 15 | 16 | Here's how you would make a program execute 3 NO OPS on x86-64: 17 | 18 | 19 | proctal_execute(proctal, "\x90\x90\x90", 3); 20 | 21 | 22 | Check the Error handling page to 23 | learn how to deal with an error. 24 | 25 | -------------------------------------------------------------------------------- /src/doc/api/initialization-deinitialization.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Initialization / Deinitialization 4 | 5 | Handles are used by the API to keep track of state. The type of a 6 | handle is proctal_t. 7 | 8 | A handle must not be used simultaneously in multiple threads, however, 9 | it is possible to use multiple handles as long as they are used in 10 | different threads and are not accessing the same program. 11 | 12 | Handles are created by calling the proctal_open 13 | function. It takes no arguments and returns a new handle. 14 | 15 | 16 | proctal_t proctal = proctal_open(); 17 | 18 | 19 | Check the Error handling page to 20 | learn how to deal with an error. An error at this stage is not 21 | recoverable, so you have to destroy the handle. How to destroy a handle 22 | will be explained in a few sentences. 23 | 24 | A new handle has all of its state set to default values. To set which 25 | program you want to access you need to call 26 | proctal_pid_set. It takes a handle and an id as 27 | arguments. 28 | 29 | 30 | proctal_pid_set(proctal, 12345); 31 | 32 | 33 | On Linux, the id must be a PID (Process ID). 34 | 35 | When you're done using the handle you must destroy it by calling the 36 | proctal_close function. It takes a handle as argument. 37 | 38 | 39 | proctal_close(proctal); 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/doc/api/pausing-execution.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pausing execution 4 | 5 | You can pause the execution of a program by calling the 6 | proctal_pause function. It takes a handle as argument. 7 | 8 | 9 | proctal_pause(proctal); 10 | 11 | 12 | Check the Error handling page to 13 | learn how to deal with an error. 14 | 15 | To resume execution you must call the proctal_resume 16 | function. It takes a handle as argument. 17 | 18 | 19 | proctal_resume(proctal); 20 | 21 | 22 | You must only call proctal_resume after having 23 | successfully called proctal_pause otherwise behavior is 24 | undefined. 25 | 26 | Closing a handle while the program is paused results in undefined 27 | behavior. 28 | 29 | You may call other functions on the handle while the program is paused. 30 | 31 | Calling proctal_pause again without calling 32 | proctal_resume beforehand results in undefined behavior. 33 | 34 | -------------------------------------------------------------------------------- /src/doc/api/reading-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reading from memory 4 | 5 | To read contents from the address space of another program you must 6 | call the proctal_read function. It takes a handle, an 7 | address in the program, a pointer to a buffer and the number of bytes 8 | to read as arguments and returns the number of bytes read. 9 | 10 | 11 | void *address = (void *) 0x1C09346; 12 | int i; 13 | size_t bytes_read = proctal_read(proctal, address, &i, sizeof(i)); 14 | 15 | 16 | You must make sure that it's possible to write to the given buffer up 17 | to the given number of bytes. 18 | 19 | If the returned number is not equal to the given number, then an error 20 | must have occurred. Check the Error 21 | handling page to learn how to deal with an error. 22 | 23 | -------------------------------------------------------------------------------- /src/doc/api/watching-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Watching memory 4 | 5 | The API provides a way to get the value of the instruction pointer the 6 | moment after a memory address is read, written or executed. 7 | 8 | To choose the address you want to watch, you must call 9 | proctal_watch_address_set. 10 | 11 | Here's how you would set to watch the address 81AC27. 12 | 13 | 14 | proctal_watch_address_set(proctal, (void *) 0x81AC27); 15 | 16 | 17 | To choose whether you want to watch for instructions that read, write 18 | or execute you can call proctal_watch_read_set, 19 | proctal_watch_write_set or 20 | proctal_watch_execute_set, respectively. 21 | 22 | Here's how you would watch for instructions that read and write but do 23 | not execute: 24 | 25 | 26 | proctal_watch_read_set(proctal, 1); 27 | proctal_watch_write_set(proctal, 1); 28 | proctal_watch_execute_set(proctal, 0); 29 | 30 | 31 | To start watching, you need to call proctal_watch_start. 32 | 33 | 34 | proctal_watch_start(proctal); 35 | 36 | 37 | Check the Error handling page to learn how to deal with 38 | an error. 39 | 40 | To check if the memory address was accessed in the mean time, you need 41 | to call proctal_watch_next. 42 | 43 | 44 | for (;;) { 45 | void *address; 46 | int result = proctal_watch_next(proctal, &address); 47 | 48 | if (proctal_error(proctal)) { 49 | // Error handling. 50 | break; 51 | } 52 | 53 | if (result) { 54 | // Use address. 55 | } 56 | } 57 | 58 | 59 | If a memory access was detected, it will return 1 and write out the 60 | address. 61 | 62 | If no memory access was detected, it will return 0. On failure it will 63 | also return 0. 64 | 65 | 66 | The address of the instruction that is returned may 67 | be the instruction that comes after the actual instruction that 68 | accessed the given memory location. 69 | 70 | 71 | To stop watching, you need to call proctal_watch_stop. 72 | 73 | 74 | proctal_watch_stop(proctal); 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/doc/api/writing-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Writing to memory 4 | 5 | To write contents to the address space of another program you must 6 | call the proctal_write function. It takes a handle, an 7 | address in the program, a pointer to a buffer and the number of bytes 8 | to write as arguments and returns the number of bytes written. 9 | 10 | 11 | void *address = (void *) 0x1C09346; 12 | int i = 0; 13 | size_t bytes_written = proctal_write(proctal, address, &i, sizeof(i)); 14 | 15 | 16 | You must make sure that it's possible to read from the given buffer up 17 | to the given number of bytes. 18 | 19 | If the returned number is not equal to the given number, then an error 20 | must have occurred. Check the Error 21 | handling page to learn how to deal with an error. 22 | 23 | -------------------------------------------------------------------------------- /src/doc/cli/allocating-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Allocating memory 4 | 5 | To allocate a block of memory you can use the allocate 6 | command. 7 | 8 | You may optionally pass the --read, 9 | --write and --execute options to set 10 | the access permissions of the memory block. If none of these options 11 | are used, the block gets full access. 12 | 13 | Here's how you would allocate 8 bytes in the program whose PID is 14 | 12345: 15 | 16 | 17 | $ proctal allocate --pid=12345 8 18 | 19 | 20 | It will print the starting address of the block of memory. 21 | 22 | When you're done, you have to deallocate the block by passing the same 23 | address to the deallocate command. If the address were 24 | 7FFE79DEA90C, the command would look like this: 25 | 26 | 27 | $ proctal deallocate --pid=12345 7FFE79DEA90C 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/doc/cli/dumping-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dumping memory 4 | 5 | The dump command prints byte for byte what's in memory. 6 | 7 | Here's how you would create dump.bin, a file that 8 | contains the entire contents in memory of the program whose PID is 9 | 12345: 10 | 11 | 12 | $ proctal dump --pid=12345 > dump.bin 13 | 14 | 15 | The --region option lets you specify which memory 16 | regions to dump. It takes the following values: 17 | 18 | 19 | stack contents on the stack of every 20 | thread 21 | 22 | heap contents on the heap 23 | 24 | program-code instructions from the 25 | executable (does not include shared libraries) 26 | 27 | 28 | These options let you specify whether the memory regions have to be 29 | readable, writeable or executable: 30 | 31 | 32 | --read 33 | 34 | --write 35 | 36 | --execute 37 | 38 | 39 | Here's how you would dump anything that is executable in memory to the 40 | file dump2.bin: 41 | 42 | 43 | $ proctal dump --pid=12345 --execute > dump2.bin 44 | 45 | 46 | The --address-start option specifies where to start 47 | dumping the contents in memory and the --address-stop 48 | option specifies where to stop. 49 | 50 | Here's how you would dump the contents starting from the address 51 | 7F7BE75E0714 up to the address 52 | 7F7BE75ED1A0 to the file dump3.bin: 53 | 54 | 55 | $ proctal dump --pid=12345 --address-start=7F7BE75E0714 --address-stop=7F7BE75ED1A0 > dump3.bin 56 | 57 | 58 | The --pause option prevents the program from executing 59 | code while the command is running. 60 | 61 | -------------------------------------------------------------------------------- /src/doc/cli/measuring-values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Measuring values 4 | 5 | Suppose you want to allocate memory in a program. It's easy to know how 6 | much space you'd need if you were to store three 32-bit integers. Doing 7 | the math yields 12 bytes. 8 | 9 | Calculating the storage for fixed sized values is easy, but if you were 10 | to store code that you wrote in assembly, you would have to know the 11 | size of the bytecode resulting from every instruction but even if you 12 | knew that information you would still have a lot of math to do if your 13 | code was composed of many instructions. 14 | 15 | Proctal provides the measure command that takes type 16 | options and counts the values for you. You can read all about type 17 | options here. 18 | 19 | Here are some examples: 20 | 21 | 22 | $ proctal measure --address=1c09346 --type=integer --integer-bits=32 0 23 | 4 24 | $ proctal measure --address=1c09346 --type=integer --integer-bits=64 0 25 | 8 26 | $ proctal measure --address=1c09346 --type=x86 "call 0x5" 27 | 5 28 | 29 | 30 | You may find it odd that the --address option is 31 | required. That's because assembly instructions may get assembled into 32 | different bytecode depending on where they are placed in memory. 33 | 34 | -------------------------------------------------------------------------------- /src/doc/cli/pausing-execution.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pausing execution 4 | 5 | You can use the pause command to pause program 6 | execution. 7 | 8 | Here's how you would pause a program whose PID is 12345: 9 | 10 | 11 | $ proctal pause --pid=12345 12 | 13 | 14 | The program stays paused as long as the command is running. You can 15 | stop the command by sending it the SIGINT signal 16 | (^C on most terminals). 17 | 18 | -------------------------------------------------------------------------------- /src/doc/cli/watching-memory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Watching memory 4 | 5 | With the watch command you can get the value of the 6 | instruction pointer the moment after a memory address is read, written 7 | or executed. 8 | 9 | You may pass the --read option to watch for reads, 10 | the --write option to watch for writes and the 11 | --execute option to watch for execution. 12 | 13 | If you were to watch the memory address 1c09346 for 14 | reads and writes on the program whose PID is 12345, the command would 15 | look like this: 16 | 17 | 18 | $ proctal watch --pid=12345 --read --write 1c09346 19 | 20 | 21 | The command will print the value of the instruction pointer after 22 | detecting that the given memory address was accessed. Note that the 23 | instruction pointer may not actually be pointing at the instruction 24 | that accessed the memory address. 25 | 26 | You can stop the command by sending it the SIGINT signal 27 | (^C on most terminals). 28 | 29 | The --address-start option specifies where to start the 30 | --address-stop option specifies where to stop detecting 31 | accesses. 32 | 33 | If you're seeing the same address get printed several times it might be 34 | helpful to use the --unique option that will make the 35 | command print an address only once. 36 | 37 | -------------------------------------------------------------------------------- /src/doc/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/doc/installation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Installation 4 | 5 | The latest version can be found on the website's 6 | download page. 7 | 8 | To compile you need the following programs installed on your system: 9 | 10 | 11 | CMake 12 | GCC 13 | sed 14 | m4 15 | php 16 | 17 | 18 | Proctal can optionally compile with the following libraries: 19 | 20 | 21 | capstone - For disassembling instructions. 22 | keystone - For assembling instructions. 23 | 24 | 25 | Then run: 26 | 27 | 28 | $ mkdir build 29 | 30 | # cmake -DCMAKE_BUILD_TYPE=Release .. 31 | 32 | $ make 33 | 34 | $ make install 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/doc/introduction.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Introduction 4 | 5 | Proctal is a tool for modding programs on Linux through a command line 6 | interface (CLI) and an abstract programming interface (API). 7 | 8 | Features: 9 | 10 | 11 | Reading and writing to memory 12 | Searching for values and byte patterns 13 | Freezing program execution 14 | Watching for accesses to memory locations 15 | Allocating and deallocating memory blocks 16 | Assembling and disassembling instructions 17 | Running your own code in the context of the program 18 | Dumping contents in memory 19 | 20 | 21 | I work on this project in my spare time. You can follow my progress on 22 | GitHub. 23 | 24 | -------------------------------------------------------------------------------- /src/doc/references.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/doc/tutorial/linux-find-pid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Finding the Process ID (PID) on Linux 4 | 5 | Every program running on your system is assigned a number that uniquely 6 | identifies it. This is known as the Process ID (commonly referred to as 7 | PID). Proctal needs this number to identify the program you want to 8 | take control of. 9 | 10 | These are a couple of methods for finding the PID of a program on 11 | Linux. 12 | 13 |
14 | pidof 15 | 16 | pidof takes the name of the program as its first 17 | argument and prints a list of PIDs of all matching programs, 18 | separated by spaces. 19 | 20 | 21 | $ pidof program-name 22 | 12345 23 | 24 |
25 | 26 |
27 | pgrep 28 | 29 | pgrep takes an Extended Regular Expression and 30 | prints a list of PIDs of all programs whose name or command 31 | line match the expression. 32 | 33 | 34 | $ pgrep prog*am-n?me 35 | 12345 12346 36 | 37 |
38 | 39 |
40 | ps and grep 41 | 42 | grep matches patterns and ps 43 | prints a row for each program running on the system. By piping 44 | the output of ps to grep, you can 45 | find the row for the program you are interested in. 46 | 47 | 48 | $ ps ax | grep program-name 49 | 12345 pts/0 S+ 0:00 program-name argument1 argument2 50 | 51 | 52 | The first column is the PID and the last column is the command 53 | line. 54 | 55 | 56 | Due to a race condition, the grep command matches 57 | itself sometimes. 58 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /src/magic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(tests) 2 | -------------------------------------------------------------------------------- /src/magic/magic.h: -------------------------------------------------------------------------------- 1 | #ifndef MAGIC_MAGIC_H 2 | #define MAGIC_MAGIC_H 3 | 4 | /* 5 | * This file provides functions and macros that perform C magic tricks. 6 | */ 7 | 8 | /* 9 | * Dereferences a pointer to a specific type. 10 | */ 11 | #define DEREF(T, P) (*(T *) (P)) 12 | 13 | /* 14 | * Figures out the size of an array statically. 15 | */ 16 | #define ARRAY_SIZE(A) (sizeof(A) / sizeof(A[0])) 17 | 18 | /* 19 | * Compares two integer values. 20 | * 21 | * Results in 0 if both are equal, 1 if X is greater than Y and -1 if X is 22 | * less than Y. 23 | */ 24 | #define COMPARE_INT(X, Y) (((X) > (Y)) - ((X) < (Y))) 25 | 26 | /* 27 | * Like COMPARE_INT but works on floating point numbers. 28 | */ 29 | #define COMPARE_FLOAT(X, Y) ((X) == (Y) ? 0 : ((X) > (Y) ? 1 : -1)) 30 | 31 | #endif /* MAGIC_MAGIC_H */ 32 | -------------------------------------------------------------------------------- /src/magic/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_magic_${file} ${file}.c) 3 | add_test(NAME proctal_magic_${file} COMMAND proctal_magic_${file}) 4 | endfunction() 5 | 6 | test_single_c_file(array-size) 7 | test_single_c_file(compare) 8 | test_single_c_file(deref) 9 | -------------------------------------------------------------------------------- /src/magic/tests/array-size.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "magic/magic.h" 5 | 6 | struct mixed { 7 | char a; 8 | int b; 9 | }; 10 | 11 | int main(void) 12 | { 13 | #define CHECK(ARRAY, EXPECTED_ITEM_SIZE, ITEM_SIZE) \ 14 | do { \ 15 | size_t item_size = ITEM_SIZE; \ 16 | size_t expected_item_size = EXPECTED_ITEM_SIZE; \ 17 | \ 18 | if (item_size != expected_item_size) { \ 19 | fprintf(stderr, "Wrong size for '" #ARRAY "' array.\n"); \ 20 | fprintf(stderr, "Expected %lu but got %lu\n", expected_item_size, item_size); \ 21 | return 1; \ 22 | } \ 23 | } while (0); 24 | 25 | char chars[20]; 26 | int ints[20]; 27 | int *pointers[20]; 28 | struct mixed mixed[20]; 29 | 30 | // Where each element is exactly 1 character in size. 31 | CHECK(chars, 20, ARRAY_SIZE(chars)); 32 | 33 | // Where each element is more than 1 character in size. 34 | CHECK(ints, 20, ARRAY_SIZE(ints)); 35 | 36 | // Where each element is a pointer. 37 | CHECK(pointers, 20, ARRAY_SIZE(pointers)); 38 | 39 | // Where each element is a struct with members of different sizes. 40 | CHECK(mixed, 20, ARRAY_SIZE(mixed)); 41 | 42 | return 0; 43 | 44 | #undef CHECK 45 | } 46 | -------------------------------------------------------------------------------- /src/magic/tests/compare.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "magic/magic.h" 5 | 6 | int main(void) 7 | { 8 | #define CHECK(W, X, Y, Z) \ 9 | do { \ 10 | int result = W(X, Y); \ 11 | \ 12 | if (result != Z) { \ 13 | fprintf(stderr, #W "(" #X ", " #Y") resulted in %d but was expecting " #Z ".\n", result); \ 14 | return 1; \ 15 | } \ 16 | } while (0); 17 | 18 | // Using plain integers. 19 | CHECK(COMPARE_INT, 0, 0, 0); 20 | CHECK(COMPARE_INT, 1, 2, -1); 21 | CHECK(COMPARE_INT, 1, 1, 0); 22 | CHECK(COMPARE_INT, 2, 1, 1); 23 | 24 | // Long ints. 25 | CHECK(COMPARE_INT, 0U, 0U, 0); 26 | CHECK(COMPARE_INT, 1U, 2U, -1); 27 | CHECK(COMPARE_INT, 1U, 1U, 0); 28 | CHECK(COMPARE_INT, 2U, 1U, 1); 29 | 30 | // Floats. 31 | CHECK(COMPARE_FLOAT, 0, 0, 0); 32 | CHECK(COMPARE_FLOAT, 1.0, 2.0, -1); 33 | CHECK(COMPARE_FLOAT, 1.0, 1.0, 0); 34 | CHECK(COMPARE_FLOAT, 2.0, 1.0, 1); 35 | CHECK(COMPARE_FLOAT, 0.1, 0.2, -1); 36 | CHECK(COMPARE_FLOAT, 0.2, 0.1, 1); 37 | 38 | return 0; 39 | 40 | #undef CHECK 41 | } 42 | -------------------------------------------------------------------------------- /src/magic/tests/deref.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "magic/magic.h" 5 | 6 | int main(void) 7 | { 8 | #define CHECK(P, T, V) \ 9 | do { \ 10 | T result = DEREF(T, P); \ 11 | \ 12 | if (result != V) { \ 13 | fprintf(stderr, "DEREF(" #T ", " #P ") did not result in " #V ".\n"); \ 14 | return 1; \ 15 | } \ 16 | } while (0); 17 | 18 | union { 19 | float f; 20 | int i; 21 | } value; 22 | 23 | value.f = 2.0; 24 | CHECK(&value, int, 0x40000000); 25 | 26 | float *float_pointer = &value.f; 27 | CHECK(float_pointer, int, 0x40000000); 28 | 29 | return 0; 30 | 31 | #undef CHECK 32 | } 33 | -------------------------------------------------------------------------------- /src/otrap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(proctal_otrap STATIC otrap.c otrap.h) 2 | 3 | add_subdirectory(tests) 4 | -------------------------------------------------------------------------------- /src/otrap/otrap.c: -------------------------------------------------------------------------------- 1 | #include "otrap/otrap.h" 2 | 3 | extern inline void otrap_init(struct otrap *o); 4 | 5 | extern inline void otrap_deinit(struct otrap *o); 6 | 7 | extern inline int swbuf_error(struct otrap *o); 8 | 9 | extern inline FILE *otrap_file(struct otrap *o); 10 | 11 | extern inline size_t otrap_read(struct otrap *o, char *destination, size_t size); 12 | 13 | extern inline size_t otrap_skip(struct otrap *o, size_t size); 14 | 15 | FILE *otrap_create_file(void) 16 | { 17 | return tmpfile(); 18 | } 19 | 20 | void otrap_destroy_file(FILE *file) 21 | { 22 | fclose(file); 23 | } 24 | -------------------------------------------------------------------------------- /src/otrap/otrap.h: -------------------------------------------------------------------------------- 1 | #ifndef OTRAP_OTRAP_H 2 | #define OTRAP_OTRAP_H 3 | 4 | #include 5 | 6 | struct otrap { 7 | // Read offset. 8 | size_t r_offset; 9 | 10 | // File used for trapping output. 11 | FILE *file; 12 | }; 13 | 14 | /* 15 | * This is an implementation detail. Do not call this function. 16 | * 17 | * Creates a read and write capable FILE structure. 18 | */ 19 | FILE *otrap_create_file(void); 20 | 21 | /* 22 | * This is an implementation detail. Do not call this function. 23 | * 24 | * Destroys a FILE structure created by otrap_create_file. 25 | */ 26 | void otrap_destroy_file(FILE *file); 27 | 28 | /* 29 | * Initialize an otrap struct. 30 | */ 31 | inline void otrap_init(struct otrap *o) 32 | { 33 | o->r_offset = 0; 34 | o->file = otrap_create_file(); 35 | } 36 | 37 | /* 38 | * Deinitialize an otrap struct. 39 | */ 40 | inline void otrap_deinit(struct otrap *o) 41 | { 42 | if (o->file) { 43 | otrap_destroy_file(o->file); 44 | } 45 | } 46 | 47 | /* 48 | * Returns 1 if an error ocurred, 0 if everything is ok. 49 | */ 50 | inline int swbuf_error(struct otrap *o) 51 | { 52 | return o->file == NULL; 53 | } 54 | 55 | /* 56 | * Returns the FILE structure that will be used to capture output. 57 | */ 58 | inline FILE *otrap_file(struct otrap *o) 59 | { 60 | return o->file; 61 | } 62 | 63 | /* 64 | * Read captured output. 65 | * 66 | * Returns the number of characters written to destination. 67 | */ 68 | inline size_t otrap_read(struct otrap *o, char *destination, size_t size) 69 | { 70 | size_t original_offset = ftell(o->file); 71 | 72 | fseek(o->file, o->r_offset, SEEK_SET); 73 | 74 | size_t read = fread(destination, 1, size, o->file); 75 | 76 | o->r_offset += read; 77 | 78 | fseek(o->file, original_offset, SEEK_SET); 79 | 80 | return read; 81 | } 82 | 83 | /* 84 | * Skip captured output. 85 | * 86 | * Returns how many characters were skipped. 87 | */ 88 | inline size_t otrap_skip(struct otrap *o, size_t size) 89 | { 90 | size_t original_offset = ftell(o->file); 91 | 92 | size_t remaining = original_offset - o->r_offset; 93 | 94 | if (size > remaining) { 95 | size = remaining; 96 | } 97 | 98 | o->r_offset += size; 99 | 100 | return size; 101 | } 102 | 103 | #endif /* OTRAP_OTRAP_H */ 104 | -------------------------------------------------------------------------------- /src/otrap/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_otrap_${file} ${file}.c) 3 | target_link_libraries(proctal_otrap_${file} PRIVATE proctal_otrap) 4 | add_test(NAME proctal_otrap_${file} COMMAND proctal_otrap_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(read-nothing) 8 | test_single_c_file(read-too-much) 9 | test_single_c_file(read-twice) 10 | test_single_c_file(read) 11 | test_single_c_file(skip-nothing) 12 | test_single_c_file(skip-too-much) 13 | test_single_c_file(skip-twice) 14 | test_single_c_file(skip) 15 | -------------------------------------------------------------------------------- /src/otrap/tests/read-nothing.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "otrap/otrap.h" 6 | 7 | static void test_after_init(void) 8 | { 9 | struct otrap trap; 10 | otrap_init(&trap); 11 | 12 | char read_back[4]; 13 | size_t read_size = otrap_read(&trap, read_back, 4); 14 | 15 | if (read_size != 0) { 16 | fprintf(stderr, "There should be nothing to read after just having initialized.\n"); 17 | otrap_deinit(&trap); 18 | exit(1); 19 | } 20 | 21 | otrap_deinit(&trap); 22 | } 23 | 24 | static void test_after_reading_everything(void) 25 | { 26 | struct otrap trap; 27 | otrap_init(&trap); 28 | 29 | FILE *f = otrap_file(&trap); 30 | 31 | char output[] = "test"; 32 | size_t output_size = sizeof(output); 33 | 34 | fputs(output, f); 35 | 36 | char read_to_discard[output_size]; 37 | otrap_read(&trap, read_to_discard, output_size); 38 | 39 | char buf[1]; 40 | size_t read_size = otrap_read(&trap, buf, 1); 41 | 42 | if (read_size != 0) { 43 | fprintf(stderr, "There should be nothing else to read after reading everything.\n"); 44 | otrap_deinit(&trap); 45 | exit(1); 46 | } 47 | 48 | otrap_deinit(&trap); 49 | } 50 | 51 | /* 52 | * Test cases where there should be nothing to read. 53 | */ 54 | int main(void) 55 | { 56 | test_after_init(); 57 | test_after_reading_everything(); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /src/otrap/tests/read-too-much.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "otrap/otrap.h" 5 | 6 | int main(void) 7 | { 8 | struct otrap trap; 9 | otrap_init(&trap); 10 | 11 | FILE *f = otrap_file(&trap); 12 | 13 | char output[] = "test"; 14 | size_t output_size = sizeof(output) - 1; 15 | 16 | fputs(output, f); 17 | 18 | size_t extra_size = 3; 19 | 20 | size_t read_back_size = output_size + extra_size; 21 | char read_back[read_back_size]; 22 | 23 | size_t read_size = otrap_read(&trap, read_back, read_back_size); 24 | 25 | if (read_size != read_back_size - extra_size) { 26 | fprintf(stderr, "Was only supposed to read back as much as was written.\n"); 27 | otrap_deinit(&trap); 28 | return 1; 29 | } 30 | 31 | otrap_deinit(&trap); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/otrap/tests/read-twice.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "otrap/otrap.h" 6 | 7 | static void read(struct otrap *trap, const char *output) 8 | { 9 | static int call = 0; 10 | ++call; 11 | 12 | FILE *f = otrap_file(trap); 13 | 14 | size_t output_size = strlen(output); 15 | 16 | fputs(output, f); 17 | 18 | char trapped[output_size]; 19 | 20 | size_t read_size = otrap_read(trap, trapped, output_size); 21 | 22 | if (read_size != output_size 23 | || memcmp(output, trapped, read_size) != 0) { 24 | fprintf(stderr, "Output read back is different on #%d call.\n", call); 25 | otrap_deinit(trap); 26 | exit(1); 27 | } 28 | } 29 | 30 | int main(void) 31 | { 32 | struct otrap trap; 33 | otrap_init(&trap); 34 | 35 | read(&trap, "test1"); 36 | read(&trap, "test2asdf"); 37 | 38 | otrap_deinit(&trap); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/otrap/tests/read.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "otrap/otrap.h" 5 | 6 | int main(void) 7 | { 8 | struct otrap trap; 9 | otrap_init(&trap); 10 | 11 | FILE *f = otrap_file(&trap); 12 | 13 | char expected[] = "test"; 14 | size_t expected_size = sizeof(expected) - 1; 15 | 16 | fputs(expected, f); 17 | 18 | char trapped[expected_size]; 19 | size_t trapped_size = sizeof(trapped); 20 | 21 | size_t read = otrap_read(&trap, trapped, trapped_size); 22 | 23 | if (read != expected_size 24 | || memcmp(expected, trapped, read) != 0) { 25 | fprintf(stderr, "Output read back is different.\n"); 26 | fprintf(stderr, "Expected (size: %d): ", (int) expected_size); 27 | fwrite(expected, expected_size, 1, stderr); 28 | fputs("\n", stderr); 29 | fprintf(stderr, "Read back (size: %d): ", (int) read); 30 | fwrite(trapped, read > trapped_size ? trapped_size : read, 1, stderr); 31 | fputs("\n", stderr); 32 | 33 | otrap_deinit(&trap); 34 | return 1; 35 | } 36 | 37 | otrap_deinit(&trap); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/otrap/tests/skip-nothing.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "otrap/otrap.h" 6 | 7 | static void test_after_init(void) 8 | { 9 | struct otrap trap; 10 | otrap_init(&trap); 11 | 12 | size_t skip_size = otrap_skip(&trap, 1); 13 | 14 | if (skip_size != 0) { 15 | fprintf(stderr, "There should be nothing to skip after just having initialized.\n"); 16 | otrap_deinit(&trap); 17 | exit(1); 18 | } 19 | 20 | otrap_deinit(&trap); 21 | } 22 | 23 | static void test_after_skipping_everything(void) 24 | { 25 | struct otrap trap; 26 | otrap_init(&trap); 27 | 28 | FILE *f = otrap_file(&trap); 29 | 30 | char output[] = "test"; 31 | size_t output_size = sizeof(output); 32 | 33 | fputs(output, f); 34 | 35 | otrap_skip(&trap, output_size); 36 | size_t skip_size = otrap_skip(&trap, 1); 37 | 38 | if (skip_size != 0) { 39 | fprintf(stderr, "There should be nothing else to skip after skipping everything.\n"); 40 | otrap_deinit(&trap); 41 | exit(1); 42 | } 43 | 44 | otrap_deinit(&trap); 45 | } 46 | 47 | /* 48 | * Test cases where there should be nothing to skip. 49 | */ 50 | int main(void) 51 | { 52 | test_after_init(); 53 | test_after_skipping_everything(); 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /src/otrap/tests/skip-too-much.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "otrap/otrap.h" 5 | 6 | int main(void) 7 | { 8 | struct otrap trap; 9 | otrap_init(&trap); 10 | 11 | FILE *f = otrap_file(&trap); 12 | 13 | char output[] = "test"; 14 | size_t output_size = sizeof(output) - 1; 15 | 16 | fputs(output, f); 17 | 18 | size_t skipped = otrap_skip(&trap, output_size + 3); 19 | 20 | if (skipped != output_size) { 21 | fprintf(stderr, "Skipped more characters than there is output.\n"); 22 | otrap_deinit(&trap); 23 | return 1; 24 | } 25 | 26 | otrap_deinit(&trap); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/otrap/tests/skip-twice.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "otrap/otrap.h" 6 | 7 | static void skip(struct otrap *trap, const char *output) 8 | { 9 | static int call = 0; 10 | ++call; 11 | 12 | FILE *f = otrap_file(trap); 13 | 14 | size_t output_size = strlen(output); 15 | 16 | fputs(output, f); 17 | 18 | size_t skipped = otrap_skip(trap, output_size); 19 | 20 | if (skipped != output_size) { 21 | fprintf(stderr, "Did not skip the right number of characters on the #%d call.\n", call); 22 | otrap_deinit(trap); 23 | exit(1); 24 | } 25 | } 26 | 27 | int main(void) 28 | { 29 | struct otrap trap; 30 | otrap_init(&trap); 31 | 32 | skip(&trap, "test1"); 33 | skip(&trap, "test2asdf"); 34 | 35 | otrap_deinit(&trap); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/otrap/tests/skip.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "otrap/otrap.h" 5 | 6 | int main(void) 7 | { 8 | struct otrap trap; 9 | otrap_init(&trap); 10 | 11 | FILE *f = otrap_file(&trap); 12 | 13 | char output[] = "test"; 14 | size_t output_size = sizeof(output) - 1; 15 | 16 | fputs(output, f); 17 | 18 | size_t skipped = otrap_skip(&trap, output_size); 19 | 20 | if (skipped != output_size) { 21 | fprintf(stderr, "Did not skip the correct number of characters.\n"); 22 | fprintf(stderr, "Expected to skip %d but was actually %d.\n", (int) output_size, (int) skipped); 23 | otrap_deinit(&trap); 24 | return 1; 25 | } 26 | 27 | otrap_deinit(&trap); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/pq/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library( 2 | proctal_pq 3 | 4 | STATIC 5 | 6 | implementation.h 7 | implementation.c 8 | interface.c 9 | pq.h 10 | quit-state.c 11 | quit-state.h 12 | ) 13 | -------------------------------------------------------------------------------- /src/pq/implementation.c: -------------------------------------------------------------------------------- 1 | #include "pq/implementation.h" 2 | #include "config.h" 3 | 4 | #ifdef PROCTAL_PLATFORM_LINUX 5 | 6 | #include "pq/posix/implementation.c" 7 | 8 | #elif defined PROCTAL_PLATFORM_WINDOWS 9 | 10 | #include "pq/windows-posix/implementation.c" 11 | 12 | #else 13 | 14 | #include "pq/unimplemented.c" 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/pq/implementation.h: -------------------------------------------------------------------------------- 1 | #ifndef PQ_IMPLEMENTATION_H 2 | #define PQ_IMPLEMENTATION_H 3 | 4 | /* 5 | * Starts keeping track of messages and signals. 6 | * 7 | * Returns 1 on success and 0 on failure. 8 | */ 9 | int pq_implementation_start(void); 10 | 11 | /* 12 | * Stops tracking. 13 | * 14 | * Returns 1 on success and 0 on failure. 15 | */ 16 | int pq_implementation_stop(void); 17 | 18 | /* 19 | * Waits until the process receives a message that it wants to quit. 20 | * 21 | * Returns 1 on success and 0 on failure. 22 | */ 23 | int pq_implementation_wait(void); 24 | 25 | #endif /* PQ_IMPLEMENTATION_H */ 26 | -------------------------------------------------------------------------------- /src/pq/interface.c: -------------------------------------------------------------------------------- 1 | #include "pq/pq.h" 2 | #include "pq/implementation.h" 3 | #include "pq/quit-state.h" 4 | 5 | int pq_start(void) 6 | { 7 | pq_quit_state_set(0); 8 | 9 | return pq_implementation_start(); 10 | } 11 | 12 | int pq_wait(void) 13 | { 14 | if (pq_check()) { 15 | return 1; 16 | } 17 | 18 | return pq_implementation_wait(); 19 | } 20 | 21 | int pq_check(void) 22 | { 23 | return pq_quit_state(); 24 | } 25 | 26 | int pq_stop(void) 27 | { 28 | return pq_implementation_stop(); 29 | } 30 | -------------------------------------------------------------------------------- /src/pq/posix/implementation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "pq/implementation.h" 5 | #include "pq/quit-state.h" 6 | 7 | static void quit(int signum) 8 | { 9 | pq_quit_state_set(1); 10 | } 11 | 12 | int pq_implementation_start(void) 13 | { 14 | struct sigaction sa = { 15 | .sa_handler = quit, 16 | .sa_flags = 0, 17 | }; 18 | 19 | sigemptyset(&sa.sa_mask); 20 | 21 | return sigaction(SIGINT, &sa, NULL) != -1 22 | && sigaction(SIGTERM, &sa, NULL) != -1; 23 | } 24 | 25 | int pq_implementation_stop(void) 26 | { 27 | signal(SIGINT, SIG_DFL); 28 | signal(SIGTERM, SIG_DFL); 29 | 30 | return 1; 31 | } 32 | 33 | int pq_implementation_wait(void) 34 | { 35 | sigset_t mask, oldmask; 36 | 37 | sigemptyset(&mask); 38 | sigaddset(&mask, SIGINT); 39 | sigaddset(&mask, SIGTERM); 40 | sigprocmask(SIG_BLOCK, &mask, &oldmask); 41 | 42 | while (!pq_quit_state()) { 43 | sigsuspend(&oldmask); 44 | } 45 | 46 | sigprocmask(SIG_UNBLOCK, &mask, NULL); 47 | 48 | return 1; 49 | } 50 | -------------------------------------------------------------------------------- /src/pq/pq.h: -------------------------------------------------------------------------------- 1 | #ifndef PQ_PQ_H 2 | #define PQ_PQ_H 3 | 4 | /* 5 | * Starts keeping track of messages and signals. 6 | * 7 | * Returns 1 on success and 0 on failure. 8 | */ 9 | int pq_start(void); 10 | 11 | /* 12 | * Stops tracking. 13 | * 14 | * Returns 1 on success and 0 on failure. 15 | */ 16 | int pq_stop(void); 17 | 18 | /* 19 | * Waits until the process receives a message that it wants to quit. 20 | * 21 | * This function can only be called while keeping track of messages and 22 | * signals. 23 | * 24 | * Returns 1 on success and 0 on failure. 25 | */ 26 | int pq_wait(void); 27 | 28 | /* 29 | * Checks whether the process received a message to quit. 30 | * 31 | * This function can be called after having stopped tracking for messages and 32 | * signals. 33 | * 34 | * Returns 1 if yes and 0 if not. 35 | */ 36 | int pq_check(void); 37 | 38 | #endif /* PQ_PQ_H */ 39 | -------------------------------------------------------------------------------- /src/pq/quit-state.c: -------------------------------------------------------------------------------- 1 | #include "pq/quit-state.h" 2 | 3 | static int quit_state = 0; 4 | 5 | void pq_quit_state_set(int state) 6 | { 7 | quit_state = !!state; 8 | } 9 | 10 | int pq_quit_state(void) 11 | { 12 | return quit_state; 13 | } 14 | -------------------------------------------------------------------------------- /src/pq/quit-state.h: -------------------------------------------------------------------------------- 1 | #ifndef PQ_QUIT_STATE_H 2 | #define PQ_QUIT_STATE_H 3 | 4 | /* 5 | * Lets you set whether to want to quit. 6 | * 7 | * 1 means yes, 0 means no. 8 | */ 9 | void pq_quit_state_set(int state); 10 | 11 | /* 12 | * Whether you want to quit. 13 | * 14 | * 1 means yes, 0 means no. 15 | */ 16 | int pq_quit_state(void); 17 | 18 | #endif /* PQ_QUIT_STATE_H */ 19 | -------------------------------------------------------------------------------- /src/pq/unimplemented.c: -------------------------------------------------------------------------------- 1 | #include "pq/implementation.h" 2 | 3 | int pq_implementation_start(void) 4 | { 5 | return 0; 6 | } 7 | 8 | int pq_implementation_stop(void) 9 | { 10 | return 0; 11 | } 12 | 13 | int pq_implementation_wait(void) 14 | { 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/pq/windows-posix/implementation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "pq/implementation.h" 6 | #include "pq/quit-state.h" 7 | 8 | static void quit(int signum) 9 | { 10 | pq_quit_state_set(1); 11 | } 12 | 13 | int pq_implementation_start(void) 14 | { 15 | signal(SIGINT, quit); 16 | signal(SIGTERM, quit); 17 | 18 | return 1; 19 | } 20 | 21 | int pq_implementation_stop(void) 22 | { 23 | signal(SIGINT, SIG_DFL); 24 | signal(SIGTERM, SIG_DFL); 25 | 26 | return 1; 27 | } 28 | 29 | int pq_implementation_wait(void) 30 | { 31 | while (!pq_quit_state()) { 32 | // With no support for sigsuspend or pause, this is the 33 | // quickest workaround. 34 | Sleep(33); 35 | } 36 | 37 | return 1; 38 | } 39 | -------------------------------------------------------------------------------- /src/riter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(proctal_riter STATIC riter.c riter.h) 2 | 3 | target_link_libraries(proctal_riter PRIVATE proctal_swbuf) 4 | target_link_libraries(proctal_riter PRIVATE proctal_chunk) 5 | 6 | add_subdirectory(tests) 7 | -------------------------------------------------------------------------------- /src/riter/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_riter_${file} ${file}.c) 3 | target_link_libraries(proctal_riter_${file} PRIVATE proctal_riter) 4 | add_test(NAME proctal_riter_${file} COMMAND proctal_riter_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(init-defaults) 8 | test_single_c_file(init-errors) 9 | test_single_c_file(read-align) 10 | test_single_c_file(read-end) 11 | test_single_c_file(read-failure) 12 | test_single_c_file(read-redundancy) 13 | test_single_c_file(read) 14 | test_single_c_file(reader-data) 15 | -------------------------------------------------------------------------------- /src/riter/tests/init-defaults.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "riter/riter.h" 4 | 5 | /* 6 | * Region of memory that is going to be iterated over. 7 | */ 8 | char memory[] = { 9 | 0, 0, 0, 0, 10 | 1, 2, 3, 4, 11 | 5, 5, 5, 12 | }; 13 | 14 | static void test_default_align_is_1(void) 15 | { 16 | struct riter riter; 17 | 18 | riter_init(&riter, &(struct riter_config) { 19 | .source = memory, 20 | .source_size = sizeof(memory), 21 | .buffer_size = 4, 22 | }); 23 | 24 | assert(riter_data_align(&riter) == 1); 25 | 26 | riter_deinit(&riter); 27 | } 28 | 29 | static void test_default_size_is_1(void) 30 | { 31 | struct riter riter; 32 | 33 | riter_init(&riter, &(struct riter_config) { 34 | .source = memory, 35 | .source_size = sizeof(memory), 36 | .buffer_size = 4, 37 | }); 38 | 39 | assert(riter_data_size(&riter) == 1); 40 | 41 | riter_deinit(&riter); 42 | } 43 | 44 | int main(void) 45 | { 46 | test_default_align_is_1(); 47 | test_default_size_is_1(); 48 | } 49 | -------------------------------------------------------------------------------- /src/riter/tests/init-errors.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "riter/riter.h" 4 | 5 | char memory[] = { 6 | 0, 0, 0, 0, 7 | 1, 2, 3, 4, 8 | 5, 5, 5, 9 | }; 10 | 11 | static void test_init_fails_source_required(void) 12 | { 13 | struct riter riter; 14 | 15 | riter_init(&riter, &(struct riter_config) { 16 | .source_size = sizeof(memory), 17 | .buffer_size = 4, 18 | }); 19 | 20 | assert(riter_error(&riter) == RITER_ERROR_SOURCE_REQUIRED); 21 | 22 | riter_deinit(&riter); 23 | } 24 | 25 | static void test_init_fails_source_size_required(void) 26 | { 27 | struct riter riter; 28 | 29 | riter_init(&riter, &(struct riter_config) { 30 | .source = memory, 31 | .buffer_size = 4, 32 | }); 33 | 34 | assert(riter_error(&riter) == RITER_ERROR_SOURCE_SIZE_REQUIRED); 35 | 36 | riter_deinit(&riter); 37 | } 38 | 39 | static void test_init_fails_buffer_size_required(void) 40 | { 41 | struct riter riter; 42 | 43 | riter_init(&riter, &(struct riter_config) { 44 | .source = memory, 45 | .source_size = sizeof(memory), 46 | }); 47 | 48 | assert(riter_error(&riter) == RITER_ERROR_BUFFER_SIZE_REQUIRED); 49 | 50 | riter_deinit(&riter); 51 | } 52 | 53 | static void test_init_fails_data_size_larger_than_buffer_size(void) 54 | { 55 | struct riter riter; 56 | 57 | riter_init(&riter, &(struct riter_config) { 58 | .source = memory, 59 | .source_size = sizeof(memory), 60 | .buffer_size = 1, 61 | .data_size = 2, 62 | }); 63 | 64 | assert(riter_error(&riter) == RITER_ERROR_DATA_SIZE_LARGER_THAN_BUFFER_SIZE); 65 | 66 | riter_deinit(&riter); 67 | } 68 | 69 | int main(void) 70 | { 71 | test_init_fails_source_required(); 72 | test_init_fails_source_size_required(); 73 | test_init_fails_buffer_size_required(); 74 | test_init_fails_data_size_larger_than_buffer_size(); 75 | } 76 | -------------------------------------------------------------------------------- /src/riter/tests/read-align.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "riter/riter.h" 7 | 8 | /* 9 | * Region of memory that is going to be iterated over. 10 | */ 11 | char memory[] = { 12 | 0, 0, 0, 0, 13 | 1, 2, 3, 4, 14 | 5, 5, 5, 15 | }; 16 | 17 | static void test_only_reads_aligned_data(void) 18 | { 19 | struct riter riter; 20 | 21 | riter_init(&riter, &(struct riter_config) { 22 | // The source address will start at an incorrect alignment boundary. 23 | .source = memory + 1, 24 | .source_size = sizeof(memory), 25 | .buffer_size = 4, 26 | .data_align = 2, 27 | .data_size = 2, 28 | }); 29 | 30 | size_t index = riter_index(&riter); 31 | 32 | if (index != 1) { 33 | fprintf(stderr, "Index should start at 1, it is %zu instead\n", index); 34 | abort(); 35 | } 36 | 37 | riter_deinit(&riter); 38 | } 39 | 40 | static void test_aligned_data_even_when_they_overlap(void) 41 | { 42 | struct riter riter; 43 | 44 | riter_init(&riter, &(struct riter_config) { 45 | .source = memory, 46 | .source_size = 3, 47 | .buffer_size = 2, 48 | .data_align = 1, 49 | .data_size = 2, 50 | }); 51 | assert(riter_index(&riter) == 0); 52 | 53 | assert(riter_next(&riter)); 54 | assert(riter_index(&riter) == 1); 55 | 56 | assert(riter_next(&riter) == 0); 57 | assert(riter_end(&riter)); 58 | 59 | riter_deinit(&riter); 60 | } 61 | 62 | int main(void) 63 | { 64 | test_only_reads_aligned_data(); 65 | test_aligned_data_even_when_they_overlap(); 66 | } 67 | -------------------------------------------------------------------------------- /src/riter/tests/read-end.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "riter/riter.h" 7 | 8 | /* 9 | * Region of memory that is going to be iterated over. 10 | */ 11 | char memory[] = { 12 | 1, 2, 3, 4, 13 | }; 14 | 15 | static void test_end_is_reported_when_next_read_signals_end(void) 16 | { 17 | struct riter riter; 18 | 19 | riter_init(&riter, &(struct riter_config) { 20 | .source = memory, 21 | .source_size = sizeof(memory), 22 | .buffer_size = 2, 23 | .data_align = 2, 24 | .data_size = 2, 25 | }); 26 | 27 | assert(riter_next(&riter)); 28 | assert(riter_next(&riter) == 0); 29 | assert(riter_end(&riter)); 30 | 31 | riter_deinit(&riter); 32 | } 33 | 34 | int main(void) 35 | { 36 | test_end_is_reported_when_next_read_signals_end(); 37 | //test_end_is_reported_when_next_read_signals_end_without_reaching_the_end_of_chunk(); 38 | } 39 | -------------------------------------------------------------------------------- /src/riter/tests/read-failure.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "riter/riter.h" 7 | 8 | /* 9 | * Region of memory that is going to be iterated over. 10 | */ 11 | char memory[] = { 12 | 0, 0, 0, 0, 13 | 1, 2, 3, 4, 14 | 5, 5, 5, 15 | }; 16 | 17 | static int reader_always_fails(void *user, void *src, void *out, size_t size) 18 | { 19 | return 0; 20 | } 21 | 22 | static int reader_fails_second(void *user, void *src, void *out, size_t size) 23 | { 24 | static int called = 0; 25 | memcpy(src, out, size); 26 | return ++called == 2 ? 0 : 1; 27 | } 28 | 29 | // Checking that riter_error returns an error when reading fails in riter_init 30 | static void test_init_read_failure_sets_error(void) 31 | { 32 | struct riter riter; 33 | 34 | riter_init(&riter, &(struct riter_config) { 35 | .reader = reader_always_fails, 36 | .source = memory, 37 | .source_size = sizeof(memory), 38 | .buffer_size = 2, 39 | .data_align = 1, 40 | .data_size = 2, 41 | }); 42 | 43 | assert(riter_error(&riter) == RITER_ERROR_READ_FAILURE); 44 | 45 | riter_deinit(&riter); 46 | } 47 | 48 | // Checking that riter_error returns an error when reading fails in riter_next 49 | static void test_next_read_failure_sets_error(void) 50 | { 51 | struct riter riter; 52 | 53 | riter_init(&riter, &(struct riter_config) { 54 | .reader = reader_fails_second, 55 | .source = memory, 56 | .source_size = sizeof(memory), 57 | .buffer_size = 2, 58 | .data_align = 1, 59 | .data_size = 2, 60 | }); 61 | 62 | assert(riter_next(&riter) == 0); 63 | assert(riter_error(&riter) == RITER_ERROR_READ_FAILURE); 64 | 65 | riter_deinit(&riter); 66 | } 67 | 68 | int main(void) 69 | { 70 | test_init_read_failure_sets_error(); 71 | test_next_read_failure_sets_error(); 72 | } 73 | -------------------------------------------------------------------------------- /src/riter/tests/read-redundancy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "riter/riter.h" 7 | 8 | /* 9 | * Region of memory that is going to be iterated over. 10 | */ 11 | char memory[] = { 0, 0, 0, 0 }; 12 | 13 | struct user { 14 | int called; 15 | }; 16 | 17 | static int reader_counter(void *user, void *src, void *out, size_t size) 18 | { 19 | struct user *real_user = user; 20 | 21 | real_user->called += 1; 22 | 23 | return memcpy(out, src, size) != NULL; 24 | } 25 | 26 | static void test_should_not_read_first_if_empty(void) 27 | { 28 | struct riter riter; 29 | struct user user = {}; 30 | 31 | riter_init(&riter, &(struct riter_config) { 32 | .reader = reader_counter, 33 | .source = memory, 34 | .source_size = 0, 35 | .buffer_size = 2, 36 | .data_align = 1, 37 | .data_size = 2, 38 | .user = &user, 39 | }); 40 | 41 | assert(user.called == 0); 42 | 43 | riter_deinit(&riter); 44 | } 45 | 46 | static void test_should_not_read_first_if_not_enough_data(void) 47 | { 48 | struct riter riter; 49 | struct user user = {}; 50 | 51 | riter_init(&riter, &(struct riter_config) { 52 | .reader = reader_counter, 53 | .source = memory, 54 | .source_size = 1, 55 | .buffer_size = 2, 56 | .data_align = 1, 57 | .data_size = 2, 58 | .user = &user, 59 | }); 60 | 61 | assert(user.called == 0); 62 | 63 | riter_deinit(&riter); 64 | } 65 | 66 | static void test_should_not_read_last_if_not_enough_data(void) 67 | { 68 | struct riter riter; 69 | struct user user = {}; 70 | 71 | riter_init(&riter, &(struct riter_config) { 72 | .reader = reader_counter, 73 | .source = memory, 74 | .source_size = 3, 75 | .buffer_size = 2, 76 | .data_align = 1, 77 | .data_size = 2, 78 | .user = &user, 79 | }); 80 | 81 | assert(riter_next(&riter)); 82 | assert(riter_next(&riter) == 0); 83 | assert(user.called == 2); 84 | 85 | riter_deinit(&riter); 86 | } 87 | 88 | int main(void) 89 | { 90 | test_should_not_read_first_if_empty(); 91 | test_should_not_read_first_if_not_enough_data(); 92 | test_should_not_read_last_if_not_enough_data(); 93 | } 94 | -------------------------------------------------------------------------------- /src/riter/tests/reader-data.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "riter/riter.h" 7 | 8 | /* 9 | * Region of memory that is going to be iterated over. 10 | */ 11 | char memory[] = { 0, 0, 0, 0 }; 12 | 13 | struct user { 14 | int called; 15 | }; 16 | 17 | static int reader_always_fails(void *user, void *src, void *out, size_t size) 18 | { 19 | struct user *real_user = user; 20 | 21 | real_user->called++; 22 | 23 | return 0; 24 | } 25 | 26 | static void test_passes_user_in_init(void) 27 | { 28 | struct riter riter; 29 | struct user user = {}; 30 | 31 | riter_init(&riter, &(struct riter_config) { 32 | .reader = reader_always_fails, 33 | .source = memory, 34 | .source_size = sizeof(memory), 35 | .buffer_size = 2, 36 | .data_align = 1, 37 | .data_size = 2, 38 | .user = &user, 39 | }); 40 | 41 | assert(user.called == 1); 42 | } 43 | 44 | static void test_passes_user_in_next(void) 45 | { 46 | struct riter riter; 47 | struct user user = {}; 48 | 49 | riter_init(&riter, &(struct riter_config) { 50 | .reader = reader_always_fails, 51 | .source = memory, 52 | .source_size = sizeof(memory), 53 | .buffer_size = 2, 54 | .data_align = 1, 55 | .data_size = 2, 56 | .user = &user, 57 | }); 58 | 59 | riter_next(&riter); 60 | 61 | assert(user.called == 2); 62 | } 63 | 64 | int main(void) 65 | { 66 | test_passes_user_in_init(); 67 | test_passes_user_in_next(); 68 | } 69 | -------------------------------------------------------------------------------- /src/swbuf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(proctal_swbuf STATIC swbuf.c swbuf.h) 2 | 3 | add_subdirectory(tests) 4 | -------------------------------------------------------------------------------- /src/swbuf/swbuf.c: -------------------------------------------------------------------------------- 1 | #include "swbuf/swbuf.h" 2 | 3 | static void *(*alloc)(size_t) = malloc; 4 | static void (*dealloc)(void *) = free; 5 | 6 | extern inline void swbuf_init(struct swbuf *b, size_t size); 7 | 8 | extern inline void swbuf_deinit(struct swbuf *b); 9 | 10 | extern inline int swbuf_error(struct swbuf *b); 11 | 12 | extern inline size_t swbuf_size(struct swbuf *b); 13 | 14 | extern inline void swbuf_swap(struct swbuf *b); 15 | 16 | extern inline void *swbuf_offset(struct swbuf *b, ptrdiff_t offset); 17 | 18 | void swbuf_malloc_set(void *(*f)(size_t)) 19 | { 20 | alloc = f; 21 | } 22 | 23 | void swbuf_free_set(void (*f)(void *)) 24 | { 25 | dealloc = f; 26 | } 27 | 28 | void *swbuf_malloc(size_t size) 29 | { 30 | return alloc(size); 31 | } 32 | 33 | void swbuf_free(void *b) 34 | { 35 | dealloc(b); 36 | } 37 | -------------------------------------------------------------------------------- /src/swbuf/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(test_single_c_file file) 2 | add_executable(proctal_swbuf_${file} ${file}.c) 3 | target_link_libraries(proctal_swbuf_${file} PRIVATE proctal_swbuf) 4 | add_test(NAME proctal_swbuf_${file} COMMAND proctal_swbuf_${file}) 5 | endfunction() 6 | 7 | test_single_c_file(address-offset-arithmetic) 8 | test_single_c_file(init-fail-alloc) 9 | test_single_c_file(init-succeed-alloc) 10 | test_single_c_file(size) 11 | test_single_c_file(swap) 12 | -------------------------------------------------------------------------------- /src/swbuf/tests/address-offset-arithmetic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "swbuf/swbuf.h" 5 | 6 | int main(void) 7 | { 8 | struct swbuf buf; 9 | swbuf_init(&buf, 1); 10 | 11 | char *start_addr = swbuf_offset(&buf, 0); 12 | char *next_addr = swbuf_offset(&buf, 1); 13 | char *prev_addr = swbuf_offset(&buf, -1); 14 | 15 | assert(start_addr + 1 == next_addr); 16 | assert(start_addr - 1 == prev_addr); 17 | 18 | swbuf_deinit(&buf); 19 | } 20 | -------------------------------------------------------------------------------- /src/swbuf/tests/init-fail-alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "swbuf/swbuf.h" 5 | 6 | static void *malloc_fail(size_t size) 7 | { 8 | return NULL; 9 | } 10 | 11 | static void free_fail(void *b) 12 | { 13 | fprintf(stderr, "Should not free something that was never allocated.\n"); 14 | abort(); 15 | } 16 | 17 | int main(void) 18 | { 19 | swbuf_malloc_set(malloc_fail); 20 | swbuf_free_set(free_fail); 21 | 22 | struct swbuf buf; 23 | swbuf_init(&buf, 1); 24 | 25 | assert(swbuf_error(&buf)); 26 | 27 | swbuf_deinit(&buf); 28 | } 29 | -------------------------------------------------------------------------------- /src/swbuf/tests/init-succeed-alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "swbuf/swbuf.h" 5 | 6 | static void *malloc_succeed(size_t size) 7 | { 8 | static char block[1024]; 9 | 10 | if (size < sizeof(block)) { 11 | return block; 12 | } 13 | 14 | return NULL; 15 | } 16 | 17 | static void fake_free(void *b) 18 | { 19 | } 20 | 21 | /* 22 | * Checks if swbuf_error reports no errors if swbuf_init succeeds. 23 | */ 24 | int main(void) 25 | { 26 | swbuf_malloc_set(malloc_succeed); 27 | swbuf_free_set(fake_free); 28 | 29 | struct swbuf buf; 30 | swbuf_init(&buf, 200); 31 | 32 | assert(swbuf_error(&buf) == 0); 33 | 34 | swbuf_deinit(&buf); 35 | } 36 | -------------------------------------------------------------------------------- /src/swbuf/tests/size.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "swbuf/swbuf.h" 5 | 6 | int main(void) 7 | { 8 | struct swbuf buf; 9 | swbuf_init(&buf, 20); 10 | 11 | assert(swbuf_size(&buf) == 20); 12 | 13 | swbuf_swap(&buf); 14 | 15 | // Shouldn't change because of a swap. 16 | assert(swbuf_size(&buf) == 20); 17 | 18 | swbuf_swap(&buf); 19 | 20 | // Back to the initial state. 21 | assert(swbuf_size(&buf) == 20); 22 | 23 | swbuf_deinit(&buf); 24 | } 25 | -------------------------------------------------------------------------------- /src/swbuf/tests/swap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "swbuf/swbuf.h" 6 | 7 | #define BUF_SIZE 2 8 | 9 | int main(void) 10 | { 11 | struct swbuf buf; 12 | swbuf_init(&buf, BUF_SIZE); 13 | 14 | // One side has 0s 15 | memset(swbuf_offset(&buf, 0), 0x00, BUF_SIZE); 16 | 17 | // The other has 1s 18 | memset(swbuf_offset(&buf, -BUF_SIZE), 0xFF, BUF_SIZE); 19 | 20 | swbuf_swap(&buf); 21 | 22 | for (int i = 0; i < BUF_SIZE; i++) { 23 | unsigned char *ptr = swbuf_offset(&buf, i); 24 | 25 | if (*ptr != 0xFF) { 26 | fprintf(stderr, "Offset %i does not equal 0xFF.", i); 27 | abort(); 28 | } 29 | } 30 | 31 | for (int i = -BUF_SIZE; i < 0; i++) { 32 | unsigned char *ptr = swbuf_offset(&buf, i); 33 | 34 | if (*ptr != 0x00) { 35 | fprintf(stderr, "Offset %i does not equal 0x00.", i); 36 | abort(); 37 | } 38 | } 39 | 40 | swbuf_deinit(&buf); 41 | } 42 | --------------------------------------------------------------------------------