blob: 373d6457d790a5a899f6e98528781e3372e2b3a3 [file] [log] [blame]
// Copyright 2018 The Chromium 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 "components/zucchini/disassembler_dex.h"
#include <stddef.h>
#include <stdlib.h>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <iterator>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "base/strings/stringprintf.h"
#include "components/zucchini/buffer_source.h"
#include "components/zucchini/buffer_view.h"
#include "components/zucchini/io_utils.h"
namespace zucchini {
namespace {
// A DEX item specified by an offset, if absent, has a sentinel value of 0 since
// 0 is never a valid item offset (it points to magic at start of DEX).
constexpr offset_t kDexSentinelOffset = 0U;
// A DEX item specified by an index, if absent, has a sentinel value of
// NO_INDEX = 0xFFFFFFFF. This is represented as an offset_t for uniformity.
constexpr offset_t kDexSentinelIndexAsOffset = 0xFFFFFFFFU;
static_assert(kDexSentinelIndexAsOffset != kInvalidOffset,
"Sentinel should not be confused with invalid offset.");
// Size of a Dalvik instruction unit. Need to cast to signed int because
// sizeof() gives size_t, which dominates when operated on ptrdiff_t, then
// wrecks havoc for base::checked_cast<int16_t>().
constexpr int kInstrUnitSize = static_cast<int>(sizeof(uint16_t));
// Checks if |offset| is byte aligned to 32 bits or 4 bytes.
bool Is32BitAligned(offset_t offset) {
return offset % 4 == 0;
}
// Returns a lower bound for the size of an item of type |type_item_code|.
// - For fixed-length items (e.g., kTypeFieldIdItem) this is the exact size.
// - For variant-length items (e.g., kTypeCodeItem), returns a value that is
// known to be less than the item length (e.g., header size).
// - For items not handled by this function, returns 1 for sanity check.
size_t GetItemBaseSize(uint16_t type_item_code) {
switch (type_item_code) {
case dex::kTypeStringIdItem:
return sizeof(dex::StringIdItem);
case dex::kTypeTypeIdItem:
return sizeof(dex::TypeIdItem);
case dex::kTypeProtoIdItem:
return sizeof(dex::ProtoIdItem);
case dex::kTypeFieldIdItem:
return sizeof(dex::FieldIdItem);
case dex::kTypeMethodIdItem:
return sizeof(dex::MethodIdItem);
case dex::kTypeClassDefItem:
return sizeof(dex::ClassDefItem);
// No need to handle dex::kTypeMapList.
case dex::kTypeTypeList:
return sizeof(uint32_t); // Variable-length.
case dex::kTypeAnnotationSetRefList:
return sizeof(uint32_t); // Variable-length.
case dex::kTypeAnnotationSetItem:
return sizeof(uint32_t); // Variable-length.
case dex::kTypeCodeItem:
return sizeof(dex::CodeItem); // Variable-length.
case dex::kTypeAnnotationsDirectoryItem:
return sizeof(dex::AnnotationsDirectoryItem); // Variable-length.
default:
return 1U; // Unhandled item. For sanity check assume size >= 1.
}
}
/******** CodeItemParser ********/
// A parser to extract successive code items from a DEX image whose header has
// been parsed.
class CodeItemParser {
public:
using size_type = BufferSource::size_type;
explicit CodeItemParser(ConstBufferView image) : image_(image) {}
// Initializes the parser, returns true on success and false on error.
bool Init(const dex::MapItem& code_map_item) {
// Sanity check to quickly fail if |code_map_item.offset| or
// |code_map_item.size| is too large. This is a heuristic because code item
// sizes need to be parsed (sizeof(dex::CodeItem) is a lower bound).
if (!image_.covers_array(code_map_item.offset, code_map_item.size,
sizeof(dex::CodeItem))) {
return false;
}
source_ = std::move(BufferSource(image_).Skip(code_map_item.offset));
return true;
}
// Extracts the header of the next code item, and skips the variable-length
// data. Returns the offset of the code item if successful. Otherwise returns
// kInvalidOffset, and thereafter the parser becomes valid. For reference,
// here's a pseudo-struct of a complete code item:
//
// struct code_item {
// // 4-byte aligned here.
// // 16-byte header defined (dex::CodeItem).
// uint16_t registers_size;
// uint16_t ins_size;
// uint16_t outs_size;
// uint16_t tries_size;
// uint32_t debug_info_off;
// uint32_t insns_size;
//
// // Variable-length data follow.
// uint16_t insns[insns_size]; // Instruction bytes.
// uint16_t padding[(tries_size > 0 && insns_size % 2 == 1) ? 1 : 0];
//
// if (tries_size > 0) {
// // 4-byte aligned here.
// struct try_item { // dex::TryItem.
// uint32_t start_addr;
// uint16_t insn_count;
// uint16_t handler_off;
// } tries[tries_size];
//
// struct encoded_catch_handler_list {
// uleb128 handlers_size;
// struct encoded_catch_handler {
// sleb128 encoded_catch_handler_size;
// struct encoded_type_addr_pair {
// uleb128 type_idx;
// uleb128 addr;
// } handlers[abs(encoded_catch_handler_size)];
// if (encoded_catch_handler_size <= 0) {
// uleb128 catch_all_addr;
// }
// } handlers_list[handlers_size];
// } handlers_group; // Confusingly called "handlers" in DEX doc.
// }
//
// // Padding to 4-bytes align next code_item *only if more exist*.
// }
offset_t GetNext() {
// Read header CodeItem.
if (!source_.AlignOn(image_, 4U))
return kInvalidOffset;
const offset_t code_item_offset =
base::checked_cast<offset_t>(source_.begin() - image_.begin());
const auto* code_item = source_.GetPointer<const dex::CodeItem>();
if (!code_item)
return kInvalidOffset;
DCHECK(Is32BitAligned(code_item_offset));
// TODO(huangs): Fail if |code_item->insns_size == 0| (Constraint A1).
// Skip instruction bytes.
if (!source_.GetArray<uint16_t>(code_item->insns_size))
return kInvalidOffset;
// Skip padding if present.
if (code_item->tries_size > 0 && !source_.AlignOn(image_, 4U))
return kInvalidOffset;
// Skip tries[] and handlers_group to arrive at the next code item. Parsing
// is nontrivial due to use of uleb128 / sleb128.
if (code_item->tries_size > 0) {
// Skip (try_item) tries[].
if (!source_.GetArray<dex::TryItem>(code_item->tries_size))
return kInvalidOffset;
// Skip handlers_group.
uint32_t handlers_size = 0;
if (!source_.GetUleb128(&handlers_size))
return kInvalidOffset;
// Sanity check to quickly reject excessively large |handlers_size|.
if (source_.Remaining() < static_cast<size_type>(handlers_size))
return kInvalidOffset;
// Skip (encoded_catch_handler) handlers_list[].
for (uint32_t k = 0; k < handlers_size; ++k) {
int32_t encoded_catch_handler_size = 0;
if (!source_.GetSleb128(&encoded_catch_handler_size))
return kInvalidOffset;
const size_type abs_size = std::abs(encoded_catch_handler_size);
if (source_.Remaining() < abs_size) // Sanity check.
return kInvalidOffset;
// Skip (encoded_type_addr_pair) handlers[].
for (size_type j = 0; j < abs_size; ++j) {
if (!source_.SkipLeb128() || !source_.SkipLeb128())
return kInvalidOffset;
}
// Skip catch_all_addr.
if (encoded_catch_handler_size <= 0) {
if (!source_.SkipLeb128())
return kInvalidOffset;
}
}
}
// Success! |code_item->insns_size| is validated, but its content is still
// considered unsafe and requires validation.
return code_item_offset;
}
// Given |code_item_offset| that points to the start of a valid code item in
// |image|, returns |insns| bytes as ConstBufferView.
static ConstBufferView GetCodeItemInsns(ConstBufferView image,
offset_t code_item_offset) {
BufferSource source(BufferSource(image).Skip(code_item_offset));
const auto* code_item = source.GetPointer<const dex::CodeItem>();
DCHECK(code_item);
BufferRegion insns{0, code_item->insns_size * kInstrUnitSize};
DCHECK(source.covers(insns));
return source[insns];
}
private:
ConstBufferView image_;
BufferSource source_;
};
/******** InstructionParser ********/
// A class that successively reads |code_item| for Dalvik instructions, which
// are found at |insns|, spanning |insns_size| uint16_t "units". These units
// store instructions followed by optional non-instruction "payload". Finding
// payload boundary requires parsing: On finding an instruction that uses (and
// points to) payload, the boundary is updated.
class InstructionParser {
public:
struct Value {
offset_t instr_offset;
const dex::Instruction* instr = nullptr; // null for unknown instructions.
};
// Returns pointer to DEX Instruction data for |opcode|, or null if |opcode|
// is unknown. An internal initialize-on-first-use table is used for fast
// lookup.
const dex::Instruction* FindDalvikInstruction(uint8_t opcode) {
static bool is_init = false;
static const dex::Instruction* instruction_table[256];
if (!is_init) {
is_init = true;
std::fill(std::begin(instruction_table), std::end(instruction_table),
nullptr);
for (const dex::Instruction& instr : dex::kByteCode) {
std::fill(instruction_table + instr.opcode,
instruction_table + instr.opcode + instr.variant, &instr);
}
}
return instruction_table[opcode];
}
InstructionParser() = default;
InstructionParser(ConstBufferView image, offset_t base_offset)
: image_begin_(image.begin()),
insns_(CodeItemParser::GetCodeItemInsns(image, base_offset)),
payload_boundary_(insns_.end()) {}
// Reads the next instruction. On success, makes the data read available via
// value() and returns true. Otherwise (done or found error) returns false.
bool ReadNext() {
// Do not scan past payload boundary.
if (insns_.begin() >= payload_boundary_)
return false;
const offset_t instr_offset =
base::checked_cast<offset_t>(insns_.begin() - image_begin_);
const uint8_t op = insns_.read<uint8_t>(0);
const dex::Instruction* instr = FindDalvikInstruction(op);
// Stop on finding unknown instructions. ODEX files might trigger this.
if (!instr) {
LOG(WARNING) << "Unknown Dalvik instruction detected at "
<< AsHex<8>(instr_offset) << ".";
return false;
}
const int instr_length_units = instr->layout;
const size_t instr_length_bytes = instr_length_units * kInstrUnitSize;
if (insns_.size() < instr_length_bytes)
return false;
// Handle instructions with variable-length data payload (31t).
if (instr->opcode == 0x26 || // fill-array-data
instr->opcode == 0x2B || // packed-switch
instr->opcode == 0x2C) { // sparse-switch
const int32_t unsafe_payload_rel_units = insns_.read<int32_t>(2);
// Payload must be in current code item, after current instruction.
if (unsafe_payload_rel_units < instr_length_units ||
static_cast<uint32_t>(unsafe_payload_rel_units) >=
insns_.size() / kInstrUnitSize) {
LOG(WARNING) << "Invalid payload found.";
return false;
}
// Update boundary between instructions and payload.
const ConstBufferView::const_iterator payload_it =
insns_.begin() + unsafe_payload_rel_units * kInstrUnitSize;
payload_boundary_ = std::min(payload_boundary_, payload_it);
}
insns_.remove_prefix(instr_length_bytes);
value_ = {instr_offset, instr};
return true;
}
const Value& value() const { return value_; }
private:
ConstBufferView::const_iterator image_begin_;
ConstBufferView insns_;
ConstBufferView::const_iterator payload_boundary_;
Value value_;
};
/******** InstructionReferenceReader ********/
// A class to visit |code_items|, parse instructions, and emit embedded
// References of a type determined by |filter_| and |mapper_|. Only References
// located in |[lo, hi)| are emitted. |lo| and |hi| are assumed to never
// straddle the body of a Reference.
class InstructionReferenceReader : public ReferenceReader {
public:
// A function that takes a parsed Dalvik instruction and decides whether it
// contains a specific type of Reference. If true, then returns the Reference
// location. Otherwise returns kInvalidOffset.
using Filter =
base::RepeatingCallback<offset_t(const InstructionParser::Value&)>;
// A function that takes Reference location from |filter_| to extract the
// stored target. If valid, returns it. Otherwise returns kInvalidOffset.
using Mapper = base::RepeatingCallback<offset_t(offset_t)>;
InstructionReferenceReader(ConstBufferView image,
offset_t lo,
offset_t hi,
const std::vector<offset_t>& code_item_offsets,
Filter&& filter,
Mapper&& mapper)
: image_(image),
lo_(lo),
hi_(hi),
end_it_(code_item_offsets.end()),
filter_(std::move(filter)),
mapper_(std::move(mapper)) {
const auto begin_it = code_item_offsets.begin();
// Use binary search to find the code item that contains |lo_|.
auto comp = [](offset_t test_offset, offset_t code_item_offset) {
return test_offset < code_item_offset;
};
cur_it_ = std::upper_bound(begin_it, end_it_, lo_, comp);
if (cur_it_ != begin_it)
--cur_it_;
parser_ = InstructionParser(image_, *cur_it_);
}
// ReferenceReader:
base::Optional<Reference> GetNext() override {
while (true) {
while (parser_.ReadNext()) {
const auto& v = parser_.value();
DCHECK_NE(v.instr, nullptr);
if (v.instr_offset >= hi_)
return base::nullopt;
const offset_t location = filter_.Run(v);
if (location == kInvalidOffset || location < lo_)
continue;
// The general check is |location + reference_width > hi_|. However, by
// assumption |hi_| and |lo_| do not straddle the body of a Reference.
// So |reference_width| is unneeded.
if (location >= hi_)
return base::nullopt;
offset_t target = mapper_.Run(location);
if (target != kInvalidOffset)
return Reference{location, target};
else
LOG(WARNING) << "Invalid target at " << AsHex<8>(location) << ".";
}
++cur_it_;
if (cur_it_ == end_it_)
return base::nullopt;
parser_ = InstructionParser(image_, *cur_it_);
}
}
private:
const ConstBufferView image_;
const offset_t lo_;
const offset_t hi_;
const std::vector<offset_t>::const_iterator end_it_;
const Filter filter_;
const Mapper mapper_;
std::vector<offset_t>::const_iterator cur_it_;
InstructionParser parser_;
};
/******** ItemReferenceReader ********/
// A class to visit fixed-size item elements (determined by |item_size|) and
// emit a "member variable of interest" (MVI, determined by |rel_location| and
// |mapper|) as Reference. Only MVIs lying in |[lo, hi)| are emitted. |lo| and
// |hi| are assumed to never straddle the body of a Reference.
class ItemReferenceReader : public ReferenceReader {
public:
// A function that takes an MVI's location and emit its target offset.
using Mapper = base::RepeatingCallback<offset_t(offset_t)>;
// |item_size| is the size of a fixed-size item. |rel_location| is the
// relative location of MVI from the start of the item containing it.
ItemReferenceReader(offset_t lo,
offset_t hi,
const dex::MapItem& map_item,
size_t item_size,
size_t rel_location,
Mapper&& mapper)
: hi_(hi),
item_base_offset_(base::checked_cast<offset_t>(map_item.offset)),
num_items_(base::checked_cast<uint32_t>(map_item.size)),
item_size_(base::checked_cast<uint32_t>(item_size)),
rel_location_(base::checked_cast<uint32_t>(rel_location)),
mapper_(std::move(mapper)) {
static_assert(sizeof(decltype(map_item.offset)) <= sizeof(offset_t),
"map_item.offset too large.");
static_assert(sizeof(decltype(map_item.size)) <= sizeof(offset_t),
"map_item.size too large.");
if (!item_base_offset_) {
// Empty item: Assign |cur_idx| to |num_items_| to skip everything.
cur_idx_ = num_items_;
} else if (lo < item_base_offset_) {
cur_idx_ = 0;
} else if (lo < OffsetOfIndex(num_items_)) {
cur_idx_ = (lo - item_base_offset_) / item_size_;
// Fine-tune: Advance if |lo| lies beyond the MVI.
if (lo > OffsetOfIndex(cur_idx_) + rel_location_)
++cur_idx_;
} else {
cur_idx_ = num_items_;
}
}
// ReferenceReader:
base::Optional<Reference> GetNext() override {
while (cur_idx_ < num_items_) {
const offset_t item_offset = OffsetOfIndex(cur_idx_);
const offset_t location = item_offset + rel_location_;
// The general check is |location + reference_width > hi_|. However, by
// assumption |hi_| and |lo_| do not straddle the body of a Reference. So
// |reference_width| is unneeded.
if (location >= hi_)
break;
const offset_t target = mapper_.Run(location);
// kDexSentinelOffset (0) may appear for the following:
// - ProtoIdItem: parameters_off.
// - ClassDefItem: interfaces_off, annotations_off, class_data_off,
// static_values_off.
// - AnnotationsDirectoryItem: class_annotations_off.
// - AnnotationSetRefItem: annotations_off.
// kDexSentinelIndexAsOffset (0xFFFFFFFF) may appear for the following:
// - ClassDefItem: superclass_idx, source_file_idx.
if (target == kDexSentinelOffset || target == kDexSentinelIndexAsOffset) {
++cur_idx_;
continue;
}
if (target == kInvalidOffset) {
LOG(WARNING) << "Invalid item target at " << AsHex<8>(location) << ".";
break;
}
++cur_idx_;
return Reference{location, target};
}
return base::nullopt;
}
private:
offset_t OffsetOfIndex(uint32_t idx) {
return base::checked_cast<uint32_t>(item_base_offset_ + idx * item_size_);
}
const offset_t hi_;
const offset_t item_base_offset_;
const uint32_t num_items_;
const uint32_t item_size_;
const uint32_t rel_location_;
const Mapper mapper_;
offset_t cur_idx_ = 0;
};
// Parses a flattened jagged list of lists of items that looks like:
// NTTT|NTT|NTTTT|N|NTT...
// where |N| is an uint32_t representing the number of items in each sub-list,
// and "T" is a fixed-size item (|item_width|) of type "T". On success, stores
// the offset of each |T| into |item_offsets|, and returns true. Otherwise
// (e.g., on finding any structural problem) returns false.
bool ParseItemOffsets(ConstBufferView image,
const dex::MapItem& map_item,
size_t item_width,
std::vector<offset_t>* item_offsets) {
// Sanity check: |image| should at least fit |map_item.size| copies of "N".
if (!image.covers_array(map_item.offset, map_item.size, sizeof(uint32_t)))
return false;
BufferSource source = std::move(BufferSource(image).Skip(map_item.offset));
item_offsets->clear();
for (uint32_t i = 0; i < map_item.size; ++i) {
if (!source.AlignOn(image, 4U))
return false;
uint32_t unsafe_size;
if (!source.GetValue<uint32_t>(&unsafe_size))
return false;
DCHECK(Is32BitAligned(
base::checked_cast<offset_t>(source.begin() - image.begin())));
if (!source.covers_array(0, unsafe_size, item_width))
return false;
for (uint32_t j = 0; j < unsafe_size; ++j) {
item_offsets->push_back(
base::checked_cast<offset_t>(source.begin() - image.begin()));
source.Skip(item_width);
}
}
return true;
}
// Parses AnnotationDirectoryItems of the format (using RegEx) "(AF*M*P*)*",
// where:
// A = AnnotationsDirectoryItem (contains class annotation),
// F = FieldAnnotation,
// M = MethodAnnotation,
// P = ParameterAnnotation.
// On success, stores the offsets of each class, field, method and parameter
// annotation for each item into |*_annotation_offsets|. Otherwise on finding
// structural issues returns false.
bool ParseAnnotationsDirectoryItems(
ConstBufferView image,
const dex::MapItem& annotations_directory_map_item,
std::vector<offset_t>* annotations_directory_item_offsets,
std::vector<offset_t>* field_annotation_offsets,
std::vector<offset_t>* method_annotation_offsets,
std::vector<offset_t>* parameter_annotation_offsets) {
// Sanity check: |image| should at least fit
// |annotations_directory_map_item.size| copies of "A".
if (!image.covers_array(annotations_directory_map_item.offset,
annotations_directory_map_item.size,
sizeof(dex::AnnotationsDirectoryItem))) {
return false;
}
BufferSource source = std::move(
BufferSource(image).Skip(annotations_directory_map_item.offset));
annotations_directory_item_offsets->clear();
field_annotation_offsets->clear();
method_annotation_offsets->clear();
parameter_annotation_offsets->clear();
// Helper to process sublists.
auto parse_list = [&source, image](uint32_t unsafe_size, size_t item_width,
std::vector<offset_t>* item_offsets) {
DCHECK(Is32BitAligned(
base::checked_cast<offset_t>(source.begin() - image.begin())));
if (!source.covers_array(0, unsafe_size, item_width))
return false;
item_offsets->reserve(item_offsets->size() + unsafe_size);
for (uint32_t i = 0; i < unsafe_size; ++i) {
item_offsets->push_back(
base::checked_cast<offset_t>(source.begin() - image.begin()));
source.Skip(item_width);
}
return true;
};
annotations_directory_item_offsets->reserve(
annotations_directory_map_item.size);
for (uint32_t i = 0; i < annotations_directory_map_item.size; ++i) {
if (!source.AlignOn(image, 4U))
return false;
// Parse header.
annotations_directory_item_offsets->push_back(
base::checked_cast<offset_t>(source.begin() - image.begin()));
dex::AnnotationsDirectoryItem unsafe_annotations_directory_item;
if (!source.GetValue(&unsafe_annotations_directory_item))
return false;
// Parse sublists.
if (!(parse_list(unsafe_annotations_directory_item.fields_size,
sizeof(dex::FieldAnnotation), field_annotation_offsets) &&
parse_list(unsafe_annotations_directory_item.annotated_methods_size,
sizeof(dex::MethodAnnotation),
method_annotation_offsets) &&
parse_list(
unsafe_annotations_directory_item.annotated_parameters_size,
sizeof(dex::ParameterAnnotation),
parameter_annotation_offsets))) {
return false;
}
}
return true;
}
/******** CachedItemListReferenceReader ********/
// A class that takes sorted |item_offsets|, and emits all member variable of
// interest (MVIs) that fall inside |[lo, hi)|. The MVI of each item has
// location of |rel_location| from item offset, and has target extracted with
// |mapper| (which performs validation). By the "atomicity assumption",
// [|lo, hi)| never cut across an MVI.
class CachedItemListReferenceReader : public ReferenceReader {
public:
// A function that takes an MVI's location and emit its target offset.
using Mapper = base::RepeatingCallback<offset_t(offset_t)>;
CachedItemListReferenceReader(offset_t lo,
offset_t hi,
uint32_t rel_location,
const std::vector<offset_t>& item_offsets,
Mapper&& mapper)
: hi_(hi),
rel_location_(rel_location),
end_it_(item_offsets.cend()),
mapper_(mapper) {
cur_it_ = std::upper_bound(item_offsets.cbegin(), item_offsets.cend(), lo);
// Adding |rel_location_| is necessary as references can be offset from the
// start of the item.
if (cur_it_ != item_offsets.begin() && *(cur_it_ - 1) + rel_location_ >= lo)
--cur_it_;
}
// ReferenceReader:
base::Optional<Reference> GetNext() override {
while (cur_it_ < end_it_) {
const offset_t location = *cur_it_ + rel_location_;
if (location >= hi_) // Check is simplified by atomicity assumption.
break;
const offset_t target = mapper_.Run(location);
if (target == kInvalidOffset) {
LOG(WARNING) << "Invalid item target at " << AsHex<8>(location) << ".";
break;
}
++cur_it_;
// kDexSentinelOffset is a sentinel for;
// - AnnotationsDirectoryItem: class_annotations_off
if (target == kDexSentinelOffset)
continue;
return Reference{location, target};
}
return base::nullopt;
}
private:
const offset_t hi_;
const uint32_t rel_location_;
const std::vector<offset_t>::const_iterator end_it_;
const Mapper mapper_;
std::vector<offset_t>::const_iterator cur_it_;
DISALLOW_COPY_AND_ASSIGN(CachedItemListReferenceReader);
};
// Reads an INT index at |location| in |image| and translates the index to the
// offset of a fixed-size item specified by |target_map_item| and
// |target_item_size|. Returns the target offset if valid, or kInvalidOffset
// otherwise. This is compatible with
// CachedReferenceListReferenceReader::Mapper,
// InstructionReferenceReader::Mapper, and ItemReferenceReader::Mapper.
template <typename INT>
static offset_t ReadTargetIndex(ConstBufferView image,
const dex::MapItem& target_map_item,
size_t target_item_size,
offset_t location) {
static_assert(sizeof(INT) <= sizeof(offset_t),
"INT may not fit into offset_t.");
const offset_t unsafe_idx = image.read<INT>(location);
// kDexSentinalIndexAsOffset (0xFFFFFFFF) is a sentinel for
// - ClassDefItem: superclass_idx, source_file_idx.
if (unsafe_idx == kDexSentinelIndexAsOffset)
return unsafe_idx;
if (unsafe_idx >= target_map_item.size)
return kInvalidOffset;
return target_map_item.offset +
base::checked_cast<offset_t>(unsafe_idx * target_item_size);
}
// Reads uint32_t value in |image| at (valid) |location| and checks whether it
// is a safe offset of a fixed-size item. Returns the target offset (possibly a
// sentinel) if valid, or kInvalidOffset otherwise. This is compatible with
// CachedReferenceListReferenceReader::Mapper,
// InstructionReferenceReader::Mapper, and ItemReferenceReader::Mapper.
static offset_t ReadTargetOffset32(ConstBufferView image, offset_t location) {
const offset_t unsafe_target =
static_cast<offset_t>(image.read<uint32_t>(location));
// Skip and don't validate kDexSentinelOffset as it is indicative of an
// empty reference.
if (unsafe_target == kDexSentinelOffset)
return unsafe_target;
// TODO(huangs): Check that |unsafe_target| is within the correct data
// section.
if (unsafe_target >= image.size())
return kInvalidOffset;
return unsafe_target;
}
/******** ReferenceWriterAdaptor ********/
// A ReferenceWriter that adapts a callback that performs type-specific
// Reference writes.
class ReferenceWriterAdaptor : public ReferenceWriter {
public:
using Writer = base::RepeatingCallback<void(Reference, MutableBufferView)>;
ReferenceWriterAdaptor(MutableBufferView image, Writer&& writer)
: image_(image), writer_(std::move(writer)) {}
// ReferenceWriter:
void PutNext(Reference ref) override { writer_.Run(ref, image_); }
private:
MutableBufferView image_;
Writer writer_;
};
// Helper that's compatible with ReferenceWriterAdaptor::Writer.
// Given that |ref.target| points to the start of a fixed size DEX item (e.g.,
// FieldIdItem), translates |ref.target| to item index, and writes the result to
// |ref.location| as |INT|.
template <typename INT>
static void WriteTargetIndex(const dex::MapItem& target_map_item,
size_t target_item_size,
Reference ref,
MutableBufferView image) {
const size_t unsafe_idx =
(ref.target - target_map_item.offset) / target_item_size;
// Verify that index is within bound.
if (unsafe_idx >= target_map_item.size) {
LOG(ERROR) << "Target index out of bounds at: " << AsHex<8>(ref.location)
<< ".";
return;
}
// Verify that |ref.target| points to start of item.
DCHECK_EQ(ref.target, target_map_item.offset + unsafe_idx * target_item_size);
image.write<INT>(ref.location, base::checked_cast<INT>(unsafe_idx));
}
// Buffer for ReadDexHeader() to optionally return results.
struct ReadDexHeaderResults {
BufferSource source;
const dex::HeaderItem* header;
int dex_version;
};
// Returns whether |image| points to a DEX file. If this is a possibility and
// |opt_results| is not null, then uses it to pass extracted data to enable
// further parsing.
bool ReadDexHeader(ConstBufferView image, ReadDexHeaderResults* opt_results) {
// This part needs to be fairly efficient since it may be called many times.
BufferSource source(image);
const dex::HeaderItem* header = source.GetPointer<dex::HeaderItem>();
if (!header)
return false;
if (header->magic[0] != 'd' || header->magic[1] != 'e' ||
header->magic[2] != 'x' || header->magic[3] != '\n' ||
header->magic[7] != '\0') {
return false;
}
// Magic matches: More detailed tests can be conducted.
int dex_version = 0;
for (int i = 4; i < 7; ++i) {
if (!isdigit(header->magic[i]))
return false;
dex_version = dex_version * 10 + (header->magic[i] - '0');
}
// Only support DEX versions 35 and 37.
// TODO(huangs): Handle version 38.
if (dex_version != 35 && dex_version != 37)
return false;
if (header->file_size > image.size() ||
header->file_size < sizeof(dex::HeaderItem) ||
header->map_off < sizeof(dex::HeaderItem)) {
return false;
}
if (opt_results)
*opt_results = {source, header, dex_version};
return true;
}
} // namespace
/******** DisassemblerDex ********/
DisassemblerDex::DisassemblerDex() : Disassembler(4) {}
DisassemblerDex::~DisassemblerDex() = default;
// static.
bool DisassemblerDex::QuickDetect(ConstBufferView image) {
return ReadDexHeader(image, nullptr);
}
ExecutableType DisassemblerDex::GetExeType() const {
return kExeTypeDex;
}
std::string DisassemblerDex::GetExeTypeString() const {
return base::StringPrintf("DEX (version %d)", dex_version_);
}
std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const {
// Must follow DisassemblerDex::ReferenceType order. Initialized on first use.
return {
{{4, TypeTag(kTypeIdToDescriptorStringId), PoolTag(kStringId)},
&DisassemblerDex::MakeReadTypeIdToDescriptorStringId32,
&DisassemblerDex::MakeWriteStringId32},
{{4, TypeTag(kProtoIdToShortyStringId), PoolTag(kStringId)},
&DisassemblerDex::MakeReadProtoIdToShortyStringId32,
&DisassemblerDex::MakeWriteStringId32},
{{4, TypeTag(kFieldIdToNameStringId), PoolTag(kStringId)},
&DisassemblerDex::MakeReadFieldToNameStringId32,
&DisassemblerDex::MakeWriteStringId32},
{{4, TypeTag(kMethodIdToNameStringId), PoolTag(kStringId)},
&DisassemblerDex::MakeReadMethodIdToNameStringId32,
&DisassemblerDex::MakeWriteStringId32},
{{4, TypeTag(kClassDefToSourceFileStringId), PoolTag(kStringId)},
&DisassemblerDex::MakeReadClassDefToSourceFileStringId32,
&DisassemblerDex::MakeWriteStringId32},
{{2, TypeTag(kCodeToStringId16), PoolTag(kStringId)},
&DisassemblerDex::MakeReadCodeToStringId16,
&DisassemblerDex::MakeWriteStringId16},
{{4, TypeTag(kCodeToStringId32), PoolTag(kStringId)},
&DisassemblerDex::MakeReadCodeToStringId32,
&DisassemblerDex::MakeWriteStringId32},
{{4, TypeTag(kProtoIdToReturnTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadProtoIdToReturnTypeId32,
&DisassemblerDex::MakeWriteTypeId32},
{{2, TypeTag(kFieldIdToClassTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadFieldToClassTypeId16,
&DisassemblerDex::MakeWriteTypeId16},
{{2, TypeTag(kFieldIdToTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadFieldToTypeId16,
&DisassemblerDex::MakeWriteTypeId16},
{{2, TypeTag(kMethodIdToClassTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadMethodIdToClassTypeId16,
&DisassemblerDex::MakeWriteTypeId16},
{{4, TypeTag(kClassDefToClassTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadClassDefToClassTypeId32,
&DisassemblerDex::MakeWriteTypeId32},
{{4, TypeTag(kClassDefToSuperClassTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadClassDefToSuperClassTypeId32,
&DisassemblerDex::MakeWriteTypeId32},
{{2, TypeTag(kTypeListToTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadTypeListToTypeId16,
&DisassemblerDex::MakeWriteTypeId16},
{{2, TypeTag(kCodeToTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadCodeToTypeId16,
&DisassemblerDex::MakeWriteTypeId16},
{{2, TypeTag(kMethodIdToProtoId), PoolTag(kProtoId)},
&DisassemblerDex::MakeReadMethodIdToProtoId16,
&DisassemblerDex::MakeWriteProtoId16},
{{2, TypeTag(kCodeToFieldId), PoolTag(kFieldId)},
&DisassemblerDex::MakeReadCodeToFieldId16,
&DisassemblerDex::MakeWriteFieldId16},
{{4, TypeTag(kAnnotationsDirectoryToFieldId), PoolTag(kFieldId)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToFieldId32,
&DisassemblerDex::MakeWriteFieldId32},
{{2, TypeTag(kCodeToMethodId), PoolTag(kMethodId)},
&DisassemblerDex::MakeReadCodeToMethodId16,
&DisassemblerDex::MakeWriteMethodId16},
{{4, TypeTag(kAnnotationsDirectoryToMethodId), PoolTag(kMethodId)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToMethodId32,
&DisassemblerDex::MakeWriteMethodId32},
{{4, TypeTag(kAnnotationsDirectoryToParameterMethodId),
PoolTag(kMethodId)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToParameterMethodId32,
&DisassemblerDex::MakeWriteMethodId32},
{{4, TypeTag(kProtoIdToParametersTypeList), PoolTag(kTypeList)},
&DisassemblerDex::MakeReadProtoIdToParametersTypeList,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kClassDefToInterfacesTypeList), PoolTag(kTypeList)},
&DisassemblerDex::MakeReadClassDefToInterfacesTypeList,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kAnnotationsDirectoryToParameterAnnotationSetRef),
PoolTag(kAnnotationSetRefList)},
&DisassemblerDex::
MakeReadAnnotationsDirectoryToParameterAnnotationSetRef,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kAnnotationSetRefListToAnnotationSet),
PoolTag(kAnnotionSet)},
&DisassemblerDex::MakeReadAnnotationSetRefListToAnnotationSet,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kAnnotationsDirectoryToClassAnnotationSet),
PoolTag(kAnnotionSet)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToClassAnnotationSet,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kAnnotationsDirectoryToFieldAnnotationSet),
PoolTag(kAnnotionSet)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToFieldAnnotationSet,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kAnnotationsDirectoryToMethodAnnotationSet),
PoolTag(kAnnotionSet)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToMethodAnnotationSet,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kClassDefToClassData), PoolTag(kClassData)},
&DisassemblerDex::MakeReadClassDefToClassData,
&DisassemblerDex::MakeWriteAbs32},
{{1, TypeTag(kCodeToRelCode8), PoolTag(kCode)},
&DisassemblerDex::MakeReadCodeToRelCode8,
&DisassemblerDex::MakeWriteRelCode8},
{{2, TypeTag(kCodeToRelCode16), PoolTag(kCode)},
&DisassemblerDex::MakeReadCodeToRelCode16,
&DisassemblerDex::MakeWriteRelCode16},
{{4, TypeTag(kCodeToRelCode32), PoolTag(kCode)},
&DisassemblerDex::MakeReadCodeToRelCode32,
&DisassemblerDex::MakeWriteRelCode32},
{{4, TypeTag(kStringIdToStringData), PoolTag(kStringData)},
&DisassemblerDex::MakeReadStringIdToStringData,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kAnnotationSetToAnnotation), PoolTag(kAnnotation)},
&DisassemblerDex::MakeReadAnnotationSetToAnnotation,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kClassDefToStaticValuesEncodedArray),
PoolTag(kEncodedArray)},
&DisassemblerDex::MakeReadClassDefToStaticValuesEncodedArray,
&DisassemblerDex::MakeWriteAbs32},
{{4, TypeTag(kClassDefToAnnotationDirectory),
PoolTag(kAnnotationsDirectory)},
&DisassemblerDex::MakeReadClassDefToAnnotationDirectory,
&DisassemblerDex::MakeWriteAbs32},
};
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadStringIdToStringData(
offset_t lo,
offset_t hi) {
// dex::StringIdItem::string_data_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<ItemReferenceReader>(
lo, hi, string_map_item_, sizeof(dex::StringIdItem),
offsetof(dex::StringIdItem, string_data_off), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadTypeIdToDescriptorStringId32(offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::TypeIdItem::descriptor_idx)>, image_,
string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, type_map_item_, sizeof(dex::TypeIdItem),
offsetof(dex::TypeIdItem, descriptor_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadProtoIdToShortyStringId32(offset_t lo, offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::ProtoIdItem::shorty_idx)>, image_,
string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, proto_map_item_, sizeof(dex::ProtoIdItem),
offsetof(dex::ProtoIdItem, shorty_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadProtoIdToReturnTypeId32(offset_t lo, offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::ProtoIdItem::return_type_idx)>, image_,
type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, proto_map_item_, sizeof(dex::ProtoIdItem),
offsetof(dex::ProtoIdItem, return_type_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadProtoIdToParametersTypeList(offset_t lo, offset_t hi) {
// dex::ProtoIdItem::parameters_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<ItemReferenceReader>(
lo, hi, proto_map_item_, sizeof(dex::ProtoIdItem),
offsetof(dex::ProtoIdItem, parameters_off), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadFieldToClassTypeId16(
offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::FieldIdItem::class_idx)>, image_,
type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, field_map_item_, sizeof(dex::FieldIdItem),
offsetof(dex::FieldIdItem, class_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadFieldToTypeId16(
offset_t lo,
offset_t hi) {
auto mapper =
base::BindRepeating(ReadTargetIndex<decltype(dex::FieldIdItem::type_idx)>,
image_, type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, field_map_item_, sizeof(dex::FieldIdItem),
offsetof(dex::FieldIdItem, type_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadFieldToNameStringId32(
offset_t lo,
offset_t hi) {
auto mapper =
base::BindRepeating(ReadTargetIndex<decltype(dex::FieldIdItem::name_idx)>,
image_, string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, field_map_item_, sizeof(dex::FieldIdItem),
offsetof(dex::FieldIdItem, name_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadMethodIdToClassTypeId16(offset_t lo, offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::MethodIdItem::class_idx)>, image_,
type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, method_map_item_, sizeof(dex::MethodIdItem),
offsetof(dex::MethodIdItem, class_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadMethodIdToProtoId16(
offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::MethodIdItem::proto_idx)>, image_,
proto_map_item_, sizeof(dex::ProtoIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, method_map_item_, sizeof(dex::MethodIdItem),
offsetof(dex::MethodIdItem, proto_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadMethodIdToNameStringId32(offset_t lo, offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::MethodIdItem::name_idx)>, image_,
string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, method_map_item_, sizeof(dex::MethodIdItem),
offsetof(dex::MethodIdItem, name_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadClassDefToClassTypeId32(offset_t lo, offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::ClassDefItem::superclass_idx)>, image_,
type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, class_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadClassDefToSuperClassTypeId32(offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::ClassDefItem::superclass_idx)>, image_,
type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, superclass_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadClassDefToInterfacesTypeList(offset_t lo,
offset_t hi) {
// dex::ClassDefItem::interfaces_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, interfaces_off), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadClassDefToSourceFileStringId32(offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::ClassDefItem::source_file_idx)>, image_,
string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, source_file_idx), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadClassDefToAnnotationDirectory(offset_t lo,
offset_t hi) {
// dex::ClassDefItem::annotations_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, annotations_off), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadClassDefToClassData(
offset_t lo,
offset_t hi) {
// dex::ClassDefItem::class_data_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, class_data_off), std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadClassDefToStaticValuesEncodedArray(offset_t lo,
offset_t hi) {
// dex::ClassDefItem::static_values_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<ItemReferenceReader>(
lo, hi, class_def_map_item_, sizeof(dex::ClassDefItem),
offsetof(dex::ClassDefItem, static_values_off), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadTypeListToTypeId16(
offset_t lo,
offset_t hi) {
auto mapper =
base::BindRepeating(ReadTargetIndex<decltype(dex::TypeItem::type_idx)>,
image_, type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::TypeItem, type_idx), type_list_offsets_,
std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationSetToAnnotation(offset_t lo, offset_t hi) {
// dex::AnnotationOffItem::annotation_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::AnnotationOffItem, annotation_off),
annotation_set_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationSetRefListToAnnotationSet(offset_t lo,
offset_t hi) {
// dex::AnnotationSetRefItem::annotations_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::AnnotationSetRefItem, annotations_off),
annotation_set_ref_list_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToClassAnnotationSet(offset_t lo,
offset_t hi) {
// dex::AnnotationsDirectoryItem::class_annotations_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::AnnotationsDirectoryItem, class_annotations_off),
annotations_directory_item_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToFieldId32(offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::FieldAnnotation::field_idx)>, image_,
field_map_item_, sizeof(dex::FieldIdItem));
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::FieldAnnotation, field_idx),
annotations_directory_item_field_annotation_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToFieldAnnotationSet(offset_t lo,
offset_t hi) {
// dex::FieldAnnotation::annotations_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::FieldAnnotation, annotations_off),
annotations_directory_item_field_annotation_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToMethodId32(offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::MethodAnnotation::method_idx)>, image_,
method_map_item_, sizeof(dex::MethodIdItem));
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::MethodAnnotation, method_idx),
annotations_directory_item_method_annotation_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToMethodAnnotationSet(
offset_t lo,
offset_t hi) {
// dex::MethodAnnotation::annotations_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::MethodAnnotation, annotations_off),
annotations_directory_item_method_annotation_offsets_, std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToParameterMethodId32(
offset_t lo,
offset_t hi) {
auto mapper = base::BindRepeating(
ReadTargetIndex<decltype(dex::ParameterAnnotation::method_idx)>, image_,
method_map_item_, sizeof(dex::MethodIdItem));
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::ParameterAnnotation, method_idx),
annotations_directory_item_parameter_annotation_offsets_,
std::move(mapper));
}
std::unique_ptr<ReferenceReader>
DisassemblerDex::MakeReadAnnotationsDirectoryToParameterAnnotationSetRef(
offset_t lo,
offset_t hi) {
// dex::ParameterAnnotation::annotations_off mapper.
auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
return std::make_unique<CachedItemListReferenceReader>(
lo, hi, offsetof(dex::ParameterAnnotation, annotations_off),
annotations_directory_item_parameter_annotation_offsets_,
std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToStringId16(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::c &&
(value.instr->opcode == 0x1A)) { // const-string
// BBBB from e.g., const-string vAA, string@BBBB.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper =
base::BindRepeating(ReadTargetIndex<uint16_t>, image_, string_map_item_,
sizeof(dex::StringIdItem));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToStringId32(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::c &&
(value.instr->opcode == 0x1B)) { // const-string/jumbo
// BBBBBBBB from e.g., const-string/jumbo vAA, string@BBBBBBBB.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper =
base::BindRepeating(ReadTargetIndex<uint32_t>, image_, string_map_item_,
sizeof(dex::StringIdItem));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToTypeId16(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::c &&
(value.instr->opcode == 0x1C || // const-class
value.instr->opcode == 0x1F || // check-cast
value.instr->opcode == 0x20 || // instance-of
value.instr->opcode == 0x22 || // new-instance
value.instr->opcode == 0x23 || // new-array
value.instr->opcode == 0x24 || // filled-new-array
value.instr->opcode == 0x25)) { // filled-new-array/range
// BBBB from e.g., const-class vAA, type@BBBB.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
type_map_item_, sizeof(dex::TypeIdItem));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToFieldId16(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::c &&
(value.instr->opcode == 0x52 || // iinstanceop (iget-*, iput-*)
value.instr->opcode == 0x60)) { // sstaticop (sget-*, sput-*)
// CCCC from e.g., iget vA, vB, field@CCCC.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
field_map_item_, sizeof(dex::FieldIdItem));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToMethodId16(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::c &&
(value.instr->opcode == 0x6E || // invoke-kind
value.instr->opcode == 0x74)) { // invoke-kind/range
// BBBB from e.g., invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper =
base::BindRepeating(ReadTargetIndex<uint16_t>, image_, method_map_item_,
sizeof(dex::MethodIdItem));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToRelCode8(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::t &&
value.instr->opcode == 0x28) { // goto
// +AA from e.g., goto +AA.
return value.instr_offset + 1;
}
return kInvalidOffset;
});
auto mapper = base::BindRepeating(
[](DisassemblerDex* dis, offset_t location) {
// Address is relative to the current instruction, which begins 1 unit
// before |location|. This needs to be subtracted out. Also, store as
// int32_t so |unsafe_delta - 1| won't underflow!
int32_t unsafe_delta = dis->image_.read<int8_t>(location);
offset_t unsafe_target = static_cast<offset_t>(
location + (unsafe_delta - 1) * kInstrUnitSize);
// TODO(huangs): Check that |unsafe_target| stays within code item.
return unsafe_target;
},
base::Unretained(this));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToRelCode16(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::t &&
(value.instr->opcode == 0x29 || // goto/16
value.instr->opcode == 0x32 || // if-test
value.instr->opcode == 0x38)) { // if-testz
// +AAAA from e.g., goto/16 +AAAA.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper = base::BindRepeating(
[](DisassemblerDex* dis, offset_t location) {
// Address is relative to the current instruction, which begins 1 unit
// before |location|. This needs to be subtracted out. Also, store as
// int32_t so |unsafe_delta - 1| won't underflow!
int32_t unsafe_delta = dis->image_.read<int16_t>(location);
offset_t unsafe_target = static_cast<offset_t>(
location + (unsafe_delta - 1) * kInstrUnitSize);
// TODO(huangs): Check that |unsafe_target| stays within code item.
return unsafe_target;
},
base::Unretained(this));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToRelCode32(
offset_t lo,
offset_t hi) {
auto filter = base::BindRepeating(
[](const InstructionParser::Value& value) -> offset_t {
if (value.instr->format == dex::FormatId::t &&
(value.instr->opcode == 0x26 || // fill-array-data
value.instr->opcode == 0x2A || // goto/32
value.instr->opcode == 0x2B || // packed-switch
value.instr->opcode == 0x2C)) { // sparse-switch
// +BBBBBBBB from e.g., fill-array-data vAA, +BBBBBBBB.
// +AAAAAAAA from e.g., goto/32 +AAAAAAAA.
return value.instr_offset + 2;
}
return kInvalidOffset;
});
auto mapper = base::BindRepeating(
[](DisassemblerDex* dis, offset_t location) {
// Address is relative to the current instruction, which begins 1 unit
// before |location|. This needs to be subtracted out. Use int64_t to
// avoid underflow and overflow.
int64_t unsafe_delta = dis->image_.read<int32_t>(location);
int64_t unsafe_target = location + (unsafe_delta - 1) * kInstrUnitSize;
// TODO(huangs): Check that |unsafe_target| stays within code item.
offset_t checked_unsafe_target =
static_cast<offset_t>(base::CheckedNumeric<offset_t>(unsafe_target)
.ValueOrDefault(kInvalidOffset));
return checked_unsafe_target < kOffsetBound ? checked_unsafe_target
: kInvalidOffset;
},
base::Unretained(this));
return std::make_unique<InstructionReferenceReader>(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteStringId16(
MutableBufferView image) {
auto writer = base::BindRepeating(
WriteTargetIndex<uint16_t>, string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteStringId32(
MutableBufferView image) {
auto writer = base::BindRepeating(
WriteTargetIndex<uint32_t>, string_map_item_, sizeof(dex::StringIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteTypeId16(
MutableBufferView image) {
auto writer = base::BindRepeating(WriteTargetIndex<uint16_t>, type_map_item_,
sizeof(dex::TypeIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteTypeId32(
MutableBufferView image) {
auto writer = base::BindRepeating(WriteTargetIndex<uint32_t>, type_map_item_,
sizeof(dex::TypeIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteProtoId16(
MutableBufferView image) {
auto writer = base::BindRepeating(WriteTargetIndex<uint16_t>, proto_map_item_,
sizeof(dex::ProtoIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteFieldId16(
MutableBufferView image) {
auto writer = base::BindRepeating(WriteTargetIndex<uint16_t>, field_map_item_,
sizeof(dex::FieldIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteFieldId32(
MutableBufferView image) {
auto writer = base::BindRepeating(WriteTargetIndex<uint32_t>, field_map_item_,
sizeof(dex::FieldIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodId16(
MutableBufferView image) {
auto writer = base::BindRepeating(
WriteTargetIndex<uint16_t>, method_map_item_, sizeof(dex::MethodIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodId32(
MutableBufferView image) {
auto writer = base::BindRepeating(
WriteTargetIndex<uint32_t>, method_map_item_, sizeof(dex::MethodIdItem));
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteRelCode8(
MutableBufferView image) {
auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) {
ptrdiff_t unsafe_byte_diff =
static_cast<ptrdiff_t>(ref.target) - ref.location;
DCHECK_EQ(0, unsafe_byte_diff % kInstrUnitSize);
// |delta| is relative to start of instruction, which is 1 unit before
// |ref.location|. The subtraction above removed too much, so +1 to fix.
base::CheckedNumeric<int8_t> delta((unsafe_byte_diff / kInstrUnitSize) + 1);
if (!delta.IsValid()) {
LOG(ERROR) << "Invalid reference at: " << AsHex<8>(ref.location) << ".";
return;
}
image.write<int8_t>(ref.location, delta.ValueOrDie());
});
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteRelCode16(
MutableBufferView image) {
auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) {
ptrdiff_t unsafe_byte_diff =
static_cast<ptrdiff_t>(ref.target) - ref.location;
DCHECK_EQ(0, unsafe_byte_diff % kInstrUnitSize);
// |delta| is relative to start of instruction, which is 1 unit before
// |ref.location|. The subtraction above removed too much, so +1 to fix.
base::CheckedNumeric<int16_t> delta((unsafe_byte_diff / kInstrUnitSize) +
1);
if (!delta.IsValid()) {
LOG(ERROR) << "Invalid reference at: " << AsHex<8>(ref.location) << ".";
return;
}
image.write<int16_t>(ref.location, delta.ValueOrDie());
});
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteRelCode32(
MutableBufferView image) {
auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) {
ptrdiff_t unsafe_byte_diff =
static_cast<ptrdiff_t>(ref.target) - ref.location;
DCHECK_EQ(0, unsafe_byte_diff % kInstrUnitSize);
// |delta| is relative to start of instruction, which is 1 unit before
// |ref.location|. The subtraction above removed too much, so +1 to fix.
base::CheckedNumeric<int32_t> delta((unsafe_byte_diff / kInstrUnitSize) +
1);
if (!delta.IsValid()) {
LOG(ERROR) << "Invalid reference at: " << AsHex<8>(ref.location) << ".";
return;
}
image.write<int32_t>(ref.location, delta.ValueOrDie());
});
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteAbs32(
MutableBufferView image) {
auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) {
image.write<uint32_t>(ref.location, ref.target);
});
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
bool DisassemblerDex::Parse(ConstBufferView image) {
image_ = image;
return ParseHeader();
}
bool DisassemblerDex::ParseHeader() {
ReadDexHeaderResults results;
if (!ReadDexHeader(image_, &results))
return false;
header_ = results.header;
dex_version_ = results.dex_version;
BufferSource source = results.source;
// DEX header contains file size, so use it to resize |image_| right away.
image_.shrink(header_->file_size);
// Read map list. This is not a fixed-size array, so instead of reading
// MapList directly, read |MapList::size| first, then visit elements in
// |MapList::list|.
static_assert(
offsetof(dex::MapList, list) == sizeof(decltype(dex::MapList::size)),
"MapList size error.");
source = std::move(BufferSource(image_).Skip(header_->map_off));
decltype(dex::MapList::size) list_size = 0;
if (!source.GetValue(&list_size) || list_size > dex::kMaxItemListSize)
return false;
const auto* item_list = source.GetArray<const dex::MapItem>(list_size);
if (!item_list)
return false;
// Read and validate map list, ensuring that required item types are present.
// - GetItemBaseSize() should have an entry for each item.
// - dex::kTypeCodeItem is actually not required; it's possible to have a DEX
// file with classes that have no code. However, this is unlikely to appear
// in application, so for simplicity we require DEX files to have code.
std::set<uint16_t> required_item_types = {
dex::kTypeStringIdItem, dex::kTypeTypeIdItem, dex::kTypeProtoIdItem,
dex::kTypeFieldIdItem, dex::kTypeMethodIdItem, dex::kTypeClassDefItem,
dex::kTypeTypeList, dex::kTypeCodeItem,
};
for (offset_t i = 0; i < list_size; ++i) {
const dex::MapItem* item = &item_list[i];
// Reject unreasonably large |item->size|.
size_t item_size = GetItemBaseSize(item->type);
// Confusing name: |item->size| is actually the number of items.
if (!image_.covers_array(item->offset, item->size, item_size))
return false;
if (!map_item_map_.insert(std::make_pair(item->type, item)).second)
return false; // A given type must appear at most once.
required_item_types.erase(item->type);
}
// TODO(huangs): Replace this with guards throughout file.
if (!required_item_types.empty())
return false;
// Make local copies of main map items.
string_map_item_ = *map_item_map_[dex::kTypeStringIdItem];
type_map_item_ = *map_item_map_[dex::kTypeTypeIdItem];
proto_map_item_ = *map_item_map_[dex::kTypeProtoIdItem];
field_map_item_ = *map_item_map_[dex::kTypeFieldIdItem];
method_map_item_ = *map_item_map_[dex::kTypeMethodIdItem];
class_def_map_item_ = *map_item_map_[dex::kTypeClassDefItem];
type_list_map_item_ = *map_item_map_[dex::kTypeTypeList];
code_map_item_ = *map_item_map_[dex::kTypeCodeItem];
// The following types are optional and may not be present in every DEX file.
if (map_item_map_.count(dex::kTypeAnnotationSetRefList)) {
annotation_set_ref_list_map_item_ =
*map_item_map_[dex::kTypeAnnotationSetRefList];
}
if (map_item_map_.count(dex::kTypeAnnotationSetItem))
annotation_set_map_item_ = *map_item_map_[dex::kTypeAnnotationSetItem];
if (map_item_map_.count(dex::kTypeAnnotationsDirectoryItem)) {
annotations_directory_map_item_ =
*map_item_map_[dex::kTypeAnnotationsDirectoryItem];
}
// Iteratively parse variable length lists, annotations directory items, and
// code items blocks. Any failure would indicate invalid DEX. Success
// indicates that no structural problem is found. However, contained
// references data read from parsed items still require validation.
if (!(ParseItemOffsets(image_, type_list_map_item_, sizeof(dex::TypeItem),
&type_list_offsets_) &&
ParseItemOffsets(image_, annotation_set_ref_list_map_item_,
sizeof(dex::AnnotationSetRefItem),
&annotation_set_ref_list_offsets_) &&
ParseItemOffsets(image_, annotation_set_map_item_,
sizeof(dex::AnnotationOffItem),
&annotation_set_offsets_) &&
ParseAnnotationsDirectoryItems(
image_, annotations_directory_map_item_,
&annotations_directory_item_offsets_,
&annotations_directory_item_field_annotation_offsets_,
&annotations_directory_item_method_annotation_offsets_,
&annotations_directory_item_parameter_annotation_offsets_))) {
return false;
}
CodeItemParser code_item_parser(image_);
if (!code_item_parser.Init(code_map_item_))
return false;
code_item_offsets_.resize(code_map_item_.size);
for (size_t i = 0; i < code_map_item_.size; ++i) {
const offset_t code_item_offset = code_item_parser.GetNext();
if (code_item_offset == kInvalidOffset)
return false;
code_item_offsets_[i] = code_item_offset;
}
// DEX files are required to have parsable code items.
return !code_item_offsets_.empty();
}
} // namespace zucchini