aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKelvin Zhang <zhangkelvin@google.com>2021-11-02 21:56:43 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-11-02 21:56:43 +0000
commit8be98142ecdba40b2ea9be10b11a23f4e6b67b05 (patch)
tree0a310ca653ef73c547d80da63c87fcb2c0636bcb
parentb77318bc7ecfba25e4966b56b9e9ab593925ad9d (diff)
parent7be427590935bc88198d32004449ccfcf60b60d4 (diff)
downloadzucchini-8be98142ecdba40b2ea9be10b11a23f4e6b67b05.tar.gz
Merge remote-tracking branch 'aosp/upstream-main' into dev am: a90c04389f am: ddc43dc959 am: d9eed84a58 am: 58b4d19a6b am: 7be4275909
Original change: https://android-review.googlesource.com/c/platform/external/zucchini/+/1877854 Change-Id: Ifdb1f7882cc237258a3395d28158d50bb4b43136
-rw-r--r--README.md18
-rw-r--r--algorithm.h3
-rw-r--r--disassembler_dex.cc280
-rw-r--r--disassembler_dex.h44
-rw-r--r--disassembler_elf.cc197
-rw-r--r--disassembler_elf.h47
-rw-r--r--disassembler_elf_unittest.cc6
-rw-r--r--disassembler_no_op.h2
-rw-r--r--disassembler_win32.h3
-rw-r--r--disassembler_ztf.h2
-rw-r--r--element_detection.cc35
-rw-r--r--element_detection.h3
-rw-r--r--equivalence_map.cc7
-rw-r--r--equivalence_map.h13
-rw-r--r--equivalence_map_unittest.cc56
-rw-r--r--fuzzers/testdata/patch_fuzzer/empty.zucbin76 -> 82 bytes
-rw-r--r--main_utils.cc4
-rw-r--r--patch_read_write_unittest.cc80
-rw-r--r--patch_reader.cc12
-rw-r--r--patch_utils.h22
-rw-r--r--patch_writer.cc6
-rw-r--r--target_pool.cc2
-rw-r--r--target_pool.h9
-rw-r--r--target_pool_unittest.cc20
-rw-r--r--targets_affinity.cc4
-rw-r--r--targets_affinity.h5
-rw-r--r--targets_affinity_unittest.cc8
-rw-r--r--testdata/all.smali628
-rw-r--r--testdata/const-method-handle-min.smali14
-rw-r--r--testdata/const-method-type-min.smali16
-rw-r--r--testdata/invoke-custom-min.smali39
-rw-r--r--testdata/invoke-polymorphic.smali70
-rw-r--r--type_dex.h85
-rw-r--r--zucchini_commands.cc5
-rw-r--r--zucchini_commands.h3
-rw-r--r--zucchini_gen_unittest.cc5
-rw-r--r--zucchini_integration.cc27
-rw-r--r--zucchini_integration.h10
38 files changed, 1462 insertions, 328 deletions
diff --git a/README.md b/README.md
index d3fd0a1..2d885b6 100644
--- a/README.md
+++ b/README.md
@@ -204,12 +204,14 @@ Position of elements in new file is ascending.
Name | Format | Description
--- | --- | ---
magic | uint32 = kMagic | Magic value.
+major_version | uint16 | Major version number indicating breaking changes.
+minor_version | uint16 | Minor version number indicating possibly breaking changes.
old_size | uint32 | Size of old file in bytes.
old_crc | uint32 | CRC32 of old file.
new_size | uint32 | Size of new file in bytes.
new_crc | uint32 | CRC32 of new file.
-**kMagic** == `'Z' | ('u' << 8) | ('c' << 16)`
+**kMagic** == `'Z' | ('u' << 8) | ('c' << 16) | ('c' << 24)`
**PatchElement**
Contains all the information required to produce a single element in new file.
@@ -235,6 +237,7 @@ old_length | uint32 | Length of the element in old file.
new_offset | uint32 | Starting offset of the element in new file.
new_length | uint32 | Length of the element in new file.
exe_type | uint32 | Executable type for this unit, see `enum ExecutableType`.
+version | uint16 | Version specific to the executable type for this unit.
**EquivalenceList**
Encodes a list of equivalences, where dst offsets (in new image) are ascending.
@@ -278,3 +281,16 @@ Name | Format | Description
--- | --- | ---
size |uint32 | Size of content in bytes.
content |T[] | List of integers.
+
+# Format Changelog
+All breaking changes to zucchini patch format will be documented in this
+section.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## [Unreleased]
+
+## [1.0] - 2021-10-27
+### Added
+Major/Minor version is encoded in PatchHeader
+Disassembler version associated with an element version is encoded in PatchElementHeader.
diff --git a/algorithm.h b/algorithm.h
index f5d49e3..4cafe93 100644
--- a/algorithm.h
+++ b/algorithm.h
@@ -8,6 +8,7 @@
#include <stddef.h>
#include <algorithm>
+#include <deque>
#include <type_traits>
#include <vector>
@@ -69,7 +70,7 @@ inline int IncrementForAlignCeil4(T pos) {
// Sorts values in |container| and removes duplicates.
template <class T>
-void SortAndUniquify(std::vector<T>* container) {
+void SortAndUniquify(std::deque<T>* container) {
std::sort(container->begin(), container->end());
container->erase(std::unique(container->begin(), container->end()),
container->end());
diff --git a/disassembler_dex.cc b/disassembler_dex.cc
index 5b25c50..8ea0314 100644
--- a/disassembler_dex.cc
+++ b/disassembler_dex.cc
@@ -69,6 +69,10 @@ size_t GetItemBaseSize(uint16_t type_item_code) {
return sizeof(dex::MethodIdItem);
case dex::kTypeClassDefItem:
return sizeof(dex::ClassDefItem);
+ case dex::kTypeCallSiteIdItem:
+ return sizeof(dex::CallSiteIdItem);
+ case dex::kTypeMethodHandleItem:
+ return sizeof(dex::MethodHandleItem);
// No need to handle dex::kTypeMapList.
case dex::kTypeTypeList:
return sizeof(uint32_t); // Variable-length.
@@ -416,17 +420,23 @@ class ItemReferenceReader : public ReferenceReader {
// |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.
+ // |rel_item_offset| is the offset to use relative to |item_offset| in cases
+ // where a value other than |rel_location| is required. For an example of this
+ // see ReadMethodHandleFieldOrMethodId.
ItemReferenceReader(offset_t lo,
offset_t hi,
const dex::MapItem& map_item,
size_t item_size,
size_t rel_location,
- Mapper&& mapper)
+ Mapper&& mapper,
+ bool mapper_wants_item = false)
: 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_input_delta_(
+ mapper_wants_item ? 0 : 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.");
@@ -457,7 +467,11 @@ class ItemReferenceReader : public ReferenceReader {
// |reference_width| is unneeded.
if (location >= hi_)
break;
- const offset_t target = mapper_.Run(location);
+
+ // |location == item_offset + mapper_input_delta_| in the majority of
+ // cases. The exception is when |mapper_| wants an item aligned location
+ // instead e.g. ReadMethodHandleFieldOrMethodId.
+ const offset_t target = mapper_.Run(item_offset + mapper_input_delta_);
// kDexSentinelOffset (0) may appear for the following:
// - ProtoIdItem: parameters_off.
@@ -467,6 +481,9 @@ class ItemReferenceReader : public ReferenceReader {
// - AnnotationSetRefItem: annotations_off.
// kDexSentinelIndexAsOffset (0xFFFFFFFF) may appear for the following:
// - ClassDefItem: superclass_idx, source_file_idx.
+ // - MethodHandleItem: |mapper_| uses ReadMethodHandleFieldOrMethodId and
+ // determines the item at |cur_idx_| is not of the required reference
+ // type.
if (target == kDexSentinelOffset || target == kDexSentinelIndexAsOffset) {
++cur_idx_;
continue;
@@ -492,6 +509,7 @@ class ItemReferenceReader : public ReferenceReader {
const uint32_t num_items_;
const uint32_t item_size_;
const uint32_t rel_location_;
+ const uint32_t mapper_input_delta_;
const Mapper mapper_;
offset_t cur_idx_ = 0;
};
@@ -688,6 +706,53 @@ static offset_t ReadTargetIndex(ConstBufferView image,
base::checked_cast<offset_t>(unsafe_idx * target_item_size);
}
+// Reads a field or method index of the MethodHandleItem located at |location|
+// in |image| and translates |method_handle_item.field_or_method_id| to the
+// offset of a fixed-size item specified by |target_map_item| and
+// |target_item_size|. The index is deemed to be of the correct target type if
+// |method_handle_item.method_handle_type| falls within the range [|min_type|,
+// |max_type|]. If the target type is correct ReadTargetIndex is called.
+// Returns the target offset if valid, or kDexSentinelIndexAsOffset if
+// |method_handle_item.method_handle_type| is of the wrong type, or
+// kInvalidOffset otherwise.
+//
+// As of DEX version 39 MethodHandleType values for FieldId and MethodId each
+// form one consecutive block of values. If this changes, then the interface to
+// this function will need to be redesigned.
+static offset_t ReadMethodHandleFieldOrMethodId(
+ ConstBufferView image,
+ const dex::MapItem& target_map_item,
+ size_t target_item_size,
+ dex::MethodHandleType min_type,
+ dex::MethodHandleType max_type,
+ offset_t location) {
+ dex::MethodHandleItem method_handle_item =
+ image.read<dex::MethodHandleItem>(location);
+
+ // Cannot use base::checked_cast as dex::MethodHandleType is an enum class so
+ // static_assert on the size instead.
+ static_assert(sizeof(decltype(dex::MethodHandleItem::method_handle_type)) <=
+ sizeof(dex::MethodHandleType),
+ "dex::MethodHandleItem::method_handle_type may not fit into "
+ "dex::MethodHandleType.");
+ dex::MethodHandleType method_handle_type =
+ static_cast<dex::MethodHandleType>(method_handle_item.method_handle_type);
+
+ if (method_handle_type >= dex::MethodHandleType::kMaxMethodHandleType) {
+ return kInvalidOffset;
+ }
+
+ // Use DexSentinelIndexAsOffset to skip the item as it isn't of the
+ // corresponding method handle type.
+ if (method_handle_type < min_type || method_handle_type > max_type) {
+ return kDexSentinelIndexAsOffset;
+ }
+
+ return ReadTargetIndex<decltype(dex::MethodHandleItem::field_or_method_id)>(
+ image, target_map_item, target_item_size,
+ location + offsetof(dex::MethodHandleItem, field_or_method_id));
+}
+
// 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
@@ -779,10 +844,11 @@ bool ReadDexHeader(ConstBufferView image, ReadDexHeaderResults* opt_results) {
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)
+ // Only support DEX versions 35, 37, 38, and 39
+ if (dex_version != 35 && dex_version != 37 && dex_version != 38 &&
+ dex_version != 39) {
return false;
+ }
if (header->file_size > image.size() ||
header->file_size < sizeof(dex::HeaderItem) ||
@@ -864,18 +930,27 @@ std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const {
{{2, TypeTag(kCodeToTypeId), PoolTag(kTypeId)},
&DisassemblerDex::MakeReadCodeToTypeId16,
&DisassemblerDex::MakeWriteTypeId16},
+ {{2, TypeTag(kCodeToProtoId), PoolTag(kProtoId)},
+ &DisassemblerDex::MakeReadCodeToProtoId16,
+ &DisassemblerDex::MakeWriteProtoId16},
{{2, TypeTag(kMethodIdToProtoId), PoolTag(kProtoId)},
&DisassemblerDex::MakeReadMethodIdToProtoId16,
&DisassemblerDex::MakeWriteProtoId16},
{{2, TypeTag(kCodeToFieldId), PoolTag(kFieldId)},
&DisassemblerDex::MakeReadCodeToFieldId16,
&DisassemblerDex::MakeWriteFieldId16},
+ {{2, TypeTag(kMethodHandleToFieldId), PoolTag(kFieldId)},
+ &DisassemblerDex::MakeReadMethodHandleToFieldId16,
+ &DisassemblerDex::MakeWriteFieldId16},
{{4, TypeTag(kAnnotationsDirectoryToFieldId), PoolTag(kFieldId)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToFieldId32,
&DisassemblerDex::MakeWriteFieldId32},
{{2, TypeTag(kCodeToMethodId), PoolTag(kMethodId)},
&DisassemblerDex::MakeReadCodeToMethodId16,
&DisassemblerDex::MakeWriteMethodId16},
+ {{2, TypeTag(kMethodHandleToMethodId), PoolTag(kMethodId)},
+ &DisassemblerDex::MakeReadMethodHandleToMethodId16,
+ &DisassemblerDex::MakeWriteMethodId16},
{{4, TypeTag(kAnnotationsDirectoryToMethodId), PoolTag(kMethodId)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToMethodId32,
&DisassemblerDex::MakeWriteMethodId32},
@@ -883,6 +958,12 @@ std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const {
PoolTag(kMethodId)},
&DisassemblerDex::MakeReadAnnotationsDirectoryToParameterMethodId32,
&DisassemblerDex::MakeWriteMethodId32},
+ {{2, TypeTag(kCodeToCallSiteId), PoolTag(kCallSiteId)},
+ &DisassemblerDex::MakeReadCodeToCallSiteId16,
+ &DisassemblerDex::MakeWriteCallSiteId16},
+ {{2, TypeTag(kCodeToMethodHandle), PoolTag(kMethodHandle)},
+ &DisassemblerDex::MakeReadCodeToMethodHandle16,
+ &DisassemblerDex::MakeWriteMethodHandle16},
{{4, TypeTag(kProtoIdToParametersTypeList), PoolTag(kTypeList)},
&DisassemblerDex::MakeReadProtoIdToParametersTypeList,
&DisassemblerDex::MakeWriteAbs32},
@@ -936,6 +1017,9 @@ std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const {
PoolTag(kAnnotationsDirectory)},
&DisassemblerDex::MakeReadClassDefToAnnotationDirectory,
&DisassemblerDex::MakeWriteAbs32},
+ {{4, TypeTag(kCallSiteIdToCallSite), PoolTag(kCallSite)},
+ &DisassemblerDex::MakeReadCallSiteIdToCallSite32,
+ &DisassemblerDex::MakeWriteAbs32},
};
}
@@ -1125,6 +1209,46 @@ DisassemblerDex::MakeReadClassDefToStaticValuesEncodedArray(offset_t lo,
offsetof(dex::ClassDefItem, static_values_off), std::move(mapper));
}
+std::unique_ptr<ReferenceReader>
+DisassemblerDex::MakeReadCallSiteIdToCallSite32(offset_t lo, offset_t hi) {
+ auto mapper = base::BindRepeating(ReadTargetOffset32, image_);
+ return std::make_unique<ItemReferenceReader>(
+ lo, hi, call_site_map_item_, sizeof(dex::CallSiteIdItem),
+ offsetof(dex::CallSiteIdItem, call_site_off), std::move(mapper));
+}
+
+std::unique_ptr<ReferenceReader>
+DisassemblerDex::MakeReadMethodHandleToFieldId16(offset_t lo, offset_t hi) {
+ auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_,
+ field_map_item_, sizeof(dex::FieldIdItem),
+ dex::MethodHandleType::kStaticPut,
+ dex::MethodHandleType::kInstanceGet);
+ // Use |mapper_wants_item == true| for ItemReferenceReader such that
+ // |location| is aligned with MethodHandleItem when passed to |mapper|. This
+ // allows ReadMethodHandleFieldOrMethodId to safely determine whether the
+ // reference in the MethodHandleItem is of the correct type to be emitted.
+ return std::make_unique<ItemReferenceReader>(
+ lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem),
+ offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper),
+ /*mapper_wants_item=*/true);
+}
+
+std::unique_ptr<ReferenceReader>
+DisassemblerDex::MakeReadMethodHandleToMethodId16(offset_t lo, offset_t hi) {
+ auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_,
+ method_map_item_, sizeof(dex::MethodIdItem),
+ dex::MethodHandleType::kInvokeStatic,
+ dex::MethodHandleType::kInvokeInterface);
+ // Use |mapper_wants_item == true| for ItemReferenceReader such that
+ // |location| is aligned with MethodHandleItem when passed to |mapper|. This
+ // allows ReadMethodHandleFieldOrMethodId to safely determine whether the
+ // reference in the MethodHandleItem is of the correct type to be emitted.
+ return std::make_unique<ItemReferenceReader>(
+ lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem),
+ offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper),
+ /*mapper_wants_item=*/true);
+}
+
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadTypeListToTypeId16(
offset_t lo,
offset_t hi) {
@@ -1233,6 +1357,10 @@ DisassemblerDex::MakeReadAnnotationsDirectoryToParameterAnnotationSetRef(
std::move(mapper));
}
+// MakeReadCode* readers use offset relative to the instruction beginning based
+// on the instruction format ID.
+// See https://source.android.com/devices/tech/dalvik/instruction-formats
+
std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToStringId16(
offset_t lo,
offset_t hi) {
@@ -1295,6 +1423,71 @@ std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToTypeId16(
image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
}
+std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToProtoId16(
+ offset_t lo,
+ offset_t hi) {
+ auto filter = base::BindRepeating(
+ [](const InstructionParser::Value& value) -> offset_t {
+ if (value.instr->format == dex::FormatId::c) {
+ if (value.instr->opcode == 0xFA || // invoke-polymorphic
+ value.instr->opcode == 0xFB) { // invoke-polymorphic/range
+ // HHHH from e.g, invoke-polymorphic {vC, vD, vE, vF, vG},
+ // meth@BBBB, proto@HHHH
+ return value.instr_offset + 6;
+ }
+ if (value.instr->opcode == 0xFF) { // const-method-type
+ // BBBB from e.g., const-method-type vAA, proto@BBBB
+ return value.instr_offset + 2;
+ }
+ }
+ return kInvalidOffset;
+ });
+ auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
+ proto_map_item_, sizeof(dex::ProtoIdItem));
+ return std::make_unique<InstructionReferenceReader>(
+ image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
+}
+
+std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToCallSiteId16(
+ 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 == 0xFC || // invoke-custom
+ value.instr->opcode == 0xFD)) { // invoke-custom/range
+ // BBBB from e.g, invoke-custom {vC, vD, vE, vF, vG},
+ // call_site@BBBB
+ return value.instr_offset + 2;
+ }
+ return kInvalidOffset;
+ });
+ auto mapper =
+ base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
+ call_site_map_item_, sizeof(dex::CallSiteIdItem));
+ return std::make_unique<InstructionReferenceReader>(
+ image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper));
+}
+
+std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToMethodHandle16(
+ 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 == 0xFE) { // const-method-handle
+ // BBBB from e.g, const-method-handle vAA, method_handle@BBBB
+ return value.instr_offset + 2;
+ }
+ return kInvalidOffset;
+ });
+ auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_,
+ method_handle_map_item_,
+ sizeof(dex::MethodHandleItem));
+ 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) {
@@ -1321,7 +1514,9 @@ std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToMethodId16(
[](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
+ value.instr->opcode == 0x74 || // invoke-kind/range
+ value.instr->opcode == 0xFA || // invoke-polymorphic
+ value.instr->opcode == 0xFB)) { // invoke-polymorphic/range
// BBBB from e.g., invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB.
return value.instr_offset + 2;
}
@@ -1491,6 +1686,22 @@ std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodId32(
return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
}
+std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteCallSiteId16(
+ MutableBufferView image) {
+ auto writer =
+ base::BindRepeating(WriteTargetIndex<uint16_t>, call_site_map_item_,
+ sizeof(dex::CallSiteIdItem));
+ return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer));
+}
+
+std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodHandle16(
+ MutableBufferView image) {
+ auto writer =
+ base::BindRepeating(WriteTargetIndex<uint16_t>, method_handle_map_item_,
+ sizeof(dex::MethodHandleItem));
+ 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) {
@@ -1587,15 +1798,7 @@ bool DisassemblerDex::ParseHeader() {
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,
- };
+ // GetItemBaseSize() should have an entry for each item.
for (offset_t i = 0; i < list_size; ++i) {
const dex::MapItem* item = &item_list[i];
// Reject unreasonably large |item->size|.
@@ -1605,29 +1808,46 @@ bool DisassemblerDex::ParseHeader() {
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::kTypeStringIdItem)) {
+ string_map_item_ = *map_item_map_[dex::kTypeStringIdItem];
+ }
+ if (map_item_map_.count(dex::kTypeTypeIdItem)) {
+ type_map_item_ = *map_item_map_[dex::kTypeTypeIdItem];
+ }
+ if (map_item_map_.count(dex::kTypeProtoIdItem)) {
+ proto_map_item_ = *map_item_map_[dex::kTypeProtoIdItem];
+ }
+ if (map_item_map_.count(dex::kTypeFieldIdItem)) {
+ field_map_item_ = *map_item_map_[dex::kTypeFieldIdItem];
+ }
+ if (map_item_map_.count(dex::kTypeMethodIdItem)) {
+ method_map_item_ = *map_item_map_[dex::kTypeMethodIdItem];
+ }
+ if (map_item_map_.count(dex::kTypeClassDefItem)) {
+ class_def_map_item_ = *map_item_map_[dex::kTypeClassDefItem];
+ }
+ if (map_item_map_.count(dex::kTypeCallSiteIdItem)) {
+ call_site_map_item_ = *map_item_map_[dex::kTypeCallSiteIdItem];
+ }
+ if (map_item_map_.count(dex::kTypeMethodHandleItem)) {
+ method_handle_map_item_ = *map_item_map_[dex::kTypeMethodHandleItem];
+ }
+ if (map_item_map_.count(dex::kTypeTypeList)) {
+ type_list_map_item_ = *map_item_map_[dex::kTypeTypeList];
+ }
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))
+ if (map_item_map_.count(dex::kTypeAnnotationSetItem)) {
annotation_set_map_item_ = *map_item_map_[dex::kTypeAnnotationSetItem];
+ }
+ if (map_item_map_.count(dex::kTypeCodeItem)) {
+ code_map_item_ = *map_item_map_[dex::kTypeCodeItem];
+ }
if (map_item_map_.count(dex::kTypeAnnotationsDirectoryItem)) {
annotations_directory_map_item_ =
*map_item_map_[dex::kTypeAnnotationsDirectoryItem];
diff --git a/disassembler_dex.h b/disassembler_dex.h
index 2038a3c..d9d93b2 100644
--- a/disassembler_dex.h
+++ b/disassembler_dex.h
@@ -24,6 +24,7 @@ namespace zucchini {
class DisassemblerDex : public Disassembler {
public:
+ static constexpr uint16_t kVersion = 1;
// Pools follow canonical order.
enum ReferencePool : uint8_t {
kStringId,
@@ -32,8 +33,8 @@ class DisassemblerDex : public Disassembler {
kFieldId,
kMethodId,
// kClassDef, // Unused
- // kCallSiteId, // Unused
- // kMethodHandle, // Unused
+ kCallSiteId,
+ kMethodHandle,
kTypeList,
kAnnotationSetRefList,
kAnnotionSet,
@@ -43,7 +44,7 @@ class DisassemblerDex : public Disassembler {
kAnnotation,
kEncodedArray,
kAnnotationsDirectory,
- // kCallSite, // Unused
+ kCallSite,
kNumPools
};
@@ -69,15 +70,22 @@ class DisassemblerDex : public Disassembler {
kTypeListToTypeId,
kCodeToTypeId,
- kMethodIdToProtoId, // kProtoId
+ kCodeToProtoId, // kProtoId
+ kMethodIdToProtoId,
kCodeToFieldId, // kFieldId
+ kMethodHandleToFieldId,
kAnnotationsDirectoryToFieldId,
kCodeToMethodId, // kMethodId
+ kMethodHandleToMethodId,
kAnnotationsDirectoryToMethodId,
kAnnotationsDirectoryToParameterMethodId,
+ kCodeToCallSiteId, // kCallSiteId
+
+ kCodeToMethodHandle, // kMethodHandle
+
kProtoIdToParametersTypeList, // kTypeList
kClassDefToInterfacesTypeList,
@@ -102,10 +110,7 @@ class DisassemblerDex : public Disassembler {
kClassDefToAnnotationDirectory, // kAnnotationsDirectory
- // Intentionally ignored references (never appeared in test corpus).
- // kMethodHandleToFieldId,
- // kMethodHandleToMethodId,
- // kCallSiteIdToCallSite,
+ kCallSiteIdToCallSite, // kCallSite
kNumTypes
};
@@ -172,6 +177,13 @@ class DisassemblerDex : public Disassembler {
std::unique_ptr<ReferenceReader> MakeReadClassDefToStaticValuesEncodedArray(
offset_t lo,
offset_t hi);
+ std::unique_ptr<ReferenceReader> MakeReadCallSiteIdToCallSite32(offset_t lo,
+ offset_t hi);
+ std::unique_ptr<ReferenceReader> MakeReadMethodHandleToFieldId16(offset_t lo,
+ offset_t hi);
+ std::unique_ptr<ReferenceReader> MakeReadMethodHandleToMethodId16(
+ offset_t lo,
+ offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadTypeListToTypeId16(offset_t lo,
offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadAnnotationSetToAnnotation(
@@ -203,10 +215,16 @@ class DisassemblerDex : public Disassembler {
offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadCodeToTypeId16(offset_t lo,
offset_t hi);
+ std::unique_ptr<ReferenceReader> MakeReadCodeToProtoId16(offset_t lo,
+ offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadCodeToFieldId16(offset_t lo,
offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadCodeToMethodId16(offset_t lo,
offset_t hi);
+ std::unique_ptr<ReferenceReader> MakeReadCodeToCallSiteId16(offset_t lo,
+ offset_t hi);
+ std::unique_ptr<ReferenceReader> MakeReadCodeToMethodHandle16(offset_t lo,
+ offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadCodeToRelCode8(offset_t lo,
offset_t hi);
std::unique_ptr<ReferenceReader> MakeReadCodeToRelCode16(offset_t lo,
@@ -225,6 +243,10 @@ class DisassemblerDex : public Disassembler {
std::unique_ptr<ReferenceWriter> MakeWriteFieldId32(MutableBufferView image);
std::unique_ptr<ReferenceWriter> MakeWriteMethodId16(MutableBufferView image);
std::unique_ptr<ReferenceWriter> MakeWriteMethodId32(MutableBufferView image);
+ std::unique_ptr<ReferenceWriter> MakeWriteCallSiteId16(
+ MutableBufferView image);
+ std::unique_ptr<ReferenceWriter> MakeWriteMethodHandle16(
+ MutableBufferView image);
std::unique_ptr<ReferenceWriter> MakeWriteRelCode8(MutableBufferView image);
std::unique_ptr<ReferenceWriter> MakeWriteRelCode16(MutableBufferView image);
std::unique_ptr<ReferenceWriter> MakeWriteRelCode32(MutableBufferView image);
@@ -248,12 +270,12 @@ class DisassemblerDex : public Disassembler {
dex::MapItem field_map_item_ = {};
dex::MapItem method_map_item_ = {};
dex::MapItem class_def_map_item_ = {};
+ dex::MapItem call_site_map_item_ = {};
+ dex::MapItem method_handle_map_item_ = {};
dex::MapItem type_list_map_item_ = {};
- dex::MapItem code_map_item_ = {};
-
- // Optionally supported (not all DEX files have these).
dex::MapItem annotation_set_ref_list_map_item_ = {};
dex::MapItem annotation_set_map_item_ = {};
+ dex::MapItem code_map_item_ = {};
dex::MapItem annotations_directory_map_item_ = {};
// Sorted list of offsets of parsed items in |image_|.
diff --git a/disassembler_elf.cc b/disassembler_elf.cc
index 94dc12a..22a29ba 100644
--- a/disassembler_elf.cc
+++ b/disassembler_elf.cc
@@ -596,6 +596,24 @@ std::unique_ptr<ReferenceWriter> DisassemblerElfArm<Traits>::MakeWriteAbs32(
image, AbsoluteAddress(Traits::kBitness, 0), this->translator_);
}
+template <class TRAITS>
+template <class ADDR_TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerElfArm<TRAITS>::MakeReadRel32(
+ offset_t lower,
+ offset_t upper) {
+ return std::make_unique<Rel32ReaderArm<ADDR_TRAITS>>(
+ this->translator_, this->image_,
+ this->rel32_locations_table_[ADDR_TRAITS::addr_type], lower, upper);
+}
+
+template <class TRAITS>
+template <class ADDR_TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerElfArm<TRAITS>::MakeWriteRel32(
+ MutableBufferView image) {
+ return std::make_unique<Rel32WriterArm<ADDR_TRAITS>>(this->translator_,
+ image);
+}
+
/******** DisassemblerElfAArch32 ********/
DisassemblerElfAArch32::DisassemblerElfAArch32() = default;
@@ -616,24 +634,34 @@ std::vector<ReferenceGroup> DisassemblerElfAArch32::MakeReferenceGroups()
&DisassemblerElfAArch32::MakeWriteAbs32},
{ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_A24),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch32::MakeReadRel32A24,
- &DisassemblerElfAArch32::MakeWriteRel32A24},
+ &DisassemblerElfAArch32::MakeReadRel32<
+ AArch32Rel32Translator::AddrTraits_A24>,
+ &DisassemblerElfAArch32::MakeWriteRel32<
+ AArch32Rel32Translator::AddrTraits_A24>},
{ReferenceTypeTraits{2, TypeTag(AArch32ReferenceType::kRel32_T8),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch32::MakeReadRel32T8,
- &DisassemblerElfAArch32::MakeWriteRel32T8},
+ &DisassemblerElfAArch32::MakeReadRel32<
+ AArch32Rel32Translator::AddrTraits_T8>,
+ &DisassemblerElfAArch32::MakeWriteRel32<
+ AArch32Rel32Translator::AddrTraits_T8>},
{ReferenceTypeTraits{2, TypeTag(AArch32ReferenceType::kRel32_T11),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch32::MakeReadRel32T11,
- &DisassemblerElfAArch32::MakeWriteRel32T11},
+ &DisassemblerElfAArch32::MakeReadRel32<
+ AArch32Rel32Translator::AddrTraits_T11>,
+ &DisassemblerElfAArch32::MakeWriteRel32<
+ AArch32Rel32Translator::AddrTraits_T11>},
{ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_T20),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch32::MakeReadRel32T20,
- &DisassemblerElfAArch32::MakeWriteRel32T20},
+ &DisassemblerElfAArch32::MakeReadRel32<
+ AArch32Rel32Translator::AddrTraits_T20>,
+ &DisassemblerElfAArch32::MakeWriteRel32<
+ AArch32Rel32Translator::AddrTraits_T20>},
{ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_T24),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch32::MakeReadRel32T24,
- &DisassemblerElfAArch32::MakeWriteRel32T24},
+ &DisassemblerElfAArch32::MakeReadRel32<
+ AArch32Rel32Translator::AddrTraits_T24>,
+ &DisassemblerElfAArch32::MakeWriteRel32<
+ AArch32Rel32Translator::AddrTraits_T24>},
};
}
@@ -673,86 +701,6 @@ bool DisassemblerElfAArch32::IsExecSectionThumb2(
return num < den * kAArch32BitCondAlwaysDensityThreshold;
}
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32A24(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_A24>>(
- translator_, image_,
- rel32_locations_table_[AArch32Rel32Translator::ADDR_A24], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32A24(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch32Rel32Translator::AddrTraits_A24>>(translator_,
- image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T8(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T8>>(
- translator_, image_,
- rel32_locations_table_[AArch32Rel32Translator::ADDR_T8], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T8(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T8>>(translator_,
- image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T11(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T11>>(
- translator_, image_,
- rel32_locations_table_[AArch32Rel32Translator::ADDR_T11], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T11(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T11>>(translator_,
- image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T20(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T20>>(
- translator_, image_,
- rel32_locations_table_[AArch32Rel32Translator::ADDR_T20], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T20(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T20>>(translator_,
- image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch32::MakeReadRel32T24(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch32Rel32Translator::AddrTraits_T24>>(
- translator_, image_,
- rel32_locations_table_[AArch32Rel32Translator::ADDR_T24], lower, upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch32::MakeWriteRel32T24(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch32Rel32Translator::AddrTraits_T24>>(translator_,
- image);
-}
-
/******** DisassemblerElfAArch64 ********/
DisassemblerElfAArch64::DisassemblerElfAArch64() = default;
@@ -774,16 +722,22 @@ std::vector<ReferenceGroup> DisassemblerElfAArch64::MakeReferenceGroups()
&DisassemblerElfAArch64::MakeWriteAbs32},
{ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd14),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch64::MakeReadRel32Immd14,
- &DisassemblerElfAArch64::MakeWriteRel32Immd14},
+ &DisassemblerElfAArch64::MakeReadRel32<
+ AArch64Rel32Translator::AddrTraits_Immd14>,
+ &DisassemblerElfAArch64::MakeWriteRel32<
+ AArch64Rel32Translator::AddrTraits_Immd14>},
{ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd19),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch64::MakeReadRel32Immd19,
- &DisassemblerElfAArch64::MakeWriteRel32Immd19},
+ &DisassemblerElfAArch64::MakeReadRel32<
+ AArch64Rel32Translator::AddrTraits_Immd19>,
+ &DisassemblerElfAArch64::MakeWriteRel32<
+ AArch64Rel32Translator::AddrTraits_Immd19>},
{ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd26),
PoolTag(ArmReferencePool::kPoolRel32)},
- &DisassemblerElfAArch64::MakeReadRel32Immd26,
- &DisassemblerElfAArch64::MakeWriteRel32Immd26},
+ &DisassemblerElfAArch64::MakeReadRel32<
+ AArch64Rel32Translator::AddrTraits_Immd26>,
+ &DisassemblerElfAArch64::MakeWriteRel32<
+ AArch64Rel32Translator::AddrTraits_Immd26>},
};
}
@@ -793,57 +747,6 @@ DisassemblerElfAArch64::MakeRel32Finder(
return std::make_unique<Rel32FinderAArch64>(image_, translator_);
}
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch64::MakeReadRel32Immd14(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch64Rel32Translator::AddrTraits_Immd14>>(
- translator_, this->image_,
- rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD14], lower,
- upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch64::MakeWriteRel32Immd14(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch64Rel32Translator::AddrTraits_Immd14>>(translator_,
- image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch64::MakeReadRel32Immd19(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch64Rel32Translator::AddrTraits_Immd19>>(
- translator_, this->image_,
- rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD19], lower,
- upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch64::MakeWriteRel32Immd19(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch64Rel32Translator::AddrTraits_Immd19>>(translator_,
- image);
-}
-
-std::unique_ptr<ReferenceReader> DisassemblerElfAArch64::MakeReadRel32Immd26(
- offset_t lower,
- offset_t upper) {
- return std::make_unique<
- Rel32ReaderArm<AArch64Rel32Translator::AddrTraits_Immd26>>(
- translator_, this->image_,
- rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD26], lower,
- upper);
-}
-
-std::unique_ptr<ReferenceWriter> DisassemblerElfAArch64::MakeWriteRel32Immd26(
- MutableBufferView image) {
- return std::make_unique<
- Rel32WriterArm<AArch64Rel32Translator::AddrTraits_Immd26>>(translator_,
- image);
-}
-
// Explicit instantiation for supported classes.
template class DisassemblerElfArm<ElfAArch32Traits>;
template class DisassemblerElfArm<ElfAArch64Traits>;
diff --git a/disassembler_elf.h b/disassembler_elf.h
index 0bd11a6..353c444 100644
--- a/disassembler_elf.h
+++ b/disassembler_elf.h
@@ -63,6 +63,7 @@ struct AArch64ReferenceType {
};
struct Elf32Traits {
+ static constexpr uint16_t kVersion = 1;
static constexpr Bitness kBitness = kBit32;
static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS32;
using Elf_Shdr = elf::Elf32_Shdr;
@@ -94,6 +95,7 @@ struct ElfAArch32Traits : public Elf32Traits {
};
struct Elf64Traits {
+ static constexpr uint16_t kVersion = 1;
static constexpr Bitness kBitness = kBit64;
static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS64;
using Elf_Shdr = elf::Elf64_Shdr;
@@ -151,6 +153,7 @@ template <class TRAITS>
class DisassemblerElf : public Disassembler {
public:
using Traits = TRAITS;
+ static constexpr uint16_t kVersion = Traits::kVersion;
// Applies quick checks to determine whether |image| *may* point to the start
// of an executable. Returns true iff the check passes.
static bool QuickDetect(ConstBufferView image);
@@ -296,6 +299,13 @@ class DisassemblerElfArm : public DisassemblerElf<TRAITS> {
std::unique_ptr<ReferenceReader> MakeReadAbs32(offset_t lo, offset_t hi);
std::unique_ptr<ReferenceWriter> MakeWriteAbs32(MutableBufferView image);
+ // Specialized Read/Write functions for different rel32 address types.
+ template <class ADDR_TRAITS>
+ std::unique_ptr<ReferenceReader> MakeReadRel32(offset_t lower,
+ offset_t upper);
+ template <class ADDR_TRAITS>
+ std::unique_ptr<ReferenceWriter> MakeWriteRel32(MutableBufferView image);
+
protected:
// Sorted file offsets of rel32 locations for each rel32 address type.
std::deque<offset_t>
@@ -322,27 +332,6 @@ class DisassemblerElfAArch32 : public DisassemblerElfArm<ElfAArch32Traits> {
// or THUMB2 mode, this function implements heuristics to distinguish between
// the two. Returns true if section is THUMB2 mode; otherwise return false.
bool IsExecSectionThumb2(const typename Traits::Elf_Shdr& section) const;
-
- // Specialized Read/Write functions for different rel32 address types.
- std::unique_ptr<ReferenceReader> MakeReadRel32A24(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32A24(MutableBufferView image);
-
- std::unique_ptr<ReferenceReader> MakeReadRel32T8(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32T8(MutableBufferView image);
-
- std::unique_ptr<ReferenceReader> MakeReadRel32T11(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32T11(MutableBufferView image);
-
- std::unique_ptr<ReferenceReader> MakeReadRel32T20(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32T20(MutableBufferView image);
-
- std::unique_ptr<ReferenceReader> MakeReadRel32T24(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32T24(MutableBufferView image);
};
// Disassembler for ELF with AArch64 (AKA ARM64).
@@ -360,22 +349,6 @@ class DisassemblerElfAArch64 : public DisassemblerElfArm<ElfAArch64Traits> {
// DisassemblerElfArm:
std::unique_ptr<typename Traits::Rel32FinderUse> MakeRel32Finder(
const typename Traits::Elf_Shdr& section) override;
-
- // Specialized Read/Write functions for different rel32 address types.
- std::unique_ptr<ReferenceReader> MakeReadRel32Immd14(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32Immd14(
- MutableBufferView image);
-
- std::unique_ptr<ReferenceReader> MakeReadRel32Immd19(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32Immd19(
- MutableBufferView image);
-
- std::unique_ptr<ReferenceReader> MakeReadRel32Immd26(offset_t lower,
- offset_t upper);
- std::unique_ptr<ReferenceWriter> MakeWriteRel32Immd26(
- MutableBufferView image);
};
} // namespace zucchini
diff --git a/disassembler_elf_unittest.cc b/disassembler_elf_unittest.cc
index d98eb50..1e52136 100644
--- a/disassembler_elf_unittest.cc
+++ b/disassembler_elf_unittest.cc
@@ -153,8 +153,7 @@ TEST(DisassemblerElfTest, QuickDetect) {
header.e_machine = elf::EM_386;
header.e_version = 1;
header.e_shentsize = sizeof(elf::Elf32_Shdr);
- ConstBufferView image(reinterpret_cast<const uint8_t*>(&header),
- sizeof(header));
+ image = {reinterpret_cast<const uint8_t*>(&header), sizeof(header)};
EXPECT_TRUE(DisassemblerElfX86::QuickDetect(image));
EXPECT_FALSE(DisassemblerElfX64::QuickDetect(image));
}
@@ -169,8 +168,7 @@ TEST(DisassemblerElfTest, QuickDetect) {
header.e_machine = elf::EM_X86_64;
header.e_version = 1;
header.e_shentsize = sizeof(elf::Elf64_Shdr);
- ConstBufferView image(reinterpret_cast<const uint8_t*>(&header),
- sizeof(header));
+ image = {reinterpret_cast<const uint8_t*>(&header), sizeof(header)};
EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image));
EXPECT_TRUE(DisassemblerElfX64::QuickDetect(image));
}
diff --git a/disassembler_no_op.h b/disassembler_no_op.h
index ef10651..1d436dd 100644
--- a/disassembler_no_op.h
+++ b/disassembler_no_op.h
@@ -18,6 +18,8 @@ namespace zucchini {
// This disassembler works on any file and does not look for reference.
class DisassemblerNoOp : public Disassembler {
public:
+ static constexpr uint16_t kVersion = 1;
+
DisassemblerNoOp();
DisassemblerNoOp(const DisassemblerNoOp&) = delete;
const DisassemblerNoOp& operator=(const DisassemblerNoOp&) = delete;
diff --git a/disassembler_win32.h b/disassembler_win32.h
index 77b65ac..dfb2533 100644
--- a/disassembler_win32.h
+++ b/disassembler_win32.h
@@ -26,6 +26,7 @@ class Rel32FinderX86;
class Rel32FinderX64;
struct Win32X86Traits {
+ static constexpr uint16_t kVersion = 1;
static constexpr Bitness kBitness = kBit32;
static constexpr ExecutableType kExeType = kExeTypeWin32X86;
enum : uint16_t { kMagic = 0x10B };
@@ -39,6 +40,7 @@ struct Win32X86Traits {
};
struct Win32X64Traits {
+ static constexpr uint16_t kVersion = 1;
static constexpr Bitness kBitness = kBit64;
static constexpr ExecutableType kExeType = kExeTypeWin32X64;
enum : uint16_t { kMagic = 0x20B };
@@ -55,6 +57,7 @@ template <class TRAITS>
class DisassemblerWin32 : public Disassembler {
public:
using Traits = TRAITS;
+ static constexpr uint16_t kVersion = Traits::kVersion;
enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount };
// Applies quick checks to determine whether |image| *may* point to the start
diff --git a/disassembler_ztf.h b/disassembler_ztf.h
index 0e73c2a..9b4a94b 100644
--- a/disassembler_ztf.h
+++ b/disassembler_ztf.h
@@ -123,6 +123,8 @@ class ZtfTranslator {
// Disassembler for Zucchini Text Format (ZTF).
class DisassemblerZtf : public Disassembler {
public:
+ static constexpr uint16_t kVersion = 1;
+
// Target Pools
enum ReferencePool : uint8_t {
kAngles, // <>
diff --git a/element_detection.cc b/element_detection.cc
index 356c0d7..5548610 100644
--- a/element_detection.cc
+++ b/element_detection.cc
@@ -9,6 +9,7 @@
#include "components/zucchini/buildflags.h"
#include "components/zucchini/disassembler.h"
#include "components/zucchini/disassembler_no_op.h"
+#include "components/zucchini/patch_utils.h"
#if BUILDFLAG(ENABLE_DEX)
#include "components/zucchini/disassembler_dex.h"
@@ -134,6 +135,40 @@ std::unique_ptr<Disassembler> MakeDisassemblerOfType(ConstBufferView image,
}
}
+uint16_t DisassemblerVersionOfType(ExecutableType exe_type) {
+ switch (exe_type) {
+#if BUILDFLAG(ENABLE_WIN)
+ case kExeTypeWin32X86:
+ return DisassemblerWin32X86::kVersion;
+ case kExeTypeWin32X64:
+ return DisassemblerWin32X64::kVersion;
+#endif // BUILDFLAG(ENABLE_WIN)
+#if BUILDFLAG(ENABLE_ELF)
+ case kExeTypeElfX86:
+ return DisassemblerElfX86::kVersion;
+ case kExeTypeElfX64:
+ return DisassemblerElfX64::kVersion;
+ case kExeTypeElfAArch32:
+ return DisassemblerElfAArch32::kVersion;
+ case kExeTypeElfAArch64:
+ return DisassemblerElfAArch64::kVersion;
+#endif // BUILDFLAG(ENABLE_ELF)
+#if BUILDFLAG(ENABLE_DEX)
+ case kExeTypeDex:
+ return DisassemblerDex::kVersion;
+#endif // BUILDFLAG(ENABLE_DEX)
+#if BUILDFLAG(ENABLE_ZTF)
+ case kExeTypeZtf:
+ return DisassemblerZtf::kVersion;
+#endif // BUILDFLAG(ENABLE_ZTF)
+ case kExeTypeNoOp:
+ return DisassemblerNoOp::kVersion;
+ default:
+ // If an architecture is disabled then null is handled gracefully.
+ return kInvalidVersion;
+ }
+}
+
absl::optional<Element> DetectElementFromDisassembler(ConstBufferView image) {
std::unique_ptr<Disassembler> disasm = MakeDisassemblerWithoutFallback(image);
if (disasm)
diff --git a/element_detection.h b/element_detection.h
index 856ec27..febedc5 100644
--- a/element_detection.h
+++ b/element_detection.h
@@ -28,6 +28,9 @@ std::unique_ptr<Disassembler> MakeDisassemblerWithoutFallback(
std::unique_ptr<Disassembler> MakeDisassemblerOfType(ConstBufferView image,
ExecutableType exe_type);
+// Returns the version associated with disassembler of type |exe_type|.
+uint16_t DisassemblerVersionOfType(ExecutableType exe_type);
+
// Attempts to detect an element associated with |image| and returns it, or
// returns nullopt if no element is detected.
using ElementDetector =
diff --git a/equivalence_map.cc b/equivalence_map.cc
index 26c0764..b4d5e27 100644
--- a/equivalence_map.cc
+++ b/equivalence_map.cc
@@ -214,7 +214,7 @@ EquivalenceCandidate VisitEquivalenceSeed(
/******** OffsetMapper ********/
-OffsetMapper::OffsetMapper(std::vector<Equivalence>&& equivalences,
+OffsetMapper::OffsetMapper(std::deque<Equivalence>&& equivalences,
offset_t old_image_size,
offset_t new_image_size)
: equivalences_(std::move(equivalences)),
@@ -291,7 +291,7 @@ offset_t OffsetMapper::ExtendedForwardProject(offset_t offset) const {
: kOffsetBound - 1;
}
-void OffsetMapper::ForwardProjectAll(std::vector<offset_t>* offsets) const {
+void OffsetMapper::ForwardProjectAll(std::deque<offset_t>* offsets) const {
DCHECK(std::is_sorted(offsets->begin(), offsets->end()));
auto current = equivalences_.begin();
for (auto& src : *offsets) {
@@ -310,7 +310,7 @@ void OffsetMapper::ForwardProjectAll(std::vector<offset_t>* offsets) const {
}
void OffsetMapper::PruneEquivalencesAndSortBySource(
- std::vector<Equivalence>* equivalences) {
+ std::deque<Equivalence>* equivalences) {
std::sort(equivalences->begin(), equivalences->end(),
[](const Equivalence& a, const Equivalence& b) {
return a.src_offset < b.src_offset;
@@ -368,6 +368,7 @@ void OffsetMapper::PruneEquivalencesAndSortBySource(
base::EraseIf(*equivalences, [](const Equivalence& equivalence) {
return equivalence.length == 0;
});
+ equivalences->shrink_to_fit();
}
/******** EquivalenceMap ********/
diff --git a/equivalence_map.h b/equivalence_map.h
index 8b716a1..af99ac4 100644
--- a/equivalence_map.h
+++ b/equivalence_map.h
@@ -7,6 +7,7 @@
#include <stddef.h>
+#include <deque>
#include <limits>
#include <vector>
@@ -82,13 +83,13 @@ EquivalenceCandidate VisitEquivalenceSeed(
// bytes in |old_image| and |new_image|) one-to-one.
class OffsetMapper {
public:
- using const_iterator = std::vector<Equivalence>::const_iterator;
+ using const_iterator = std::deque<Equivalence>::const_iterator;
// Constructors for various data sources. "Old" and "new" image sizes are
// needed for bounds checks and to handle dangling targets.
// - From a list of |equivalences|, already sorted (by |src_offset|) and
// pruned, useful for tests.
- OffsetMapper(std::vector<Equivalence>&& equivalences,
+ OffsetMapper(std::deque<Equivalence>&& equivalences,
offset_t old_image_size,
offset_t new_image_size);
// - From a generator, useful for Zucchini-apply.
@@ -128,10 +129,10 @@ class OffsetMapper {
// Given sorted |offsets|, applies a projection in-place of all offsets that
// are part of a pruned equivalence from |old_image| to |new_image|. Other
// offsets are removed from |offsets|.
- void ForwardProjectAll(std::vector<offset_t>* offsets) const;
+ void ForwardProjectAll(std::deque<offset_t>* offsets) const;
// Accessor for testing.
- const std::vector<Equivalence> equivalences() const { return equivalences_; }
+ const std::deque<Equivalence> equivalences() const { return equivalences_; }
// Sorts |equivalences| by |src_offset| and removes all source overlaps; so a
// source location that was covered by some Equivalence would become covered
@@ -140,12 +141,12 @@ class OffsetMapper {
// of a tie, the Equivalence with minimal |src_offset|. |equivalences| may
// change in size since empty Equivalences are removed.
static void PruneEquivalencesAndSortBySource(
- std::vector<Equivalence>* equivalences);
+ std::deque<Equivalence>* equivalences);
private:
// |equivalences_| is pruned, i.e., no "old" blocks overlap (and no "new"
// block overlaps). Also, it is sorted by "old" offsets.
- std::vector<Equivalence> equivalences_;
+ std::deque<Equivalence> equivalences_;
const offset_t old_image_size_;
const offset_t new_image_size_;
};
diff --git a/equivalence_map_unittest.cc b/equivalence_map_unittest.cc
index b3a4ea4..6b826d1 100644
--- a/equivalence_map_unittest.cc
+++ b/equivalence_map_unittest.cc
@@ -22,7 +22,7 @@ namespace zucchini {
namespace {
-using OffsetVector = std::vector<offset_t>;
+using OffsetDeque = std::deque<offset_t>;
// Make all references 2 bytes long.
constexpr offset_t kReferenceSize = 2;
@@ -252,28 +252,28 @@ TEST(EquivalenceMapTest, ExtendEquivalenceBackward) {
TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) {
auto PruneEquivalencesAndSortBySourceTest =
- [](std::vector<Equivalence>&& equivalences) {
+ [](std::deque<Equivalence>&& equivalences) {
OffsetMapper::PruneEquivalencesAndSortBySource(&equivalences);
return std::move(equivalences);
};
- EXPECT_EQ(std::vector<Equivalence>(),
+ EXPECT_EQ(std::deque<Equivalence>(),
PruneEquivalencesAndSortBySourceTest({}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 1}}));
- EXPECT_EQ(std::vector<Equivalence>(),
+ EXPECT_EQ(std::deque<Equivalence>(),
PruneEquivalencesAndSortBySourceTest({{0, 10, 0}}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}, {1, 11, 1}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}, {1, 11, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 1}, {1, 11, 1}}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}, {2, 13, 1}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}, {2, 13, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 2}}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 1}}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 2}, {2, 14, 1}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}, {2, 14, 1}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 13, 2}}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 1}, {1, 12, 3}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}, {1, 12, 3}}),
PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 3}}));
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, 3}, {3, 16, 2}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, 3}, {3, 16, 2}}),
PruneEquivalencesAndSortBySourceTest(
{{0, 10, 3}, {1, 13, 3}, {3, 16, 2}})); // Pruning is greedy
@@ -288,9 +288,9 @@ TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) {
// ***************
// This test case makes sure the function does not stall on a large instance
// of this pattern.
- EXPECT_EQ(std::vector<Equivalence>({{0, 10, +300000}, {300000, 30, +300000}}),
+ EXPECT_EQ(std::deque<Equivalence>({{0, 10, +300000}, {300000, 30, +300000}}),
PruneEquivalencesAndSortBySourceTest([] {
- std::vector<Equivalence> equivalenses;
+ std::deque<Equivalence> equivalenses;
equivalenses.push_back({0, 10, +300000});
for (offset_t i = 0; i < 100000; ++i)
equivalenses.push_back({200000 + i, 20, +200000 - 2 * i});
@@ -302,7 +302,7 @@ TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) {
TEST(EquivalenceMapTest, NaiveExtendedForwardProject) {
constexpr size_t kOldImageSize = 1000U;
constexpr size_t kNewImageSize = 1000U;
- OffsetMapper offset_mapper(std::vector<Equivalence>(), kOldImageSize,
+ OffsetMapper offset_mapper(std::deque<Equivalence>(), kOldImageSize,
kNewImageSize);
// Convenience function to declutter.
@@ -417,7 +417,7 @@ TEST(EquivalenceMapTest, ExtendedForwardProjectEncoding) {
// - '.' are "new" offsets that appear as output.
// - '(' and ')' surround a single "new" location that are repeated as output.
int case_no = 0;
- auto run_test = [&case_no](std::vector<Equivalence>&& equivalences,
+ auto run_test = [&case_no](std::deque<Equivalence>&& equivalences,
const std::string& old_spec,
const std::string& new_spec) {
const size_t old_size = old_spec.length();
@@ -504,7 +504,7 @@ TEST(EquivalenceMapTest, ExtendedForwardProjectEncoding) {
TEST(EquivalenceMapTest, ForwardProjectAll) {
auto ForwardProjectAllTest = [](const OffsetMapper& offset_mapper,
std::initializer_list<offset_t> offsets) {
- OffsetVector offsets_vec(offsets);
+ std::deque<offset_t> offsets_vec(offsets);
offset_mapper.ForwardProjectAll(&offsets_vec);
return offsets_vec;
};
@@ -512,29 +512,29 @@ TEST(EquivalenceMapTest, ForwardProjectAll) {
// [0,2) --> [10,12), [2,3) --> [13,14), [4,6) --> [16,18).
OffsetMapper offset_mapper1({{0, 10, +2}, {2, 13, +1}, {4, 16, +2}}, 100U,
100U);
- EXPECT_EQ(OffsetVector({10}), ForwardProjectAllTest(offset_mapper1, {0}));
- EXPECT_EQ(OffsetVector({13}), ForwardProjectAllTest(offset_mapper1, {2}));
- EXPECT_EQ(OffsetVector({}), ForwardProjectAllTest(offset_mapper1, {3}));
- EXPECT_EQ(OffsetVector({10, 13}),
+ EXPECT_EQ(OffsetDeque({10}), ForwardProjectAllTest(offset_mapper1, {0}));
+ EXPECT_EQ(OffsetDeque({13}), ForwardProjectAllTest(offset_mapper1, {2}));
+ EXPECT_EQ(OffsetDeque({}), ForwardProjectAllTest(offset_mapper1, {3}));
+ EXPECT_EQ(OffsetDeque({10, 13}),
ForwardProjectAllTest(offset_mapper1, {0, 2}));
- EXPECT_EQ(OffsetVector({11, 13, 17}),
+ EXPECT_EQ(OffsetDeque({11, 13, 17}),
ForwardProjectAllTest(offset_mapper1, {1, 2, 5}));
- EXPECT_EQ(OffsetVector({11, 17}),
+ EXPECT_EQ(OffsetDeque({11, 17}),
ForwardProjectAllTest(offset_mapper1, {1, 3, 5}));
- EXPECT_EQ(OffsetVector({10, 11, 13, 16, 17}),
+ EXPECT_EQ(OffsetDeque({10, 11, 13, 16, 17}),
ForwardProjectAllTest(offset_mapper1, {0, 1, 2, 3, 4, 5, 6}));
// [0,2) --> [10,12), [13,14) --> [2,3), [16,18) --> [4,6).
OffsetMapper offset_mapper2({{0, 10, +2}, {13, 2, +1}, {16, 4, +2}}, 100U,
100U);
- EXPECT_EQ(OffsetVector({2}), ForwardProjectAllTest(offset_mapper2, {13}));
- EXPECT_EQ(OffsetVector({10, 2}),
+ EXPECT_EQ(OffsetDeque({2}), ForwardProjectAllTest(offset_mapper2, {13}));
+ EXPECT_EQ(OffsetDeque({10, 2}),
ForwardProjectAllTest(offset_mapper2, {0, 13}));
- EXPECT_EQ(OffsetVector({11, 2, 5}),
+ EXPECT_EQ(OffsetDeque({11, 2, 5}),
ForwardProjectAllTest(offset_mapper2, {1, 13, 17}));
- EXPECT_EQ(OffsetVector({11, 5}),
+ EXPECT_EQ(OffsetDeque({11, 5}),
ForwardProjectAllTest(offset_mapper2, {1, 14, 17}));
- EXPECT_EQ(OffsetVector({10, 11, 2, 4, 5}),
+ EXPECT_EQ(OffsetDeque({10, 11, 2, 4, 5}),
ForwardProjectAllTest(offset_mapper2, {0, 1, 13, 14, 16, 17, 18}));
}
diff --git a/fuzzers/testdata/patch_fuzzer/empty.zuc b/fuzzers/testdata/patch_fuzzer/empty.zuc
index 64eacf5..1bda1e9 100644
--- a/fuzzers/testdata/patch_fuzzer/empty.zuc
+++ b/fuzzers/testdata/patch_fuzzer/empty.zuc
Binary files differ
diff --git a/main_utils.cc b/main_utils.cc
index 8c47c91..b499817 100644
--- a/main_utils.cc
+++ b/main_utils.cc
@@ -22,6 +22,7 @@
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/zucchini/io_utils.h"
+#include "components/zucchini/patch_utils.h"
#include "components/zucchini/zucchini_commands.h"
#if defined(OS_WIN)
@@ -69,6 +70,7 @@ constexpr Command kCommands[] = {
3, &MainGen},
{"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3,
&MainApply},
+ {"verify", "-verify <patch_file>", 1, &MainVerify},
{"read", "-read <exe> [-dump]", 1, &MainRead},
{"detect", "-detect <archive_file>", 1, &MainDetect},
{"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2,
@@ -206,6 +208,8 @@ bool CheckAndGetFilePathParams(const base::CommandLine& command_line,
// Prints main Zucchini usage text.
void PrintUsage(std::ostream& err) {
+ err << "Version: " << zucchini::kMajorVersion << "."
+ << zucchini::kMinorVersion << std::endl;
err << "Usage:" << std::endl;
for (const Command& command : kCommands)
err << " zucchini " << command.usage << std::endl;
diff --git a/patch_read_write_unittest.cc b/patch_read_write_unittest.cc
index 627513c..25e1fb0 100644
--- a/patch_read_write_unittest.cc
+++ b/patch_read_write_unittest.cc
@@ -63,6 +63,7 @@ ByteVector CreatePatchElement() {
0x03, 0, 0, 0, // new_offset
0x13, 0, 0, 0, // new_length
'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86
+ 0x01, 0x00, // element version
// EquivalenceSource
1, 0, 0, 0, // src_skip size
0x10, // src_skip content
@@ -95,11 +96,12 @@ ByteVector CreatePatchElement() {
ByteVector CreateElementMatch() {
return {
// PatchElementHeader
- 0x01, 0, 0, 0, // old_offset
- 0x02, 0, 0, 0, // old_length
- 0x03, 0, 0, 0, // new_offset
- 0x04, 0, 0, 0, // new_length
- 'D', 'E', 'X', ' ', // exe_type = kExeTypeDex
+ 0x01, 0, 0, 0, // old_offset
+ 0x02, 0, 0, 0, // old_length
+ 0x03, 0, 0, 0, // new_offset
+ 0x04, 0, 0, 0, // new_length
+ 'D', 'E', 'X', ' ', // exe_type = kExeTypeDex
+ 0x01, 0x00, // element version
};
}
@@ -586,10 +588,26 @@ TEST(PatchElementTest, WrongExtraData) {
}
}
+TEST(PatchElementTest, WrongVersion) {
+ // Bump element version to 2.
+ {
+ ByteVector data = CreatePatchElement();
+ ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x02, &data);
+ TestInvalidInitialize<PatchElementReader>(&data);
+ }
+ // Bump element version to 0.
+ {
+ ByteVector data = CreatePatchElement();
+ ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x00, &data);
+ TestInvalidInitialize<PatchElementReader>(&data);
+ }
+}
+
TEST(EnsemblePatchTest, RawPatch) {
ByteVector data = {
// PatchHeader
- 0x5A, 0x75, 0x63, 0x00, // magic
+ 0x5A, 0x75, 0x63, 0x63, // magic
+ 0x01, 0x00, 0x00, 0x00, // major/minor version
0x10, 0x32, 0x54, 0x76, // old_size
0x00, 0x11, 0x22, 0x33, // old_crc
0x01, 0, 0, 0, // new_size
@@ -602,7 +620,8 @@ TEST(EnsemblePatchTest, RawPatch) {
0x02, 0, 0, 0, // old_length
0x00, 0, 0, 0, // new_offset
0x01, 0, 0, 0, // new_length
- 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86
+ 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X8
+ 0x01, 0x00, // element version
// EquivalenceSource
0, 0, 0, 0, // src_skip size
0, 0, 0, 0, // dst_skip size
@@ -624,6 +643,8 @@ TEST(EnsemblePatchTest, RawPatch) {
PatchHeader header = ensemble_patch_reader.header();
EXPECT_EQ(PatchHeader::kMagic, header.magic);
+ EXPECT_EQ(kMajorVersion, header.major_version);
+ EXPECT_EQ(kMinorVersion, header.minor_version);
EXPECT_EQ(0x76543210U, header.old_size);
EXPECT_EQ(0x33221100U, header.old_crc);
EXPECT_EQ(0x01U, header.new_size);
@@ -647,7 +668,8 @@ TEST(EnsemblePatchTest, RawPatch) {
TEST(EnsemblePatchTest, CheckFile) {
ByteVector data = {
// PatchHeader
- 0x5A, 0x75, 0x63, 0x00, // magic
+ 0x5A, 0x75, 0x63, 0x63, // magic
+ 0x01, 0x00, 0x00, 0x00, // major/minor version
0x05, 0x00, 0x00, 0x00, // old_size
0xDF, 0x13, 0xE4, 0x10, // old_crc
0x03, 0x00, 0x00, 0x00, // new_size
@@ -661,6 +683,7 @@ TEST(EnsemblePatchTest, CheckFile) {
0x00, 0, 0, 0, // new_offset
0x03, 0, 0, 0, // new_length
'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86
+ 0x01, 0x00, // element version
// EquivalenceSource
0, 0, 0, 0, // src_skip size
0, 0, 0, 0, // dst_skip size
@@ -695,7 +718,45 @@ TEST(EnsemblePatchTest, CheckFile) {
TEST(EnsemblePatchTest, InvalidMagic) {
ByteVector data = {
// PatchHeader
- 0x42, 0x42, 0x42, 0x00, // magic
+ 0x42, 0x42, 0x42, 0x42, // magic
+ 0x01, 0x00, 0x00, 0x00, // major/minor version
+ 0x10, 0x32, 0x54, 0x76, // old_size
+ 0x00, 0x11, 0x22, 0x33, // old_crc
+ 0x03, 0x00, 0x00, 0x00, // new_size
+ 0x44, 0x55, 0x66, 0x77, // new_crc
+
+ 1, 0, 0, 0, // number of element
+
+ // PatchElementHeader
+ 0x01, 0, 0, 0, // old_offset
+ 0x02, 0, 0, 0, // old_length
+ 0x00, 0, 0, 0, // new_offset
+ 0x03, 0, 0, 0, // new_length
+ 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86
+ 0x01, 0x00, // element version
+ // EquivalenceSource
+ 0, 0, 0, 0, // src_skip size
+ 0, 0, 0, 0, // dst_skip size
+ 0, 0, 0, 0, // copy_count size
+ // ExtraDataSource
+ 0, 0, 0, 0, // extra_data size
+ // RawDeltaSource
+ 0, 0, 0, 0, // raw_delta_skip size
+ 0, 0, 0, 0, // raw_delta_diff size
+ // ReferenceDeltaSource
+ 0, 0, 0, 0, // reference_delta size
+ // PatchElementReader
+ 0, 0, 0, 0, // pool count
+ };
+
+ TestInvalidInitialize<EnsemblePatchReader>(&data);
+}
+
+TEST(EnsemblePatchTest, InvalidVersion) {
+ ByteVector data = {
+ // PatchHeader
+ 0x5A, 0x75, 0x63, 0x63, // magic
+ 0x02, 0x01, 0x00, 0x00, // major/minor version
0x10, 0x32, 0x54, 0x76, // old_size
0x00, 0x11, 0x22, 0x33, // old_crc
0x03, 0x00, 0x00, 0x00, // new_size
@@ -709,6 +770,7 @@ TEST(EnsemblePatchTest, InvalidMagic) {
0x00, 0, 0, 0, // new_offset
0x03, 0, 0, 0, // new_length
'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86
+ 0x01, 0x00, // element version
// EquivalenceSource
0, 0, 0, 0, // src_skip size
0, 0, 0, 0, // dst_skip size
diff --git a/patch_reader.cc b/patch_reader.cc
index 99951da..8fd9b57 100644
--- a/patch_reader.cc
+++ b/patch_reader.cc
@@ -10,6 +10,7 @@
#include "base/numerics/safe_conversions.h"
#include "components/zucchini/algorithm.h"
#include "components/zucchini/crc32.h"
+#include "components/zucchini/element_detection.h"
namespace zucchini {
@@ -27,6 +28,12 @@ bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) {
LOG(ERROR) << "Invalid ExecutableType found.";
return false;
}
+ uint16_t element_version = DisassemblerVersionOfType(exe_type);
+ if (element_version != unsafe_element_header.version) {
+ LOG(ERROR) << "Element version doesn't match. Expected: " << element_version
+ << ", Actual:" << unsafe_element_header.version;
+ return false;
+ }
if (!unsafe_element_header.old_length || !unsafe_element_header.new_length) {
LOG(ERROR) << "Empty patch element found.";
return false;
@@ -334,6 +341,11 @@ bool EnsemblePatchReader::Initialize(BufferSource* source) {
LOG(ERROR) << "Patch contains invalid magic.";
return false;
}
+ if (header_.major_version != kMajorVersion) {
+ LOG(ERROR) << "Patch major version doesn't match. Expected: "
+ << kMajorVersion << ", Actual:" << header_.major_version;
+ return false;
+ }
// |header_| is assumed to be safe from this point forward.
uint32_t element_count = 0;
diff --git a/patch_utils.h b/patch_utils.h
index 5f49195..822fedc 100644
--- a/patch_utils.h
+++ b/patch_utils.h
@@ -14,6 +14,17 @@
namespace zucchini {
+// A change in major version indicates breaking changes such that a patch
+// definitely cannot be applied by a zucchini binary whose major version doesn't
+// match.
+enum : uint16_t { kMajorVersion = 1 };
+// A change in minor version indicates possibly breaking changes at the element
+// level, such that it may not be possible to apply a patch whose minor version
+// doesn't match this version. To determine if a given patch may be applied with
+// this version, VerifyPatch() should be called.
+enum : uint16_t { kMinorVersion = 0 };
+enum : uint16_t { kInvalidVersion = 0xffff };
+
// A Zucchini 'ensemble' patch is the concatenation of a patch header with a
// list of patch 'elements', each containing data for patching individual
// elements.
@@ -24,9 +35,11 @@ namespace zucchini {
// Header for a Zucchini patch, found at the beginning of an ensemble patch.
struct PatchHeader {
// Magic signature at the beginning of a Zucchini patch file.
- enum : uint32_t { kMagic = 'Z' | ('u' << 8) | ('c' << 16) };
+ enum : uint32_t { kMagic = 'Z' | ('u' << 8) | ('c' << 16) | ('c' << 24) };
uint32_t magic = 0;
+ uint16_t major_version = kInvalidVersion;
+ uint16_t minor_version = kInvalidVersion;
uint32_t old_size = 0;
uint32_t old_crc = 0;
uint32_t new_size = 0;
@@ -34,7 +47,7 @@ struct PatchHeader {
};
// Sanity check.
-static_assert(sizeof(PatchHeader) == 20, "PatchHeader must be 20 bytes");
+static_assert(sizeof(PatchHeader) == 24, "PatchHeader must be 24 bytes");
// Header for a patch element, found at the beginning of every patch element.
struct PatchElementHeader {
@@ -43,11 +56,12 @@ struct PatchElementHeader {
uint32_t new_offset;
uint32_t new_length;
uint32_t exe_type; // ExecutableType.
+ uint16_t version = kInvalidVersion;
};
// Sanity check.
-static_assert(sizeof(PatchElementHeader) == 20,
- "PatchElementHeader must be 20 bytes");
+static_assert(sizeof(PatchElementHeader) == 22,
+ "PatchElementHeader must be 22 bytes");
#pragma pack(pop)
diff --git a/patch_writer.cc b/patch_writer.cc
index 1206208..04f3244 100644
--- a/patch_writer.cc
+++ b/patch_writer.cc
@@ -10,6 +10,7 @@
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "components/zucchini/crc32.h"
+#include "components/zucchini/element_detection.h"
namespace zucchini {
@@ -30,6 +31,7 @@ bool SerializeElementMatch(const ElementMatch& element_match,
element_header.new_length =
base::checked_cast<uint32_t>(element_match.new_element.size);
element_header.exe_type = element_match.exe_type();
+ element_header.version = DisassemblerVersionOfType(element_match.exe_type());
return sink->PutValue<PatchElementHeader>(element_header);
}
@@ -248,11 +250,15 @@ EnsemblePatchWriter::~EnsemblePatchWriter() = default;
EnsemblePatchWriter::EnsemblePatchWriter(const PatchHeader& header)
: header_(header) {
DCHECK_EQ(header_.magic, PatchHeader::kMagic);
+ DCHECK_EQ(header_.major_version, kMajorVersion);
+ DCHECK_EQ(header_.minor_version, kMinorVersion);
}
EnsemblePatchWriter::EnsemblePatchWriter(ConstBufferView old_image,
ConstBufferView new_image) {
header_.magic = PatchHeader::kMagic;
+ header_.major_version = kMajorVersion;
+ header_.minor_version = kMinorVersion;
header_.old_size = base::checked_cast<uint32_t>(old_image.size());
header_.old_crc = CalculateCrc32(old_image.begin(), old_image.end());
header_.new_size = base::checked_cast<uint32_t>(new_image.size());
diff --git a/target_pool.cc b/target_pool.cc
index 23551fd..e15d0b9 100644
--- a/target_pool.cc
+++ b/target_pool.cc
@@ -16,7 +16,7 @@ namespace zucchini {
TargetPool::TargetPool() = default;
-TargetPool::TargetPool(std::vector<offset_t>&& targets) {
+TargetPool::TargetPool(std::deque<offset_t>&& targets) {
DCHECK(targets_.empty());
DCHECK(std::is_sorted(targets.begin(), targets.end()));
targets_ = std::move(targets);
diff --git a/target_pool.h b/target_pool.h
index 27884d6..fb462b2 100644
--- a/target_pool.h
+++ b/target_pool.h
@@ -7,6 +7,7 @@
#include <stddef.h>
+#include <deque>
#include <vector>
#include "components/zucchini/image_utils.h"
@@ -21,11 +22,11 @@ class TargetSource;
// with a list of associated reference types, only used during patch generation.
class TargetPool {
public:
- using const_iterator = std::vector<offset_t>::const_iterator;
+ using const_iterator = std::deque<offset_t>::const_iterator;
TargetPool();
// Initializes the object with given sorted and unique |targets|.
- explicit TargetPool(std::vector<offset_t>&& targets);
+ explicit TargetPool(std::deque<offset_t>&& targets);
TargetPool(TargetPool&&);
TargetPool(const TargetPool&);
~TargetPool();
@@ -62,7 +63,7 @@ class TargetPool {
void FilterAndProject(const OffsetMapper& offset_mapper);
// Accessors for testing.
- const std::vector<offset_t>& targets() const { return targets_; }
+ const std::deque<offset_t>& targets() const { return targets_; }
const std::vector<TypeTag>& types() const { return types_; }
// Returns the number of targets.
@@ -72,7 +73,7 @@ class TargetPool {
private:
std::vector<TypeTag> types_; // Enumerates type_tag for this pool.
- std::vector<offset_t> targets_; // Targets for pool in ascending order.
+ std::deque<offset_t> targets_; // Targets for pool in ascending order.
};
} // namespace zucchini
diff --git a/target_pool_unittest.cc b/target_pool_unittest.cc
index 4c3efec..9a779b0 100644
--- a/target_pool_unittest.cc
+++ b/target_pool_unittest.cc
@@ -5,9 +5,9 @@
#include "components/zucchini/target_pool.h"
#include <cmath>
+#include <deque>
#include <string>
#include <utility>
-#include <vector>
#include "components/zucchini/image_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -16,29 +16,29 @@ namespace zucchini {
namespace {
-using OffsetVector = std::vector<offset_t>;
+using OffsetDeque = std::deque<offset_t>;
} // namespace
TEST(TargetPoolTest, InsertTargetsFromReferences) {
- auto test_insert = [](std::vector<Reference>&& references) -> OffsetVector {
+ auto test_insert = [](std::vector<Reference>&& references) -> OffsetDeque {
TargetPool target_pool;
target_pool.InsertTargets(references);
// Return copy since |target_pool| goes out of scope.
return target_pool.targets();
};
- EXPECT_EQ(OffsetVector(), test_insert({}));
- EXPECT_EQ(OffsetVector({0, 1}), test_insert({{0, 0}, {10, 1}}));
- EXPECT_EQ(OffsetVector({0, 1}), test_insert({{0, 1}, {10, 0}}));
- EXPECT_EQ(OffsetVector({0, 1, 2}), test_insert({{0, 1}, {10, 0}, {20, 2}}));
- EXPECT_EQ(OffsetVector({0}), test_insert({{0, 0}, {10, 0}}));
- EXPECT_EQ(OffsetVector({0, 1}), test_insert({{0, 0}, {10, 0}, {20, 1}}));
+ EXPECT_EQ(OffsetDeque(), test_insert({}));
+ EXPECT_EQ(OffsetDeque({0, 1}), test_insert({{0, 0}, {10, 1}}));
+ EXPECT_EQ(OffsetDeque({0, 1}), test_insert({{0, 1}, {10, 0}}));
+ EXPECT_EQ(OffsetDeque({0, 1, 2}), test_insert({{0, 1}, {10, 0}, {20, 2}}));
+ EXPECT_EQ(OffsetDeque({0}), test_insert({{0, 0}, {10, 0}}));
+ EXPECT_EQ(OffsetDeque({0, 1}), test_insert({{0, 0}, {10, 0}, {20, 1}}));
}
TEST(TargetPoolTest, KeyOffset) {
auto test_key_offset = [](const std::string& nearest_offsets_key,
- OffsetVector&& targets) {
+ OffsetDeque&& targets) {
TargetPool target_pool(std::move(targets));
for (offset_t offset : target_pool.targets()) {
offset_t key = target_pool.KeyForOffset(offset);
diff --git a/targets_affinity.cc b/targets_affinity.cc
index d083787..b9a4877 100644
--- a/targets_affinity.cc
+++ b/targets_affinity.cc
@@ -21,8 +21,8 @@ TargetsAffinity::~TargetsAffinity() = default;
void TargetsAffinity::InferFromSimilarities(
const EquivalenceMap& equivalences,
- const std::vector<offset_t>& old_targets,
- const std::vector<offset_t>& new_targets) {
+ const std::deque<offset_t>& old_targets,
+ const std::deque<offset_t>& new_targets) {
forward_association_.assign(old_targets.size(), {});
backward_association_.assign(new_targets.size(), {});
diff --git a/targets_affinity.h b/targets_affinity.h
index dff1741..163b015 100644
--- a/targets_affinity.h
+++ b/targets_affinity.h
@@ -8,6 +8,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <deque>
#include <vector>
#include "components/zucchini/image_utils.h"
@@ -30,8 +31,8 @@ class TargetsAffinity {
// affinity scores. Both |old_targets| and |new_targets| are targets in the
// same pool and are sorted in ascending order.
void InferFromSimilarities(const EquivalenceMap& equivalence_map,
- const std::vector<offset_t>& old_targets,
- const std::vector<offset_t>& new_targets);
+ const std::deque<offset_t>& old_targets,
+ const std::deque<offset_t>& new_targets);
// Assigns labels to targets based on associations previously inferred, using
// |min_affinity| to reject associations with weak |affinity|. Label 0 is
diff --git a/targets_affinity_unittest.cc b/targets_affinity_unittest.cc
index 86182f9..abcbd3f 100644
--- a/targets_affinity_unittest.cc
+++ b/targets_affinity_unittest.cc
@@ -25,8 +25,8 @@ TEST(TargetsAffinityTest, AffinityBetween) {
auto test_affinity = [&targets_affinity](
const EquivalenceMap& equivalence_map,
- const std::vector<offset_t>& old_targets,
- const std::vector<offset_t>& new_targets) {
+ const std::deque<offset_t>& old_targets,
+ const std::deque<offset_t>& new_targets) {
targets_affinity.InferFromSimilarities(equivalence_map, old_targets,
new_targets);
AffinityVector affinities(old_targets.size());
@@ -81,8 +81,8 @@ TEST(TargetsAffinityTest, AssignLabels) {
auto test_labels_assignment =
[&targets_affinity](const EquivalenceMap& equivalence_map,
- const std::vector<offset_t>& old_targets,
- const std::vector<offset_t>& new_targets,
+ const std::deque<offset_t>& old_targets,
+ const std::deque<offset_t>& new_targets,
double min_affinity,
const std::vector<uint32_t>& expected_old_labels,
const std::vector<uint32_t>& expected_new_labels) {
diff --git a/testdata/all.smali b/testdata/all.smali
new file mode 100644
index 0000000..7a1d272
--- /dev/null
+++ b/testdata/all.smali
@@ -0,0 +1,628 @@
+# Tests most/all DEX behaviors as of version 37.
+# Disassembled from dexdump test files.
+# Repo: https://android.googlesource.com/platform/art/
+# File: test/dexdump/all.dex
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble all.smali --api 25
+
+.class public LA;
+.super Ljava/lang/Object;
+
+
+# static fields
+.field private static sB:B
+
+.field private static sC:C
+
+.field private static sI:I
+
+.field private static sJ:J
+
+.field private static sO:LA;
+
+.field private static sS:S
+
+.field private static sZ:Z
+
+
+# instance fields
+.field private mB:B
+
+.field private mC:C
+
+.field private mI:I
+
+.field private mJ:J
+
+.field private mO:LA;
+
+.field private mS:S
+
+.field private mZ:Z
+
+
+# direct methods
+.method public constructor <init>()V
+ .registers 1
+
+ invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+
+ return-void
+.end method
+
+.method public static arrays()V
+ .registers 3
+
+ aget v0, v1, v2
+
+ aget-wide v0, v1, v2
+
+ aget-object v0, v1, v2
+
+ aget-boolean v0, v1, v2
+
+ aget-byte v0, v1, v2
+
+ aget-char v0, v1, v2
+
+ aget-short v0, v1, v2
+
+ aput v0, v1, v2
+
+ aput-wide v0, v1, v2
+
+ aput-object v0, v1, v2
+
+ aput-boolean v0, v1, v2
+
+ aput-byte v0, v1, v2
+
+ aput-char v0, v1, v2
+
+ aput-short v0, v1, v2
+
+ return-void
+.end method
+
+.method public static binary_ops()V
+ .registers 3
+
+ add-int v0, v1, v2
+
+ sub-int v0, v1, v2
+
+ mul-int v0, v1, v2
+
+ div-int v0, v1, v2
+
+ rem-int v0, v1, v2
+
+ and-int v0, v1, v2
+
+ or-int v0, v1, v2
+
+ xor-int v0, v1, v2
+
+ shl-int v0, v1, v2
+
+ shr-int v0, v1, v2
+
+ ushr-int v0, v1, v2
+
+ add-long v0, v1, v2
+
+ sub-long v0, v1, v2
+
+ mul-long v0, v1, v2
+
+ div-long v0, v1, v2
+
+ rem-long v0, v1, v2
+
+ and-long v0, v1, v2
+
+ or-long v0, v1, v2
+
+ xor-long v0, v1, v2
+
+ shl-long v0, v1, v2
+
+ shr-long v0, v1, v2
+
+ ushr-long v0, v1, v2
+
+ add-float v0, v1, v2
+
+ sub-float v0, v1, v2
+
+ mul-float v0, v1, v2
+
+ div-float v0, v1, v2
+
+ rem-float v0, v1, v2
+
+ add-double v0, v1, v2
+
+ sub-double v0, v1, v2
+
+ mul-double v0, v1, v2
+
+ div-double v0, v1, v2
+
+ rem-double v0, v1, v2
+
+ return-void
+.end method
+
+.method public static binary_ops_2addr()V
+ .registers 2
+
+ add-int/2addr v0, v1
+
+ sub-int/2addr v0, v1
+
+ mul-int/2addr v0, v1
+
+ div-int/2addr v0, v1
+
+ rem-int/2addr v0, v1
+
+ and-int/2addr v0, v1
+
+ or-int/2addr v0, v1
+
+ xor-int/2addr v0, v1
+
+ shl-int/2addr v0, v1
+
+ shr-int/2addr v0, v1
+
+ ushr-int/2addr v0, v1
+
+ add-long/2addr v0, v1
+
+ sub-long/2addr v0, v1
+
+ mul-long/2addr v0, v1
+
+ div-long/2addr v0, v1
+
+ rem-long/2addr v0, v1
+
+ and-long/2addr v0, v1
+
+ or-long/2addr v0, v1
+
+ xor-long/2addr v0, v1
+
+ shl-long/2addr v0, v1
+
+ shr-long/2addr v0, v1
+
+ ushr-long/2addr v0, v1
+
+ add-float/2addr v0, v1
+
+ sub-float/2addr v0, v1
+
+ mul-float/2addr v0, v1
+
+ div-float/2addr v0, v1
+
+ rem-float/2addr v0, v1
+
+ add-double/2addr v0, v1
+
+ sub-double/2addr v0, v1
+
+ mul-double/2addr v0, v1
+
+ div-double/2addr v0, v1
+
+ rem-double/2addr v0, v1
+
+ return-void
+.end method
+
+.method public static binary_ops_lit16()V
+ .registers 2
+
+ add-int/lit16 v0, v1, 0x1234
+
+ rsub-int v0, v1, 0x1234
+
+ mul-int/lit16 v0, v1, 0x1234
+
+ div-int/lit16 v0, v1, 0x1234
+
+ rem-int/lit16 v0, v1, 0x1234
+
+ and-int/lit16 v0, v1, 0x1234
+
+ or-int/lit16 v0, v1, 0x1234
+
+ xor-int/lit16 v0, v1, 0x1234
+
+ return-void
+.end method
+
+.method public static binary_ops_lit8()V
+ .registers 2
+
+ add-int/lit8 v0, v1, 0x12
+
+ rsub-int/lit8 v0, v1, 0x12
+
+ mul-int/lit8 v0, v1, 0x12
+
+ div-int/lit8 v0, v1, 0x12
+
+ rem-int/lit8 v0, v1, 0x12
+
+ and-int/lit8 v0, v1, 0x12
+
+ or-int/lit8 v0, v1, 0x12
+
+ xor-int/lit8 v0, v1, 0x12
+
+ shl-int/lit8 v0, v1, 0x12
+
+ shr-int/lit8 v0, v1, 0x12
+
+ ushr-int/lit8 v0, v1, 0x12
+
+ return-void
+.end method
+
+.method public static compares()V
+ .registers 3
+
+ cmpl-float v0, v1, v2
+
+ cmpg-float v0, v1, v2
+
+ cmpl-double v0, v1, v2
+
+ cmpg-double v0, v1, v2
+
+ cmp-long v0, v1, v2
+
+ return-void
+.end method
+
+.method public static conditionals()V
+ .registers 2
+
+ if-eq v0, v1, :cond_18
+
+ if-ne v0, v1, :cond_18
+
+ if-lt v0, v1, :cond_18
+
+ if-ge v0, v1, :cond_18
+
+ if-gt v0, v1, :cond_18
+
+ if-le v0, v1, :cond_18
+
+ if-eqz v0, :cond_18
+
+ if-nez v0, :cond_18
+
+ if-ltz v0, :cond_18
+
+ if-gez v0, :cond_18
+
+ if-gtz v0, :cond_18
+
+ if-lez v0, :cond_18
+
+ :cond_18
+ return-void
+.end method
+
+.method public static constants()V
+ .registers 1
+
+ const/4 v0, 0x1
+
+ const/16 v0, 0x1234
+
+ const v0, 0x12345678
+
+ const/high16 v0, 0x12340000
+
+ const-wide/16 v0, 0x1234
+
+ const-wide/32 v0, 0x12345678
+
+ const-wide v0, 0x1234567890abcdefL # 5.626349108908516E-221
+
+ const-wide/high16 v0, 0x1234000000000000L
+
+ const-string v0, "string"
+
+ const-string/jumbo v0, "string"
+
+ const-class v0, Ljava/lang/Object;
+
+ return-void
+.end method
+
+.method public static misc()V
+ .registers 5
+
+ nop
+
+ monitor-enter v0
+
+ monitor-exit v0
+
+ check-cast v0, Ljava/lang/Object;
+
+ instance-of v0, v1, Ljava/lang/Object;
+
+ array-length v0, v1
+
+ new-instance v0, Ljava/lang/Object;
+
+ new-array v0, v1, Ljava/lang/Object;
+
+ filled-new-array {v0, v1, v2, v3, v4}, [Ljava/lang/Object;
+
+ filled-new-array/range {v0 .. v4}, [Ljava/lang/Object;
+
+ fill-array-data v0, :array_1e
+
+ throw v0
+
+ goto :goto_1c
+
+ goto/16 :goto_1c
+
+ goto/32 :goto_1c
+
+ :goto_1c
+ return-void
+
+ nop
+
+ :array_1e
+ .array-data 4
+ 0x1
+ 0x2
+ 0x3
+ 0x4
+ 0x5
+ 0x6
+ 0x7
+ 0x8
+ 0x9
+ 0x0
+ .end array-data
+.end method
+
+.method public static moves()V
+ .registers 2
+
+ move v0, v1
+
+ move/from16 v0, v1
+
+ move/16 v0, v1
+
+ move-wide v0, v1
+
+ move-wide/from16 v0, v1
+
+ move-wide/16 v0, v1
+
+ move-object v0, v1
+
+ move-object/from16 v0, v1
+
+ move-object/16 v0, v1
+
+ move-result v0
+
+ move-result-wide v0
+
+ move-result-object v0
+
+ move-exception v0
+
+ return-void
+.end method
+
+.method public static packed_switch()V
+ .registers 1
+
+ packed-switch v0, :pswitch_data_8
+
+ :goto_3
+ return-void
+
+ goto :goto_3
+
+ :pswitch_5
+ goto :goto_3
+
+ :pswitch_6
+ goto :goto_3
+
+ nop
+
+ :pswitch_data_8
+ .packed-switch 0x7ffffffe
+ :pswitch_5
+ :pswitch_6
+ .end packed-switch
+.end method
+
+.method public static return32()I
+ .registers 1
+
+ return v0
+.end method
+
+.method public static return64()I
+ .registers 2
+
+ return-wide v0
+.end method
+
+.method public static return_object()Ljava/lang/Object;
+ .registers 1
+
+ return-object v0
+.end method
+
+.method public static sparse_switch()V
+ .registers 2
+
+ sparse-switch v0, :sswitch_data_4
+
+ :sswitch_3
+ return-void
+
+ :sswitch_data_4
+ .sparse-switch
+ 0x1111 -> :sswitch_3
+ 0x2222 -> :sswitch_3
+ 0x3333 -> :sswitch_3
+ 0x4444 -> :sswitch_3
+ .end sparse-switch
+.end method
+
+.method public static static_fields()V
+ .registers 1
+
+ sget v0, LA;->sI:I
+
+ sget-wide v0, LA;->sJ:J
+
+ sget-object v0, LA;->sO:LA;
+
+ sget-boolean v0, LA;->sZ:Z
+
+ sget-byte v0, LA;->sB:B
+
+ sget-char v0, LA;->sC:C
+
+ sget-short v0, LA;->sS:S
+
+ sput v0, LA;->sI:I
+
+ sput-wide v0, LA;->sJ:J
+
+ sput-object v0, LA;->sO:LA;
+
+ sput-boolean v0, LA;->sZ:Z
+
+ sput-byte v0, LA;->sB:B
+
+ sput-char v0, LA;->sC:C
+
+ sput-short v0, LA;->mS:S
+
+ return-void
+.end method
+
+.method public static unary_ops()V
+ .registers 2
+
+ neg-int v0, v1
+
+ not-int v0, v1
+
+ neg-long v0, v1
+
+ not-long v0, v1
+
+ neg-float v0, v1
+
+ neg-double v0, v1
+
+ int-to-long v0, v1
+
+ int-to-float v0, v1
+
+ int-to-double v0, v1
+
+ long-to-int v0, v1
+
+ long-to-float v0, v1
+
+ long-to-double v0, v1
+
+ float-to-int v0, v1
+
+ float-to-long v0, v1
+
+ float-to-double v0, v1
+
+ double-to-int v0, v1
+
+ double-to-long v0, v1
+
+ double-to-float v0, v1
+
+ int-to-byte v0, v1
+
+ int-to-char v0, v1
+
+ int-to-short v0, v1
+
+ return-void
+.end method
+
+
+# virtual methods
+.method public instance_fields()V
+ .registers 2
+
+ iget v0, p0, LA;->sI:I
+
+ iget-wide v0, p0, LA;->sJ:J
+
+ iget-object v0, p0, LA;->sO:LA;
+
+ iget-boolean v0, p0, LA;->sZ:Z
+
+ iget-byte v0, p0, LA;->sB:B
+
+ iget-char v0, p0, LA;->sC:C
+
+ iget-short v0, p0, LA;->sS:S
+
+ iput v0, p0, LA;->sI:I
+
+ iput-wide v0, p0, LA;->sJ:J
+
+ iput-object v0, p0, LA;->sO:LA;
+
+ iput-boolean v0, p0, LA;->sZ:Z
+
+ iput-byte v0, p0, LA;->sB:B
+
+ iput-char v0, p0, LA;->sC:C
+
+ iput-short v0, p0, LA;->sS:S
+
+ return-void
+.end method
+
+.method public invokes()V
+ .registers 5
+
+ invoke-virtual {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+ invoke-super {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+ invoke-direct {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+ invoke-static {v0, v1, v2, v3, p0}, LA;->invokes()V
+
+ invoke-interface {v0, v1, v2, v3, p0}, LA;->invokes()V
+.end method
diff --git a/testdata/const-method-handle-min.smali b/testdata/const-method-handle-min.smali
new file mode 100644
index 0000000..0bf157f
--- /dev/null
+++ b/testdata/const-method-handle-min.smali
@@ -0,0 +1,14 @@
+# Tests const-method-handle added in DEX version 39.
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble const-method-handle.smali --api 28
+
+.class public LConstMethodHandle;
+.super Ljava/lang/Object;
+
+.method public (I)V
+ .registers 2
+ const-method-handle v1, invoke-static@Ljava/lang/String;->copyValueOf([C)Ljava/lang/String;
+ const-method-handle v0, invoke-instance@Ljava/lang/String;->charAt(I)C
+ return-void
+.end method
diff --git a/testdata/const-method-type-min.smali b/testdata/const-method-type-min.smali
new file mode 100644
index 0000000..8a0f632
--- /dev/null
+++ b/testdata/const-method-type-min.smali
@@ -0,0 +1,16 @@
+# Tests const-method-type added in DEX version 39.
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble const-method-type.smali --api 28
+
+.class public LConstMethodTypeTest;
+.super Ljava/lang/Object;
+
+.method public test(I)V
+ .registers 4
+ const-method-type v0, ()I
+ const-method-type v1, (C)V
+ const-method-type v2, (I)V
+ const-method-type v3, (I)I
+ return-void
+.end method
diff --git a/testdata/invoke-custom-min.smali b/testdata/invoke-custom-min.smali
new file mode 100644
index 0000000..64bccbc
--- /dev/null
+++ b/testdata/invoke-custom-min.smali
@@ -0,0 +1,39 @@
+# Tests invoke-custom added in DEX version 38.
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble invoke-custom-min.smali --api 28
+
+.class public LFoo;
+.super Ljava/lang/Object;
+
+.method public la1(Ljava/util/ArrayList;)V
+ .registers 5
+ .annotation system Ldalvik/annotation/Signature;
+ value = {
+ "(",
+ "Ljava/util/ArrayList",
+ "<",
+ "Ljava/lang/String;",
+ ">;)V"
+ }
+ .end annotation
+
+ .prologue
+ .line 42
+ invoke-virtual {p1}, Ljava/util/ArrayList;->stream()Ljava/util/stream/Stream;
+
+ move-result-object v0
+
+ invoke-custom {}, call_site_1("bar", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z, invoke-static@LFoo;->lambda$la1$1(I)Z, (I)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+
+ move-result-object v1
+
+ invoke-custom {}, call_site_2("test", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z, invoke-static@LFoo;->lambda$la1$1(Ljava/lang/String;)Z, (Ljava/lang/String;)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+
+ move-result-object v2
+
+ invoke-interface {v0, v1, v2}, Ljava/util/stream/Stream;->filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
+
+ .line 50
+ return-void
+.end method
diff --git a/testdata/invoke-polymorphic.smali b/testdata/invoke-polymorphic.smali
new file mode 100644
index 0000000..1ad7246
--- /dev/null
+++ b/testdata/invoke-polymorphic.smali
@@ -0,0 +1,70 @@
+# Tests invoke-polymorphic added in DEX version 38.
+# Disassembled from dexdump test files.
+# Repo: https://android.googlesource.com/platform/art/
+# File: test/dexdump/invoke-polymorphic.dex
+
+# Compile using smali: https://github.com/JesusFreke/smali
+# java -jar smali.jar assemble invoke-polymorphic.smali --api 28
+
+.class public LMain;
+.super Ljava/lang/Object;
+.source "Main.java"
+
+
+# direct methods
+.method public constructor <init>()V
+ .registers 1
+
+ .prologue
+ .line 9
+ invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+
+ return-void
+.end method
+
+.method public static main([Ljava/lang/String;)V
+ .registers 10
+ .param p0, "args" # [Ljava/lang/String;
+ .annotation system Ldalvik/annotation/Throws;
+ value = {
+ Ljava/lang/Throwable;
+ }
+ .end annotation
+
+ .prologue
+ const-wide v2, 0x400199999999999aL # 2.2
+
+ const/4 v4, 0x1
+
+ .line 31
+ const/4 v0, 0x0
+
+ .line 32
+ .local v0, "handle":Ljava/lang/invoke/MethodHandle;
+ const/4 v5, 0x0
+
+ .line 33
+ .local v5, "o":Ljava/lang/Object;
+ const-string/jumbo v1, "a"
+
+ move v6, v4
+
+ invoke-polymorphic/range {v0 .. v6}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;DILjava/lang/Object;I)Ljava/lang/String;
+
+ move-result-object v7
+
+ .line 34
+ .local v7, "s":Ljava/lang/String;
+ invoke-polymorphic {v0, v2, v3, v4}, Ljava/lang/invoke/MethodHandle;->invokeExact([Ljava/lang/Object;)Ljava/lang/Object;, (DI)I
+
+ move-result v8
+
+ .line 35
+ .local v8, "x":I
+ const-string/jumbo v1, "a"
+
+ invoke-polymorphic {v0, v1, v2, v3, v4}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;DI)V
+
+ .line 56
+ return-void
+.end method
diff --git a/type_dex.h b/type_dex.h
index 432a031..ee61ecd 100644
--- a/type_dex.h
+++ b/type_dex.h
@@ -12,11 +12,11 @@ namespace dex {
// Contains types that models DEX executable format data structures.
// See https://source.android.com/devices/tech/dalvik/dex-format
-// The supported versions are 035 and 037.
+// The supported versions are 035, 037, 038, and 039.
enum class FormatId : uint8_t {
b, // 22b.
- c, // 21c, 22c, 31c, 35c, 3rc.
+ c, // 21c, 22c, 31c, 35c, 3rc, 45cc, 4rcc.
h, // 21h.
i, // 31i.
l, // 51l.
@@ -110,6 +110,12 @@ constexpr Instruction kByteCode[] = {
{0xD0, 2, FormatId::s, 8},
{0xD8, 2, FormatId::b, 11},
// {0xE3, 1, FormatId::x, 29}, unused
+ {0xFA, 4, FormatId::c},
+ {0xFB, 4, FormatId::c},
+ {0xFC, 3, FormatId::c},
+ {0xFD, 3, FormatId::c},
+ {0xFE, 2, FormatId::c},
+ {0xFF, 2, FormatId::c},
};
// Supported by MSVC, g++, and clang++. Ensures no gaps in packing.
@@ -185,6 +191,36 @@ struct ClassDefItem {
uint32_t static_values_off;
};
+// call_site_id_item: Call site identifiers list.
+struct CallSiteIdItem {
+ uint32_t call_site_off;
+};
+
+// method_handle_type: Determines the behavior of the MethodHandleItem.
+enum class MethodHandleType : uint16_t {
+ // FieldId
+ kStaticPut = 0x00,
+ kStaticGet = 0x01,
+ kInstancePut = 0x02,
+ kInstanceGet = 0x03,
+ // MethodId
+ kInvokeStatic = 0x04,
+ kInvokeInstance = 0x05,
+ kInvokeConstructor = 0x06,
+ kInvokeDirect = 0x07,
+ kInvokeInterface = 0x08,
+ // Sentinel. If new types are added put them before this and increment.
+ kMaxMethodHandleType = 0x09
+};
+
+// method_handle_item: Method handles referred within the Dex file.
+struct MethodHandleItem {
+ uint16_t method_handle_type;
+ uint16_t unused_1;
+ uint16_t field_or_method_id;
+ uint16_t unused_2;
+};
+
// code_item: Header of a code item.
struct CodeItem {
uint16_t registers_size;
@@ -196,7 +232,31 @@ struct CodeItem {
// Variable length data follow for complete code item.
};
-constexpr uint32_t kMaxItemListSize = 18;
+// Number of valid type codes for map_item elements in map_list.
+// See: https://source.android.com/devices/tech/dalvik/dex-format#type-codes
+constexpr uint32_t kMaxItemListSize = 21;
+
+constexpr uint16_t kTypeHeaderItem = 0x0000;
+constexpr uint16_t kTypeStringIdItem = 0x0001;
+constexpr uint16_t kTypeTypeIdItem = 0x0002;
+constexpr uint16_t kTypeProtoIdItem = 0x0003;
+constexpr uint16_t kTypeFieldIdItem = 0x0004;
+constexpr uint16_t kTypeMethodIdItem = 0x0005;
+constexpr uint16_t kTypeClassDefItem = 0x0006;
+constexpr uint16_t kTypeCallSiteIdItem = 0x0007;
+constexpr uint16_t kTypeMethodHandleItem = 0x0008;
+constexpr uint16_t kTypeMapList = 0x1000;
+constexpr uint16_t kTypeTypeList = 0x1001;
+constexpr uint16_t kTypeAnnotationSetRefList = 0x1002;
+constexpr uint16_t kTypeAnnotationSetItem = 0x1003;
+constexpr uint16_t kTypeClassDataItem = 0x2000;
+constexpr uint16_t kTypeCodeItem = 0x2001;
+constexpr uint16_t kTypeStringDataItem = 0x2002;
+constexpr uint16_t kTypeDebugInfoItem = 0x2003;
+constexpr uint16_t kTypeAnnotationItem = 0x2004;
+constexpr uint16_t kTypeEncodedArrayItem = 0x2005;
+constexpr uint16_t kTypeAnnotationsDirectoryItem = 0x2006;
+constexpr uint16_t kTypeHiddenApiClassDataItem = 0xF000;
// map_item
struct MapItem {
@@ -264,25 +324,6 @@ struct TryItem {
uint16_t handler_off;
};
-constexpr uint16_t kTypeHeaderItem = 0x0000;
-constexpr uint16_t kTypeStringIdItem = 0x0001;
-constexpr uint16_t kTypeTypeIdItem = 0x0002;
-constexpr uint16_t kTypeProtoIdItem = 0x0003;
-constexpr uint16_t kTypeFieldIdItem = 0x0004;
-constexpr uint16_t kTypeMethodIdItem = 0x0005;
-constexpr uint16_t kTypeClassDefItem = 0x0006;
-constexpr uint16_t kTypeMapList = 0x1000;
-constexpr uint16_t kTypeTypeList = 0x1001;
-constexpr uint16_t kTypeAnnotationSetRefList = 0x1002;
-constexpr uint16_t kTypeAnnotationSetItem = 0x1003;
-constexpr uint16_t kTypeClassDataItem = 0x2000;
-constexpr uint16_t kTypeCodeItem = 0x2001;
-constexpr uint16_t kTypeStringDataItem = 0x2002;
-constexpr uint16_t kTypeDebugInfoItem = 0x2003;
-constexpr uint16_t kTypeAnnotationItem = 0x2004;
-constexpr uint16_t kTypeEncodedArrayItem = 0x2005;
-constexpr uint16_t kTypeAnnotationsDirectoryItem = 0x2006;
-
#pragma pack(pop)
} // namespace dex
diff --git a/zucchini_commands.cc b/zucchini_commands.cc
index 93929bd..0699cbe 100644
--- a/zucchini_commands.cc
+++ b/zucchini_commands.cc
@@ -51,6 +51,11 @@ zucchini::status::Code MainApply(MainParams params) {
params.command_line.HasSwitch(kSwitchKeep));
}
+zucchini::status::Code MainVerify(MainParams params) {
+ CHECK_EQ(1U, params.file_paths.size());
+ return zucchini::VerifyPatch(params.file_paths[0]);
+}
+
zucchini::status::Code MainRead(MainParams params) {
CHECK_EQ(1U, params.file_paths.size());
base::File input_file(params.file_paths[0],
diff --git a/zucchini_commands.h b/zucchini_commands.h
index cef18dc..91c2ef8 100644
--- a/zucchini_commands.h
+++ b/zucchini_commands.h
@@ -36,6 +36,9 @@ zucchini::status::Code MainGen(MainParams params);
// Command Function: Patch application.
zucchini::status::Code MainApply(MainParams params);
+// Command Function: Verify patch format and compatibility.
+zucchini::status::Code MainVerify(MainParams params);
+
// Command Function: Read and dump references from an executable.
zucchini::status::Code MainRead(MainParams params);
diff --git a/zucchini_gen_unittest.cc b/zucchini_gen_unittest.cc
index 3a6d2cb..bc37bf6 100644
--- a/zucchini_gen_unittest.cc
+++ b/zucchini_gen_unittest.cc
@@ -6,6 +6,7 @@
#include <stdint.h>
+#include <deque>
#include <utility>
#include <vector>
@@ -30,8 +31,8 @@ constexpr double kDummySim = 0.0;
std::vector<int32_t> GenerateReferencesDeltaTest(
std::vector<Reference>&& old_references,
std::vector<Reference>&& new_references,
- std::vector<offset_t>&& exp_old_targets,
- std::vector<offset_t>&& exp_projected_old_targets,
+ std::deque<offset_t>&& exp_old_targets,
+ std::deque<offset_t>&& exp_projected_old_targets,
EquivalenceMap&& equivalence_map) {
// OffsetMapper needs image sizes for forward-projection overflow check. These
// are tested elsewhere, so just use arbitrary large value.
diff --git a/zucchini_integration.cc b/zucchini_integration.cc
index ff7e792..bf28b3c 100644
--- a/zucchini_integration.cc
+++ b/zucchini_integration.cc
@@ -146,6 +146,22 @@ status::Code ApplyCommon(base::File old_file,
return status::kStatusSuccess;
}
+status::Code VerifyPatchCommon(base::File patch_file,
+ base::FilePath patch_name) {
+ MappedFileReader mapped_patch(std::move(patch_file));
+ if (mapped_patch.HasError()) {
+ LOG(ERROR) << "Error with file " << patch_name.value() << ": "
+ << mapped_patch.error();
+ return status::kStatusFileReadError;
+ }
+ auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region());
+ if (!patch_reader.has_value()) {
+ LOG(ERROR) << "Error reading patch header.";
+ return status::kStatusPatchReadError;
+ }
+ return status::kStatusSuccess;
+}
+
} // namespace
status::Code Generate(base::File old_file,
@@ -206,4 +222,15 @@ status::Code Apply(const base::FilePath& old_path,
std::move(new_file), file_names, force_keep);
}
+status::Code VerifyPatch(base::File patch_file) {
+ return VerifyPatchCommon(std::move(patch_file), base::FilePath());
+}
+
+status::Code VerifyPatch(const base::FilePath& patch_path) {
+ using base::File;
+ File patch_file(patch_path, File::FLAG_OPEN | File::FLAG_READ |
+ base::File::FLAG_SHARE_DELETE);
+ return VerifyPatchCommon(std::move(patch_file), patch_path);
+}
+
} // namespace zucchini
diff --git a/zucchini_integration.h b/zucchini_integration.h
index 2ae6091..2b6287b 100644
--- a/zucchini_integration.h
+++ b/zucchini_integration.h
@@ -63,6 +63,16 @@ status::Code Apply(const base::FilePath& old_path,
const base::FilePath& new_path,
bool force_keep = false);
+// Verifies the patch format in |patch_file| and returns
+// Code::kStatusPatchReadError if the patch is malformed or version is
+// unsupported. Since this uses memory mapped files, crashes are expected in
+// case of I/O errors.
+status::Code VerifyPatch(base::File patch_file);
+
+// Alternative VerifyPatch() interface that takes base::FilePath as arguments.
+// Performs proper cleanup in Windows and UNIX if failure occurs.
+status::Code VerifyPatch(const base::FilePath& patch_path);
+
} // namespace zucchini
#endif // COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_