1 /*
2  * Copyright (C) 2011 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 com.android.dex;
18 
19 import java.io.IOException;
20 import java.io.UnsupportedEncodingException;
21 import java.util.Arrays;
22 
23 /**
24  * The file header and map.
25  */
26 public final class TableOfContents {
27 
28     /*
29      * TODO: factor out ID constants.
30      */
31 
32     public final Section header = new Section(0x0000);
33     public final Section stringIds = new Section(0x0001);
34     public final Section typeIds = new Section(0x0002);
35     public final Section protoIds = new Section(0x0003);
36     public final Section fieldIds = new Section(0x0004);
37     public final Section methodIds = new Section(0x0005);
38     public final Section classDefs = new Section(0x0006);
39     public final Section callSiteIds = new Section(0x0007);
40     public final Section methodHandles = new Section(0x0008);
41     public final Section mapList = new Section(0x1000);
42     public final Section typeLists = new Section(0x1001);
43     public final Section annotationSetRefLists = new Section(0x1002);
44     public final Section annotationSets = new Section(0x1003);
45     public final Section classDatas = new Section(0x2000);
46     public final Section codes = new Section(0x2001);
47     public final Section stringDatas = new Section(0x2002);
48     public final Section debugInfos = new Section(0x2003);
49     public final Section annotations = new Section(0x2004);
50     public final Section encodedArrays = new Section(0x2005);
51     public final Section annotationsDirectories = new Section(0x2006);
52     public final Section[] sections = {
53         header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList, callSiteIds,
54         methodHandles, typeLists, annotationSetRefLists, annotationSets, classDatas, codes,
55         stringDatas, debugInfos, annotations, encodedArrays, annotationsDirectories
56     };
57 
58     public int apiLevel;
59     public int checksum;
60     public byte[] signature;
61     public int fileSize;
62     public int linkSize;
63     public int linkOff;
64     public int dataSize;
65     public int dataOff;
66 
TableOfContents()67     public TableOfContents() {
68         signature = new byte[20];
69     }
70 
readFrom(Dex dex)71     public void readFrom(Dex dex) throws IOException {
72         readHeader(dex.open(0));
73         readMap(dex.open(mapList.off));
74         computeSizesFromOffsets();
75     }
76 
readHeader(Dex.Section headerIn)77     private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException {
78         byte[] magic = headerIn.readByteArray(8);
79 
80         if (!DexFormat.isSupportedDexMagic(magic)) {
81             String msg =
82                     String.format("Unexpected magic: [0x%02x, 0x%02x, 0x%02x, 0x%02x, "
83                                   + "0x%02x, 0x%02x, 0x%02x, 0x%02x]",
84                                   magic[0], magic[1], magic[2], magic[3],
85                                   magic[4], magic[5], magic[6], magic[7]);
86             throw new DexException(msg);
87         }
88 
89         apiLevel = DexFormat.magicToApi(magic);
90         checksum = headerIn.readInt();
91         signature = headerIn.readByteArray(20);
92         fileSize = headerIn.readInt();
93         int headerSize = headerIn.readInt();
94         if (headerSize != SizeOf.HEADER_ITEM) {
95             throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize));
96         }
97         int endianTag = headerIn.readInt();
98         if (endianTag != DexFormat.ENDIAN_TAG) {
99             throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag));
100         }
101         linkSize = headerIn.readInt();
102         linkOff = headerIn.readInt();
103         mapList.off = headerIn.readInt();
104         if (mapList.off == 0) {
105             throw new DexException("Cannot merge dex files that do not contain a map");
106         }
107         stringIds.size = headerIn.readInt();
108         stringIds.off = headerIn.readInt();
109         typeIds.size = headerIn.readInt();
110         typeIds.off = headerIn.readInt();
111         protoIds.size = headerIn.readInt();
112         protoIds.off = headerIn.readInt();
113         fieldIds.size = headerIn.readInt();
114         fieldIds.off = headerIn.readInt();
115         methodIds.size = headerIn.readInt();
116         methodIds.off = headerIn.readInt();
117         classDefs.size = headerIn.readInt();
118         classDefs.off = headerIn.readInt();
119         dataSize = headerIn.readInt();
120         dataOff = headerIn.readInt();
121     }
122 
readMap(Dex.Section in)123     private void readMap(Dex.Section in) throws IOException {
124         int mapSize = in.readInt();
125         Section previous = null;
126         for (int i = 0; i < mapSize; i++) {
127             short type = in.readShort();
128             in.readShort(); // unused
129             Section section = getSection(type);
130             int size = in.readInt();
131             int offset = in.readInt();
132 
133             if ((section.size != 0 && section.size != size)
134                     || (section.off != -1 && section.off != offset)) {
135                 throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type));
136             }
137 
138             section.size = size;
139             section.off = offset;
140 
141             if (previous != null && previous.off > section.off) {
142                 throw new DexException("Map is unsorted at " + previous + ", " + section);
143             }
144 
145             previous = section;
146         }
147         Arrays.sort(sections);
148     }
149 
computeSizesFromOffsets()150     public void computeSizesFromOffsets() {
151         int end = dataOff + dataSize;
152         for (int i = sections.length - 1; i >= 0; i--) {
153             Section section = sections[i];
154             if (section.off == -1) {
155                 continue;
156             }
157             if (section.off > end) {
158                 throw new DexException("Map is unsorted at " + section);
159             }
160             section.byteCount = end - section.off;
161             end = section.off;
162         }
163     }
164 
getSection(short type)165     private Section getSection(short type) {
166         for (Section section : sections) {
167             if (section.type == type) {
168                 return section;
169             }
170         }
171         throw new IllegalArgumentException("No such map item: " + type);
172     }
173 
writeHeader(Dex.Section out, int api)174     public void writeHeader(Dex.Section out, int api) throws IOException {
175         out.write(DexFormat.apiToMagic(api).getBytes("UTF-8"));
176         out.writeInt(checksum);
177         out.write(signature);
178         out.writeInt(fileSize);
179         out.writeInt(SizeOf.HEADER_ITEM);
180         out.writeInt(DexFormat.ENDIAN_TAG);
181         out.writeInt(linkSize);
182         out.writeInt(linkOff);
183         out.writeInt(mapList.off);
184         out.writeInt(stringIds.size);
185         out.writeInt(stringIds.off);
186         out.writeInt(typeIds.size);
187         out.writeInt(typeIds.off);
188         out.writeInt(protoIds.size);
189         out.writeInt(protoIds.off);
190         out.writeInt(fieldIds.size);
191         out.writeInt(fieldIds.off);
192         out.writeInt(methodIds.size);
193         out.writeInt(methodIds.off);
194         out.writeInt(classDefs.size);
195         out.writeInt(classDefs.off);
196         out.writeInt(dataSize);
197         out.writeInt(dataOff);
198     }
199 
writeMap(Dex.Section out)200     public void writeMap(Dex.Section out) throws IOException {
201         int count = 0;
202         for (Section section : sections) {
203             if (section.exists()) {
204                 count++;
205             }
206         }
207 
208         out.writeInt(count);
209         for (Section section : sections) {
210             if (section.exists()) {
211                 out.writeShort(section.type);
212                 out.writeShort((short) 0);
213                 out.writeInt(section.size);
214                 out.writeInt(section.off);
215             }
216         }
217     }
218 
219     public static class Section implements Comparable<Section> {
220         public final short type;
221         public int size = 0;
222         public int off = -1;
223         public int byteCount = 0;
224 
Section(int type)225         public Section(int type) {
226             this.type = (short) type;
227         }
228 
exists()229         public boolean exists() {
230             return size > 0;
231         }
232 
233         @Override
compareTo(Section section)234         public int compareTo(Section section) {
235             if (off != section.off) {
236                 return off < section.off ? -1 : 1;
237             }
238             return 0;
239         }
240 
241         @Override
toString()242         public String toString() {
243             return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size);
244         }
245     }
246 }
247