Chromium Code Reviews| Index: snapshot/win/pe_image_resource_reader.cc |
| diff --git a/snapshot/win/pe_image_resource_reader.cc b/snapshot/win/pe_image_resource_reader.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3a73ec43fc98a3e4e0f36484dc73d9fe64dde8e1 |
| --- /dev/null |
| +++ b/snapshot/win/pe_image_resource_reader.cc |
| @@ -0,0 +1,269 @@ |
| +// Copyright 2015 The Crashpad Authors. 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 "snapshot/win/pe_image_resource_reader.h" |
| + |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| + |
| +namespace crashpad { |
| + |
| +PEImageResourceReader::PEImageResourceReader() |
| + : process_reader_(nullptr), |
| + resources_range_(), |
| + module_base_(0), |
| + module_name_(), |
| + initialized_() { |
| +} |
| + |
| +PEImageResourceReader::~PEImageResourceReader() { |
| +} |
| + |
| +bool PEImageResourceReader::Initialize( |
| + ProcessReaderWin* process_reader, |
| + const IMAGE_DATA_DIRECTORY& resources_directory_entry, |
| + const CheckedWinAddressRange& module_range, |
| + std::string module_name) { |
| + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| + |
| + process_reader_ = process_reader; |
| + |
| + resources_range_.SetRange( |
| + process_reader_->Is64Bit(), |
| + module_range.Base() + resources_directory_entry.VirtualAddress, |
| + resources_directory_entry.Size); |
| + if (!resources_range_.IsValid()) { |
| + LOG(WARNING) << "invalid resources range " << resources_range_.AsString() |
| + << " for " << module_name; |
| + return false; |
| + } |
| + |
| + if (!module_range.ContainsRange(resources_range_)) { |
| + LOG(WARNING) << "resources range " << resources_range_.AsString() |
| + << " outside of module range " << module_range.AsString() |
| + << " for " << module_name; |
| + return false; |
| + } |
| + |
| + module_base_ = module_range.Base(); |
| + module_name_ = module_name; |
| + |
| + INITIALIZATION_STATE_SET_VALID(initialized_); |
| + return true; |
| +} |
| + |
| +bool PEImageResourceReader::FindResourceByID(uint16_t type, |
| + uint16_t name, |
| + uint16_t language, |
| + WinVMAddress* address, |
| + WinVMSize* size, |
| + uint32_t* code_page) const { |
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| + |
| + // The root resource directory is at the beginning of the resources area |
| + // within the module. |
| + const uint32_t name_directory_offset = |
| + GetEntryFromResourceDirectoryByID(0, type, true); |
| + if (!name_directory_offset) { |
| + return false; |
| + } |
| + |
| + const uint32_t language_directory_offset = |
| + GetEntryFromResourceDirectoryByID(name_directory_offset, name, true); |
| + if (!language_directory_offset) { |
| + return false; |
| + } |
| + |
| + // The definition of IMAGE_RESOURCE_DIRECTORY_ENTRY in <winnt.h> has a comment |
| + // saying that its offsets are relative to “the resource directory of the data |
| + // associated with this directory entry”. That could be interpreted to mean |
| + // that language_directory_offset is relative to name_directory_offset, since |
| + // the language directory entry is found within the name directory. This is |
| + // not correct. All resource offsets are relative to the resources area within |
| + // the module. |
| + const uint32_t data_offset = GetEntryFromResourceDirectoryByLanguage( |
| + language_directory_offset, language); |
| + if (!data_offset) { |
| + return false; |
| + } |
| + |
| + IMAGE_RESOURCE_DATA_ENTRY data_entry; |
| + if (!CheckedReadMemory(resources_range_.Base() + data_offset, |
| + sizeof(data_entry), |
| + &data_entry)) { |
| + return false; |
| + } |
| + |
| + // The definition of IMAGE_RESOURCE_DATA_ENTRY in <winnt.h> has a comment |
| + // saying that OffsetToData is relative to the beginning of the resource data. |
| + // This is not correct. It’s module-relative. |
| + *address = module_base_ + data_entry.OffsetToData; |
| + *size = data_entry.Size; |
| + if (code_page) { |
| + *code_page = data_entry.CodePage; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByID( |
| + uint32_t language_directory_offset, |
| + uint16_t id, |
| + bool want_subdirectory) const { |
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| + |
| + std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY> entries_by_id; |
| + if (!ReadResourceDirectory( |
| + language_directory_offset, nullptr, nullptr, &entries_by_id)) { |
| + return 0; |
| + } |
| + |
| + for (const auto& entry : entries_by_id) { |
| + if (!entry.NameIsString && entry.Id == id) { |
| + if (static_cast<bool>(entry.DataIsDirectory) != want_subdirectory) { |
| + LOG(WARNING) << "expected " << (want_subdirectory ? "" : "non-") |
| + << "directory for entry id " << id << " in " |
| + << module_name_; |
| + return 0; |
| + } |
| + |
| + return entry.DataIsDirectory ? entry.OffsetToDirectory |
|
scottmg
2015/11/26 21:30:00
The != above warns but this one doesn't? Silly.
Mark Mentovai
2015/11/27 17:56:28
scottmg wrote:
scottmg
2015/11/27 18:06:58
Sorry, I just meant that there wasn't a need to st
Mark Mentovai
2015/11/27 21:40:37
scottmg wrote:
|
| + : entry.OffsetToData; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByLanguage( |
| + uint32_t resource_directory_offset, |
| + uint16_t language) const { |
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| + |
| + std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY> entries_by_language; |
| + if (!ReadResourceDirectory( |
| + resource_directory_offset, nullptr, nullptr, &entries_by_language)) { |
| + return 0; |
| + } |
| + |
| + if (entries_by_language.empty()) { |
| + return 0; |
| + } |
| + |
| + // https://msdn.microsoft.com/en-us/library/cc194810.aspx |
| + // |
| + // TODO(mark): It seems like the FindResourceEx() might do something more |
|
scottmg
2015/11/26 21:30:00
Remove "the ".
Mark Mentovai
2015/12/01 18:48:01
scottmg wrote:
|
| + // complex. It would be best to mimic its behavior. |
| + std::vector<uint16_t> try_languages; |
| + try_languages.push_back(language); |
| + try_languages.push_back(MAKELANGID(PRIMARYLANGID(language), SUBLANG_NEUTRAL)); |
| + try_languages.push_back(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); |
| + try_languages.push_back(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT)); |
|
scottmg
2015/11/26 21:30:00
Presumably this should be the UI language rather t
Mark Mentovai
2015/12/01 18:48:01
scottmg wrote:
|
| + |
| + for (const auto try_language : try_languages) { |
| + for (const auto& entry : entries_by_language) { |
| + if (!entry.NameIsString && entry.Id == try_language) { |
| + if (entry.DataIsDirectory) { |
| + LOG(WARNING) << "expected non-directory for entry language " |
| + << try_language << " in " << module_name_; |
| + return 0; |
| + } |
| + |
| + return entry.OffsetToData; |
| + } |
| + } |
| + } |
| + |
| + // Fall back to the first entry in the list. |
| + const auto& entry = entries_by_language.front(); |
| + if (entry.DataIsDirectory) { |
| + LOG(WARNING) << "expected non-directory for entry in " << module_name_; |
| + return 0; |
| + } |
| + |
| + return entry.OffsetToData; |
| +} |
| + |
| +bool PEImageResourceReader::ReadResourceDirectory( |
| + uint32_t resource_directory_offset, |
| + IMAGE_RESOURCE_DIRECTORY* resource_directory, |
| + std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY>* named_entries, |
| + std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY>* id_entries) const { |
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| + |
| + // resource_directory is optional, but it’s still needed locally even if the |
| + // caller isn’t interested in it. |
| + scoped_ptr<IMAGE_RESOURCE_DIRECTORY> local_resource_directory; |
| + if (!resource_directory) { |
| + local_resource_directory.reset(new IMAGE_RESOURCE_DIRECTORY); |
| + resource_directory = local_resource_directory.get(); |
| + } |
| + |
| + const WinVMAddress address = |
| + resources_range_.Base() + resource_directory_offset; |
| + |
| + if (!CheckedReadMemory( |
| + address, sizeof(*resource_directory), resource_directory)) { |
| + LOG(WARNING) << "could not read resource directory"; |
| + return false; |
| + } |
| + |
| + if (named_entries) { |
| + named_entries->clear(); |
| + named_entries->resize(resource_directory->NumberOfNamedEntries); |
| + if (!named_entries->empty() && |
| + !CheckedReadMemory(address + sizeof(*resource_directory), |
| + named_entries->size() * sizeof((*named_entries)[0]), |
| + &(*named_entries)[0])) { |
| + LOG(WARNING) << "could not read resource directory named entries"; |
| + return false; |
| + } |
| + } |
| + |
| + if (id_entries) { |
| + id_entries->clear(); |
| + id_entries->resize(resource_directory->NumberOfIdEntries); |
| + if (!id_entries->empty() && |
| + !CheckedReadMemory(address + sizeof(*resource_directory) + |
| + resource_directory->NumberOfNamedEntries * |
| + sizeof((*named_entries)[0]), |
|
scottmg
2015/11/26 21:30:00
Just confirming that this is OK when named_entries
Mark Mentovai
2015/12/01 18:48:01
scottmg wrote:
|
| + id_entries->size() * sizeof((*id_entries)[0]), |
| + &(*id_entries)[0])) { |
| + LOG(WARNING) << "could not read resource directory ID entries"; |
| + return false; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool PEImageResourceReader::CheckedReadMemory(WinVMAddress address, |
|
scottmg
2015/11/26 21:30:00
Can we avoid duplicating this here and in PEImageR
Mark Mentovai
2015/12/01 18:48:01
scottmg wrote:
|
| + WinVMSize size, |
| + void* into) const { |
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| + |
| + CheckedWinAddressRange read_range(process_reader_->Is64Bit(), address, size); |
| + if (!read_range.IsValid()) { |
| + LOG(WARNING) << "invalid read range: " << read_range.AsString(); |
| + return false; |
| + } |
| + if (!resources_range_.ContainsRange(read_range)) { |
| + LOG(WARNING) << "attempt to read outside of module " << module_name_ |
| + << " resources at range: " << read_range.AsString(); |
| + return false; |
| + } |
| + return process_reader_->ReadMemory(address, size, into); |
| +} |
| + |
| +} // namespace crashpad |