elf_zstd_decompress only handles a single frame, while lld produces mulitple 1MB zstd frames. libbacktrace reads the first frame's content_size (0x100000 = 1 MB), compares it to the total expected output, finds they don't match, and silently fails — returning success but with no decompressed data. The DWARF parser then sees empty sections and produces no file/line info.
Patch by our AI agent that worked for us: refactors elf_zstd_decompress into two functions: elf_zstd_decompress_one which handles a single frame, and a wrapper elf_zstd_decompress that loops over all frames in the stream. The key changes are relaxing the content_size check from != sout to > sout, adjusting poutend per-frame, and advancing input/output pointers between frames:
diff --git a/elf.c b/elf.c
index 0040479..da416ca 100644
--- a/elf.c
+++ b/elf.c
@@ -4332,13 +4332,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.
- Return 1 on success, 0 on error. */
+/* Decompress a single zstd frame from PIN/SIN to POUT/SOUT.
+ Set *PPIN to the position after the consumed input.
+ Set *PFRAME_SIZE to the number of decompressed bytes written.
+ SOUT is the remaining output buffer size.
+ Return 1 on success, 0 on error. Code based on RFC 8878. */
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_one (const unsigned char *pin, size_t sin,
+ unsigned char *zdebug_table, unsigned char *pout,
+ size_t sout, const unsigned char **ppin,
+ size_t *pframe_size)
{
const unsigned char *pinend;
unsigned char *poutstart;
@@ -4486,12 +4490,16 @@ 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;
}
+ /* Adjust sout and poutend to this frame's content_size. */
+ sout = (size_t) content_size;
+ poutend = pout + sout;
+
last_block = 0;
while (!last_block)
{
@@ -4990,7 +4998,47 @@ elf_zstd_decompress (const unsigned char *pin, size_t sin,
pin += 4;
}
- if (pin != pinend)
+ *ppin = pin;
+ *pframe_size = sout;
+
+ return 1;
+}
+
+/* Decompress a zstd stream, which may consist of multiple concatenated
+ frames. Linkers such as lld split large sections into multiple 1MB
+ frames. 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)
+{
+ size_t remaining;
+
+ remaining = sout;
+
+ while (sin > 0)
+ {
+ const unsigned char *next;
+ size_t frame_size;
+
+ if (!elf_zstd_decompress_one (pin, sin, zdebug_table, pout,
+ remaining, &next, &frame_size))
+ return 0;
+
+ if (next <= pin || (size_t) (next - pin) > sin)
+ {
+ elf_uncompress_failed ();
+ return 0;
+ }
+
+ sin -= (size_t) (next - pin);
+ pin = next;
+ pout += frame_size;
+ remaining -= frame_size;
+ }
+
+ if (remaining != 0)
{
elf_uncompress_failed ();
return 0;
elf_zstd_decompressonly handles a single frame, while lld produces mulitple 1MB zstd frames. libbacktrace reads the first frame's content_size (0x100000 = 1 MB), compares it to the total expected output, finds they don't match, and silently fails — returning success but with no decompressed data. The DWARF parser then sees empty sections and produces no file/line info.Patch by our AI agent that worked for us: refactors
elf_zstd_decompressinto two functions:elf_zstd_decompress_onewhich handles a single frame, and a wrapperelf_zstd_decompressthat loops over all frames in the stream. The key changes are relaxing the content_size check from != sout to > sout, adjustingpoutendper-frame, and advancing input/output pointers between frames: