From 67931757f8e3f752c9379fb7963d29534fdd4dc3 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Fri, 18 Feb 2022 13:59:17 +0000 Subject: [PATCH] alloc: compressed backtrace instrumentation support This adds apis that enable usage of compressed backtraces in heap instrumentation. A decompressor tool is also provided that emits a textual call stack suitable for use with addr2line. --- CMakeLists.txt | 10 + READMEs/README.lws_backtrace.md | 181 ++++++++ cmake/lws_config.h.in | 4 + contrib/heapmap.sh | 29 ++ doc-assets/backtrace.png | Bin 0 -> 29829 bytes include/libwebsockets.h | 1 + include/libwebsockets/lws-backtrace.h | 280 +++++++++++++ include/libwebsockets/lws-misc.h | 8 + lib/core/alloc.c | 47 ++- lib/core/libwebsockets.c | 25 ++ lib/misc/CMakeLists.txt | 5 + lib/misc/backtrace.c | 392 ++++++++++++++++++ lib/roles/h2/ops-h2.c | 2 +- .../api-test-backtrace/CMakeLists.txt | 24 ++ .../api-tests/api-test-backtrace/README.md | 20 + .../api-tests/api-test-backtrace/main.c | 145 +++++++ .../minimal-http-client/minimal-http-client.c | 4 + 17 files changed, 1175 insertions(+), 2 deletions(-) create mode 100644 READMEs/README.lws_backtrace.md create mode 100755 contrib/heapmap.sh create mode 100644 doc-assets/backtrace.png create mode 100644 include/libwebsockets/lws-backtrace.h create mode 100644 lib/misc/backtrace.c create mode 100644 minimal-examples-lowlevel/api-tests/api-test-backtrace/CMakeLists.txt create mode 100644 minimal-examples-lowlevel/api-tests/api-test-backtrace/README.md create mode 100644 minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ca269957..53eb5b44e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,6 +329,16 @@ else() set(LWS_WITH_NETLINK 0) endif() +# +# Compressed backtraces +# +option(LWS_WITH_COMPRESSED_BACKTRACES "Build with support for compressed backtraces" OFF) +set(LWS_COMPRESSED_BACKTRACES_SNIP_PRE 2 CACHE STRING "Amount of callstack to snip from top") +set(LWS_COMPRESSED_BACKTRACES_SNIP_POST 1 CACHE STRING "Amount of callstack to snip from bottom") +option(LWS_WITH_ALLOC_METADATA_LWS "Build lws_*alloc() with compressed backtraces (requires WITH_COMPRESSED_BACKTRACES)" OFF) + + + if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS") # its openssl has md5 deprecated set(LWS_SUPPRESS_DEPRECATED_API_WARNINGS 1) diff --git a/READMEs/README.lws_backtrace.md b/READMEs/README.lws_backtrace.md new file mode 100644 index 000000000..f38893741 --- /dev/null +++ b/READMEs/README.lws_backtrace.md @@ -0,0 +1,181 @@ +# lws_backtrace and lws_alloc_metadata + +|Area|Definition| +|---|---| +|Cmake|`LWS_WITH_COMPRESSED_BACKTRACES` on by default| +|API|`./include/libwebsockets/lws-backtrace.h`| +|README|./READMEs/README.lws_backtrace.md| + +## lws_backtrace + +The `lws_backtrace` apis provide a way to collect backtrace addresses into a +struct, and an efficient domain-specific compressor to reduce the number of +bytes needed to express the backtrace stack. + +This information is particularly useful in RTOS type systems to understand heap +usage. The information would typically be sent off the embedded device, in logs +or it into own stream, and decompressed and processed off the embedded device, +converted to source information via addr2line or similar. + +It only works with gcc and probably clang at the moment (patches welcome). + +## lws_alloc_metadata apis + +This provides helpers on top of `lws_backtrace` that are suitable for adapting +your heap allocator to create compressed metadata such as the call stack at +allocation time + + - optionally report allocation and free events with this information + synchronously to a user supplied callback + + - optionally conceal the additional metadata behind allocations transparently + +The extra metadata contains information on allocation size, and the backtrace of +the code path that originally performed the allocation. Live allocations are +also listed on one or more lws_dll2_owner_t that can be walked to dump active +allocations along with the responsible code path. + +## Tuning the call stack + +Entries at both ends of the call stack may be invariant and therefore just +bloat to store. At the top end of the call stack, the backtrace will show the +path through lws_backtrace apis and perhaps other apis. At the bottom end, +depending on your system, the backtrace may detail call sequences from the +loader that started your application. + +For those reasons, the cmake variables `LWS_COMPRESSED_BACKTRACES_SNIP_PRE` +and `LWS_COMPRESSED_BACKTRACES_SNIP_POST` (defaulting to 2 and 1 respectively) +may be set to remove invariant, uninteresting call stack information from the +top and bottom of the call stack. + +## LWS_WITH_ALLOC_METADATA_LWS + +An optional, off-by-default implementation is provided for the lws_*alloc() +apis, using the alloc_metadata apis to instrument all allocations via +lws_*alloc(). This is not so useful as instrumenting the system allocator with +alloc_metadata apis, since it only shows lws allocations, but it is a complete +example to show how to do it. + +## Allocator instrumentation and thread-safety + +Unless your application is totally singlethreaded, when instrumenting a real +allocator, care must be taken with + + - `_lws_alloc_metadata_adjust()` + - `_lws_alloc_metadata_trim()` + - `_lws_alloc_metadata_dump()` + +apis which deal with the hidden overallocation and listing allocations, that +they are called from a locked critical section that disallows reentry, either +an existing one that the allocator already uses, or add a new mutex. + +## Dumping entire active instrumented heap allocations + +Calling `_lws_alloc_metadata_dump()` allows you to walk the current list of +allocations from a heap and dump the backtrace responsible for its allocation. +You can define your own iterator callback, or use a helper callback that is +provided, `lws_alloc_metadata_dump_stdout`, which issues the heap metadata in +the lws convention base64 format described below. + +## Convention for emission of compressed backtraces + +To simplify triggering dumps, a convention is defined with a 3-character +lead-in identifying lines as dumps or backtraces. This kind of approach makes +it easy to emit the metadata into logs and fish them out with grep or similar. + +|lead-in|signifies|Example| +|---|---|---| +|~m#|Compressed allocator backtrace, eg, emitted into logs|~m#IF0BmagugNDWgCnkhdAYpQa6wAAV| +|~b#|Decoded, uncompressed backtrace line suitable for `addr2line`|~b#size: 7520, 0x406651 0x406852 0x406c1b 0x406294| + +Both examples are complete representations of the same 4-level, 64-bit compressed +backtrace. + +## Compressed backtrace decode tool + +The `lws-api-test-backtrace` example (requires `LWS_WITH_COMPRESSED_BACKTRACES` +to build) decodes the base64 representations with or without the 3-character +lead-in, to textual output suitable for `addr2line`. Eg + +``` +$ echo -n "~m#IF0BmagugNDWgCnkhdAYpQa6wAAV" | lws-api-test-backtrace +~b#size: 7520, 0x406651 0x406852 0x406c1b 0x406294 +``` + +You can use it with `addr2line` in this kind of way (you probably want to give +`-f -p` to `addr2line` as well) + +``` +addr2line -e myapplication `echo -n "~m#IF0BmUQugNCkgCnkhdAYpQa6wAAV" | ./bin/lws-api-test-backtrace 2>/dev/null | grep '~b#' | cut -d',' -f2-` +/projects/libwebsockets/lib/core/alloc.c:124 +/projects/libwebsockets/lib/core/alloc.c:213 +/projects/libwebsockets/lib/core/context.c:600 +/projects/myapplication/main.c:55 +``` + +There is a shell script `./contrib/heapmap.sh` which takes a screenscrape of +a dump's `~m#` log lines and processes them into an allocation size, backtrace, +and function names (especially in RELEASE mode, either a function name hint or +the source coordinates are available). + +## lws_backtrace compression + +The compressed blob has an outer structure designed for prepending, where the +information available at recovery is a pointer to the end of it. + +![overview](../doc-assets/backtrace.png) + +### Outer compressed blob layout in memory + +This goes behind the reported allocation, the actual allocation is increased +to allow for it and we report what the caller asked for by pointing at the end +of this. It means eg at free() time, we are told the address just past the end +of this and work backwards to find the start of the compressed blob (which is +further aligned backwards to ptr boundary to recover the true allocation start). + +|data|bits|meaning| +|---|---|---| +|compressed blob|variable, padded to byte boundary|Backtrace and extra info| +|compressed length|fixed, 16|MSB-first 16-bit byte count of compressed blob, including the 16-bit length itself| +|lws_dll2_t|fixed, 3 x pointers|linked-list for tracking| + +### Bitwise structure inside the compressed blob + +|data|bits|meaning| +|---|---|---| +|stack depth|5|Number of backtrace callstack levels present| +|Call stack items, one per stack depth|variable|Compressed Instruction Pointer value| +|alloc size bits|6|Number of bits in alloc size| +|alloc size literal|variable|Allocation size| + +### Call stack item domain-specific compression + +The goal is to compress 32- or 64-bit backtraces efficiently. + +The Call stack items are compressed one of two ways and start with a bit +indicating which method was used for this Call stack item. + + - 0 = literal value, 1 = delta against a previous reference value + +The literals issue a bit count and then the significant bits + + - a 6-bit bit count + - the significant bits of the literal + +The delta from a previous Call stack item looks like this: + + - a 3-bit index (from -1 to -8) says how far back from the +current stack item the reference value can be found from the call stack + - a 1-bit sign where 0 == add the delta and 1 == subtract the delta + - a 6-bit bit count for the delta + - the significant bits of the delta + +The delta is decoded, and added or subtracted from the earlier reference result +to arrive at the correct reconstruction. + +The first Call stack item is always a literal. + +## Note for esp-idf + +Backtrace generation in esp-idf requires `CONFIG_COMPILER_CXX_EXCEPTIONS` set +in sdkconfig. diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 90f94ec8a..e87b18725 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -145,10 +145,14 @@ #cmakedefine LWS_WITH_ABSTRACT #cmakedefine LWS_WITH_ACCESS_LOG #cmakedefine LWS_WITH_ACME +#cmakedefine LWS_WITH_ALLOC_METADATA_LWS #cmakedefine LWS_WITH_ALSA #cmakedefine LWS_WITH_SYS_ASYNC_DNS #cmakedefine LWS_WITH_BORINGSSL #cmakedefine LWS_WITH_CGI +#cmakedefine LWS_WITH_COMPRESSED_BACKTRACES +#cmakedefine LWS_COMPRESSED_BACKTRACES_SNIP_PRE ${LWS_COMPRESSED_BACKTRACES_SNIP_PRE} +#cmakedefine LWS_COMPRESSED_BACKTRACES_SNIP_POST ${LWS_COMPRESSED_BACKTRACES_SNIP_POST} #cmakedefine LWS_WITH_CONMON #cmakedefine LWS_WITH_COSE #cmakedefine LWS_WITH_CUSTOM_HEADERS diff --git a/contrib/heapmap.sh b/contrib/heapmap.sh new file mode 100755 index 000000000..a8f933df1 --- /dev/null +++ b/contrib/heapmap.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Pass the the scraped compressed alloc metadata on stdin. +# +# $1 is the path to the elf file with the debugging info. +# $2 is the path to lws-api-test-backtrace, may be omitted if it's on the path +# +# Eg, +# +# cat /tmp/mydump | ../../../../../contrib/heapmap.sh build/myapp.elf ../../../../../build/bin/ + +echo -n 0 > /tmp/_total_size + +while read line ; do + X=`echo -n $line | "$2"lws-api-test-backtrace 2>/dev/null` + if [ "$X" != "" ] ; then + S=`echo -n $X | cut -d' ' -f2 | sed "s/\,//g"` + T=`cat /tmp/_total_size` + echo -n $(( $T + $S )) > /tmp/_total_size + echo "$S" + addr2line -f -p -e $1 `echo $X | cut -d',' -f2-` + echo + fi +done + +T=`cat /tmp/_total_size` + +echo +echo "# Total instrumented allocation $T" diff --git a/doc-assets/backtrace.png b/doc-assets/backtrace.png new file mode 100644 index 0000000000000000000000000000000000000000..48525b4e20bbc5c55d7c451f6c03f7ec61943729 GIT binary patch literal 29829 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 WDOnNhm;-)76iHrIS*HA>QQ&`6hnhkF literal 0 HcmV?d00001 diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 6e0b10d77..3387021d3 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -609,6 +609,7 @@ struct lws; #include #include +#include #include #include #if defined(LWS_WITH_SYS_SMD) diff --git a/include/libwebsockets/lws-backtrace.h b/include/libwebsockets/lws-backtrace.h new file mode 100644 index 000000000..d4fb9fc10 --- /dev/null +++ b/include/libwebsockets/lws-backtrace.h @@ -0,0 +1,280 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2022 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** \defgroup lws_backtrace generic and compressed backtrace acquisition + * ##Backtrace apis + * \ingroup lwsbacktrace + * + * lws_backtrace + * + * These apis abstract acquisition and optionally compressed on binary back- + * traces, effectively build-specific signatures for where in the code you are + * and how you got there. + */ +//@{ + +typedef struct { + uintptr_t st[32]; + uintptr_t asize; + + uint8_t sp; + uint8_t pre; + uint8_t post; +} lws_backtrace_info_t; + +typedef struct { + uint8_t *comp; + size_t pos; + size_t len; +} lws_backtrace_comp_t; + +/* + * lws_backtrace() - init and fiull a backtrace struct + * + * \param si: the backtrace struct to populate + * \param pre: the number of call levels to snip from the top + * \param post: the number of call levels to snip from the bottom + * + * This describes the call stack into \p si. \p si doesn't need preparing + * before the call. \p pre levels of the call stack at the top will be snipped, + * this will usually want to be 1 or 2 to conceal the helpers that are making + * the call stack, such as lws_backtrace itself. + * + * \p post levels of the call stack at the bottom will be snipped, this is to + * conceal loaders or other machinery that was used to start your application, + * otherwise those entries will bloat all call stacks results on that platform. + * + * Returns 0 for success. + */ +LWS_VISIBLE LWS_EXTERN int +lws_backtrace(lws_backtrace_info_t *si, uint8_t pre, uint8_t post); + +/* + * lws_backtrace_compression_stream_init() - init and fiull a backtrace struct + * + * \param c: the backtrace compression struct + * \param comp: the buffer to take the compressed bytes + * \param comp_len: the number of bytes available at \p comp + * + * This initializes the caller's lws_backtrace_comp_t. Because it's expected + * the caller will want to put his own compressed data after the compressed + * backtrace, he is responsible for the compression context. + */ +LWS_VISIBLE LWS_EXTERN void +lws_backtrace_compression_stream_init(lws_backtrace_comp_t *c, + uint8_t *comp, size_t comp_len); + +/* + * lws_backtrace_compression_stream() - add bitfields to compression stream + * + * \param c: the backtrace compression context struct + * \param v: the bitfield to add to the stream + * \param bits: the number of bits of v to add + * + * This inserts bits from the LSB end of v to the compression stream. + * + * This is used by the backtrace compression, user code can use this to add + * its own bitfields into the compression stream after the compressed backtrace. + * + * User data should be added after, so that the backtrace can be processed even + * if the additional data is not understood by the processing script. + * + * Returns 0 for success or nonzero if ran out of compression output buffer. + */ +LWS_VISIBLE LWS_EXTERN int +lws_backtrace_compression_stream(lws_backtrace_comp_t *c, uintptr_t v, + unsigned int bits); + +/* + * lws_backtrace_compression_destream() - add bitfields to compression stream + * + * \param c: the backtrace compression context struct + * \param _v: pointer to take the bitfield result + * \param bits: the number of bits to bring out into _v + * + * This reads the compression stream and creates a bitfield from it in \p _v. + * + * Returns 0 for success (with \p _v set to the value), or nonzero if ran out + * of compression output buffer. + */ +LWS_VISIBLE LWS_EXTERN int +lws_backtrace_compression_destream(lws_backtrace_comp_t *c, uintptr_t *_v, + unsigned int bits); + +/* + * lws_backtrace_compress_backtrace() - compress backtrace si into c + * + * \param si: the backtrace struct to compress + * \param c: the backtrace compression context struct + * + * This compresses backtrace information acquired in \p si into the compression + * context \p c. It compresses first the call stack length and then each IP + * address in turn. + * + * Returns 0 for success. + */ +LWS_VISIBLE LWS_EXTERN int +lws_backtrace_compress_backtrace(lws_backtrace_info_t *si, + lws_backtrace_comp_t *c); + +//@} + +/** \defgroup lws_alloc_metadata helpers for allocator instrumentation + * ##Alloc Metadata APIs + * \ingroup lwsallocmetadata + * + * lws_alloc_metadata + * + * These helpers let you rapidly instrument your libc or platform memory + * allocator so that you can later dump details, including a backtrace of where + * the allocation was made, for every live heap allocation. + * + * You would use it at peak memory usage, to audit who is using what at that + * time. + * + * Effective compression is used to keep the metadata overhead to ~48 bytes + * per active allocation on 32-bit systems. + */ +//@{ + +/** + * lws_alloc_metadata_gen() - generate metadata blob (with compressed backtrace) + * + * \param size: the allocation size + * \param comp: buffer for compressed backtrace + * \param comp_len: number of bytes available in the compressed backtrace + * \param adj: takes the count of additional bytes needed for metadata behind + * the allocation we tell the user about + * \param cl: takes the count of bytes used in comp + * + * This helper creates the compressed part of the alloc metadata blob and + * calculates the total overallocation that is needed in \p adj. + * + * This doesn't need any locking. + * + * If \p comp_len is too small for the whole result, or it was not possible to + * get the backtrace information, the compressed part is set to empty (total + * length 2 to carry the 00 00 length). + * + * 6 or 10 (64-bit) bytes per backtrace IP allowed (currently 16) should always + * be enough, typically the compression reduces this very significantly. + */ +LWS_VISIBLE LWS_EXTERN void +lws_alloc_metadata_gen(size_t size, uint8_t *comp, size_t comp_len, size_t *adj, + size_t *cl); + +/** + * _lws_alloc_metadata_adjust() - helper to inject metadata and list as active + * + * \param active: the allocation owner + * \param v: Original, true allocation pointer, adjusted on exit + * \param adj: Total size of metadata overallocation + * \param comp: The compressed metadata + * \param cl: takes the count of bytes used in comp + * + * THIS MUST BE LOCKED BY THE CALLER IF YOUR ALLOCATOR MAY BE CALLED BY OTHER + * THREADS. You can call it from an existing mutex or similar -protected + * critical section in your allocator if there is one already, or you will have + * to protect the caller of it with your own mutex so it cannot reenter. + * + * This is a helper that adjusts the allocation past the metadata part so the + * caller of the allocator using this sees what he asked for. The deallocator + * must call _lws_alloc_metadata_trim() to balance this before actual + * deallocation. + */ +LWS_VISIBLE LWS_EXTERN void +_lws_alloc_metadata_adjust(lws_dll2_owner_t *active, void **v, size_t adj, uint8_t *comp, unsigned int cl); + +/** + * _lws_alloc_metadata_trim() - helper to trim metadata and remove from active + * + * \param ptr: Adjusted allocation pointer on entry, true allocation ptr on exit + * \param comp: NULL, or set on exit to point to start of compressed area + * \param complen: NULL, or set on exit to length of compressed area in bytes + * + * THIS MUST BE LOCKED BY THE CALLER IF YOUR DEALLOCATOR MAY BE CALLED BY OTHER + * THREADS. You can call it from an existing mutex or similar -protected + * critical section in your deallocator if there is one already, or you will + * have to protect that caller of it with your own mutex so it cannot reenter. + */ +LWS_VISIBLE LWS_EXTERN void +_lws_alloc_metadata_trim(void **ptr, uint8_t **comp, uint16_t *complen); + +/** + * lws_alloc_metadata_parse() - parse compressed metadata into struct + * + * \param si: Struct to take the backtrace results from decompression + * \param adjusted_alloc: pointer to adjusted, user allocation start + * + * This api parses and decompresses the blob behind the \p adjusted_alloc + * address into \p si. + * + * Returns 0 for success. + */ +LWS_VISIBLE LWS_EXTERN int +lws_alloc_metadata_parse(lws_backtrace_info_t *si, const uint8_t *adjusted_alloc); + +/** + * lws_alloc_metadata_dump_stdout() - helper to print base64 blob on stdout + * + * \param d: the current list item + * \param user: the optional arg given to the dump api (ignored) + * + * Generic helper that can be given to _lws_alloc_metadata_dump() as the + * callback that will emit a standardized base64 blob for the alloc metadata + */ +LWS_VISIBLE LWS_EXTERN int +lws_alloc_metadata_dump_stdout(struct lws_dll2 *d, void *user); + +/** + * lws_alloc_metadata_dump_stdout() - dump all live allocs in instrumented heap + * + * \param active: the owner of the active allocation list for this heap + * \param cb: the callback to receive information + * \param arg: optional arg devivered to the callback + * + * THIS MUST BE LOCKED BY THE CALLER IF YOUR ALLOCATOR MAY BE CALLED BY OTHER + * THREADS. You can call it from an existing mutex or similar -protected + * critical section in your allocator if there is one already, or you will have + * to protect the caller of it with your own mutex so it cannot reenter. + * + * Iterates through the list of instrumented allocations calling the given + * callback for each one. + */ +LWS_VISIBLE LWS_EXTERN void +_lws_alloc_metadata_dump(lws_dll2_owner_t *active, lws_dll2_foreach_cb_t cb, + void *arg); + +#if defined(LWS_WITH_ALLOC_METADATA_LWS) +/* + * Wrapper for _lws_alloc_metadata_dump() that uses the list owner that tracks + * + */ +LWS_VISIBLE LWS_EXTERN void +_lws_alloc_metadata_dump_lws(lws_dll2_foreach_cb_t cb, void *arg); +#else +#define _lws_alloc_metadata_dump_lws(_a, _b) +#endif + +//@} diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 233cb3456..52aff1a5c 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -1221,3 +1221,11 @@ lws_fsmount_unmount(struct lws_fsmount *fsm); LWS_VISIBLE LWS_EXTERN int lws_minilex_parse(const uint8_t *lex, int16_t *ps, const uint8_t c, int *match); + +/* + * Reports the number of significant bits (from the left) that is needed to + * represent u. So if u is 0x80, result is 8. + */ + +LWS_VISIBLE LWS_EXTERN unsigned int +lws_sigbits(uintptr_t u); diff --git a/lib/core/alloc.c b/lib/core/alloc.c index b9ca8f0e8..3619ff244 100644 --- a/lib/core/alloc.c +++ b/lib/core/alloc.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2020 Andy Green + * Copyright (C) 2010 - 2022 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -32,6 +32,10 @@ static size_t allocated; #endif +#if defined(LWS_WITH_ALLOC_METADATA_LWS) +static lws_dll2_owner_t active; +#endif + #if defined(LWS_PLAT_OPTEE) #define TEE_USER_MEM_HINT_NO_FILL_ZERO 0x80000000 @@ -107,9 +111,19 @@ void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason)) static void * _realloc(void *ptr, size_t size, const char *reason) { +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + uint8_t comp[16 * LWS_ARRAY_SIZE(((lws_backtrace_info_t *)NULL)->st)]; + size_t complen; + size_t adj = 0; +#endif void *v; if (size) { +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + lws_alloc_metadata_gen(size, comp, sizeof(comp), &adj, &complen); + size += adj; +#endif + #if defined(LWS_PLAT_FREERTOS) lwsl_debug("%s: size %lu: %s (free heap %d)\n", __func__, #if defined(LWS_AMAZON_RTOS) @@ -127,17 +141,39 @@ _realloc(void *ptr, size_t size, const char *reason) allocated -= malloc_usable_size(ptr); #endif +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + size += adj; +#endif + #if defined(LWS_PLAT_OPTEE) v = (void *)TEE_Realloc(ptr, size); #else v = (void *)realloc(ptr, size); #endif + + if (!v) + return v; + #if defined(LWS_HAVE_MALLOC_USABLE_SIZE) allocated += malloc_usable_size(v); #endif + +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + _lws_alloc_metadata_adjust(&active, &v, adj, comp, (unsigned int)complen); +#endif + return v; } + + /* + * We are freeing it then... + */ + if (ptr) { +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + _lws_alloc_metadata_trim(&ptr, NULL, NULL); +#endif + #if defined(LWS_HAVE_MALLOC_USABLE_SIZE) allocated -= malloc_usable_size(ptr); #endif @@ -147,6 +183,15 @@ _realloc(void *ptr, size_t size, const char *reason) return NULL; } +#if defined(LWS_WITH_ALLOC_METADATA_LWS) +void +_lws_alloc_metadata_dump_lws(lws_dll2_foreach_cb_t cb, void *arg) +{ + lwsl_err("%s\n", __func__); + _lws_alloc_metadata_dump(&active, cb, arg); +} +#endif + void *(*_lws_realloc)(void *ptr, size_t size, const char *reason) = _realloc; void *lws_realloc(void *ptr, size_t size, const char *reason) diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index 665051a6f..865292d29 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -1703,3 +1703,28 @@ nope: return LWS_MINILEX_FAIL; } + +unsigned int +lws_sigbits(uintptr_t u) +{ + uintptr_t mask = (uintptr_t)(0xffllu << ((sizeof(u) - 1) * 8)), + m1 = (uintptr_t)(0x80llu << ((sizeof(u) - 1) * 8)); + unsigned int n; + + for (n = sizeof(u) * 8; n > 0; n -= 8) { + if (u & mask) + break; + mask >>= 8; + m1 >>= 8; + } + + if (!n) + return 1; /* not bits are set, we need at least 1 to represent */ + + while (!(u & m1)) { + n--; + m1 >>= 1; + } + + return n; +} diff --git a/lib/misc/CMakeLists.txt b/lib/misc/CMakeLists.txt index 7a1293ae1..1b16acd1d 100644 --- a/lib/misc/CMakeLists.txt +++ b/lib/misc/CMakeLists.txt @@ -49,6 +49,11 @@ if (LWS_WITH_NETWORK) endif() +if (LWS_WITH_COMPRESSED_BACKTRACES) + list(APPEND SOURCES + misc/backtrace.c) +endif() + if (LWS_WITH_FTS) list(APPEND SOURCES misc/fts/trie.c diff --git a/lib/misc/backtrace.c b/lib/misc/backtrace.c new file mode 100644 index 000000000..3794a03ba --- /dev/null +++ b/lib/misc/backtrace.c @@ -0,0 +1,392 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2022 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" + +#define _GNU_SOURCE +#include + +static _Unwind_Reason_Code +uwcb(struct _Unwind_Context* uctx, void *arg) +{ + lws_backtrace_info_t *si = (lws_backtrace_info_t *)arg; + + if (si->sp == LWS_ARRAY_SIZE(si->st)) + return _URC_END_OF_STACK; + + if (!si->pre) { + if (_Unwind_GetIP(uctx)) + si->st[si->sp++] = _Unwind_GetIP(uctx); + } else + si->pre--; + + return _URC_NO_REASON; +} + +int +lws_backtrace(lws_backtrace_info_t *si, uint8_t pre, uint8_t post) +{ + _Unwind_Reason_Code r; + + si->sp = 0; + si->pre = pre; /* skip the top couple of backtrace results */ + si->post = post; + + r = _Unwind_Backtrace(uwcb, si); + + if (si->sp > si->post) + si->sp -= si->post; + + return r != _URC_END_OF_STACK; +} + +int +lws_backtrace_compression_stream(lws_backtrace_comp_t *c, uintptr_t v, + unsigned int bits) +{ + int nbits = (int)bits; + + while (nbits-- >= 0) { + if (!(c->pos & 7)) + c->comp[c->pos >> 3] = 0; + if (v & (1 << nbits)) + c->comp[c->pos >> 3] |= (1 << (7 - (c->pos & 7))); + + c->pos++; + + if ((c->pos >> 3) == c->len) { + lwsl_err("%s: overrun %u\n", __func__, (unsigned int)c->len); + return 1; + } + } + + return 0; +} + +int +lws_backtrace_compression_destream(lws_backtrace_comp_t *c, uintptr_t *_v, + unsigned int bits) +{ + int nbits = (int)bits; + uintptr_t v = 0; + + while (nbits-- >= 0) { + if ((c->pos >> 3) == c->len) + return 1; + if (c->comp[c->pos >> 3] & (1 << (7 - (c->pos & 7)))) + v |= (1 << nbits); + c->pos++; + } + + *_v = v; + + return 0; +} + +void +lws_backtrace_compression_stream_init(lws_backtrace_comp_t *c, + uint8_t *comp, size_t comp_len) +{ + *comp = 0; + c->pos = 0; + c->comp = comp; + c->len = comp_len; +} + +int +lws_backtrace_compress_backtrace(lws_backtrace_info_t *si, + lws_backtrace_comp_t *c) +{ + int n; + + lws_backtrace_compression_stream(c, si->sp, 5); + + for (n = 0; n < si->sp; n++) { /* go through each in turn */ + uintptr_t delta = (uintptr_t)~0ll, d1; + char hit = -1, sign, _sign; + unsigned int q, ql; + int m; + + if (n > 8) + m = n - 8; + else + m = 0; + + /* we can look for 1 to 8 back */ + for (; m < n; m++) { + if (si->st[n] > si->st[m]) { + d1 = si->st[n] - si->st[m]; + _sign = 0; + } else { + d1 = si->st[m] - si->st[n]; + _sign = 1; + } + if (d1 < delta) { + delta = d1; + hit = (char)m; + sign = _sign; + } + } + + q = lws_sigbits(delta); + ql = lws_sigbits(si->st[n]); + + /* + * Bitwise compression: + * + * 0: zzzzzz literal (number of bits following) + * 1: xxx: y: zzzzzz delta (base index is (xxx + 1) back + * from this index) + * y == 1 == subtract from base, + * zzzzzz delta bits follow + */ + + if (n && hit && q + 11 < ql + 7) { + /* shorter to issue a delta froma previous address */ + lws_backtrace_compression_stream(c, 1, 1); + lws_backtrace_compression_stream(c, (uintptr_t)((n - hit) - 1), 3); + lws_backtrace_compression_stream(c, (uintptr_t)sign, 1); + lws_backtrace_compression_stream(c, q, 6); + + if (lws_backtrace_compression_stream(c, delta, q)) + return 1; + } else { + /* shorter to issue a literal */ + lws_backtrace_compression_stream(c, 0, 1); + lws_backtrace_compression_stream(c, ql, 6); + + if (lws_backtrace_compression_stream(c, si->st[n], ql)) + return 1; + } + } + + return 0; +} + + +void +lws_alloc_metadata_gen(size_t size, uint8_t *comp, size_t comp_len, + size_t *adj, size_t *cl) +{ + lws_backtrace_info_t si; + lws_backtrace_comp_t c; + unsigned int q, ql; + + /**< We need enough here to take the compressed results of however many + * callstack Instruction Pointers are allowed, currently 16. + */ + + lws_backtrace_compression_stream_init(&c, comp, comp_len); + + lws_backtrace(&si, LWS_COMPRESSED_BACKTRACES_SNIP_PRE, + LWS_COMPRESSED_BACKTRACES_SNIP_POST); + + /* + * We have the result stack, let's compress it + * + * - (implicit alignment) + * - call stack len (5b) / call stack literal [ { literal | delta } ... ] + * - bitcount(6), alloc size literal + * + * - 2 bytes MSB-first at end on byte boundary, total compressed length + * behind it. + * - lws_dll2_t + */ + + if (!lws_backtrace_compress_backtrace(&si, &c)) { + + lws_backtrace_compression_stream(&c, lws_sigbits(size), 6); + lws_backtrace_compression_stream(&c, size, lws_sigbits(size)); + + q = (unsigned int)(c.pos >> 3); + if (c.pos & 7) + q++; + + if (q + 2 >= c.len) { + lwsl_err("ovf\n"); + goto nope; + } + + ql = q + 2; + c.comp[q++] = (uint8_t)((ql >> 8) & 0xff); + c.comp[q++] = (uint8_t)(ql & 0xff); + + /* + * So we have it compressed along with our additional data. + */ + + /* pointer-aligned total overallocation */ + *adj = sizeof(lws_dll2_t) + + ((q + sizeof(void *) - 1) / sizeof(void *)) * + sizeof(void *); + /* compression buf contents amount */ + *cl = q; + } else { + /* put an explicit zero-length prepend for want of anything else */ +nope: + c.comp[0] = 0; + c.comp[1] = 0; + c.pos = 16; /* bits */ + *cl = 2; + *adj = sizeof(lws_dll2_t) + sizeof(void *); + } +} + +/* incoming *v is the true allocation */ + +void +_lws_alloc_metadata_adjust(lws_dll2_owner_t *active, void **v, size_t adj, + uint8_t *comp, unsigned int cl) +{ + /* + * Lie about the alloc start in order to conceal our metadata behind + * what was asked for. Incoming v is the real + * + * True alloc /Comp Reported alloc + * V V + * <16-bit MSB len to comp> lws_dll2_t + */ + + *v = (void *)((uint8_t *)(*v) + adj - sizeof(lws_dll2_t)); + memcpy((uint8_t *)(*v) - cl, comp, cl); + lws_dll2_clear((*v)); + lws_dll2_add_tail((*v), active); + *v = (void *)((uint8_t *)(*v) + sizeof(lws_dll2_t)); +} + +void +_lws_alloc_metadata_trim(void **ptr, uint8_t **comp, uint16_t *complen) +{ + const uint8_t *p = ((const uint8_t *)*ptr) - sizeof(lws_dll2_t); + uint16_t cofs = p[-1] | (p[-2] << 8); + size_t adj = ((sizeof(lws_dll2_t) + cofs + sizeof(void *) - 1) / + sizeof(void *)) * sizeof(void *); + + //lwsl_hexdump_notice((uint8_t *)(*ptr) - adj, adj); + + if (comp) + *comp = (uint8_t *)p - cofs; /* start of compressed area */ + if (complen) + *complen = cofs - 2; + + lws_dll2_remove((lws_dll2_t *)p); + *ptr = (void *)((uint8_t *)*ptr - adj); /* original alloc point */ +} + +/* past_len: after the 16-bit len, pointing at the lws_dll2_t at the end */ + +int +lws_alloc_metadata_parse(lws_backtrace_info_t *si, const uint8_t *past_len) +{ + const uint8_t *p = (const uint8_t *)past_len; + uintptr_t n, entries, ri, sign, field; + uint16_t cofs = p[-1] | (p[-2] << 8); + lws_backtrace_comp_t c; + + c.comp = (uint8_t *)p - cofs; + c.pos = 0; + c.len = cofs - 2; + si->sp = 0; + + /* 5-bit bitfield contains callstack depth */ + if (lws_backtrace_compression_destream(&c, &entries, 5)) + return 1; + + while (si->sp != entries) { + + if (lws_backtrace_compression_destream(&c, &n, 1)) + return 1; + + if (n) { /* delta: 3-bit refidx, 1-bit delta sign, 6-bit fieldlen, field */ + + assert(si->sp); /* first must be literal */ + + if (lws_backtrace_compression_destream(&c, &ri, 3)) + return 1; + if (lws_backtrace_compression_destream(&c, &sign, 1)) + return 1; + if (lws_backtrace_compression_destream(&c, &n, 6)) + return 1; + if (lws_backtrace_compression_destream(&c, &field, (unsigned int)n)) + return 1; + + if (si->sp < si->sp - ri - 1 ) { + lwsl_err("ref err\n"); + return 1; + } + + if (sign) /* backwards from ref */ + si->st[si->sp] = si->st[si->sp - (ri + 1)] - field; + else /* forwards from ref */ + si->st[si->sp] = si->st[si->sp - (ri + 1)] + field; + + } else { /* literal */ + if (lws_backtrace_compression_destream(&c, &n, 6)) + return 1; + if (lws_backtrace_compression_destream(&c, &field, (unsigned int)n)) + return 1; + + si->st[si->sp] = field; + } + + si->sp++; + } + + /* 6-bit bitlength, then allocated size */ + if (lws_backtrace_compression_destream(&c, &n, 6)) + return 1; + if (lws_backtrace_compression_destream(&c, &si->asize, (unsigned int)n)) + return 1; + + return 0; +} + +int +lws_alloc_metadata_dump_stdout(struct lws_dll2 *d, void *user) +{ + char ab[192]; + + const uint8_t *p = (const uint8_t *)d; + uint16_t cofs = p[-1] | (p[-2] << 8); + + p = (uint8_t *)p - cofs; + + ab[0] = '~'; + ab[1] = 'm'; + ab[2] = '#'; + lws_b64_encode_string((const char *)p, (int)cofs, + ab + 3, (int)sizeof(ab) - 4); + + puts(ab); + + return 0; +} + +void +_lws_alloc_metadata_dump(lws_dll2_owner_t *active, lws_dll2_foreach_cb_t cb, + void *arg) +{ + lws_dll2_foreach_safe(active, arg, cb); +} + diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index 989cb5b95..c4e2665e1 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -722,7 +722,7 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason) while (w) { w1 = w->next; - free(w); + lws_free(w); w = w1; } wsi->h2.h2n->pps = NULL; diff --git a/minimal-examples-lowlevel/api-tests/api-test-backtrace/CMakeLists.txt b/minimal-examples-lowlevel/api-tests/api-test-backtrace/CMakeLists.txt new file mode 100644 index 000000000..34a6faa1a --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-backtrace/CMakeLists.txt @@ -0,0 +1,24 @@ +project(lws-api-test-backtrace C) +cmake_minimum_required(VERSION 2.8.12) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SRCS main.c) + +set(requirements 1) +require_lws_config(LWS_WITH_COMPRESSED_BACKTRACES 1 requirements) + +if (requirements) + + add_executable(${PROJECT_NAME} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${PROJECT_NAME} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${PROJECT_NAME} websockets_shared) + else() + target_link_libraries(${PROJECT_NAME} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() + diff --git a/minimal-examples-lowlevel/api-tests/api-test-backtrace/README.md b/minimal-examples-lowlevel/api-tests/api-test-backtrace/README.md new file mode 100644 index 000000000..17a1af0b9 --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-backtrace/README.md @@ -0,0 +1,20 @@ +# lws api test Compressed Backtraces + +Tool to decompress the `lws_backtrace` compressed backtraces + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 + +``` + $ echo -n "~m#ghawu9ICDldHWP9xuFCTFrDOOUzlHOLYIbqO1C3eYbrpcC3NoQo41CtHWBxkZcnU4BA1VCoANw==" | ./lws-api-test-backtrace +``` + diff --git a/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c b/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c new file mode 100644 index 000000000..b7155987e --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c @@ -0,0 +1,145 @@ +/* + * lws-api-test-backtrace + * + * Written in 2010-2022 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include +#include +#include + +int fdin = 0; + +int +main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int result = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, n; + uint8_t ib[2048], ob[1536], *eib = ib; + lws_backtrace_info_t si; + unsigned int m; + uintptr_t uipt; + ssize_t s = 0; + size_t l = 0; + uint16_t san; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + + if ((p = lws_cmdline_option(argc, argv, "--stdin"))) { + fdin = open(p, LWS_O_RDONLY, 0); + if (fdin < 0) { + result = 1; + lwsl_err("%s: unable to open stdin file\n", __func__); + goto bail; + } + } + + lwsl_user("LWS Compressed Backtrace Decoder\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ +#if defined(LWS_WITH_NETWORK) + info.port = CONTEXT_PORT_NO_LISTEN; +#endif + info.options = 0; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* confirm operation of lws_sigbits */ + + uipt = 0x8000000000000000ull; + for (n = 64; n; n--) { + m = lws_sigbits(uipt); + if (n != (int)m) { + lwsl_err("a: %d %d\n", n, m); + goto bail; + } + uipt >>= 1; + } + +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + _lws_alloc_metadata_dump_lws(lws_alloc_metadata_dump_stdout, NULL); +#endif + + if (!fdin) { + struct timeval timeout; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + + if (select(fdin + 1, &fds, NULL, NULL, &timeout) < 0 || + !FD_ISSET(0, &fds)) { + result = 1; + lwsl_err("%s: pass Compressed Backtrace line " + "on stdin or use --stdin\n", __func__); + goto bail; + } + } + + while (l != sizeof(ib)) { + s = read(fdin, ib + l, sizeof(ib) - l); + if (s <= 0) + break; + l = l + (size_t)s; + } + + if (l < 4) + goto bail; + + if (ib[0] == '~' && ib[2] == '#') { + eib += 3; + l -= 3; + } + + n = lws_b64_decode_string_len((char *)eib, (int)l, (char *)ob, (int)sizeof(ob)); + if (n <= 0) { + lwsl_err("%s: invalid base64\n", __func__); + goto bail; + } + + lwsl_hexdump_notice(ob, (size_t)n); + + san = (ob[n - 2] << 8) | ob[n - 1]; + if (san != (unsigned int)n) { + lwsl_err("%s: compressed length wrong\n", __func__); + goto bail; + } + + if (lws_alloc_metadata_parse(&si, ob + n)) { + lwsl_err("%s: compressed parse failed\n", __func__); + goto bail; + } + + printf("~b#size: %llu, ", (unsigned long long)si.asize); + + for (n = 0; n < si.sp; n++) + printf("0x%llx ", (unsigned long long)si.st[n]); + printf("\n"); + + result = 0; + +bail: + lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); + + lws_context_destroy(context); + + return result; +} + diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c index ac9aa32fb..5f1f1aa13 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c @@ -94,6 +94,10 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, lws_get_peer_simple(wsi, buf, sizeof(buf)); status = (int)lws_http_client_http_response(wsi); +#if defined(LWS_WITH_ALLOC_METADATA_LWS) + _lws_alloc_metadata_dump_lws(lws_alloc_metadata_dump_stdout, NULL); +#endif + lwsl_user("Connected to %s, http response: %d\n", buf, status); }