|
| 1 | +// Copyright (c) Meta Platforms, Inc. and affiliates. |
| 2 | + |
| 3 | +#include "cinderx/Jit/code_patcher.h" |
| 4 | + |
| 5 | +#include "cinderx/Common/log.h" |
| 6 | +#include "cinderx/Common/util.h" |
| 7 | + |
| 8 | +#include <array> |
| 9 | +#include <cstring> |
| 10 | + |
| 11 | +namespace jit { |
| 12 | + |
| 13 | +namespace { |
| 14 | + |
| 15 | +static_assert( |
| 16 | + sizeof(CodePatcher) == 24, |
| 17 | + "CodePatcher should be kept small as there could be many per function"); |
| 18 | + |
| 19 | +// 5-byte nop - https://www.felixcloutier.com/x86/nop |
| 20 | +// |
| 21 | +// Asmjit supports multi-byte nops but for whatever reason we can't get it to |
| 22 | +// emit the 5-byte version. |
| 23 | +constexpr auto kJmpNopBytes = |
| 24 | + std::to_array<uint8_t>({0x0f, 0x1f, 0x44, 0x00, 0x00}); |
| 25 | + |
| 26 | +// Compute an x86-64 jump displacement operand. |
| 27 | +uint32_t jumpDisplacement(uintptr_t from, uintptr_t to) { |
| 28 | + auto disp = to - (from + kJmpNopBytes.size()); |
| 29 | + JIT_CHECK( |
| 30 | + fitsInt32(disp), |
| 31 | + "Can't encode jump from {:#x} to {:#x} as relative", |
| 32 | + from, |
| 33 | + to); |
| 34 | + return static_cast<uint32_t>(disp); |
| 35 | +} |
| 36 | + |
| 37 | +// Given the starting address and displacement operand of a jump instruction, |
| 38 | +// resolve it to a target address. |
| 39 | +uintptr_t resolveDisplacement(uintptr_t from, uint32_t displacement) { |
| 40 | + return from + displacement + kJmpNopBytes.size(); |
| 41 | +} |
| 42 | + |
| 43 | +} // namespace |
| 44 | + |
| 45 | +void CodePatcher::link(uintptr_t patchpoint, std::span<const uint8_t> data) { |
| 46 | + JIT_CHECK(!isLinked(), "Trying to re-link a patcher"); |
| 47 | + |
| 48 | + patchpoint_ = reinterpret_cast<uint8_t*>(patchpoint); |
| 49 | + |
| 50 | + JIT_CHECK( |
| 51 | + data.size() <= data_.size(), |
| 52 | + "Trying to link a patch point with {} bytes of data but only {} are " |
| 53 | + "supported", |
| 54 | + data.size(), |
| 55 | + data_.size()); |
| 56 | + |
| 57 | + std::memcpy(data_.data(), data.data(), data.size()); |
| 58 | + data_len_ = data.size(); |
| 59 | + |
| 60 | + onLink(); |
| 61 | +} |
| 62 | + |
| 63 | +void CodePatcher::patch() { |
| 64 | + JIT_CHECK(isLinked(), "Trying to patch a patcher that isn't linked"); |
| 65 | + JIT_DLOG("Patching DeoptPatchPoint at {}", static_cast<void*>(patchpoint_)); |
| 66 | + |
| 67 | + swap(); |
| 68 | + |
| 69 | + is_patched_ = true; |
| 70 | + onPatch(); |
| 71 | +} |
| 72 | + |
| 73 | +void CodePatcher::unpatch() { |
| 74 | + JIT_CHECK(isLinked(), "Trying to unpatch a patcher that isn't linked"); |
| 75 | + JIT_DLOG("Unpatching DeoptPatchPoint at {}", static_cast<void*>(patchpoint_)); |
| 76 | + |
| 77 | + swap(); |
| 78 | + |
| 79 | + is_patched_ = false; |
| 80 | + onUnpatch(); |
| 81 | +} |
| 82 | + |
| 83 | +bool CodePatcher::isLinked() const { |
| 84 | + return patchpoint_ != nullptr; |
| 85 | +} |
| 86 | + |
| 87 | +bool CodePatcher::isPatched() const { |
| 88 | + return is_patched_; |
| 89 | +} |
| 90 | + |
| 91 | +uint8_t* CodePatcher::patchpoint() const { |
| 92 | + return patchpoint_; |
| 93 | +} |
| 94 | + |
| 95 | +std::span<const uint8_t> CodePatcher::storedBytes() const { |
| 96 | + return std::span{data_.data(), data_len_}; |
| 97 | +} |
| 98 | + |
| 99 | +void CodePatcher::swap() { |
| 100 | + decltype(data_) temp; |
| 101 | + std::memcpy(temp.data(), patchpoint_, data_len_); |
| 102 | + std::memcpy(patchpoint_, data_.data(), data_len_); |
| 103 | + std::memcpy(data_.data(), temp.data(), data_len_); |
| 104 | +} |
| 105 | + |
| 106 | +JumpPatcher::JumpPatcher() { |
| 107 | + // Initializes to a nop. |
| 108 | + std::memcpy(data_.data(), kJmpNopBytes.data(), kJmpNopBytes.size()); |
| 109 | + data_len_ = kJmpNopBytes.size(); |
| 110 | +} |
| 111 | + |
| 112 | +void JumpPatcher::linkJump(uintptr_t patchpoint, uintptr_t jump_target) { |
| 113 | + auto disp = jumpDisplacement(patchpoint, jump_target); |
| 114 | + |
| 115 | + // 32 bit relative jump - https://www.felixcloutier.com/x86/jmp |
| 116 | + std::array<uint8_t, kJmpNopBytes.size()> buf{}; |
| 117 | + buf[0] = 0xe9; |
| 118 | + std::memcpy(buf.data() + 1, &disp, sizeof(uint32_t)); |
| 119 | + |
| 120 | + link(patchpoint, buf); |
| 121 | +} |
| 122 | + |
| 123 | +uint8_t* JumpPatcher::jumpTarget() const { |
| 124 | + JIT_CHECK( |
| 125 | + isLinked(), "Can't compute jump target before JumpPatcher is linked"); |
| 126 | + |
| 127 | + std::span<const uint8_t> bytes = storedBytes(); |
| 128 | + JIT_CHECK( |
| 129 | + bytes.size() == 5, |
| 130 | + "Must have linked a 5-byte 'jmp $DISP' instruction into a JumpPatcher"); |
| 131 | + |
| 132 | + uint32_t disp = 0; |
| 133 | + std::memcpy(&disp, bytes.data() + 1, bytes.size() - 1); |
| 134 | + |
| 135 | + return reinterpret_cast<uint8_t*>( |
| 136 | + resolveDisplacement(reinterpret_cast<uintptr_t>(patchpoint_), disp)); |
| 137 | +} |
| 138 | + |
| 139 | +} // namespace jit |
0 commit comments