blob: 4fbd832e46e03425b1bae78e102574ca074948f4 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/unwinder/arm_ehabi_parser.h"
#include <elf.h>
#include <inttypes.h>
#include <span>
#include "src/lib/unwinder/arm_ehabi_module.h"
#include "src/lib/unwinder/registers.h"
#define LOG_DEBUG(...)
// #define LOG_DEBUG(...) fprintf(stderr, __VA_ARGS__);
namespace unwinder {
namespace {
constexpr uint32_t kExIdxCantUnwind = 1;
constexpr uint32_t kFinishedIndicator = 0xb0;
fit::result<Error, uint64_t> ParseULEB128FromBytes(std::span<const uint8_t> bytes) {
uint64_t res = 0;
uint64_t shift = 0;
uint8_t byte = 0;
size_t i = 0;
do {
if (i == bytes.size()) {
return fit::error(Error("Failed to parse ULEB128."));
}
byte = bytes[i++];
res |= (byte & 0x7F) << shift;
shift += 7;
} while (byte & 0x80);
return fit::ok(res);
}
uint8_t GetNextByteFromData(uint32_t data, size_t offset) {
const uint8_t* byte_data = reinterpret_cast<uint8_t*>(&data);
// This reverses the byte order from a little endian representation to be more like big endian
// to make decoding simpler. The encoding scheme typically will have us walk from most significant
// to least significant bytes within a 16 or 32 bit range and interpret different bit pairings
// within there so we need to be careful to take things in the correct order. This works for
// arbitrary offsets broken on a 4 byte boundary. For example, for offsets 0, 1, 2, 3, 4, ... the
// returned bytes will be at offsets 3, 2, 1, 0, 7, ... and so on. Since this unwinder is
// out-of-process, this function won't work for data beyond the particular 32 bits in |data|, the
// rest of the data must be fetched from the remote process.
//
// An example: if you have an encoding for popping a register off the stack (see EHABI 10.3)
// the binary encoding looks like this:
//
// 0b1000iiii iiiiiiii
// ^ ^
// | |
// r15
// r14
// r13
// r12
// |
// r11
// ..
// r5
// r4
//
// On a little endian system this will look like this in memory:
//
// 0biiiiiiii iiii0010
// ^ ^
// | |
// | r13
// | r12
// | r15
// | r14
// r5
// r4
// r7
// r6
//
// So when you ask for offset 0 from this 16 bit integer, you'll get back
//
// 0b1000iiii
// ^
// |
// r15
// r14
// r13
// r12
//
// And offset 1 will be
//
// 0biiiiiiii
// ^
// |
// r11
// r10
// ..
// r5
// r4
return byte_data[(offset & ~static_cast<size_t>(0x03)) +
(3 - (offset & static_cast<size_t>(0x03)))];
}
} // namespace
ArmEhAbiParser::ArmEhAbiParser(Memory* elf, const ArmEhAbiModule::IdxHeader& entry) : elf_(elf) {
switch (entry.type) {
case ArmEhAbiModule::IdxHeader::Type::kCompact:
extab_offset_ = static_cast<int32_t>(entry.header.data);
break;
case ArmEhAbiModule::IdxHeader::Type::kCompactInline:
data_ = entry.header.data;
break;
default:
break;
}
}
Error ArmEhAbiParser::Step(Memory* stack, const Registers& current, Registers& next) {
if (data_ == 0 && extab_offset_ == 0) {
return Error("Invalid IdxHeaderEntry.");
} else if (data_ == kExIdxCantUnwind || extab_offset_ == kExIdxCantUnwind) {
return Error("Got ExIdxCantUnwind.");
}
// The ARM EH ABI specifies that we're working with a singular "virtual register set" which is
// always updated in place. We don't have that concept here, so every step operation has to start
// with updating all of the registers in |next| to |current| with the exception of PC, which will
// be set explicitly by one of the unwinding instructions or to LR (r14) if not set explicitly by
// the end of the instructions.
for (size_t i = 0; i < static_cast<size_t>(RegisterID::kArm32_last); i++) {
uint64_t v;
// Some registers in |next| may have been set from CFI instructions, which we don't want to
// overwrite.
if (i != static_cast<size_t>(RegisterID::kArm32_pc) &&
next.Get(static_cast<RegisterID>(i), v).has_err()) {
// Not an error if something isn't set here.
if (current.Get(static_cast<RegisterID>(i), v).ok()) {
next.Set(static_cast<RegisterID>(i), v);
}
}
}
if (auto result = CollectInstructions(); result.is_ok()) {
LOG_DEBUG("Executing %zu instructions\n", result.value().size());
if (auto err = ExecuteInstructions(stack, result.value(), next); err.has_err()) {
return err;
}
} else {
return result.error_value();
}
Error err = Success();
// Finally, restore PC to LR if not directly set by the unwinding instructions.
if (uint64_t pc; next.GetPC(pc).has_err()) {
// Undefined LR register usually means the end of unwinding. This is not considered an error.
if (uint64_t return_address; next.GetReturnAddress(return_address).ok()) {
if (err = next.SetPC(return_address); err.ok()) {
next.Unset(RegisterID::kArm32_lr);
// Don't overwrite the SetPC error if something went wrong.
err = next.AdjustPCForThumb();
}
} else {
err = Error("Could not set PC from LR!");
}
}
LOG_DEBUG("%s => %s\n", current.Describe().c_str(), next.Describe().c_str());
return err;
}
fit::result<Error, std::vector<uint8_t>> ArmEhAbiParser::CollectInstructions() {
uint32_t data = 0;
if (auto result = GetFirstDataWord(); result.is_ok()) {
data = result.value();
} else {
return result;
}
size_t offset = 0;
uint8_t num_extra_words = 0;
if (auto result = GetExtraWordsCountAndAdvance(data, offset); result.is_ok()) {
num_extra_words = result.value();
} else {
return result;
}
return ParseToFinished(data, offset, num_extra_words);
}
fit::result<Error, uint8_t> ArmEhAbiParser::GetExtraWordsCountAndAdvance(uint32_t data,
size_t& offset) {
uint8_t byte = GetNextByteFromData(data, offset++);
FrameHandlerType type = static_cast<FrameHandlerType>(byte & 0x0f);
// The number of 32 bit words _after_ |data| that we need to read for all of the unwinding
// instructions.
uint8_t num_extra_words;
switch (type) {
case FrameHandlerType::kSu16:
num_extra_words = 0; // All instructions present in |data|.
break;
case FrameHandlerType::kLu16:
case FrameHandlerType::kLu32:
num_extra_words = GetNextByteFromData(data, offset++);
break;
default:
return fit::error(Error("Unknown FrameHandlerType: %d\n", static_cast<uint8_t>(type)));
}
return fit::ok(num_extra_words);
}
// Will not read any more bytes other than what it is given. This won't return an error if it
// doesn't find |kFinishedIndicator|.
ArmEhAbiParser::ParsedResult ArmEhAbiParser::ParseWordFromOffset(uint32_t data, size_t offset) {
uint8_t byte = GetNextByteFromData(data, offset++);
std::vector<uint8_t> parsed_bytes;
parsed_bytes.push_back(byte);
while (offset < 4) {
if (byte = GetNextByteFromData(data, offset++); byte == kFinishedIndicator)
break;
parsed_bytes.push_back(byte);
}
return {.data = parsed_bytes, .found_terminator_opcode = byte == kFinishedIndicator};
}
// Will continue to read bytes from the given offset until it finds a "finished" instruction, but
// will not decode any other instructions.
fit::result<Error, std::vector<uint8_t>> ArmEhAbiParser::ParseToFinished(uint32_t data,
size_t offset,
uint8_t num_extra_words) {
auto res = ParseWordFromOffset(data, offset);
if (num_extra_words == 0) {
// The compact inline format will not include a finished instruction if it fills the allocated 4
// bytes in the index entry exactly, so we cannot assert that the result from parsing the first
// word has found the terminator opcode.
return fit::ok(res.data);
}
std::vector<uint8_t> data_stream = std::move(res.data);
// Results from parsing each word of data. These are outside of the loop so we can do checking at
// the end to make sure that we actually encountered a terminator opcode and give a good error
// message if we didn't.
ParsedResult next_data_result;
uint32_t next_data = 0;
// Start at i = 1 because we already have the first word in |data|.
for (size_t i = 1; i <= num_extra_words; i++) {
if (auto err = elf_->Read(static_cast<uint64_t>(extab_offset_) + (i * 4), next_data);
err.has_err()) {
return fit::error(err);
}
next_data_result = ParseWordFromOffset(next_data, 0);
data_stream.insert(data_stream.end(), next_data_result.data.begin(),
next_data_result.data.end());
}
if (!next_data_result.found_terminator_opcode) {
return fit::error(
Error("Failed to find finished indicator in final data word: 0x%" PRIx32, next_data));
}
return fit::ok(data_stream);
}
fit::result<Error, uint32_t> ArmEhAbiParser::GetFirstDataWord() {
if (data_ > 0) {
return fit::ok(data_);
}
// We have to read from |extab_offset_| to get the first data word.
uint32_t data = 0;
if (auto err = elf_->Read(static_cast<uint64_t>(extab_offset_), data); err.has_err()) {
return fit::error(err);
}
return fit::ok(data);
}
Error ArmEhAbiParser::ExecuteInstructions(Memory* stack, std::span<const uint8_t> bytes,
Registers& next) {
for (size_t i = 0; i < bytes.size(); i++) {
uint8_t byte = bytes[i];
// Modifying SP with an immediate.
if ((byte & 0x80) == 0) {
uint64_t sp;
if (auto err = next.GetSP(sp); err.has_err()) {
return err;
}
// The addend is in the lower 6 bits of this byte.
if (byte & 0x40) {
// sp = sp - (XXXXXX << 2) - 4
// = sp - ((XXXXXX << 2) + 4)
// Make sure to mask away the high bits so we don't accidentally subtract a negative.
sp -= ((static_cast<uint32_t>(byte) & 0x3f) << 2) + 4;
} else {
// sp = sp + (XXXXXXX << 2) + 4
sp += (static_cast<uint32_t>(byte) << 2) + 4;
}
LOG_DEBUG("SP_OFFSET [0x%" PRIx8 "]: new sp: 0x%" PRIx64 "\n", byte, sp);
if (auto err = next.SetSP(sp); err.has_err()) {
return err;
}
} else {
// Have to decode the rest of the bits in the high nibble now that we know that MSB == 1.
switch (byte & 0xf0) {
case 0x80: {
// Pop register values from stack.
// Registers r4-r15 are packed into the low 4 bits of this byte and the entire next byte.
// This mask decodes those low 12 bits and creates a mask where each register is in it's
// respective bit position in the mask (i.e. r15 is in bit 15, r14 in bit 14, etc).
uint32_t register_mask = (((static_cast<uint32_t>(byte & 0x0f)) << 12) |
(static_cast<uint32_t>(bytes[++i])) << 4);
LOG_DEBUG("SET_REGS_FROM_MASK [0x%" PRIx8 "]: mask: 0x%" PRIx32 "\n", byte,
register_mask);
if (auto err = SetRegistersFromMask(stack, register_mask, next); err.has_err()) {
return err;
}
break;
}
case 0x90: {
// Set SP from a register.
uint32_t reg = byte & 0x0f;
if (reg == 13 || reg == 15) {
return Error("Setting SP from register 13 or 15 is disallowed.");
}
uint64_t val;
if (auto err = next.Get(static_cast<RegisterID>(reg), val); err.has_err()) {
return err;
}
LOG_DEBUG("SP_REG [0x%" PRIx8 "]: from reg: %" PRId32 " val: 0x%" PRIx64 "\n", byte, reg,
val);
if (auto err = next.SetSP(val); err.has_err()) {
return err;
}
break;
}
case 0xa0: {
// Pop registers from r4 thru r[4 + N] (inclusive).
// N is encoded in the lower 3 bits. We'll create a register mask same as above, where
// each bit corresponds the register index.
uint32_t n = byte & 0x7;
// Add 1 to |n| to get 1 past the last register we want to include. After shifting,
// subtract 1 to set all the lower bits to 1 and shift up by 4 so the mask ends with r4.
uint32_t register_mask = ((1u << (n + 1)) - 1) << 4;
// If the high bit of the low nibble is set, we also pop r14.
if (byte & 0x08) {
register_mask |= 1 << 14;
}
LOG_DEBUG("SET_REGS_FROM_MASK [0x%" PRIx8 "]: mask: 0x%" PRIx32 "\n", byte,
register_mask);
if (auto err = SetRegistersFromMask(stack, register_mask, next); err.has_err()) {
return err;
}
break;
}
case 0xb0: {
if (byte == kFinishedIndicator) {
// If the lower 4 bits are all 0s then this is the finished indicator. This check is
// outside of the switch below for clarity rather than handling the "0x0" case for the
// lower bits.
break;
}
// Need to do some decoding of the lower 4 bits.
switch (byte & 0x0f) {
case 0x01: {
uint8_t next_byte = bytes[++i];
if (next_byte == 0 || (next_byte & 0xf0) != 0) {
// Spare. ARM Specification indicates that this is an unwinding failure.
return Error("Encountered SPARE opcode, ending unwinding.");
}
// The mask is for r3-r0 (in MSB->LSB order) in the low nibble.
uint32_t register_mask = next_byte & 0xf;
LOG_DEBUG("SET_REGS_FROM_MASK [0x%" PRIx8 "]: maks: 0x%" PRIx32 "\n", byte,
register_mask);
if (auto err = SetRegistersFromMask(stack, register_mask, next); err.has_err()) {
return err;
}
break;
}
case 0x02: {
// vsp = vsp + 0x204 + (uleb128 << 2)
uint64_t uleb_value = 0;
if (auto result = ParseULEB128FromBytes(bytes.subspan(++i)); result.is_ok()) {
uleb_value = result.value();
} else {
return result.error_value();
}
uint64_t sp = 0;
if (auto err = next.GetSP(sp); err.has_err()) {
return err;
}
sp += 0x204 + (uleb_value << 2);
LOG_DEBUG("SP_OFFSET [0x%" PRIx8 "]: modifying sp by offset: 0x%" PRIx64
" new sp: 0x%" PRIx64 "\n",
byte, 0x204 + (uleb_value << 2), sp);
if (auto err = next.SetSP(sp); err.has_err()) {
return err;
}
break;
}
case 0x3: {
// This is for popping double-precision float registers. We ignore the values here for
// now, but we need to update SP accordingly. The rule states that SP is incremented
// by 8N + 4. where N is the distance between the high nibble and the low nibble,
// which mark the beginning and end (inclusive) respectively.
uint8_t next_byte = bytes[++i];
uint8_t high = (next_byte & 0xf0) >> 4;
uint8_t low = next_byte & 0x0f;
// Add one since the range is inclusive.
uint8_t count = low - high + 1;
uint64_t sp = 0;
if (auto err = next.GetSP(sp); err.has_err()) {
return err;
}
sp += (8 * count) + 4;
LOG_DEBUG("POP_VFA [0x%" PRIx8 "]: modifying sp by offset: 0x%" PRIx64
" new sp: 0x%" PRIx64 "\n",
byte, (8 * count) + 4, sp);
if (auto err = next.SetSP(sp); err.has_err()) {
return err;
}
break;
}
}
break;
}
case 0xc0: {
// This opcode is dealing with special double- and quad-word registers. We can ignore the
// register values for now, but we have to make sure we modify SP accordingly.
uint8_t next_byte = bytes[++i];
// |count| does not include the 0th register included in the opcode, so we have to add one
// to the count to get the true value of N.
uint8_t count = (next_byte & 0xf) + 1;
uint64_t sp;
if (auto err = next.GetSP(sp); err.has_err()) {
return err;
}
// This opcode is the "VPUSH" method, which says that SP should be incremented by 8 * N
// where N is the count of registers, which is stored in the low nibble of |next_byte|.
sp += count * 8;
LOG_DEBUG("VPUSH [0x%" PRIx8 "]: modifying sp by offset: %" PRId32 " new sp: 0x%" PRIx64
"\n",
byte, count * 8, sp);
if (auto err = next.SetSP(sp); err.has_err()) {
return err;
}
break;
}
default: {
return Error("OP 0x%" PRIx8 " not implemented yet", byte);
}
}
}
}
return Success();
}
Error ArmEhAbiParser::SetRegistersFromMask(Memory* stack, uint32_t register_mask, Registers& next) {
uint64_t sp;
if (auto err = next.GetSP(sp); err.has_err()) {
return err;
}
bool set_sp = false;
for (size_t i = 0; i < static_cast<size_t>(RegisterID::kArm32_last); i++) {
if (register_mask & (1 << i)) {
uint32_t val;
if (auto err = stack->ReadAndAdvance(sp, val); err.has_err()) {
return err;
}
if (static_cast<RegisterID>(i) == RegisterID::kArm32_sp) {
set_sp = true;
}
LOG_DEBUG("POP_REG: reg: %" PRId64 " new val: 0x%" PRIx32 "\n", i, val);
next.Set(static_cast<RegisterID>(i), val);
}
}
if (!set_sp) {
// SP wasn't popped by the bitmask above, so we set it to the value after popping all registers
// from the mask.
next.SetSP(sp);
}
return Success();
}
} // namespace unwinder