From 0add2fc587ff07e39bc3a22187c2425a129c7d41 Mon Sep 17 00:00:00 2001 From: Mark Wielaard Date: Mon, 7 Apr 2025 16:27:11 +0200 Subject: [PATCH] debugedit: Handle unused .debug_str_offsets entries As a sanity check we would assert when rewriting a .debug_str index we hadn't seen while processing the DWARF. This relies on the DWARF producer/compiler to not emit strings that aren't used. So when processing/rewriting the .debug_str_offsets we assume all indexes point to .debugstr offsets we have seen before. This assumption doesn't seem to hold for clang++ at the moment. It does put strings in the .debug_str section referenced from the .debug_str_offsets section that aren't used anywhere else. So debugedit will fail with an assert when processing such files. We don't want to process the .debug_str_offsets section multiple time and possibly have to rewrite other str references in other sections. So instead when we encounter an unseen/unused string we replace it with a dummy string "". To help DWARF producers indentify these unused strings we keep a reference to the original string table and produce a warning explaining which .debug_str_offsets table at which index points to the unseen .debug_str offset. debugedit: Warning, .debug_str_offsets table at offset ce2d0 index [4213] .debug_str [48cad9] entry 'CrossThreadCopierBaseHelper' unused, replacing with '' * tools/debugedit.c (debugedit_stridxentry): New static struct stridxentry. (struct strings): Add new field orig_data. (create_dummy_debugedit_stridxentry): New function. (string_find_new_entry): Add boolean argument accept_missing. Return &debugedit_stridxentry if no existing string index could be found. (setup_strings): Fill in orig_data field. (update_strings): New function to set orig_data field. (orig_str): New function returns the original string at index. (edit_strp): Call string_find_entry with false. (update_str_offsets): Call string_find_entry with true, emit warning if entry returned is &debugedit_stridxentry. (edit_dwarf2): Call update_strings and create_dummy_debugedit_stridxentry. https://sourceware.org/bugzilla/show_bug.cgi?id=32845 Signed-off-by: Mark Wielaard --- tools/debugedit.c | 89 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/tools/debugedit.c b/tools/debugedit.c index 43f9cee65cf8..fcf8f7d6564b 100644 --- a/tools/debugedit.c +++ b/tools/debugedit.c @@ -144,6 +144,15 @@ struct stridxentry Strent *entry; /* Entry in the new table. */ }; +/* Turns out we do need at least one replacement string when there are + strings that are never used in any of the debug sections, but that + turn up in the .debug_str_offsets. In that case (which really + shouldn't occur because it means the DWARF producer added unused + strings to the string table) we (warn and) replace the entry with + "". Call create_dummy_debugedit_stridxentry to add the + actual string. */ +static struct stridxentry debugedit_stridxentry = { 0, NULL }; + /* Storage for new string table entries. Keep together in memory to quickly search through them with tsearch. */ #define STRIDXENTRIES ((16 * 1024) / sizeof (struct stridxentry)) @@ -157,6 +166,7 @@ struct strentblock struct strings { Strtab *str_tab; /* The new string table. */ + Elf_Data orig_data; /* Original Elf_Data. */ char *str_buf; /* New Elf_Data d_buf. */ struct strmemblock *blocks; /* The first strmemblock. */ struct strmemblock *last_block; /* The currently used strmemblock. */ @@ -1119,6 +1129,32 @@ new_string_storage (struct strings *strings, size_t size) return &strings->last_block->memory[stridx]; } +static void +create_dummy_debugedit_stridxentry (DSO *dso) +{ + if (debugedit_stridxentry.entry != NULL) + { + fprintf (stderr, "debugedit: " + "create_dummy_debugedit_stridxentry called more than once."); + exit (-1); + } + + const char *dummy_name = ""; /* Kilroy was here */ + const size_t dummy_size = strlen (dummy_name) + 1; + + Strent *strent; + char* dummy_str = new_string_storage (&dso->debug_str, dummy_size); + if (dummy_str == NULL) + error (1, ENOMEM, "Couldn't allocate new string storage"); + memcpy (dummy_str, dummy_name, dummy_size); + strent = strtab_add_len (dso->debug_str.str_tab, dummy_str, dummy_size); + if (strent == NULL) + error (1, ENOMEM, "Could not create new string table entry"); + + debugedit_stridxentry.idx = (uint32_t) -1; + debugedit_stridxentry.entry = strent; +} + /* Comparison function used for tsearch. */ static int strent_compare (const void *a, const void *b) @@ -1189,12 +1225,14 @@ string_find_new_entry (struct strings *strings, size_t old_idx) } static struct stridxentry * -string_find_entry (struct strings *strings, size_t old_idx) +string_find_entry (struct strings *strings, size_t old_idx, bool accept_missing) { struct stridxentry **ret; struct stridxentry key; key.idx = old_idx; ret = tfind (&key, &strings->strent_root, strent_compare); + if (accept_missing && ret == NULL) + return &debugedit_stridxentry; assert (ret != NULL); /* Can only happen for a bad/non-existing old_idx. */ return *ret; } @@ -1291,6 +1329,8 @@ static void setup_strings (struct strings *strings) { strings->str_tab = strtab_init (false); + /* call update_strings to fill this in. */ + memset (&strings->orig_data, 0, sizeof (strings->orig_data)); strings->str_buf = NULL; strings->blocks = NULL; strings->last_block = NULL; @@ -1299,6 +1339,21 @@ setup_strings (struct strings *strings) strings->strent_root = NULL; } +static void +update_strings (struct strings *strings, struct debug_section *sec) +{ + if (sec->elf_data != NULL) + strings->orig_data = *sec->elf_data; +} + +static const char * +orig_str (struct strings *strings, size_t idx) +{ + if (idx < strings->orig_data.d_size) + return (const char *) strings->orig_data.d_buf + idx; + return ""; +} + /* Noop for tdestroy. */ static void free_node (void *p __attribute__((__unused__))) { } @@ -1707,7 +1762,7 @@ edit_strp (DSO *dso, uint32_t form, unsigned char *ptr, int phase, struct strings *strings = (form == DW_FORM_line_strp ? &dso->debug_line_str : &dso->debug_str); idx = do_read_32_relocated (ptr, sec); - entry = string_find_entry (strings, idx); + entry = string_find_entry (strings, idx, false); new_idx = strent_offset (entry->entry); do_write_32_relocated (ptr, new_idx); } @@ -2791,6 +2846,7 @@ update_str_offsets (DSO *dso) while (ptr < endp) { /* Read header, unit_length, version and padding. */ + unsigned char *index_start = ptr; if (endp - ptr < 3 * 4) break; uint32_t unit_length = read_32 (ptr); @@ -2803,13 +2859,21 @@ update_str_offsets (DSO *dso) uint32_t padding = read_16 (ptr); if (padding != 0) break; + unsigned char *offstart = ptr; while (ptr < endidxp) { struct stridxentry *entry; size_t idx, new_idx; idx = do_read_32_relocated (ptr, str_off_sec); - entry = string_find_entry (&dso->debug_str, idx); + entry = string_find_entry (&dso->debug_str, idx, true); + if (entry == &debugedit_stridxentry) + error (0, 0, "Warning, .debug_str_offsets table at offset %zx " + "index [%zd] .debug_str [%zx] entry '%s' unused, " + "replacing with ''\n", + (index_start - str_off_sec->data), + (ptr - offstart) / sizeof (uint32_t), idx, + orig_str (&dso->debug_str, idx)); new_idx = strent_offset (entry->entry); write_32_relocated (ptr, new_idx); } @@ -2963,6 +3027,9 @@ edit_dwarf2 (DSO *dso) } } + update_strings (&dso->debug_str, &debug_sections[DEBUG_STR]); + update_strings (&dso->debug_line_str, &debug_sections[DEBUG_LINE_STR]); + if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2LSB) { do_read_16 = buf_read_ule16; @@ -3211,7 +3278,8 @@ edit_dwarf2 (DSO *dso) struct stridxentry *entry; size_t idx, new_idx; idx = do_read_32_relocated (ptr, macro_sec); - entry = string_find_entry (&dso->debug_str, idx); + entry = string_find_entry (&dso->debug_str, idx, + false); new_idx = strent_offset (entry->entry); write_32_relocated (ptr, new_idx); } @@ -3265,8 +3333,17 @@ edit_dwarf2 (DSO *dso) Make sure everything is in place for phase 1 updating of debug_info references. */ if (phase == 0 && need_strp_update) - edit_dwarf2_any_str (dso, &dso->debug_str, - &debug_sections[DEBUG_STR]); + { + /* We might need a dummy .debug_str entry for + .debug_str_offsets entries of unused strings. We have to + add it unconditionally when there is a .debug_str_offsets + section because we don't know if there are any such + entries. */ + if (debug_sections[DEBUG_STR_OFFSETS].data != NULL) + create_dummy_debugedit_stridxentry (dso); + edit_dwarf2_any_str (dso, &dso->debug_str, + &debug_sections[DEBUG_STR]); + } if (phase == 0 && need_line_strp_update) edit_dwarf2_any_str (dso, &dso->debug_line_str, &debug_sections[DEBUG_LINE_STR]); -- 2.49.0