diff options
author | ckitagawa <ckitagawa@chromium.org> | 2021-09-03 15:48:28 +0000 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2021-09-03 08:59:53 -0700 |
commit | 26518ffbdf042c4c35617be2446dac49590a89c2 (patch) | |
tree | 1fc7aa788fd12474c4df6bd18b47461cd93e6023 | |
parent | f137bf4b5542b966abc4c08762c5e60b21913f4d (diff) | |
download | zucchini-26518ffbdf042c4c35617be2446dac49590a89c2.tar.gz |
[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 <huangs@chromium.org>
Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#918119}
NOKEYCHECK=True
GitOrigin-RevId: 9cc600ef0b60ff1ec76683a2bfb98a6bdbb05d1e
-rw-r--r-- | disassembler_dex.cc | 244 | ||||
-rw-r--r-- | disassembler_dex.h | 37 | ||||
-rw-r--r-- | testdata/all.smali | 628 | ||||
-rw-r--r-- | testdata/const-method-handle-min.smali | 14 | ||||
-rw-r--r-- | testdata/invoke-custom-min.smali | 39 | ||||
-rw-r--r-- | testdata/invoke-polymorphic.smali | 70 | ||||
-rw-r--r-- | type_dex.h | 83 |
7 files changed, 1052 insertions, 63 deletions
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<offset_t>(map_item.offset)), num_items_(base::checked_cast<uint32_t>(map_item.size)), item_size_(base::checked_cast<uint32_t>(item_size)), rel_location_(base::checked_cast<uint32_t>(rel_location)), + mapper_input_delta_( + mapper_wants_item ? 0 : base::checked_cast<uint32_t>(rel_location)), mapper_(std::move(mapper)) { static_assert(sizeof(decltype(map_item.offset)) <= sizeof(offset_t), "map_item.offset too large."); @@ -457,7 +467,11 @@ class ItemReferenceReader : public ReferenceReader { // |reference_width| is unneeded. if (location >= hi_) break; - const offset_t target = mapper_.Run(location); + + // |location == item_offset + mapper_input_delta_| in the majority of + // cases. The exception is when |mapper_| wants an item aligned location + // instead e.g. ReadMethodHandleFieldOrMethodId. + const offset_t target = mapper_.Run(item_offset + mapper_input_delta_); // kDexSentinelOffset (0) may appear for the following: // - ProtoIdItem: parameters_off. @@ -467,6 +481,9 @@ class ItemReferenceReader : public ReferenceReader { // - AnnotationSetRefItem: annotations_off. // kDexSentinelIndexAsOffset (0xFFFFFFFF) may appear for the following: // - ClassDefItem: superclass_idx, source_file_idx. + // - MethodHandleItem: |mapper_| uses ReadMethodHandleFieldOrMethodId and + // determines the item at |cur_idx_| is not of the required reference + // type. if (target == kDexSentinelOffset || target == kDexSentinelIndexAsOffset) { ++cur_idx_; continue; @@ -492,6 +509,7 @@ class ItemReferenceReader : public ReferenceReader { const uint32_t num_items_; const uint32_t item_size_; const uint32_t rel_location_; + const uint32_t mapper_input_delta_; const Mapper mapper_; offset_t cur_idx_ = 0; }; @@ -688,6 +706,53 @@ static offset_t ReadTargetIndex(ConstBufferView image, base::checked_cast<offset_t>(unsafe_idx * target_item_size); } +// Reads a field or method index of the MethodHandleItem located at |location| +// in |image| and translates |method_handle_item.field_or_method_id| to the +// offset of a fixed-size item specified by |target_map_item| and +// |target_item_size|. The index is deemed to be of the correct target type if +// |method_handle_item.method_handle_type| falls within the range [|min_type|, +// |max_type|]. If the target type is correct ReadTargetIndex is called. +// Returns the target offset if valid, or kDexSentinelIndexAsOffset if +// |method_handle_item.method_handle_type| is of the wrong type, or +// kInvalidOffset otherwise. +// +// As of DEX version 39 MethodHandleType values for FieldId and MethodId each +// form one consecutive block of values. If this changes, then the interface to +// this function will need to be redesigned. +static offset_t ReadMethodHandleFieldOrMethodId( + ConstBufferView image, + const dex::MapItem& target_map_item, + size_t target_item_size, + dex::MethodHandleType min_type, + dex::MethodHandleType max_type, + offset_t location) { + dex::MethodHandleItem method_handle_item = + image.read<dex::MethodHandleItem>(location); + + // Cannot use base::checked_cast as dex::MethodHandleType is an enum class so + // static_assert on the size instead. + static_assert(sizeof(decltype(dex::MethodHandleItem::method_handle_type)) <= + sizeof(dex::MethodHandleType), + "dex::MethodHandleItem::method_handle_type may not fit into " + "dex::MethodHandleType."); + dex::MethodHandleType method_handle_type = + static_cast<dex::MethodHandleType>(method_handle_item.method_handle_type); + + if (method_handle_type >= dex::MethodHandleType::kMaxMethodHandleType) { + return kInvalidOffset; + } + + // Use DexSentinelIndexAsOffset to skip the item as it isn't of the + // corresponding method handle type. + if (method_handle_type < min_type || method_handle_type > max_type) { + return kDexSentinelIndexAsOffset; + } + + return ReadTargetIndex<decltype(dex::MethodHandleItem::field_or_method_id)>( + image, target_map_item, target_item_size, + location + offsetof(dex::MethodHandleItem, field_or_method_id)); +} + // Reads uint32_t value in |image| at (valid) |location| and checks whether it // is a safe offset of a fixed-size item. Returns the target offset (possibly a // sentinel) if valid, or kInvalidOffset otherwise. This is compatible with @@ -779,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<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const { {{2, TypeTag(kCodeToTypeId), PoolTag(kTypeId)}, &DisassemblerDex::MakeReadCodeToTypeId16, &DisassemblerDex::MakeWriteTypeId16}, + {{2, TypeTag(kCodeToProtoId), PoolTag(kProtoId)}, + &DisassemblerDex::MakeReadCodeToProtoId16, + &DisassemblerDex::MakeWriteProtoId16}, {{2, TypeTag(kMethodIdToProtoId), PoolTag(kProtoId)}, &DisassemblerDex::MakeReadMethodIdToProtoId16, &DisassemblerDex::MakeWriteProtoId16}, {{2, TypeTag(kCodeToFieldId), PoolTag(kFieldId)}, &DisassemblerDex::MakeReadCodeToFieldId16, &DisassemblerDex::MakeWriteFieldId16}, + {{2, TypeTag(kMethodHandleToFieldId), PoolTag(kFieldId)}, + &DisassemblerDex::MakeReadMethodHandleToFieldId16, + &DisassemblerDex::MakeWriteFieldId16}, {{4, TypeTag(kAnnotationsDirectoryToFieldId), PoolTag(kFieldId)}, &DisassemblerDex::MakeReadAnnotationsDirectoryToFieldId32, &DisassemblerDex::MakeWriteFieldId32}, {{2, TypeTag(kCodeToMethodId), PoolTag(kMethodId)}, &DisassemblerDex::MakeReadCodeToMethodId16, &DisassemblerDex::MakeWriteMethodId16}, + {{2, TypeTag(kMethodHandleToMethodId), PoolTag(kMethodId)}, + &DisassemblerDex::MakeReadMethodHandleToMethodId16, + &DisassemblerDex::MakeWriteMethodId16}, {{4, TypeTag(kAnnotationsDirectoryToMethodId), PoolTag(kMethodId)}, &DisassemblerDex::MakeReadAnnotationsDirectoryToMethodId32, &DisassemblerDex::MakeWriteMethodId32}, @@ -883,6 +957,9 @@ std::vector<ReferenceGroup> 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<ReferenceGroup> 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<ReferenceReader> +DisassemblerDex::MakeReadCallSiteIdToCallSite32(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadTargetOffset32, image_); + return std::make_unique<ItemReferenceReader>( + lo, hi, call_site_map_item_, sizeof(dex::CallSiteIdItem), + offsetof(dex::CallSiteIdItem, call_site_off), std::move(mapper)); +} + +std::unique_ptr<ReferenceReader> +DisassemblerDex::MakeReadMethodHandleToFieldId16(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_, + field_map_item_, sizeof(dex::FieldIdItem), + dex::MethodHandleType::kStaticPut, + dex::MethodHandleType::kInstanceGet); + // Use |mapper_wants_item == true| for ItemReferenceReader such that + // |location| is aligned with MethodHandleItem when passed to |mapper|. This + // allows ReadMethodHandleFieldOrMethodId to safely determine whether the + // reference in the MethodHandleItem is of the correct type to be emitted. + return std::make_unique<ItemReferenceReader>( + lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem), + offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper), + /*mapper_wants_item=*/true); +} + +std::unique_ptr<ReferenceReader> +DisassemblerDex::MakeReadMethodHandleToMethodId16(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_, + method_map_item_, sizeof(dex::MethodIdItem), + dex::MethodHandleType::kInvokeStatic, + dex::MethodHandleType::kInvokeInterface); + // Use |mapper_wants_item == true| for ItemReferenceReader such that + // |location| is aligned with MethodHandleItem when passed to |mapper|. This + // allows ReadMethodHandleFieldOrMethodId to safely determine whether the + // reference in the MethodHandleItem is of the correct type to be emitted. + return std::make_unique<ItemReferenceReader>( + lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem), + offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper), + /*mapper_wants_item=*/true); +} + std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadTypeListToTypeId16( offset_t lo, offset_t hi) { @@ -1233,6 +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<ReferenceReader> DisassemblerDex::MakeReadCodeToStringId16( offset_t lo, offset_t hi) { @@ -1295,6 +1419,47 @@ std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToTypeId16( image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); } +std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToProtoId16( + offset_t lo, + offset_t hi) { + auto filter = base::BindRepeating( + [](const InstructionParser::Value& value) -> offset_t { + if (value.instr->format == dex::FormatId::c && + (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<uint16_t>, image_, + proto_map_item_, sizeof(dex::ProtoIdItem)); + return std::make_unique<InstructionReferenceReader>( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + +std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToCallSiteId16( + offset_t lo, + offset_t hi) { + auto filter = base::BindRepeating( + [](const InstructionParser::Value& value) -> offset_t { + if (value.instr->format == dex::FormatId::c && + (value.instr->opcode == 0xFC || // invoke-custom + value.instr->opcode == 0xFD)) { // invoke-custom/range + // BBBB from e.g, invoke-custom {vC, vD, vE, vF, vG}, + // call_site@BBBB + return value.instr_offset + 2; + } + return kInvalidOffset; + }); + auto mapper = + base::BindRepeating(ReadTargetIndex<uint16_t>, image_, + call_site_map_item_, sizeof(dex::CallSiteIdItem)); + return std::make_unique<InstructionReferenceReader>( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToFieldId16( offset_t lo, offset_t hi) { @@ -1321,7 +1486,9 @@ std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToMethodId16( [](const InstructionParser::Value& value) -> offset_t { if (value.instr->format == dex::FormatId::c && (value.instr->opcode == 0x6E || // invoke-kind - value.instr->opcode == 0x74)) { // invoke-kind/range + value.instr->opcode == 0x74 || // invoke-kind/range + value.instr->opcode == 0xFA || // invoke-polymorphic + value.instr->opcode == 0xFB)) { // invoke-polymorphic/range // BBBB from e.g., invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB. return value.instr_offset + 2; } @@ -1491,6 +1658,14 @@ std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodId32( return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer)); } +std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteCallSiteId16( + MutableBufferView image) { + auto writer = + base::BindRepeating(WriteTargetIndex<uint16_t>, call_site_map_item_, + sizeof(dex::CallSiteIdItem)); + return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer)); +} + std::unique_ptr<ReferenceWriter> DisassemblerDex::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<uint16_t> required_item_types = { - dex::kTypeStringIdItem, dex::kTypeTypeIdItem, dex::kTypeProtoIdItem, - dex::kTypeFieldIdItem, dex::kTypeMethodIdItem, dex::kTypeClassDefItem, - dex::kTypeTypeList, dex::kTypeCodeItem, - }; + // GetItemBaseSize() should have an entry for each item. for (offset_t i = 0; i < list_size; ++i) { const dex::MapItem* item = &item_list[i]; // Reject unreasonably large |item->size|. @@ -1605,29 +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<ReferenceReader> MakeReadClassDefToStaticValuesEncodedArray( offset_t lo, offset_t hi); + std::unique_ptr<ReferenceReader> MakeReadCallSiteIdToCallSite32(offset_t lo, + offset_t hi); + std::unique_ptr<ReferenceReader> MakeReadMethodHandleToFieldId16(offset_t lo, + offset_t hi); + std::unique_ptr<ReferenceReader> MakeReadMethodHandleToMethodId16( + offset_t lo, + offset_t hi); std::unique_ptr<ReferenceReader> MakeReadTypeListToTypeId16(offset_t lo, offset_t hi); std::unique_ptr<ReferenceReader> MakeReadAnnotationSetToAnnotation( @@ -203,10 +212,14 @@ class DisassemblerDex : public Disassembler { offset_t hi); std::unique_ptr<ReferenceReader> MakeReadCodeToTypeId16(offset_t lo, offset_t hi); + std::unique_ptr<ReferenceReader> MakeReadCodeToProtoId16(offset_t lo, + offset_t hi); std::unique_ptr<ReferenceReader> MakeReadCodeToFieldId16(offset_t lo, offset_t hi); std::unique_ptr<ReferenceReader> MakeReadCodeToMethodId16(offset_t lo, offset_t hi); + std::unique_ptr<ReferenceReader> MakeReadCodeToCallSiteId16(offset_t lo, + offset_t hi); std::unique_ptr<ReferenceReader> MakeReadCodeToRelCode8(offset_t lo, offset_t hi); std::unique_ptr<ReferenceReader> MakeReadCodeToRelCode16(offset_t lo, @@ -225,6 +238,8 @@ class DisassemblerDex : public Disassembler { std::unique_ptr<ReferenceWriter> MakeWriteFieldId32(MutableBufferView image); std::unique_ptr<ReferenceWriter> MakeWriteMethodId16(MutableBufferView image); std::unique_ptr<ReferenceWriter> MakeWriteMethodId32(MutableBufferView image); + std::unique_ptr<ReferenceWriter> MakeWriteCallSiteId16( + MutableBufferView image); std::unique_ptr<ReferenceWriter> MakeWriteRelCode8(MutableBufferView image); std::unique_ptr<ReferenceWriter> MakeWriteRelCode16(MutableBufferView image); std::unique_ptr<ReferenceWriter> 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 <init>()V + .registers 1 + + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + + return-void +.end method + +.method public static arrays()V + .registers 3 + + aget v0, v1, v2 + + aget-wide v0, v1, v2 + + aget-object v0, v1, v2 + + aget-boolean v0, v1, v2 + + aget-byte v0, v1, v2 + + aget-char v0, v1, v2 + + aget-short v0, v1, v2 + + aput v0, v1, v2 + + aput-wide v0, v1, v2 + + aput-object v0, v1, v2 + + aput-boolean v0, v1, v2 + + aput-byte v0, v1, v2 + + aput-char v0, v1, v2 + + aput-short v0, v1, v2 + + return-void +.end method + +.method public static binary_ops()V + .registers 3 + + add-int v0, v1, v2 + + sub-int v0, v1, v2 + + mul-int v0, v1, v2 + + div-int v0, v1, v2 + + rem-int v0, v1, v2 + + and-int v0, v1, v2 + + or-int v0, v1, v2 + + xor-int v0, v1, v2 + + shl-int v0, v1, v2 + + shr-int v0, v1, v2 + + ushr-int v0, v1, v2 + + add-long v0, v1, v2 + + sub-long v0, v1, v2 + + mul-long v0, v1, v2 + + div-long v0, v1, v2 + + rem-long v0, v1, v2 + + and-long v0, v1, v2 + + or-long v0, v1, v2 + + xor-long v0, v1, v2 + + shl-long v0, v1, v2 + + shr-long v0, v1, v2 + + ushr-long v0, v1, v2 + + add-float v0, v1, v2 + + sub-float v0, v1, v2 + + mul-float v0, v1, v2 + + div-float v0, v1, v2 + + rem-float v0, v1, v2 + + add-double v0, v1, v2 + + sub-double v0, v1, v2 + + mul-double v0, v1, v2 + + div-double v0, v1, v2 + + rem-double v0, v1, v2 + + return-void +.end method + +.method public static binary_ops_2addr()V + .registers 2 + + add-int/2addr v0, v1 + + sub-int/2addr v0, v1 + + mul-int/2addr v0, v1 + + div-int/2addr v0, v1 + + rem-int/2addr v0, v1 + + and-int/2addr v0, v1 + + or-int/2addr v0, v1 + + xor-int/2addr v0, v1 + + shl-int/2addr v0, v1 + + shr-int/2addr v0, v1 + + ushr-int/2addr v0, v1 + + add-long/2addr v0, v1 + + sub-long/2addr v0, v1 + + mul-long/2addr v0, v1 + + div-long/2addr v0, v1 + + rem-long/2addr v0, v1 + + and-long/2addr v0, v1 + + or-long/2addr v0, v1 + + xor-long/2addr v0, v1 + + shl-long/2addr v0, v1 + + shr-long/2addr v0, v1 + + ushr-long/2addr v0, v1 + + add-float/2addr v0, v1 + + sub-float/2addr v0, v1 + + mul-float/2addr v0, v1 + + div-float/2addr v0, v1 + + rem-float/2addr v0, v1 + + add-double/2addr v0, v1 + + sub-double/2addr v0, v1 + + mul-double/2addr v0, v1 + + div-double/2addr v0, v1 + + rem-double/2addr v0, v1 + + return-void +.end method + +.method public static binary_ops_lit16()V + .registers 2 + + add-int/lit16 v0, v1, 0x1234 + + rsub-int v0, v1, 0x1234 + + mul-int/lit16 v0, v1, 0x1234 + + div-int/lit16 v0, v1, 0x1234 + + rem-int/lit16 v0, v1, 0x1234 + + and-int/lit16 v0, v1, 0x1234 + + or-int/lit16 v0, v1, 0x1234 + + xor-int/lit16 v0, v1, 0x1234 + + return-void +.end method + +.method public static binary_ops_lit8()V + .registers 2 + + add-int/lit8 v0, v1, 0x12 + + rsub-int/lit8 v0, v1, 0x12 + + mul-int/lit8 v0, v1, 0x12 + + div-int/lit8 v0, v1, 0x12 + + rem-int/lit8 v0, v1, 0x12 + + and-int/lit8 v0, v1, 0x12 + + or-int/lit8 v0, v1, 0x12 + + xor-int/lit8 v0, v1, 0x12 + + shl-int/lit8 v0, v1, 0x12 + + shr-int/lit8 v0, v1, 0x12 + + ushr-int/lit8 v0, v1, 0x12 + + return-void +.end method + +.method public static compares()V + .registers 3 + + cmpl-float v0, v1, v2 + + cmpg-float v0, v1, v2 + + cmpl-double v0, v1, v2 + + cmpg-double v0, v1, v2 + + cmp-long v0, v1, v2 + + return-void +.end method + +.method public static conditionals()V + .registers 2 + + if-eq v0, v1, :cond_18 + + if-ne v0, v1, :cond_18 + + if-lt v0, v1, :cond_18 + + if-ge v0, v1, :cond_18 + + if-gt v0, v1, :cond_18 + + if-le v0, v1, :cond_18 + + if-eqz v0, :cond_18 + + if-nez v0, :cond_18 + + if-ltz v0, :cond_18 + + if-gez v0, :cond_18 + + if-gtz v0, :cond_18 + + if-lez v0, :cond_18 + + :cond_18 + return-void +.end method + +.method public static constants()V + .registers 1 + + const/4 v0, 0x1 + + const/16 v0, 0x1234 + + const v0, 0x12345678 + + const/high16 v0, 0x12340000 + + const-wide/16 v0, 0x1234 + + const-wide/32 v0, 0x12345678 + + const-wide v0, 0x1234567890abcdefL # 5.626349108908516E-221 + + const-wide/high16 v0, 0x1234000000000000L + + const-string v0, "string" + + const-string/jumbo v0, "string" + + const-class v0, Ljava/lang/Object; + + return-void +.end method + +.method public static misc()V + .registers 5 + + nop + + monitor-enter v0 + + monitor-exit v0 + + check-cast v0, Ljava/lang/Object; + + instance-of v0, v1, Ljava/lang/Object; + + array-length v0, v1 + + new-instance v0, Ljava/lang/Object; + + new-array v0, v1, Ljava/lang/Object; + + filled-new-array {v0, v1, v2, v3, v4}, [Ljava/lang/Object; + + filled-new-array/range {v0 .. v4}, [Ljava/lang/Object; + + fill-array-data v0, :array_1e + + throw v0 + + goto :goto_1c + + goto/16 :goto_1c + + goto/32 :goto_1c + + :goto_1c + return-void + + nop + + :array_1e + .array-data 4 + 0x1 + 0x2 + 0x3 + 0x4 + 0x5 + 0x6 + 0x7 + 0x8 + 0x9 + 0x0 + .end array-data +.end method + +.method public static moves()V + .registers 2 + + move v0, v1 + + move/from16 v0, v1 + + move/16 v0, v1 + + move-wide v0, v1 + + move-wide/from16 v0, v1 + + move-wide/16 v0, v1 + + move-object v0, v1 + + move-object/from16 v0, v1 + + move-object/16 v0, v1 + + move-result v0 + + move-result-wide v0 + + move-result-object v0 + + move-exception v0 + + return-void +.end method + +.method public static packed_switch()V + .registers 1 + + packed-switch v0, :pswitch_data_8 + + :goto_3 + return-void + + goto :goto_3 + + :pswitch_5 + goto :goto_3 + + :pswitch_6 + goto :goto_3 + + nop + + :pswitch_data_8 + .packed-switch 0x7ffffffe + :pswitch_5 + :pswitch_6 + .end packed-switch +.end method + +.method public static return32()I + .registers 1 + + return v0 +.end method + +.method public static return64()I + .registers 2 + + return-wide v0 +.end method + +.method public static return_object()Ljava/lang/Object; + .registers 1 + + return-object v0 +.end method + +.method public static sparse_switch()V + .registers 2 + + sparse-switch v0, :sswitch_data_4 + + :sswitch_3 + return-void + + :sswitch_data_4 + .sparse-switch + 0x1111 -> :sswitch_3 + 0x2222 -> :sswitch_3 + 0x3333 -> :sswitch_3 + 0x4444 -> :sswitch_3 + .end sparse-switch +.end method + +.method public static static_fields()V + .registers 1 + + sget v0, LA;->sI:I + + sget-wide v0, LA;->sJ:J + + sget-object v0, LA;->sO:LA; + + sget-boolean v0, LA;->sZ:Z + + sget-byte v0, LA;->sB:B + + sget-char v0, LA;->sC:C + + sget-short v0, LA;->sS:S + + sput v0, LA;->sI:I + + sput-wide v0, LA;->sJ:J + + sput-object v0, LA;->sO:LA; + + sput-boolean v0, LA;->sZ:Z + + sput-byte v0, LA;->sB:B + + sput-char v0, LA;->sC:C + + sput-short v0, LA;->mS:S + + return-void +.end method + +.method public static unary_ops()V + .registers 2 + + neg-int v0, v1 + + not-int v0, v1 + + neg-long v0, v1 + + not-long v0, v1 + + neg-float v0, v1 + + neg-double v0, v1 + + int-to-long v0, v1 + + int-to-float v0, v1 + + int-to-double v0, v1 + + long-to-int v0, v1 + + long-to-float v0, v1 + + long-to-double v0, v1 + + float-to-int v0, v1 + + float-to-long v0, v1 + + float-to-double v0, v1 + + double-to-int v0, v1 + + double-to-long v0, v1 + + double-to-float v0, v1 + + int-to-byte v0, v1 + + int-to-char v0, v1 + + int-to-short v0, v1 + + return-void +.end method + + +# virtual methods +.method public instance_fields()V + .registers 2 + + iget v0, p0, LA;->sI:I + + iget-wide v0, p0, LA;->sJ:J + + iget-object v0, p0, LA;->sO:LA; + + iget-boolean v0, p0, LA;->sZ:Z + + iget-byte v0, p0, LA;->sB:B + + iget-char v0, p0, LA;->sC:C + + iget-short v0, p0, LA;->sS:S + + iput v0, p0, LA;->sI:I + + iput-wide v0, p0, LA;->sJ:J + + iput-object v0, p0, LA;->sO:LA; + + iput-boolean v0, p0, LA;->sZ:Z + + iput-byte v0, p0, LA;->sB:B + + iput-char v0, p0, LA;->sC:C + + iput-short v0, p0, LA;->sS:S + + return-void +.end method + +.method public invokes()V + .registers 5 + + invoke-virtual {v0, v1, v2, v3, p0}, LA;->invokes()V + + invoke-super {v0, v1, v2, v3, p0}, LA;->invokes()V + + invoke-direct {v0, v1, v2, v3, p0}, LA;->invokes()V + + invoke-static {v0, v1, v2, v3, p0}, LA;->invokes()V + + invoke-interface {v0, v1, v2, v3, p0}, LA;->invokes()V +.end method diff --git a/testdata/const-method-handle-min.smali b/testdata/const-method-handle-min.smali new file mode 100644 index 0000000..0bf157f --- /dev/null +++ b/testdata/const-method-handle-min.smali @@ -0,0 +1,14 @@ +# Tests const-method-handle added in DEX version 39. + +# Compile using smali: https://github.com/JesusFreke/smali +# java -jar smali.jar assemble const-method-handle.smali --api 28 + +.class public LConstMethodHandle; +.super Ljava/lang/Object; + +.method public (I)V + .registers 2 + const-method-handle v1, invoke-static@Ljava/lang/String;->copyValueOf([C)Ljava/lang/String; + const-method-handle v0, invoke-instance@Ljava/lang/String;->charAt(I)C + return-void +.end method diff --git a/testdata/invoke-custom-min.smali b/testdata/invoke-custom-min.smali new file mode 100644 index 0000000..64bccbc --- /dev/null +++ b/testdata/invoke-custom-min.smali @@ -0,0 +1,39 @@ +# Tests invoke-custom added in DEX version 38. + +# Compile using smali: https://github.com/JesusFreke/smali +# java -jar smali.jar assemble invoke-custom-min.smali --api 28 + +.class public LFoo; +.super Ljava/lang/Object; + +.method public la1(Ljava/util/ArrayList;)V + .registers 5 + .annotation system Ldalvik/annotation/Signature; + value = { + "(", + "Ljava/util/ArrayList", + "<", + "Ljava/lang/String;", + ">;)V" + } + .end annotation + + .prologue + .line 42 + invoke-virtual {p1}, Ljava/util/ArrayList;->stream()Ljava/util/stream/Stream; + + move-result-object v0 + + invoke-custom {}, call_site_1("bar", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z, invoke-static@LFoo;->lambda$la1$1(I)Z, (I)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; + + move-result-object v1 + + invoke-custom {}, call_site_2("test", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z, invoke-static@LFoo;->lambda$la1$1(Ljava/lang/String;)Z, (Ljava/lang/String;)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; + + move-result-object v2 + + invoke-interface {v0, v1, v2}, Ljava/util/stream/Stream;->filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream; + + .line 50 + return-void +.end method diff --git a/testdata/invoke-polymorphic.smali b/testdata/invoke-polymorphic.smali new file mode 100644 index 0000000..1ad7246 --- /dev/null +++ b/testdata/invoke-polymorphic.smali @@ -0,0 +1,70 @@ +# Tests invoke-polymorphic added in DEX version 38. +# Disassembled from dexdump test files. +# Repo: https://android.googlesource.com/platform/art/ +# File: test/dexdump/invoke-polymorphic.dex + +# Compile using smali: https://github.com/JesusFreke/smali +# java -jar smali.jar assemble invoke-polymorphic.smali --api 28 + +.class public LMain; +.super Ljava/lang/Object; +.source "Main.java" + + +# direct methods +.method public constructor <init>()V + .registers 1 + + .prologue + .line 9 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .registers 10 + .param p0, "args" # [Ljava/lang/String; + .annotation system Ldalvik/annotation/Throws; + value = { + Ljava/lang/Throwable; + } + .end annotation + + .prologue + const-wide v2, 0x400199999999999aL # 2.2 + + const/4 v4, 0x1 + + .line 31 + const/4 v0, 0x0 + + .line 32 + .local v0, "handle":Ljava/lang/invoke/MethodHandle; + const/4 v5, 0x0 + + .line 33 + .local v5, "o":Ljava/lang/Object; + const-string/jumbo v1, "a" + + move v6, v4 + + invoke-polymorphic/range {v0 .. v6}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;DILjava/lang/Object;I)Ljava/lang/String; + + move-result-object v7 + + .line 34 + .local v7, "s":Ljava/lang/String; + invoke-polymorphic {v0, v2, v3, v4}, Ljava/lang/invoke/MethodHandle;->invokeExact([Ljava/lang/Object;)Ljava/lang/Object;, (DI)I + + move-result v8 + + .line 35 + .local v8, "x":I + const-string/jumbo v1, "a" + + invoke-polymorphic {v0, v1, v2, v3, v4}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;DI)V + + .line 56 + return-void +.end method @@ -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 |