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 read(DexRandomAccessFile file)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 write(DexRandomAccessFile file)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 */ calculateAdler32Checksum(DexRandomAccessFile file)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 */ updateHeader(DexRandomAccessFile file)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 */ getOffsetTracker()347 public OffsetTracker getOffsetTracker() { 348 return offsetTracker; 349 } 350 351 @Override incrementIndex(IndexUpdateKind kind, int insertedIdx)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