From 5745b489a2d726302eeaeff206f5b4d749f00932 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Thu, 25 Jun 2026 22:09:45 +0300 Subject: [PATCH 1/4] callstack: Calculate end address from highest LOAD header Headers aren't guaranteed to be sorted in final address order. For example, from readelf -lW of a .so built with clang: Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R 0x8 LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x5e8274 0x5e8274 R 0x1000 LOAD 0x5e8280 0x00000000005e9280 0x00000000005e9280 0x51e990 0x51e990 R E 0x1000 LOAD 0xb06c10 0x0000000000b08c10 0x0000000000b08c10 0x031628 0x0323f0 RW 0x1000 LOAD 0xb38240 0x0000000000b3b240 0x0000000000b3b240 0x009aa8 0x011c40 RW 0x1000 DYNAMIC 0xb32270 0x0000000000b34270 0x0000000000b34270 0x000410 0x000410 RW 0x8 GNU_RELRO 0xb06c10 0x0000000000b08c10 0x0000000000b08c10 0x031628 0x0323f0 R 0x1 GNU_EH_FRAME 0x4e8360 0x00000000004e8360 0x00000000004e8360 0x029cf4 0x029cf4 R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0 NOTE 0x000270 0x0000000000000270 0x0000000000000270 0x000024 0x000024 R 0x4 --- public/client/TracyCallstack.cpp | 15 ++++++++++++--- public/libbacktrace/elf.cpp | 14 +++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/public/client/TracyCallstack.cpp b/public/client/TracyCallstack.cpp index 9095d847..59c4182f 100644 --- a/public/client/TracyCallstack.cpp +++ b/public/client/TracyCallstack.cpp @@ -237,9 +237,18 @@ private: if( cache->ContainsImage( startAddress ) ) return 0; const uint32_t headerCount = info->dlpi_phnum; - assert( headerCount > 0); - const auto endAddress = static_cast( info->dlpi_addr + - info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz); + assert( headerCount > 0 ); + + // headers aren't guaranteed to be in address order; find the max + uint64_t endAddress = startAddress; + for( uint32_t i=0; idlpi_phdr[i]; + if( phdr.p_type != PT_LOAD ) continue; + + const auto phdrEnd = static_cast( info->dlpi_addr + phdr.p_vaddr + phdr.p_memsz ); + endAddress = std::max( phdrEnd, endAddress ); + } ImageEntry image{}; image.m_startAddress = startAddress; diff --git a/public/libbacktrace/elf.cpp b/public/libbacktrace/elf.cpp index 5f45b4e6..6d54ecff 100644 --- a/public/libbacktrace/elf.cpp +++ b/public/libbacktrace/elf.cpp @@ -7410,9 +7410,17 @@ phdr_callback_mock (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, ptr->dlpi_addr = info->dlpi_addr; // calculate the end address as well, so we can quickly determine if a PC is within the range of this image - ptr->dlpi_end_addr = uintptr_t(info->dlpi_addr) + (info->dlpi_phnum ? uintptr_t( - info->dlpi_phdr[info->dlpi_phnum - 1].p_vaddr + - info->dlpi_phdr[info->dlpi_phnum - 1].p_memsz) : 0); + // headers aren't guaranteed to be in address order; find the max + ptr->dlpi_end_addr = uintptr_t(info->dlpi_addr); + for (uint32_t i = 0; i < info->dlpi_phnum; i++) + { + const auto &phdr = info->dlpi_phdr[i]; + if (phdr.p_type != PT_LOAD) + continue; + + const auto phdr_end = uintptr_t(info->dlpi_addr + phdr.p_vaddr + phdr.p_memsz); + ptr->dlpi_end_addr = std::max(phdr_end, ptr->dlpi_end_addr); + } return 0; } From 7fbe1d13456bef8bba9ebb0fbfbe4b1d7a646275 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Sun, 3 May 2026 17:58:32 -0700 Subject: [PATCH 2/4] libbacktrace: support multiple zstd frames Based on patch by GitHub user ofats. * elf.c (elf_zstd_decompress_frame): New static function, broken out of elf_zstd_decompress. (elf_zstd_decompress): Call elf_zstd_decompress_frame in a loop. * zstdtest.c (test_large): Compress the file in chunks. --- public/libbacktrace/elf.cpp | 59 ++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/public/libbacktrace/elf.cpp b/public/libbacktrace/elf.cpp index 6d54ecff..0a0773ff 100644 --- a/public/libbacktrace/elf.cpp +++ b/public/libbacktrace/elf.cpp @@ -4356,15 +4356,17 @@ elf_zstd_unpack_seq_decode (int mode, return 1; } -/* Decompress a zstd stream from PIN/SIN to POUT/SOUT. Code based on RFC 8878. +/* Decompress a single zstd frame from *PPIN, ending at PINEND, to *PPOUT/SOUT. Return 1 on success, 0 on error. */ static int -elf_zstd_decompress (const unsigned char *pin, size_t sin, - unsigned char *zdebug_table, unsigned char *pout, - size_t sout) +elf_zstd_decompress_frame (const unsigned char **ppin, + const unsigned char *pinend, + unsigned char *zdebug_table, unsigned char **ppout, + size_t sout) { - const unsigned char *pinend; + const unsigned char *pin; + unsigned char *pout; unsigned char *poutstart; unsigned char *poutend; struct elf_zstd_seq_decode literal_decode; @@ -4384,9 +4386,9 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, uint64_t content_size; int last_block; - pinend = pin + sin; + pin = *ppin; + pout = *ppout; poutstart = pout; - poutend = pout + sout; literal_decode.table = NULL; literal_decode.table_bits = -1; @@ -4412,7 +4414,7 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, repeated_offset2 = 4; repeated_offset3 = 8; - if (unlikely (sin < 4)) + if (unlikely (pinend - pin < 4)) { elf_uncompress_failed (); return 0; @@ -4510,12 +4512,14 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, } if (unlikely (content_size != (size_t) content_size - || (size_t) content_size != sout)) + || (size_t) content_size > sout)) { elf_uncompress_failed (); return 0; } + poutend = pout + content_size; + last_block = 0; while (!last_block) { @@ -5014,7 +5018,42 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin, pin += 4; } - if (pin != pinend) + *ppin = pin; + *ppout = pout; + + return 1; +} + +/* Decompress a zstd stream from PIN/SIN to POUT/SOUT. Code based on RFC 8878. + Return 1 on success, 0 on error. */ + +static int +elf_zstd_decompress (const unsigned char *pin, size_t sin, + unsigned char *zdebug_table, unsigned char *pout, + size_t sout) +{ + const unsigned char *pinend; + + pinend = pin + sin; + + while (sin > 0) + { + const unsigned char *pin_frame; + unsigned char *pout_frame; + + pin_frame = pin; + pout_frame = pout; + if (!elf_zstd_decompress_frame (&pin_frame, pinend, zdebug_table, + &pout_frame, sout)) + return 0; + + sin -= pin_frame - pin; + pin = pin_frame; + sout -= pout_frame - pout; + pout = pout_frame; + } + + if (sout > 0) { elf_uncompress_failed (); return 0; From 99c13e53bf1fea52f4154f5a7efe672bed7272ef Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 1 Jun 2026 21:34:27 -0700 Subject: [PATCH 3/4] libbacktrace: support compressed block with no sequences * elf.c (elf_zstd_decompress_frame): Support a compressed block with no sequences, only literals. Fixes https://github.com/ianlancetaylor/libbacktrace/issues/164 --- public/libbacktrace/elf.cpp | 113 +++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/public/libbacktrace/elf.cpp b/public/libbacktrace/elf.cpp index 0a0773ff..488ca5b5 100644 --- a/public/libbacktrace/elf.cpp +++ b/public/libbacktrace/elf.cpp @@ -4638,6 +4638,11 @@ elf_zstd_decompress_frame (const unsigned char **ppin, pin += 2; } + pback = NULL; + bits = 0; + literal_state = 0; + offset_state = 0; + match_state = 0; if (seq_count > 0) { int (*pfn)(const struct elf_zstd_fse_entry *, @@ -4677,28 +4682,28 @@ elf_zstd_decompress_frame (const unsigned char **ppin, match_fse_table, 9, pfn, &match_decode)) return 0; + + pback = pblockend - 1; + if (!elf_fetch_backward_init (&pback, pin, &val, &bits)) + return 0; + + bits -= literal_decode.table_bits; + literal_state = ((val >> bits) + & ((1U << literal_decode.table_bits) - 1)); + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= offset_decode.table_bits; + offset_state = ((val >> bits) + & ((1U << offset_decode.table_bits) - 1)); + + if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) + return 0; + bits -= match_decode.table_bits; + match_state = ((val >> bits) + & ((1U << match_decode.table_bits) - 1)); } - pback = pblockend - 1; - if (!elf_fetch_backward_init (&pback, pin, &val, &bits)) - return 0; - - bits -= literal_decode.table_bits; - literal_state = ((val >> bits) - & ((1U << literal_decode.table_bits) - 1)); - - if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) - return 0; - bits -= offset_decode.table_bits; - offset_state = ((val >> bits) - & ((1U << offset_decode.table_bits) - 1)); - - if (!elf_fetch_bits_backward (&pback, pin, &val, &bits)) - return 0; - bits -= match_decode.table_bits; - match_state = ((val >> bits) - & ((1U << match_decode.table_bits) - 1)); - seq = 0; while (1) { @@ -4719,6 +4724,40 @@ elf_zstd_decompress_frame (const unsigned char **ppin, uint32_t need; uint32_t add; + if (unlikely (seq >= seq_count)) + { + /* Copy remaining literals. */ + if (literal_count > 0 && plit != pout) + { + if (unlikely ((size_t)(poutend - pout) + < literal_count)) + { + elf_uncompress_failed (); + return 0; + } + + if ((size_t)(plit - pout) < literal_count) + { + uint32_t move; + + move = plit - pout; + while (literal_count > move) + { + memcpy (pout, plit, move); + pout += move; + plit += move; + literal_count -= move; + } + } + + memcpy (pout, plit, literal_count); + } + + pout += literal_count; + + break; + } + pt = &offset_decode.table[offset_state]; offset_basebits = pt->basebits; offset_baseline = pt->baseline; @@ -4956,40 +4995,6 @@ elf_zstd_decompress_frame (const unsigned char **ppin, } } } - - if (unlikely (seq >= seq_count)) - { - /* Copy remaining literals. */ - if (literal_count > 0 && plit != pout) - { - if (unlikely ((size_t)(poutend - pout) - < literal_count)) - { - elf_uncompress_failed (); - return 0; - } - - if ((size_t)(plit - pout) < literal_count) - { - uint32_t move; - - move = plit - pout; - while (literal_count > move) - { - memcpy (pout, plit, move); - pout += move; - plit += move; - literal_count -= move; - } - } - - memcpy (pout, plit, literal_count); - } - - pout += literal_count; - - break; - } } pin = pblockend; From bd2106db6886afbbaa7dd946877881b9966c76f5 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Fri, 26 Jun 2026 09:41:40 +0300 Subject: [PATCH 4/4] libbacktrace: support compressed block with window descriptor Some debuginfo on AerynOS has this format. --- public/libbacktrace/elf.cpp | 38 ++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/public/libbacktrace/elf.cpp b/public/libbacktrace/elf.cpp index 488ca5b5..924fd9f3 100644 --- a/public/libbacktrace/elf.cpp +++ b/public/libbacktrace/elf.cpp @@ -4382,6 +4382,7 @@ elf_zstd_decompress_frame (const unsigned char **ppin, uint32_t repeated_offset3; uint16_t *scratch; unsigned char hdr; + int single_segment; int has_checksum; uint64_t content_size; int last_block; @@ -4440,12 +4441,18 @@ elf_zstd_decompress_frame (const unsigned char **ppin, hdr = *pin++; - /* We expect a single frame. */ - if (unlikely ((hdr & (1 << 5)) == 0)) + single_segment = (hdr & (1 << 5)) != 0; + if (!single_segment) { - elf_uncompress_failed (); - return 0; + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + /* skip Window_Descriptor */ + pin++; } + /* Reserved bit must be zero. */ if (unlikely ((hdr & (1 << 3)) != 0)) { @@ -4462,13 +4469,22 @@ elf_zstd_decompress_frame (const unsigned char **ppin, switch (hdr >> 6) { case 0: - if (unlikely (pin >= pinend)) - { - elf_uncompress_failed (); - return 0; - } - content_size = (uint64_t) *pin++; - break; + if (single_segment) + { + if (unlikely (pin >= pinend)) + { + elf_uncompress_failed (); + return 0; + } + content_size = (uint64_t) *pin++; + break; + } + else + { + /* no Frame_Content_Size; use the remaining size as the upper bound */ + content_size = (uint64_t) sout; + break; + } case 1: if (unlikely (pin + 1 >= pinend)) {