diff --git a/.gitignore b/.gitignore index bbfa489..a5889b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/systemtap-4.8.tar.gz +SOURCES/systemtap-4.9.tar.gz diff --git a/.systemtap.metadata b/.systemtap.metadata index e947e62..4877693 100644 --- a/.systemtap.metadata +++ b/.systemtap.metadata @@ -1 +1 @@ -35e1168d72c9d553a6094825f067e2e62d662a59 SOURCES/systemtap-4.8.tar.gz +7ba2ad579a5ba66ccfd36ad6df0896c9e136f9e9 SOURCES/systemtap-4.9.tar.gz diff --git a/SOURCES/pr29108.patch b/SOURCES/pr29108.patch new file mode 100644 index 0000000..43f5170 --- /dev/null +++ b/SOURCES/pr29108.patch @@ -0,0 +1,1845 @@ +commit bf95ad72c984c9e68d12707c4d34dbe6bc1f89f2 +gpg: Signature made Sat 12 Aug 2023 02:49:06 PM EDT +gpg: using RSA key 5D38116FA4D3A7CC77E378D37E83610126DCC2E8 +gpg: Good signature from "Frank Ch. Eigler " [full] +Author: Aliaksandr Valialkin +Date: Thu Jul 27 18:52:37 2023 -0400 + + runtime/staprun: import gheap routines + + BSD-2-Clause gift from the Aliaksandr Valialkin: + https://github.com/valyala/gheap + +diff --git a/staprun/gheap.h b/staprun/gheap.h +new file mode 100644 +index 000000000..4af4b29ed +--- /dev/null ++++ b/staprun/gheap.h +@@ -0,0 +1,561 @@ ++#ifndef GHEAP_H ++#define GHEAP_H ++ ++/* ++ * Generalized heap implementation for C99. ++ * ++ * Don't forget passing -DNDEBUG option to the compiler when creating optimized ++ * builds. This significantly speeds up gheap code by removing debug assertions. ++ * ++ * Author: Aliaksandr Valialkin . ++ */ ++/* ++Copyright (c) 2011 Aliaksandr Valialkin ++All rights reserved. ++ ++Redistribution and use in source and binary forms, with or without ++modification, are permitted provided that the following conditions ++are met: ++1. Redistributions of source code must retain the above copyright ++ notice, this list of conditions and the following disclaimer. ++2. Redistributions in binary form must reproduce the above copyright ++ notice, this list of conditions and the following disclaimer in the ++ documentation and/or other materials provided with the distribution. ++ ++THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ++ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE ++FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++SUCH DAMAGE. ++*/ ++ ++ ++ ++/******************************************************************************* ++ * Interface. ++ ******************************************************************************/ ++ ++#include /* for size_t */ ++#include /* for SIZE_MAX */ ++ ++/* ++ * Less comparer must return non-zero value if a < b. ++ * ctx is the gheap_ctx->less_comparer_ctx. ++ * Otherwise it must return 0. ++ */ ++typedef int (*gheap_less_comparer_t)(const void *ctx, const void *a, ++ const void *b); ++ ++/* ++ * Moves the item from src to dst. ++ */ ++typedef void (*gheap_item_mover_t)(void *dst, const void *src); ++ ++/* ++ * Gheap context. ++ * This context must be passed to every gheap function. ++ */ ++struct gheap_ctx ++{ ++ /* ++ * How much children each heap item can have. ++ */ ++ size_t fanout; ++ ++ /* ++ * A chunk is a tuple containing fanout items arranged sequentially in memory. ++ * A page is a subheap containing page_chunks chunks arranged sequentially ++ * in memory. ++ * The number of chunks in a page is an arbitrary integer greater than 0. ++ */ ++ size_t page_chunks; ++ ++ /* ++ * The size of each item in bytes. ++ */ ++ size_t item_size; ++ ++ gheap_less_comparer_t less_comparer; ++ const void *less_comparer_ctx; ++ ++ gheap_item_mover_t item_mover; ++}; ++ ++/* ++ * Returns parent index for the given child index. ++ * Child index must be greater than 0. ++ * Returns 0 if the parent is root. ++ */ ++static inline size_t gheap_get_parent_index(const struct gheap_ctx *ctx, ++ size_t u); ++ ++/* ++ * Returns the index of the first child for the given parent index. ++ * Parent index must be less than SIZE_MAX. ++ * Returns SIZE_MAX if the index of the first child for the given parent ++ * cannot fit size_t. ++ */ ++static inline size_t gheap_get_child_index(const struct gheap_ctx *ctx, ++ size_t u); ++ ++/* ++ * Returns a pointer to the first non-heap item using less_comparer ++ * for items' comparison. ++ * Returns the index of the first non-heap item. ++ * Returns heap_size if base points to valid max heap with the given size. ++ */ ++static inline size_t gheap_is_heap_until(const struct gheap_ctx *ctx, ++ const void *base, size_t heap_size); ++ ++/* ++ * Returns non-zero if base points to valid max heap. Returns zero otherwise. ++ * Uses less_comparer for items' comparison. ++ */ ++static inline int gheap_is_heap(const struct gheap_ctx *ctx, ++ const void *base, size_t heap_size); ++ ++/* ++ * Makes max heap from items base[0] ... base[heap_size-1]. ++ * Uses less_comparer for items' comparison. ++ */ ++static inline void gheap_make_heap(const struct gheap_ctx *ctx, ++ void *base, size_t heap_size); ++ ++/* ++ * Pushes the item base[heap_size-1] into max heap base[0] ... base[heap_size-2] ++ * Uses less_comparer for items' comparison. ++ */ ++static inline void gheap_push_heap(const struct gheap_ctx *ctx, ++ void *base, size_t heap_size); ++ ++/* ++ * Pops the maximum item from max heap base[0] ... base[heap_size-1] into ++ * base[heap_size-1]. ++ * Uses less_comparer for items' comparison. ++ */ ++static inline void gheap_pop_heap(const struct gheap_ctx *ctx, ++ void *base, size_t heap_size); ++ ++/* ++ * Sorts items in place of max heap in ascending order. ++ * Uses less_comparer for items' comparison. ++ */ ++static inline void gheap_sort_heap(const struct gheap_ctx *ctx, ++ void *base, size_t heap_size); ++ ++/* ++ * Swaps the item outside the heap with the maximum item inside ++ * the heap and restores heap invariant. ++ */ ++static inline void gheap_swap_max_item(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size, void *item); ++ ++/* ++ * Restores max heap invariant after item's value has been increased, ++ * i.e. less_comparer(old_item, new_item) != 0. ++ */ ++static inline void gheap_restore_heap_after_item_increase( ++ const struct gheap_ctx *ctx, ++ void *base, size_t heap_size, size_t modified_item_index); ++ ++/* ++ * Restores max heap invariant after item's value has been decreased, ++ * i.e. less_comparer(new_item, old_item) != 0. ++ */ ++static inline void gheap_restore_heap_after_item_decrease( ++ const struct gheap_ctx *ctx, ++ void *base, size_t heap_size, size_t modified_item_index); ++ ++/* ++ * Removes the given item from the heap and puts it into base[heap_size-1]. ++ * Uses less_comparer for items' comparison. ++ */ ++static inline void gheap_remove_from_heap(const struct gheap_ctx *ctx, ++ void *base, size_t heap_size, size_t item_index); ++ ++/******************************************************************************* ++ * Implementation. ++ * ++ * Define all functions inline, so compiler will be able optimizing out common ++ * args (fanout, page_chunks, item_size, less_comparer and item_mover), ++ * which are usually constants, using contant folding optimization ++ * ( http://en.wikipedia.org/wiki/Constant_folding ). ++ *****************************************************************************/ ++ ++#include /* for assert */ ++#include /* for size_t */ ++#include /* for uintptr_t, SIZE_MAX and UINTPTR_MAX */ ++ ++static inline size_t gheap_get_parent_index(const struct gheap_ctx *const ctx, ++ size_t u) ++{ ++ assert(u > 0); ++ ++ const size_t fanout = ctx->fanout; ++ const size_t page_chunks = ctx->page_chunks; ++ ++ --u; ++ if (page_chunks == 1) { ++ return u / fanout; ++ } ++ ++ if (u < fanout) { ++ /* Parent is root. */ ++ return 0; ++ } ++ ++ assert(page_chunks <= SIZE_MAX / fanout); ++ const size_t page_size = fanout * page_chunks; ++ size_t v = u % page_size; ++ if (v >= fanout) { ++ /* Fast path. Parent is on the same page as the child. */ ++ return u - v + v / fanout; ++ } ++ ++ /* Slow path. Parent is on another page. */ ++ v = u / page_size - 1; ++ const size_t page_leaves = (fanout - 1) * page_chunks + 1; ++ u = v / page_leaves + 1; ++ return u * page_size + v % page_leaves - page_leaves + 1; ++} ++ ++static inline size_t gheap_get_child_index(const struct gheap_ctx *const ctx, ++ size_t u) ++{ ++ assert(u < SIZE_MAX); ++ ++ const size_t fanout = ctx->fanout; ++ const size_t page_chunks = ctx->page_chunks; ++ ++ if (page_chunks == 1) { ++ if (u > (SIZE_MAX - 1) / fanout) { ++ /* Child overflow. */ ++ return SIZE_MAX; ++ } ++ return u * fanout + 1; ++ } ++ ++ if (u == 0) { ++ /* Root's child is always 1. */ ++ return 1; ++ } ++ ++ assert(page_chunks <= SIZE_MAX / fanout); ++ const size_t page_size = fanout * page_chunks; ++ --u; ++ size_t v = u % page_size + 1; ++ if (v < page_size / fanout) { ++ /* Fast path. Child is on the same page as the parent. */ ++ v *= fanout - 1; ++ if (u > SIZE_MAX - 2 - v) { ++ /* Child overflow. */ ++ return SIZE_MAX; ++ } ++ return u + v + 2; ++ } ++ ++ /* Slow path. Child is on another page. */ ++ const size_t page_leaves = (fanout - 1) * page_chunks + 1; ++ v += (u / page_size + 1) * page_leaves - page_size; ++ if (v > (SIZE_MAX - 1) / page_size) { ++ /* Child overflow. */ ++ return SIZE_MAX; ++ } ++ return v * page_size + 1; ++} ++ ++/* Returns a pointer to base[index]. */ ++static inline void *_gheap_get_item_ptr(const struct gheap_ctx *const ctx, ++ const void *const base, const size_t index) ++{ ++ const size_t item_size = ctx->item_size; ++ ++ assert(index <= SIZE_MAX / item_size); ++ ++ const size_t offset = item_size * index; ++ assert((uintptr_t)base <= UINTPTR_MAX - offset); ++ ++ return ((char *)base) + offset; ++} ++ ++/* ++ * Sifts the item up in the given sub-heap with the given root_index ++ * starting from the hole_index. ++ */ ++static inline void _gheap_sift_up(const struct gheap_ctx *const ctx, ++ void *const base, const size_t root_index, size_t hole_index, ++ const void *const item) ++{ ++ assert(hole_index >= root_index); ++ ++ const gheap_less_comparer_t less_comparer = ctx->less_comparer; ++ const void *const less_comparer_ctx = ctx->less_comparer_ctx; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ while (hole_index > root_index) { ++ const size_t parent_index = gheap_get_parent_index(ctx, hole_index); ++ assert(parent_index >= root_index); ++ const void *const parent = _gheap_get_item_ptr(ctx, base, parent_index); ++ if (!less_comparer(less_comparer_ctx, parent, item)) { ++ break; ++ } ++ item_mover(_gheap_get_item_ptr(ctx, base, hole_index), ++ parent); ++ hole_index = parent_index; ++ } ++ ++ item_mover(_gheap_get_item_ptr(ctx, base, hole_index), item); ++} ++ ++/* ++ * Moves the max child into the given hole and returns index ++ * of the new hole. ++ */ ++static inline size_t _gheap_move_up_max_child(const struct gheap_ctx *const ctx, ++ void *const base, const size_t children_count, ++ const size_t hole_index, const size_t child_index) ++{ ++ assert(children_count > 0); ++ assert(children_count <= ctx->fanout); ++ assert(child_index == gheap_get_child_index(ctx, hole_index)); ++ ++ const gheap_less_comparer_t less_comparer = ctx->less_comparer; ++ const void *const less_comparer_ctx = ctx->less_comparer_ctx; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ size_t max_child_index = child_index; ++ for (size_t i = 1; i < children_count; ++i) { ++ if (!less_comparer(less_comparer_ctx, ++ _gheap_get_item_ptr(ctx, base, child_index + i), ++ _gheap_get_item_ptr(ctx, base, max_child_index))) { ++ max_child_index = child_index + i; ++ } ++ } ++ item_mover(_gheap_get_item_ptr(ctx, base, hole_index), ++ _gheap_get_item_ptr(ctx, base, max_child_index)); ++ return max_child_index; ++} ++ ++/* ++ * Sifts the given item down in the heap of the given size starting ++ * from the hole_index. ++ */ ++static inline void _gheap_sift_down(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size, size_t hole_index, ++ const void *const item) ++{ ++ assert(heap_size > 0); ++ assert(hole_index < heap_size); ++ ++ const size_t fanout = ctx->fanout; ++ ++ const size_t root_index = hole_index; ++ const size_t last_full_index = heap_size - (heap_size - 1) % fanout; ++ while (1) { ++ const size_t child_index = gheap_get_child_index(ctx, hole_index); ++ if (child_index >= last_full_index) { ++ if (child_index < heap_size) { ++ assert(child_index == last_full_index); ++ hole_index = _gheap_move_up_max_child(ctx, base, ++ heap_size - child_index, hole_index, child_index); ++ } ++ break; ++ } ++ assert(heap_size - child_index >= fanout); ++ hole_index = _gheap_move_up_max_child(ctx, base, fanout, hole_index, ++ child_index); ++ } ++ _gheap_sift_up(ctx, base, root_index, hole_index, item); ++} ++ ++/* ++ * Pops the maximum item from the heap [base[0] ... base[heap_size-1]] ++ * into base[heap_size]. ++ */ ++static inline void _gheap_pop_max_item(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size) ++{ ++ void *const hole = _gheap_get_item_ptr(ctx, base, heap_size); ++ gheap_swap_max_item(ctx, base, heap_size, hole); ++} ++ ++static inline size_t gheap_is_heap_until(const struct gheap_ctx *const ctx, ++ const void *const base, const size_t heap_size) ++{ ++ const gheap_less_comparer_t less_comparer = ctx->less_comparer; ++ const void *const less_comparer_ctx = ctx->less_comparer_ctx; ++ ++ for (size_t u = 1; u < heap_size; ++u) { ++ const size_t v = gheap_get_parent_index(ctx, u); ++ const void *const a = _gheap_get_item_ptr(ctx, base, v); ++ const void *const b = _gheap_get_item_ptr(ctx, base, u); ++ if (less_comparer(less_comparer_ctx, a, b)) { ++ return u; ++ } ++ } ++ return heap_size; ++} ++ ++static inline int gheap_is_heap(const struct gheap_ctx *const ctx, ++ const void *const base, const size_t heap_size) ++{ ++ return (gheap_is_heap_until(ctx, base, heap_size) == heap_size); ++} ++ ++static inline void gheap_make_heap(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size) ++{ ++ const size_t fanout = ctx->fanout; ++ const size_t page_chunks = ctx->page_chunks; ++ const size_t item_size = ctx->item_size; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ if (heap_size > 1) { ++ /* Skip leaf nodes without children. This is easy to do for non-paged heap, ++ * i.e. when page_chunks = 1, but it is difficult for paged heaps. ++ * So leaf nodes in paged heaps are visited anyway. ++ */ ++ size_t i = (page_chunks == 1) ? ((heap_size - 2) / fanout) : ++ (heap_size - 2); ++ do { ++ char tmp[item_size]; ++ item_mover(tmp, _gheap_get_item_ptr(ctx, base, i)); ++ _gheap_sift_down(ctx, base, heap_size, i, tmp); ++ } while (i-- > 0); ++ } ++ ++ assert(gheap_is_heap(ctx, base, heap_size)); ++} ++ ++static inline void gheap_push_heap(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size) ++{ ++ assert(heap_size > 0); ++ assert(gheap_is_heap(ctx, base, heap_size - 1)); ++ ++ const size_t item_size = ctx->item_size; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ if (heap_size > 1) { ++ const size_t u = heap_size - 1; ++ char tmp[item_size]; ++ item_mover(tmp, _gheap_get_item_ptr(ctx, base, u)); ++ _gheap_sift_up(ctx, base, 0, u, tmp); ++ } ++ ++ assert(gheap_is_heap(ctx, base, heap_size)); ++} ++ ++static inline void gheap_pop_heap(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size) ++{ ++ assert(heap_size > 0); ++ assert(gheap_is_heap(ctx, base, heap_size)); ++ ++ if (heap_size > 1) { ++ _gheap_pop_max_item(ctx, base, heap_size - 1); ++ } ++ ++ assert(gheap_is_heap(ctx, base, heap_size - 1)); ++} ++ ++static inline void gheap_sort_heap(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size) ++{ ++ for (size_t i = heap_size; i > 1; --i) { ++ _gheap_pop_max_item(ctx, base, i - 1); ++ } ++} ++ ++static inline void gheap_swap_max_item(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size, void *item) ++{ ++ assert(heap_size > 0); ++ assert(gheap_is_heap(ctx, base, heap_size)); ++ ++ const size_t item_size = ctx->item_size; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ char tmp[item_size]; ++ item_mover(tmp, item); ++ item_mover(item, base); ++ _gheap_sift_down(ctx, base, heap_size, 0, tmp); ++ ++ assert(gheap_is_heap(ctx, base, heap_size)); ++} ++ ++static inline void gheap_restore_heap_after_item_increase( ++ const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size, size_t modified_item_index) ++{ ++ assert(heap_size > 0); ++ assert(modified_item_index < heap_size); ++ assert(gheap_is_heap(ctx, base, modified_item_index)); ++ ++ const size_t item_size = ctx->item_size; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ if (modified_item_index > 0) { ++ char tmp[item_size]; ++ item_mover(tmp, _gheap_get_item_ptr(ctx, base, modified_item_index)); ++ _gheap_sift_up(ctx, base, 0, modified_item_index, tmp); ++ } ++ ++ assert(gheap_is_heap(ctx, base, heap_size)); ++ (void)heap_size; ++} ++ ++static inline void gheap_restore_heap_after_item_decrease( ++ const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size, size_t modified_item_index) ++{ ++ assert(heap_size > 0); ++ assert(modified_item_index < heap_size); ++ assert(gheap_is_heap(ctx, base, modified_item_index)); ++ ++ const size_t item_size = ctx->item_size; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ char tmp[item_size]; ++ item_mover(tmp, _gheap_get_item_ptr(ctx, base, modified_item_index)); ++ _gheap_sift_down(ctx, base, heap_size, modified_item_index, tmp); ++ ++ assert(gheap_is_heap(ctx, base, heap_size)); ++} ++ ++static inline void gheap_remove_from_heap(const struct gheap_ctx *const ctx, ++ void *const base, const size_t heap_size, size_t item_index) ++{ ++ assert(heap_size > 0); ++ assert(item_index < heap_size); ++ assert(gheap_is_heap(ctx, base, heap_size)); ++ ++ const size_t item_size = ctx->item_size; ++ const gheap_less_comparer_t less_comparer = ctx->less_comparer; ++ const void *const less_comparer_ctx = ctx->less_comparer_ctx; ++ const gheap_item_mover_t item_mover = ctx->item_mover; ++ ++ const size_t new_heap_size = heap_size - 1; ++ if (item_index < new_heap_size) { ++ char tmp[item_size]; ++ void *const hole = _gheap_get_item_ptr(ctx, base, new_heap_size); ++ item_mover(tmp, hole); ++ item_mover(hole, _gheap_get_item_ptr(ctx, base, item_index)); ++ if (less_comparer(less_comparer_ctx, tmp, hole)) { ++ _gheap_sift_down(ctx, base, new_heap_size, item_index, tmp); ++ } ++ else { ++ _gheap_sift_up(ctx, base, 0, item_index, tmp); ++ } ++ } ++ ++ assert(gheap_is_heap(ctx, base, new_heap_size)); ++} ++ ++#endif + +commit 5b39471380a238469c8fc18136f12600e5e9aec7 +gpg: Signature made Sat 12 Aug 2023 02:49:21 PM EDT +gpg: using RSA key 5D38116FA4D3A7CC77E378D37E83610126DCC2E8 +gpg: Good signature from "Frank Ch. Eigler " [full] +Author: Frank Ch. Eigler +Date: Mon Jul 31 14:06:57 2023 -0400 + + PR29108 / BZ2095359: rewrite staprun serializer logic + + Logic in commit cd48874296e00 (2021, PR28449) fixed broken cross-cpu + message ordering that followed previous transport concurrency fixes, + but imposed a lot of userspace synchronization delays upon the threads + who were supposed to drain messages from the kernel relayfs streams as + fast as possible. This has led to unnecessarily lossy output overall. + + New code uses a new many-writers single-reader data structure, a mutex + protected heap. All the per-cpu readers copy & pump messages into + that heap as rapidly as possible, sorted by the generally monotonic + sequence number. The reader is signalled via a condition variable and + time to print & release messages in sequence number order. It also + handles lost messages (jumps in the sequence numbers) by waiting a while + to let the stragglers come in. + + The kernel-user messages now also include a framing sequence to allow + the per-cpu readers to resynchronize to the message boundaries, in + case some sort of buffer overflow or something else occurs. It + reports how many bytes and/or messages were skipped in order to + resynchronize. It does so in a lot less lossy way than previous code, + which just tried to flush everything then-currently available, hoping + that it'd match message boundaries. + + Unfortunately, this means that the user-kernel message ABI has + changed! Previous-version staprun instances won't work with the new + modules, nor will current-version staprun with old modules. This flag + day is enforced by changing the numbers of the various ctl message + numbers, so old/new kernel/user combinations will generate errors + rather than quasi-successful staprun startup. + + New code also dramatically simplifies the use of signals in staprun + (or rather stapio). Gone is the signal thread, a lot of the + masking/blocking/waiting. Instead a single basic signal handler just + increments globals when signals of various kinds arrive, and all the + per-cpu etc. threads poll those globals periodically. This includes + logic needed for -S (output file rotation on SIGUSR2) as well as + flight recorder (-L / -A) modes. + + The reader_timeout_ms value (-T) in both bulk/serialized mode for all + ppoll timeouts, to prevent those threads from sleeping indefinitely, + now that they won't be bothered by signals. + +diff --git a/configure b/configure +index 974cc2c81..1ff5580b4 100755 +--- a/configure ++++ b/configure +@@ -12694,6 +12694,14 @@ printf "%s\n" "$as_me: WARNING: cannot find librpmio" >&2;} + fi + fi + ++ac_fn_c_check_header_compile "$LINENO" "stdatomic.h" "ac_cv_header_stdatomic_h" "$ac_includes_default" ++if test "x$ac_cv_header_stdatomic_h" = xyes ++then : ++ printf "%s\n" "#define HAVE_STDATOMIC_H 1" >>confdefs.h ++ ++fi ++ ++ + for ac_header in rpm/rpmcrypto.h + do : + ac_fn_c_check_header_compile "$LINENO" "rpm/rpmcrypto.h" "ac_cv_header_rpm_rpmcrypto_h" "$ac_includes_default" +diff --git a/configure.ac b/configure.ac +index 3f184f862..e9176b725 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -490,6 +490,8 @@ if test "$with_rpm" != "no"; then + fi + fi + ++AC_CHECK_HEADERS([stdatomic.h]) ++ + dnl Look for rpmcrypto.h + AC_CHECK_HEADERS([rpm/rpmcrypto.h], [ + AC_DEFINE([HAVE_RPMCRYPTO_H],[1],[have rpmcrypto_h]) +diff --git a/man/stap.1.in b/man/stap.1.in +index 4e1f0a537..c1a81fef3 100644 +--- a/man/stap.1.in ++++ b/man/stap.1.in +@@ -388,7 +388,7 @@ With \-o option, run staprun in background as a daemon and show its pid. + Sets the maximum size of output file and the maximum number of output files. + If the size of output file will exceed + .B size +-, systemtap switches output file to the next file. And if the number of ++megabytes, systemtap switches output file to the next file. And if the number of + output files exceed + .B N + , systemtap removes the oldest output file. You can omit the second argument. +diff --git a/runtime/print_flush.c b/runtime/print_flush.c +index 35677b225..4141f95b9 100644 +--- a/runtime/print_flush.c ++++ b/runtime/print_flush.c +@@ -43,6 +43,7 @@ static void __stp_print_flush(struct _stp_log *log) + if (likely(entry && bytes_reserved > hlen)) { + /* copy new _stp_trace_ header */ + struct _stp_trace t = { ++ .magic = STAP_TRACE_MAGIC, + .sequence = _stp_seq_inc(), + .pdu_len = len + }; +diff --git a/runtime/transport/control.c b/runtime/transport/control.c +index 3d7333403..d0a8bdf53 100644 +--- a/runtime/transport/control.c ++++ b/runtime/transport/control.c +@@ -57,7 +57,7 @@ static ssize_t _stp_ctl_write_cmd(struct file *file, const char __user *buf, siz + + #if defined(DEBUG_TRANS) && (DEBUG_TRANS >= 2) + if (type < STP_MAX_CMD) +- dbug_trans2("Got %s. euid=%ld, len=%d\n", _stp_command_name[type], ++ dbug_trans2("Got %s. euid=%ld, len=%d\n", _stp_command_name[min(type,STP_MAX_CMD)] ?: "?", + (long)euid, (int)count); + #endif + +@@ -211,7 +211,9 @@ out: + + #if defined(DEBUG_TRANS) && (DEBUG_TRANS >= 2) + if (type < STP_MAX_CMD) +- dbug_trans2("Completed %s (rc=%d)\n", _stp_command_name[type], rc); ++ dbug_trans2("Completed %s (rc=%d)\n", ++ _stp_command_name[min(type,STP_MAX_CMD)] ?: "?", ++ rc); + #endif + return rc; + } +diff --git a/runtime/transport/transport_msgs.h b/runtime/transport/transport_msgs.h +index 9e0081c80..e3aa995b1 100644 +--- a/runtime/transport/transport_msgs.h ++++ b/runtime/transport/transport_msgs.h +@@ -1,7 +1,7 @@ + /* -*- linux-c -*- + * transport_msgs.h - messages exchanged between module and userspace + * +- * Copyright (C) Red Hat Inc, 2006-2011 ++ * Copyright (C) Red Hat Inc, 2006-2023 + * + * This file is part of systemtap, and is free software. You can + * redistribute it and/or modify it under the terms of the GNU General +@@ -19,7 +19,9 @@ + #define STP_TZ_NAME_LEN 64 + #define STP_REMOTE_URI_LEN 128 + ++#define STAP_TRACE_MAGIC "\xF0\x9F\xA9\xBA" /* unicode stethoscope 🩺 in UTF-8 */ + struct _stp_trace { ++ char magic[4]; /* framing helper */ + uint32_t sequence; /* event number */ + uint32_t pdu_len; /* length of data after this trace */ + }; +@@ -30,7 +32,7 @@ enum + /** stapio sends a STP_START after recieving a STP_TRANSPORT from + the module. The module sends STP_START back with result of call + systemtap_module_init() which will install all initial probes. */ +- STP_START, ++ STP_START = 0x50, // renumbered in version 5.0 to force incompatibility + /** stapio sends STP_EXIT to signal it wants to stop the module + itself or in response to receiving a STP_REQUEST_EXIT. + The module sends STP_EXIT once _stp_clean_and_exit has been +@@ -87,16 +89,21 @@ enum + /** Send by staprun to notify module of remote identity, if any. + Only send once at startup. */ + STP_REMOTE_ID, ++ /** Placeholder, it was mistakenly labeled STP_MAX_CMD */ ++ STP_MAX_CMD_PLACEHOLDER, ++ /** Sent by stapio after having recevied STP_TRANSPORT. Notifies ++ the module of the target namespaces pid.*/ ++ STP_NAMESPACES_PID, ++ ++ /** INSERT NEW MESSAGE TYPES HERE */ ++ + /** Max number of message types, sanity check only. */ + STP_MAX_CMD, +- /** Sent by stapio after having recevied STP_TRANSPORT. Notifies +- the module of the target namespaces pid.*/ +- STP_NAMESPACES_PID + }; + + #ifdef DEBUG_TRANS +-static const char *_stp_command_name[] = { +- "STP_START", ++static const char *_stp_command_name[STP_MAX_CMD] = { ++ [STP_START]="STP_START", + "STP_EXIT", + "STP_OOB_DATA", + "STP_SYSTEM", +@@ -113,7 +120,9 @@ static const char *_stp_command_name[] = { + "STP_TZINFO", + "STP_PRIVILEGE_CREDENTIALS", + "STP_REMOTE_ID", +- "STP_NAMESPACES_PID", ++ "STP_MAX_CMD_PLACEHOLDER", ++ "STP_NAMESPACE_PID", ++ [STP_MAX_CMD]="?" /* in control.c, STP_MAX_CMD represents unknown message numbers/names */ + }; + #endif /* DEBUG_TRANS */ + +diff --git a/staprun/common.c b/staprun/common.c +index 3d23d7319..f8d618e24 100644 +--- a/staprun/common.c ++++ b/staprun/common.c +@@ -115,7 +115,7 @@ void parse_args(int argc, char **argv) + target_pid = 0; + target_namespaces_pid = 0; + buffer_size = 0; +- reader_timeout_ms = 0; ++ reader_timeout_ms = 200; + target_cmd = NULL; + outfile_name = NULL; + rename_mod = 0; +diff --git a/staprun/mainloop.c b/staprun/mainloop.c +index 4af21e950..c507fc069 100644 +--- a/staprun/mainloop.c ++++ b/staprun/mainloop.c +@@ -7,7 +7,7 @@ + * Public License (GPL); either version 2, or (at your option) any + * later version. + * +- * Copyright (C) 2005-2021 Red Hat Inc. ++ * Copyright (C) 2005-2023 Red Hat Inc. + */ + + #include "staprun.h" +@@ -23,31 +23,9 @@ + + /* globals */ + int ncpus; +-static int pending_interrupts = 0; ++static volatile sig_atomic_t pending_interrupts = 0; // tells stp_main_loop to trigger STP_EXIT message to kernel + static int target_pid_failed_p = 0; + +-/* Setup by setup_main_signals, used by signal_thread to notify the +- main thread of interruptable events. */ +-static pthread_t main_thread; +- +-static void set_nonblocking_std_fds(void) +-{ +- int fd; +- for (fd = 1; fd < 3; fd++) { +- /* NB: writing to stderr/stdout blockingly in signal handler is +- * dangerous since it may prevent the stap process from quitting +- * gracefully on receiving SIGTERM/etc signals when the stderr/stdout +- * write buffer is full. PR23891 */ +- int flags = fcntl(fd, F_GETFL); +- if (flags == -1) +- continue; +- +- if (flags & O_NONBLOCK) +- continue; +- +- (void) fcntl(fd, F_SETFL, flags | O_NONBLOCK); +- } +-} + + static void set_blocking_std_fds(void) + { +@@ -77,43 +55,16 @@ static void my_exit(int rc) + _exit(rc); + } + +-static void *signal_thread(void *arg) +-{ +- sigset_t *s = (sigset_t *) arg; +- int signum = 0; + +- while (1) { +- if (sigwait(s, &signum) < 0) { +- _perr("sigwait"); +- continue; +- } ++ ++static void interrupt_handler(int signum) ++{ + if (signum == SIGQUIT) { + load_only = 1; /* flag for stp_main_loop */ +- pending_interrupts ++; +- } else if (signum == SIGINT || signum == SIGHUP || signum == SIGTERM +- || signum == SIGPIPE) +- { +- pending_interrupts ++; + } +- if (pending_interrupts > 2) { +- set_nonblocking_std_fds(); +- pthread_kill (main_thread, SIGURG); +- } +- dbug(2, "sigproc %d (%s)\n", signum, strsignal(signum)); +- } +- /* Notify main thread (interrupts select). */ +- pthread_kill (main_thread, SIGURG); +- return NULL; ++ pending_interrupts ++; + } + +-static void urg_proc(int signum) +-{ +- /* This handler is just notified from the signal_thread +- whenever an interruptable condition is detected. The +- handler itself doesn't do anything. But this will +- result select to detect an EINTR event. */ +- dbug(2, "urg_proc %d (%s)\n", signum, strsignal(signum)); +-} + + static void chld_proc(int signum) + { +@@ -143,9 +94,9 @@ static void chld_proc(int signum) + (void) rc; /* XXX: notused */ + } + ++ + static void setup_main_signals(void) + { +- pthread_t tid; + struct sigaction sa; + sigset_t *s = malloc(sizeof(*s)); + if (!s) { +@@ -153,25 +104,11 @@ static void setup_main_signals(void) + exit(1); + } + +- /* The main thread will only handle SIGCHLD and SIGURG. +- SIGURG is send from the signal thread in case the interrupt +- flag is set. This will then interrupt any select call. */ +- main_thread = pthread_self(); +- sigfillset(s); +- pthread_sigmask(SIG_SETMASK, s, NULL); +- + memset(&sa, 0, sizeof(sa)); + /* select will report EINTR even when SA_RESTART is set. */ + sa.sa_flags = SA_RESTART; + sigfillset(&sa.sa_mask); + +- /* Ignore all these events on the main thread. */ +- sa.sa_handler = SIG_IGN; +- sigaction(SIGINT, &sa, NULL); +- sigaction(SIGTERM, &sa, NULL); +- sigaction(SIGHUP, &sa, NULL); +- sigaction(SIGQUIT, &sa, NULL); +- + /* This is to notify when our child process (-c) ends. */ + sa.sa_handler = chld_proc; + sigaction(SIGCHLD, &sa, NULL); +@@ -182,26 +119,21 @@ static void setup_main_signals(void) + sigaction(SIGWINCH, &sa, NULL); + } + +- /* This signal handler is notified from the signal_thread +- whenever a interruptable event is detected. It will +- result in an EINTR event for select or sleep. */ +- sa.sa_handler = urg_proc; +- sigaction(SIGURG, &sa, NULL); +- +- /* Everything else is handled on a special signal_thread. */ +- sigemptyset(s); +- sigaddset(s, SIGINT); +- sigaddset(s, SIGTERM); +- sigaddset(s, SIGHUP); +- sigaddset(s, SIGQUIT); +- sigaddset(s, SIGPIPE); +- pthread_sigmask(SIG_SETMASK, s, NULL); +- if (pthread_create(&tid, NULL, signal_thread, s) < 0) { +- _perr(_("failed to create thread")); +- exit(1); +- } ++ // listen to these signals via general interrupt handler in whatever thread ++ memset(&sa, 0, sizeof(sa)); ++ sa.sa_flags = SA_RESTART; ++ sigfillset(&sa.sa_mask); ++ ++ sa.sa_handler = interrupt_handler; ++ sigaction(SIGINT, &sa, NULL); ++ sigaction(SIGTERM, &sa, NULL); ++ sigaction(SIGHUP, &sa, NULL); ++ sigaction(SIGQUIT, &sa, NULL); ++ ++ // Formerly, we had a signal catching thread. + } + ++ + /** + * system_cmd() executes system commands in response + * to an STP_SYSTEM message from the module. These +diff --git a/staprun/relay.c b/staprun/relay.c +index dea1d5ae9..08850b246 100644 +--- a/staprun/relay.c ++++ b/staprun/relay.c +@@ -7,30 +7,32 @@ + * Public License (GPL); either version 2, or (at your option) any + * later version. + * +- * Copyright (C) 2007-2013 Red Hat Inc. ++ * Copyright (C) 2007-2023 Red Hat Inc. + */ + + #include "staprun.h" ++#include ++#ifdef HAVE_STDATOMIC_H ++#include ++#endif ++#define NDEBUG ++#include "gheap.h" ++ + + int out_fd[MAX_NR_CPUS]; + int monitor_end = 0; + static pthread_t reader[MAX_NR_CPUS]; +-static int relay_fd[MAX_NR_CPUS]; ++static int relay_fd[MAX_NR_CPUS]; // fd to kernel per-cpu relayfs + static int avail_cpus[MAX_NR_CPUS]; +-static int switch_file[MAX_NR_CPUS]; +-static pthread_mutex_t mutex[MAX_NR_CPUS]; ++static volatile sig_atomic_t sigusr2_count; // number of SIGUSR2's received by process ++static int sigusr2_processed[MAX_NR_CPUS]; // each thread's count of processed SIGUSR2's + static int bulkmode = 0; +-static volatile int stop_threads = 0; ++static volatile int stop_threads = 0; // set during relayfs_close to signal threads to die + static time_t *time_backlog[MAX_NR_CPUS]; + static int backlog_order=0; + #define BACKLOG_MASK ((1 << backlog_order) - 1) + #define MONITORLINELENGTH 4096 + +-/* tracking message sequence #s for cross-cpu merging */ +-static uint32_t last_sequence_number; +-static pthread_mutex_t last_sequence_number_mutex = PTHREAD_MUTEX_INITIALIZER; +-static pthread_cond_t last_sequence_number_changed = PTHREAD_COND_INITIALIZER; +- + #ifdef NEED_PPOLL + int ppoll(struct pollfd *fds, nfds_t nfds, + const struct timespec *timeout, const sigset_t *sigmask) +@@ -123,18 +125,375 @@ static int switch_outfile(int cpu, int *fnum) + return 0; + } + ++ ++ ++/* In serialized (non-bulk) output mode, ndividual messages that have ++ been received from the kernel per-cpu relays are stored in an central ++ serializing data structure - in this case, a heap. They are ordered ++ by message sequence number. An additional thread (serializer_thread) ++ scans & sequences the output. */ ++struct serialized_message { ++ union { ++ struct _stp_trace bufhdr; ++ char bufhdr_raw[sizeof(struct _stp_trace)]; ++ }; ++ time_t received; // timestamp when this message was enqueued ++ char *buf; // malloc()'d size >= rounded(bufhdr.pdu_len) ++}; ++static struct serialized_message* buffer_heap = NULL; // the heap ++ ++// NB: we control memory via realloc(), gheap just manipulates entries in place ++static unsigned buffer_heap_size = 0; // used number of entries ++static unsigned buffer_heap_alloc = 0; // allocation length, always >= buffer_heap_size ++static unsigned last_sequence_number = 0; // last processed sequential message number ++ ++#ifdef HAVE_STDATOMIC_H ++static atomic_ulong lost_message_count = 0; // how many sequence numbers we know we missed ++static atomic_ulong lost_byte_count = 0; // how many bytes were skipped during resync ++#else ++static unsigned long lost_message_count = 0; // how many sequence numbers we know we missed ++static unsigned long lost_byte_count = 0; // how many bytes were skipped during resync ++#endif ++ ++// concurrency control for the buffer_heap ++static pthread_cond_t buffer_heap_cond = PTHREAD_COND_INITIALIZER; ++static pthread_mutex_t buffer_heap_mutex = PTHREAD_MUTEX_INITIALIZER; ++static pthread_t serializer_thread; // ! bulkmode only ++ ++ ++static void buffer_heap_mover (void *const dest, const void *const src) ++{ ++ memmove (dest, src, sizeof(struct serialized_message)); ++} ++ ++// NB: since we want to sort messages into an INCREASING heap sequence, ++// we reverse the normal comparison operator. gheap_pop_heap() should ++// therefore return the SMALLEST element. ++static int buffer_heap_comparer (const void *const ctx, const void *a, const void *b) ++{ ++ (void) ctx; ++ uint32_t aa = ((struct serialized_message *)a)->bufhdr.sequence; ++ uint32_t bb = ((struct serialized_message *)b)->bufhdr.sequence; ++ return (aa > bb); ++} ++ ++static const struct gheap_ctx buffer_heap_ctx = { ++ .item_size = sizeof(struct serialized_message), ++ .less_comparer = buffer_heap_comparer, ++ .item_mover = buffer_heap_mover, ++ .page_chunks = 16, // arbitrary ++ .fanout = 2 // standard binary heap ++}; ++ ++ ++#define MAX_MESSAGE_LENGTH (128*1024) /* maximum likely length of a single pdu */ ++ ++ ++ ++/* Thread that reads per-cpu messages, and stuffs complete ones into ++ dynamically allocated serialized_message nodes in a binary tree. */ ++static void* reader_thread_serialmode (void *data) ++{ ++ int rc, cpu = (int)(long)data; ++ struct pollfd pollfd; ++ sigset_t sigs; ++ cpu_set_t cpu_mask; ++ ++ sigemptyset(&sigs); ++ sigaddset(&sigs,SIGUSR2); ++ pthread_sigmask(SIG_BLOCK, &sigs, NULL); ++ ++ sigfillset(&sigs); ++ sigdelset(&sigs,SIGUSR2); ++ ++ CPU_ZERO(&cpu_mask); ++ CPU_SET(cpu, &cpu_mask); ++ if( sched_setaffinity( 0, sizeof(cpu_mask), &cpu_mask ) < 0 ) ++ _perr("sched_setaffinity"); ++ ++ pollfd.fd = relay_fd[cpu]; ++ pollfd.events = POLLIN; ++ ++ while (! stop_threads) { ++ // read a message header ++ struct serialized_message message; ++ ++ /* 200ms, close to human level of "instant" */ ++ struct timespec tim, *timeout = &tim; ++ timeout->tv_sec = reader_timeout_ms / 1000; ++ timeout->tv_nsec = (reader_timeout_ms - timeout->tv_sec * 1000) * 1000000; ++ ++ rc = ppoll(&pollfd, 1, timeout, &sigs); ++ if (rc < 0) { ++ dbug(3, "cpu=%d poll=%d errno=%d\n", cpu, rc, errno); ++ if (errno == EINTR) { ++ if (stop_threads) ++ break; ++ } else { ++ _perr("poll error"); ++ goto error_out; ++ } ++ } ++ ++ // set the timestamp ++ message.received = time(NULL); ++ ++ /* Read the header. */ ++ rc = read(relay_fd[cpu], &message.bufhdr, sizeof(message.bufhdr)); ++ if (rc <= 0) /* seen during normal shutdown or error */ ++ continue; ++ if (rc != sizeof(message.bufhdr)) { ++ lost_byte_count += rc; ++ continue; ++ } ++ ++ /* Validate the magic value. In case of mismatch, ++ keep on reading & shifting the header, one byte at ++ a time, until we get a match. */ ++ while (! stop_threads && memcmp(message.bufhdr.magic, STAP_TRACE_MAGIC, 4)) { ++ lost_byte_count ++; ++ memmove(& message.bufhdr_raw[0], ++ & message.bufhdr_raw[1], ++ sizeof(message.bufhdr_raw)-1); ++ rc = read(relay_fd[cpu], ++ &message.bufhdr_raw[sizeof(message.bufhdr_raw)-1], ++ 1); ++ if (rc <= 0) /* seen during normal shutdown or error */ ++ break; ++ } ++ ++ /* Validate it slightly. Because of lost messages, we might be getting ++ not a proper _stp_trace struct but the interior of some piece of ++ trace text message. XXX: validate bufhdr.sequence a little bit too? */ ++ if (message.bufhdr.pdu_len == 0 || ++ message.bufhdr.pdu_len > MAX_MESSAGE_LENGTH) { ++ lost_byte_count += sizeof(message.bufhdr); ++ continue; ++ } ++ ++ // Allocate the pdu body ++ message.buf = malloc(message.bufhdr.pdu_len); ++ if (message.buf == NULL) ++ { ++ lost_byte_count += message.bufhdr.pdu_len; ++ continue; ++ } ++ ++ /* Read the message, perhaps in pieces (such as if crossing ++ * relayfs subbuf boundaries). */ ++ size_t bufread = 0; ++ while (bufread < message.bufhdr.pdu_len) { ++ rc = read(relay_fd[cpu], message.buf+bufread, message.bufhdr.pdu_len-bufread); ++ if (rc <= 0) { ++ lost_byte_count += message.bufhdr.pdu_len-bufread; ++ break; /* still process it; hope we can resync at next packet. */ ++ } ++ bufread += rc; ++ } ++ ++ // plop the message into the buffer_heap ++ pthread_mutex_lock(& buffer_heap_mutex); ++ if (message.bufhdr.sequence < last_sequence_number) { ++ // whoa! is this some old message that we've assumed lost? ++ // or are we wrapping around the uint_32 sequence numbers? ++ _perr("unexpected sequence=%u", message.bufhdr.sequence); ++ } ++ ++ // is it large enough? if not, realloc ++ if (buffer_heap_alloc - buffer_heap_size == 0) { // full ++ unsigned new_buffer_heap_alloc = (buffer_heap_alloc + 1) * 1.5; ++ struct serialized_message *new_buffer_heap = ++ realloc(buffer_heap, ++ new_buffer_heap_alloc * sizeof(struct serialized_message)); ++ if (new_buffer_heap == NULL) { ++ _perr("out of memory while enlarging buffer heap"); ++ free (message.buf); ++ lost_message_count ++; ++ pthread_mutex_unlock(& buffer_heap_mutex); ++ continue; ++ } ++ buffer_heap = new_buffer_heap; ++ buffer_heap_alloc = new_buffer_heap_alloc; ++ } ++ // plop copy of message struct into slot at end of heap ++ buffer_heap[buffer_heap_size++] = message; ++ // push it into heap ++ gheap_push_heap(&buffer_heap_ctx, ++ buffer_heap, ++ buffer_heap_size); ++ // and c'est tout ++ pthread_mutex_unlock(& buffer_heap_mutex); ++ pthread_cond_broadcast (& buffer_heap_cond); ++ dbug(3, "thread %d received seq=%u\n", cpu, message.bufhdr.sequence); ++ } ++ ++ dbug(3, "exiting thread for cpu %d\n", cpu); ++ return NULL; ++ ++error_out: ++ /* Signal the main thread that we need to quit */ ++ kill(getpid(), SIGTERM); ++ dbug(2, "exiting thread for cpu %d after error\n", cpu); ++ ++ return NULL; ++} ++ ++ ++// Print and free buffer of given serialized message. ++static void print_serialized_message (struct serialized_message *msg) ++{ ++ // check if file switching is necessary, as per staprun -S ++ ++ // NB: unlike reader_thread_bulkmode(), we don't need to use ++ // mutexes to protect switch_file[] or such, because we're the ++ // ONLY thread doing output. ++ unsigned cpu = 0; // arbitrary ++ static ssize_t wsize = 0; // how many bytes we've written into the serialized file so far ++ static int fnum = 0; // which file number we're using ++ ++ if ((fsize_max && (wsize > fsize_max)) || ++ (sigusr2_count > sigusr2_processed[cpu])) { ++ dbug(2, "switching output file wsize=%ld fsize_max=%ld sigusr2 %d > %d\n", ++ wsize, fsize_max, sigusr2_count, sigusr2_processed[cpu]); ++ sigusr2_processed[cpu] = sigusr2_count; ++ if (switch_outfile(cpu, &fnum) < 0) { ++ perr("unable to switch output file"); ++ // but continue ++ } ++ wsize = 0; ++ } ++ ++ ++ // write loop ... could block if e.g. the output disk is slow ++ // or the user hits a ^S (XOFF) on the tty ++ ssize_t sent = 0; ++ do { ++ ssize_t ret = write (out_fd[avail_cpus[0]], ++ msg->buf+sent, msg->bufhdr.pdu_len-sent); ++ if (ret <= 0) { ++ perr("error writing output"); ++ break; ++ } ++ sent += ret; ++ } while ((unsigned)sent < msg->bufhdr.pdu_len); ++ wsize += sent; ++ ++ // free the associated buffer ++ free (msg->buf); ++ msg->buf = NULL; ++} ++ ++ ++/* Thread that checks on the heap of messages, and pumps them out to ++ the designated output fd in sequence. It waits, but only a little ++ while, if it has only fresher messages than it's expecting. It ++ exits upon a global stop_threads indication. ++*/ ++static void* reader_thread_serializer (void *data) { ++ (void) data; ++ while (! stop_threads) { ++ /* timeout 0-1 seconds; this is the maximum extra time that ++ stapio will be waiting after a ^C */ ++ struct timespec ts = {.tv_sec=time(NULL)+1, .tv_nsec=0}; ++ int rc; ++ pthread_mutex_lock(& buffer_heap_mutex); ++ rc = pthread_cond_timedwait (& buffer_heap_cond, ++ & buffer_heap_mutex, ++ & ts); ++ ++ dbug(3, "serializer cond wait rc=%d heapsize=%u\n", rc, buffer_heap_size); ++ time_t now = time(NULL); ++ unsigned processed = 0; ++ while (buffer_heap_size > 0) { // consume as much as possible ++ // check out the sequence# of the first element ++ uint32_t buf_min_seq = buffer_heap[0].bufhdr.sequence; ++ ++ dbug(3, "serializer last=%u seq=%u\n", last_sequence_number, buf_min_seq); ++ ++ if ((buf_min_seq == last_sequence_number + 1) || // expected seq# ++ (buffer_heap[0].received + 2 <= now)) { // message too old ++ // "we've got one!" -- or waited too long for one ++ // get it off the head of the heap ++ gheap_pop_heap(&buffer_heap_ctx, ++ buffer_heap, ++ buffer_heap_size); ++ buffer_heap_size --; // becomes index where the head was moved ++ processed ++; ++ ++ // take a copy of the whole message ++ struct serialized_message msg = buffer_heap[buffer_heap_size]; ++ ++ // paranoid clear this field of the now-unused slot ++ buffer_heap[buffer_heap_size].buf = NULL; ++ // update statistics ++ if (attach_mod == 1 && last_sequence_number == 0) // first message after staprun -A ++ ; // do not penalize it with lost messages ++ else ++ lost_message_count += (buf_min_seq - last_sequence_number - 1); ++ last_sequence_number = buf_min_seq; ++ ++ // unlock the mutex, permitting ++ // reader_thread_serialmode threads to ++ // resume piling messages into the ++ // heap while we print stuff ++ pthread_mutex_unlock(& buffer_heap_mutex); ++ ++ print_serialized_message (& msg); ++ ++ // must re-take lock for next iteration of the while loop ++ pthread_mutex_lock(& buffer_heap_mutex); ++ } else { ++ // processed as much of the heap as we ++ // could this time; wait for the ++ // condition again ++ break; ++ } ++ } ++ pthread_mutex_unlock(& buffer_heap_mutex); ++ if (processed > 0) ++ dbug(2, "serializer processed n=%u\n", processed); ++ } ++ return NULL; ++} ++ ++ ++ ++// At the end of the program main loop, flush out any the remaining ++// messages and free up all that heapy data. ++static void reader_serialized_flush() ++{ ++ dbug(3, "serializer flushing messages=%u\n", buffer_heap_size); ++ while (buffer_heap_size > 0) { // consume it all ++ // check out the sequence# of the first element ++ uint32_t buf_min_seq = buffer_heap[0].bufhdr.sequence; ++ dbug(3, "serializer seq=%u\n", buf_min_seq); ++ gheap_pop_heap(&buffer_heap_ctx, ++ buffer_heap, ++ buffer_heap_size); ++ buffer_heap_size --; // also index where the head was moved ++ ++ // NB: no need for mutex manipulations, this is super single threaded ++ print_serialized_message (& buffer_heap[buffer_heap_size]); ++ ++ lost_message_count += (buf_min_seq - last_sequence_number - 1); ++ last_sequence_number = buf_min_seq; ++ } ++ free (buffer_heap); ++ buffer_heap = NULL; ++} ++ ++ ++ + /** +- * reader_thread - per-cpu channel buffer reader ++ * reader_thread - per-cpu channel buffer reader, bulkmode (one output file per cpu input file) + */ +-static void *reader_thread(void *data) ++static void *reader_thread_bulkmode (void *data) + { +- char buf[128*1024]; // NB: maximum possible output amount from a single probe hit's print_flush ++ char buf[MAX_MESSAGE_LENGTH]; + struct _stp_trace bufhdr; + + int rc, cpu = (int)(long)data; + struct pollfd pollfd; +- /* 200ms, close to human level of "instant" */ +- struct timespec tim = {.tv_sec=0, .tv_nsec=200000000}, *timeout = &tim; + sigset_t sigs; + off_t wsize = 0; + int fnum = 0; +@@ -151,44 +510,30 @@ static void *reader_thread(void *data) + CPU_SET(cpu, &cpu_mask); + if( sched_setaffinity( 0, sizeof(cpu_mask), &cpu_mask ) < 0 ) + _perr("sched_setaffinity"); +-#ifdef NEED_PPOLL +- /* Without a real ppoll, there is a small race condition that could */ +- /* block ppoll(). So use a timeout to prevent that. */ +- timeout->tv_sec = 10; +- timeout->tv_nsec = 0; +-#else +- timeout = NULL; +-#endif +- +- if (reader_timeout_ms && timeout) { +- timeout->tv_sec = reader_timeout_ms / 1000; +- timeout->tv_nsec = (reader_timeout_ms - timeout->tv_sec * 1000) * 1000000; +- } + + pollfd.fd = relay_fd[cpu]; + pollfd.events = POLLIN; + + do { +- dbug(3, "thread %d start ppoll\n", cpu); ++ /* 200ms, close to human level of "instant" */ ++ struct timespec tim, *timeout = &tim; ++ timeout->tv_sec = reader_timeout_ms / 1000; ++ timeout->tv_nsec = (reader_timeout_ms - timeout->tv_sec * 1000) * 1000000; ++ + rc = ppoll(&pollfd, 1, timeout, &sigs); +- dbug(3, "thread %d end ppoll:%d\n", cpu, rc); + if (rc < 0) { + dbug(3, "cpu=%d poll=%d errno=%d\n", cpu, rc, errno); + if (errno == EINTR) { + if (stop_threads) + break; + +- pthread_mutex_lock(&mutex[cpu]); +- if (switch_file[cpu]) { +- if (switch_outfile(cpu, &fnum) < 0) { +- switch_file[cpu] = 0; +- pthread_mutex_unlock(&mutex[cpu]); ++ if (sigusr2_count > sigusr2_processed[cpu]) { ++ sigusr2_processed[cpu] = sigusr2_count; ++ if (switch_outfile(cpu, &fnum) < 0) { + goto error_out; +- } +- switch_file[cpu] = 0; +- wsize = 0; ++ } ++ wsize = 0; + } +- pthread_mutex_unlock(&mutex[cpu]); + } else { + _perr("poll error"); + goto error_out; +@@ -197,7 +542,7 @@ static void *reader_thread(void *data) + + /* Read the header. */ + rc = read(relay_fd[cpu], &bufhdr, sizeof(bufhdr)); +- if (rc == 0) /* seen during normal shutdown */ ++ if (rc <= 0) /* seen during normal shutdown */ + continue; + if (rc != sizeof(bufhdr)) { + _perr("bufhdr read error, attempting resync"); +@@ -228,41 +573,20 @@ static void *reader_thread(void *data) + bufread += rc; + } + +- if (! bulkmode) { +- /* Wait until the bufhdr.sequence number indicates it's OUR TURN to go ahead. */ +- struct timespec ts = {.tv_sec=time(NULL)+2, .tv_nsec=0}; /* wait 1-2 seconds */ +- pthread_mutex_lock(& last_sequence_number_mutex); +- while ((last_sequence_number+1 != bufhdr.sequence) && /* normal case */ +- (last_sequence_number < bufhdr.sequence)) { /* we're late!!! */ +- int rc = pthread_cond_timedwait (& last_sequence_number_changed, +- & last_sequence_number_mutex, +- & ts); +- if (rc == ETIMEDOUT) { +- /* _perr("message sequencing timeout"); */ +- break; +- } +- } +- pthread_mutex_unlock(& last_sequence_number_mutex); +- } +- + int wbytes = rc; + char *wbuf = buf; + + dbug(3, "cpu %d: read %d bytes of data\n", cpu, rc); + + /* Switching file */ +- pthread_mutex_lock(&mutex[cpu]); + if ((fsize_max && ((wsize + rc) > fsize_max)) || +- switch_file[cpu]) { ++ (sigusr2_count > sigusr2_processed[cpu])) { ++ sigusr2_processed[cpu] = sigusr2_count; + if (switch_outfile(cpu, &fnum) < 0) { +- switch_file[cpu] = 0; +- pthread_mutex_unlock(&mutex[cpu]); + goto error_out; + } +- switch_file[cpu] = 0; + wsize = 0; + } +- pthread_mutex_unlock(&mutex[cpu]); + + /* Copy loop. Must repeat write(2) in case of a pipe overflow + or other transient fullness. */ +@@ -291,13 +615,8 @@ static void *reader_thread(void *data) + int fd; + /* Only bulkmode and fsize_max use per-cpu output files. Otherwise, + there's just a single output fd stored at out_fd[avail_cpus[0]]. */ +- if (bulkmode || fsize_max) +- fd = out_fd[cpu]; +- else +- fd = out_fd[avail_cpus[0]]; +- rc = 0; +- if (bulkmode) +- rc = write(fd, &bufhdr, sizeof(bufhdr)); // write header ++ fd = out_fd[cpu]; ++ rc = write(fd, &bufhdr, sizeof(bufhdr)); // write header + rc |= write(fd, wbuf, wbytes); // write payload + if (rc <= 0) { + perr("Couldn't write to output %d for cpu %d, exiting.", +@@ -310,14 +629,6 @@ static void *reader_thread(void *data) + } + } + +- /* update the sequence number & let other cpus go ahead */ +- pthread_mutex_lock(& last_sequence_number_mutex); +- if (last_sequence_number < bufhdr.sequence) { /* not if someone leapfrogged us */ +- last_sequence_number = bufhdr.sequence; +- pthread_cond_broadcast (& last_sequence_number_changed); +- } +- pthread_mutex_unlock(& last_sequence_number_mutex); +- + } while (!stop_threads); + dbug(3, "exiting thread for cpu %d\n", cpu); + return(NULL); +@@ -329,41 +640,16 @@ error_out: + return(NULL); + } + ++ + static void switchfile_handler(int sig) + { +- int i; ++ (void) sig; + if (stop_threads || !outfile_name) + return; +- +- for (i = 0; i < ncpus; i++) { +- pthread_mutex_lock(&mutex[avail_cpus[i]]); +- if (reader[avail_cpus[i]] && switch_file[avail_cpus[i]]) { +- pthread_mutex_unlock(&mutex[avail_cpus[i]]); +- dbug(2, "file switching is progressing, signal ignored.\n", sig); +- return; +- } +- pthread_mutex_unlock(&mutex[avail_cpus[i]]); +- } +- for (i = 0; i < ncpus; i++) { +- pthread_mutex_lock(&mutex[avail_cpus[i]]); +- if (reader[avail_cpus[i]]) { +- switch_file[avail_cpus[i]] = 1; +- pthread_mutex_unlock(&mutex[avail_cpus[i]]); +- +- // Make sure we don't send the USR2 signal to +- // ourselves. +- if (pthread_equal(pthread_self(), +- reader[avail_cpus[i]])) +- break; +- pthread_kill(reader[avail_cpus[i]], SIGUSR2); +- } +- else { +- pthread_mutex_unlock(&mutex[avail_cpus[i]]); +- break; +- } +- } ++ sigusr2_count ++; + } + ++ + /** + * init_relayfs - create files and threads for relayfs processing + * +@@ -507,19 +793,20 @@ int init_relayfs(void) + sigaction(SIGUSR2, &sa, NULL); + + dbug(2, "starting threads\n"); +- for (i = 0; i < ncpus; i++) { +- if (pthread_mutex_init(&mutex[avail_cpus[i]], NULL) < 0) { +- _perr("failed to create mutex"); +- return -1; +- } +- } + for (i = 0; i < ncpus; i++) { +- if (pthread_create(&reader[avail_cpus[i]], NULL, reader_thread, ++ if (pthread_create(&reader[avail_cpus[i]], NULL, ++ bulkmode ? reader_thread_bulkmode : reader_thread_serialmode, + (void *)(long)avail_cpus[i]) < 0) { + _perr("failed to create thread"); + return -1; + } + } ++ if (! bulkmode) ++ if (pthread_create(&serializer_thread, NULL, ++ reader_thread_serializer, NULL) < 0) { ++ _perr("failed to create thread"); ++ return -1; ++ } + + return 0; + } +@@ -529,27 +816,31 @@ void close_relayfs(void) + int i; + stop_threads = 1; + dbug(2, "closing\n"); +- for (i = 0; i < ncpus; i++) { +- if (reader[avail_cpus[i]]) +- pthread_kill(reader[avail_cpus[i]], SIGUSR2); +- else +- break; +- } ++ + for (i = 0; i < ncpus; i++) { + if (reader[avail_cpus[i]]) + pthread_join(reader[avail_cpus[i]], NULL); + else + break; + } ++ if (! bulkmode) { ++ if (serializer_thread) // =0 on load_only! ++ pthread_join(serializer_thread, NULL); ++ // at this point, we know all reader and writer ++ // threads for the buffer_heap are dead. ++ reader_serialized_flush(); ++ ++ if (lost_message_count > 0 || lost_byte_count > 0) ++ eprintf("WARNING: There were %u lost messages and %u lost bytes.\n", ++ lost_message_count, lost_byte_count); ++ } ++ + for (i = 0; i < ncpus; i++) { + if (relay_fd[avail_cpus[i]] >= 0) + close(relay_fd[avail_cpus[i]]); + else + break; + } +- for (i = 0; i < ncpus; i++) { +- pthread_mutex_destroy(&mutex[avail_cpus[i]]); +- } + dbug(2, "done\n"); + } + +@@ -558,12 +849,6 @@ void kill_relayfs(void) + int i; + stop_threads = 1; + dbug(2, "killing\n"); +- for (i = 0; i < ncpus; i++) { +- if (reader[avail_cpus[i]]) +- pthread_kill(reader[avail_cpus[i]], SIGUSR2); +- else +- break; +- } + for (i = 0; i < ncpus; i++) { + if (reader[avail_cpus[i]]) + pthread_cancel(reader[avail_cpus[i]]); /* no wait */ +@@ -576,8 +861,5 @@ void kill_relayfs(void) + else + break; + } +- for (i = 0; i < ncpus; i++) { +- pthread_mutex_destroy(&mutex[avail_cpus[i]]); +- } + dbug(2, "done\n"); + } +diff --git a/staprun/stap_merge.c b/staprun/stap_merge.c +index 87de7d465..b210db663 100644 +--- a/staprun/stap_merge.c ++++ b/staprun/stap_merge.c +@@ -76,6 +76,7 @@ int main (int argc, char *argv[]) + fprintf(stderr, "error opening file %s.\n", argv[optind - 1]); + return -1; + } ++ (void) fread(buf, 4, 1, fp[i]); // read & ignore magic word + if (fread (buf, TIMESTAMP_SIZE, 1, fp[i])) + num[i] = *((int *)buf); + else +@@ -133,6 +134,7 @@ int main (int argc, char *argv[]) + count = min; + } + ++ (void) fread(buf, 4, 1, fp[i]); // read & ignore magic word + if (fread (buf, TIMESTAMP_SIZE, 1, fp[j])) + num[j] = *((int *)buf); + else +diff --git a/staprun/stap_merge.tcl b/staprun/stap_merge.tcl +deleted file mode 100755 +index 0c7d7b694..000000000 +--- a/staprun/stap_merge.tcl ++++ /dev/null +@@ -1,101 +0,0 @@ +-#!/usr/bin/env tclsh +-# +-# stap_merge.tcl - systemtap merge program +-# +-# This program is free software; you can redistribute it and/or modify +-# it under the terms of the GNU General Public License as published by +-# the Free Software Foundation; either version 2 of the License, or +-# (at your option) any later version. +-# +-# This program is distributed in the hope that it will be useful, +-# but WITHOUT ANY WARRANTY; without even the implied warranty of +-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-# GNU General Public License for more details. +-# +-# You should have received a copy of the GNU General Public License +-# along with this program. If not, see . +-# +-# Copyright (C) Red Hat Inc, 2007 +-# +-# +- +-proc usage {} { +- puts stderr "$::argv0 \[-v\] \[-o output_filename\] input_files ...\n" +- exit 1 +-} +- +-set outfile "stdout" +-set verbose 0 +-set index 0 +-while {[string match -* [lindex $argv $index]]} { +- switch -glob -- [lindex $argv $index] { +- -v {set verbose 1} +- -o {incr index; set outfile [lindex $argv $index]} +- default {usage} +- } +- incr index +-} +- +-if {$tcl_platform(byteOrder) == "littleEndian"} { +- set int_format i +-} else { +- set int_format I +-} +- +-set files [lrange $argv $index end] +- +-set n 0 +-foreach file $files { +- if {[catch {open $file} fd($n)]} { +- puts stderr $fd($n) +- exit 1 +- } +- fconfigure $fd($n) -translation binary +- if {![binary scan [read $fd($n) 4] $int_format timestamp($n)]} { +- continue +- } +- set timestamp($n) [expr $timestamp($n) & 0xFFFFFFFF] +- incr n +-} +-set ncpus $n +- +-if {$outfile != "stdout"} { +- if {[catch {open $outfile w} outfile]} { +- puts stderr $outfile +- exit 1 +- } +-} +-fconfigure $outfile -translation binary +- +-while {1} { +- set mincpu -1 +- for {set n 0} {$n < $ncpus} {incr n} { +- if {[info exists fd($n)] && (![info exists min] || $timestamp($n) <= $min)} { +- set min $timestamp($n) +- set mincpu $n +- } +- } +- +- if {![info exists min]} {break} +- +- if {![binary scan [read $fd($mincpu) 4] $int_format len]} { +- puts stderr "Error reading length from channel $mincpu" +- exit 1 +- } +- +- if {$verbose == 1} { +- puts stderr "\[CPU:$mincpu, seq=$min, length=$len\]" +- } +- +- set data [read $fd($mincpu) $len] +- puts -nonewline $outfile $data +- +- set data [read $fd($mincpu) 4] +- if {$data == ""} { +- unset fd($mincpu) +- } else { +- binary scan $data $int_format timestamp($mincpu) +- set timestamp($mincpu) [expr $timestamp($mincpu) & 0xFFFFFFFF] +- } +- unset min +-} +diff --git a/staprun/staprun.8 b/staprun/staprun.8 +index 3bc16ab95..4e1ca9af6 100644 +--- a/staprun/staprun.8 ++++ b/staprun/staprun.8 +@@ -120,7 +120,7 @@ remote_id() and remote_uri(). + Sets the maximum size of output file and the maximum number of output files. + If the size of output file will exceed + .B size +-, systemtap switches output file to the next file. And if the number of ++megabytes, systemtap switches output file to the next file. And if the number of + output files exceed + .B N + , systemtap removes the oldest output file. You can omit the second argument. +commit 2442beb99eeab3144c2622cae1fc98b999f72108 +gpg: Signature made Mon 14 Aug 2023 01:55:27 PM EDT +gpg: using RSA key 5D38116FA4D3A7CC77E378D37E83610126DCC2E8 +gpg: Good signature from "Frank Ch. Eigler " [full] +Author: Frank Ch. Eigler +Date: Mon Aug 14 13:54:50 2023 -0400 + + PR29108 / BZ2095359 tweak: stap_merge magic handling + + We don't bother do much error checking in this infrequently used + tool, but gcc warnings require us to do some. + +diff --git a/staprun/stap_merge.c b/staprun/stap_merge.c +index b210db663..388b14938 100644 +--- a/staprun/stap_merge.c ++++ b/staprun/stap_merge.c +@@ -76,7 +76,8 @@ int main (int argc, char *argv[]) + fprintf(stderr, "error opening file %s.\n", argv[optind - 1]); + return -1; + } +- (void) fread(buf, 4, 1, fp[i]); // read & ignore magic word ++ if (fread(buf, 4, 1, fp[i]) != 1) // read magic word ++ fprintf(stderr, "warning: erro reading magic word\n"); + if (fread (buf, TIMESTAMP_SIZE, 1, fp[i])) + num[i] = *((int *)buf); + else +@@ -134,7 +135,8 @@ int main (int argc, char *argv[]) + count = min; + } + +- (void) fread(buf, 4, 1, fp[i]); // read & ignore magic word ++ if (fread(buf, 4, 1, fp[i]) != 1) // read magic word ++ fprintf(stderr, "warning: erro reading magic word\n"); + if (fread (buf, TIMESTAMP_SIZE, 1, fp[j])) + num[j] = *((int *)buf); + else diff --git a/SOURCES/pr30749.patch b/SOURCES/pr30749.patch new file mode 100644 index 0000000..108a642 --- /dev/null +++ b/SOURCES/pr30749.patch @@ -0,0 +1,99 @@ +commit 9839db5514a29cf4f58b3de8cc6155088be6d061 +gpg: Signature made Sat 12 Aug 2023 02:49:26 PM EDT +gpg: using RSA key 5D38116FA4D3A7CC77E378D37E83610126DCC2E8 +gpg: Good signature from "Frank Ch. Eigler " [full] +Author: Frank Ch. Eigler +Date: Sat Aug 12 14:28:44 2023 -0400 + + PR30749: correct stap --sign-module timing + + Previous code signed the temp directory copy, after it had already + been copied into the cache -- so the signature never made it to a + permanent artifact. + + If the module was being fetched from the cache from a previous build + run, a sign (re)attempt will still be done. This may not be + necessary, but shouldn't be harmful. + + Reported-By: Renaud Métrich + +diff --git a/main.cxx b/main.cxx +index 06adb66ad..9f695cbd8 100644 +--- a/main.cxx ++++ b/main.cxx +@@ -1190,8 +1190,10 @@ passes_0_4 (systemtap_session &s) + s.mok_fingerprints.clear(); + s.mok_fingerprints.push_back(mok_fingerprint); + } +- rc = +- sign_module (s.tmpdir, s.module_filename(), s.mok_fingerprints, mok_path, s.kernel_build_tree); ++ if (s.verbose) ++ clog << _F("Signing %s with mok key %s", s.module_filename().c_str(), mok_path.c_str()) ++ << endl; ++ rc = sign_module (s.tmpdir, s.module_filename(), s.mok_fingerprints, mok_path, s.kernel_build_tree); + } + #endif + +@@ -1310,8 +1312,30 @@ passes_0_4 (systemtap_session &s) + if (! s.use_script_cache && s.last_pass <= 4) + s.save_module = true; + ++#if HAVE_NSS ++ // PR30749 ++ if (!rc && s.module_sign_given) ++ { ++ // when run on client as --sign-module, mok fingerprints are result of mokutil -l ++ // when run from server as --sign-module=PATH, mok fingerprint is given by PATH ++ string mok_path; ++ if (!s.module_sign_mok_path.empty()) ++ { ++ string mok_fingerprint; ++ split_path (s.module_sign_mok_path, mok_path, mok_fingerprint); ++ s.mok_fingerprints.clear(); ++ s.mok_fingerprints.push_back(mok_fingerprint); ++ } ++ ++ if (s.verbose) ++ clog << _F("Signing %s with mok key %s", s.module_filename().c_str(), mok_path.c_str()) ++ << endl; ++ rc = sign_module (s.tmpdir, s.module_filename(), s.mok_fingerprints, mok_path, s.kernel_build_tree); ++ } ++#endif ++ + // Copy module to the current directory. +- if (s.save_module && !pending_interrupts) ++ if (!rc && s.save_module && !pending_interrupts) + { + string module_src_path = s.tmpdir + "/" + s.module_filename(); + string module_dest_path = s.module_filename(); +@@ -1327,29 +1351,11 @@ passes_0_4 (systemtap_session &s) + } + } + +-#if HAVE_NSS +- if (s.module_sign_given) +- { +- // when run on client as --sign-module, mok fingerprints are result of mokutil -l +- // when run from server as --sign-module=PATH, mok fingerprint is given by PATH +- string mok_path; +- if (!s.module_sign_mok_path.empty()) +- { +- string mok_fingerprint; +- split_path (s.module_sign_mok_path, mok_path, mok_fingerprint); +- s.mok_fingerprints.clear(); +- s.mok_fingerprints.push_back(mok_fingerprint); +- } +- +- rc = sign_module (s.tmpdir, s.module_filename(), s.mok_fingerprints, mok_path, s.kernel_build_tree); +- } +-#endif +- + PROBE1(stap, pass4__end, &s); + + return rc; + } +- ++ + int + pass_5 (systemtap_session &s, vector targets) + { diff --git a/SOURCES/rhbz1997192.patch b/SOURCES/rhbz1997192.patch deleted file mode 100644 index 4500e85..0000000 --- a/SOURCES/rhbz1997192.patch +++ /dev/null @@ -1,50 +0,0 @@ -commit 47cab810bb7ea315a2dec23c2f61e7ba74515b82 -Author: Martin Cermak -Date: Fri Dec 16 16:08:20 2022 -0500 - - tapset: nfs.proc.commit_done compilation on some kernels - - Correct: - 9.0 Server x86_64 # stap -vp3 nfs.proc.commit_done.stp - - Pass 1: parsed user script and 482 library scripts using 108088virt/88468res/12460shr/75476data kb, in 190usr/60sys/501real ms. - semantic error: invalid access '->task' vs 'void*': operator '->' at /usr/share/systemtap/tapset/linux/nfs_proc.stpm:16:21 - source: ( get_ip(&@nfs_data->task) ) - ^ - in expansion of macro: operator '@_nfs_data_server_ip' at /usr/share/systemtap/tapset/linux/nfs_proc.stp:1421:15 - source: server_ip = @_nfs_data_server_ip($task->tk_calldata) - ^ - -diff --git a/tapset/linux/nfs_proc.stp b/tapset/linux/nfs_proc.stp -index 9b1f65f5f..4413384f9 100644 ---- a/tapset/linux/nfs_proc.stp -+++ b/tapset/linux/nfs_proc.stp -@@ -1442,10 +1442,11 @@ probe nfs.proc3.commit_done = kernel.function("nfs3_commit_done") !, - valid = @_nfs_data_valid($data) - } - else { -- server_ip = @_nfs_data_server_ip($task->tk_calldata) -- prot = @_nfs_data_prot($task->tk_calldata) -- count = @_nfs_data_res_count($task->tk_calldata) -- valid = @_nfs_data_valid($task->tk_calldata) -+ _tk_calldata=@choose_defined($task->tk_calldata, @cast($task, "rpc_task", "kernel:nfs")->tk_calldata) -+ server_ip = @_nfs_data_server_ip(_tk_calldata) -+ prot = @_nfs_data_prot(_tk_calldata) -+ count = @_nfs_data_res_count(_tk_calldata) -+ valid = @_nfs_data_valid(_tk_calldata) - } - timestamp = 0 - version = 3 -diff --git a/tapset/linux/nfs_proc.stpm b/tapset/linux/nfs_proc.stpm -index 8576c0f33..4fe40b2a5 100644 ---- a/tapset/linux/nfs_proc.stpm -+++ b/tapset/linux/nfs_proc.stpm -@@ -13,7 +13,7 @@ - - @define _nfs_data_server_ip(nfs_data) - %( -- ( get_ip(&@nfs_data->task) ) -+ ( get_ip(@choose_defined(&@nfs_data->task, &@cast(@nfs_data, "nfs_commit_data", "kernel:nfs")->task)) ) - %) - - @define _nfs_data_prot(nfs_data) diff --git a/SOURCES/rhbz2145242.patch b/SOURCES/rhbz2145242.patch deleted file mode 100644 index 1853a64..0000000 --- a/SOURCES/rhbz2145242.patch +++ /dev/null @@ -1,30 +0,0 @@ -commit 578e60102871d11ed8c18d36f6286f3a96258d8f -Author: Ryan Goldberg -Date: Thu Dec 1 16:15:44 2022 -0500 - - PR29676: Wildcard expansion fix for labels - - PR29676, introduced an bug where function symbols from the symbol - table were expanded in the function component resulting in wildcards - not being expanded in labels. This fix, removes the issue by restricting - the symbol table query to probes which don't need further debuginfo to - expand. - -diff --git a/tapsets.cxx b/tapsets.cxx -index 0ec71ebda..46b10f26e 100644 ---- a/tapsets.cxx -+++ b/tapsets.cxx -@@ -1256,7 +1256,11 @@ dwarf_query::handle_query_module() - // in the symbol table but not in dwarf and minidebuginfo is - // located in the gnu_debugdata section, alias_dupes checking - // is done before adding any probe points -- if(!pending_interrupts) -+ // PR29676. Some probes require additional debuginfo -+ // to expand wildcards (ex. .label, .callee). Since the debuginfo is -+ // not available, don't bother looking in the symbol table for these results. -+ // This can result in 0 results, if there is no dwarf info present -+ if(!pending_interrupts && !(has_label || has_callee || has_callees_num)) - query_module_symtab(); - } - - diff --git a/SOURCES/rhbz2149223.patch b/SOURCES/rhbz2149223.patch deleted file mode 100644 index 75ba486..0000000 --- a/SOURCES/rhbz2149223.patch +++ /dev/null @@ -1,78 +0,0 @@ -commit 05eb6742c169226ae09f1737aa8b9dc1dc12adb5 -Author: Mark Wielaard -Date: Tue Nov 29 18:50:58 2022 +0100 - - Handle DWARF5 DW_OP_implicit_pointer and DW_OP_entry_value - - These are the same as the GNU extensions for older DWARF, - DW_OP_GNU_implicit_pointer and DW_GNU_entry_value. - -diff --git a/loc2stap.cxx b/loc2stap.cxx -index efc78cc57..53316a480 100644 ---- a/loc2stap.cxx -+++ b/loc2stap.cxx -@@ -23,6 +23,11 @@ - #define DW_OP_GNU_entry_value 0xf3 - #endif - -+#if ! _ELFUTILS_PREREQ(0, 171) -+#define DW_OP_entry_value 0xa3 -+#define DW_OP_implicit_pointer 0xa0 -+#endif -+ - #define N_(x) x - - -@@ -372,7 +377,7 @@ location_context::translate (const Dwarf_Op *expr, const size_t len, - DIE ("operations follow DW_OP_implicit_value"); - - if (implicit_pointer != NULL) -- DIE ("operations follow DW_OP_GNU_implicit_pointer"); -+ DIE ("operations follow DW_OP implicit_pointer"); - } - - switch (expr[i].atom) -@@ -662,6 +667,7 @@ location_context::translate (const Dwarf_Op *expr, const size_t len, - - #if _ELFUTILS_PREREQ (0, 149) - case DW_OP_GNU_implicit_pointer: -+ case DW_OP_implicit_pointer: - implicit_pointer = &expr[i]; - /* Fake top of stack: implicit_pointer being set marks it. */ - PUSH(NULL); -@@ -684,10 +690,11 @@ location_context::translate (const Dwarf_Op *expr, const size_t len, - break; - - case DW_OP_GNU_entry_value: -+ case DW_OP_entry_value: - { - expression *result = handle_GNU_entry_value (expr[i]); - if (result == NULL) -- DIE("DW_OP_GNU_entry_value unable to resolve value"); -+ DIE("DW_OP entry_value unable to resolve value"); - PUSH(result); - } - break; -@@ -1248,7 +1255,8 @@ location_context::location_relative (const Dwarf_Op *expr, size_t len, - break; - - case DW_OP_GNU_entry_value: -- DIE ("unhandled DW_OP_GNU_entry_value"); -+ case DW_OP_entry_value: -+ DIE ("unhandled DW_OP entry_value"); - break; - - default: -diff --git a/testsuite/systemtap.base/dw_entry_value.exp b/testsuite/systemtap.base/dw_entry_value.exp -index 7339fc5fa..b728fa7ff 100644 ---- a/testsuite/systemtap.base/dw_entry_value.exp -+++ b/testsuite/systemtap.base/dw_entry_value.exp -@@ -10,7 +10,7 @@ if { $res != "" } { - pass "$test: compiled $test.c" - } - --if { ![catch { exec eu-readelf -w $test | grep GNU_entry_value }] } { -+if { ![catch { exec eu-readelf -w $test | grep entry_value: }] } { - stap_run $test no_load $all_pass_string $srcdir/$subdir/$test.stp -c ./${test} -w - } else { - untested "$test: no DW_OP_GNU_entry_value found" diff --git a/SOURCES/rhbz2149666.patch b/SOURCES/rhbz2149666.patch deleted file mode 100644 index 74be846..0000000 --- a/SOURCES/rhbz2149666.patch +++ /dev/null @@ -1,73 +0,0 @@ -commit 7eed8d1fef36997b9e4c1d9cdb67643483a51e56 -Author: William Cohen -Date: Fri Nov 4 11:12:05 2022 -0400 - - Ensure that SystemTap runtime uses smp_processor_id() in proper context - - There were cases on Fedora 36 and Rawhide running kernels with - CONFIG_DEBUG_PREEMPT=y where systemtap scripts would trigger kernel - log messages like the following: - - [ 257.544406] check_preemption_disabled: 4 callbacks suppressed - [ 257.544409] BUG: using smp_processor_id() in preemptible [00000000] code: staprun/2106 - [ 257.544465] caller is _stp_runtime_context_trylock+0x12/0x70 [stap_e36600406768aeefd49daf9fc7a3d23c_2106] - [ 257.544507] CPU: 0 PID: 2106 Comm: staprun Tainted: G OE ------- --- 6.1.0-0.rc2.20221028git23758867219c.24.fc38.x86_64 #1 - [ 257.544544] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.0-1.fc36 04/01/2014 - [ 257.544571] Call Trace: - [ 257.544583] - [ 257.544593] dump_stack_lvl+0x5b/0x77 - [ 257.544620] check_preemption_disabled+0xe1/0xf0 - [ 257.544641] _stp_runtime_context_trylock+0x12/0x70 [stap_e36600406768aeefd49daf9fc7a3d23c_2106] - [ 257.544673] _stp_runtime_entryfn_get_context+0xb/0x70 [stap_e36600406768aeefd49daf9fc7a3d23c_2106] - [ 257.544705] _stp_ctl_send+0x76/0x1e0 [stap_e36600406768aeefd49daf9fc7a3d23c_2106] - [ 257.544735] _stp_transport_init+0x71a/0x860 [stap_e36600406768aeefd49daf9fc7a3d23c_2106] - [ 257.544771] ? kallsyms_on_each_symbol+0x30/0x30 [stap_e36600406768aeefd49daf9fc7a3d23c_2106] - [ 257.544803] do_one_initcall+0x6b/0x320 - [ 257.544827] do_init_module+0x4a/0x200 - [ 257.544844] __do_sys_init_module+0x16a/0x1a0 - [ 257.544870] do_syscall_64+0x58/0x80 - [ 257.544885] ? up_read+0x17/0x20 - [ 257.544902] ? lock_is_held_type+0xe8/0x140 - [ 257.544921] ? asm_exc_page_fault+0x22/0x30 - [ 257.544939] ? lockdep_hardirqs_on+0x7d/0x100 - [ 257.544956] entry_SYSCALL_64_after_hwframe+0x63/0xcd - [ 257.544975] RIP: 0033:0x7f3cde12f5de - [ 257.544992] Code: 48 8b 0d 35 68 0c 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 49 89 ca b8 af 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 02 68 0c 00 f7 d8 64 89 01 48 - [ 257.545010] RSP: 002b:00007ffc5170c418 EFLAGS: 00000246 ORIG_RAX: 00000000000000af - [ 257.545010] RAX: ffffffffffffffda RBX: 0000563620bd4020 RCX: 00007f3cde12f5de - [ 257.545010] RDX: 0000563620bd4020 RSI: 0000000000040ea0 RDI: 00007f3cde44a010 - [ 257.545010] RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000 - [ 257.545010] R10: 0000000000000053 R11: 0000000000000246 R12: 00007ffc5170c510 - [ 257.545010] R13: 00007f3cde44a010 R14: 00007ffc5170c440 R15: 00007f3cde4631e8 - [ 257.545010] - - This issue was introduced by git commit 1641b6e7ea which added a fast - path check that used smp_processor_id() without first having a - preempt_disable(). The code now ensures that preemption is disabled - before using the smp_processor_id(). - -diff --git a/runtime/linux/runtime_context.h b/runtime/linux/runtime_context.h -index 3ed3cbd22..ee3870f32 100644 ---- a/runtime/linux/runtime_context.h -+++ b/runtime/linux/runtime_context.h -@@ -49,13 +49,18 @@ static bool _stp_runtime_context_trylock(void) - { - bool locked; - -+ /* Need to disable preemption because of the smp_processor_id() call -+ in _stp_runtime_get_context(). */ -+ preempt_disable(); -+ - /* fast path to ignore new online CPUs without percpu context memory - * allocations. this also serves as an extra safe guard for NULL context - * pointers. */ -- if (unlikely(_stp_runtime_get_context() == NULL)) -+ if (unlikely(_stp_runtime_get_context() == NULL)) { -+ preempt_enable_no_resched(); - return false; -+ } - -- preempt_disable(); - locked = atomic_add_unless(&_stp_contexts_busy_ctr, 1, INT_MAX); - if (!locked) - preempt_enable_no_resched(); diff --git a/SOURCES/rhbz2154430.patch b/SOURCES/rhbz2154430.patch deleted file mode 100644 index 0e6a7c2..0000000 --- a/SOURCES/rhbz2154430.patch +++ /dev/null @@ -1,72 +0,0 @@ -commit 8f3498781413a710dc9e128f5a96891a6a18fc52 -Author: Martin Cermak -Date: Wed Dec 14 17:37:58 2022 +0100 - - PR29766: kernel.function("__set_page_dirty_buffers") not found in vfs.stp - - Updates needed for 6.1.0-65.fc38 compatibility. - -diff --git a/tapset/linux/vfs.stp b/tapset/linux/vfs.stp -index e3c1a3032..aebeb3fc0 100644 ---- a/tapset/linux/vfs.stp -+++ b/tapset/linux/vfs.stp -@@ -822,13 +822,15 @@ probe vfs.buffer_migrate_page.return = - } - - /* default if aop not set, __set_page_dirty_nobuffers usually used if set */ --probe vfs.__set_page_dirty_buffers = kernel.function("__set_page_dirty_buffers") -+/* block_dirty_folio related to kernel commit e621900ad28b748e058b81d6078 */ -+probe vfs.__set_page_dirty_buffers = kernel.function("__set_page_dirty_buffers")!, -+ kernel.function("block_dirty_folio") - { -- dev = __page_dev($page) -- devname = __find_bdevname(dev, __page_bdev($page)) -- ino = __page_ino($page) -+ dev = __page_dev(@choose_defined($page, $folio)) -+ devname = __find_bdevname(dev, __page_bdev(@choose_defined($page, $folio))) -+ ino = __page_ino(@choose_defined($page, $folio)) - -- index = __page_index($page) -+ index = __page_index(@choose_defined($page, $folio)) - - name = "__set_page_dirty_buffers" - argstr = sprintf("%d", index) -@@ -837,7 +839,8 @@ probe vfs.__set_page_dirty_buffers = kernel.function("__set_page_dirty_buffers") - units = "pages" - } - probe vfs.__set_page_dirty_buffers.return = -- kernel.function("__set_page_dirty_buffers").return -+ kernel.function("__set_page_dirty_buffers").return!, -+ kernel.function("block_dirty_folio") - { - name = "__set_page_dirty_buffers" - retstr = sprintf("%d", $return) -@@ -914,8 +917,9 @@ probe __vfs.ext4_mpage_readpages.return = - - - /* newer style */ -+/* add_to_page_cache_locked removed per kernel commit f00654007fe1c15 */ - probe vfs.__add_to_page_cache = -- kernel.function("add_to_page_cache_locked"), -+ kernel.function("add_to_page_cache_locked") !, - kernel.function("add_to_page_cache_lru") - { } - -@@ -939,7 +943,7 @@ probe vfs.add_to_page_cache = - - /* newer style */ - probe vfs.__add_to_page_cache.return = -- kernel.function("add_to_page_cache_locked").return, -+ kernel.function("add_to_page_cache_locked").return !, - kernel.function("add_to_page_cache_lru").return - { } - -@@ -958,7 +962,7 @@ probe vfs.add_to_page_cache.return = - - probe vfs.remove_from_page_cache = - kernel.function("__delete_from_page_cache") !, -- kernel.function("__remove_from_page_cache") -+ kernel.function("__remove_from_page_cache") ? - { - dev = __page_dev($page) - devname = __find_bdevname(dev, __page_bdev($page)) diff --git a/SOURCES/rhbz2223733.patch b/SOURCES/rhbz2223733.patch new file mode 100644 index 0000000..81255ed --- /dev/null +++ b/SOURCES/rhbz2223733.patch @@ -0,0 +1,24 @@ +commit ead30c04c7157fec194c0f6d81e5c51c99bf25cf +gpg: Signature made Wed 24 May 2023 10:23:54 AM EDT +gpg: using RSA key 5D38116FA4D3A7CC77E378D37E83610126DCC2E8 +gpg: Good signature from "Frank Ch. Eigler " [full] +Author: Frank Ch. Eigler +Date: Wed May 24 10:22:08 2023 -0400 + + PR30484: stap-report: scrape less of /sys /proc + + Mainly: avoid process/busy parts like /proc/$pid. + +diff --git a/stap-report b/stap-report +index 217ddf840..3b3a1a258 100755 +--- a/stap-report ++++ b/stap-report +@@ -105,7 +105,7 @@ elif [ -f /var/log/packages ]; then + run "cat /var/log/packages | egrep 'systemtap|elfutils|kernel|gcc|dyninst|java|byteman|avahi|nss|nspr|dejagnu' | sort -k9" + fi + run "egrep 'PROBE|RANDOMIZE|RELOC|TRACE|MARKER|KALLSYM|_DEBUG_|LOCKDEP|LOCKING|MODULE|FENTRY|_SIG|BPF' /lib/modules/`uname -r`/build/.config | grep -v not.set | sort | fmt -w 80" +-run "find /debugfs /proc /sys /dev /sys/kernel/debug -type f -path '*kprobe*' -o -path '*yama*' 2>/dev/null | xargs grep -H ." ++run "find /debugfs /proc/sys /sys/kernel /dev -type f -path '*kprobe*' -o -path '*yama*' 2>/dev/null | xargs grep -H ." + run "lsmod" + run "avahi-browse -r -t _stap._tcp" + run "ifconfig -a" diff --git a/SOURCES/rhbz2223735.patch b/SOURCES/rhbz2223735.patch new file mode 100644 index 0000000..2f21386 --- /dev/null +++ b/SOURCES/rhbz2223735.patch @@ -0,0 +1,64 @@ +commit ab0c5c25509600b7c9cecc9e10baebc984082b50 +gpg: Signature made Fri 12 May 2023 11:18:18 AM EDT +gpg: using RSA key 5D38116FA4D3A7CC77E378D37E83610126DCC2E8 +gpg: Good signature from "Frank Ch. Eigler " [full] +Author: Frank Ch. Eigler +Date: Fri May 12 11:13:45 2023 -0400 + + PR30442: failing optional statement probes should not trigger pass2 exceptions + + In tapsets.cxx, query_cu() and query_module() aggressively caught & + sess-print_error'd semantic_errors from subsidiary call sites. They + are unaware of whether the probe in question is being resolved within + an optional (? or !) context. Instead of this, they now simply let + the exceptions propagate out to derive_probes() or similar, which does + know whether exceptions are errors in that context. That means + exceptions can propagate through elfutils iteration machinery too, + perhaps risking C level memory leaks, but so be it. + + This fix goes well beyond statement probes per se, but hand-testing + and the testsuite appear not to show regressions related to this. + + Added semok/badstmt.exp to test. + +diff --git a/tapsets.cxx b/tapsets.cxx +index 859160bc5..7b7107371 100644 +--- a/tapsets.cxx ++++ b/tapsets.cxx +@@ -2453,8 +2453,9 @@ query_cu (Dwarf_Die * cudie, dwarf_query * q) + } + catch (const semantic_error& e) + { +- q->sess.print_error (e); +- return DWARF_CB_ABORT; ++ // q->sess.print_error (e); ++ throw; ++ // return DWARF_CB_ABORT; + } + } + +@@ -2696,8 +2697,9 @@ query_module (Dwfl_Module *mod, + } + catch (const semantic_error& e) + { +- q->sess.print_error (e); +- return DWARF_CB_ABORT; ++ // q->sess.print_error (e); ++ // return DWARF_CB_ABORT; ++ throw; + } + } + +diff --git a/testsuite/semok/stmtbad.stp b/testsuite/semok/stmtbad.stp +new file mode 100755 +index 000000000..06780790a +--- /dev/null ++++ b/testsuite/semok/stmtbad.stp +@@ -0,0 +1,7 @@ ++#! /bin/sh ++ ++exec stap -v -p2 -e 'probe oneshot {log("nothing") } ++ probe process.statement("main@*:1")? { log("yo") }' -c stap ++ ++# The optional misaddressed statement probe should let stap still ++# succeed with the oneshot probe. diff --git a/SPECS/systemtap.spec b/SPECS/systemtap.spec index 2ab4c52..bcf6752 100644 --- a/SPECS/systemtap.spec +++ b/SPECS/systemtap.spec @@ -121,8 +121,9 @@ m stapdev stapdev Name: systemtap -Version: 4.8 -Release: 2%{?release_override}%{?dist} +# PRERELEASE +Version: 4.9 +Release: 3%{?release_override}%{?dist} # for version, see also configure.ac @@ -141,6 +142,7 @@ Release: 2%{?release_override}%{?dist} # systemtap-runtime-virtguest udev rules, init scripts/systemd service, req:-runtime # systemtap-runtime-python2 HelperSDT python2 module, req:-runtime # systemtap-runtime-python3 HelperSDT python3 module, req:-runtime +# systemtap-jupyter /usr/bin/stap-jupyter-* interactive-notebook req:systemtap # # Typical scenarios: # @@ -158,12 +160,10 @@ License: GPLv2+ URL: http://sourceware.org/systemtap/ Source: ftp://sourceware.org/pub/systemtap/releases/systemtap-%{version}.tar.gz -Patch1: rhbz1997192.patch -Patch2: rhbz2145242.patch -Patch3: rhbz2149223.patch -Patch4: rhbz2149666.patch -Patch5: rhbz2154430.patch - +Patch1: rhbz2223733.patch +Patch2: rhbz2223735.patch +Patch3: pr29108.patch +Patch4: pr30749.patch # Build* @@ -578,16 +578,26 @@ This package installs the services necessary on a virtual machine for a systemtap-runtime-virthost machine to execute systemtap scripts. %endif +%if %{with_python3} && %{with_monitor} +%package jupyter +Summary: ISystemtap jupyter kernel and examples +License: GPLv2+ +URL: http://sourceware.org/systemtap/ +Requires: systemtap = %{version}-%{release} + +%description jupyter +This package includes files needed to build and run +the interactive systemtap Jupyter kernel, either locally +or within a container. +%endif # ------------------------------------------------------------------------ %prep %setup -q - -%patch1 -p1 -%patch2 -p1 -%patch3 -p1 -%patch4 -p1 -%patch5 -p1 +%patch -P1 -p1 +%patch -P2 -p1 +%patch -P3 -p1 +%patch -P4 -p1 %build @@ -1289,6 +1299,15 @@ exit 0 %{_sbindir}/stap-exporter %endif +%if %{with_python3} && %{with_monitor} +%files jupyter +%{_bindir}/stap-jupyter-container +%{_bindir}/stap-jupyter-install +%{_mandir}/man1/stap-jupyter.1* +%dir %{_datadir}/systemtap +%{_datadir}/systemtap/interactive-notebook +%endif + # ------------------------------------------------------------------------ # Future new-release entries should be of the form @@ -1298,6 +1317,18 @@ exit 0 # PRERELEASE %changelog +* Mon Aug 14 2023 Frank Ch. Eigler - 4.9-3 +- rhbz2231619 +- rhbz2095359 + +* Tue Jul 18 2023 Frank Ch. Eigler - 4.9-2 +- rhbz2223733 = rhbz2211288 +- rhbz2223735 = rhbz2223739 + +* Fri Apr 28 2023 Frank Ch. Eigler - 4.9-1 +- Upstream release, see wiki page below for detailed notes. + https://sourceware.org/systemtap/wiki/SystemTapReleases + * Fri Dec 23 2022 Frank Ch. Eigler - 4.8-2 - rhbz2156092 = rhbz1997192 - rhbz2145241 = rhbz2145242