blob: 8b1e60f92fe0c6f18d61dbd3a80d329bc770bba0 [file] [log] [blame]
// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "syzygy/pdb/pdb_writer.h"
#include "base/logging.h"
#include "syzygy/pdb/pdb_constants.h"
#include "syzygy/pdb/pdb_data.h"
namespace pdb {
namespace {
const uint32 kZeroBuffer[kPdbPageSize] = { 0 };
// A byte-based bitmap for keeping track of free pages in an MSF/PDB file.
// TODO(chrisha): Promote this to its own file and unittest it when we make
// a library for MSF-specific stuff.
class FreePageBitMap {
public:
FreePageBitMap() : page_count_(0) {
}
void SetPageCount(uint32 page_count) {
page_count_ = page_count;
data_.resize((page_count + 7) / 8);
// Double check our invariant.
DCHECK_LE(page_count, data_.size() * 8);
DCHECK_LE(page_count / 8, data_.size());
}
void SetBit(uint32 page_index, bool free) {
DCHECK_LT(page_index, page_count_);
uint32 byte = page_index / 8;
uint32 bit = page_index % 8;
uint8 bitmask = 1 << bit;
DCHECK_LT(byte, data_.size());
if (free) {
data_[byte] |= bitmask;
} else {
data_[byte] &= ~bitmask;
}
}
void SetFree(uint32 page_index) { SetBit(page_index, true); }
void SetUsed(uint32 page_index) { SetBit(page_index, false); }
// TODO(chrisha): Make this an invariant of the class and move the logic
// to SetPageCount. This involves both clearing and setting bits in that
// case.
void Finalize() {
uint32 bits_left = data_.size() * 8 - page_count_;
DCHECK_LE(bits_left, 7u);
// This leaves the top |bits_left| bits set.
uint8 bitmask = ~(0xFF >> bits_left);
// Mark any bits as free beyond those specifically allocated.
data_.back() |= bitmask;
}
const std::vector<uint8>& data() const { return data_; }
private:
std::vector<uint8> data_;
uint32 page_count_;
};
// A light-weight wrapper that allows a previously allocated buffer to be read
// as a PdbStream.
class ReadOnlyPdbStream : public PdbStream {
public:
ReadOnlyPdbStream(const void* data, size_t bytes)
: PdbStream(bytes), data_(data) {
}
virtual bool ReadBytes(
void* dest, size_t count, size_t* bytes_read) OVERRIDE {
DCHECK(dest != NULL);
DCHECK(bytes_read != NULL);
bool result = true;
size_t bytes_to_read = count;
size_t bytes_left = length() - pos();
if (bytes_left < bytes_to_read) {
bytes_to_read = bytes_left;
result = false;
}
::memcpy(dest, reinterpret_cast<const uint8*>(data_) + pos(),
bytes_to_read);
Seek(pos() + bytes_to_read);
*bytes_read = bytes_to_read;
return result;
}
private:
const void* data_;
};
// Appends a page to the provided file, adding the written page ID to the vector
// of @p pages_written, and incrementing the total @P page_count. This will
// occasionally cause more than one single page to be written to the output,
// thus advancing @p page_count by more than one (when reserving pages for the
// free page map). It is expected that @data be kPdbPageSize in length.
// @pre the file is expected to be positioned at @p *page_count * kPdbPageSize
// when entering this routine
// @post the file will be positioned at @p *page_count * kPdbPageSiz when
// exiting this routine.
bool AppendPage(const void* data,
std::vector<uint32>* pages_written,
uint32* page_count,
FILE* file) {
DCHECK(data != NULL);
DCHECK(pages_written != NULL);
DCHECK(page_count != NULL);
DCHECK(file != NULL);
uint32 local_page_count = *page_count;
// The file is written sequentially, so it will already be pointing to
// the appropriate spot.
DCHECK_EQ(local_page_count * kPdbPageSize,
static_cast<uint32>(::ftell(file)));
// If we're due to allocate pages for the free page map, then do so.
if (((*page_count) % kPdbPageSize) == 1) {
if (::fwrite(kZeroBuffer, 1, kPdbPageSize, file) != kPdbPageSize ||
::fwrite(kZeroBuffer, 1, kPdbPageSize, file) != kPdbPageSize) {
LOG(ERROR) << "Failed to allocate free page map pages.";
return false;
}
local_page_count += 2;
}
// Write the page itself.
if (::fwrite(data, 1, kPdbPageSize, file) != kPdbPageSize) {
LOG(ERROR) << "Failed to write page " << *page_count << ".";
return false;
}
pages_written->push_back(local_page_count);
++local_page_count;
DCHECK_EQ(local_page_count * kPdbPageSize,
static_cast<uint32>(::ftell(file)));
*page_count = local_page_count;
return true;
}
bool WriteFreePageBitMap(const FreePageBitMap& free, FILE* file) {
DCHECK(file != NULL);
const uint8* data = free.data().data();
size_t bytes_left = free.data().size();
size_t page_index = 1;
size_t bytes_to_write = kPdbPageSize;
while (true) {
if (::fseek(file, page_index * kPdbPageSize, SEEK_SET) != 0) {
LOG(ERROR) << "Failed to seek to page " << page_index << ".";
return false;
}
bytes_to_write = kPdbPageSize;
if (bytes_left < bytes_to_write)
bytes_to_write = bytes_left;
if (::fwrite(data, 1, bytes_to_write, file) != bytes_to_write) {
LOG(ERROR) << "Failed to write page " << page_index
<< " of free page map.";
return false;
}
bytes_left -= bytes_to_write;
if (bytes_left == 0)
break;
data += bytes_to_write;
page_index += kPdbPageSize;
}
// Was the last write partial? If so, we need to flush out the rest of the
// free page map with ones (0xFF bytes).
if (bytes_to_write < kPdbPageSize) {
// Create a vector of bytes with all the bits set.
std::vector<uint8> ones(kPdbPageSize - bytes_to_write, 0xFF);
if (::fwrite(ones.data(), 1, ones.size(), file) != ones.size()) {
LOG(ERROR) << "Failed to pad page " << page_index
<< " of free page map.";
return false;
}
}
return true;
}
} // namespace
PdbWriter::PdbWriter() {
}
PdbWriter::~PdbWriter() {
}
bool PdbWriter::Write(const base::FilePath& pdb_path, const PdbFile& pdb_file) {
file_.reset(base::OpenFile(pdb_path, "wb"));
if (!file_.get()) {
LOG(ERROR) << "Failed to create '" << pdb_path.value() << "'.";
return false;
}
// Initialize the directory with stream count and lengths.
std::vector<uint32> directory;
directory.push_back(pdb_file.StreamCount());
for (size_t i = 0; i < pdb_file.StreamCount(); ++i) {
// Null streams have an implicit zero length.
PdbStream* stream = pdb_file.GetStream(i);
if (stream == NULL)
directory.push_back(0);
else
directory.push_back(stream->length());
}
// Reserve space for the header page, the two free page map pages, and a
// fourth empty page. The fourth empty page doesn't appear to be strictly
// necessary but MSF/PDB files produced by MS tools always contain it.
uint32 page_count = 4;
for (uint32 i = 0; i < page_count; ++i) {
if (::fwrite(kZeroBuffer, 1, kPdbPageSize, file_.get()) != kPdbPageSize) {
LOG(ERROR) << "Failed to allocate preamble page.";
return false;
}
}
// Append all the streams after the preamble and build the directory while
// we're at it. We keep track of which pages host stream 0 for some free page
// map bookkeeping later on.
size_t stream0_start = directory.size();
size_t stream0_end = 0;
for (size_t i = 0; i < pdb_file.StreamCount(); ++i) {
if (i == 1)
stream0_end = directory.size();
// Null streams are treated as empty streams.
PdbStream* stream = pdb_file.GetStream(i);
if (stream == NULL || stream->length() == 0)
continue;
// Write the stream, updating the directory and page index. This routine
// takes care of making room for the free page map pages.
if (!AppendStream(stream, &directory, &page_count)) {
LOG(ERROR) << "Failed to write stream " << i << ".";
return false;
}
}
DCHECK_LE(stream0_start, stream0_end);
// Write the directory, and keep track of the pages it is written to.
std::vector<uint32> directory_pages;
scoped_refptr<PdbStream> directory_stream(new ReadOnlyPdbStream(
directory.data(), sizeof(directory[0]) * directory.size()));
if (!AppendStream(directory_stream.get(), &directory_pages, &page_count)) {
LOG(ERROR) << "Failed to write directory.";
return false;
}
// Write the root directory, and keep track of the pages it is written to.
// These will in turn go into the header root directory pointers.
std::vector<uint32> root_directory_pages;
scoped_refptr<PdbStream> root_directory_stream(new ReadOnlyPdbStream(
directory_pages.data(),
sizeof(directory_pages[0]) * directory_pages.size()));
if (!AppendStream(root_directory_stream.get(), &root_directory_pages,
&page_count)) {
LOG(ERROR) << "Failed to write root directory.";
return false;
}
// Write the header.
if (!WriteHeader(root_directory_pages,
sizeof(directory[0]) * directory.size(),
page_count)) {
LOG(ERROR) << "Failed to write PDB header.";
return false;
}
// Initialize the free page bit map. The pages corresponding to stream 0 are
// always marked as free, as well as page 3 which we allocated in the
// preamble.
FreePageBitMap free;
free.SetPageCount(page_count);
free.SetFree(3);
for (size_t i = stream0_start; i < stream0_end; ++i)
free.SetFree(directory[i]);
free.Finalize();
if (!WriteFreePageBitMap(free, file_.get())) {
LOG(ERROR) << "Failed to write free page bitmap.";
return false;
}
// On success we want the file to be closed right away.
file_.reset();
return true;
}
bool PdbWriter::AppendStream(PdbStream* stream,
std::vector<uint32>* pages_written,
uint32* page_count) {
DCHECK(stream != NULL);
DCHECK(pages_written != NULL);
DCHECK(page_count != NULL);
#ifndef NDEBUG
size_t old_pages_written_count = pages_written->size();
size_t old_page_count = *page_count;
#endif
// Write the stream page by page.
stream->Seek(0);
uint8 buffer[kPdbPageSize] = { 0 };
size_t bytes_left = stream->length();
while (bytes_left) {
size_t bytes_to_read = sizeof(buffer);
if (bytes_to_read > bytes_left) {
bytes_to_read = bytes_left;
// If we're only reading a partial buffer then pad the end of it with
// zeros.
::memset(buffer + bytes_to_read, 0, sizeof(buffer) - bytes_to_read);
}
// Read the buffer from the stream.
size_t bytes_read = 0;
if (!stream->ReadBytes(buffer, bytes_to_read, &bytes_read) ||
bytes_read != bytes_to_read) {
size_t offset = stream->length() - bytes_left;
LOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset "
<< offset << " of PDB stream.";
return false;
}
if (!AppendPage(buffer, pages_written, page_count, file_.get()))
return false;
bytes_left -= bytes_read;
}
DCHECK_EQ(0u, bytes_left);
#ifndef NDEBUG
size_t expected_pages_written = (stream->length() + kPdbPageSize - 1) /
kPdbPageSize;
DCHECK_EQ(old_pages_written_count + expected_pages_written,
pages_written->size());
// We can't say anything about |page_count| as AppendPage occasionally snags
// extra pages for the free page map.
#endif
return true;
}
bool PdbWriter::WriteHeader(const std::vector<uint32>& root_directory_pages,
size_t directory_size,
uint32 page_count) {
VLOG(1) << "Writing MSF Header ...";
PdbHeader header = { 0 };
// Make sure the root directory pointers won't overflow.
if (root_directory_pages.size() > arraysize(header.root_pages)) {
LOG(ERROR) << "Too many root directory pages for header ("
<< root_directory_pages.size() << " > "
<< arraysize(header.root_pages) << ").";
return false;
}
// Seek to the beginning of the file so we can stamp in the header.
if (::fseek(file_.get(), 0, SEEK_SET) != 0) {
LOG(ERROR) << "Seek failed while writing header.";
return false;
}
::memcpy(header.magic_string, kPdbHeaderMagicString,
sizeof(kPdbHeaderMagicString));
header.page_size = kPdbPageSize;
header.free_page_map = 1;
header.num_pages = page_count;
header.directory_size = directory_size;
header.reserved = 0;
::memcpy(header.root_pages,
root_directory_pages.data(),
sizeof(root_directory_pages[0]) * root_directory_pages.size());
if (::fwrite(&header, sizeof(header), 1, file_.get()) != 1) {
LOG(ERROR) << "Failed to write header.";
return false;
}
return true;
}
} // namespace pdb