From 4acca12450a50772b32a82389766922d2c0e7e4a Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Fri, 24 May 2024 19:49:35 +0100 Subject: [PATCH 1/3] API: Export a `hu_report()` function for requesting reports on demand The tool currently only produces reports on a clean exit from the analyzed program, which is not always achievable (daemons, programs crashing, etc.). An "on demand" report requested from within the application itself has proven useful in debugging scenarios, e.g. by attaching a debugger to the analyzed program and then just call a routine from there for producing a `heapusage` report. This way, extend `heapusage` with a public API, currently only having a `hu_report()` function for generating a `heapusage` report as done from `log_summary()`. The new API files are `huapi.h` and `huapi.cpp`. The header file first gets installed in the standard include dir so other SW modules can use it. The source file is added to the library's link step. The `log_summary()` call for generating the report from `hu_report()` is wrapped by a new `log_summary_safe()` function, so that `heapusage` analysis is temporarily disabled when actually calling `log_summary()`, otherwise `log_summary()` itself, which does memory allocations, would interfere with the analysis. This problem didn't exist before, because `log_summary()` was only being called from the exit handler, which also disables the analysis before calling `log_summary()`. Also add an internal global `log_message()` function, useful for logging generic messages into the `heapusage` log. Update `README.md` with this new usage possibility. Signed-off-by: Ricardo Silva --- CMakeLists.txt | 7 +++++-- README.md | 6 ++++++ src/huapi.cpp | 22 ++++++++++++++++++++++ src/huapi.h | 27 +++++++++++++++++++++++++++ src/hulog.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/hulog.h | 2 ++ 6 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/huapi.cpp create mode 100644 src/huapi.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d062266..f3e1746 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,9 +23,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Library -add_library(heapusage SHARED src/humain.cpp src/hulog.cpp src/humalloc.cpp) +add_library(heapusage SHARED src/humain.cpp src/hulog.cpp src/humalloc.cpp src/huapi.cpp) +set_target_properties(heapusage PROPERTIES PUBLIC_HEADER "src/huapi.h") target_compile_features(heapusage PRIVATE cxx_variadic_templates) -install(TARGETS heapusage LIBRARY DESTINATION lib) +install(TARGETS heapusage + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) target_link_libraries(heapusage pthread dl) # Dependency backward-cpp providing more detailed stacktraces on Linux, when diff --git a/README.md b/README.md index 817083d..3f8c0b1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,12 @@ Examples: heapusage -t all -m 0 ./ex002 analyze heap allocations of any size with all tools. +Programs being ran with Heapusage can themselves also request reports from +Heapusage, while the program is running, by using the `hu_report()` public API +function. For doing so, they must include the `huapi.h` public header file, link +with the Heapusage shared library itself and call `hu_report()` when wanted. +Still, this only works if the program is running through the `heapusage` tool. + Output Format ============= Example output: diff --git a/src/huapi.cpp b/src/huapi.cpp new file mode 100644 index 0000000..5eeaaa1 --- /dev/null +++ b/src/huapi.cpp @@ -0,0 +1,22 @@ +/* + * huapi.cpp + * + * Copyright (C) 2017-2021 Kristofer Berggren + * All rights reserved. + * + * heapusage is distributed under the BSD 3-Clause license, see LICENSE for details. + * + */ + + +/* ----------- Includes ------------------------------------------ */ +#include "hulog.h" + + +/* ----------- Global Functions ---------------------------------- */ + +extern "C" void hu_report() +{ + log_message("hu_report: Request Log Summary\n"); + log_summary_safe(); +} diff --git a/src/huapi.h b/src/huapi.h new file mode 100644 index 0000000..77cb918 --- /dev/null +++ b/src/huapi.h @@ -0,0 +1,27 @@ +/* + * huapi.h + * + * Copyright (C) 2021 Kristofer Berggren + * All rights reserved. + * + * heapusage is distributed under the BSD 3-Clause license, see LICENSE for details. + * + */ + +#ifndef _HUAPI_H_ +#define _HUAPI_H_ + + +/* ----------- Global Function Prototypes ------------------------ */ + +#ifdef __cplusplus +extern "C" { +#endif + +void hu_report(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _HUAPI_H_ */ diff --git a/src/hulog.cpp b/src/hulog.cpp index e6e3302..d52ec9e 100644 --- a/src/hulog.cpp +++ b/src/hulog.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -385,6 +386,30 @@ void hu_sig_handler(int sig, siginfo_t* si, void* /*ucontext*/) exit(EXIT_FAILURE); } +void log_message(const char *format, ...) +{ + va_list args; + + FILE *f = NULL; + if (hu_log_file) + { + f = fopen(hu_log_file, "a"); + } + + if (!f) + { + return; + } + + fprintf(f, "==%d== MESSAGE: ", pid); + + va_start(args, format); + vfprintf(f, format, args); + va_end(args); + + fclose(f); +} + void log_summary() { FILE *f = NULL; @@ -465,6 +490,17 @@ void log_summary() fclose(f); } +void log_summary_safe() +{ + hu_set_bypass(true); + log_enable(0); + + log_summary(); + + log_enable(1); + hu_set_bypass(false); +} + void hu_log_remove_freed_allocation(void* ptr) { if (!hu_log_free) diff --git a/src/hulog.h b/src/hulog.h index 778ab31..2aa8bc1 100644 --- a/src/hulog.h +++ b/src/hulog.h @@ -24,5 +24,7 @@ void log_enable(int flag); void log_event(int event, void* ptr, size_t size); void log_invalid_access(void* ptr); void hu_sig_handler(int sig, siginfo_t* si, void* /*ucontext*/); +void log_message(const char *format, ...); void log_summary(); +void log_summary_safe(); void hu_log_remove_freed_allocation(void* ptr); From 3d60e08c3dae92d4cb77b717dd6c98add6b33efc Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Fri, 24 May 2024 20:06:28 +0100 Subject: [PATCH 2/3] log_summary: Fix for allowing multiple calls in the same run The `log_summary()` function is causing problems when being called multiple times in the same run, i.e. if called multiple process while the program being analyzed is running. The observed problems are: * The `size` and `count` fields for leaked blocks for the same callstack are accumulating between calls, messing with the statistics. * If there are previous calls to `log_summary()` from e.g. on demand report requests done by using `hu_report()`, the last call done when the program exits (from `hu_fini()`), causes crashes (`SIGABRT` and `SIGSEGV` have been observed). This all happens because the `allocations_by_callstack` map is being used as `static`. This doesn't really seem to be needed or that useful, since the size of the map is really small (measured as 24 bytes), so no real memory savings by declaring it as static. There's also no observed impact on the `log_summary()` performance by removing the `static` attribute. This way, let `allocations_by_callstack` be allocated from the stack and restarted on every `log_summary()` call, hence avoiding the mentioned problems. Signed-off-by: Ricardo Silva --- src/hulog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hulog.cpp b/src/hulog.cpp index d52ec9e..39e2d1d 100644 --- a/src/hulog.cpp +++ b/src/hulog.cpp @@ -427,7 +427,7 @@ void log_summary() unsigned long long leak_total_blocks = 0; /* Group results by callstack */ - static std::map, hu_allocinfo_t> allocations_by_callstack; + std::map, hu_allocinfo_t> allocations_by_callstack; for (auto it = allocations->begin(); it != allocations->end(); ++it) { std::vector callstack; From 2de0126ce0c079807661d9870074c8eb20435be8 Mon Sep 17 00:00:00 2001 From: Ricardo Silva Date: Fri, 24 May 2024 20:17:11 +0100 Subject: [PATCH 3/3] API: Allow sending a `SIGUSR1` signal for requesting reports on demand The tool currently only produces reports on a clean exit from the analyzed program, which is not always achievable (daemons, programs crashing, etc.), and also using `hu_report()` as an API, which might also not be doable in all scenarios (e.g. no source code for the analyzed program). An "on demand" report requested from "the outside world" has proven very useful in some debugging scenarios. Having a signal that could be sent to the analyzed program (e.g. sent from a shell) and have the `heapusage` tool catch it and produce a report in response is very helpful. This way, install a signal handler for the `SIGUSR1` and `SIGUSR2` user defined signals and, for `SIGUSR1` trigger a report using `log_summary_safe()`, just like the `hu_report()` API does. Leave the `SIGUSR2` available for other future requests. Update `README.md` with this new usage possibility. Signed-off-by: Ricardo Silva --- README.md | 8 ++++++++ src/humain.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/README.md b/README.md index 3f8c0b1..fe5cce8 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,14 @@ function. For doing so, they must include the `huapi.h` public header file, link with the Heapusage shared library itself and call `hu_report()` when wanted. Still, this only works if the program is running through the `heapusage` tool. +Alternatively, on Linux, sending a `SIGUSR1` signal to the program being run +through Heapusage will also produce a Heapusage report on demand. + +For both `hu_report()` and `SIGUSR1`, it should be noted that the report will +reflect the state when they are used, which can e.g. report non-freed memory +that might be still released before the program exits and, therefore, not +necessarily constitute a memory leak. + Output Format ============= Example output: diff --git a/src/humain.cpp b/src/humain.cpp index 428a5eb..d0589c2 100644 --- a/src/humain.cpp +++ b/src/humain.cpp @@ -91,6 +91,34 @@ static inline bool hu_get_env_bool(const char* name) return (strcmp(value, "1") == 0); } +#if defined(__linux__) +static void hu_sigusr_handler(int sig) +{ + switch (sig) { + case SIGUSR1: + log_message("Caught User Signal: SIGUSR1 (%d): Request Log Summary\n", sig); + log_summary_safe(); + break; + default: + log_message("Caught User Signal: Unexpected Signal (%d): Ignore\n", sig); + break; + } +} +#endif + +static void hu_sigusr_init(void) +{ +#if defined(__linux__) + struct sigaction sa; + + sa.sa_handler = hu_sigusr_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGUSR2, &sa, NULL); +#endif +} + /* ----------- Global Functions ---------------------------------- */ extern "C" @@ -116,6 +144,8 @@ void __attribute__ ((constructor)) hu_init(void) hu_malloc_init(hu_overflow, hu_useafterfree, hu_minsize); } + hu_sigusr_init(); + /* Do not enable preload for child processes */ unsetenv("DYLD_INSERT_LIBRARIES"); unsetenv("LD_PRELOAD");