Home | History | Annotate | Download | only in rawdex
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package dexfuzz.rawdex;
     18 
     19 import dexfuzz.Log;
     20 
     21 import java.io.IOException;
     22 import java.util.ArrayList;
     23 import java.util.List;
     24 
     25 public class RawDexFile implements RawDexObject {
     26   private OffsetTracker offsetTracker;
     27 
     28   public HeaderItem header;
     29 
     30   public MapList mapList;
     31 
     32   // Can be allocated after reading the header.
     33   public List<StringIdItem> stringIds;
     34   public List<TypeIdItem> typeIds;
     35   public List<ProtoIdItem> protoIds;
     36   public List<FieldIdItem> fieldIds;
     37   public List<MethodIdItem> methodIds;
     38   public List<ClassDefItem> classDefs;
     39 
     40   // Need to be allocated later (will be allocated in MapList.java)
     41   public List<StringDataItem> stringDatas;
     42   public List<ClassDataItem> classDatas;
     43   public List<TypeList> typeLists;
     44   public List<CodeItem> codeItems;
     45   public DebugInfoItem debugInfoItem;
     46   public List<AnnotationsDirectoryItem> annotationsDirectoryItems;
     47   public List<AnnotationSetRefList> annotationSetRefLists;
     48   public List<AnnotationSetItem> annotationSetItems;
     49   public List<AnnotationItem> annotationItems;
     50   public List<EncodedArrayItem> encodedArrayItems;
     51 
     52   @Override
     53   public void read(DexRandomAccessFile file) throws IOException {
     54     // Get a reference to the OffsetTracker, so that IdCreator can use it.
     55     offsetTracker = file.getOffsetTracker();
     56 
     57     file.seek(0);
     58 
     59     // Read header.
     60     (header = new HeaderItem()).read(file);
     61 
     62     // We can allocate all of these now.
     63     stringIds = new ArrayList<StringIdItem>(header.stringIdsSize);
     64     typeIds = new ArrayList<TypeIdItem>(header.typeIdsSize);
     65     protoIds = new ArrayList<ProtoIdItem>(header.protoIdsSize);
     66     fieldIds = new ArrayList<FieldIdItem>(header.fieldIdsSize);
     67     methodIds = new ArrayList<MethodIdItem>(header.methodIdsSize);
     68     classDefs = new ArrayList<ClassDefItem>(header.classDefsSize);
     69 
     70     mapList = new MapList(this);
     71     mapList.read(file);
     72 
     73     file.getOffsetTracker().associateOffsets();
     74   }
     75 
     76   @Override
     77   public void write(DexRandomAccessFile file) throws IOException {
     78     file.seek(0);
     79 
     80     // We read the header first, and then the map list, and then everything
     81     // else. Therefore, when we get to the end of the header, tell OffsetTracker
     82     // to skip past the map list offsets, and then when we get to the map list,
     83     // tell OffsetTracker to skip back there, and then return to where it was previously.
     84 
     85     // Update the map items' sizes first
     86     // - but only update the items that we expect to have changed size.
     87     // ALSO update the header's table sizes!
     88     for (MapItem mapItem : mapList.mapItems) {
     89       switch (mapItem.type) {
     90         case MapItem.TYPE_STRING_ID_ITEM:
     91           if (mapItem.size != stringIds.size()) {
     92             Log.debug("Updating StringIDs List size: " + stringIds.size());
     93             mapItem.size = stringIds.size();
     94             header.stringIdsSize = stringIds.size();
     95           }
     96           break;
     97         case MapItem.TYPE_STRING_DATA_ITEM:
     98           if (mapItem.size != stringDatas.size()) {
     99             Log.debug("Updating StringDatas List size: " + stringDatas.size());
    100             mapItem.size = stringDatas.size();
    101           }
    102           break;
    103         case MapItem.TYPE_METHOD_ID_ITEM:
    104           if (mapItem.size != methodIds.size()) {
    105             Log.debug("Updating MethodIDs List size: " + methodIds.size());
    106             mapItem.size = methodIds.size();
    107             header.methodIdsSize = methodIds.size();
    108           }
    109           break;
    110         case MapItem.TYPE_FIELD_ID_ITEM:
    111           if (mapItem.size != fieldIds.size()) {
    112             Log.debug("Updating FieldIDs List size: " + fieldIds.size());
    113             mapItem.size = fieldIds.size();
    114             header.fieldIdsSize = fieldIds.size();
    115           }
    116           break;
    117         case MapItem.TYPE_PROTO_ID_ITEM:
    118           if (mapItem.size != protoIds.size()) {
    119             Log.debug("Updating ProtoIDs List size: " + protoIds.size());
    120             mapItem.size = protoIds.size();
    121             header.protoIdsSize = protoIds.size();
    122           }
    123           break;
    124         case MapItem.TYPE_TYPE_ID_ITEM:
    125           if (mapItem.size != typeIds.size()) {
    126             Log.debug("Updating TypeIDs List size: " + typeIds.size());
    127             mapItem.size = typeIds.size();
    128             header.typeIdsSize = typeIds.size();
    129           }
    130           break;
    131         case MapItem.TYPE_TYPE_LIST:
    132           if (mapItem.size != typeLists.size()) {
    133             Log.debug("Updating TypeLists List size: " + typeLists.size());
    134             mapItem.size = typeLists.size();
    135           }
    136           break;
    137         default:
    138       }
    139     }
    140 
    141     // Use the map list to write the file.
    142     for (MapItem mapItem : mapList.mapItems) {
    143       switch (mapItem.type) {
    144         case MapItem.TYPE_HEADER_ITEM:
    145           header.write(file);
    146           file.getOffsetTracker().skipToAfterMapList();
    147           break;
    148         case MapItem.TYPE_STRING_ID_ITEM:
    149           if (mapItem.size != stringIds.size()) {
    150             Log.errorAndQuit("MapItem's size " + mapItem.size
    151                 + " no longer matches StringIDs table size " + stringIds.size());
    152           }
    153           for (StringIdItem stringId : stringIds) {
    154             stringId.write(file);
    155           }
    156           break;
    157         case MapItem.TYPE_TYPE_ID_ITEM:
    158           if (mapItem.size != typeIds.size()) {
    159             Log.errorAndQuit("MapItem's size " + mapItem.size
    160                 + " no longer matches TypeIDs table size " + typeIds.size());
    161           }
    162           for (TypeIdItem typeId : typeIds) {
    163             typeId.write(file);
    164           }
    165           break;
    166         case MapItem.TYPE_PROTO_ID_ITEM:
    167           if (mapItem.size != protoIds.size()) {
    168             Log.errorAndQuit("MapItem's size " + mapItem.size
    169                 + " no longer matches ProtoIDs table size " + protoIds.size());
    170           }
    171           for (ProtoIdItem protoId : protoIds) {
    172             protoId.write(file);
    173           }
    174           break;
    175         case MapItem.TYPE_FIELD_ID_ITEM:
    176           if (mapItem.size != fieldIds.size()) {
    177             Log.errorAndQuit("MapItem's size " + mapItem.size
    178                 + " no longer matches FieldIDs table size " + fieldIds.size());
    179           }
    180           for (FieldIdItem fieldId : fieldIds) {
    181             fieldId.write(file);
    182           }
    183           break;
    184         case MapItem.TYPE_METHOD_ID_ITEM:
    185           if (mapItem.size != methodIds.size()) {
    186             Log.errorAndQuit("MapItem's size " + mapItem.size
    187                 + " no longer matches MethodIDs table size " + methodIds.size());
    188           }
    189           for (MethodIdItem methodId : methodIds) {
    190             methodId.write(file);
    191           }
    192           break;
    193         case MapItem.TYPE_CLASS_DEF_ITEM:
    194           if (mapItem.size != classDefs.size()) {
    195             Log.errorAndQuit("MapItem's size " + mapItem.size
    196                 + " no longer matches ClassDefs table size " + classDefs.size());
    197           }
    198           for (ClassDefItem classDef : classDefs) {
    199             classDef.write(file);
    200           }
    201           break;
    202         case MapItem.TYPE_MAP_LIST:
    203           file.getOffsetTracker().goBackToMapList();
    204           mapList.write(file);
    205           file.getOffsetTracker().goBackToPreviousPoint();
    206           break;
    207         case MapItem.TYPE_TYPE_LIST:
    208           if (mapItem.size != typeLists.size()) {
    209             Log.errorAndQuit("MapItem's size " + mapItem.size
    210                 + " no longer matches TypeLists table size " + typeLists.size());
    211           }
    212           for (TypeList typeList : typeLists) {
    213             typeList.write(file);
    214           }
    215           break;
    216         case MapItem.TYPE_ANNOTATION_SET_REF_LIST:
    217           if (mapItem.size != annotationSetRefLists.size()) {
    218             Log.errorAndQuit("MapItem's size " + mapItem.size
    219                 + " no longer matches AnnotationSetRefLists table size "
    220                 + annotationSetRefLists.size());
    221           }
    222           for (AnnotationSetRefList annotationSetRefList : annotationSetRefLists) {
    223             annotationSetRefList.write(file);
    224           }
    225           break;
    226         case MapItem.TYPE_ANNOTATION_SET_ITEM:
    227           if (mapItem.size != annotationSetItems.size()) {
    228             Log.errorAndQuit("MapItem's size " + mapItem.size
    229                 + " no longer matches AnnotationSetItems table size "
    230                 + annotationSetItems.size());
    231           }
    232           for (AnnotationSetItem annotationSetItem : annotationSetItems) {
    233             annotationSetItem.write(file);
    234           }
    235           break;
    236         case MapItem.TYPE_CLASS_DATA_ITEM:
    237           if (mapItem.size != classDatas.size()) {
    238             Log.errorAndQuit("MapItem's size " + mapItem.size
    239                 + " no longer matches ClassDataItems table size " + classDatas.size());
    240           }
    241           for (ClassDataItem classData : classDatas) {
    242             classData.write(file);
    243           }
    244           break;
    245         case MapItem.TYPE_CODE_ITEM:
    246           if (mapItem.size != codeItems.size()) {
    247             Log.errorAndQuit("MapItem's size " + mapItem.size
    248                 + " no longer matches CodeItems table size " + codeItems.size());
    249           }
    250           for (CodeItem codeItem : codeItems) {
    251             codeItem.write(file);
    252           }
    253           break;
    254         case MapItem.TYPE_STRING_DATA_ITEM:
    255           if (mapItem.size != stringDatas.size()) {
    256             Log.errorAndQuit("MapItem's size " + mapItem.size
    257                 + " no longer matches StringDataItems table size "
    258                 + stringDatas.size());
    259           }
    260           for (StringDataItem stringDataItem : stringDatas) {
    261             stringDataItem.write(file);
    262           }
    263           break;
    264         case MapItem.TYPE_DEBUG_INFO_ITEM:
    265           debugInfoItem.write(file);
    266           break;
    267         case MapItem.TYPE_ANNOTATION_ITEM:
    268           if (mapItem.size != annotationItems.size()) {
    269             Log.errorAndQuit("MapItem's size " + mapItem.size
    270                 + " no longer matches AnnotationItems table size "
    271                 + annotationItems.size());
    272           }
    273           for (AnnotationItem annotationItem : annotationItems) {
    274             annotationItem.write(file);
    275           }
    276           break;
    277         case MapItem.TYPE_ENCODED_ARRAY_ITEM:
    278           if (mapItem.size != encodedArrayItems.size()) {
    279             Log.errorAndQuit("MapItem's size " + mapItem.size
    280                 + " no longer matches EncodedArrayItems table size "
    281                 + encodedArrayItems.size());
    282           }
    283           for (EncodedArrayItem encodedArrayItem : encodedArrayItems) {
    284             encodedArrayItem.write(file);
    285           }
    286           break;
    287         case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM:
    288           if (mapItem.size != annotationsDirectoryItems.size()) {
    289             Log.errorAndQuit("MapItem's size " + mapItem.size
    290                 + " no longer matches AnnotationDirectoryItems table size "
    291                 + annotationsDirectoryItems.size());
    292           }
    293           for (AnnotationsDirectoryItem annotationsDirectory : annotationsDirectoryItems) {
    294             annotationsDirectory.write(file);
    295           }
    296           break;
    297         default:
    298           Log.errorAndQuit("Encountered unknown map item in map item list.");
    299       }
    300     }
    301 
    302     file.getOffsetTracker().updateOffsets(file);
    303   }
    304 
    305   /**
    306    * Given a DexRandomAccessFile, calculate the correct adler32 checksum for it.
    307    */
    308   private int calculateAdler32Checksum(DexRandomAccessFile file) throws IOException {
    309     // Skip magic + checksum.
    310     file.seek(12);
    311     int a = 1;
    312     int b = 0;
    313     while (file.getFilePointer() < file.length()) {
    314       a = (a + file.readUnsignedByte()) % 65521;
    315       b = (b + a) % 65521;
    316     }
    317     return (b << 16) | a;
    318   }
    319 
    320   /**
    321    * Given a DexRandomAccessFile, update the file size, data size, and checksum.
    322    */
    323   public void updateHeader(DexRandomAccessFile file) throws IOException {
    324     // File size must be updated before checksum.
    325     int newFileSize = (int) file.length();
    326     file.seek(32);
    327     file.writeUInt(newFileSize);
    328 
    329     // Data size must be updated before checksum.
    330     int newDataSize = newFileSize - header.dataOff.getNewPositionOfItem();
    331     file.seek(104);
    332     file.writeUInt(newDataSize);
    333 
    334     // Now update the checksum.
    335     int newChecksum = calculateAdler32Checksum(file);
    336     file.seek(8);
    337     file.writeUInt(newChecksum);
    338 
    339     header.fileSize = newFileSize;
    340     header.dataSize = newDataSize;
    341     header.checksum = newChecksum;
    342   }
    343 
    344   /**
    345    * This should only be called from NewItemCreator.
    346    */
    347   public OffsetTracker getOffsetTracker() {
    348     return offsetTracker;
    349   }
    350 
    351   @Override
    352   public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
    353     for (TypeIdItem typeId : typeIds) {
    354       typeId.incrementIndex(kind, insertedIdx);
    355     }
    356     for (ProtoIdItem protoId : protoIds) {
    357       protoId.incrementIndex(kind, insertedIdx);
    358     }
    359     for (FieldIdItem fieldId : fieldIds) {
    360       fieldId.incrementIndex(kind, insertedIdx);
    361     }
    362     for (MethodIdItem methodId : methodIds) {
    363       methodId.incrementIndex(kind, insertedIdx);
    364     }
    365     for (ClassDefItem classDef : classDefs) {
    366       classDef.incrementIndex(kind, insertedIdx);
    367     }
    368     for (ClassDataItem classData : classDatas) {
    369       classData.incrementIndex(kind, insertedIdx);
    370     }
    371     if (typeLists != null) {
    372       for (TypeList typeList : typeLists) {
    373         typeList.incrementIndex(kind, insertedIdx);
    374       }
    375     }
    376     for (CodeItem codeItem : codeItems) {
    377       codeItem.incrementIndex(kind, insertedIdx);
    378     }
    379     if (annotationsDirectoryItems != null) {
    380       for (AnnotationsDirectoryItem annotationsDirectoryItem : annotationsDirectoryItems) {
    381         annotationsDirectoryItem.incrementIndex(kind, insertedIdx);
    382       }
    383     }
    384     if (annotationItems != null) {
    385       for (AnnotationItem annotationItem : annotationItems) {
    386         annotationItem.incrementIndex(kind, insertedIdx);
    387       }
    388     }
    389   }
    390 }
    391