From 26518ffbdf042c4c35617be2446dac49590a89c2 Mon Sep 17 00:00:00 2001 From: ckitagawa Date: Fri, 3 Sep 2021 15:48:28 +0000 Subject: [Zucchini] DEX Version 38 Support DEX Version 38 added: * CallSiteId & CallSite items * MethodHandle items * invoke-polymorphic containing meth@BBBB and proto@HHHH references * invoke-custom containing a call_site@BBBB reference This CL: * Adds CallSiteIdToCallSite * Adds MethodHandleTo{MethodId, FieldId} * Adds CodeToProtoId16 for invoke-polymorphic * Adds CodeToCallSiteId16 and WriteCallSiteId16 for invoke-custom * Updates CodeToMethodId16 for invoke-polymorphic Fuzzed about 1 million iterations locally and uploaded new samples to the clusterfuzz bucket. 97% coverage. Manually tested on hand-written dex files using smali as well as the dexdump test corpus. Bug: 1231885 Change-Id: Icd885be2cfd433d0befe689d16c4a1e99573ca6c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3060745 Reviewed-by: Samuel Huang Reviewed-by: Etienne Pierre-Doray Commit-Queue: Calder Kitagawa Cr-Commit-Position: refs/heads/main@{#918119} NOKEYCHECK=True GitOrigin-RevId: 9cc600ef0b60ff1ec76683a2bfb98a6bdbb05d1e --- disassembler_dex.cc | 244 +++++++++++-- disassembler_dex.h | 37 +- testdata/all.smali | 628 +++++++++++++++++++++++++++++++++ testdata/const-method-handle-min.smali | 14 + testdata/invoke-custom-min.smali | 39 ++ testdata/invoke-polymorphic.smali | 70 ++++ type_dex.h | 83 +++-- 7 files changed, 1052 insertions(+), 63 deletions(-) create mode 100644 testdata/all.smali create mode 100644 testdata/const-method-handle-min.smali create mode 100644 testdata/invoke-custom-min.smali create mode 100644 testdata/invoke-polymorphic.smali diff --git a/disassembler_dex.cc b/disassembler_dex.cc index 5b25c50..74c5e69 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(map_item.offset)), num_items_(base::checked_cast(map_item.size)), item_size_(base::checked_cast(item_size)), rel_location_(base::checked_cast(rel_location)), + mapper_input_delta_( + mapper_wants_item ? 0 : base::checked_cast(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(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(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(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( + 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,9 +844,9 @@ 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, and 38. + // TODO(ckitagawa): Handle version 39. + if (dex_version != 35 && dex_version != 37 && dex_version != 38) return false; if (header->file_size > image.size() || @@ -864,18 +929,27 @@ std::vector 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 +957,9 @@ std::vector DisassemblerDex::MakeReferenceGroups() const { PoolTag(kMethodId)}, &DisassemblerDex::MakeReadAnnotationsDirectoryToParameterMethodId32, &DisassemblerDex::MakeWriteMethodId32}, + {{2, TypeTag(kCodeToCallSiteId), PoolTag(kCallSiteId)}, + &DisassemblerDex::MakeReadCodeToCallSiteId16, + &DisassemblerDex::MakeWriteCallSiteId16}, {{4, TypeTag(kProtoIdToParametersTypeList), PoolTag(kTypeList)}, &DisassemblerDex::MakeReadProtoIdToParametersTypeList, &DisassemblerDex::MakeWriteAbs32}, @@ -936,6 +1013,9 @@ std::vector DisassemblerDex::MakeReferenceGroups() const { PoolTag(kAnnotationsDirectory)}, &DisassemblerDex::MakeReadClassDefToAnnotationDirectory, &DisassemblerDex::MakeWriteAbs32}, + {{4, TypeTag(kCallSiteIdToCallSite), PoolTag(kCallSite)}, + &DisassemblerDex::MakeReadCallSiteIdToCallSite32, + &DisassemblerDex::MakeWriteAbs32}, }; } @@ -1125,6 +1205,46 @@ DisassemblerDex::MakeReadClassDefToStaticValuesEncodedArray(offset_t lo, offsetof(dex::ClassDefItem, static_values_off), std::move(mapper)); } +std::unique_ptr +DisassemblerDex::MakeReadCallSiteIdToCallSite32(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadTargetOffset32, image_); + return std::make_unique( + lo, hi, call_site_map_item_, sizeof(dex::CallSiteIdItem), + offsetof(dex::CallSiteIdItem, call_site_off), std::move(mapper)); +} + +std::unique_ptr +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( + 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 +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( + 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 DisassemblerDex::MakeReadTypeListToTypeId16( offset_t lo, offset_t hi) { @@ -1233,6 +1353,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 DisassemblerDex::MakeReadCodeToStringId16( offset_t lo, offset_t hi) { @@ -1295,6 +1419,47 @@ std::unique_ptr DisassemblerDex::MakeReadCodeToTypeId16( image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); } +std::unique_ptr 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 && + (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; + } + return kInvalidOffset; + }); + auto mapper = base::BindRepeating(ReadTargetIndex, image_, + proto_map_item_, sizeof(dex::ProtoIdItem)); + return std::make_unique( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + +std::unique_ptr 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, image_, + call_site_map_item_, sizeof(dex::CallSiteIdItem)); + return std::make_unique( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + std::unique_ptr DisassemblerDex::MakeReadCodeToFieldId16( offset_t lo, offset_t hi) { @@ -1321,7 +1486,9 @@ std::unique_ptr 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 +1658,14 @@ std::unique_ptr DisassemblerDex::MakeWriteMethodId32( return std::make_unique(image, std::move(writer)); } +std::unique_ptr DisassemblerDex::MakeWriteCallSiteId16( + MutableBufferView image) { + auto writer = + base::BindRepeating(WriteTargetIndex, call_site_map_item_, + sizeof(dex::CallSiteIdItem)); + return std::make_unique(image, std::move(writer)); +} + std::unique_ptr DisassemblerDex::MakeWriteRelCode8( MutableBufferView image) { auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) { @@ -1587,15 +1762,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 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 +1772,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..e75d13e 100644 --- a/disassembler_dex.h +++ b/disassembler_dex.h @@ -32,8 +32,8 @@ class DisassemblerDex : public Disassembler { kFieldId, kMethodId, // kClassDef, // Unused - // kCallSiteId, // Unused - // kMethodHandle, // Unused + kCallSiteId, + // kMethodHandle, // Unused kTypeList, kAnnotationSetRefList, kAnnotionSet, @@ -43,7 +43,7 @@ class DisassemblerDex : public Disassembler { kAnnotation, kEncodedArray, kAnnotationsDirectory, - // kCallSite, // Unused + kCallSite, kNumPools }; @@ -69,15 +69,20 @@ class DisassemblerDex : public Disassembler { kTypeListToTypeId, kCodeToTypeId, - kMethodIdToProtoId, // kProtoId + kCodeToProtoId, // kProtoId + kMethodIdToProtoId, kCodeToFieldId, // kFieldId + kMethodHandleToFieldId, kAnnotationsDirectoryToFieldId, kCodeToMethodId, // kMethodId + kMethodHandleToMethodId, kAnnotationsDirectoryToMethodId, kAnnotationsDirectoryToParameterMethodId, + kCodeToCallSiteId, // kCallSiteId + kProtoIdToParametersTypeList, // kTypeList kClassDefToInterfacesTypeList, @@ -102,10 +107,7 @@ class DisassemblerDex : public Disassembler { kClassDefToAnnotationDirectory, // kAnnotationsDirectory - // Intentionally ignored references (never appeared in test corpus). - // kMethodHandleToFieldId, - // kMethodHandleToMethodId, - // kCallSiteIdToCallSite, + kCallSiteIdToCallSite, // kCallSite kNumTypes }; @@ -172,6 +174,13 @@ class DisassemblerDex : public Disassembler { std::unique_ptr MakeReadClassDefToStaticValuesEncodedArray( offset_t lo, offset_t hi); + std::unique_ptr MakeReadCallSiteIdToCallSite32(offset_t lo, + offset_t hi); + std::unique_ptr MakeReadMethodHandleToFieldId16(offset_t lo, + offset_t hi); + std::unique_ptr MakeReadMethodHandleToMethodId16( + offset_t lo, + offset_t hi); std::unique_ptr MakeReadTypeListToTypeId16(offset_t lo, offset_t hi); std::unique_ptr MakeReadAnnotationSetToAnnotation( @@ -203,10 +212,14 @@ class DisassemblerDex : public Disassembler { offset_t hi); std::unique_ptr MakeReadCodeToTypeId16(offset_t lo, offset_t hi); + std::unique_ptr MakeReadCodeToProtoId16(offset_t lo, + offset_t hi); std::unique_ptr MakeReadCodeToFieldId16(offset_t lo, offset_t hi); std::unique_ptr MakeReadCodeToMethodId16(offset_t lo, offset_t hi); + std::unique_ptr MakeReadCodeToCallSiteId16(offset_t lo, + offset_t hi); std::unique_ptr MakeReadCodeToRelCode8(offset_t lo, offset_t hi); std::unique_ptr MakeReadCodeToRelCode16(offset_t lo, @@ -225,6 +238,8 @@ class DisassemblerDex : public Disassembler { std::unique_ptr MakeWriteFieldId32(MutableBufferView image); std::unique_ptr MakeWriteMethodId16(MutableBufferView image); std::unique_ptr MakeWriteMethodId32(MutableBufferView image); + std::unique_ptr MakeWriteCallSiteId16( + MutableBufferView image); std::unique_ptr MakeWriteRelCode8(MutableBufferView image); std::unique_ptr MakeWriteRelCode16(MutableBufferView image); std::unique_ptr MakeWriteRelCode32(MutableBufferView image); @@ -248,12 +263,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/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 ()V + .registers 1 + + invoke-direct {p0}, Ljava/lang/Object;->()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/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 ()V + .registers 1 + + .prologue + .line 9 + invoke-direct {p0}, Ljava/lang/Object;->()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..e0ccc28 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, and 038. 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,10 @@ 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}, }; // Supported by MSVC, g++, and clang++. Ensures no gaps in packing. @@ -185,6 +189,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 +230,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 +322,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 -- cgit v1.2.3 From 737d35c6383995ae4d474cd975aebf92aaf73393 Mon Sep 17 00:00:00 2001 From: Etienne Pierre-doray Date: Fri, 3 Sep 2021 20:52:11 +0000 Subject: [zucchini]: Simplify DisassemblerElfArm read/write functions. Use template read/write functions instead of repeated versions. Change-Id: Ie87d307ebd7b297fe802216fe07aa820d7b1fa4d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3082253 Reviewed-by: Samuel Huang Commit-Queue: Etienne Pierre-Doray Cr-Commit-Position: refs/heads/main@{#918237} NOKEYCHECK=True GitOrigin-RevId: d64aec31e8bb5e1acb9a2da1e6e92fbd5e59d5f6 --- disassembler_elf.cc | 197 +++++++++++++--------------------------------------- disassembler_elf.h | 44 ++---------- 2 files changed, 57 insertions(+), 184 deletions(-) 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 DisassemblerElfArm::MakeWriteAbs32( image, AbsoluteAddress(Traits::kBitness, 0), this->translator_); } +template +template +std::unique_ptr DisassemblerElfArm::MakeReadRel32( + offset_t lower, + offset_t upper) { + return std::make_unique>( + this->translator_, this->image_, + this->rel32_locations_table_[ADDR_TRAITS::addr_type], lower, upper); +} + +template +template +std::unique_ptr DisassemblerElfArm::MakeWriteRel32( + MutableBufferView image) { + return std::make_unique>(this->translator_, + image); +} + /******** DisassemblerElfAArch32 ********/ DisassemblerElfAArch32::DisassemblerElfAArch32() = default; @@ -616,24 +634,34 @@ std::vector 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 DisassemblerElfAArch32::MakeReadRel32A24( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, image_, - rel32_locations_table_[AArch32Rel32Translator::ADDR_A24], lower, upper); -} - -std::unique_ptr DisassemblerElfAArch32::MakeWriteRel32A24( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - -std::unique_ptr DisassemblerElfAArch32::MakeReadRel32T8( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, image_, - rel32_locations_table_[AArch32Rel32Translator::ADDR_T8], lower, upper); -} - -std::unique_ptr DisassemblerElfAArch32::MakeWriteRel32T8( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - -std::unique_ptr DisassemblerElfAArch32::MakeReadRel32T11( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, image_, - rel32_locations_table_[AArch32Rel32Translator::ADDR_T11], lower, upper); -} - -std::unique_ptr DisassemblerElfAArch32::MakeWriteRel32T11( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - -std::unique_ptr DisassemblerElfAArch32::MakeReadRel32T20( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, image_, - rel32_locations_table_[AArch32Rel32Translator::ADDR_T20], lower, upper); -} - -std::unique_ptr DisassemblerElfAArch32::MakeWriteRel32T20( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - -std::unique_ptr DisassemblerElfAArch32::MakeReadRel32T24( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, image_, - rel32_locations_table_[AArch32Rel32Translator::ADDR_T24], lower, upper); -} - -std::unique_ptr DisassemblerElfAArch32::MakeWriteRel32T24( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - /******** DisassemblerElfAArch64 ********/ DisassemblerElfAArch64::DisassemblerElfAArch64() = default; @@ -774,16 +722,22 @@ std::vector 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(image_, translator_); } -std::unique_ptr DisassemblerElfAArch64::MakeReadRel32Immd14( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, this->image_, - rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD14], lower, - upper); -} - -std::unique_ptr DisassemblerElfAArch64::MakeWriteRel32Immd14( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - -std::unique_ptr DisassemblerElfAArch64::MakeReadRel32Immd19( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, this->image_, - rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD19], lower, - upper); -} - -std::unique_ptr DisassemblerElfAArch64::MakeWriteRel32Immd19( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - -std::unique_ptr DisassemblerElfAArch64::MakeReadRel32Immd26( - offset_t lower, - offset_t upper) { - return std::make_unique< - Rel32ReaderArm>( - translator_, this->image_, - rel32_locations_table_[AArch64Rel32Translator::ADDR_IMMD26], lower, - upper); -} - -std::unique_ptr DisassemblerElfAArch64::MakeWriteRel32Immd26( - MutableBufferView image) { - return std::make_unique< - Rel32WriterArm>(translator_, - image); -} - // Explicit instantiation for supported classes. template class DisassemblerElfArm; template class DisassemblerElfArm; diff --git a/disassembler_elf.h b/disassembler_elf.h index 0bd11a6..b29e89f 100644 --- a/disassembler_elf.h +++ b/disassembler_elf.h @@ -296,6 +296,13 @@ class DisassemblerElfArm : public DisassemblerElf { std::unique_ptr MakeReadAbs32(offset_t lo, offset_t hi); std::unique_ptr MakeWriteAbs32(MutableBufferView image); + // Specialized Read/Write functions for different rel32 address types. + template + std::unique_ptr MakeReadRel32(offset_t lower, + offset_t upper); + template + std::unique_ptr MakeWriteRel32(MutableBufferView image); + protected: // Sorted file offsets of rel32 locations for each rel32 address type. std::deque @@ -322,27 +329,6 @@ class DisassemblerElfAArch32 : public DisassemblerElfArm { // 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 MakeReadRel32A24(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32A24(MutableBufferView image); - - std::unique_ptr MakeReadRel32T8(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32T8(MutableBufferView image); - - std::unique_ptr MakeReadRel32T11(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32T11(MutableBufferView image); - - std::unique_ptr MakeReadRel32T20(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32T20(MutableBufferView image); - - std::unique_ptr MakeReadRel32T24(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32T24(MutableBufferView image); }; // Disassembler for ELF with AArch64 (AKA ARM64). @@ -360,22 +346,6 @@ class DisassemblerElfAArch64 : public DisassemblerElfArm { // DisassemblerElfArm: std::unique_ptr MakeRel32Finder( const typename Traits::Elf_Shdr& section) override; - - // Specialized Read/Write functions for different rel32 address types. - std::unique_ptr MakeReadRel32Immd14(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32Immd14( - MutableBufferView image); - - std::unique_ptr MakeReadRel32Immd19(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32Immd19( - MutableBufferView image); - - std::unique_ptr MakeReadRel32Immd26(offset_t lower, - offset_t upper); - std::unique_ptr MakeWriteRel32Immd26( - MutableBufferView image); }; } // namespace zucchini -- cgit v1.2.3 From 1269b5c6a8a1275b7dfdb7ec349f1990bc7c5d77 Mon Sep 17 00:00:00 2001 From: ckitagawa Date: Tue, 7 Sep 2021 21:12:21 +0000 Subject: [Zucchini] DEX Version 39 Support DEX Version 39 added: * const-method-handle containing a method_handle@BBBB reference * const-method-type containing a proto@BBBB reference This CL * Updates CodeToProtoId for const-method-type * Adds CodeToMethodHandle and WriteMethodHandle Fuzzed about 500k iterations locally and uploaded new samples to the clusterfuzz bucket. 97% coverage. Manually tested on hand-written dex files using smali as well as the dexdump test corpus. Bug: 1231885 Change-Id: Id8ab09ac8d3331902c5e6f92ac39ebd26d36e79b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3060660 Commit-Queue: Calder Kitagawa Reviewed-by: Samuel Huang Reviewed-by: Etienne Pierre-Doray Cr-Commit-Position: refs/heads/main@{#918948} NOKEYCHECK=True GitOrigin-RevId: d08c50abf7b49f3a5b97a03d5bb79bce9fdb7fad --- disassembler_dex.cc | 54 ++++++++++++++++++++++++++++++------ disassembler_dex.h | 8 +++++- testdata/const-method-type-min.smali | 16 +++++++++++ type_dex.h | 4 ++- 4 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 testdata/const-method-type-min.smali diff --git a/disassembler_dex.cc b/disassembler_dex.cc index 74c5e69..8ea0314 100644 --- a/disassembler_dex.cc +++ b/disassembler_dex.cc @@ -844,10 +844,11 @@ bool ReadDexHeader(ConstBufferView image, ReadDexHeaderResults* opt_results) { dex_version = dex_version * 10 + (header->magic[i] - '0'); } - // Only support DEX versions 35, 37, and 38. - // TODO(ckitagawa): Handle version 39. - if (dex_version != 35 && dex_version != 37 && dex_version != 38) + // 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) || @@ -960,6 +961,9 @@ std::vector DisassemblerDex::MakeReferenceGroups() const { {{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}, @@ -1424,12 +1428,17 @@ std::unique_ptr DisassemblerDex::MakeReadCodeToProtoId16( offset_t hi) { auto filter = base::BindRepeating( [](const InstructionParser::Value& value) -> offset_t { - if (value.instr->format == dex::FormatId::c && - (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->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; }); @@ -1460,6 +1469,25 @@ std::unique_ptr DisassemblerDex::MakeReadCodeToCallSiteId16( image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); } +std::unique_ptr 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, image_, + method_handle_map_item_, + sizeof(dex::MethodHandleItem)); + return std::make_unique( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + std::unique_ptr DisassemblerDex::MakeReadCodeToFieldId16( offset_t lo, offset_t hi) { @@ -1666,6 +1694,14 @@ std::unique_ptr DisassemblerDex::MakeWriteCallSiteId16( return std::make_unique(image, std::move(writer)); } +std::unique_ptr DisassemblerDex::MakeWriteMethodHandle16( + MutableBufferView image) { + auto writer = + base::BindRepeating(WriteTargetIndex, method_handle_map_item_, + sizeof(dex::MethodHandleItem)); + return std::make_unique(image, std::move(writer)); +} + std::unique_ptr DisassemblerDex::MakeWriteRelCode8( MutableBufferView image) { auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) { diff --git a/disassembler_dex.h b/disassembler_dex.h index e75d13e..8e739d0 100644 --- a/disassembler_dex.h +++ b/disassembler_dex.h @@ -33,7 +33,7 @@ class DisassemblerDex : public Disassembler { kMethodId, // kClassDef, // Unused kCallSiteId, - // kMethodHandle, // Unused + kMethodHandle, kTypeList, kAnnotationSetRefList, kAnnotionSet, @@ -83,6 +83,8 @@ class DisassemblerDex : public Disassembler { kCodeToCallSiteId, // kCallSiteId + kCodeToMethodHandle, // kMethodHandle + kProtoIdToParametersTypeList, // kTypeList kClassDefToInterfacesTypeList, @@ -220,6 +222,8 @@ class DisassemblerDex : public Disassembler { offset_t hi); std::unique_ptr MakeReadCodeToCallSiteId16(offset_t lo, offset_t hi); + std::unique_ptr MakeReadCodeToMethodHandle16(offset_t lo, + offset_t hi); std::unique_ptr MakeReadCodeToRelCode8(offset_t lo, offset_t hi); std::unique_ptr MakeReadCodeToRelCode16(offset_t lo, @@ -240,6 +244,8 @@ class DisassemblerDex : public Disassembler { std::unique_ptr MakeWriteMethodId32(MutableBufferView image); std::unique_ptr MakeWriteCallSiteId16( MutableBufferView image); + std::unique_ptr MakeWriteMethodHandle16( + MutableBufferView image); std::unique_ptr MakeWriteRelCode8(MutableBufferView image); std::unique_ptr MakeWriteRelCode16(MutableBufferView image); std::unique_ptr MakeWriteRelCode32(MutableBufferView image); 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/type_dex.h b/type_dex.h index e0ccc28..ee61ecd 100644 --- a/type_dex.h +++ b/type_dex.h @@ -12,7 +12,7 @@ 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, 037, and 038. +// The supported versions are 035, 037, 038, and 039. enum class FormatId : uint8_t { b, // 22b. @@ -114,6 +114,8 @@ constexpr Instruction kByteCode[] = { {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. -- cgit v1.2.3 From 9ff43f558d334f100a92bf93d764f32293f9c5aa Mon Sep 17 00:00:00 2001 From: Peter Kasting Date: Wed, 8 Sep 2021 17:53:30 +0000 Subject: Fix some instances of -Wshadow. Bug: 794619 Change-Id: Ic842b420403fe932525fb5878d2e1d4e81577c32 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3141022 Commit-Queue: Peter Kasting Auto-Submit: Peter Kasting Reviewed-by: Colin Blundell Cr-Commit-Position: refs/heads/main@{#919333} NOKEYCHECK=True GitOrigin-RevId: bef7bc52a0e081b382fd660a2ea3adc49500cac3 --- disassembler_elf_unittest.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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(&header), - sizeof(header)); + image = {reinterpret_cast(&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(&header), - sizeof(header)); + image = {reinterpret_cast(&header), sizeof(header)}; EXPECT_FALSE(DisassemblerElfX86::QuickDetect(image)); EXPECT_TRUE(DisassemblerElfX64::QuickDetect(image)); } -- cgit v1.2.3 From aff408603b3db5b7974c522db2ad8c5ce2a0f3c1 Mon Sep 17 00:00:00 2001 From: Etienne Pierre-doray Date: Tue, 14 Sep 2021 17:31:51 +0000 Subject: [zucchini]: Convert TargetPool to deque. shrink_to_fit with vector tends to cause high memory peak. Changing deque is a simple change that reduces memory peak at the cost of loss of guarantee (contiguous storage). Similar to https://chromium-review.googlesource.com/c/chromium/src/+/2830864 which dramatically reduced crach rate https://crash.corp.google.com/browse?q=product_name%3D%27Chrome%27+AND+EXISTS+%28SELECT+1+FROM+UNNEST%28CrashedStackTrace.StackFrame%29+WHERE+FunctionName%3D%27installer%3A%3AArchivePatchHelper%3A%3AZucchiniEnsemblePatch%27%29+AND+expanded_custom_data.ChromeCrashProto.magic_signature_1.name%3D%27%5BOut+of+Memory%5D+zucchini%3A%3ADisassemblerWin32%3Czucchini%3A%3AWin32X64Traits%3E%3A%3AParseAndStoreRel32%27 An alternative is to look ahead to determine vector size. The is hard to do with SortAndUniquify, which performs in-place modifications. Bug: 1247633 Change-Id: I624c360ee1f2bf18bd584d1aafdde0f0c2ffb61e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3149810 Commit-Queue: Etienne Pierre-Doray Reviewed-by: Samuel Huang Cr-Commit-Position: refs/heads/main@{#921292} NOKEYCHECK=True GitOrigin-RevId: 380557e6b592531eb360513791968dd7ab0ee77d --- algorithm.h | 3 ++- equivalence_map.cc | 2 +- equivalence_map.h | 2 +- equivalence_map_unittest.cc | 28 ++++++++++++++-------------- target_pool.cc | 2 +- target_pool.h | 9 +++++---- target_pool_unittest.cc | 20 ++++++++++---------- targets_affinity.cc | 4 ++-- targets_affinity.h | 5 +++-- targets_affinity_unittest.cc | 8 ++++---- zucchini_gen_unittest.cc | 5 +++-- 11 files changed, 46 insertions(+), 42 deletions(-) diff --git a/algorithm.h b/algorithm.h index f5d49e3..4cafe93 100644 --- a/algorithm.h +++ b/algorithm.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -69,7 +70,7 @@ inline int IncrementForAlignCeil4(T pos) { // Sorts values in |container| and removes duplicates. template -void SortAndUniquify(std::vector* container) { +void SortAndUniquify(std::deque* container) { std::sort(container->begin(), container->end()); container->erase(std::unique(container->begin(), container->end()), container->end()); diff --git a/equivalence_map.cc b/equivalence_map.cc index 26c0764..be9ec0f 100644 --- a/equivalence_map.cc +++ b/equivalence_map.cc @@ -291,7 +291,7 @@ offset_t OffsetMapper::ExtendedForwardProject(offset_t offset) const { : kOffsetBound - 1; } -void OffsetMapper::ForwardProjectAll(std::vector* offsets) const { +void OffsetMapper::ForwardProjectAll(std::deque* offsets) const { DCHECK(std::is_sorted(offsets->begin(), offsets->end())); auto current = equivalences_.begin(); for (auto& src : *offsets) { diff --git a/equivalence_map.h b/equivalence_map.h index 8b716a1..2a8b7de 100644 --- a/equivalence_map.h +++ b/equivalence_map.h @@ -128,7 +128,7 @@ 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* offsets) const; + void ForwardProjectAll(std::deque* offsets) const; // Accessor for testing. const std::vector equivalences() const { return equivalences_; } diff --git a/equivalence_map_unittest.cc b/equivalence_map_unittest.cc index b3a4ea4..508bf23 100644 --- a/equivalence_map_unittest.cc +++ b/equivalence_map_unittest.cc @@ -22,7 +22,7 @@ namespace zucchini { namespace { -using OffsetVector = std::vector; +using OffsetDeque = std::deque; // Make all references 2 bytes long. constexpr offset_t kReferenceSize = 2; @@ -504,7 +504,7 @@ TEST(EquivalenceMapTest, ExtendedForwardProjectEncoding) { TEST(EquivalenceMapTest, ForwardProjectAll) { auto ForwardProjectAllTest = [](const OffsetMapper& offset_mapper, std::initializer_list offsets) { - OffsetVector offsets_vec(offsets); + std::deque 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/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&& targets) { +TargetPool::TargetPool(std::deque&& 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 +#include #include #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::const_iterator; + using const_iterator = std::deque::const_iterator; TargetPool(); // Initializes the object with given sorted and unique |targets|. - explicit TargetPool(std::vector&& targets); + explicit TargetPool(std::deque&& 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& targets() const { return targets_; } + const std::deque& targets() const { return targets_; } const std::vector& types() const { return types_; } // Returns the number of targets. @@ -72,7 +73,7 @@ class TargetPool { private: std::vector types_; // Enumerates type_tag for this pool. - std::vector targets_; // Targets for pool in ascending order. + std::deque 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 +#include #include #include -#include #include "components/zucchini/image_utils.h" #include "testing/gtest/include/gtest/gtest.h" @@ -16,29 +16,29 @@ namespace zucchini { namespace { -using OffsetVector = std::vector; +using OffsetDeque = std::deque; } // namespace TEST(TargetPoolTest, InsertTargetsFromReferences) { - auto test_insert = [](std::vector&& references) -> OffsetVector { + auto test_insert = [](std::vector&& 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& old_targets, - const std::vector& new_targets) { + const std::deque& old_targets, + const std::deque& 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 #include +#include #include #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& old_targets, - const std::vector& new_targets); + const std::deque& old_targets, + const std::deque& 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& old_targets, - const std::vector& new_targets) { + const std::deque& old_targets, + const std::deque& 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& old_targets, - const std::vector& new_targets, + const std::deque& old_targets, + const std::deque& new_targets, double min_affinity, const std::vector& expected_old_labels, const std::vector& expected_new_labels) { 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 +#include #include #include @@ -30,8 +31,8 @@ constexpr double kDummySim = 0.0; std::vector GenerateReferencesDeltaTest( std::vector&& old_references, std::vector&& new_references, - std::vector&& exp_old_targets, - std::vector&& exp_projected_old_targets, + std::deque&& exp_old_targets, + std::deque&& 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. -- cgit v1.2.3 From b90a947429fdce96b1d684b9a7af9683cb4a13c1 Mon Sep 17 00:00:00 2001 From: Etienne Pierre-doray Date: Thu, 28 Oct 2021 21:16:04 +0000 Subject: [Zucchini]: Add patch version. This is a breaking change to zucchini patch format: Zucchini 1.0, see changelog. Add major/minor patch-wide version, and element version. Also add VerifyPatch() API and command line option to verify patch compatibility. Design: go/zucchini-versions Bug: 1231882 Change-Id: I19f1fbe2ee866c23f0814ffe6a912fb72812edbc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3140224 Commit-Queue: Etienne Pierre-Doray Reviewed-by: Samuel Huang Reviewed-by: Calder Kitagawa Cr-Commit-Position: refs/heads/main@{#936096} NOKEYCHECK=True GitOrigin-RevId: 559d77a9741428a48add017d389d104e431e6de7 --- README.md | 18 ++++++- disassembler_dex.h | 1 + disassembler_elf.h | 3 ++ disassembler_no_op.h | 2 + disassembler_win32.h | 3 ++ disassembler_ztf.h | 2 + element_detection.cc | 35 ++++++++++++++ element_detection.h | 3 ++ fuzzers/testdata/patch_fuzzer/empty.zuc | Bin 76 -> 82 bytes main_utils.cc | 4 ++ patch_read_write_unittest.cc | 80 ++++++++++++++++++++++++++++---- patch_reader.cc | 12 +++++ patch_utils.h | 22 +++++++-- patch_writer.cc | 6 +++ zucchini_commands.cc | 5 ++ zucchini_commands.h | 3 ++ zucchini_integration.cc | 27 +++++++++++ zucchini_integration.h | 10 ++++ 18 files changed, 222 insertions(+), 14 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/disassembler_dex.h b/disassembler_dex.h index 8e739d0..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, diff --git a/disassembler_elf.h b/disassembler_elf.h index b29e89f..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 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); 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 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 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 DetectElementFromDisassembler(ConstBufferView image) { std::unique_ptr 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 MakeDisassemblerWithoutFallback( std::unique_ptr 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/fuzzers/testdata/patch_fuzzer/empty.zuc b/fuzzers/testdata/patch_fuzzer/empty.zuc index 64eacf5..1bda1e9 100644 Binary files a/fuzzers/testdata/patch_fuzzer/empty.zuc and b/fuzzers/testdata/patch_fuzzer/empty.zuc 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 [-keep]", 3, &MainApply}, + {"verify", "-verify ", 1, &MainVerify}, {"read", "-read [-dump]", 1, &MainRead}, {"detect", "-detect ", 1, &MainDetect}, {"match", "-match [-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(&data); + } + // Bump element version to 0. + { + ByteVector data = CreatePatchElement(); + ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x00, &data); + TestInvalidInitialize(&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(&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(element_match.new_element.size); element_header.exe_type = element_match.exe_type(); + element_header.version = DisassemblerVersionOfType(element_match.exe_type()); return sink->PutValue(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(old_image.size()); header_.old_crc = CalculateCrc32(old_image.begin(), old_image.end()); header_.new_size = base::checked_cast(new_image.size()); 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_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_ -- cgit v1.2.3 From 8bb965d29e918d0559589a215ff7f4bd0874bc08 Mon Sep 17 00:00:00 2001 From: Etienne Pierre-doray Date: Fri, 29 Oct 2021 14:12:23 +0000 Subject: [Zucchini]: Convert OffsetMapper to deque push_back with vector tends to cause higher memory peak than necessary. Changing deque is a simple change that reduces memory peak at the cost of loss of guarantee (contiguous storage). This has no significant impact on cpu time. On MacBook pro 2017 Before: Zucchini.TotalTime 9.95879 s Zucchini.TotalTime 9.11599 s Zucchini.TotalTime 9.33174 s After: Zucchini.TotalTime 10.5557 s Zucchini.TotalTime 8.78599 s Zucchini.TotalTime 8.95282 s Bug: 1262150 Change-Id: I078a671832f2a33d5e1a3d9d971bff66d4179b89 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3247092 Commit-Queue: Etienne Pierre-Doray Reviewed-by: Samuel Huang Cr-Commit-Position: refs/heads/main@{#936371} NOKEYCHECK=True GitOrigin-RevId: 7abe67cf21e8f30c0ff2499410c8d57aae9bf8fc --- equivalence_map.cc | 5 +++-- equivalence_map.h | 11 ++++++----- equivalence_map_unittest.cc | 28 ++++++++++++++-------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/equivalence_map.cc b/equivalence_map.cc index be9ec0f..b4d5e27 100644 --- a/equivalence_map.cc +++ b/equivalence_map.cc @@ -214,7 +214,7 @@ EquivalenceCandidate VisitEquivalenceSeed( /******** OffsetMapper ********/ -OffsetMapper::OffsetMapper(std::vector&& equivalences, +OffsetMapper::OffsetMapper(std::deque&& equivalences, offset_t old_image_size, offset_t new_image_size) : equivalences_(std::move(equivalences)), @@ -310,7 +310,7 @@ void OffsetMapper::ForwardProjectAll(std::deque* offsets) const { } void OffsetMapper::PruneEquivalencesAndSortBySource( - std::vector* equivalences) { + std::deque* 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 2a8b7de..af99ac4 100644 --- a/equivalence_map.h +++ b/equivalence_map.h @@ -7,6 +7,7 @@ #include +#include #include #include @@ -82,13 +83,13 @@ EquivalenceCandidate VisitEquivalenceSeed( // bytes in |old_image| and |new_image|) one-to-one. class OffsetMapper { public: - using const_iterator = std::vector::const_iterator; + using const_iterator = std::deque::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&& equivalences, + OffsetMapper(std::deque&& equivalences, offset_t old_image_size, offset_t new_image_size); // - From a generator, useful for Zucchini-apply. @@ -131,7 +132,7 @@ class OffsetMapper { void ForwardProjectAll(std::deque* offsets) const; // Accessor for testing. - const std::vector equivalences() const { return equivalences_; } + const std::deque 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* equivalences); + std::deque* 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 equivalences_; + std::deque 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 508bf23..6b826d1 100644 --- a/equivalence_map_unittest.cc +++ b/equivalence_map_unittest.cc @@ -252,28 +252,28 @@ TEST(EquivalenceMapTest, ExtendEquivalenceBackward) { TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) { auto PruneEquivalencesAndSortBySourceTest = - [](std::vector&& equivalences) { + [](std::deque&& equivalences) { OffsetMapper::PruneEquivalencesAndSortBySource(&equivalences); return std::move(equivalences); }; - EXPECT_EQ(std::vector(), + EXPECT_EQ(std::deque(), PruneEquivalencesAndSortBySourceTest({})); - EXPECT_EQ(std::vector({{0, 10, 1}}), + EXPECT_EQ(std::deque({{0, 10, 1}}), PruneEquivalencesAndSortBySourceTest({{0, 10, 1}})); - EXPECT_EQ(std::vector(), + EXPECT_EQ(std::deque(), PruneEquivalencesAndSortBySourceTest({{0, 10, 0}})); - EXPECT_EQ(std::vector({{0, 10, 1}, {1, 11, 1}}), + EXPECT_EQ(std::deque({{0, 10, 1}, {1, 11, 1}}), PruneEquivalencesAndSortBySourceTest({{0, 10, 1}, {1, 11, 1}})); - EXPECT_EQ(std::vector({{0, 10, 2}, {2, 13, 1}}), + EXPECT_EQ(std::deque({{0, 10, 2}, {2, 13, 1}}), PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 2}})); - EXPECT_EQ(std::vector({{0, 10, 2}}), + EXPECT_EQ(std::deque({{0, 10, 2}}), PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 1}})); - EXPECT_EQ(std::vector({{0, 10, 2}, {2, 14, 1}}), + EXPECT_EQ(std::deque({{0, 10, 2}, {2, 14, 1}}), PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 13, 2}})); - EXPECT_EQ(std::vector({{0, 10, 1}, {1, 12, 3}}), + EXPECT_EQ(std::deque({{0, 10, 1}, {1, 12, 3}}), PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 3}})); - EXPECT_EQ(std::vector({{0, 10, 3}, {3, 16, 2}}), + EXPECT_EQ(std::deque({{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({{0, 10, +300000}, {300000, 30, +300000}}), + EXPECT_EQ(std::deque({{0, 10, +300000}, {300000, 30, +300000}}), PruneEquivalencesAndSortBySourceTest([] { - std::vector equivalenses; + std::deque 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(), kOldImageSize, + OffsetMapper offset_mapper(std::deque(), 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&& equivalences, + auto run_test = [&case_no](std::deque&& equivalences, const std::string& old_spec, const std::string& new_spec) { const size_t old_size = old_spec.length(); -- cgit v1.2.3