diff --git a/ci/Dockerfile b/ci/Dockerfile
index d8b0115bd..2e872be5f 100644
--- a/ci/Dockerfile
+++ b/ci/Dockerfile
@@ -45,6 +45,7 @@ RUN dnf -y install \
   pkgconfig \
   /usr/bin/gdb-add-index \
   dwz \
+  clang llvm \
   && dnf clean all
 
 COPY . .
diff --git a/tests/debugedit.at b/tests/debugedit.at
index bcd86ac67..5fa2bf6a7 100644
--- a/tests/debugedit.at
+++ b/tests/debugedit.at
@@ -22,8 +22,9 @@ AT_BANNER([RPM debugedit])
 AT_TESTED([debugedit])
 
 # Helper to create some test binaries.
-# Optional parameter can specify additional gcc parameters.
-m4_define([RPM_DEBUGEDIT_SETUP],[[
+# Optional first parameter can specify alternative compiler than [gcc].
+# Optional second parameter can specify additional compiler parameters.
+m4_define([RPM_DEBUGEDIT_SETUP],[
 # Create some test binaries. Create and build them in different subdirs
 # to make sure they produce different relative/absolute paths.
 
@@ -37,11 +38,11 @@ cp "${abs_srcdir}"/data/SOURCES/foobar.h subdir_headers
 cp "${abs_srcdir}"/data/SOURCES/baz.c .
 
 # First three object files (foo.o subdir_bar/bar.o and baz.o)
-gcc -g3 -Isubdir_headers $1 -c subdir_foo/foo.c
+m4_if($1,,gcc,$1) -g3 -Isubdir_headers -c $2 subdir_foo/foo.c
 cd subdir_bar
-gcc -g3 -I../subdir_headers $1 -c bar.c
+m4_if($1,,gcc,$1) -g3 -I../subdir_headers -c $2 bar.c
 cd ..
-gcc -g3 -I$(pwd)/subdir_headers $1 -c $(pwd)/baz.c
+m4_if($1,,gcc,$1) -g3 -I$(pwd)/subdir_headers -c $2 $(pwd)/baz.c
 
 # Then a partially linked object file (somewhat like a kernel module).
 # This will still have relocations between the debug sections.
@@ -49,8 +50,8 @@ ld -r -o foobarbaz.part.o foo.o subdir_bar/bar.o baz.o
 
 # Create an executable. Relocations between debug sections will
 # have been resolved.
-gcc -g3 -o foobarbaz.exe foo.o subdir_bar/bar.o baz.o
-]])
+m4_if($1,,gcc,$1) -g3 -o foobarbaz.exe foo.o subdir_bar/bar.o baz.o
+])
 
 # ===
 # Check debugedit --help doesn't crash and burn.
@@ -254,7 +255,7 @@ AT_CLEANUP
 # Make sure -fdebug-types-section has updated strings in objects.
 # ===
 m4_define([RPM_DEBUGEDIT_DEBUG_TYPES_OBJECTS],[
-RPM_DEBUGEDIT_SETUP($1)
+RPM_DEBUGEDIT_SETUP($1,$2)
 
 AT_DATA([expout],
 [st1
@@ -274,21 +275,28 @@ stz
 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([[
+AT_CHECK([
 for i in ./foo.o ./subdir_bar/bar.o ./baz.o;do \
+m4_if($1,gcc,[[ \
   readelf --debug-dump=info $i \
           | awk '/Abbrev Number:.*DW_TAG_type_unit/{p=1}{if(p)print}/^$/{p=0}' 
\
           | sed -n 's/^.*> *DW_AT_name *:.* \(stringp[^ ]*\|st.\)$/\1/p' \
           | sort;
+]],[[ \
+  llvm-dwarfdump $i \
+          | awk '/^0x[0-9a-f]*: [^ ]* Unit:/{p=0}/^0x[0-9a-f]*: Type 
Unit:/{p=1}{if(p)print}' \
+          | sed -n 's/^ *DW_AT_name\t("\(stringp[^ ]*\|st.\)")$/\1/p' \
+          | sort;
+]]) \
 done
-]],[0],[expout])
+],[0],[expout])
 ])
 
 # ===
 # Make sure -fdebug-types-section has updated strings in partial linked object.
 # ===
 m4_define([RPM_DEBUGEDIT_DEBUG_TYPES_PARTIAL],[
-RPM_DEBUGEDIT_SETUP($1)
+RPM_DEBUGEDIT_SETUP($1,$2)
 
 AT_DATA([expout],
 [st1
@@ -302,19 +310,26 @@ stz
 ])
 
 AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.part.o]])
-AT_CHECK([[
+AT_CHECK([
+m4_if($1,gcc,[[
 readelf --debug-dump=info ./foobarbaz.part.o \
         | awk '/Abbrev Number:.*DW_TAG_type_unit/{p=1}{if(p)print}/^$/{p=0}' \
         | sed -n 's/^.*> *DW_AT_name *:.* \(stringp[^ ]*\|st.\)$/\1/p' \
         | sort
-]],[0],[expout])
+]],[[
+  llvm-dwarfdump ./foobarbaz.part.o \
+        | awk '/^0x[0-9a-f]*: [^ ]* Unit:/{p=0}/^0x[0-9a-f]*: Type 
Unit:/{p=1}{if(p)print}' \
+        | sed -n 's/^ *DW_AT_name\t("\(stringp[^ ]*\|st.\)")$/\1/p' \
+        | sort
+]])
+],[0],[expout])
 ])
 
 # ===
 # Make sure -fdebug-types-section has updated strings in executable.
 # ===
 m4_define([RPM_DEBUGEDIT_DEBUG_TYPES_EXE],[
-RPM_DEBUGEDIT_SETUP($1)
+RPM_DEBUGEDIT_SETUP($1,$2)
 
 AT_DATA([expout],
 [st1
@@ -328,42 +343,67 @@ stz
 ])
 
 AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
-AT_CHECK([[
+AT_CHECK([
+m4_if($1,gcc,[[
 readelf --debug-dump=info ./foobarbaz.exe \
         | awk '/Abbrev Number:.*DW_TAG_type_unit/{p=1}{if(p)print}/^$/{p=0}' \
         | sed -n 's/^.*> *DW_AT_name *:.* \(stringp[^ ]*\|st.\)$/\1/p' \
         | sort
-]],[0],[expout])
+]],[[
+  llvm-dwarfdump ./foobarbaz.exe \
+        | awk '/^0x[0-9a-f]*: [^ ]* Unit:/{p=0}/^0x[0-9a-f]*: Type 
Unit:/{p=1}{if(p)print}' \
+        | sed -n 's/^ *DW_AT_name\t("\(stringp[^ ]*\|st.\)")$/\1/p' \
+        | sort
+]])
+],[0],[expout])
 ])
 
-AT_SETUP([debugedit DWARF-4 .debug_types objects])
+AT_SETUP([debugedit gcc DWARF-4 .debug_types objects])
+AT_KEYWORDS([debugtypes] [debugedit])
+RPM_DEBUGEDIT_DEBUG_TYPES_OBJECTS([gcc],[-gdwarf-4 -fdebug-types-section])
+AT_CLEANUP
+
+AT_SETUP([debugedit gcc DWARF-4 .debug_types partial])
 AT_KEYWORDS([debugtypes] [debugedit])
-RPM_DEBUGEDIT_DEBUG_TYPES_OBJECTS([-gdwarf-4 -fdebug-types-section])
+RPM_DEBUGEDIT_DEBUG_TYPES_PARTIAL([gcc],[-gdwarf-4 -fdebug-types-section])
 AT_CLEANUP
 
-AT_SETUP([debugedit DWARF-4 .debug_types partial])
+AT_SETUP([debugedit gcc DWARF-4 .debug_types exe])
 AT_KEYWORDS([debugtypes] [debugedit])
-RPM_DEBUGEDIT_DEBUG_TYPES_PARTIAL([-gdwarf-4 -fdebug-types-section])
+RPM_DEBUGEDIT_DEBUG_TYPES_EXE([gcc],[-gdwarf-4 -fdebug-types-section])
 AT_CLEANUP
 
-AT_SETUP([debugedit DWARF-4 .debug_types exe])
+AT_SETUP([debugedit gcc DWARF-5 .debug_types objects])
 AT_KEYWORDS([debugtypes] [debugedit])
-RPM_DEBUGEDIT_DEBUG_TYPES_EXE([-gdwarf-4 -fdebug-types-section])
+RPM_DEBUGEDIT_DEBUG_TYPES_OBJECTS([gcc],[-gdwarf-5 -fdebug-types-section])
 AT_CLEANUP
 
-AT_SETUP([debugedit DWARF-5 .debug_types objects])
+AT_SETUP([debugedit gcc DWARF-5 .debug_types partial])
 AT_KEYWORDS([debugtypes] [debugedit])
-RPM_DEBUGEDIT_DEBUG_TYPES_OBJECTS([-gdwarf-5 -fdebug-types-section])
+RPM_DEBUGEDIT_DEBUG_TYPES_PARTIAL([gcc],[-gdwarf-5 -fdebug-types-section])
 AT_CLEANUP
 
-AT_SETUP([debugedit DWARF-5 .debug_types partial])
+AT_SETUP([debugedit gcc DWARF-5 .debug_types exe])
 AT_KEYWORDS([debugtypes] [debugedit])
-RPM_DEBUGEDIT_DEBUG_TYPES_PARTIAL([-gdwarf-5 -fdebug-types-section])
+RPM_DEBUGEDIT_DEBUG_TYPES_EXE([gcc],[-gdwarf-5 -fdebug-types-section])
 AT_CLEANUP
 
-AT_SETUP([debugedit DWARF-5 .debug_types exe])
+AT_SETUP([debugedit clang DWARF-5 .debug_types objects])
 AT_KEYWORDS([debugtypes] [debugedit])
-RPM_DEBUGEDIT_DEBUG_TYPES_EXE([-gdwarf-5 -fdebug-types-section])
+# -x c++ as clang does ignores -fdebug-types-section for C.
+RPM_DEBUGEDIT_DEBUG_TYPES_OBJECTS([clang],[-gdwarf-5 -fdebug-types-section -x 
c++])
+AT_CLEANUP
+
+AT_SETUP([debugedit clang DWARF-5 .debug_types partial])
+AT_KEYWORDS([debugtypes] [debugedit])
+# -x c++ as clang does ignores -fdebug-types-section for C.
+RPM_DEBUGEDIT_DEBUG_TYPES_PARTIAL([clang],[-gdwarf-5 -fdebug-types-section -x 
c++])
+AT_CLEANUP
+
+AT_SETUP([debugedit clang DWARF-5 .debug_types exe])
+AT_KEYWORDS([debugtypes] [debugedit])
+# -x c++ as clang does ignores -fdebug-types-section for C.
+RPM_DEBUGEDIT_DEBUG_TYPES_EXE([clang],[-gdwarf-5 -fdebug-types-section -x c++])
 AT_CLEANUP
 
 # foo.o and bar.o are build with relative paths and so will use the
@@ -373,9 +413,9 @@ AT_CLEANUP
 
 # ===
 # Make sure .debug_line Directory Table entries are replaced
-# in objects.
+# in objects for DWARF-4.
 # ===
-AT_SETUP([debugedit .debug_line objects])
+AT_SETUP([debugedit gcc DWARF-4 .debug_line objects])
 AT_KEYWORDS([debuginfo] [debugedit])
 RPM_DEBUGEDIT_SETUP
 
@@ -397,9 +437,9 @@ AT_CLEANUP
 
 # ===
 # Make sure .debug_line Directory Table entries are replaced
-# in partial linked object.
+# in partial linked object for DWARF-4.
 # ===
-AT_SETUP([debugedit .debug_line partial])
+AT_SETUP([debugedit gcc DWARF-4 .debug_line partial])
 AT_KEYWORDS([debuginfo] [debugedit])
 RPM_DEBUGEDIT_SETUP
 
@@ -419,9 +459,9 @@ AT_CLEANUP
 
 # ===
 # Make sure .debug_line Directory Table entries are replaced
-# in executable.
+# in executable for DWARF-4.
 # ===
-AT_SETUP([debugedit .debug_line exe])
+AT_SETUP([debugedit gcc DWARF-4 .debug_line exe])
 AT_KEYWORDS([debuginfo] [debugedit])
 RPM_DEBUGEDIT_SETUP
 
@@ -439,6 +479,107 @@ readelf --debug-dump=line ./foobarbaz.exe \
 
 AT_CLEANUP
 
+# ===
+# Make sure .debug_line Directory Table entries are replaced
+# in objects for DWARF-5.
+# ===
+AT_SETUP([debugedit clang DWARF-5 .debug_line objects])
+AT_KEYWORDS([debuginfo] [debugedit])
+RPM_DEBUGEDIT_SETUP([clang],[-gdwarf-5])
+
+AT_DATA([expout],
+[../subdir_headers
+/foo/bar/baz
+/foo/bar/baz
+/foo/bar/baz/baz.c
+/foo/bar/baz/subdir_bar
+bar.c
+baz.c
+foobar.h
+foobar.h
+foobar.h
+subdir_foo/foo.c
+subdir_headers
+subdir_headers
+])
+
+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=line foo.o subdir_bar/bar.o baz.o \
+        | sed -n '/^ The Directory Table/,/^ Line Number Statements:/p' | grep 
"^  [0-9]" \
+       | sed 's/^.* //' | sort
+]],[0],[expout])
+
+AT_CLEANUP
+
+# ===
+# Make sure .debug_line Directory Table entries are replaced
+# in partial linked object for DWARF-5.
+# ===
+AT_SETUP([debugedit clang DWARF-5 .debug_line partial])
+AT_KEYWORDS([debuginfo] [debugedit])
+RPM_DEBUGEDIT_SETUP([clang],[-gdwarf-5])
+
+AT_DATA([expout],
+[../subdir_headers
+/foo/bar/baz
+/foo/bar/baz
+/foo/bar/baz/baz.c
+/foo/bar/baz/subdir_bar
+bar.c
+baz.c
+foobar.h
+foobar.h
+foobar.h
+subdir_foo/foo.c
+subdir_headers
+subdir_headers
+])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.part.o]])
+AT_CHECK([[
+readelf --debug-dump=line ./foobarbaz.part.o \
+        | sed -n '/^ The Directory Table/,/^ Line Number Statements:/p' | grep 
"^  [0-9]" \
+       | sed 's/^.* //' | sort
+]],[0],[expout])
+
+AT_CLEANUP
+
+# ===
+# Make sure .debug_line Directory Table entries are replaced
+# in executable for DWARF-5.
+# ===
+AT_SETUP([debugedit clang DWARF-5 .debug_line exe])
+AT_KEYWORDS([debuginfo] [debugedit])
+RPM_DEBUGEDIT_SETUP([clang],[-gdwarf-5])
+
+AT_DATA([expout],
+[../subdir_headers
+/foo/bar/baz
+/foo/bar/baz
+/foo/bar/baz/baz.c
+/foo/bar/baz/subdir_bar
+bar.c
+baz.c
+foobar.h
+foobar.h
+foobar.h
+subdir_foo/foo.c
+subdir_headers
+subdir_headers
+])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo/bar/baz ./foobarbaz.exe]])
+AT_CHECK([[
+readelf --debug-dump=line ./foobarbaz.exe \
+        | sed -n '/^ The Directory Table/,/^ Line Number Statements:/p' | grep 
"^  [0-9]" \
+       | sed 's/^.* //' | sort
+]],[0],[expout])
+
+AT_CLEANUP
+
 # ===
 # Make sure .debug_macro strings are still there
 # in objects.
diff --git a/tools/debugedit.c b/tools/debugedit.c
index 19f69e263..0d4b50df4 100644
--- a/tools/debugedit.c
+++ b/tools/debugedit.c
@@ -103,6 +103,8 @@ static bool need_string_replacement = false;
 /* Whether we need to do any updates of the string indexes (DW_FORM_strp)
    in debug_info for string indexes. */
 static bool need_strp_update = false;
+/* Likewise for DW_FORM_line_strp. */
+static bool need_line_strp_update = false;
 /* If the debug_line changes size we will need to update the
    DW_AT_stmt_list attributes indexes in the debug_info. */
 static bool need_stmt_update = false;
@@ -159,6 +161,7 @@ struct line_table
   ssize_t size_diff;  /* Difference in (header) size. */
   bool replace_dirs;  /* Whether to replace any dir paths.  */
   bool replace_files; /* Whether to replace any file paths. */
+  int phase_done;     /* Which phase is already processed, initially -1. */
 
   /* Header fields. */
   uint32_t unit_length;
@@ -192,7 +195,7 @@ typedef struct
   const char *filename;
   int lastscn;
   size_t phnum;
-  struct strings strings;
+  struct strings debug_str, debug_line_str;
   struct debug_lines lines;
   GElf_Shdr shdr[0];
 } DSO;
@@ -404,6 +407,9 @@ dwarf2_write_be32 (unsigned char *p, uint32_t v)
 
 static bool rel_updated;
 
+/* Whether .debug_line needs update of its relocations.  */
+static bool line_rel_updated;
+
 #define do_write_32_relocated(ptr,val) ({ \
   if (relptr && relptr < relend && relptr->ptr == ptr) \
     {                                                  \
@@ -454,6 +460,11 @@ static debug_section debug_sections[] =
 #define DEBUG_TYPES    11
 #define DEBUG_MACRO    12
 #define DEBUG_GDB_SCRIPT       13
+#define DEBUG_RNGLISTS 14
+#define DEBUG_LINE_STR 15
+#define DEBUG_ADDR     16
+#define DEBUG_STR_OFFSETS      17
+#define DEBUG_LOCLISTS 18
     { ".debug_info", NULL, NULL, 0, 0, 0 },
     { ".debug_abbrev", NULL, NULL, 0, 0, 0 },
     { ".debug_line", NULL, NULL, 0, 0, 0 },
@@ -468,6 +479,11 @@ static debug_section debug_sections[] =
     { ".debug_types", NULL, NULL, 0, 0, 0 },
     { ".debug_macro", NULL, NULL, 0, 0, 0 },
     { ".debug_gdb_scripts", NULL, NULL, 0, 0, 0 },
+    { ".debug_rnglists", NULL, NULL, 0, 0, 0 },
+    { ".debug_line_str", NULL, NULL, 0, 0, 0 },
+    { ".debug_addr", NULL, NULL, 0, 0, 0 },
+    { ".debug_str_offsets", NULL, NULL, 0, 0, 0 },
+    { ".debug_loclists", NULL, NULL, 0, 0, 0 },
     { NULL, NULL, NULL, 0, 0, 0 }
   };
 
@@ -544,9 +560,10 @@ setup_relbuf (DSO *dso, debug_section *sec, int *reltype)
       /* Relocations against section symbols are uninteresting in REL.  */
       if (dso->shdr[i].sh_type == SHT_REL && sym.st_value == 0)
        continue;
-      /* Only consider relocations against .debug_str, .debug_line
-        and .debug_abbrev.  */
+      /* Only consider relocations against .debug_str, .debug_line_str,
+        .debug_line and .debug_abbrev.  */
       if (sym.st_shndx != debug_sections[DEBUG_STR].sec
+         && sym.st_shndx != debug_sections[DEBUG_LINE_STR].sec
          && sym.st_shndx != debug_sections[DEBUG_LINE].sec
          && sym.st_shndx != debug_sections[DEBUG_ABBREV].sec)
        continue;
@@ -757,7 +774,9 @@ no_memory:
          form = read_uleb128 (ptr);
          if (form == 2
              || (form > DW_FORM_flag_present && form != DW_FORM_ref_sig8
-                 && form != DW_FORM_implicit_const))
+                 && form != DW_FORM_addrx && form != DW_FORM_implicit_const
+                 && form != DW_FORM_rnglistx
+                 && (form < DW_FORM_strx1 || form > DW_FORM_addrx4)))
            {
              error (0, 0, "%s: Unknown DWARF abbrev DW_FORM_0x%x",
                     dso->filename, form);
@@ -1031,17 +1050,22 @@ string_find_entry (struct strings *strings, size_t 
old_idx)
    a replacement file string has been recorded for it, otherwise
    returns false.  */
 static bool
-record_file_string_entry_idx (struct strings *strings, size_t old_idx)
+record_file_string_entry_idx (bool is_line_str, DSO *dso, size_t old_idx)
 {
+  struct strings *strings = is_line_str ? &dso->debug_line_str
+                                       : &dso->debug_str;
   bool ret = false;
   struct stridxentry *entry = string_find_new_entry (strings, old_idx);
   if (entry != NULL)
     {
-      if (old_idx >= debug_sections[DEBUG_STR].size)
+      debug_section *sec = &debug_sections[is_line_str ? DEBUG_LINE_STR
+                                                      : DEBUG_STR];
+
+      if (old_idx >= sec->size)
        error (1, 0, "Bad string pointer index %zd", old_idx);
 
       Strent *strent;
-      const char *old_str = (char *)debug_sections[DEBUG_STR].data + old_idx;
+      const char *old_str = (char *)sec->data + old_idx;
       const char *file = skip_dir_prefix (old_str, base_dir);
       if (file == NULL)
        {
@@ -1085,15 +1109,20 @@ record_file_string_entry_idx (struct strings *strings, 
size_t old_idx)
    base_dir with dest_dir, just records the existing string associated
    with the index. */
 static void
-record_existing_string_entry_idx (struct strings *strings, size_t old_idx)
+record_existing_string_entry_idx (bool is_line_str, DSO *dso, size_t old_idx)
 {
+  struct strings *strings = is_line_str ? &dso->debug_line_str
+                                       : &dso->debug_str;
   struct stridxentry *entry = string_find_new_entry (strings, old_idx);
   if (entry != NULL)
     {
-      if (old_idx >= debug_sections[DEBUG_STR].size)
+      debug_section *sec = &debug_sections[is_line_str ? DEBUG_LINE_STR
+                                                      : DEBUG_STR];
+
+      if (old_idx >= sec->size)
        error (1, 0, "Bad string pointer index %zd", old_idx);
 
-      const char *str = (char *)debug_sections[DEBUG_STR].data + old_idx;
+      const char *str = (char *)sec->data + old_idx;
       Strent *strent = strtab_add_len (strings->str_tab,
                                       str, strlen (str) + 1);
       if (strent == NULL)
@@ -1190,6 +1219,7 @@ get_line_table (DSO *dso, size_t off, struct line_table 
**table)
   t->size_diff = 0;
   t->replace_dirs = false;
   t->replace_files = false;
+  t->phase_done = -1;
 
   unsigned char *ptr = debug_sections[DEBUG_LINE].data;
   unsigned char *endsec = ptr + debug_sections[DEBUG_LINE].size;
@@ -1226,13 +1256,28 @@ get_line_table (DSO *dso, size_t off, struct line_table 
**table)
 
   /* version */
   t->version = read_16 (ptr);
-  if (t->version != 2 && t->version != 3 && t->version != 4)
+  if (t->version != 2 && t->version != 3 && t->version != 4 && t->version != 5)
     {
       error (0, 0, "%s: DWARF version %d unhandled", dso->filename,
             t->version);
       return false;
     }
 
+  if (t->version >= 5)
+    {
+      /* address_size */
+      assert (ptr_size != 0);
+      if (ptr_size != read_8 (ptr))
+       {
+         error (0, 0, "%s: .debug_line address size differs from .debug_info",
+                dso->filename);
+         return false;
+       }
+
+      /* segment_selector_size */
+      (void) read_8 (ptr);
+    }
+
   /* header_length */
   unsigned char *endprol = ptr + 4;
   t->header_length = read_32 (ptr);
@@ -1465,11 +1510,14 @@ edit_dwarf2_line (DSO *dso)
     }
 }
 
-/* Record or adjust (according to phase) DW_FORM_strp.  */
+/* Record or adjust (according to phase) DW_FORM_strp or DW_FORM_line_strp.  */
 static void
-edit_strp (DSO *dso, unsigned char *ptr, int phase, bool handled_strp)
+edit_strp (DSO *dso, bool is_line_str, unsigned char *ptr, int phase,
+          bool handled_strp)
 {
   unsigned char *ptr_orig = ptr;
+  struct strings *strings = is_line_str ? &dso->debug_line_str
+                                       : &dso->debug_str;
 
   /* In the first pass we collect all strings, in the
      second we put the new references back (if there are
@@ -1482,15 +1530,16 @@ edit_strp (DSO *dso, unsigned char *ptr, int phase, 
bool handled_strp)
       if (! handled_strp)
        {
          size_t idx = do_read_32_relocated (ptr);
-         record_existing_string_entry_idx (&dso->strings, idx);
+         record_existing_string_entry_idx (is_line_str, dso, idx);
        }
     }
-  else if (need_strp_update) /* && phase == 1 */
+  else if (is_line_str ? need_line_strp_update : need_strp_update)
+  /* && phase == 1 */
     {
       struct stridxentry *entry;
       size_t idx, new_idx;
       idx = do_read_32_relocated (ptr);
-      entry = string_find_entry (&dso->strings, idx);
+      entry = string_find_entry (strings, idx);
       new_idx = strent_offset (entry->entry);
       do_write_32_relocated (ptr, new_idx);
     }
@@ -1521,14 +1570,24 @@ skip_form (DSO *dso, uint32_t *formp, unsigned char 
**ptrp)
     case DW_FORM_ref1:
     case DW_FORM_flag:
     case DW_FORM_data1:
+    case DW_FORM_strx1:
+    case DW_FORM_addrx1:
       ++*ptrp;
       break;
     case DW_FORM_ref2:
     case DW_FORM_data2:
+    case DW_FORM_strx2:
+    case DW_FORM_addrx2:
       *ptrp += 2;
       break;
+    case DW_FORM_strx3:
+    case DW_FORM_addrx3:
+      *ptrp += 3;
+      break;
     case DW_FORM_ref4:
     case DW_FORM_data4:
+    case DW_FORM_strx4:
+    case DW_FORM_addrx4:
     case DW_FORM_sec_offset:
       *ptrp += 4;
       break;
@@ -1537,14 +1596,23 @@ skip_form (DSO *dso, uint32_t *formp, unsigned char 
**ptrp)
     case DW_FORM_ref_sig8:
       *ptrp += 8;
       break;
+    case DW_FORM_data16:
+      *ptrp += 16;
+      break;
     case DW_FORM_sdata:
     case DW_FORM_ref_udata:
     case DW_FORM_udata:
+    case DW_FORM_strx:
+    case DW_FORM_rnglistx:
+    case DW_FORM_addrx:
       read_uleb128 (*ptrp);
       break;
     case DW_FORM_strp:
       *ptrp += 4;
       break;
+    case DW_FORM_line_strp:
+      *ptrp += 4;
+      break;
     case DW_FORM_string:
       *ptrp = (unsigned char *) strchr ((char *)*ptrp, '\0') + 1;
       break;
@@ -1569,7 +1637,7 @@ skip_form (DSO *dso, uint32_t *formp, unsigned char 
**ptrp)
       assert (len < UINT_MAX);
       break;
     default:
-      error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, *formp);
+      error (0, 0, "%s: Unknown DWARF DW_FORM_0x%x", dso->filename, *formp);
       return FORM_ERROR;
     }
 
@@ -1723,12 +1791,119 @@ read_dwarf4_line (DSO *dso, unsigned char *ptr, char 
*comp_dir,
   return true;
 }
 
+/* Called by read_dwarf5_line first for directories and then file names as they
+   both have the same format.  */
+static bool
+read_dwarf5_line_entries (DSO *dso, unsigned char **ptrp,
+                         struct line_table *table, int phase,
+                         const char *entry_name)
+{
+  /* directory_entry_format_count */
+  /* file_name_entry_format_count */
+  unsigned format_count = read_8 (*ptrp);
+
+  unsigned char *formats = *ptrp;
+
+  /* directory_entry_format */
+  /* file_name_entry_format */
+  for (unsigned formati = 0; formati < format_count; ++formati)
+    {
+      read_uleb128 (*ptrp);
+      read_uleb128 (*ptrp);
+    }
+
+  /* directories_count */
+  /* file_names_count */
+  unsigned entry_count = read_uleb128 (*ptrp);
+
+  /* directories */
+  /* file_names */
+  for (unsigned entryi = 0; entryi < entry_count; ++entryi)
+    {
+      unsigned char *format_ptr = formats;
+      for (unsigned formati = 0; formati < format_count; ++formati)
+       {
+         unsigned lnct = read_uleb128 (format_ptr); 
+         unsigned form = read_uleb128 (format_ptr); 
+         bool handled_strp = false;
+         bool is_line_str = form == DW_FORM_line_strp;
+         if (lnct == DW_LNCT_path)
+           switch (form)
+             {
+             case DW_FORM_strp:
+             case DW_FORM_line_strp:
+               if (dest_dir && phase == 0)
+                 {
+                   size_t idx = do_read_32_relocated (*ptrp);
+                   if (record_file_string_entry_idx (is_line_str, dso, idx))
+                     *(is_line_str ? &need_line_strp_update
+                                   : &need_strp_update) = true;
+                   handled_strp = true;
+                 }
+               break;
+             default:
+               error (0, 0, "%s: Unsupported "
+                            ".debug_line %s %u DW_FORM_0x%x",
+                      dso->filename, entry_name, entryi, form);
+               return false;
+             }
+
+         switch (form)
+           {
+           case DW_FORM_strp:
+           case DW_FORM_line_strp:
+             edit_strp (dso, is_line_str, *ptrp, phase, handled_strp);
+             break;
+           }
+
+         switch (skip_form (dso, &form, ptrp))
+           {
+           case FORM_OK:
+             break;
+           case FORM_ERROR:
+             return false;
+           case FORM_INDIRECT:
+             error (0, 0, "%s: Unsupported "
+                          ".debug_line %s %u DW_FORM_indirect",
+                    dso->filename, entry_name, entryi);
+             return false;
+           }
+       }
+    }
+
+  return true;
+}
+
+/* Part of read_dwarf2_line processing DWARF-5.  */
+static bool
+read_dwarf5_line (DSO *dso, unsigned char *ptr, struct line_table *table,
+                 int phase)
+{
+  REL *relptr_saved = relptr, *relend_saved = relend;
+  int reltype_saved = reltype;
+  bool rel_updated_saved = rel_updated;
+  rel_updated = false;
+  setup_relbuf(dso, &debug_sections[DEBUG_LINE], &reltype);
+
+  bool retval = (read_dwarf5_line_entries (dso, &ptr, table, phase, 
"directory")
+                && read_dwarf5_line_entries (dso, &ptr, table, phase,
+                                             "file name"));
+
+  relptr = relptr_saved;
+  relend = relend_saved;
+  reltype = reltype_saved;
+  line_rel_updated = rel_updated;
+  rel_updated = rel_updated_saved;
+
+  return retval;
+}
+
 /* Called during phase zero for each debug_line table referenced from
    .debug_info.  Outputs all source files seen and records any
    adjustments needed in the debug_list data structures. Returns true
    if line_table needs to be rewrite either the dir or file paths. */
 static bool
-read_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir)
+read_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
 {
   unsigned char *ptr;
   struct line_table *table;
@@ -1742,6 +1917,9 @@ read_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir)
   ptr = debug_sections[DEBUG_LINE].data + off;
   ptr += (4 /* unit len */
          + 2 /* version */
+         + (table->version < 5 ? 0 : 0
+            + 1 /* address_size */
+            + 1 /* segment_selector*/)
          + 4 /* header len */
          + 1 /* min instr len */
          + (table->version >= 4) /* max op per instr, if version >= 4 */
@@ -1751,8 +1929,20 @@ read_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir)
          + 1 /* opcode base */
          + table->opcode_base - 1); /* opcode len table */
 
-  if (! read_dwarf4_line (dso, ptr, comp_dir, table))
-   return false;
+  if (phase <= table->phase_done)
+    return true;
+  table->phase_done = phase;
+
+  if (table->version >= 5)
+    {
+      if (! read_dwarf5_line (dso, ptr, table, phase))
+       return false;
+    }
+  else if (phase == 0)
+    {
+      if (! read_dwarf4_line (dso, ptr, comp_dir, table))
+       return false;
+    }
 
   dso->lines.debug_lines_len += 4 + table->unit_length + table->size_diff;
   need_stmt_update = table->replace_dirs || table->replace_files;
@@ -1772,20 +1962,24 @@ find_new_list_offs (struct debug_lines *lines, size_t 
idx)
   return table->new_idx;
 }
 
-/* Read DW_FORM_strp collecting compilation directory.  */
+/* Read DW_FORM_strp or DW_FORM_line_strp collecting compilation directory.  */
 static void
-edit_attributes_str_comp_dir (DSO *dso, unsigned char **ptrp, int phase,
-                             char **comp_dirp, bool *handled_strpp)
+edit_attributes_str_comp_dir (bool is_line_str, DSO *dso, unsigned char **ptrp,
+                             int phase, char **comp_dirp, bool *handled_strpp)
 {
+  debug_section *sec = &debug_sections[is_line_str ? DEBUG_LINE_STR
+                                                  : DEBUG_STR];
+  if (sec->data == NULL)
+    return;
   const char *dir;
   size_t idx = do_read_32_relocated (*ptrp);
   /* In phase zero we collect the comp_dir.  */
   if (phase == 0)
     {
-      if (idx >= debug_sections[DEBUG_STR].size)
+      if (idx >= sec->size)
        error (1, 0, "%s: Bad string pointer index %zd for comp_dir",
               dso->filename, idx);
-      dir = (char *) debug_sections[DEBUG_STR].data + idx;
+      dir = (char *) sec->data + idx;
 
       free (*comp_dirp);
       *comp_dirp = strdup (dir);
@@ -1793,8 +1987,8 @@ edit_attributes_str_comp_dir (DSO *dso, unsigned char 
**ptrp, int phase,
 
   if (dest_dir != NULL && phase == 0)
     {
-      if (record_file_string_entry_idx (&dso->strings, idx))
-       need_strp_update = true;
+      if (record_file_string_entry_idx (is_line_str, dso, idx))
+       *(is_line_str ? &need_line_strp_update : &need_strp_update) = true;
       *handled_strpp = true;
     }
 }
@@ -1834,9 +2028,8 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct 
abbrev_tag *t, int phase)
                  || form == DW_FORM_sec_offset)
                {
                  list_offs = do_read_32_relocated (ptr);
-                 if (phase == 0)
-                   found_list_offs = 1;
-                 else if (need_stmt_update) /* phase one */
+                 found_list_offs = 1;
+                 if (phase == 1 && need_stmt_update)
                    {
                      size_t idx, new_idx;
                      idx = do_read_32_relocated (ptr);
@@ -1902,62 +2095,82 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct 
abbrev_tag *t, int phase)
                        }
                    }
                }
-             else if (form == DW_FORM_strp &&
-                      debug_sections[DEBUG_STR].data)
-               edit_attributes_str_comp_dir (dso, &ptr, phase, &comp_dir,
+             else if (form == DW_FORM_strp)
+               edit_attributes_str_comp_dir (false /* is_line_str */, dso,
+                                             &ptr, phase, &comp_dir,
                                              &handled_strp);
+             else if (form == DW_FORM_line_strp)
+               edit_attributes_str_comp_dir (true /* is_line_str */, dso, &ptr,
+                                             phase, &comp_dir, &handled_strp);
            }
          else if ((t->tag == DW_TAG_compile_unit
                    || t->tag == DW_TAG_partial_unit)
-                  && t->attr[i].attr == DW_AT_name
-                  && form == DW_FORM_strp
-                  && debug_sections[DEBUG_STR].data)
+                  && t->attr[i].attr == DW_AT_name)
            {
-             /* DW_AT_name is the primary file for this compile
-                unit. If starting with / it is a full path name.
-                Note that we don't handle DW_FORM_string in this
-                case.  */
-             size_t idx = do_read_32_relocated (ptr);
-
-             /* In phase zero we will look for a comp_dir to use.  */
-             if (phase == 0)
+             if (form == DW_FORM_strp && debug_sections[DEBUG_STR].data)
                {
-                 if (idx >= debug_sections[DEBUG_STR].size)
-                   error (1, 0,
-                          "%s: Bad string pointer index %zd for unit name",
-                          dso->filename, idx);
-                 char *name = (char *) debug_sections[DEBUG_STR].data + idx;
-                 if (*name == '/' && comp_dir == NULL)
-                   {
-                     char *enddir = strrchr (name, '/');
+                 /* DW_AT_name is the primary file for this compile
+                    unit. If starting with / it is a full path name.
+                    Note that we don't handle DW_FORM_string in this
+                    case.  */
+                 size_t idx = do_read_32_relocated (ptr);
 
-                     if (enddir != name)
+                 /* In phase zero we will look for a comp_dir to use.  */
+                 if (phase == 0)
+                   {
+                     if (idx >= debug_sections[DEBUG_STR].size)
+                       error (1, 0,
+                              "%s: Bad string pointer index %zd for unit name",
+                              dso->filename, idx);
+                     char *name = ((char *) debug_sections[DEBUG_STR].data
+                                   + idx);
+                     if (*name == '/' && comp_dir == NULL)
                        {
-                         comp_dir = malloc (enddir - name + 1);
-                         memcpy (comp_dir, name, enddir - name);
-                         comp_dir [enddir - name] = '\0';
+                         char *enddir = strrchr (name, '/');
+
+                         if (enddir != name)
+                           {
+                             comp_dir = malloc (enddir - name + 1);
+                             memcpy (comp_dir, name, enddir - name);
+                             comp_dir [enddir - name] = '\0';
+                           }
+                         else
+                           comp_dir = strdup ("/");
                        }
-                     else
-                       comp_dir = strdup ("/");
                    }
-               }
 
-             /* First pass (0) records the new name to be
-                added to the debug string pool, the second
-                pass (1) stores it (the new index). */
-             if (dest_dir && phase == 0)
+                 /* First pass (0) records the new name to be
+                    added to the debug string pool, the second
+                    pass (1) stores it (the new index). */
+                 if (dest_dir && phase == 0)
+                   {
+                     if (record_file_string_entry_idx (false /* is_line_str */,
+                                                       dso, idx))
+                       need_strp_update = true;
+                     handled_strp = true;
+                   }
+               }
+             else if (form == DW_FORM_line_strp)
                {
-                 if (record_file_string_entry_idx (&dso->strings, idx))
-                   need_strp_update = true;
-                 handled_strp = true;
+                 error (0, 0, "%s: Unsupported "
+                              "%s DW_AT_name DW_FORM_line_strp",
+                        dso->filename,
+                        t->tag == DW_TAG_compile_unit ? "DW_TAG_compile_unit"
+                                                      : "DW_TAG_partial_unit");
+                 return NULL;
                }
            }
 
          switch (form)
            {
            case DW_FORM_strp:
-             edit_strp (dso, ptr, phase, handled_strp);
+             edit_strp (dso, false /* is_line_str */, ptr, phase,
+                        handled_strp);
              break;
+           case DW_FORM_line_strp:
+             edit_strp (dso, true /* is_line_str */, ptr, phase, handled_strp);
+             break;
+           /* DW_FORM_strx* will be registered from .debug_str_offsets.  */
            }
 
          switch (skip_form (dso, &form, &ptr))
@@ -2001,7 +2214,7 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct 
abbrev_tag *t, int phase)
      that).  Note that calculating the new size and offsets is done
      separately (at the end of phase zero after all CUs have been
      scanned in dwarf2_edit). */
-  if (found_list_offs && ! read_dwarf2_line (dso, list_offs, comp_dir))
+  if (found_list_offs && ! read_dwarf2_line (dso, list_offs, comp_dir, phase))
     {
       free (comp_dir);
       return NULL;
@@ -2026,6 +2239,73 @@ line_rel_cmp (const void *a, const void *b)
   return 0;
 }
 
+/* Update .debug_str_offsets.  */
+static bool
+edit_dwarf5_str_offsets (DSO *dso, int phase)
+{
+  if (!need_strp_update)
+    return true;
+
+  debug_section *sec = &debug_sections[DEBUG_STR_OFFSETS];
+  unsigned char *ptr = sec->data;
+  if (ptr == NULL)
+    return true;
+
+  setup_relbuf(dso, sec, &reltype);
+  unsigned char *endsec = ptr + sec->size;
+  while (ptr < endsec)
+    {
+      if (ptr + 4 + 2 + 2 > endsec)
+       {
+         error (0, 0, "%s: %s CU header too small",
+                dso->filename, sec->name);
+         return false;
+       }
+
+      unsigned char *endunit = ptr + 4;
+      endunit += read_32 (ptr);
+      if (endunit == ptr + 0xffffffff)
+       {
+         error (0, 0, "%s: %s: 64-bit DWARF not supported", dso->filename,
+                sec->name);
+         return false;
+       }
+
+      if ((endunit - ptr) % 4 != 0)
+       {
+         error (0, 0, "%s: %s: unit size %% 4 != 0", dso->filename, sec->name);
+         return false;
+       }
+
+      if (endunit > endsec)
+       {
+         error (0, 0, "%s: %s too small", dso->filename, sec->name);
+         return false;
+       }
+
+      int version = read_16 (ptr);
+      if (version != 5)
+       {
+         error (0, 0, "%s: %s: DWARF version %d unhandled", dso->filename,
+                sec->name, version);
+         return false;
+       }
+
+      int padding = read_16 (ptr);
+      if (padding != 0)
+       {
+         error (0, 0, "%s: %s: Padding is not zero", dso->filename, sec->name);
+         return false;
+       }
+
+      for (; ptr < endunit; ptr += 4)
+       edit_strp (dso, false /* is_line_str */, ptr, phase,
+                  false /* handled_strp - unused */);
+    }
+  dirty_section (DEBUG_STR_OFFSETS);
+  return true;
+}
+
 static int
 edit_info (DSO *dso, int phase, struct debug_section *sec, bool is_types)
 {
@@ -2175,13 +2455,13 @@ edit_info (DSO *dso, int phase, struct debug_section 
*sec, bool is_types)
   return 0;
 }
 
-/* Rebuild .debug_str.  */
+/* Rebuild .debug_str or .debug_line_str.  */
 static void
-edit_dwarf2_any_str (DSO *dso)
+edit_dwarf2_any_str (DSO *dso, struct strings *strings, debug_section *secp)
 {
-  Strtab *strtab = dso->strings.str_tab;
-  Elf_Data *strdata = debug_sections[DEBUG_STR].elf_data;
-  int strndx = debug_sections[DEBUG_STR].sec;
+  Strtab *strtab = strings->str_tab;
+  Elf_Data *strdata = secp->elf_data;
+  int strndx = secp->sec;
   Elf_Scn *strscn = dso->scn[strndx];
 
   /* Out with the old. */
@@ -2193,8 +2473,8 @@ edit_dwarf2_any_str (DSO *dso)
      but the old ebl version will just abort on out of
      memory... */
   strtab_finalize (strtab, strdata);
-  debug_sections[DEBUG_STR].size = strdata->d_size;
-  dso->strings.str_buf = strdata->d_buf;
+  secp->size = strdata->d_size;
+  strings->str_buf = strdata->d_buf;
 }
 
 static int
@@ -2342,12 +2622,14 @@ edit_dwarf2 (DSO *dso)
   bool info_rel_updated = false;
   bool types_rel_updated = false;
   bool macro_rel_updated = false;
+  line_rel_updated = false;
 
   for (phase = 0; phase < 2; phase++)
     {
       /* If we don't need to update anyhing, skip phase 1. */
       if (phase == 1
          && !need_strp_update
+         && !need_line_strp_update
          && !need_string_replacement
          && !need_stmt_update)
        break;
@@ -2570,15 +2852,15 @@ edit_dwarf2 (DSO *dso)
                      if (phase == 0)
                        {
                          size_t idx = read_32_relocated (ptr);
-                         record_existing_string_entry_idx (&dso->strings,
-                                                           idx);
+                         record_existing_string_entry_idx
+                                           (false /* is_line_str */, dso, idx);
                        }
                      else
                        {
                          struct stridxentry *entry;
                          size_t idx, new_idx;
                          idx = do_read_32_relocated (ptr);
-                         entry = string_find_entry (&dso->strings, idx);
+                         entry = string_find_entry (&dso->debug_str, idx);
                          new_idx = strent_offset (entry->entry);
                          write_32_relocated (ptr, new_idx);
                        }
@@ -2598,24 +2880,30 @@ edit_dwarf2 (DSO *dso)
            }
        }
 
+      if (! edit_dwarf5_str_offsets (dso, phase))
+       return 1;
+
       /* Same for the debug_str section. 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);
-
+       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]);
     }
 
   /* After phase 1 we might have rewritten the debug_info with
      new strp, strings and/or linep offsets.  */
-  if (need_strp_update || need_string_replacement || need_stmt_update) {
+  if (need_strp_update || need_line_strp_update || need_string_replacement
+      || need_stmt_update) {
     dirty_section (DEBUG_INFO);
     if (debug_sections[DEBUG_TYPES].data != NULL)
       dirty_section (DEBUG_TYPES);
   }
   if (need_strp_update || need_stmt_update)
     dirty_section (DEBUG_MACRO);
-  if (need_stmt_update)
+  if (need_stmt_update || need_line_strp_update)
     dirty_section (DEBUG_LINE);
 
   /* Update any relocations addends we might have touched. */
@@ -2648,6 +2936,9 @@ edit_dwarf2 (DSO *dso)
        }
     }
 
+  if (line_rel_updated)
+    update_rela_data (dso, &debug_sections[DEBUG_LINE]);
+
   return 0;
 }
 
@@ -2740,7 +3031,8 @@ fdopen_dso (int fd, const char *name)
     }
 
   dso->filename = (const char *) strdup (name);
-  setup_strings (&dso->strings);
+  setup_strings (&dso->debug_str);
+  setup_strings (&dso->debug_line_str);
   setup_lines (&dso->lines);
   return dso;
 
@@ -2748,7 +3040,8 @@ error_out:
   if (dso)
     {
       free ((char *) dso->filename);
-      destroy_strings (&dso->strings);
+      destroy_strings (&dso->debug_str);
+      destroy_strings (&dso->debug_line_str);
       destroy_lines (&dso->lines);
       free (dso);
     }
@@ -3032,7 +3325,8 @@ main (int argc, char *argv[])
      in elfutils before 0.169 we will have to update and write out all
      section data if any data has changed (when ELF_F_LAYOUT was
      set). https://sourceware.org/bugzilla/show_bug.cgi?id=21199 */
-  bool need_update = need_strp_update || need_stmt_update;
+  bool need_update = (need_strp_update || need_line_strp_update
+                     || need_stmt_update);
 
 #if !_ELFUTILS_PREREQ (0, 169)
   /* string replacements or build_id updates don't change section size. */
@@ -3110,6 +3404,8 @@ main (int argc, char *argv[])
                sec_size = debug_sections[DEBUG_STR].size;
              if (secnum == debug_sections[DEBUG_LINE].sec)
                sec_size = debug_sections[DEBUG_LINE].size;
+             if (secnum == debug_sections[DEBUG_LINE_STR].sec)
+               sec_size = debug_sections[DEBUG_LINE_STR].size;
 
              /* Zero means one.  No alignment constraints.  */
              size_t addralign = shdr->sh_addralign ?: 1;
@@ -3177,7 +3473,8 @@ main (int argc, char *argv[])
   chmod (file, stat_buf.st_mode);
 
   free ((char *) dso->filename);
-  destroy_strings (&dso->strings);
+  destroy_strings (&dso->debug_str);
+  destroy_strings (&dso->debug_line_str);
   destroy_lines (&dso->lines);
   free (dso);
 

_______________________________________________
Rpm-maint mailing list
Rpm-maint@lists.rpm.org
http://lists.rpm.org/mailman/listinfo/rpm-maint

Reply via email to