blob: 30bc8c9c5af6b8ce83122e3de21c45de42e7000c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "test.h"
#include <lib/arch/cache.h>
#include <lib/code-patching/code-patching.h>
#include <lib/zbitl/error_stdio.h>
#include <lib/zbitl/view.h>
#include <stdio.h>
#include <ktl/byte.h>
#include <ktl/span.h>
#include <ktl/string_view.h>
#include "../test-main.h"
const char Symbolize::kProgramName_[] = "code-patching-test";
// Defined in add-one.S.
extern "C" uint64_t AddOne(uint64_t x);
// Defined by //zircon/kernel/phys/test/code-patching:multiply_by_factor.
extern "C" uint64_t multiply_by_factor(uint64_t x);
namespace {
constexpr size_t kExpectedNumPatches = 2;
// Returns the address range within this executable associated with a given
// link-time, virtual range.
ktl::span<ktl::byte> GetInstructionRange(uint64_t range_start, size_t range_size) {
// If we are not static PIE, then we expect link-time addresses to already
// be offset from PHYS_LOAD_ADDRESS; in that case, adjust.
#ifndef ZX_STATIC_PIE
range_start -= reinterpret_cast<uint64_t>(PHYS_LOAD_ADDRESS);
#endif
auto* loaded_start = reinterpret_cast<ktl::byte*>(const_cast<char*>(PHYS_LOAD_ADDRESS));
const size_t loaded_size = static_cast<size_t>(_end - PHYS_LOAD_ADDRESS);
ktl::span<ktl::byte> loaded_range{loaded_start, loaded_size};
ZX_ASSERT(range_size <= loaded_range.size());
ZX_ASSERT(range_start <= loaded_range.size() - range_size);
return loaded_range.subspan(range_start, range_size);
}
int TestAddOnePatching(const code_patching::Directive& patch) {
ZX_ASSERT_MSG(patch.range_size == kAddOnePatchSize,
"Expected patch case #%u to cover %zu bytes; got %u", kAddOneCaseId,
kAddOnePatchSize, patch.range_size);
{
uint64_t result = AddOne(583);
ZX_ASSERT_MSG(result == 584, "AddOne(583) returned %lu; expected 584.\n", result);
}
// After patching (and synchronizing the instruction and data caches), we
// expect AddOne() to be the identity function.
auto insns = GetInstructionRange(patch.range_start, patch.range_size);
code_patching::NopFill(insns);
arch::CacheConsistencyContext().SyncRange(patch.range_start, patch.range_size);
{
uint64_t result = AddOne(583);
ZX_ASSERT_MSG(result == 583, "Patched AddOne(583) returned %lu; expected 583.\n", result);
}
return 0;
}
int TestMultiplyByFactorPatching(const code_patching::Directive& patch) {
ZX_ASSERT_MSG(patch.range_size == kMultiplyByFactorPatchSize,
"Expected patch case #%u to cover %zu bytes; got %u", kMultiplyByFactorCaseId,
kMultiplyByFactorPatchSize, patch.range_size);
auto insns = GetInstructionRange(patch.range_start, patch.range_size);
// After patching and synchronizing, we expect multiply_by_factor() to
// multiply by 2.
ktl::span<const ktl::byte> multiply_by_two = GetPatchAlternative("multiply_by_two");
code_patching::Patch(insns, multiply_by_two);
arch::CacheConsistencyContext().SyncRange(patch.range_start, patch.range_size);
{
uint64_t result = multiply_by_factor(583);
ZX_ASSERT_MSG(result == 2 * 583, "multiply_by_factor(583) returned %lu; expected %d.\n", result,
2 * 583);
}
// After patching and synchronizing, we expect multiply_by_factor() to
// multiply by ten.
ktl::span<const ktl::byte> multiply_by_ten = GetPatchAlternative("multiply_by_ten");
code_patching::Patch(insns, multiply_by_ten);
arch::CacheConsistencyContext().SyncRange(patch.range_start, patch.range_size);
{
uint64_t result = multiply_by_factor(583);
ZX_ASSERT_MSG(result == 10 * 583, "multiply_by_factor(583) returned %lu; expected %d.\n",
result, 10 * 583);
}
return 0;
}
} // namespace
int TestMain(void* zbi_ptr, arch::EarlyTicks) {
zbitl::View<ktl::span<ktl::byte>> zbi({static_cast<ktl::byte*>(zbi_ptr), SIZE_MAX});
ktl::span<ktl::byte> raw_patches;
for (auto [header, payload] : zbi) {
// The patch metadata is expected to be stored in an uncompressed ramdisk
// item.
if (header->type == ZBI_TYPE_STORAGE_RAMDISK &&
(header->flags & ZBI_FLAG_STORAGE_COMPRESSED) == 0) {
raw_patches = payload;
break;
}
}
if (auto result = zbi.take_error(); result.is_error()) {
zbitl::PrintViewError(result.error_value());
return 1;
}
if (raw_patches.size() % sizeof(code_patching::Directive)) {
printf("Expected total size of code patch directives to be a multiple of %lu: got %zu\n",
sizeof(code_patching::Directive), raw_patches.size());
return 1;
}
ktl::span<code_patching::Directive> patches{
reinterpret_cast<code_patching::Directive*>(raw_patches.data()),
raw_patches.size() / sizeof(code_patching::Directive),
};
printf("Patches found:\n");
printf("| %-4s | %-8s | %-8s | %-4s |\n", "ID", "Start", "End", "Size");
for (const auto& patch : patches) {
printf("| %-4u | %#-8lx | %#-8lx | %-4u |\n", patch.id, patch.range_start,
patch.range_start + patch.range_size, patch.range_size);
}
if (patches.size() != kExpectedNumPatches) {
printf("Expected %zu code patch directive: got %zu", kExpectedNumPatches, patches.size());
return 1;
}
for (size_t i = 0; i < patches.size(); ++i) {
const auto& patch = patches[i];
switch (patch.id) {
case kAddOneCaseId:
if (int result = TestAddOnePatching(patch); result != 0) {
return result;
}
break;
case kMultiplyByFactorCaseId:
if (int result = TestMultiplyByFactorPatching(patch); result != 0) {
return result;
}
break;
default:
printf("Unexpected patch case ID: %u\n", patch.id);
return 1;
}
}
return 0;
}