aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordigit@chromium.org <digit@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2013-04-24 10:06:14 +0000
committerdigit@chromium.org <digit@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2013-04-24 10:06:14 +0000
commit593eff42ca4a5c38835577cf83152f356d262414 (patch)
tree6102e9d82b8f109d9aab1d52bdff2e4d75fcb9fe /src
parentCleanup: Remove duplicate wording in license headers. (diff)
downloadbreakpad-593eff42ca4a5c38835577cf83152f356d262414.tar.xz
Improve ARM CPU info reporting.
This patch improves several things for Linux/ARM: - Better detection of the number of CPUs on the target device. The content of /proc/cpuinfo only matches the number of "online" CPUs, which varies over time with recent Android devices. - Reconstruct the CPUID and ELF hwcaps values from /proc/cpuinfo, this is useful to better identify target devices in minidumps. - Make minidump_dump display the new information in useful ways. - Write a small helper class to parse /proc/cpuinfo and also use it for x86/64. - Write a small helper class to parse sysfds cpu lists. - Add a my_memchr() implementation. - Add unit tests. Tested on a Nexus S (1 CPU), Galaxy Nexus (2 CPUs) and a Nexus 4 (4 CPUs). Review URL: https://breakpad.appspot.com/540003 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1160 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src')
-rw-r--r--src/client/linux/minidump_writer/cpu_set.h144
-rw-r--r--src/client/linux/minidump_writer/cpu_set_unittest.cc165
-rw-r--r--src/client/linux/minidump_writer/line_reader_unittest.cc101
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc283
-rw-r--r--src/client/linux/minidump_writer/proc_cpuinfo_reader.h130
-rw-r--r--src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc200
-rw-r--r--src/common/linux/linux_libc_support.cc10
-rw-r--r--src/common/linux/linux_libc_support.h2
-rw-r--r--src/common/linux/linux_libc_support_unittest.cc12
-rw-r--r--src/common/tests/auto_testfile.h127
-rw-r--r--src/google_breakpad/common/minidump_format.h30
-rw-r--r--src/processor/minidump_processor.cc123
-rw-r--r--src/processor/synth_minidump.cc3
13 files changed, 1203 insertions, 127 deletions
diff --git a/src/client/linux/minidump_writer/cpu_set.h b/src/client/linux/minidump_writer/cpu_set.h
new file mode 100644
index 00000000..1cca9aa5
--- /dev/null
+++ b/src/client/linux/minidump_writer/cpu_set.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// Helper class used to model a set of CPUs, as read from sysfs
+// files like /sys/devices/system/cpu/present
+// See See http://www.kernel.org/doc/Documentation/cputopology.txt
+class CpuSet {
+public:
+ // The maximum number of supported CPUs.
+ static const size_t kMaxCpus = 1024;
+
+ CpuSet() {
+ my_memset(mask_, 0, sizeof(mask_));
+ }
+
+ // Parse a sysfs file to extract the corresponding CPU set.
+ bool ParseSysFile(int fd) {
+ char buffer[512];
+ int ret = sys_read(fd, buffer, sizeof(buffer)-1);
+ if (ret < 0)
+ return false;
+
+ buffer[ret] = '\0';
+
+ // Expected format: comma-separated list of items, where each
+ // item can be a decimal integer, or two decimal integers separated
+ // by a dash.
+ // E.g.:
+ // 0
+ // 0,1,2,3
+ // 0-3
+ // 1,10-23
+ const char* p = buffer;
+ const char* p_end = p + ret;
+ while (p < p_end) {
+ // Skip leading space, if any
+ while (p < p_end && my_isspace(*p))
+ p++;
+
+ // Find start and size of current item.
+ const char* item = p;
+ size_t item_len = static_cast<size_t>(p_end - p);
+ const char* item_next =
+ static_cast<const char*>(my_memchr(p, ',', item_len));
+ if (item_next != NULL) {
+ p = item_next + 1;
+ item_len = static_cast<size_t>(item_next - item);
+ } else {
+ p = p_end;
+ item_next = p_end;
+ }
+
+ // Ignore trailing spaces.
+ while (item_next > item && my_isspace(item_next[-1]))
+ item_next--;
+
+ // skip empty items.
+ if (item_next == item)
+ continue;
+
+ // read first decimal value.
+ uintptr_t start = 0;
+ const char* next = my_read_decimal_ptr(&start, item);
+ uintptr_t end = start;
+ if (*next == '-')
+ my_read_decimal_ptr(&end, next+1);
+
+ while (start <= end)
+ SetBit(start++);
+ }
+ return true;
+ }
+
+ // Intersect this CPU set with another one.
+ void IntersectWith(const CpuSet& other) {
+ for (size_t nn = 0; nn < kMaskWordCount; ++nn)
+ mask_[nn] &= other.mask_[nn];
+ }
+
+ // Return the number of CPUs in this set.
+ int GetCount() {
+ int result = 0;
+ for (size_t nn = 0; nn < kMaskWordCount; ++nn) {
+ result += __builtin_popcount(mask_[nn]);
+ }
+ return result;
+ }
+
+private:
+ void SetBit(uintptr_t index) {
+ size_t nn = static_cast<size_t>(index);
+ if (nn < kMaxCpus)
+ mask_[nn / kMaskWordBits] |= (1U << (nn % kMaskWordBits));
+ }
+
+ typedef uint32_t MaskWordType;
+ static const size_t kMaskWordBits = 8*sizeof(MaskWordType);
+ static const size_t kMaskWordCount =
+ (kMaxCpus + kMaskWordBits - 1) / kMaskWordBits;
+
+ MaskWordType mask_[kMaskWordCount];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
diff --git a/src/client/linux/minidump_writer/cpu_set_unittest.cc b/src/client/linux/minidump_writer/cpu_set_unittest.cc
new file mode 100644
index 00000000..6c0fa43c
--- /dev/null
+++ b/src/client/linux/minidump_writer/cpu_set_unittest.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "breakpad_googletest_includes.h"
+#include "client/linux/minidump_writer/cpu_set.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/tests/auto_testfile.h"
+
+using namespace google_breakpad;
+
+namespace {
+
+typedef testing::Test CpuSetTest;
+
+// Helper class to write test text file to a temporary file and return
+// its file descriptor.
+class ScopedTestFile : public AutoTestFile {
+public:
+ explicit ScopedTestFile(const char* text)
+ : AutoTestFile("cpu_set", text) {
+ }
+};
+
+}
+
+TEST(CpuSetTest, EmptyCount) {
+ CpuSet set;
+ ASSERT_EQ(0, set.GetCount());
+}
+
+TEST(CpuSetTest, OneCpu) {
+ ScopedTestFile file("10");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(1, set.GetCount());
+}
+
+TEST(CpuSetTest, OneCpuTerminated) {
+ ScopedTestFile file("10\n");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(1, set.GetCount());
+}
+
+TEST(CpuSetTest, TwoCpusWithComma) {
+ ScopedTestFile file("1,10");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(2, set.GetCount());
+}
+
+TEST(CpuSetTest, TwoCpusWithRange) {
+ ScopedTestFile file("1-2");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(2, set.GetCount());
+}
+
+TEST(CpuSetTest, TenCpusWithRange) {
+ ScopedTestFile file("9-18");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(10, set.GetCount());
+}
+
+TEST(CpuSetTest, MultiItems) {
+ ScopedTestFile file("0, 2-4, 128");
+ ASSERT_TRUE(file.IsOk());
+
+ CpuSet set;
+ ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
+ ASSERT_EQ(5, set.GetCount());
+}
+
+TEST(CpuSetTest, IntersectWith) {
+ ScopedTestFile file1("9-19");
+ ASSERT_TRUE(file1.IsOk());
+ CpuSet set1;
+ ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
+ ASSERT_EQ(11, set1.GetCount());
+
+ ScopedTestFile file2("16-24");
+ ASSERT_TRUE(file2.IsOk());
+ CpuSet set2;
+ ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
+ ASSERT_EQ(9, set2.GetCount());
+
+ set1.IntersectWith(set2);
+ ASSERT_EQ(4, set1.GetCount());
+ ASSERT_EQ(9, set2.GetCount());
+}
+
+TEST(CpuSetTest, SelfIntersection) {
+ ScopedTestFile file1("9-19");
+ ASSERT_TRUE(file1.IsOk());
+ CpuSet set1;
+ ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
+ ASSERT_EQ(11, set1.GetCount());
+
+ set1.IntersectWith(set1);
+ ASSERT_EQ(11, set1.GetCount());
+}
+
+TEST(CpuSetTest, EmptyIntersection) {
+ ScopedTestFile file1("0-19");
+ ASSERT_TRUE(file1.IsOk());
+ CpuSet set1;
+ ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
+ ASSERT_EQ(20, set1.GetCount());
+
+ ScopedTestFile file2("20-39");
+ ASSERT_TRUE(file2.IsOk());
+ CpuSet set2;
+ ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
+ ASSERT_EQ(20, set2.GetCount());
+
+ set1.IntersectWith(set2);
+ ASSERT_EQ(0, set1.GetCount());
+
+ ASSERT_EQ(20, set2.GetCount());
+}
+
diff --git a/src/client/linux/minidump_writer/line_reader_unittest.cc b/src/client/linux/minidump_writer/line_reader_unittest.cc
index 4448281d..928626e1 100644
--- a/src/client/linux/minidump_writer/line_reader_unittest.cc
+++ b/src/client/linux/minidump_writer/line_reader_unittest.cc
@@ -34,47 +34,41 @@
#include "client/linux/minidump_writer/line_reader.h"
#include "breakpad_googletest_includes.h"
#include "common/linux/eintr_wrapper.h"
+#include "common/tests/auto_testfile.h"
using namespace google_breakpad;
-#if !defined(__ANDROID__)
-#define TEMPDIR "/tmp"
-#else
-#define TEMPDIR "/data/local/tmp"
-#endif
-
-static int TemporaryFile() {
- static const char templ[] = TEMPDIR "/line-reader-unittest-XXXXXX";
- char templ_copy[sizeof(templ)];
- memcpy(templ_copy, templ, sizeof(templ));
- const int fd = mkstemp(templ_copy);
- if (fd >= 0)
- unlink(templ_copy);
-
- return fd;
-}
-
namespace {
+
typedef testing::Test LineReaderTest;
+
+class ScopedTestFile : public AutoTestFile {
+public:
+ explicit ScopedTestFile(const char* text)
+ : AutoTestFile("line_reader", text) {
+ }
+
+ ScopedTestFile(const char* text, size_t text_len)
+ : AutoTestFile("line_reader", text, text_len) {
+ }
+};
+
}
TEST(LineReaderTest, EmptyFile) {
- const int fd = TemporaryFile();
- LineReader reader(fd);
+ ScopedTestFile file("");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
-
- close(fd);
}
TEST(LineReaderTest, OneLineTerminated) {
- const int fd = TemporaryFile();
- const int r = HANDLE_EINTR(write(fd, "a\n", 2));
- ASSERT_EQ(2, r);
- lseek(fd, 0, SEEK_SET);
- LineReader reader(fd);
+ ScopedTestFile file("a\n");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned int len;
@@ -85,16 +79,12 @@ TEST(LineReaderTest, OneLineTerminated) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
-
- close(fd);
}
TEST(LineReaderTest, OneLine) {
- const int fd = TemporaryFile();
- const int r = HANDLE_EINTR(write(fd, "a", 1));
- ASSERT_EQ(1, r);
- lseek(fd, 0, SEEK_SET);
- LineReader reader(fd);
+ ScopedTestFile file("a");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -105,16 +95,12 @@ TEST(LineReaderTest, OneLine) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
-
- close(fd);
}
TEST(LineReaderTest, TwoLinesTerminated) {
- const int fd = TemporaryFile();
- const int r = HANDLE_EINTR(write(fd, "a\nb\n", 4));
- ASSERT_EQ(4, r);
- lseek(fd, 0, SEEK_SET);
- LineReader reader(fd);
+ ScopedTestFile file("a\nb\n");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -131,16 +117,12 @@ TEST(LineReaderTest, TwoLinesTerminated) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
-
- close(fd);
}
TEST(LineReaderTest, TwoLines) {
- const int fd = TemporaryFile();
- const int r = HANDLE_EINTR(write(fd, "a\nb", 3));
- ASSERT_EQ(3, r);
- lseek(fd, 0, SEEK_SET);
- LineReader reader(fd);
+ ScopedTestFile file("a\nb");
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -157,18 +139,14 @@ TEST(LineReaderTest, TwoLines) {
reader.PopLine(len);
ASSERT_FALSE(reader.GetNextLine(&line, &len));
-
- close(fd);
}
TEST(LineReaderTest, MaxLength) {
- const int fd = TemporaryFile();
- char l[LineReader::kMaxLineLen - 1];
+ char l[LineReader::kMaxLineLen-1];
memset(l, 'a', sizeof(l));
- const int r = HANDLE_EINTR(write(fd, l, sizeof(l)));
- ASSERT_EQ(static_cast<ssize_t>(sizeof(l)), r);
- lseek(fd, 0, SEEK_SET);
- LineReader reader(fd);
+ ScopedTestFile file(l, sizeof(l));
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned len;
@@ -176,22 +154,17 @@ TEST(LineReaderTest, MaxLength) {
ASSERT_EQ(sizeof(l), len);
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
ASSERT_EQ('\0', line[len]);
-
- close(fd);
}
TEST(LineReaderTest, TooLong) {
- const int fd = TemporaryFile();
+ // Note: this writes kMaxLineLen 'a' chars in the test file.
char l[LineReader::kMaxLineLen];
memset(l, 'a', sizeof(l));
- const int r = HANDLE_EINTR(write(fd, l, sizeof(l)));
- ASSERT_EQ(static_cast<ssize_t>(sizeof(l)), r);
- lseek(fd, 0, SEEK_SET);
- LineReader reader(fd);
+ ScopedTestFile file(l, sizeof(l));
+ ASSERT_TRUE(file.IsOk());
+ LineReader reader(file.GetFd());
const char *line;
unsigned len;
ASSERT_FALSE(reader.GetNextLine(&line, &len));
-
- close(fd);
}
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
index 34091dbe..76c6e5a6 100644
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -64,9 +64,11 @@
#include <algorithm>
#include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/cpu_set.h"
#include "client/linux/minidump_writer/line_reader.h"
#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
+#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
#include "client/minidump_file_writer.h"
#include "common/linux/linux_libc_support.h"
#include "google_breakpad/common/minidump_format.h"
@@ -76,6 +78,7 @@ namespace {
using google_breakpad::AppMemoryList;
using google_breakpad::ExceptionHandler;
+using google_breakpad::CpuSet;
using google_breakpad::LineReader;
using google_breakpad::LinuxDumper;
using google_breakpad::LinuxPtraceDumper;
@@ -84,6 +87,7 @@ using google_breakpad::MappingInfo;
using google_breakpad::MappingList;
using google_breakpad::MinidumpFileWriter;
using google_breakpad::PageAllocator;
+using google_breakpad::ProcCpuInfoReader;
using google_breakpad::ThreadInfo;
using google_breakpad::TypedMDRVA;
using google_breakpad::UntypedMDRVA;
@@ -92,7 +96,7 @@ using google_breakpad::wasteful_vector;
// Minidump defines register structures which are different from the raw
// structures which we get from the kernel. These are platform specific
// functions to juggle the ucontext and user structures into minidump format.
-#if defined(__i386)
+#if defined(__i386__)
typedef MDRawContextX86 RawContextCPU;
// Write a uint16_t to memory
@@ -597,7 +601,7 @@ class MinidumpWriter {
return;
}
}
-#elif defined(__i386)
+#elif defined(__i386__)
uint32_t bp = cpu->ebp;
uint32_t top = thread.stack.start_of_memory_range;
for (int i = 4; i--; ) {
@@ -1163,7 +1167,7 @@ class MinidumpWriter {
return dumper_->crash_thread();
}
-#if defined(__i386)
+#if defined(__i386__)
uintptr_t GetStackPointer() {
return ucontext_->uc_mcontext.gregs[REG_ESP];
}
@@ -1209,10 +1213,10 @@ class MinidumpWriter {
dirent->location.rva = 0;
}
+#if defined(__i386__) || defined(__x86_64__)
bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0};
static const char vendor_id_name[] = "vendor_id";
- static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
struct CpuInfoEntry {
const char* info_name;
@@ -1220,23 +1224,17 @@ class MinidumpWriter {
bool found;
} cpu_info_table[] = {
{ "processor", -1, false },
-#if defined(__i386) || defined(__x86_64)
{ "model", 0, false },
{ "stepping", 0, false },
{ "cpu family", 0, false },
-#endif
};
// processor_architecture should always be set, do this first
sys_info->processor_architecture =
-#if defined(__i386)
+#if defined(__i386__)
MD_CPU_ARCHITECTURE_X86;
-#elif defined(__x86_64)
- MD_CPU_ARCHITECTURE_AMD64;
-#elif defined(__arm__)
- MD_CPU_ARCHITECTURE_ARM;
#else
-#error "Unknown CPU arch"
+ MD_CPU_ARCHITECTURE_AMD64;
#endif
const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
@@ -1245,71 +1243,39 @@ class MinidumpWriter {
{
PageAllocator allocator;
- LineReader* const line_reader = new(allocator) LineReader(fd);
- const char* line;
- unsigned line_len;
- while (line_reader->GetNextLine(&line, &line_len)) {
+ ProcCpuInfoReader* const reader = new(allocator) ProcCpuInfoReader(fd);
+ const char* field;
+ while (reader->GetNextField(&field)) {
for (size_t i = 0;
i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
i++) {
CpuInfoEntry* entry = &cpu_info_table[i];
- if (entry->found && i)
+ if (i > 0 && entry->found) {
+ // except for the 'processor' field, ignore repeated values.
continue;
- if (!my_strncmp(line, entry->info_name, strlen(entry->info_name))) {
- const char* value = my_strchr(line, ':');
- if (!value)
- continue;
-
- // the above strncmp only matches the prefix, it might be the wrong
- // line. i.e. we matched "model name" instead of "model".
- // check and make sure there is only spaces between the prefix and
- // the colon.
- const char* space_ptr = line + my_strlen(entry->info_name);
- for (; space_ptr < value; space_ptr++) {
- if (!my_isspace(*space_ptr)) {
- break;
- }
- }
- if (space_ptr != value)
+ }
+ if (!my_strcmp(field, entry->info_name)) {
+ size_t value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+ if (value_len == 0)
continue;
- // skip past the colon and all the spaces that follow
- do {
- value++;
- } while (my_isspace(*value));
-
uintptr_t val;
if (my_read_decimal_ptr(&val, value) == value)
continue;
+
entry->value = static_cast<int>(val);
entry->found = true;
}
}
// special case for vendor_id
- if (!my_strncmp(line, vendor_id_name, vendor_id_name_length)) {
- const char* value = my_strchr(line, ':');
- if (!value)
- goto popline;
-
- // skip past the colon and all the spaces that follow
- do {
- value++;
- } while (my_isspace(*value));
-
- if (*value) {
- size_t length = my_strlen(value);
- if (length == 0)
- goto popline;
+ if (!my_strcmp(field, vendor_id_name)) {
+ size_t value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+ if (value_len > 0)
my_strlcpy(vendor_id, value, sizeof(vendor_id));
- // we don't want the trailing newline
- if (length < sizeof(vendor_id) && vendor_id[length - 1] == '\n')
- vendor_id[length - 1] = '\0';
- }
}
-
- popline:
- line_reader->PopLine(line_len);
}
sys_close(fd);
}
@@ -1322,15 +1288,15 @@ class MinidumpWriter {
return false;
}
}
- // /proc/cpuinfo contains cpu id, change it into number by adding one.
+ // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo,
+ // assuming this is the highest id, change it to the number of CPUs
+ // by adding one.
cpu_info_table[0].value++;
sys_info->number_of_processors = cpu_info_table[0].value;
-#if defined(__i386) || defined(__x86_64)
sys_info->processor_level = cpu_info_table[3].value;
sys_info->processor_revision = cpu_info_table[1].value << 8 |
cpu_info_table[2].value;
-#endif
if (vendor_id[0] != '\0') {
my_memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
@@ -1338,6 +1304,199 @@ class MinidumpWriter {
}
return true;
}
+#elif defined(__arm__)
+ bool WriteCPUInformation(MDRawSystemInfo* sys_info) {
+ // The CPUID value is broken up in several entries in /proc/cpuinfo.
+ // This table is used to rebuild it from the entries.
+ const struct CpuIdEntry {
+ const char* field;
+ char format;
+ char bit_lshift;
+ char bit_length;
+ } cpu_id_entries[] = {
+ { "CPU implementer", 'x', 24, 8 },
+ { "CPU variant", 'x', 20, 4 },
+ { "CPU part", 'x', 4, 12 },
+ { "CPU revision", 'd', 0, 4 },
+ };
+
+ // The ELF hwcaps are listed in the "Features" entry as textual tags.
+ // This table is used to rebuild them.
+ const struct CpuFeaturesEntry {
+ const char* tag;
+ uint32_t hwcaps;
+ } cpu_features_entries[] = {
+ { "swp", MD_CPU_ARM_ELF_HWCAP_SWP },
+ { "half", MD_CPU_ARM_ELF_HWCAP_HALF },
+ { "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB },
+ { "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT },
+ { "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT },
+ { "fpa", MD_CPU_ARM_ELF_HWCAP_FPA },
+ { "vfp", MD_CPU_ARM_ELF_HWCAP_VFP },
+ { "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP },
+ { "java", MD_CPU_ARM_ELF_HWCAP_JAVA },
+ { "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT },
+ { "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH },
+ { "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE },
+ { "neon", MD_CPU_ARM_ELF_HWCAP_NEON },
+ { "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 },
+ { "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 },
+ { "tls", MD_CPU_ARM_ELF_HWCAP_TLS },
+ { "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 },
+ { "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA },
+ { "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT },
+ { "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT },
+ };
+
+ // processor_architecture should always be set, do this first
+ sys_info->processor_architecture = MD_CPU_ARCHITECTURE_ARM;
+
+ // /proc/cpuinfo is not readable under various sandboxed environments
+ // (e.g. Android services with the android:isolatedProcess attribute)
+ // prepare for this by setting default values now, which will be
+ // returned when this happens.
+ //
+ // Note: Bogus values are used to distinguish between failures (to
+ // read /sys and /proc files) and really badly configured kernels.
+ sys_info->number_of_processors = 0;
+ sys_info->processor_level = 1U; // There is no ARMv1
+ sys_info->processor_revision = 42;
+ sys_info->cpu.arm_cpu_info.cpuid = 0;
+ sys_info->cpu.arm_cpu_info.elf_hwcaps = 0;
+
+ // Counting the number of CPUs involves parsing two sysfs files,
+ // because the content of /proc/cpuinfo will only mirror the number
+ // of 'online' cores, and thus will vary with time.
+ // See http://www.kernel.org/doc/Documentation/cputopology.txt
+ {
+ CpuSet cpus_present;
+ CpuSet cpus_possible;
+
+ int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0);
+ if (fd >= 0) {
+ cpus_present.ParseSysFile(fd);
+ sys_close(fd);
+
+ fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0);
+ if (fd >= 0) {
+ cpus_possible.ParseSysFile(fd);
+ sys_close(fd);
+
+ cpus_present.IntersectWith(cpus_possible);
+ int cpu_count = cpus_present.GetCount();
+ if (cpu_count > 255)
+ cpu_count = 255;
+ sys_info->number_of_processors = static_cast<uint8_t>(cpu_count);
+ }
+ }
+ }
+
+ // Parse /proc/cpuinfo to reconstruct the CPUID value, as well
+ // as the ELF hwcaps field. For the latter, it would be easier to
+ // read /proc/self/auxv but unfortunately, this file is not always
+ // readable from regular Android applications on later versions
+ // (>= 4.1) of the Android platform.
+ const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
+ if (fd < 0) {
+ // Do not return false here to allow the minidump generation
+ // to happen properly.
+ return true;
+ }
+
+ {
+ PageAllocator allocator;
+ ProcCpuInfoReader* const reader =
+ new(allocator) ProcCpuInfoReader(fd);
+ const char* field;
+ while (reader->GetNextField(&field)) {
+ for (size_t i = 0;
+ i < sizeof(cpu_id_entries)/sizeof(cpu_id_entries[0]);
+ ++i) {
+ const CpuIdEntry* entry = &cpu_id_entries[i];
+ if (my_strcmp(entry->field, field) != 0)
+ continue;
+ uintptr_t result = 0;
+ const char* value = reader->GetValue();
+ const char* p = value;
+ if (value[0] == '0' && value[1] == 'x') {
+ p = my_read_hex_ptr(&result, value+2);
+ } else if (entry->format == 'x')
+ p = my_read_hex_ptr(&result, value);
+ else
+ p = my_read_decimal_ptr(&result, value);
+ if (p == value)
+ continue;
+
+ result &= (1U << entry->bit_length)-1;
+ result <<= entry->bit_lshift;
+ sys_info->cpu.arm_cpu_info.cpuid |=
+ static_cast<uint32_t>(result);
+ }
+ // Get the architecture version from the "Processor" field.
+ // Note that it is also available in the "CPU architecture" field,
+ // however, some existing kernels are misconfigured and will report
+ // invalid values here (e.g. 6, while the CPU is ARMv7-A based).
+ // The "Processor" field doesn't have this issue.
+ if (!my_strcmp(field, "Processor")) {
+ unsigned value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+ // Expected format: <text> (v<level><endian>)
+ // Where <text> is some text like "ARMv7 Processor rev 2"
+ // and <level> is a decimal corresponding to the ARM
+ // architecture number. <endian> is either 'l' or 'b'
+ // and corresponds to the endianess, it is ignored here.
+ while (value_len > 0 && my_isspace(value[value_len-1]))
+ value_len--;
+
+ size_t nn = value_len;
+ while (nn > 0 && value[nn-1] != '(')
+ nn--;
+ if (nn > 0 && value[nn] == 'v') {
+ uintptr_t arch_level = 5;
+ my_read_decimal_ptr(&arch_level, value + nn + 1);
+ sys_info->processor_level = static_cast<uint16_t>(arch_level);
+ }
+ }
+ // Rebuild the ELF hwcaps from the 'Features' field.
+ if (!my_strcmp(field, "Features")) {
+ unsigned value_len;
+ const char* value = reader->GetValueAndLen(&value_len);
+
+ // Parse each space-separated tag.
+ while (value_len > 0) {
+ const char* tag = value;
+ size_t tag_len = value_len;
+ const char* p = my_strchr(tag, ' ');
+ if (p != NULL) {
+ tag_len = static_cast<size_t>(p - tag);
+ value += tag_len + 1;
+ value_len -= tag_len + 1;
+ } else {
+ tag_len = strlen(tag);
+ value_len = 0;
+ }
+ for (size_t i = 0;
+ i < sizeof(cpu_features_entries)/
+ sizeof(cpu_features_entries[0]);
+ ++i) {
+ const CpuFeaturesEntry* entry = &cpu_features_entries[i];
+ if (tag_len == strlen(entry->tag) &&
+ !memcmp(tag, entry->tag, tag_len)) {
+ sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry->hwcaps;
+ break;
+ }
+ }
+ }
+ }
+ }
+ sys_close(fd);
+ }
+
+ return true;
+ }
+#else
+# error "Unsupported CPU"
+#endif
bool WriteFile(MDLocationDescriptor* result, const char* filename) {
const int fd = sys_open(filename, O_RDONLY, 0);
diff --git a/src/client/linux/minidump_writer/proc_cpuinfo_reader.h b/src/client/linux/minidump_writer/proc_cpuinfo_reader.h
new file mode 100644
index 00000000..d9461bf3
--- /dev/null
+++ b/src/client/linux/minidump_writer/proc_cpuinfo_reader.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "client/linux/minidump_writer/line_reader.h"
+#include "common/linux/linux_libc_support.h"
+#include "third_party/lss/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for reading /proc/cpuinfo without using fopen/fgets or other
+// functions which may allocate memory.
+class ProcCpuInfoReader {
+public:
+ ProcCpuInfoReader(int fd)
+ : line_reader_(fd), pop_count_(-1) {
+ }
+
+ // Return the next field name, or NULL in case of EOF.
+ // field: (output) Pointer to zero-terminated field name.
+ // Returns true on success, or false on EOF or error (line too long).
+ bool GetNextField(const char** field) {
+ for (;;) {
+ const char* line;
+ unsigned line_len;
+
+ // Try to read next line.
+ if (pop_count_ >= 0) {
+ line_reader_.PopLine(pop_count_);
+ pop_count_ = -1;
+ }
+
+ if (!line_reader_.GetNextLine(&line, &line_len))
+ return false;
+
+ pop_count_ = static_cast<int>(line_len);
+
+ const char* line_end = line + line_len;
+
+ // Expected format: <field-name> <space>+ ':' <space> <value>
+ // Note that:
+ // - empty lines happen.
+ // - <field-name> can contain spaces.
+ // - some fields have an empty <value>
+ char* sep = static_cast<char*>(my_memchr(line, ':', line_len));
+ if (sep == NULL)
+ continue;
+
+ // Record the value. Skip leading space after the column to get
+ // its start.
+ const char* val = sep+1;
+ while (val < line_end && my_isspace(*val))
+ val++;
+
+ value_ = val;
+ value_len_ = static_cast<size_t>(line_end - val);
+
+ // Remove trailing spaces before the column to properly 0-terminate
+ // the field name.
+ while (sep > line && my_isspace(sep[-1]))
+ sep--;
+
+ if (sep == line)
+ continue;
+
+ // zero-terminate field name.
+ *sep = '\0';
+
+ *field = line;
+ return true;
+ }
+ }
+
+ // Return the field value. This must be called after a succesful
+ // call to GetNextField().
+ const char* GetValue() {
+ assert(value_);
+ return value_;
+ }
+
+ // Same as GetValue(), but also returns the length in characters of
+ // the value.
+ const char* GetValueAndLen(size_t* length) {
+ assert(value_);
+ *length = value_len_;
+ return value_;
+ }
+
+private:
+ LineReader line_reader_;
+ int pop_count_;
+ const char* value_;
+ size_t value_len_;
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
diff --git a/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc b/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
new file mode 100644
index 00000000..1f7d0a73
--- /dev/null
+++ b/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
+#include "breakpad_googletest_includes.h"
+#include "common/linux/eintr_wrapper.h"
+#include "common/tests/auto_testfile.h"
+
+using namespace google_breakpad;
+
+#if !defined(__ANDROID__)
+#define TEMPDIR "/tmp"
+#else
+#define TEMPDIR "/data/local/tmp"
+#endif
+
+
+namespace {
+
+typedef testing::Test ProcCpuInfoReaderTest;
+
+class ScopedTestFile : public AutoTestFile {
+public:
+ explicit ScopedTestFile(const char* text)
+ : AutoTestFile("proc_cpuinfo_reader", text) {
+ }
+};
+
+}
+
+TEST(ProcCpuInfoReaderTest, EmptyFile) {
+ ScopedTestFile file("");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char *field;
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, OneLineTerminated) {
+ ScopedTestFile file("foo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char *field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, OneLine) {
+ ScopedTestFile file("foo : bar");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char *field;
+ size_t value_len;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValueAndLen(&value_len));
+ ASSERT_EQ(3U, value_len);
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) {
+ ScopedTestFile file("foo : bar\nzoo : tut\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("zoo", field);
+ ASSERT_STREQ("tut", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipMalformedLine) {
+ ScopedTestFile file("this line should have a column\nfoo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) {
+ ScopedTestFile file("\n\nfoo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipEmptyField) {
+ ScopedTestFile file(" : bar\nzoo : tut\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("zoo", field);
+ ASSERT_STREQ("tut", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) {
+ ScopedTestFile file("foo : bar\n\n\nfoo : bar\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ ASSERT_STREQ("bar", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, FieldWithSpaces) {
+ ScopedTestFile file("foo bar : zoo\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo bar", field);
+ ASSERT_STREQ("zoo", reader.GetValue());
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
+
+TEST(ProcCpuInfoReaderTest, EmptyValue) {
+ ScopedTestFile file("foo :\n");
+ ASSERT_TRUE(file.IsOk());
+ ProcCpuInfoReader reader(file.GetFd());
+
+ const char* field;
+ ASSERT_TRUE(reader.GetNextField(&field));
+ ASSERT_STREQ("foo", field);
+ size_t value_len;
+ ASSERT_STREQ("", reader.GetValueAndLen(&value_len));
+ ASSERT_EQ(0U, value_len);
+
+ ASSERT_FALSE(reader.GetNextField(&field));
+}
diff --git a/src/common/linux/linux_libc_support.cc b/src/common/linux/linux_libc_support.cc
index 845729c9..08b0325e 100644
--- a/src/common/linux/linux_libc_support.cc
+++ b/src/common/linux/linux_libc_support.cc
@@ -138,6 +138,16 @@ const char* my_strrchr(const char* haystack, char needle) {
return ret;
}
+void* my_memchr(const void* src, int needle, size_t src_len) {
+ const unsigned char* p = (const unsigned char*)src;
+ const unsigned char* p_end = p + src_len;
+ for (; p < p_end; ++p) {
+ if (*p == needle)
+ return (void*)p;
+ }
+ return NULL;
+}
+
// Read a hex value
// result: (output) the resulting value
// s: a string
diff --git a/src/common/linux/linux_libc_support.h b/src/common/linux/linux_libc_support.h
index 3429600b..ec5a8d6b 100644
--- a/src/common/linux/linux_libc_support.h
+++ b/src/common/linux/linux_libc_support.h
@@ -77,6 +77,8 @@ extern const char* my_read_decimal_ptr(uintptr_t* result, const char* s);
extern void my_memset(void* ip, char c, size_t len);
+extern void* my_memchr(const void* src, int c, size_t len);
+
// The following are considered safe to use in a compromised environment.
// Besides, this gives the compiler an opportunity to optimize their calls.
#define my_memcpy memcpy
diff --git a/src/common/linux/linux_libc_support_unittest.cc b/src/common/linux/linux_libc_support_unittest.cc
index 5d06d17e..adadfed4 100644
--- a/src/common/linux/linux_libc_support_unittest.cc
+++ b/src/common/linux/linux_libc_support_unittest.cc
@@ -154,6 +154,18 @@ TEST(LinuxLibcSupportTest, strrchr) {
ASSERT_EQ(abc3 + 6, my_strrchr(abc3, 'a'));
}
+TEST(LinuxLibcSupportTest, memchr) {
+ ASSERT_EQ(NULL, my_memchr("abc", 'd', 3));
+ ASSERT_EQ(NULL, my_memchr("abcd", 'd', 3));
+ ASSERT_EQ(NULL, my_memchr("a", 'a', 0));
+
+ static const char abc3[] = "abcabcabc";
+ ASSERT_EQ(abc3, my_memchr(abc3, 'a', 3));
+ ASSERT_EQ(abc3, my_memchr(abc3, 'a', 9));
+ ASSERT_EQ(abc3+1, my_memchr(abc3, 'b', 9));
+ ASSERT_EQ(abc3+2, my_memchr(abc3, 'c', 9));
+}
+
TEST(LinuxLibcSupportTest, read_hex_ptr) {
uintptr_t result;
const char* last;
diff --git a/src/common/tests/auto_testfile.h b/src/common/tests/auto_testfile.h
new file mode 100644
index 00000000..8fd9b50a
--- /dev/null
+++ b/src/common/tests/auto_testfile.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Utility class for creating a temporary file for unit tests
+// that is deleted in the destructor. Only supported on Posix systems.
+#ifndef GOOGLE_BREAKPAD_COMMON_TESTS_AUTO_TESTFILE
+#define GOOGLE_BREAKPAD_COMMON_TESTS_AUTO_TESTFILE
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "common/tests/auto_tempdir.h"
+
+namespace google_breakpad {
+
+#ifdef _WIN32
+#error "This header cannot be used on Windows"
+#else
+
+class AutoTestFile {
+public:
+ // Create a new empty test file.
+ // test_prefix: (input) test-specific prefix, can't be NULL.
+ explicit AutoTestFile(const char* test_prefix) {
+ Init(test_prefix);
+ }
+
+ // Create a new test file, and fill it with initial data from a C string.
+ // The terminating zero is not written.
+ // test_prefix: (input) test-specific prefix, can't be NULL.
+ // text: (input) initial content.
+ AutoTestFile(const char* test_prefix, const char* text) {
+ Init(test_prefix);
+ if (fd_ >= 0)
+ WriteText(text, static_cast<size_t>(strlen(text)));
+ }
+
+ AutoTestFile(const char* test_prefix, const char* text, size_t text_len) {
+ Init(test_prefix);
+ if (fd_ >= 0)
+ WriteText(text, text_len);
+ }
+
+ // Destroy test file on scope exit.
+ ~AutoTestFile() {
+ if (fd_ >= 0) {
+ close(fd_);
+ fd_ = -1;
+ }
+ }
+
+ // Returns true iff the test file could be created properly.
+ // Useful in tests inside EXPECT_TRUE(file.IsOk());
+ bool IsOk() {
+ return fd_ >= 0;
+ }
+
+ // Returns the Posix file descriptor for the test file, or -1
+ // If IsOk() returns false. Note: on Windows, this always returns -1.
+ int GetFd() {
+ return fd_;
+ }
+
+private:
+ void Init(const char* test_prefix) {
+ fd_ = -1;
+ char path_templ[PATH_MAX];
+ int ret = snprintf(path_templ, sizeof(path_templ),
+ TEMPDIR "/%s-unittest.XXXXXX",
+ test_prefix);
+ if (ret >= static_cast<int>(sizeof(path_templ)))
+ return;
+
+ fd_ = mkstemp(path_templ);
+ if (fd_ < 0)
+ return;
+
+ unlink(path_templ);
+ }
+
+ void WriteText(const char* text, size_t text_len) {
+ int r = HANDLE_EINTR(write(fd_, text, text_len));
+ if (r != static_cast<int>(text_len)) {
+ close(fd_);
+ fd_ = -1;
+ return;
+ }
+
+ lseek(fd_, 0, SEEK_SET);
+ }
+
+ int fd_;
+};
+
+#endif // !_WIN32
+
+} // namespace google_breakpad
+
+#endif // GOOGLE_BREAKPAD_COMMON_TESTS_AUTO_TESTFILE
diff --git a/src/google_breakpad/common/minidump_format.h b/src/google_breakpad/common/minidump_format.h
index 7fb8c5d5..f1a65cbf 100644
--- a/src/google_breakpad/common/minidump_format.h
+++ b/src/google_breakpad/common/minidump_format.h
@@ -552,18 +552,47 @@ typedef union {
uint32_t amd_extended_cpu_features; /* cpuid 0x80000001, ebx */
} x86_cpu_info;
struct {
+ uint32_t cpuid;
+ uint32_t elf_hwcaps; /* linux specific, 0 otherwise */
+ } arm_cpu_info;
+ struct {
uint64_t processor_features[2];
} other_cpu_info;
} MDCPUInformation; /* CPU_INFORMATION */
+/* For (MDCPUInformation).arm_cpu_info.elf_hwcaps.
+ * This matches the Linux kernel definitions from <asm/hwcaps.h> */
+typedef enum {
+ MD_CPU_ARM_ELF_HWCAP_SWP = (1 << 0),
+ MD_CPU_ARM_ELF_HWCAP_HALF = (1 << 1),
+ MD_CPU_ARM_ELF_HWCAP_THUMB = (1 << 2),
+ MD_CPU_ARM_ELF_HWCAP_26BIT = (1 << 3),
+ MD_CPU_ARM_ELF_HWCAP_FAST_MULT = (1 << 4),
+ MD_CPU_ARM_ELF_HWCAP_FPA = (1 << 5),
+ MD_CPU_ARM_ELF_HWCAP_VFP = (1 << 6),
+ MD_CPU_ARM_ELF_HWCAP_EDSP = (1 << 7),
+ MD_CPU_ARM_ELF_HWCAP_JAVA = (1 << 8),
+ MD_CPU_ARM_ELF_HWCAP_IWMMXT = (1 << 9),
+ MD_CPU_ARM_ELF_HWCAP_CRUNCH = (1 << 10),
+ MD_CPU_ARM_ELF_HWCAP_THUMBEE = (1 << 11),
+ MD_CPU_ARM_ELF_HWCAP_NEON = (1 << 12),
+ MD_CPU_ARM_ELF_HWCAP_VFPv3 = (1 << 13),
+ MD_CPU_ARM_ELF_HWCAP_VFPv3D16 = (1 << 14),
+ MD_CPU_ARM_ELF_HWCAP_TLS = (1 << 15),
+ MD_CPU_ARM_ELF_HWCAP_VFPv4 = (1 << 16),
+ MD_CPU_ARM_ELF_HWCAP_IDIVA = (1 << 17),
+ MD_CPU_ARM_ELF_HWCAP_IDIVT = (1 << 18),
+} MDCPUInformationARMElfHwCaps;
typedef struct {
/* The next 3 fields and numberOfProcessors are from the SYSTEM_INFO
* structure as returned by GetSystemInfo */
uint16_t processor_architecture;
uint16_t processor_level; /* x86: 5 = 586, 6 = 686, ... */
+ /* ARM: 6 = ARMv6, 7 = ARMv7 ... */
uint16_t processor_revision; /* x86: 0xMMSS, where MM=model,
* SS=stepping */
+ /* ARM: 0 */
uint8_t number_of_processors;
uint8_t product_type; /* Windows: VER_NT_* from WinNT.h */
@@ -626,7 +655,6 @@ typedef enum {
MD_OS_ANDROID = 0x8203 /* Android */
} MDOSPlatform;
-
typedef struct {
uint32_t size_of_info; /* Length of entire MDRawMiscInfo structure. */
uint32_t flags1;
diff --git a/src/processor/minidump_processor.cc b/src/processor/minidump_processor.cc
index afe15ca6..2d14cb37 100644
--- a/src/processor/minidump_processor.cc
+++ b/src/processor/minidump_processor.cc
@@ -305,6 +305,128 @@ static const MDRawSystemInfo* GetSystemInfo(Minidump *dump,
return minidump_system_info->system_info();
}
+// Extract CPU info string from ARM-specific MDRawSystemInfo structure.
+// raw_info: pointer to source MDRawSystemInfo.
+// cpu_info: address of target string, cpu info text will be appended to it.
+static void GetARMCpuInfo(const MDRawSystemInfo* raw_info,
+ std::string* cpu_info) {
+ assert(raw_info != NULL && cpu_info != NULL);
+
+ // Write ARM architecture version.
+ char cpu_string[32];
+ snprintf(cpu_string, sizeof(cpu_string), "ARMv%d",
+ raw_info->processor_level);
+ cpu_info->append(cpu_string);
+
+ // There is no good list of implementer id values, but the following
+ // pages provide some help:
+ // http://comments.gmane.org/gmane.linux.linaro.devel/6903
+ // http://forum.xda-developers.com/archive/index.php/t-480226.html
+ const struct {
+ uint32_t id;
+ const char* name;
+ } vendors[] = {
+ { 0x41, "ARM" },
+ { 0x51, "Qualcomm" },
+ { 0x56, "Marvell" },
+ { 0x69, "Intel/Marvell" },
+ };
+ const struct {
+ uint32_t id;
+ const char* name;
+ } parts[] = {
+ { 0x4100c050, "Cortex-A5" },
+ { 0x4100c080, "Cortex-A8" },
+ { 0x4100c090, "Cortex-A9" },
+ { 0x4100c0f0, "Cortex-A15" },
+ { 0x4100c140, "Cortex-R4" },
+ { 0x4100c150, "Cortex-R5" },
+ { 0x4100b360, "ARM1136" },
+ { 0x4100b560, "ARM1156" },
+ { 0x4100b760, "ARM1176" },
+ { 0x4100b020, "ARM11-MPCore" },
+ { 0x41009260, "ARM926" },
+ { 0x41009460, "ARM946" },
+ { 0x41009660, "ARM966" },
+ { 0x510006f0, "Krait" },
+ { 0x510000f0, "Scorpion" },
+ };
+
+ const struct {
+ uint32_t hwcap;
+ const char* name;
+ } features[] = {
+ { MD_CPU_ARM_ELF_HWCAP_SWP, "swp" },
+ { MD_CPU_ARM_ELF_HWCAP_HALF, "half" },
+ { MD_CPU_ARM_ELF_HWCAP_THUMB, "thumb" },
+ { MD_CPU_ARM_ELF_HWCAP_26BIT, "26bit" },
+ { MD_CPU_ARM_ELF_HWCAP_FAST_MULT, "fastmult" },
+ { MD_CPU_ARM_ELF_HWCAP_FPA, "fpa" },
+ { MD_CPU_ARM_ELF_HWCAP_VFP, "vfpv2" },
+ { MD_CPU_ARM_ELF_HWCAP_EDSP, "edsp" },
+ { MD_CPU_ARM_ELF_HWCAP_JAVA, "java" },
+ { MD_CPU_ARM_ELF_HWCAP_IWMMXT, "iwmmxt" },
+ { MD_CPU_ARM_ELF_HWCAP_CRUNCH, "crunch" },
+ { MD_CPU_ARM_ELF_HWCAP_THUMBEE, "thumbee" },
+ { MD_CPU_ARM_ELF_HWCAP_NEON, "neon" },
+ { MD_CPU_ARM_ELF_HWCAP_VFPv3, "vfpv3" },
+ { MD_CPU_ARM_ELF_HWCAP_VFPv3D16, "vfpv3d16" },
+ { MD_CPU_ARM_ELF_HWCAP_TLS, "tls" },
+ { MD_CPU_ARM_ELF_HWCAP_VFPv4, "vfpv4" },
+ { MD_CPU_ARM_ELF_HWCAP_IDIVA, "idiva" },
+ { MD_CPU_ARM_ELF_HWCAP_IDIVT, "idivt" },
+ };
+
+ uint32_t cpuid = raw_info->cpu.arm_cpu_info.cpuid;
+ if (cpuid != 0) {
+ // Extract vendor name from CPUID
+ const char* vendor = NULL;
+ uint32_t vendor_id = (cpuid >> 24) & 0xff;
+ for (size_t i = 0; i < sizeof(vendors)/sizeof(vendors[0]); ++i) {
+ if (vendors[i].id == vendor_id) {
+ vendor = vendors[i].name;
+ break;
+ }
+ }
+ cpu_info->append(" ");
+ if (vendor) {
+ cpu_info->append(vendor);
+ } else {
+ snprintf(cpu_string, sizeof(cpu_string), "vendor(0x%x)", vendor_id);
+ cpu_info->append(cpu_string);
+ }
+
+ // Extract part name from CPUID
+ uint32_t part_id = (cpuid & 0xff00fff0);
+ const char* part = NULL;
+ for (size_t i = 0; i < sizeof(parts)/sizeof(parts[0]); ++i) {
+ if (parts[i].id == part_id) {
+ part = parts[i].name;
+ break;
+ }
+ }
+ cpu_info->append(" ");
+ if (part != NULL) {
+ cpu_info->append(part);
+ } else {
+ snprintf(cpu_string, sizeof(cpu_string), "part(0x%x)", part_id);
+ cpu_info->append(cpu_string);
+ }
+ }
+ uint32_t elf_hwcaps = raw_info->cpu.arm_cpu_info.elf_hwcaps;
+ if (elf_hwcaps != 0) {
+ cpu_info->append(" features: ");
+ const char* comma = "";
+ for (size_t i = 0; i < sizeof(features)/sizeof(features[0]); ++i) {
+ if (elf_hwcaps & features[i].hwcap) {
+ cpu_info->append(comma);
+ cpu_info->append(features[i].name);
+ comma = ",";
+ }
+ }
+ }
+}
+
// static
bool MinidumpProcessor::GetCPUInfo(Minidump *dump, SystemInfo *info) {
assert(dump);
@@ -359,6 +481,7 @@ bool MinidumpProcessor::GetCPUInfo(Minidump *dump, SystemInfo *info) {
case MD_CPU_ARCHITECTURE_ARM: {
info->cpu = "arm";
+ GetARMCpuInfo(raw_system_info, &info->cpu_info);
break;
}
diff --git a/src/processor/synth_minidump.cc b/src/processor/synth_minidump.cc
index 377368ac..0940a357 100644
--- a/src/processor/synth_minidump.cc
+++ b/src/processor/synth_minidump.cc
@@ -77,6 +77,9 @@ SystemInfo::SystemInfo(const Dump &dump,
D32(system_info.cpu.x86_cpu_info.version_information);
D32(system_info.cpu.x86_cpu_info.feature_information);
D32(system_info.cpu.x86_cpu_info.amd_extended_cpu_features);
+ } else if (system_info.processor_architecture == MD_CPU_ARCHITECTURE_ARM) {
+ D32(system_info.cpu.arm_cpu_info.cpuid);
+ D32(system_info.cpu.arm_cpu_info.elf_hwcaps);
} else {
D64(system_info.cpu.other_cpu_info.processor_features[0]);
D64(system_info.cpu.other_cpu_info.processor_features[1]);