xref: /aosp_15_r20/external/cronet/net/tools/dump_cache/dump_files.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Performs basic inspection of the disk cache files with minimal disruption
6 // to the actual files (they still may change if an error is detected on the
7 // files).
8 
9 #include "net/tools/dump_cache/dump_files.h"
10 
11 #include <stdio.h>
12 
13 #include <memory>
14 #include <set>
15 #include <string>
16 
17 #include "base/command_line.h"
18 #include "base/files/file.h"
19 #include "base/files/file_enumerator.h"
20 #include "base/files/file_util.h"
21 #include "base/format_macros.h"
22 #include "base/i18n/time_formatting.h"
23 #include "base/message_loop/message_pump_type.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/task/single_thread_task_executor.h"
27 #include "base/time/time.h"
28 #include "net/disk_cache/blockfile/block_files.h"
29 #include "net/disk_cache/blockfile/disk_format.h"
30 #include "net/disk_cache/blockfile/mapped_file.h"
31 #include "net/disk_cache/blockfile/stats.h"
32 #include "net/disk_cache/blockfile/storage_block-inl.h"
33 #include "net/disk_cache/blockfile/storage_block.h"
34 #include "net/url_request/view_cache_helper.h"
35 
36 namespace {
37 
38 const base::FilePath::CharType kIndexName[] = FILE_PATH_LITERAL("index");
39 
40 // Reads the |header_size| bytes from the beginning of file |name|.
ReadHeader(const base::FilePath & name,char * header,int header_size)41 bool ReadHeader(const base::FilePath& name, char* header, int header_size) {
42   base::File file(name, base::File::FLAG_OPEN | base::File::FLAG_READ);
43   if (!file.IsValid()) {
44     printf("Unable to open file %s\n", name.MaybeAsASCII().c_str());
45     return false;
46   }
47 
48   int read = file.Read(0, header, header_size);
49   if (read != header_size) {
50     printf("Unable to read file %s\n", name.MaybeAsASCII().c_str());
51     return false;
52   }
53   return true;
54 }
55 
GetMajorVersionFromIndexFile(const base::FilePath & name)56 int GetMajorVersionFromIndexFile(const base::FilePath& name) {
57   disk_cache::IndexHeader header;
58   if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
59     return 0;
60   if (header.magic != disk_cache::kIndexMagic) {
61     return 0;
62   }
63   return header.version;
64 }
65 
GetMajorVersionFromBlockFile(const base::FilePath & name)66 int GetMajorVersionFromBlockFile(const base::FilePath& name) {
67   disk_cache::BlockFileHeader header;
68   if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) {
69     return 0;
70   }
71 
72   if (header.magic != disk_cache::kBlockMagic) {
73     return 0;
74   }
75 
76   return header.version;
77 }
78 
79 // Dumps the contents of the Stats record.
DumpStats(const base::FilePath & path,disk_cache::CacheAddr addr)80 void DumpStats(const base::FilePath& path, disk_cache::CacheAddr addr) {
81   // We need a task executor, although we really don't run any task.
82   base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
83 
84   disk_cache::BlockFiles block_files(path);
85   if (!block_files.Init(false)) {
86     printf("Unable to init block files\n");
87     return;
88   }
89 
90   disk_cache::Addr address(addr);
91   disk_cache::MappedFile* file = block_files.GetFile(address);
92   if (!file)
93     return;
94 
95   size_t length = (2 + disk_cache::Stats::kDataSizesLength) * sizeof(int32_t) +
96                   disk_cache::Stats::MAX_COUNTER * sizeof(int64_t);
97 
98   size_t offset = address.start_block() * address.BlockSize() +
99                   disk_cache::kBlockHeaderSize;
100 
101   auto buffer = std::make_unique<int32_t[]>(length);
102   if (!file->Read(buffer.get(), length, offset))
103     return;
104 
105   printf("Stats:\nSignatrure: 0x%x\n", buffer[0]);
106   printf("Total size: %d\n", buffer[1]);
107   for (int i = 0; i < disk_cache::Stats::kDataSizesLength; i++)
108     printf("Size(%d): %d\n", i, buffer[i + 2]);
109 
110   int64_t* counters = reinterpret_cast<int64_t*>(
111       buffer.get() + 2 + disk_cache::Stats::kDataSizesLength);
112   for (int i = 0; i < disk_cache::Stats::MAX_COUNTER; i++)
113     printf("Count(%d): %" PRId64 "\n", i, *counters++);
114   printf("-------------------------\n\n");
115 }
116 
117 // Dumps the contents of the Index-file header.
DumpIndexHeader(const base::FilePath & name,disk_cache::CacheAddr * stats_addr)118 void DumpIndexHeader(const base::FilePath& name,
119                      disk_cache::CacheAddr* stats_addr) {
120   disk_cache::IndexHeader header;
121   if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
122     return;
123 
124   printf("Index file:\n");
125   printf("magic: %x\n", header.magic);
126   printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
127   printf("entries: %d\n", header.num_entries);
128   printf("total bytes: %" PRId64 "\n", header.num_bytes);
129   printf("last file number: %d\n", header.last_file);
130   printf("current id: %d\n", header.this_id);
131   printf("table length: %d\n", header.table_len);
132   printf("last crash: %d\n", header.crash);
133   printf("experiment: %d\n", header.experiment);
134   printf("stats: %x\n", header.stats);
135   for (int i = 0; i < 5; i++) {
136     printf("head %d: 0x%x\n", i, header.lru.heads[i]);
137     printf("tail %d: 0x%x\n", i, header.lru.tails[i]);
138     printf("size %d: 0x%x\n", i, header.lru.sizes[i]);
139   }
140   printf("transaction: 0x%x\n", header.lru.transaction);
141   printf("operation: %d\n", header.lru.operation);
142   printf("operation list: %d\n", header.lru.operation_list);
143   printf("-------------------------\n\n");
144 
145   if (stats_addr)
146     *stats_addr = header.stats;
147 }
148 
149 // Dumps the contents of a block-file header.
DumpBlockHeader(const base::FilePath & name)150 void DumpBlockHeader(const base::FilePath& name) {
151   disk_cache::BlockFileHeader header;
152   if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
153     return;
154 
155   printf("Block file: %s\n", name.BaseName().MaybeAsASCII().c_str());
156   printf("magic: %x\n", header.magic);
157   printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
158   printf("file id: %d\n", header.this_file);
159   printf("next file id: %d\n", header.next_file);
160   printf("entry size: %d\n", header.entry_size);
161   printf("current entries: %d\n", header.num_entries);
162   printf("max entries: %d\n", header.max_entries);
163   printf("updating: %d\n", header.updating);
164   printf("empty sz 1: %d\n", header.empty[0]);
165   printf("empty sz 2: %d\n", header.empty[1]);
166   printf("empty sz 3: %d\n", header.empty[2]);
167   printf("empty sz 4: %d\n", header.empty[3]);
168   printf("user 0: 0x%x\n", header.user[0]);
169   printf("user 1: 0x%x\n", header.user[1]);
170   printf("user 2: 0x%x\n", header.user[2]);
171   printf("user 3: 0x%x\n", header.user[3]);
172   printf("-------------------------\n\n");
173 }
174 
175 // Simple class that interacts with the set of cache files.
176 class CacheDumper {
177  public:
CacheDumper(const base::FilePath & path)178   explicit CacheDumper(const base::FilePath& path)
179       : path_(path), block_files_(path) {}
180 
181   CacheDumper(const CacheDumper&) = delete;
182   CacheDumper& operator=(const CacheDumper&) = delete;
183 
184   bool Init();
185 
186   // Reads an entry from disk. Return false when all entries have been already
187   // returned.
188   bool GetEntry(disk_cache::EntryStore* entry, disk_cache::CacheAddr* addr);
189 
190   // Loads a specific block from the block files.
191   bool LoadEntry(disk_cache::CacheAddr addr, disk_cache::EntryStore* entry);
192   bool LoadRankings(disk_cache::CacheAddr addr,
193                     disk_cache::RankingsNode* rankings);
194 
195   // Appends the data store at |addr| to |out|.
196   bool HexDump(disk_cache::CacheAddr addr, std::string* out);
197 
198  private:
199   base::FilePath path_;
200   disk_cache::BlockFiles block_files_;
201   scoped_refptr<disk_cache::MappedFile> index_file_;
202   disk_cache::Index* index_ = nullptr;
203   int current_hash_ = 0;
204   disk_cache::CacheAddr next_addr_ = 0;
205   std::set<disk_cache::CacheAddr> dumped_entries_;
206 };
207 
Init()208 bool CacheDumper::Init() {
209   if (!block_files_.Init(false)) {
210     printf("Unable to init block files\n");
211     return false;
212   }
213 
214   base::FilePath index_name(path_.Append(kIndexName));
215   index_file_ = base::MakeRefCounted<disk_cache::MappedFile>();
216   index_ = reinterpret_cast<disk_cache::Index*>(
217       index_file_->Init(index_name, 0));
218   if (!index_) {
219     printf("Unable to map index\n");
220     return false;
221   }
222 
223   return true;
224 }
225 
GetEntry(disk_cache::EntryStore * entry,disk_cache::CacheAddr * addr)226 bool CacheDumper::GetEntry(disk_cache::EntryStore* entry,
227                            disk_cache::CacheAddr* addr) {
228   if (dumped_entries_.find(next_addr_) != dumped_entries_.end()) {
229     printf("Loop detected\n");
230     next_addr_ = 0;
231     current_hash_++;
232   }
233 
234   if (next_addr_) {
235     *addr = next_addr_;
236     if (LoadEntry(next_addr_, entry))
237       return true;
238 
239     printf("Unable to load entry at address 0x%x\n", next_addr_);
240     next_addr_ = 0;
241     current_hash_++;
242   }
243 
244   for (int i = current_hash_; i < index_->header.table_len; i++) {
245     // Yes, we'll crash if the table is shorter than expected, but only after
246     // dumping every entry that we can find.
247     if (index_->table[i]) {
248       current_hash_ = i;
249       *addr = index_->table[i];
250       if (LoadEntry(index_->table[i], entry))
251         return true;
252 
253       printf("Unable to load entry at address 0x%x\n", index_->table[i]);
254     }
255   }
256   return false;
257 }
258 
LoadEntry(disk_cache::CacheAddr addr,disk_cache::EntryStore * entry)259 bool CacheDumper::LoadEntry(disk_cache::CacheAddr addr,
260                             disk_cache::EntryStore* entry) {
261   disk_cache::Addr address(addr);
262   disk_cache::MappedFile* file = block_files_.GetFile(address);
263   if (!file)
264     return false;
265 
266   disk_cache::StorageBlock<disk_cache::EntryStore> entry_block(file, address);
267   if (!entry_block.Load())
268     return false;
269 
270   memcpy(entry, entry_block.Data(), sizeof(*entry));
271   if (!entry_block.VerifyHash())
272     printf("Self hash failed at 0x%x\n", addr);
273 
274   // Prepare for the next entry to load.
275   next_addr_ = entry->next;
276   if (next_addr_) {
277     dumped_entries_.insert(addr);
278   } else {
279     current_hash_++;
280     dumped_entries_.clear();
281   }
282   return true;
283 }
284 
LoadRankings(disk_cache::CacheAddr addr,disk_cache::RankingsNode * rankings)285 bool CacheDumper::LoadRankings(disk_cache::CacheAddr addr,
286                                disk_cache::RankingsNode* rankings) {
287   disk_cache::Addr address(addr);
288   if (address.file_type() != disk_cache::RANKINGS)
289     return false;
290 
291   disk_cache::MappedFile* file = block_files_.GetFile(address);
292   if (!file)
293     return false;
294 
295   disk_cache::StorageBlock<disk_cache::RankingsNode> rank_block(file, address);
296   if (!rank_block.Load())
297     return false;
298 
299   if (!rank_block.VerifyHash())
300     printf("Self hash failed at 0x%x\n", addr);
301 
302   memcpy(rankings, rank_block.Data(), sizeof(*rankings));
303   return true;
304 }
305 
HexDump(disk_cache::CacheAddr addr,std::string * out)306 bool CacheDumper::HexDump(disk_cache::CacheAddr addr, std::string* out) {
307   disk_cache::Addr address(addr);
308   disk_cache::MappedFile* file = block_files_.GetFile(address);
309   if (!file)
310     return false;
311 
312   size_t size = address.num_blocks() * address.BlockSize();
313   auto buffer = std::make_unique<char[]>(size);
314 
315   size_t offset = address.start_block() * address.BlockSize() +
316                   disk_cache::kBlockHeaderSize;
317   if (!file->Read(buffer.get(), size, offset))
318     return false;
319 
320   base::StringAppendF(out, "0x%x:\n", addr);
321   net::ViewCacheHelper::HexDump(buffer.get(), size, out);
322   return true;
323 }
324 
ToLocalTime(int64_t time_us)325 std::string ToLocalTime(int64_t time_us) {
326   return base::UnlocalizedTimeFormatWithPattern(
327       base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(time_us)),
328       "y/M/d H:m:s.S");
329 }
330 
DumpEntry(disk_cache::CacheAddr addr,const disk_cache::EntryStore & entry,bool verbose)331 void DumpEntry(disk_cache::CacheAddr addr,
332                const disk_cache::EntryStore& entry,
333                bool verbose) {
334   std::string key;
335   static bool full_key =
336       base::CommandLine::ForCurrentProcess()->HasSwitch("full-key");
337   if (!entry.long_key) {
338     key = std::string(entry.key, std::min(static_cast<size_t>(entry.key_len),
339                                           sizeof(entry.key)));
340     if (entry.key_len > 90 && !full_key)
341       key.resize(90);
342   }
343 
344   printf("Entry at 0x%x\n", addr);
345   printf("rankings: 0x%x\n", entry.rankings_node);
346   printf("key length: %d\n", entry.key_len);
347   printf("key: \"%s\"\n", key.c_str());
348 
349   if (verbose) {
350     printf("key addr: 0x%x\n", entry.long_key);
351     printf("hash: 0x%x\n", entry.hash);
352     printf("next entry: 0x%x\n", entry.next);
353     printf("reuse count: %d\n", entry.reuse_count);
354     printf("refetch count: %d\n", entry.refetch_count);
355     printf("state: %d\n", entry.state);
356     printf("creation: %s\n", ToLocalTime(entry.creation_time).c_str());
357     for (int i = 0; i < 4; i++) {
358       printf("data size %d: %d\n", i, entry.data_size[i]);
359       printf("data addr %d: 0x%x\n", i, entry.data_addr[i]);
360     }
361     printf("----------\n\n");
362   }
363 }
364 
DumpRankings(disk_cache::CacheAddr addr,const disk_cache::RankingsNode & rankings,bool verbose)365 void DumpRankings(disk_cache::CacheAddr addr,
366                   const disk_cache::RankingsNode& rankings,
367                   bool verbose) {
368   printf("Rankings at 0x%x\n", addr);
369   printf("next: 0x%x\n", rankings.next);
370   printf("prev: 0x%x\n", rankings.prev);
371   printf("entry: 0x%x\n", rankings.contents);
372 
373   if (verbose) {
374     printf("dirty: %d\n", rankings.dirty);
375     if (rankings.last_used != rankings.last_modified)
376       printf("used: %s\n", ToLocalTime(rankings.last_used).c_str());
377     printf("modified: %s\n", ToLocalTime(rankings.last_modified).c_str());
378     printf("hash: 0x%x\n", rankings.self_hash);
379     printf("----------\n\n");
380   } else {
381     printf("\n");
382   }
383 }
384 
PrintCSVHeader()385 void PrintCSVHeader() {
386   printf(
387       "entry,rankings,next,prev,rank-contents,chain,reuse,key,"
388       "d0,d1,d2,d3\n");
389 }
390 
DumpCSV(disk_cache::CacheAddr addr,const disk_cache::EntryStore & entry,const disk_cache::RankingsNode & rankings)391 void DumpCSV(disk_cache::CacheAddr addr,
392              const disk_cache::EntryStore& entry,
393              const disk_cache::RankingsNode& rankings) {
394   printf("0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n", addr,
395          entry.rankings_node, rankings.next, rankings.prev, rankings.contents,
396          entry.next, entry.reuse_count, entry.long_key, entry.data_addr[0],
397          entry.data_addr[1], entry.data_addr[2], entry.data_addr[3]);
398 
399   if (addr != rankings.contents)
400     printf("Broken entry\n");
401 }
402 
CanDump(disk_cache::CacheAddr addr)403 bool CanDump(disk_cache::CacheAddr addr) {
404   disk_cache::Addr address(addr);
405   return address.is_initialized() && address.is_block_file();
406 }
407 
408 }  // namespace.
409 
410 // -----------------------------------------------------------------------
411 
CheckFileVersion(const base::FilePath & input_path)412 bool CheckFileVersion(const base::FilePath& input_path) {
413   base::FilePath index_name(input_path.Append(kIndexName));
414 
415   int index_version = GetMajorVersionFromIndexFile(index_name);
416   if (!index_version || index_version != disk_cache::kVersion3_0) {
417     return false;
418   }
419 
420   constexpr int kCurrentBlockVersion = disk_cache::kBlockVersion2;
421   for (int i = 0; i < disk_cache::kFirstAdditionalBlockFile; i++) {
422     std::string data_name = "data_" + base::NumberToString(i);
423     auto data_path = input_path.AppendASCII(data_name);
424     int block_version = GetMajorVersionFromBlockFile(data_path);
425     if (!block_version || block_version != kCurrentBlockVersion) {
426       return false;
427     }
428   }
429   return true;
430 }
431 
432 // Dumps the headers of all files.
DumpHeaders(const base::FilePath & input_path)433 int DumpHeaders(const base::FilePath& input_path) {
434   base::FilePath index_name(input_path.Append(kIndexName));
435   disk_cache::CacheAddr stats_addr = 0;
436   DumpIndexHeader(index_name, &stats_addr);
437 
438   base::FileEnumerator iter(input_path, false,
439                             base::FileEnumerator::FILES,
440                             FILE_PATH_LITERAL("data_*"));
441   for (base::FilePath file = iter.Next(); !file.empty(); file = iter.Next())
442     DumpBlockHeader(file);
443 
444   DumpStats(input_path, stats_addr);
445   return 0;
446 }
447 
448 // Dumps all entries from the cache.
DumpContents(const base::FilePath & input_path)449 int DumpContents(const base::FilePath& input_path) {
450   bool print_csv = base::CommandLine::ForCurrentProcess()->HasSwitch("csv");
451   if (!print_csv)
452     DumpIndexHeader(input_path.Append(kIndexName), nullptr);
453 
454   // We need a task executor, although we really don't run any task.
455   base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
456   CacheDumper dumper(input_path);
457   if (!dumper.Init())
458     return -1;
459 
460   if (print_csv)
461     PrintCSVHeader();
462 
463   disk_cache::EntryStore entry;
464   disk_cache::CacheAddr addr;
465   bool verbose = base::CommandLine::ForCurrentProcess()->HasSwitch("v");
466   while (dumper.GetEntry(&entry, &addr)) {
467     if (!print_csv)
468       DumpEntry(addr, entry, verbose);
469     disk_cache::RankingsNode rankings;
470     if (!dumper.LoadRankings(entry.rankings_node, &rankings))
471       continue;
472 
473     if (print_csv)
474       DumpCSV(addr, entry, rankings);
475     else
476       DumpRankings(entry.rankings_node, rankings, verbose);
477   }
478 
479   printf("Done.\n");
480 
481   return 0;
482 }
483 
DumpLists(const base::FilePath & input_path)484 int DumpLists(const base::FilePath& input_path) {
485   base::FilePath index_name(input_path.Append(kIndexName));
486   disk_cache::IndexHeader header;
487   if (!ReadHeader(index_name, reinterpret_cast<char*>(&header), sizeof(header)))
488     return -1;
489 
490   // We need a task executor, although we really don't run any task.
491   base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
492   CacheDumper dumper(input_path);
493   if (!dumper.Init())
494     return -1;
495 
496   printf("list, addr,      next,       prev,       entry\n");
497 
498   const int kMaxLength = 1 * 1000 * 1000;
499   for (int i = 0; i < 5; i++) {
500     int32_t size = header.lru.sizes[i];
501     if (size < 0 || size > kMaxLength) {
502       printf("Wrong size %d\n", size);
503     }
504 
505     disk_cache::CacheAddr addr = header.lru.tails[i];
506     int count = 0;
507     while (addr) {
508       count++;
509       disk_cache::RankingsNode rankings;
510       if (!dumper.LoadRankings(addr, &rankings)) {
511         printf("Failed to load node at 0x%x\n", addr);
512         break;
513       }
514       printf("%d, 0x%x, 0x%x, 0x%x, 0x%x\n", i, addr, rankings.next,
515              rankings.prev, rankings.contents);
516 
517       if (rankings.prev == addr)
518         break;
519 
520       addr = rankings.prev;
521     }
522     printf("%d nodes found, %d reported\n", count, header.lru.sizes[i]);
523   }
524 
525   printf("Done.\n");
526   return 0;
527 }
528 
DumpEntryAt(const base::FilePath & input_path,const std::string & at)529 int DumpEntryAt(const base::FilePath& input_path, const std::string& at) {
530   disk_cache::CacheAddr addr;
531   if (!base::HexStringToUInt(at, &addr))
532     return -1;
533 
534   if (!CanDump(addr))
535     return -1;
536 
537   base::FilePath index_name(input_path.Append(kIndexName));
538   disk_cache::IndexHeader header;
539   if (!ReadHeader(index_name, reinterpret_cast<char*>(&header), sizeof(header)))
540     return -1;
541 
542   // We need a task executor, although we really don't run any task.
543   base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
544   CacheDumper dumper(input_path);
545   if (!dumper.Init())
546     return -1;
547 
548   disk_cache::CacheAddr entry_addr = 0;
549   disk_cache::CacheAddr rankings_addr = 0;
550   disk_cache::Addr address(addr);
551 
552   disk_cache::RankingsNode rankings;
553   if (address.file_type() == disk_cache::RANKINGS) {
554     if (dumper.LoadRankings(addr, &rankings)) {
555       rankings_addr = addr;
556       addr = rankings.contents;
557       address = disk_cache::Addr(addr);
558     }
559   }
560 
561   disk_cache::EntryStore entry = {};
562   if (address.file_type() == disk_cache::BLOCK_256 &&
563       dumper.LoadEntry(addr, &entry)) {
564     entry_addr = addr;
565     DumpEntry(addr, entry, true);
566     if (!rankings_addr && dumper.LoadRankings(entry.rankings_node, &rankings))
567       rankings_addr = entry.rankings_node;
568   }
569 
570   bool verbose = base::CommandLine::ForCurrentProcess()->HasSwitch("v");
571 
572   std::string hex_dump;
573   if (!rankings_addr || verbose)
574     dumper.HexDump(addr, &hex_dump);
575 
576   if (rankings_addr)
577     DumpRankings(rankings_addr, rankings, true);
578 
579   if (entry_addr && verbose) {
580     if (entry.long_key && CanDump(entry.long_key))
581       dumper.HexDump(entry.long_key, &hex_dump);
582 
583     for (disk_cache::CacheAddr data_addr : entry.data_addr) {
584       if (data_addr && CanDump(data_addr))
585         dumper.HexDump(data_addr, &hex_dump);
586     }
587   }
588 
589   printf("%s\n", hex_dump.c_str());
590   printf("Done.\n");
591   return 0;
592 }
593 
DumpAllocation(const base::FilePath & file)594 int DumpAllocation(const base::FilePath& file) {
595   disk_cache::BlockFileHeader header;
596   if (!ReadHeader(file, reinterpret_cast<char*>(&header), sizeof(header)))
597     return -1;
598 
599   std::string out;
600   net::ViewCacheHelper::HexDump(reinterpret_cast<char*>(&header.allocation_map),
601                                 sizeof(header.allocation_map), &out);
602   printf("%s\n", out.c_str());
603   return 0;
604 }
605