# debugedit.at: Tests for the debugedit tool
#
-# Copyright (C) 2019 Mark J. Wielaard <mark@klomp.org>
+# Copyright (C) 2019, 2024 Mark J. Wielaard <mark@klomp.org>
#
# 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
AT_TESTED([debugedit])
# Helper to create some test binaries.
-# Optional parameter can specify additional gcc parameters.
+# Optional fist parameter can specify additional gcc parameters.
+# e.g. -gdwarf-4 or -gdwarf-5 for explicit DWARF version.
+# -g3 is always given. Second parameter can be a compression flag
+# like $GZ_ZLIB_FLAG (if it exists), when not given $GZ_NONE_FLAG
+# is used.
m4_define([DEBUGEDIT_SETUP],[[
# Create some test binaries. Create and build them in different subdirs
# to make sure they produce different relative/absolute paths.
cp "${abs_srcdir}"/data/SOURCES/foobar.h subdir_headers
cp "${abs_srcdir}"/data/SOURCES/baz.c .
+# Check second param, if given use compression
+if test -z "$2"; then
+GZ_FLAG=$GZ_NONE_FLAG
+else
+GZ_FLAG=$2
+fi
+
# First three object files (foo.o subdir_bar/bar.o and baz.o)
-$CC $CFLAGS -g3 $GZ_NONE_FLAG -Isubdir_headers $1 -c subdir_foo/foo.c
+$CC $CFLAGS -g3 $GZ_FLAG -Isubdir_headers $1 -c subdir_foo/foo.c
cd subdir_bar
-$CC $CFLAGS -g3 $GZ_NONE_FLAG -I../subdir_headers $1 -c bar.c
+$CC $CFLAGS -g3 $GZ_FLAG -I../subdir_headers $1 -c bar.c
cd ..
-$CC $CFLAGS -g3 $GZ_NONE_FLAG -I$(pwd)/subdir_headers $1 -c $(pwd)/baz.c
+$CC $CFLAGS -g3 $GZ_FLAG -I$(pwd)/subdir_headers $1 -c $(pwd)/baz.c
# Then a partially linked object file (somewhat like a kernel module).
# This will still have relocations between the debug sections.
# Create an executable. Relocations between debug sections will
# have been resolved.
-$CC $CFLAGS -g3 $GZ_NONE_FLAG -o foobarbaz.exe foo.o subdir_bar/bar.o baz.o
+$CC $CFLAGS -g3 $GZ_FLAG $1 -o foobarbaz.exe foo.o subdir_bar/bar.o baz.o
]])
# ===
AT_CLEANUP
+AT_SETUP([debugedit executable (compressed)])
+AT_KEYWORDS([debuginfo] [debugedit])
+AT_SKIP_IF([test -z "$GZ_ZLIB_FLAG"])
+DEBUGEDIT_SETUP([], [$GZ_ZLIB_FLAG])
+
+AT_CHECK([[./foobarbaz.exe]])
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
+AT_CHECK([[./foobarbaz.exe]])
+
+AT_CLEANUP
+
# ===
# debugedit should at least replace the .debug_str directory paths
# in the objects.
# Capture strings that start with the testdir (pwd) directory path
# (and replace that textually with /foo/bar/baz)
-($READELF -p.debug_str foo.o; \
- $READELF -p.debug_str subdir_bar/bar.o; \
- $READELF -p.debug_str baz.o) \
+($READELF -zp.debug_str foo.o; \
+ $READELF -zp.debug_str subdir_bar/bar.o; \
+ $READELF -zp.debug_str baz.o) \
| cut -c13- \
| grep ^$(pwd) | sort \
| sed -e "s@$(pwd)@/foo/bar/baz@" > expout
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./subdir_bar/bar.o]])
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./baz.o]])
AT_CHECK([[
-($READELF -p.debug_str foo.o; \
- $READELF -p.debug_str subdir_bar/bar.o; \
- $READELF -p.debug_str baz.o) \
+($READELF -zp.debug_str foo.o; \
+ $READELF -zp.debug_str subdir_bar/bar.o; \
+ $READELF -zp.debug_str baz.o) \
| cut -c13- \
| grep ^/foo/bar/baz | sort
]],[0],[expout])
# Capture strings that start with the testdir (pwd) directory path
# (and replace that textually with /foo/bar/baz)
-($READELF -p.debug_str -p.debug_line_str foo.o; \
- $READELF -p.debug_str -p.debug_line_str subdir_bar/bar.o; \
- $READELF -p.debug_str -p.debug_line_str baz.o) \
+($READELF -zp.debug_str -zp.debug_line_str foo.o; \
+ $READELF -zp.debug_str -zp.debug_line_str subdir_bar/bar.o; \
+ $READELF -zp.debug_str -zp.debug_line_str baz.o) \
| cut -c13- \
| grep ^$(pwd) | sort | uniq \
| sed -e "s@$(pwd)@/foo/bar/baz@" > expout
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./subdir_bar/bar.o]])
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./baz.o]])
AT_CHECK([[
-($READELF -p.debug_str -p.debug_line_str foo.o; \
- $READELF -p.debug_str -p.debug_line_str subdir_bar/bar.o; \
- $READELF -p.debug_str -p.debug_line_str baz.o) \
+($READELF -zp.debug_str -zp.debug_line_str foo.o; \
+ $READELF -zp.debug_str -zp.debug_line_str subdir_bar/bar.o; \
+ $READELF -zp.debug_str -zp.debug_line_str baz.o) \
| cut -c13- \
| grep ^/foo/bar/baz | sort | uniq
]],[0],[expout],[ignore])
# (and replace that textually with /foo/bar/baz)
# Note that partially linked files, might have multiple duplicate
# strings, but debugedit will merge them. So use sort -u.
-$READELF -p.debug_str ./foobarbaz.part.o | cut -c13- \
+$READELF -zp.debug_str ./foobarbaz.part.o | cut -c13- \
| grep ^$(pwd) | sort -u \
| sed -e "s@$(pwd)@/foo/bar/baz@" > expout
# Check the replaced strings are all there.
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.part.o]])
AT_CHECK([[
-$READELF -p.debug_str ./foobarbaz.part.o | cut -c13- \
+$READELF -zp.debug_str ./foobarbaz.part.o | cut -c13- \
| grep ^/foo/bar/baz | sort
]],[0],[expout])
# (and replace that textually with /foo/bar/baz)
# Note that partially linked files, might have multiple duplicate
# strings, but debugedit will merge them. So use sort -u.
-$READELF -p.debug_str -p.debug_line_str ./foobarbaz.part.o | cut -c13- \
+$READELF -zp.debug_str -zp.debug_line_str ./foobarbaz.part.o | cut -c13- \
| grep ^$(pwd) | sort -u | uniq \
| sed -e "s@$(pwd)@/foo/bar/baz@" > expout
# Check the replaced strings are all there.
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.part.o]])
AT_CHECK([[
-$READELF -p.debug_str -p.debug_line_str ./foobarbaz.part.o | cut -c13- \
+$READELF -zp.debug_str -zp.debug_line_str ./foobarbaz.part.o | cut -c13- \
| grep ^/foo/bar/baz | sort | uniq
]],[0],[expout],[ignore])
# Capture strings that start with the testdir (pwd) directory path
# (and replace that textually with /foo/bar/baz)
-$READELF -p.debug_str foobarbaz.exe | cut -c13- \
+$READELF -zp.debug_str foobarbaz.exe | cut -c13- \
| grep ^$(pwd) | sort \
| sed -e "s@$(pwd)@/foo/bar/baz@" > expout
# Check the replaced strings are all there.
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
AT_CHECK([[
-$READELF -p.debug_str foobarbaz.exe | cut -c13- \
+$READELF -zp.debug_str foobarbaz.exe | cut -c13- \
| grep ^/foo/bar/baz | sort
]],[0],[expout])
# Capture strings that start with the testdir (pwd) directory path
# (and replace that textually with /foo/bar/baz)
-$READELF -p.debug_str -p.debug_line_str foobarbaz.exe | cut -c13- \
+$READELF -zp.debug_str -zp.debug_line_str foobarbaz.exe | cut -c13- \
| grep ^$(pwd) | sort | uniq \
| sed -e "s@$(pwd)@/foo/bar/baz@" > expout
# Check the replaced strings are all there.
AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
AT_CHECK([[
-$READELF -p.debug_str -p.debug_line_str foobarbaz.exe | cut -c13- \
+$READELF -zp.debug_str -zp.debug_line_str foobarbaz.exe | cut -c13- \
| grep ^/foo/bar/baz | sort | uniq
]],[0],[expout],[ignore])
AT_CLEANUP
+AT_SETUP([debugedit .debug_info objects (compressed)])
+AT_KEYWORDS([debuginfo] [debugedit])
+AT_SKIP_IF([test -z "$GZ_ZLIB_FLAG"])
+DEBUGEDIT_SETUP([], [$GZ_ZLIB_FLAG])
+
+AT_DATA([expout],
+[/foo/bar/baz
+/foo/bar/baz/baz.c
+/foo/bar/baz/subdir_bar
+])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foo.o]])
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./subdir_bar/bar.o]])
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./baz.o]])
+AT_CHECK([[
+($READELF --debug-dump=info foo.o; \
+ $READELF --debug-dump=info subdir_bar/bar.o; \
+ $READELF --debug-dump=info baz.o) \
+ | grep -E 'DW_AT_(name|comp_dir)' \
+ | rev | cut -d: -f1 | rev | cut -c2- | grep ^/foo/bar/baz | sort -u
+]],[0],[expout])
+
+AT_CLEANUP
+
# ===
# Make sure DW_AT_name and DW_AT_comp_dir strings are replaced
# in partial linked object.
AT_CLEANUP
+AT_SETUP([debugedit .debug_info partial (compressed)])
+AT_KEYWORDS([debuginfo] [debugedit])
+AT_SKIP_IF([test -z "$GZ_ZLIB_FLAG"])
+DEBUGEDIT_SETUP([], [$GZ_ZLIB_FLAG])
+
+AT_DATA([expout],
+[/foo/bar/baz
+/foo/bar/baz/baz.c
+/foo/bar/baz/subdir_bar
+])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.part.o]])
+AT_CHECK([[
+$READELF --debug-dump=info ./foobarbaz.part.o \
+ | grep -E 'DW_AT_(name|comp_dir)' \
+ | rev | cut -d: -f1 | rev | cut -c2- | grep ^/foo/bar/baz | sort -u
+]],[0],[expout])
+
+AT_CLEANUP
+
# ===
# Make sure DW_AT_name and DW_AT_comp_dir strings are replaced
# in executable.
AT_CLEANUP
+AT_SETUP([debugedit .debug_info exe (compressed)])
+AT_KEYWORDS([debuginfo] [debugedit])
+AT_SKIP_IF([test -z "$GZ_ZLIB_FLAG"])
+DEBUGEDIT_SETUP([], [$GZ_ZLIB_FLAG])
+
+AT_DATA([expout],
+[/foo/bar/baz
+/foo/bar/baz/baz.c
+/foo/bar/baz/subdir_bar
+])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
+AT_CHECK([[
+$READELF --debug-dump=info ./foobarbaz.exe | grep -E 'DW_AT_(name|comp_dir)' \
+ | rev | cut -d: -f1 | rev | cut -c2- | grep ^/foo/bar/baz | sort -u
+]],[0],[expout])
+
+AT_CLEANUP
+
# ===
# Make sure -fdebug-types-section has updated strings in objects.
# Currently only works with DWARF4
AT_CLEANUP
+AT_SETUP([debugedit .debug_macro exe (compressed)])
+AT_KEYWORDS([debuginfo] [debugedit])
+AT_SKIP_IF([test -z "$GZ_ZLIB_FLAG"])
+DEBUGEDIT_SETUP([$DEBUG_MACRO_FLAG], [$GZ_ZLIB_FLAG])
+
+# We expect 3 for each compile unit.
+AT_DATA([expout],
+[NUMBER 42
+NUMBER 42
+NUMBER 42
+])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
+AT_CHECK([[
+$READELF --debug-dump=macro ./foobarbaz.exe \
+ | grep NUMBER | rev | cut -d: -f1 | rev | cut -c2-
+]],[0],[expout])
+
+AT_CLEANUP
+
# ===
# source list mode dwarf-4
# ===
AT_CHECK([[grep -q main.c sources.list]])
AT_CLEANUP
+# ===
+# source list with compression dwarf-4
+# ===
+AT_SETUP([debugedit --list-file compressed DWARF4])
+AT_KEYWORDS([debuginfo] [debugedit])
+echo "int main () { }" > main.c
+$CC $CFLAGS $GZ_ZLIB_FLAG -gdwarf-4 -o main main.c
+AT_CHECK([[debugedit -l sources.list main]])
+AT_CHECK([[grep -q main.c sources.list]])
+AT_CLEANUP
+
+# ===
+# source list with compression dwarf-5
+# ===
+AT_SETUP([debugedit --list-file compressed DWARF5])
+AT_KEYWORDS([debuginfo] [debugedit])
+AT_SKIP_IF([test "$GDWARF_5_FLAG" = "no"])
+echo "int main () { }" > main.c
+$CC $CFLAGS $GZ_ZLIB_FLAG -gdwarf-5 -o main main.c
+AT_CHECK([[debugedit -l sources.list main]])
+AT_CHECK([[grep -q main.c sources.list]])
+AT_CLEANUP
# ===
# build-id recomputation
#include <gelf.h>
#include <dwarf.h>
+#include <libelf.h>
#ifndef MAX
#define MAX(m, n) ((m) < (n) ? (n) : (m))
DW_AT_stmt_list attributes indexes in the debug_info. */
static bool need_stmt_update = false;
+/* If we recompress any debug section we need to write out the ELF
+ again. */
+static bool recompressed = false;
+
/* Storage for dynamically allocated strings to put into string
table. Keep together in memory blocks of 16K. */
#define STRMEMSIZE (16 * 1024)
REL *relbuf;
REL *relend;
bool rel_updated;
+ uint32_t ch_type;
/* Only happens for COMDAT .debug_macro and .debug_types. */
struct debug_section *next;
} debug_section;
edit_dwarf2_line (DSO *dso)
{
Elf_Data *linedata = debug_sections[DEBUG_LINE].elf_data;
- int linendx = debug_sections[DEBUG_LINE].sec;
- Elf_Scn *linescn = dso->scn[linendx];
unsigned char *old_buf = linedata->d_buf;
- /* Out with the old. */
- linedata->d_size = 0;
+ /* A nicer way to do this would be to set the original d_size to
+ zero and add a new Elf_Data section to contain the new data.
+ Out with the old. In with the new.
- /* In with the new. */
+ int linendx = debug_sections[DEBUG_LINE].sec;
+ Elf_Scn *linescn = dso->scn[linendx];
+ linedata->d_size = 0;
linedata = elf_newdata (linescn);
+ But when we then (recompress) the section there is a bug in
+ elfutils < 0.192 that causes the compression to fail/create bad
+ compressed data. So we just reuse the existing linedata (possibly
+ loosing track of the original d_buf, which will be overwritten). */
+
dso->lines.line_buf = malloc (dso->lines.debug_lines_len);
if (dso->lines.line_buf == NULL)
error (1, ENOMEM, "No memory for new .debug_line table (0x%zx bytes)",
memcpy (ptr, optr, remaining);
ptr += remaining;
}
+ elf_flagdata (linedata, ELF_C_SET, ELF_F_DIRTY);
}
/* Record or adjust (according to phase) DW_FORM_strp or DW_FORM_line_strp.
{
Strtab *strtab = strings->str_tab;
Elf_Data *strdata = secp->elf_data;
+
+ /* A nicer way to do this would be to set the original d_size to
+ zero and add a new Elf_Data section to contain the new data.
+ Out with the old. In with the new.
+
int strndx = secp->sec;
Elf_Scn *strscn = dso->scn[strndx];
-
- /* Out with the old. */
strdata->d_size = 0;
- /* In with the new. */
strdata = elf_newdata (strscn);
+ But when we then (recompress) the section there is a bug in
+ elfutils < 0.192 that causes the compression to fail/create bad
+ compressed data. So we just reuse the existing strdata (possibly
+ loosing track of the original d_buf, which will be overwritten). */
+
/* We really should check whether we had enough memory,
but the old ebl version will just abort on out of
memory... */
strtab_finalize (strtab, strdata);
secp->size = strdata->d_size;
strings->str_buf = strdata->d_buf;
+ elf_flagdata (strdata, ELF_C_SET, ELF_F_DIRTY);
}
/* Rebuild .debug_str_offsets. */
}
scn = dso->scn[i];
+
+ /* Check for compressed DWARF headers. Records
+ ch_type so we can recompress headers after we
+ processed the data. */
+ if (dso->shdr[i].sh_flags & SHF_COMPRESSED)
+ {
+ GElf_Chdr chdr;
+ if (gelf_getchdr(dso->scn[i], &chdr) == NULL)
+ error (1, 0, "Couldn't get compressed header: %s",
+ elf_errmsg (-1));
+ debug_sec->ch_type = chdr.ch_type;
+ if (elf_compress (scn, 0, 0) < 0)
+ error (1, 0, "Failed decompression");
+ gelf_getshdr (scn, &dso->shdr[i]);
+ }
+
data = elf_getdata (scn, NULL);
assert (data != NULL && data->d_buf != NULL);
assert (elf_getdata (scn, data) == NULL);
}
}
+ /* Recompress any debug sections that might have been uncompressed. */
+ if (dirty_elf)
+ for (int s = 0; debug_sections[s].name; s++)
+ {
+ for (struct debug_section *secp = &debug_sections[s]; secp != NULL;
+ secp = secp->next)
+ {
+ if (secp->ch_type != 0)
+ {
+ int sec = secp->sec;
+ Elf_Scn *scn = dso->scn[sec];
+ GElf_Shdr shdr = dso->shdr[sec];
+ Elf_Data *data;
+ data = elf_getdata (scn, NULL);
+ if (elf_compress (scn, secp->ch_type, 0) < 0)
+ error (1, 0, "Failed recompression");
+ gelf_getshdr (scn, &shdr);
+ dso->shdr[secp->sec] = shdr;
+ data = elf_getdata (scn, NULL);
+ secp->elf_data = data;
+ secp->data = data->d_buf;
+ secp->size = data->d_size;
+ elf_flagshdr (scn, ELF_C_SET, ELF_F_DIRTY);
+ elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
+ recompressed = 1;
+ }
+ }
+ }
+
/* Normally we only need to explicitly update the section headers
and data when any section data changed size. But because of a bug
in elfutils before 0.169 we will have to update and write out all
set). https://sourceware.org/bugzilla/show_bug.cgi?id=21199 */
bool need_update = (need_strp_update
|| need_line_strp_update
- || need_stmt_update);
+ || need_stmt_update
+ || recompressed);
#if !_ELFUTILS_PREREQ (0, 169)
/* string replacements or build_id updates don't change section size. */
|| need_line_strp_update
|| need_stmt_update
|| dirty_elf
- || (build_id && !no_recompute_build_id))
+ || (build_id && !no_recompute_build_id)
+ || recompressed)
&& elf_update (dso->elf, ELF_C_WRITE) < 0)
{
error (1, 0, "Failed to write file: %s", elf_errmsg (elf_errno()));