linux, dump_syms: set module name from DT_SONAME

The Breakpad and Crashpad clients will use an object's DT_SONAME as
the name for a module if it exists. Previously, linux dump_syms would
assume the basename of an input elf file matches that value, causing
symbol lookups to fail if they were mismatched. This patch updates
dump_syms to use DT_SONAME as the module name, if present.

Bug: 1016924
Change-Id: I5eff0cf06c703841df3fb552cb5a8e1e50a20c64
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1876763
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
index dbedecd..5653133 100644
--- a/src/client/linux/minidump_writer/linux_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -439,49 +439,6 @@
 }
 
 namespace {
-bool ElfFileSoNameFromMappedFile(
-    const void* elf_base, char* soname, size_t soname_size) {
-  if (!IsValidElf(elf_base)) {
-    // Not ELF
-    return false;
-  }
-
-  const void* segment_start;
-  size_t segment_size;
-  if (!FindElfSection(elf_base, ".dynamic", SHT_DYNAMIC, &segment_start,
-                      &segment_size)) {
-    // No dynamic section
-    return false;
-  }
-
-  const void* dynstr_start;
-  size_t dynstr_size;
-  if (!FindElfSection(elf_base, ".dynstr", SHT_STRTAB, &dynstr_start,
-                      &dynstr_size)) {
-    // No dynstr section
-    return false;
-  }
-
-  const ElfW(Dyn)* dynamic = static_cast<const ElfW(Dyn)*>(segment_start);
-  size_t dcount = segment_size / sizeof(ElfW(Dyn));
-  for (const ElfW(Dyn)* dyn = dynamic; dyn < dynamic + dcount; ++dyn) {
-    if (dyn->d_tag == DT_SONAME) {
-      const char* dynstr = static_cast<const char*>(dynstr_start);
-      if (dyn->d_un.d_val >= dynstr_size) {
-        // Beyond the end of the dynstr section
-        return false;
-      }
-      const char* str = dynstr + dyn->d_un.d_val;
-      const size_t maxsize = dynstr_size - dyn->d_un.d_val;
-      my_strlcpy(soname, str, maxsize < soname_size ? maxsize : soname_size);
-      return true;
-    }
-  }
-
-  // Did not find SONAME
-  return false;
-}
-
 // Find the shared object name (SONAME) by examining the ELF information
 // for |mapping|. If the SONAME is found copy it into the passed buffer
 // |soname| and return true. The size of the buffer is |soname_size|.
diff --git a/src/common/linux/dump_symbols.cc b/src/common/linux/dump_symbols.cc
index 1110cb9..40bb3ff 100644
--- a/src/common/linux/dump_symbols.cc
+++ b/src/common/linux/dump_symbols.cc
@@ -38,6 +38,7 @@
 #include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <link.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -936,7 +937,14 @@
     return false;
   }
 
-  string name = google_breakpad::BaseName(obj_filename);
+  string name;
+  char name_buf[NAME_MAX];
+  memset(name_buf, 0, sizeof(name_buf));
+  name = google_breakpad::ElfFileSoNameFromMappedFile(elf_header, name_buf,
+                                                      sizeof(name_buf))
+             ? name_buf
+             : google_breakpad::BaseName(obj_filename);
+
   string os = "Linux";
   // Add an extra "0" at the end.  PDB files on Windows have an 'age'
   // number appended to the end of the file identifier; this isn't
diff --git a/src/common/linux/elfutils.cc b/src/common/linux/elfutils.cc
index 3bb8ff1..9532d5a 100644
--- a/src/common/linux/elfutils.cc
+++ b/src/common/linux/elfutils.cc
@@ -173,4 +173,65 @@
   return false;
 }
 
+template <typename ElfClass>
+bool FindElfSoNameFromDynamicSection(const void* section_start,
+                                     size_t section_size,
+                                     const void* dynstr_start,
+                                     size_t dynstr_size,
+                                     char* soname,
+                                     size_t soname_size) {
+  typedef typename ElfClass::Dyn Dyn;
+
+  auto* dynamic = static_cast<const Dyn*>(section_start);
+  size_t dcount = section_size / sizeof(Dyn);
+  for (const Dyn* dyn = dynamic; dyn < dynamic + dcount; ++dyn) {
+    if (dyn->d_tag == DT_SONAME) {
+      const char* dynstr = static_cast<const char*>(dynstr_start);
+      if (dyn->d_un.d_val >= dynstr_size) {
+        // Beyond the end of the dynstr section
+        return false;
+      }
+      const char* str = dynstr + dyn->d_un.d_val;
+      const size_t maxsize = dynstr_size - dyn->d_un.d_val;
+      my_strlcpy(soname, str, maxsize < soname_size ? maxsize : soname_size);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool ElfFileSoNameFromMappedFile(const void* elf_base,
+                                 char* soname,
+                                 size_t soname_size) {
+  if (!IsValidElf(elf_base)) {
+    // Not ELF
+    return false;
+  }
+
+  const void* segment_start;
+  size_t segment_size;
+  if (!FindElfSection(elf_base, ".dynamic", SHT_DYNAMIC, &segment_start,
+                      &segment_size)) {
+    // No dynamic section
+    return false;
+  }
+
+  const void* dynstr_start;
+  size_t dynstr_size;
+  if (!FindElfSection(elf_base, ".dynstr", SHT_STRTAB, &dynstr_start,
+                      &dynstr_size)) {
+    // No dynstr section
+    return false;
+  }
+
+  int cls = ElfClass(elf_base);
+  return cls == ELFCLASS32 ? FindElfSoNameFromDynamicSection<ElfClass32>(
+                                 segment_start, segment_size, dynstr_start,
+                                 dynstr_size, soname, soname_size)
+                           : FindElfSoNameFromDynamicSection<ElfClass64>(
+                                 segment_start, segment_size, dynstr_start,
+                                 dynstr_size, soname, soname_size);
+}
+
 }  // namespace google_breakpad
diff --git a/src/common/linux/elfutils.h b/src/common/linux/elfutils.h
index 8fcb6a9..aefb6cf 100644
--- a/src/common/linux/elfutils.h
+++ b/src/common/linux/elfutils.h
@@ -45,6 +45,7 @@
 // with specific ELF bits.
 struct ElfClass32 {
   typedef Elf32_Addr Addr;
+  typedef Elf32_Dyn Dyn;
   typedef Elf32_Ehdr Ehdr;
   typedef Elf32_Nhdr Nhdr;
   typedef Elf32_Phdr Phdr;
@@ -62,6 +63,7 @@
 
 struct ElfClass64 {
   typedef Elf64_Addr Addr;
+  typedef Elf64_Dyn Dyn;
   typedef Elf64_Ehdr Ehdr;
   typedef Elf64_Nhdr Nhdr;
   typedef Elf64_Phdr Phdr;
@@ -122,6 +124,12 @@
 GetOffset(const typename ElfClass::Ehdr* elf_header,
           typename ElfClass::Off offset);
 
+// Read the value of DT_SONAME from the elf file mapped at |elf_base|. Returns
+// true and fills |soname| with the result if found.
+bool ElfFileSoNameFromMappedFile(const void* elf_base,
+                                 char* soname,
+                                 size_t soname_size);
+
 }  // namespace google_breakpad
 
 #endif  // COMMON_LINUX_ELFUTILS_H_