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
17package dexfuzz.rawdex;
18
19import dexfuzz.Log;
20
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.List;
24
25public 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