diff options
author | digit@chromium.org <digit@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2013-04-24 10:06:14 +0000 |
---|---|---|
committer | digit@chromium.org <digit@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2013-04-24 10:06:14 +0000 |
commit | 593eff42ca4a5c38835577cf83152f356d262414 (patch) | |
tree | 6102e9d82b8f109d9aab1d52bdff2e4d75fcb9fe /src/client/linux/minidump_writer/minidump_writer.cc | |
parent | Cleanup: Remove duplicate wording in license headers. (diff) | |
download | breakpad-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.cc | 283 |
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); |