├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── deps ├── error │ ├── build.cmake │ ├── error.c │ ├── error.h │ └── package.json ├── panic │ ├── build.cmake │ ├── package.json │ ├── panic.c │ └── panic.h └── process │ ├── build.cmake │ ├── package.json │ ├── process.c │ └── process.h ├── examples ├── build.cmake └── main.c ├── package.json ├── sources ├── build.cmake ├── watchdog.c └── watchdog.h └── valgrind.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | 35 | # CMake 36 | CMakeCache.txt 37 | CMakeFiles 38 | CMakeScripts 39 | Makefile 40 | cmake_install.cmake 41 | install_manifest.txt 42 | CTestTestfile.cmake 43 | !bin/.keep 44 | bin/* 45 | !lib/.keep 46 | lib/* 47 | build/ 48 | coverage/ 49 | cmake-build-*/ 50 | 51 | # IDEs and editors 52 | *.idea 53 | *.swp 54 | 55 | valgrind.log 56 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(watchdog C) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror") 6 | 7 | # dependencies 8 | include_directories(deps) 9 | include(deps/error/build.cmake) 10 | include(deps/panic/build.cmake) 11 | include(deps/process/build.cmake) 12 | 13 | # archive 14 | include_directories(sources) 15 | include(sources/build.cmake) 16 | 17 | # examples 18 | include(examples/build.cmake) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Davide Di Carlo 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Watchdog 2 | 3 | Watchdog is a C99-compliant runtime dynamically-allocated memory-tracer library that may come in handy at the 4 | development stage, during a memory leak hunting session, or while analyzing the memory bottle-neck of you program. 5 | 6 | ### How does it work? 7 | 8 | Watchdog fulfills only the following task: 9 | 10 | * log every usage of heap memory while the program is running. 11 | 12 | This allows, with a small overhead over performances, to maintain the whole "history" of the dynamic memory usage, that can be analyzed in a separate stage. 13 | At this point one can freely analyze the "history" by itself, alternatively [watchdog analyzer](https://github.com/daddinuz/watchdog_analyzer "watchdog_analyzer") can be used to ease this task. 14 | 15 | ### How to integrate? 16 | 17 | Watchdog is designed to be integrated simply into the existing code. 18 | One should just include "watchdog.h" instead of "stdlib.h" into the files that need to be traced. 19 | 20 | Watchdog does not trace external libraries, it only traces those ones in which it is included. 21 | 22 | ### How to turn it off? 23 | 24 | If NDEBUG is defined, watchdog is automatically disabled so that programs will run with zero overhead, 25 | using the standard allocators in "stdlib.h". 26 | 27 | ### Recommendations 28 | 29 | It is strongly recommended to use Watchdog only in pre-production stages. 30 | 31 | Useful links: 32 | * [Analyzer and TUI viewer](https://github.com/daddinuz/watchdog "watchdog") for watchdog reports. 33 | -------------------------------------------------------------------------------- /deps/error/build.cmake: -------------------------------------------------------------------------------- 1 | set(ARCHIVE_NAME error) 2 | message("${ARCHIVE_NAME}@${CMAKE_CURRENT_LIST_DIR} using: ${CMAKE_CURRENT_LIST_FILE}") 3 | 4 | file(GLOB ARCHIVE_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h) 5 | file(GLOB ARCHIVE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) 6 | add_library(${ARCHIVE_NAME} ${ARCHIVE_HEADERS} ${ARCHIVE_SOURCES}) 7 | -------------------------------------------------------------------------------- /deps/error/error.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include "error.h" 31 | 32 | const char *Error_explain(Error self) { 33 | assert(NULL != self); 34 | return self->__message; 35 | } 36 | 37 | Error Ok = Error_new("Ok"); 38 | Error DomainError = Error_new("Domain error"); 39 | Error IllegalState = Error_new("Illegal state"); 40 | Error LookupError = Error_new("Lookup error"); 41 | Error MathError = Error_new("Math error"); 42 | Error MemoryError = Error_new("Memory error"); 43 | Error NullReferenceError = Error_new("Null reference error"); 44 | Error OutOfMemory = Error_new("Out of memory"); 45 | Error SystemError = Error_new("System error"); 46 | Error StopIteration = Error_new("Stop iteration"); 47 | -------------------------------------------------------------------------------- /deps/error/error.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #if !(defined(__GNUC__) || defined(__clang__)) 31 | __attribute__(...) 32 | #endif 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | #define ERROR_VERSION_MAJOR 1 39 | #define ERROR_VERSION_MINOR 0 40 | #define ERROR_VERSION_PATCH 0 41 | #define ERROR_VERSION_SUFFIX "" 42 | #define ERROR_VERSION_IS_RELEASE 0 43 | #define ERROR_VERSION_HEX 0x010000 44 | 45 | /** 46 | * Represents errors that may occur at runtime. 47 | * Every error instance must be a tack allocated singleton. 48 | * The lifetime of every error is the whole program duration, in order to check if two errors are equal a simple 49 | * comparison between pointers can be done. 50 | * 51 | * @attention this struct must be treated as opaque therefore its members must not be accessed directly. 52 | */ 53 | typedef struct __Error { 54 | const char *const __message; 55 | } const *Error; 56 | 57 | /** 58 | * An helper macro used for type hinting, useful when writing interfaces. 59 | * By convention the annotations are the errors that may be returned. 60 | */ 61 | #define ErrorOf(...) \ 62 | Error 63 | 64 | /** 65 | * Helper macro to create new errors. 66 | * 67 | * @code 68 | * Error CustomError = Error_new("Custom error explanation"); 69 | * @endcode 70 | */ 71 | #define Error_new(message) \ 72 | ((Error) &((const struct __Error) {.__message=(message)})) 73 | 74 | /** 75 | * Gets the error message explanation. 76 | * 77 | * @attention self must not be `NULL`. 78 | */ 79 | extern const char *Error_explain(Error self) 80 | __attribute__((__warn_unused_result__, __nonnull__)); 81 | 82 | /** 83 | * Built-in errors 84 | */ 85 | extern Error Ok; // Notifies a successful execution. 86 | extern Error DomainError; // Indicates that a function has been passed illegal or inappropriate arguments 87 | extern Error IllegalState; // A function has been invoked at an illegal or inappropriate time 88 | extern Error LookupError; // A key or index used on a mapping or sequence is invalid 89 | extern Error MathError; // Arithmetic errors e.g. zero division 90 | extern Error MemoryError; // Memory related error, e.g. overlapping memory. Note: should not be used to notify OOM 91 | extern Error NullReferenceError; // Got a null reference where is not allowed. 92 | extern Error OutOfMemory; // The app ran out of memory 93 | extern Error SystemError; // System-related errors e.g. file not found 94 | extern Error StopIteration; // Indicates that the end of a sequence has been reached 95 | 96 | #ifdef __cplusplus 97 | } 98 | #endif 99 | -------------------------------------------------------------------------------- /deps/error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "error", 3 | "repo": "daddinuz/error", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "description": "Errors representation.", 7 | "keywords": [ 8 | "error-handling", 9 | "error" 10 | ], 11 | "src": [ 12 | "sources/error.c", 13 | "sources/error.h" 14 | ], 15 | "makefile": "sources/build.cmake" 16 | } 17 | -------------------------------------------------------------------------------- /deps/panic/build.cmake: -------------------------------------------------------------------------------- 1 | unset(PANIC_UNWIND_SUPPORT CACHE) 2 | unset(LIBUNWIND_FOUND CACHE) 3 | 4 | set(ARCHIVE_NAME panic) 5 | message("${ARCHIVE_NAME}@${CMAKE_CURRENT_LIST_DIR} using: ${CMAKE_CURRENT_LIST_FILE}") 6 | 7 | file(GLOB ARCHIVE_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h) 8 | file(GLOB ARCHIVE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) 9 | add_library(${ARCHIVE_NAME} ${ARCHIVE_HEADERS} ${ARCHIVE_SOURCES}) 10 | 11 | # Optional features 12 | option(PANIC_UNWIND_SUPPORT "Stack unwinding support" OFF) 13 | 14 | ################### 15 | # Private section 16 | #### 17 | 18 | # Find libunwind 19 | # - Try to find Libunwind 20 | # 21 | # Input variables: 22 | # LIBUNWIND_ROOT_DIR - The libunwind install directory 23 | # LIBUNWIND_INCLUDE_DIR - The libunwind include directory 24 | # LIBUNWIND_LIBRARY - The libunwind library directory 25 | # 26 | # Output variables: 27 | # LIBUNWIND_FOUND - System has libunwind 28 | # LIBUNWIND_INCLUDE_DIRS - The libunwind include directories 29 | # LIBUNWIND_LIBRARIES - The libraries needed to use libunwind 30 | # LIBUNWIND_VERSION - The version string for libunwind 31 | include(FindPackageHandleStandardArgs) 32 | 33 | if (NOT DEFINED LIBUNWIND_FOUND) 34 | 35 | # Set default sarch paths for libunwind 36 | if (LIBUNWIND_ROOT_DIR) 37 | set(LIBUNWIND_INCLUDE_DIR ${LIBUNWIND_ROOT_DIR}/include CACHE PATH "The include directory for libunwind") 38 | if (CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_NAME STREQUAL "Linux") 39 | set(LIBUNWIND_LIBRARY ${LIBUNWIND_ROOT_DIR}/lib64;${LIBUNWIND_ROOT_DIR}/lib CACHE PATH "The library directory for libunwind") 40 | else () 41 | set(LIBUNWIND_LIBRARY ${LIBUNWIND_ROOT_DIR}/lib CACHE PATH "The library directory for libunwind") 42 | endif () 43 | endif () 44 | 45 | find_path(LIBUNWIND_INCLUDE_DIRS NAMES libunwind.h 46 | HINTS ${LIBUNWIND_INCLUDE_DIR}) 47 | 48 | find_library(LIBUNWIND_LIBRARIES unwind 49 | HINTS ${LIBUNWIND_LIBRARY}) 50 | 51 | # Get libunwind version 52 | if (EXISTS "${LIBUNWIND_INCLUDE_DIRS}/libunwind-common.h") 53 | # file(READ "${LIBUNWIND_INCLUDE_DIRS}/libunwind-common.h" _libunwind_version_header) 54 | string(REGEX REPLACE ".*define[ \t]+UNW_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" 55 | LIBUNWIND_MAJOR_VERSION "${_libunwind_version_header}") 56 | string(REGEX REPLACE ".*define[ \t]+UNW_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" 57 | LIBUNWIND_MINOR_VERSION "${_libunwind_version_header}") 58 | string(REGEX REPLACE ".*define[ \t]+UNW_VERSION_EXTRA[ \t]+([0-9]+).*" "\\1" 59 | LIBUNWIND_MICRO_VERSION "${_libunwind_version_header}") 60 | set(LIBUNWIND_VERSION "${LIBUNWIND_MAJOR_VERSION}.${LIBUNWIND_MINOR_VERSION}.${LIBUNWIND_MICRO_VERSION}") 61 | unset(_libunwind_version_header) 62 | endif () 63 | 64 | # handle the QUIETLY and REQUIRED arguments and set LIBUNWIND_FOUND to TRUE 65 | # if all listed variables are TRUE 66 | find_package_handle_standard_args(libunwind 67 | FOUND_VAR LIBUNWIND_FOUND 68 | VERSION_VAR LIBUNWIND_VERSION 69 | REQUIRED_VARS LIBUNWIND_LIBRARIES LIBUNWIND_INCLUDE_DIRS) 70 | 71 | mark_as_advanced(LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARY 72 | LIBUNWIND_INCLUDE_DIRS LIBUNWIND_LIBRARIES) 73 | 74 | endif () 75 | 76 | if (PANIC_UNWIND_SUPPORT) 77 | if (LIBUNWIND_FOUND) 78 | target_link_libraries(${ARCHIVE_NAME} PRIVATE unwind) 79 | add_definitions(-DPANIC_UNWIND_SUPPORT=1) 80 | else () 81 | message(FATAL_ERROR "libunwind required for PANIC_UNWIND_SUPPORT feature but not found") 82 | endif () 83 | endif (PANIC_UNWIND_SUPPORT) 84 | -------------------------------------------------------------------------------- /deps/panic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "panic", 3 | "repo": "daddinuz/panic", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "description": "A panic library to abort execution on non-recoverable errors with a detailed message.", 7 | "keywords": [ 8 | "error-handling", 9 | "panic", 10 | "abort" 11 | ], 12 | "src": [ 13 | "sources/panic.h", 14 | "sources/panic.c" 15 | ], 16 | "development": { 17 | "daddinuz/traits": "3.2.0", 18 | "daddinuz/traits-unit": "3.0.0" 19 | }, 20 | "makefile": "sources/build.cmake" 21 | } 22 | -------------------------------------------------------------------------------- /deps/panic/panic.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "panic.h" 34 | 35 | static Panic_Callback globalCallback = NULL; 36 | 37 | static void terminate(const char *file, int line, const char *format, ...) 38 | __attribute__((__noinline__, __noreturn__, __nonnull__(1, 3), __format__(__printf__, 3, 4))); 39 | 40 | static void vterminate(const char *file, int line, const char *format, va_list args) 41 | __attribute__((__noinline__, __noreturn__, __nonnull__(1, 3), __format__(__printf__, 3, 0))); 42 | 43 | Panic_Callback Panic_registerCallback(const Panic_Callback callback) { 44 | const Panic_Callback backup = callback; 45 | globalCallback = callback; 46 | return backup; 47 | } 48 | 49 | void __Panic_terminate(const char *const file, const int line, const char *const format, ...) { 50 | assert(NULL != file); 51 | assert(NULL != format); 52 | va_list args; 53 | va_start(args, format); 54 | vterminate(file, line, format, args); 55 | } 56 | 57 | void __Panic_vterminate(const char *const file, const int line, const char *const format, va_list args) { 58 | assert(NULL != file); 59 | assert(NULL != format); 60 | vterminate(file, line, format, args); 61 | } 62 | 63 | void __Panic_when(const char *const file, const int line, const char *const message, const bool condition) { 64 | assert(NULL != file); 65 | assert(NULL != message); 66 | if (condition) { 67 | terminate(file, line, "(%s) evaluates to `true`", message); 68 | } 69 | } 70 | 71 | void __Panic_unless(const char *const file, const int line, const char *const message, const bool condition) { 72 | assert(NULL != file); 73 | assert(NULL != message); 74 | if (!condition) { 75 | terminate(file, line, "(%s) evaluates to `false`", message); 76 | } 77 | } 78 | 79 | /* 80 | * 81 | */ 82 | #define NEWLINE "\r\n" 83 | 84 | static void doTerminate(const char *file, int line, const char *format, va_list args) 85 | __attribute__((__noreturn__, __nonnull__(1, 3), __format__(__printf__, 3, 0))); 86 | 87 | static void backtrace(FILE *stream) 88 | __attribute__((__nonnull__)); 89 | 90 | void terminate(const char *file, int line, const char *format, ...) { 91 | assert(NULL != file); 92 | assert(NULL != format); 93 | va_list args; 94 | va_start(args, format); 95 | doTerminate(file, line, format, args); 96 | } 97 | 98 | void vterminate(const char *file, int line, const char *format, va_list args) { 99 | assert(NULL != file); 100 | assert(NULL != format); 101 | doTerminate(file, line, format, args); 102 | } 103 | 104 | void doTerminate(const char *file, int line, const char *format, va_list args) { 105 | assert(NULL != file); 106 | assert(NULL != format); 107 | fputs(NEWLINE, stderr); 108 | backtrace(stderr); 109 | fprintf(stderr, " At: %s:%d" NEWLINE, file, line); 110 | if (0 != errno) { 111 | fprintf(stderr, "Error: (%d) %s" NEWLINE, errno, strerror(errno)); 112 | } 113 | fputs("Cause: ", stderr); 114 | vfprintf(stderr, format, args); 115 | fputs(NEWLINE, stderr); 116 | va_end(args); 117 | if (NULL != globalCallback) { 118 | globalCallback(); 119 | } 120 | abort(); 121 | } 122 | 123 | #if !defined(PANIC_UNWIND_SUPPORT) && PANIC_UNWIND_SUPPORT == 0 124 | 125 | void backtrace(FILE *const stream) { 126 | assert(NULL != stream); 127 | (void) stream; 128 | } 129 | 130 | #else 131 | 132 | #define UNW_LOCAL_ONLY 133 | 134 | #include 135 | 136 | void backtrace(FILE *const stream) { 137 | assert(NULL != stream); 138 | const int previousError = errno; 139 | size_t size = 0; 140 | const size_t N_SIZE = 8, M_SIZE = 32; 141 | 142 | char buffer[N_SIZE][M_SIZE + 1]; 143 | unw_cursor_t cursor; 144 | unw_context_t context; 145 | 146 | memset(&context, 0, sizeof(context)); 147 | memset(&cursor, 0, sizeof(cursor)); 148 | memset(buffer, 0, N_SIZE * M_SIZE); 149 | 150 | unw_getcontext(&context); 151 | unw_init_local(&cursor, &context); 152 | 153 | // skip: (v)terminate and __Panic_(v)terminate function calls 154 | for (size_t i = 0; i < 3; i++) { 155 | if (unw_step(&cursor) <= 0) { 156 | errno = previousError; // restore errno 157 | return; // something wrong, exit 158 | } 159 | } 160 | 161 | for (size_t i = 0; i < N_SIZE && unw_step(&cursor) > 0; i++) { 162 | if (unw_get_proc_name(&cursor, buffer[i], M_SIZE, NULL) == 0) { 163 | size += 1; 164 | if (strcmp("main", buffer[i]) == 0) { 165 | break; 166 | } 167 | } else { 168 | break; // something wrong, stop unwinding 169 | } 170 | } 171 | 172 | if (0 == size) { 173 | errno = previousError; // restore errno 174 | return; // something wrong, exit 175 | } 176 | 177 | fputs("Traceback (most recent call last):" NEWLINE, stream); 178 | if (strcmp("main", buffer[size - 1]) != 0) { 179 | fputs(" [ ]: (...)" NEWLINE, stream); 180 | } 181 | for (size_t i = 1; i < size; i++) { 182 | fprintf(stream, " [%zu]: (%s)" NEWLINE, i - 1, buffer[size - i]); 183 | } 184 | fprintf(stream, " ->-: (%s) current function" NEWLINE, buffer[0]); 185 | fputs(NEWLINE, stream); 186 | 187 | errno = previousError; // restore errno 188 | } 189 | 190 | #endif 191 | -------------------------------------------------------------------------------- /deps/panic/panic.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | #include 32 | 33 | #if !(defined(__GNUC__) || defined(__clang__)) 34 | __attribute__(...) 35 | #endif 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | #define PANIC_VERSION_MAJOR 1 42 | #define PANIC_VERSION_MINOR 0 43 | #define PANIC_VERSION_PATCH 0 44 | #define PANIC_VERSION_SUFFIX "" 45 | #define PANIC_VERSION_IS_RELEASE 0 46 | #define PANIC_VERSION_HEX 0x010000 47 | 48 | /** 49 | * Type signature of the callback to be executed before terminating. 50 | */ 51 | typedef void (*Panic_Callback)(void); 52 | 53 | /** 54 | * Registers a callback to execute before terminating. 55 | * 56 | * @param callback The callback to be executed, if NULL nothing will be executed. 57 | * @return The previous registered callback if any else NULL. 58 | */ 59 | extern Panic_Callback Panic_registerCallback(Panic_Callback callback); 60 | 61 | /** 62 | * Reports the error and terminates execution. 63 | * Takes printf-like arguments. 64 | */ 65 | #define Panic_terminate(...) \ 66 | __Panic_terminate((__FILE__), (__LINE__), __VA_ARGS__) 67 | 68 | /** 69 | * Terminates execution if condition is `true`. 70 | */ 71 | #define Panic_when(condition) \ 72 | __Panic_when((__FILE__), (__LINE__), (#condition), (condition)) 73 | 74 | /** 75 | * Terminates execution if condition is `false`. 76 | */ 77 | #define Panic_unless(condition) \ 78 | __Panic_unless((__FILE__), (__LINE__), (#condition), (condition)) 79 | 80 | /** 81 | * @attention this function must be treated as opaque therefore should not be called directly. 82 | */ 83 | extern void __Panic_terminate(const char *file, int line, const char *format, ...) 84 | __attribute__((__noinline__, __noreturn__, __nonnull__(1, 3), __format__(__printf__, 3, 4))); 85 | 86 | /** 87 | * @attention this function must be treated as opaque therefore should not be called directly. 88 | */ 89 | extern void __Panic_vterminate(const char *file, int line, const char *format, va_list args) 90 | __attribute__((__noinline__, __noreturn__, __nonnull__(1, 3), __format__(__printf__, 3, 0))); 91 | 92 | /** 93 | * @attention this function must be treated as opaque therefore should not be called directly. 94 | */ 95 | extern void __Panic_when(const char *file, int line, const char *message, bool condition) 96 | __attribute__((__noinline__, __nonnull__(1, 3))); 97 | 98 | /** 99 | * @attention this function must be treated as opaque therefore should not be called directly. 100 | */ 101 | extern void __Panic_unless(const char *file, int line, const char *message, bool condition) 102 | __attribute__((__noinline__, __nonnull__(1, 3))); 103 | 104 | #ifdef __cplusplus 105 | } 106 | #endif 107 | -------------------------------------------------------------------------------- /deps/process/build.cmake: -------------------------------------------------------------------------------- 1 | set(ARCHIVE_NAME process) 2 | message("${ARCHIVE_NAME}@${CMAKE_CURRENT_LIST_DIR} using: ${CMAKE_CURRENT_LIST_FILE}") 3 | 4 | file(GLOB ARCHIVE_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h) 5 | file(GLOB ARCHIVE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) 6 | add_library(${ARCHIVE_NAME} ${ARCHIVE_HEADERS} ${ARCHIVE_SOURCES}) 7 | target_link_libraries(${ARCHIVE_NAME} PRIVATE error panic) 8 | -------------------------------------------------------------------------------- /deps/process/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "process", 3 | "repo": "daddinuz/process", 4 | "version": "0.3.0", 5 | "license": "MIT", 6 | "description": "Spawn and intercommunication between processes.", 7 | "keywords": [ 8 | "process" 9 | ], 10 | "src": [ 11 | "sources/process.h", 12 | "sources/process.c" 13 | ], 14 | "dependencies": { 15 | "daddinuz/error": "1.0.0", 16 | "daddinuz/panic": "1.0.0" 17 | }, 18 | "makefile": "sources/build.cmake" 19 | } 20 | -------------------------------------------------------------------------------- /deps/process/process.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "process.h" 36 | 37 | #define MAGIC_NUMBER 0xdeadbeaf 38 | 39 | #define expect(condition, ...) \ 40 | __expect((__FILE__), (__LINE__), (condition), __VA_ARGS__) 41 | 42 | #define ensure(condition) \ 43 | expect((condition), (NULL)) 44 | 45 | #define die() \ 46 | ensure(false) 47 | 48 | /* 49 | * Miscellaneous 50 | */ 51 | static void __expect(const char *file, int line, bool condition, const char *format, ...) 52 | __attribute__((__nonnull__(1), __format__(__printf__, 4, 5))); 53 | 54 | static void moveFileDescriptor(int fd1, int fd2); 55 | 56 | /* 57 | * Pipe 58 | */ 59 | union Pipe { 60 | int fileDescriptors[2]; 61 | struct { int inputFileDescriptor, outputFileDescriptor; }; 62 | }; 63 | 64 | static void Pipe_open(union Pipe *self) 65 | __attribute__((__nonnull__)); 66 | 67 | /* 68 | * Process 69 | */ 70 | const Error Process_UnableToFork = Error_new("Unable to fork"); 71 | 72 | static void Process_setExitInfoFromStatus(struct Process *self, int status) 73 | __attribute__((__nonnull__)); 74 | 75 | Error Process_spawn(struct Process *const self, void (*const f)(void)) { 76 | assert(self); 77 | assert(f); 78 | pid_t pid; 79 | union Pipe pipeStderr, pipeStdout, pipeStdin; 80 | 81 | fflush(NULL); 82 | Pipe_open(&pipeStderr); 83 | Pipe_open(&pipeStdout); 84 | Pipe_open(&pipeStdin); 85 | 86 | switch (pid = fork()) { 87 | case -1: { // error 88 | return Process_UnableToFork; 89 | } 90 | case 0: { // child process 91 | fflush(NULL); 92 | const int stdinFileDescriptor = fileno(stdin); 93 | const int stderrFileDescriptor = fileno(stderr); 94 | const int stdoutFileDescriptor = fileno(stdout); 95 | 96 | ensure(close(stdinFileDescriptor) == 0); 97 | ensure(close(stderrFileDescriptor) == 0); 98 | ensure(close(stdoutFileDescriptor) == 0); 99 | 100 | ensure(close(pipeStdin.outputFileDescriptor) == 0); 101 | ensure(close(pipeStderr.inputFileDescriptor) == 0); 102 | ensure(close(pipeStdout.inputFileDescriptor) == 0); 103 | 104 | moveFileDescriptor(pipeStdin.inputFileDescriptor, stdinFileDescriptor); 105 | moveFileDescriptor(pipeStderr.outputFileDescriptor, stderrFileDescriptor); 106 | moveFileDescriptor(pipeStdout.outputFileDescriptor, stdoutFileDescriptor); 107 | 108 | fflush(NULL); 109 | f(); 110 | fflush(NULL); 111 | _exit(0); 112 | } 113 | default: { // parent process 114 | fflush(NULL); 115 | ensure(close(pipeStdin.inputFileDescriptor) == 0); 116 | ensure(close(pipeStderr.outputFileDescriptor) == 0); 117 | ensure(close(pipeStdout.outputFileDescriptor) == 0); 118 | #ifndef NDEBUG 119 | self->_magicNumber = MAGIC_NUMBER; 120 | #endif 121 | self->_id = pid; 122 | self->_inputFileDescriptor = pipeStdin.outputFileDescriptor; 123 | self->_errorFileDescriptor = pipeStderr.inputFileDescriptor; 124 | self->_outputFileDescriptor = pipeStdout.inputFileDescriptor; 125 | self->_exitValue = 0; 126 | self->_isAlive = true; 127 | self->_exitNormally = false; 128 | return Ok; 129 | } 130 | } 131 | } 132 | 133 | Error Process_wait(struct Process *const self, struct Process_ExitInfo *const out) { 134 | assert(self); 135 | assert(self->_magicNumber == MAGIC_NUMBER); 136 | 137 | if (self->_isAlive) { 138 | int status; 139 | ensure(waitpid(self->_id, &status, 0) == self->_id); 140 | 141 | Process_setExitInfoFromStatus(self, status); 142 | 143 | if (out) { 144 | const Error e = Process_exitInfo(self, out); 145 | assert(e == Ok); 146 | (void) e; 147 | } 148 | return Ok; 149 | } else { 150 | return IllegalState; 151 | } 152 | } 153 | 154 | Error Process_cancel(struct Process *const self, struct Process_ExitInfo *const out) { 155 | assert(self); 156 | assert(self->_magicNumber == MAGIC_NUMBER); 157 | 158 | if (self->_isAlive) { 159 | int status; // this must not be initialized 160 | const pid_t pid = self->_id; 161 | 162 | for (int i = 0; (i < 8) && (kill(pid, SIGTERM) != 0); i++) {} 163 | for (int i = 0; i < 2; i++) { 164 | const int w = waitpid(pid, &status, WNOHANG); 165 | if (w == pid) { 166 | Process_setExitInfoFromStatus(self, status); 167 | if (out) { 168 | const Error e = Process_exitInfo(self, out); 169 | assert(e == Ok); 170 | (void) e; 171 | } 172 | return Ok; 173 | } else if (w == 0) { 174 | sleep(1); 175 | } else { 176 | die(); 177 | } 178 | } 179 | 180 | for (int i = 0; (i < 8) && (kill(pid, SIGKILL) != 0); i++) {} 181 | ensure(waitpid(pid, &status, 0) == pid); 182 | Process_setExitInfoFromStatus(self, status); 183 | if (out) { 184 | const Error e = Process_exitInfo(self, out); 185 | assert(e == Ok); 186 | (void) e; 187 | } 188 | return Ok; 189 | } else { 190 | return IllegalState; 191 | } 192 | } 193 | 194 | Error Process_exitInfo(const struct Process *const self, struct Process_ExitInfo *const out) { 195 | assert(self); 196 | assert(self->_magicNumber == MAGIC_NUMBER); 197 | assert(out); 198 | 199 | if (self->_isAlive) { 200 | return IllegalState; 201 | } else { 202 | out->exitNormally = self->_exitNormally; 203 | out->exitValue = self->_exitValue; 204 | return Ok; 205 | } 206 | } 207 | 208 | long Process_writeInputStream(struct Process *const self, const char *const buffer, const size_t size) { 209 | assert(self); 210 | assert(self->_magicNumber == MAGIC_NUMBER); 211 | assert(buffer); 212 | assert(size != SIZE_MAX); 213 | return write(self->_inputFileDescriptor, buffer, size); 214 | } 215 | 216 | long Process_readOutputStream(struct Process *const self, char *const buffer, const size_t size) { 217 | assert(self); 218 | assert(self->_magicNumber == MAGIC_NUMBER); 219 | assert(buffer); 220 | assert(size != SIZE_MAX); 221 | return read(self->_outputFileDescriptor, buffer, size); 222 | } 223 | 224 | long Process_readErrorStream(struct Process *const self, char *const buffer, const size_t size) { 225 | assert(self); 226 | assert(self->_magicNumber == MAGIC_NUMBER); 227 | assert(buffer); 228 | assert(size != SIZE_MAX); 229 | return read(self->_errorFileDescriptor, buffer, size); 230 | } 231 | 232 | int Process_id(const struct Process *const self) { 233 | assert(self); 234 | assert(self->_magicNumber == MAGIC_NUMBER); 235 | return self->_id; 236 | } 237 | 238 | bool Process_isAlive(struct Process *const self) { 239 | assert(self); 240 | assert(self->_magicNumber == MAGIC_NUMBER); 241 | if (self->_isAlive) { 242 | int status; 243 | const pid_t pid = self->_id; 244 | 245 | const int w = waitpid(pid, &status, WNOHANG); 246 | ensure(w >= 0); 247 | if (w == pid) { 248 | Process_setExitInfoFromStatus(self, status); 249 | } 250 | } 251 | return self->_isAlive; 252 | } 253 | 254 | void Process_teardown(struct Process *const self) { 255 | assert(self); 256 | assert(self->_magicNumber == MAGIC_NUMBER); 257 | assert(!self->_isAlive); 258 | ensure(close(self->_inputFileDescriptor) == 0); 259 | ensure(close(self->_errorFileDescriptor) == 0); 260 | ensure(close(self->_outputFileDescriptor) == 0); 261 | memset(self, 0, sizeof(*self)); 262 | } 263 | 264 | int Process_getCurrentId(void) { 265 | return getpid(); 266 | } 267 | 268 | int Process_getParentId(void) { 269 | return getppid(); 270 | } 271 | 272 | void Process_sleep(unsigned seconds) { 273 | sleep(seconds); 274 | } 275 | 276 | void Process_setExitInfoFromStatus(struct Process *const self, const int status) { 277 | assert(self); 278 | assert(self->_magicNumber == MAGIC_NUMBER); 279 | assert(self->_isAlive); 280 | if (WIFEXITED(status)) { 281 | self->_exitNormally = true; 282 | self->_exitValue = WEXITSTATUS(status); 283 | } else if (WIFSIGNALED(status)) { 284 | self->_exitNormally = false; 285 | self->_exitValue = WTERMSIG(status); 286 | } else { 287 | die(); 288 | } 289 | self->_isAlive = false; 290 | } 291 | 292 | /* 293 | * Miscellaneous 294 | */ 295 | void __expect(const char *const file, const int line, const bool condition, const char *const format, ...) { 296 | if (!condition) { 297 | if (format) { 298 | va_list args; 299 | va_start(args, format); 300 | __Panic_vterminate(file, line, format, args); 301 | } else { 302 | __Panic_terminate(file, line, "Unexpected error"); 303 | } 304 | } 305 | } 306 | 307 | void moveFileDescriptor(const int fd1, const int fd2) { 308 | while (dup2(fd1, fd2) == -1) { 309 | ensure(EINTR == errno); 310 | } 311 | ensure(close(fd1) == 0); 312 | } 313 | 314 | /* 315 | * Pipe 316 | */ 317 | void Pipe_open(union Pipe *const self) { 318 | assert(self); 319 | expect(pipe(self->fileDescriptors) == 0, "Unable to open pipe"); 320 | } 321 | -------------------------------------------------------------------------------- /deps/process/process.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #if !(defined(__GNUC__) || defined(__clang__)) 36 | __attribute__(...) 37 | #endif 38 | 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | #define PROCESS_VERSION_MAJOR 0 44 | #define PROCESS_VERSION_MINOR 3 45 | #define PROCESS_VERSION_PATCH 0 46 | #define PROCESS_VERSION_SUFFIX "" 47 | #define PROCESS_VERSION_IS_RELEASE 0 48 | #define PROCESS_VERSION_HEX 0x000300 49 | 50 | extern const Error Process_UnableToFork; 51 | 52 | struct Process_ExitInfo { 53 | int exitValue; 54 | bool exitNormally; 55 | }; 56 | 57 | struct Process { 58 | /* Do not access these members directly! */ 59 | #ifndef NDEBUG 60 | long _magicNumber; 61 | #endif 62 | int _id; 63 | int _inputFileDescriptor; 64 | int _errorFileDescriptor; 65 | int _outputFileDescriptor; 66 | int _exitValue; 67 | bool _exitNormally; 68 | bool _isAlive; 69 | }; 70 | 71 | extern ErrorOf(Ok, Process_UnableToFork) Process_spawn(struct Process *self, void (*f)(void)) 72 | __attribute__((__warn_unused_result__, __nonnull__)); 73 | 74 | extern ErrorOf(Ok, IllegalState) Process_wait(struct Process *self, struct Process_ExitInfo *out) 75 | __attribute__((__warn_unused_result__, __nonnull__(1))); 76 | 77 | extern ErrorOf(Ok, IllegalState) Process_cancel(struct Process *self, struct Process_ExitInfo *out) 78 | __attribute__((__warn_unused_result__, __nonnull__(1))); 79 | 80 | extern ErrorOf(Ok, IllegalState) Process_exitInfo(const struct Process *self, struct Process_ExitInfo *out) 81 | __attribute__((__warn_unused_result__, __nonnull__)); 82 | 83 | extern long Process_writeInputStream(struct Process *self, const char *buffer, size_t size) 84 | __attribute__((__warn_unused_result__, __nonnull__)); 85 | 86 | extern long Process_readOutputStream(struct Process *self, char *buffer, size_t size) 87 | __attribute__((__warn_unused_result__, __nonnull__)); 88 | 89 | extern long Process_readErrorStream(struct Process *self, char *buffer, size_t size) 90 | __attribute__((__warn_unused_result__, __nonnull__)); 91 | 92 | extern int Process_id(const struct Process *self) 93 | __attribute__((__warn_unused_result__, __nonnull__)); 94 | 95 | extern bool Process_isAlive(struct Process *self) 96 | __attribute__((__warn_unused_result__, __nonnull__)); 97 | 98 | extern void Process_teardown(struct Process *self) 99 | __attribute__((__nonnull__)); 100 | 101 | extern int Process_getCurrentId(void) 102 | __attribute__((__warn_unused_result__)); 103 | 104 | extern int Process_getParentId(void) 105 | __attribute__((__warn_unused_result__)); 106 | 107 | extern void Process_sleep(unsigned seconds); 108 | 109 | #ifdef __cplusplus 110 | } 111 | #endif 112 | -------------------------------------------------------------------------------- /examples/build.cmake: -------------------------------------------------------------------------------- 1 | add_executable(main ${CMAKE_CURRENT_LIST_DIR}/main.c) 2 | target_link_libraries(main PRIVATE watchdog process) 3 | -------------------------------------------------------------------------------- /examples/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define BUFFER_SIZE 64 36 | 37 | void leaking(void) { 38 | char *buffer = calloc(BUFFER_SIZE + 1, sizeof(buffer[0])); 39 | snprintf(buffer, BUFFER_SIZE, "%d:%lu", Process_getCurrentId(), time(NULL)); 40 | printf("%s", buffer); 41 | Process_sleep(5); 42 | } 43 | 44 | void nonLeaking(void) { 45 | char *buffer = calloc(BUFFER_SIZE / 3, sizeof(buffer[0])); 46 | buffer = realloc(buffer, (BUFFER_SIZE + 1) * sizeof(buffer[0])); 47 | snprintf(buffer, BUFFER_SIZE, "%d:%lu", Process_getCurrentId(), time(NULL)); 48 | printf("%s", buffer); 49 | free(buffer); 50 | Process_sleep(2); 51 | } 52 | 53 | int main(void) { 54 | Error error; 55 | char *buffer = malloc((BUFFER_SIZE + 1) * sizeof(buffer[0])); 56 | struct Process leakingProcess, nonLeakingProcess; 57 | struct Process_ExitInfo info; 58 | long bytesRead; 59 | 60 | { // spawn leaking process 61 | if (Process_spawn(&leakingProcess, leaking) != Ok) { 62 | Panic_terminate("Unable to fork"); 63 | } 64 | printf("Spawned process: %d\n", Process_id(&leakingProcess)); 65 | } 66 | 67 | { // spawn non leaking process 68 | if (Process_spawn(&nonLeakingProcess, nonLeaking) != Ok) { 69 | Panic_terminate("Unable to fork"); 70 | } 71 | printf("Spawned process: %d\n", Process_id(&nonLeakingProcess)); 72 | } 73 | 74 | { // wait leaking process 75 | error = Process_isAlive(&leakingProcess) ? 76 | Process_wait(&leakingProcess, &info) : 77 | Process_exitInfo(&leakingProcess, &info); 78 | if (error != Ok) { 79 | Panic_terminate("%s", Error_explain(error)); 80 | } 81 | bytesRead = Process_readOutputStream(&leakingProcess, buffer, BUFFER_SIZE); 82 | if (bytesRead < 0) { 83 | Panic_terminate("Unexpected error while reading from output stream of process: %d", 84 | Process_id(&leakingProcess)); 85 | } 86 | printf("Process: %d exitNormally: %d exitValue: %2d output: %.*s\n", 87 | Process_id(&leakingProcess), info.exitNormally, info.exitValue, (int) bytesRead, buffer); 88 | } 89 | 90 | { // wait non leaking process 91 | error = Process_isAlive(&nonLeakingProcess) ? 92 | Process_wait(&nonLeakingProcess, &info) : 93 | Process_exitInfo(&nonLeakingProcess, &info); 94 | if (error != Ok) { 95 | Panic_terminate("%s", Error_explain(error)); 96 | } 97 | bytesRead = Process_readOutputStream(&nonLeakingProcess, buffer, BUFFER_SIZE); 98 | if (bytesRead < 0) { 99 | Panic_terminate("Unexpected error while reading from output stream of process: %d", 100 | Process_id(&nonLeakingProcess)); 101 | } 102 | printf("Process: %d exitNormally: %d exitValue: %2d output: %.*s\n", 103 | Process_id(&nonLeakingProcess), info.exitNormally, info.exitValue, (int) bytesRead, buffer); 104 | } 105 | 106 | Process_teardown(&nonLeakingProcess); 107 | Process_teardown(&leakingProcess); 108 | free(buffer); 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watchdog", 3 | "repo": "daddinuz/watchdog", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "description": "C99-compliant runtime memory tracer library useful to find memory leaks or analyze memory usage.", 7 | "keywords": [ 8 | "leaks", 9 | "memory", 10 | "memory-leaks", 11 | "memory-tracer", 12 | "memory-leak-detection" 13 | ], 14 | "src": [ 15 | "sources/watchdog.h", 16 | "sources/watchdog.c" 17 | ], 18 | "dependencies": { 19 | "daddinuz/process": "0.3.0", 20 | "daddinuz/panic": "1.0.0" 21 | }, 22 | "makefile": "sources/build.cmake" 23 | } 24 | -------------------------------------------------------------------------------- /sources/build.cmake: -------------------------------------------------------------------------------- 1 | unset(WATCHDOG_FORCE_OVERRIDE CACHE) 2 | 3 | set(ARCHIVE_NAME watchdog) 4 | message("${ARCHIVE_NAME}@${CMAKE_CURRENT_LIST_DIR} using: ${CMAKE_CURRENT_LIST_FILE}") 5 | 6 | file(GLOB ARCHIVE_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h) 7 | file(GLOB ARCHIVE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) 8 | add_library(${ARCHIVE_NAME} ${ARCHIVE_HEADERS} ${ARCHIVE_SOURCES}) 9 | target_link_libraries(${ARCHIVE_NAME} PRIVATE panic process) 10 | 11 | # Optional features 12 | option(WATCHDOG_FORCE_OVERRIDE "Force standard library allocators overriding" OFF) 13 | 14 | if (WATCHDOG_FORCE_OVERRIDE) 15 | target_compile_definitions(${ARCHIVE_NAME} PUBLIC WATCHDOG_FORCE_OVERRIDE=1) 16 | else () 17 | target_compile_definitions(${ARCHIVE_NAME} PUBLIC WATCHDOG_FORCE_OVERRIDE=0) 18 | endif (WATCHDOG_FORCE_OVERRIDE) 19 | -------------------------------------------------------------------------------- /sources/watchdog.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include "watchdog.h" 29 | 30 | /* 31 | * Un-define overrides over stdlib.h 32 | */ 33 | #if WATCHDOG_HAS_C11_SUPPORT 34 | # undef aligned_alloc 35 | #endif 36 | #undef malloc 37 | #undef calloc 38 | #undef realloc 39 | #undef free 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | /* 48 | * Global variables 49 | */ 50 | static FILE *gStream = NULL; 51 | 52 | /* 53 | * Watchdog 54 | */ 55 | static void Watchdog_report(const char *call, const char *file, const char *func, int line, 56 | const void *relocated, const void *address, size_t size); 57 | 58 | #if WATCHDOG_HAS_C11_SUPPORT 59 | 60 | void *__Watchdog_aligned_alloc(const char *const file, const char *const func, const int line, 61 | const size_t alignment, const size_t size) { 62 | assert(NULL != file); 63 | void *address = aligned_alloc(alignment, size); 64 | Watchdog_report("aligned_alloc", file, func, line, NULL, address, size); 65 | return address; 66 | } 67 | 68 | #endif 69 | 70 | void *__Watchdog_malloc(const char *const file, const char *const func, const int line, const size_t size) { 71 | assert(NULL != file); 72 | void *address = malloc(size); 73 | Watchdog_report("malloc", file, func, line, NULL, address, size); 74 | return address; 75 | } 76 | 77 | void *__Watchdog_calloc(const char *const file, const char *const func, const int line, 78 | const size_t numberOfMembers, const size_t memberSize) { 79 | assert(NULL != file); 80 | void *address = calloc(numberOfMembers, memberSize); 81 | Watchdog_report("calloc", file, func, line, NULL, address, numberOfMembers * memberSize); 82 | return address; 83 | } 84 | 85 | void *__Watchdog_realloc(const char *const file, const char *const func, const int line, 86 | void *const memory, const size_t newSize) { 87 | assert(NULL != file); 88 | void *address = realloc(memory, newSize); 89 | Watchdog_report("realloc", file, func, line, memory, address, newSize); 90 | return address; 91 | } 92 | 93 | void __Watchdog_free(const char *const file, const char *const func, const int line, void *const memory) { 94 | assert(NULL != file); 95 | free(memory); 96 | Watchdog_report("free", file, func, line, NULL, memory, 0); 97 | } 98 | 99 | /* 100 | * 101 | */ 102 | static void Watchdog_onExit(void) { 103 | if (NULL != gStream) { 104 | fclose(gStream); 105 | } 106 | } 107 | 108 | void Watchdog_report(const char *const call, const char *const file, const char *const func, const int line, 109 | const void *const relocated, const void *const address, const size_t size) { 110 | assert(NULL != call); 111 | assert(NULL != file); 112 | assert(NULL != func); 113 | assert(NULL != address); 114 | 115 | if (NULL == gStream) { 116 | char fileName[65] = ""; 117 | snprintf(fileName, 64, ".watchdog-%d-%lu.jsonl", WATCHDOG_VERSION_HEX, time(NULL)); 118 | gStream = fopen(fileName, "w"); 119 | if (NULL == gStream) { 120 | Panic_terminate("Unable to open file: %s", fileName); 121 | } 122 | atexit(Watchdog_onExit); 123 | } 124 | 125 | const long PID = Process_getCurrentId(), parentPID = Process_getParentId(), timestamp = time(NULL); 126 | if (NULL != relocated) { 127 | fprintf(gStream, 128 | "{\"PID\": %ld, \"parentPID\": %ld, \"call\": \"%s\", \"file\": \"%s\", \"func\": \"%s\", \"line\": %d, \"address\": {\"from\": \"%p\", \"to\": \"%p\"}, \"size\": %zu, \"timestamp\": %lu}\n", 129 | PID, parentPID, call, file, func, line, relocated, address, size, timestamp); 130 | } else { 131 | fprintf(gStream, 132 | "{\"PID\": %ld, \"parentPID\": %ld, \"call\": \"%s\", \"file\": \"%s\", \"func\": \"%s\", \"line\": %d, \"address\": \"%p\", \"size\": %zu, \"timestamp\": %lu}\n", 133 | PID, parentPID, call, file, func, line, address, size, timestamp); 134 | } 135 | 136 | fflush(gStream); 137 | } 138 | -------------------------------------------------------------------------------- /sources/watchdog.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: daddinuz 3 | email: daddinuz@gmail.com 4 | 5 | Copyright (c) 2018 Davide Di Carlo 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | 32 | #if !(defined(__GNUC__) || defined(__clang__)) 33 | __attribute__(...) 34 | #endif 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | #define WATCHDOG_VERSION_MAJOR 1 41 | #define WATCHDOG_VERSION_MINOR 0 42 | #define WATCHDOG_VERSION_PATCH 0 43 | #define WATCHDOG_VERSION_SUFFIX "" 44 | #define WATCHDOG_VERSION_IS_RELEASE 0 45 | #define WATCHDOG_VERSION_HEX 0x010000 46 | 47 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || (defined(__cplusplus) && __cplusplus >= 201103L) 48 | # define WATCHDOG_HAS_C11_SUPPORT 1 49 | #else 50 | # define WATCHDOG_HAS_C11_SUPPORT 0 51 | #endif 52 | 53 | #if WATCHDOG_HAS_C11_SUPPORT 54 | 55 | /** 56 | * Same as aligned_alloc from since C11 57 | * 58 | * @attention this function must be treated as opaque therefore should not be called directly, use the macro below instead. 59 | */ 60 | extern void *__Watchdog_aligned_alloc(const char *file, const char *func, int line, size_t alignment, size_t size) 61 | __attribute__((__warn_unused_result__, __nonnull__(1))); 62 | 63 | # define Watchdog_aligned_alloc(alignment, size) \ 64 | __Watchdog_aligned_alloc((__FILE__), (__func__), (__LINE__), (alignment), (size)) 65 | 66 | #endif 67 | 68 | /** 69 | * Same as malloc from 70 | * 71 | * @attention this function must be treated as opaque therefore should not be called directly, use the macro below instead. 72 | */ 73 | extern void *__Watchdog_malloc(const char *file, const char *func, int line, size_t size) 74 | __attribute__((__warn_unused_result__, __nonnull__(1))); 75 | 76 | #define Watchdog_malloc(size) \ 77 | __Watchdog_malloc((__FILE__), (__func__), (__LINE__), (size)) 78 | 79 | /** 80 | * Same as calloc from 81 | * 82 | * @attention this function must be treated as opaque therefore should not be called directly, use the macro below instead. 83 | */ 84 | extern void *__Watchdog_calloc(const char *file, const char *func, int line, size_t numberOfMembers, size_t memberSize) 85 | __attribute__((__warn_unused_result__, __nonnull__(1))); 86 | 87 | #define Watchdog_calloc(numberOfMembers, memberSize) \ 88 | __Watchdog_calloc((__FILE__), (__func__), (__LINE__), (numberOfMembers), (memberSize)) 89 | 90 | /** 91 | * Same as realloc from 92 | * 93 | * @attention this function must be treated as opaque therefore should not be called directly, use the macro below instead. 94 | */ 95 | extern void *__Watchdog_realloc(const char *file, const char *func, int line, void *memory, size_t newSize) 96 | __attribute__((__warn_unused_result__, __nonnull__(1))); 97 | 98 | #define Watchdog_realloc(memory, newSize) \ 99 | __Watchdog_realloc((__FILE__), (__func__), (__LINE__), (memory), (newSize)) 100 | 101 | /** 102 | * Same as free from 103 | * 104 | * @attention this function must be treated as opaque therefore should not be called directly, use the macro below instead. 105 | */ 106 | extern void __Watchdog_free(const char *file, const char *func, int line, void *memory) 107 | __attribute__((__nonnull__(1))); 108 | 109 | #define Watchdog_free(memory) \ 110 | __Watchdog_free((__FILE__), (__func__), (__LINE__), (memory)) 111 | 112 | /* 113 | * Macros 114 | */ 115 | #if WATCHDOG_FORCE_OVERRIDE || !defined(NDEBUG) 116 | # if WATCHDOG_HAS_C11_SUPPORT 117 | # undef aligned_alloc 118 | # define aligned_alloc(alignment, size) Watchdog_aligned_alloc((alignment), (size)) 119 | # endif 120 | # undef malloc 121 | # define malloc(size) Watchdog_malloc((size)) 122 | # undef calloc 123 | # define calloc(numberOfMembers, memberSize) Watchdog_calloc((numberOfMembers), (memberSize)) 124 | # undef realloc 125 | # define realloc(memory, newSize) Watchdog_realloc((memory), (newSize)) 126 | # undef free 127 | # define free(memory) Watchdog_free((memory)) 128 | #endif 129 | 130 | #ifdef __cplusplus 131 | } 132 | #endif 133 | -------------------------------------------------------------------------------- /valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | valgrind --trace-children=yes --track-origins=yes --leak-check=full --show-leak-kinds=all --log-file=valgrind.log "${@}" 5 | --------------------------------------------------------------------------------