blob: c0b21be1c30282e209beeb14ace390d5a3d20e94 [file] [log] [blame]
// Copyright 2015 The Crashpad Authors
//
// 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 <algorithm>
#include <memory>
#include "base/logging.h"
namespace {
void AddLanguageAndNeutralSublanguage(std::vector<uint16_t>* languages,
uint16_t language) {
languages->push_back(language);
if (SUBLANGID(language) != SUBLANG_NEUTRAL) {
languages->push_back(MAKELANGID(PRIMARYLANGID(language), SUBLANG_NEUTRAL));
}
}
} // namespace
namespace crashpad {
PEImageResourceReader::PEImageResourceReader()
: resources_subrange_reader_(),
module_base_(0),
initialized_() {
}
PEImageResourceReader::~PEImageResourceReader() {}
bool PEImageResourceReader::Initialize(
const ProcessSubrangeReader& module_subrange_reader,
const IMAGE_DATA_DIRECTORY& resources_directory_entry) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
module_base_ = module_subrange_reader.Base();
if (!resources_subrange_reader_.InitializeSubrange(
module_subrange_reader,
module_base_ + resources_directory_entry.VirtualAddress,
resources_directory_entry.Size,
"resources")) {
return false;
}
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 (!resources_subrange_reader_.ReadMemory(
resources_subrange_reader_.Base() + data_offset,
sizeof(data_entry),
&data_entry)) {
LOG(WARNING) << "could not read resource data entry from "
<< resources_subrange_reader_.name();
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;
}
const auto entry_it =
std::find_if(entries_by_id.begin(),
entries_by_id.end(),
[id](const IMAGE_RESOURCE_DIRECTORY_ENTRY& entry) {
return !entry.NameIsString && entry.Id == id;
});
if (entry_it != entries_by_id.end()) {
if ((entry_it->DataIsDirectory != 0) != want_subdirectory) {
LOG(WARNING) << "expected " << (want_subdirectory ? "" : "non-")
<< "directory for entry id " << id << " in "
<< resources_subrange_reader_.name();
return 0;
}
return entry_it->DataIsDirectory ? entry_it->OffsetToDirectory
: entry_it->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/library/cc194810.aspx
//
// TODO(mark): It seems like FindResourceEx() might do something more complex.
// It would be best to mimic its behavior.
std::vector<uint16_t> try_languages;
if (PRIMARYLANGID(language) != LANG_NEUTRAL) {
AddLanguageAndNeutralSublanguage(&try_languages, language);
} else {
if (SUBLANGID(language) != SUBLANG_SYS_DEFAULT) {
AddLanguageAndNeutralSublanguage(&try_languages,
LANGIDFROMLCID(GetThreadLocale()));
AddLanguageAndNeutralSublanguage(&try_languages,
LANGIDFROMLCID(GetUserDefaultLCID()));
}
if (SUBLANGID(language) != SUBLANG_DEFAULT) {
AddLanguageAndNeutralSublanguage(&try_languages,
LANGIDFROMLCID(GetSystemDefaultLCID()));
}
}
try_languages.push_back(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
try_languages.push_back(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT));
for (const auto try_language : try_languages) {
const auto entry_it = std::find_if(
entries_by_language.begin(),
entries_by_language.end(),
[try_language](const IMAGE_RESOURCE_DIRECTORY_ENTRY& entry) {
return !entry.NameIsString && entry.Id == try_language;
});
if (entry_it != entries_by_language.end()) {
if (entry_it->DataIsDirectory) {
LOG(WARNING) << "expected non-directory for entry language "
<< try_language << " in "
<< resources_subrange_reader_.name();
return 0;
}
return entry_it->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 "
<< resources_subrange_reader_.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.
std::unique_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_subrange_reader_.Base() + resource_directory_offset;
if (!resources_subrange_reader_.ReadMemory(
address, sizeof(*resource_directory), resource_directory)) {
LOG(WARNING) << "could not read resource directory from "
<< resources_subrange_reader_.name();
return false;
}
if (named_entries) {
named_entries->clear();
named_entries->resize(resource_directory->NumberOfNamedEntries);
if (!named_entries->empty() &&
!resources_subrange_reader_.ReadMemory(
address + sizeof(*resource_directory),
named_entries->size() * sizeof((*named_entries)[0]),
&(*named_entries)[0])) {
LOG(WARNING) << "could not read resource directory named entries from "
<< resources_subrange_reader_.name();
return false;
}
}
if (id_entries) {
id_entries->clear();
id_entries->resize(resource_directory->NumberOfIdEntries);
if (!id_entries->empty() &&
!resources_subrange_reader_.ReadMemory(
address + sizeof(*resource_directory) +
resource_directory->NumberOfNamedEntries *
sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),
id_entries->size() * sizeof((*id_entries)[0]),
&(*id_entries)[0])) {
LOG(WARNING) << "could not read resource directory ID entries from "
<< resources_subrange_reader_.name();
return false;
}
}
return true;
}
} // namespace crashpad