Skip to content

No support for multiframe zstd compressed debug info #162

@ofats

Description

@ofats

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;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions