aboutsummaryrefslogtreecommitdiff
path: root/src/client/linux/minidump_writer/minidump_writer.cc
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/client/linux/minidump_writer/minidump_writer.cc
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/client/linux/minidump_writer/minidump_writer.cc')
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc283
1 files changed, 221 insertions, 62 deletions
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);