aboutsummaryrefslogtreecommitdiff
path: root/src/client/linux/minidump_writer
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/linux/minidump_writer')
-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
6 files changed, 897 insertions, 126 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));
+}