Skip to content

Commit

Permalink
Add utils to read elf binary and get Build id
Browse files Browse the repository at this point in the history
Adds code to parse elf binary to get build id from
.note.gnu.build-id section. The build ID is needed to symbolize
heap dumps from the users.

BUG=734705

Change-Id: If4365e232c060ba96071ba1e1c43618f9807e39c
Reviewed-on: https://chromium-review.googlesource.com/1028995
Commit-Queue: Siddhartha S <ssid@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#554304}
  • Loading branch information
ssiddhartha authored and Commit Bot committed Apr 27, 2018
1 parent fae8443 commit 61248f1
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 1 deletion.
10 changes: 9 additions & 1 deletion base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,11 @@ jumbo_component("base") {
]

if (is_linux) {
sources += [ "base_paths_posix.cc" ]
sources += [
"base_paths_posix.cc",
"debug/elf_reader_linux.cc",
"debug/elf_reader_linux.h",
]
}
}

Expand Down Expand Up @@ -1264,6 +1268,8 @@ jumbo_component("base") {
# Android uses some Linux sources, put those back.
set_sources_assignment_filter([])
sources += [
"debug/elf_reader_linux.cc",
"debug/elf_reader_linux.h",
"debug/proc_maps_linux.cc",
"debug/proc_maps_linux.h",
"files/file_path_watcher_linux.cc",
Expand Down Expand Up @@ -2137,6 +2143,7 @@ test("base_unittests") {
"debug/alias_unittest.cc",
"debug/crash_logging_unittest.cc",
"debug/debugger_unittest.cc",
"debug/elf_reader_linux_unittest.cc",
"debug/leak_tracker_unittest.cc",
"debug/proc_maps_linux_unittest.cc",
"debug/stack_trace_unittest.cc",
Expand Down Expand Up @@ -2578,6 +2585,7 @@ test("base_unittests") {
deps += [ "//testing/android/native_test:native_test_native_code" ]
set_sources_assignment_filter([])
sources += [
"debug/elf_reader_linux_unittest.cc",
"debug/proc_maps_linux_unittest.cc",
"trace_event/trace_event_android_unittest.cc",
]
Expand Down
102 changes: 102 additions & 0 deletions base/debug/elf_reader_linux.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/debug/elf_reader_linux.h"

#include <arpa/inet.h>
#include <elf.h>

#include <vector>

#include "base/bits.h"
#include "base/containers/span.h"
#include "base/sha1.h"
#include "base/strings/stringprintf.h"

extern char __executable_start;

namespace base {
namespace debug {

namespace {

#if __SIZEOF_POINTER__ == 4
using Ehdr = Elf32_Ehdr;
using Half = Elf32_Half;
using Nhdr = Elf32_Nhdr;
using Phdr = Elf32_Phdr;
#else
using Ehdr = Elf64_Ehdr;
using Half = Elf64_Half;
using Nhdr = Elf64_Nhdr;
using Phdr = Elf64_Phdr;
#endif

using ElfSegment = span<const char>;

Optional<std::string> ElfSegmentBuildIDNoteAsString(const ElfSegment& segment) {
const void* section_end = segment.data() + segment.size_bytes();
const Nhdr* note_header = reinterpret_cast<const Nhdr*>(segment.data());
while (note_header < section_end) {
if (note_header->n_type == NT_GNU_BUILD_ID)
break;
note_header = reinterpret_cast<const Nhdr*>(
reinterpret_cast<const char*>(note_header) + sizeof(Nhdr) +
bits::Align(note_header->n_namesz, 4) +
bits::Align(note_header->n_descsz, 4));
}

if (note_header >= section_end || note_header->n_descsz != kSHA1Length)
return nullopt;

const uint8_t* guid = reinterpret_cast<const uint8_t*>(note_header) +
sizeof(Nhdr) + bits::Align(note_header->n_namesz, 4);

uint32_t dword = htonl(*reinterpret_cast<const int32_t*>(guid));
uint16_t word1 = htons(*reinterpret_cast<const int16_t*>(guid + 4));
uint16_t word2 = htons(*reinterpret_cast<const int16_t*>(guid + 6));
std::string identifier;
identifier.reserve(kSHA1Length * 2); // as hex string
SStringPrintf(&identifier, "%08X%04X%04X", dword, word1, word2);
for (size_t i = 8; i < note_header->n_descsz; ++i)
StringAppendF(&identifier, "%02X", guid[i]);

return identifier;
}

std::vector<ElfSegment> FindElfSegments(const char* elf_base,
uint32_t segment_type) {
if (strncmp(static_cast<const char*>(elf_base), ELFMAG, SELFMAG) != 0)
return std::vector<ElfSegment>();

const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
const Phdr* phdrs =
reinterpret_cast<const Phdr*>(elf_base + elf_header->e_phoff);
std::vector<ElfSegment> segments;
for (Half i = 0; i < elf_header->e_phnum; ++i) {
if (phdrs[i].p_type == segment_type)
segments.push_back({elf_base + phdrs[i].p_offset, phdrs[i].p_filesz});
}
return segments;
}

} // namespace

Optional<std::string> ReadElfBuildId() {
// Elf program headers can have multiple PT_NOTE arrays.
std::vector<ElfSegment> segs = FindElfSegments(&__executable_start, PT_NOTE);
if (segs.empty())
return nullopt;
Optional<std::string> id;
for (const ElfSegment& seg : segs) {
id = ElfSegmentBuildIDNoteAsString(seg);
if (id)
return id;
}

return nullopt;
}

} // namespace debug
} // namespace base
23 changes: 23 additions & 0 deletions base/debug/elf_reader_linux.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_DEBUG_ELF_READER_LINUX_H_
#define BASE_DEBUG_ELF_READER_LINUX_H_

#include <string>

#include "base/base_export.h"
#include "base/optional.h"

namespace base {
namespace debug {

// Returns the ELF section .note.gnu.build-id from current executable file, if
// present.
Optional<std::string> BASE_EXPORT ReadElfBuildId();

} // namespace debug
} // namespace base

#endif // BASE_DEBUG_ELF_READER_LINUX_H_
30 changes: 30 additions & 0 deletions base/debug/elf_reader_linux_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/debug/elf_reader_linux.h"

#include "base/strings/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace debug {

// The linker flag --build-id is passed only on official builds. Clang does not
// enable it by default and we do not have build id section in non-official
// builds.
#if defined(OFFICIAL_BUILD)
TEST(ElfReaderTest, ReadElfBuildId) {
Optional<std::string> build_id = ReadElfBuildId();
ASSERT_TRUE(build_id);
const size_t kGuidBytes = 20;
EXPECT_EQ(2 * kGuidBytes, build_id.value().size());
for (char c : *build_id) {
EXPECT_TRUE(IsHexDigit(c));
EXPECT_FALSE(IsAsciiLower(c));
}
}
#endif

} // namespace debug
} // namespace base

0 comments on commit 61248f1

Please sign in to comment.