blob: edc874d182da553a57e326f12ec895ee68af3906 [file] [log] [blame]
// Copyright 2013 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/pe/pe_coff_file.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/files/file_path.h"
#include "base/strings/string_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pe/unittest_util.h"
namespace pe {
namespace {
using core::AbsoluteAddress;
using core::FileOffsetAddress;
using core::RelativeAddress;
template <typename AddressSpaceTraits>
class TestPECoffFile : public PECoffFile<AddressSpaceTraits> {
public:
// Partial initialization, for common parts.
bool Init(const base::FilePath& path, bool has_pe_headers) {
if (!PECoffFile::Init(path))
return false;
FileOffsetAddress file_header_start(0);
if (has_pe_headers) {
// Read NT header position at 0x3C according to the spec.
uint32_t nt_header_pos = 0;
if (!ReadAt(0x3C, &nt_header_pos, sizeof(nt_header_pos)))
return false;
file_header_start.set_value(nt_header_pos +
offsetof(IMAGE_NT_HEADERS, FileHeader));
}
bool success = ReadCommonHeaders(file_header_start);
if (success)
success = ReadSections();
return success;
}
};
template <typename AddressType, size_t shift_base, size_t header_base>
struct ShiftedAddressSpaceTraits {
typedef typename AddressType AddressType;
typedef unsigned int SizeType;
static const size_t kShiftBase = shift_base;
static const AddressType invalid_address() {
return AddressType::kInvalidAddress;
}
static const AddressType header_address() {
return AddressType(header_base);
}
static AddressType GetSectionAddress(const IMAGE_SECTION_HEADER& header) {
// VirtualAddress is not necessarily 0 for object files, but it
// should, and MSVC should produce 0; we use that info as
// a heuristic to decide on the address.
if (header.VirtualAddress == 0) {
if ((header.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 &&
header.PointerToRawData == 0) {
// Unmapped section.
return invalid_address();
} else {
return AddressType(kShiftBase + header.PointerToRawData);
}
} else {
return AddressType(kShiftBase + header.VirtualAddress);
}
}
static SizeType GetSectionSize(const IMAGE_SECTION_HEADER& header) {
return 1;
}
};
typedef ShiftedAddressSpaceTraits<AbsoluteAddress, 0x10000, 0x4>
ShiftedVirtualAddressTraits;
typedef ShiftedAddressSpaceTraits<FileOffsetAddress, 0x100, 0x100>
ShiftedFileOffsetAddressTraits;
class PECoffFileTest : public testing::PELibUnitTest {
typedef testing::PELibUnitTest Super;
public:
PECoffFileTest() {
}
virtual void SetUp() override {
Super::SetUp();
test_dll_path_ = testing::GetExeRelativePath(testing::kTestDllName);
test_dll_coff_obj_path_ =
testing::GetExeTestDataRelativePath(testing::kTestDllCoffObjName);
test_dll_ltcg_obj_path_ =
testing::GetExeTestDataRelativePath(testing::kTestDllLtcgObjName);
}
protected:
bool InitImages() {
return pe_image_file_.Init(test_dll_path_, true) &&
coff_image_file_.Init(test_dll_coff_obj_path_, false);
}
base::FilePath test_dll_path_;
base::FilePath test_dll_coff_obj_path_;
base::FilePath test_dll_ltcg_obj_path_;
TestPECoffFile<ShiftedVirtualAddressTraits> pe_image_file_;
TestPECoffFile<ShiftedFileOffsetAddressTraits> coff_image_file_;
private:
DISALLOW_COPY_AND_ASSIGN(PECoffFileTest);
};
bool IsCoffSectionMapped(
const TestPECoffFile<ShiftedFileOffsetAddressTraits>& image_file,
size_t i) {
const IMAGE_SECTION_HEADER* hdr = image_file.section_header(i);
return hdr != NULL &&
(ShiftedFileOffsetAddressTraits::GetSectionAddress(*hdr) !=
ShiftedFileOffsetAddressTraits::invalid_address());
}
} // namespace
TEST_F(PECoffFileTest, Init) {
EXPECT_TRUE(pe_image_file_.file_header() == NULL);
EXPECT_TRUE(pe_image_file_.section_headers() == NULL);
EXPECT_TRUE(coff_image_file_.file_header() == NULL);
EXPECT_TRUE(coff_image_file_.section_headers() == NULL);
ASSERT_TRUE(pe_image_file_.Init(test_dll_path_, true));
ASSERT_TRUE(coff_image_file_.Init(test_dll_coff_obj_path_, false));
EXPECT_TRUE(pe_image_file_.file_header() != NULL);
EXPECT_TRUE(pe_image_file_.section_headers() != NULL);
EXPECT_TRUE(coff_image_file_.file_header() != NULL);
EXPECT_TRUE(coff_image_file_.section_headers() != NULL);
EXPECT_EQ(pe_image_file_.file_header()->Machine, IMAGE_FILE_MACHINE_I386);
EXPECT_EQ(coff_image_file_.file_header()->Machine, IMAGE_FILE_MACHINE_I386);
EXPECT_TRUE(pe_image_file_.file_header()->SizeOfOptionalHeader != 0);
EXPECT_TRUE(coff_image_file_.file_header()->PointerToSymbolTable != 0);
}
TEST_F(PECoffFileTest, FailOnAnonymousObject) {
ASSERT_FALSE(coff_image_file_.Init(test_dll_ltcg_obj_path_, false));
}
// Compare header data obtained from different methods.
TEST_F(PECoffFileTest, ReadFileHeader) {
ASSERT_TRUE(InitImages());
ASSERT_TRUE(coff_image_file_.file_header() != NULL);
IMAGE_FILE_HEADER header = {};
ASSERT_TRUE(coff_image_file_.ReadImage(coff_image_file_.header_address(),
&header, sizeof(header)));
EXPECT_TRUE(memcmp(static_cast<void*>(&header),
static_cast<const void*>(coff_image_file_.file_header()),
sizeof(header)) == 0);
uint8_t* ptr = coff_image_file_.GetImageData(
coff_image_file_.header_address(), sizeof(header));
ASSERT_TRUE(ptr != NULL);
EXPECT_TRUE(memcmp(static_cast<void*>(&header),
static_cast<void*>(ptr),
sizeof(header)) == 0);
}
TEST_F(PECoffFileTest, Contains) {
ASSERT_TRUE(InitImages());
EXPECT_TRUE(pe_image_file_.Contains(pe_image_file_.header_address(), 1));
EXPECT_FALSE(pe_image_file_.Contains(pe_image_file_.header_address() - 1, 1));
// Should be a gap in the address space before the big shift of
// ShiftedVirtualAddressTraits::kShiftBase.
EXPECT_FALSE(pe_image_file_.Contains(
AbsoluteAddress(ShiftedVirtualAddressTraits::kShiftBase - 1), 1));
EXPECT_TRUE(coff_image_file_.Contains(coff_image_file_.header_address(), 1));
EXPECT_FALSE(coff_image_file_.Contains(
coff_image_file_.header_address() - 1, 1));
}
TEST_F(PECoffFileTest, GetSectionIndex) {
ASSERT_TRUE(InitImages());
size_t num_sections = pe_image_file_.file_header()->NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
AbsoluteAddress section_start(
ShiftedVirtualAddressTraits::GetSectionAddress(
*pe_image_file_.section_header(i)));
EXPECT_EQ(i, pe_image_file_.GetSectionIndex(section_start, 1));
}
AbsoluteAddress off_by_one(ShiftedVirtualAddressTraits::kShiftBase - 1);
EXPECT_EQ(kInvalidSection, pe_image_file_.GetSectionIndex(off_by_one, 1));
num_sections = coff_image_file_.file_header()->NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
if (IsCoffSectionMapped(coff_image_file_, i)) {
FileOffsetAddress section_start(
ShiftedFileOffsetAddressTraits::GetSectionAddress(
*coff_image_file_.section_header(i)));
EXPECT_EQ(i, coff_image_file_.GetSectionIndex(section_start, 1));
}
}
}
TEST_F(PECoffFileTest, GetSectionHeader) {
ASSERT_TRUE(InitImages());
size_t num_sections = pe_image_file_.file_header()->NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
AbsoluteAddress section_start(
ShiftedVirtualAddressTraits::GetSectionAddress(
*pe_image_file_.section_header(i)));
EXPECT_EQ(pe_image_file_.section_header(i),
pe_image_file_.GetSectionHeader(section_start, 1));
}
num_sections = coff_image_file_.file_header()->NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
if (IsCoffSectionMapped(coff_image_file_, i)) {
FileOffsetAddress section_start(
ShiftedFileOffsetAddressTraits::GetSectionAddress(
*coff_image_file_.section_header(i)));
EXPECT_EQ(coff_image_file_.section_header(i),
coff_image_file_.GetSectionHeader(section_start, 1));
}
}
}
TEST_F(PECoffFileTest, GetImageData) {
ASSERT_TRUE(InitImages());
ASSERT_TRUE(pe_image_file_.file_header() != NULL);
ASSERT_TRUE(coff_image_file_.file_header() != NULL);
size_t num_sections = pe_image_file_.file_header()->NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
AbsoluteAddress section_start(
ShiftedVirtualAddressTraits::GetSectionAddress(
*pe_image_file_.section_header(i)));
EXPECT_TRUE(pe_image_file_.GetImageData(section_start, 1) != NULL);
}
num_sections = coff_image_file_.file_header()->NumberOfSections;
for (size_t i = 0; i < num_sections; ++i) {
if (IsCoffSectionMapped(coff_image_file_, i)) {
FileOffsetAddress section_start(
ShiftedFileOffsetAddressTraits::GetSectionAddress(
*coff_image_file_.section_header(i)));
EXPECT_TRUE(coff_image_file_.GetImageData(section_start, 1) != NULL);
}
}
// Get arbitrary image data spanning across sections. Expect this to fail
// with GetImageData, but to succeed with GetImageDataFromFileOffset.
AbsoluteAddress header_abs(ShiftedVirtualAddressTraits::header_address());
FileOffsetAddress header_off(
ShiftedFileOffsetAddressTraits::header_address());
size_t len = (ShiftedFileOffsetAddressTraits::GetSectionAddress(
*coff_image_file_.section_header(0)) -
header_off) +
1;
EXPECT_FALSE(pe_image_file_.GetImageData(header_abs, len));
EXPECT_TRUE(pe_image_file_.GetImageDataByFileOffset(header_off, len));
}
} // namespace pe