1/*
2 * Copyright (C) 2008 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/*
18 * List all methods in all concrete classes in one or more DEX files.
19 */
20
21#include "libdex/DexFile.h"
22
23#include "libdex/CmdUtils.h"
24#include "libdex/DexClass.h"
25#include "libdex/DexDebugInfo.h"
26#include "libdex/DexProto.h"
27#include "libdex/SysUtil.h"
28
29#include <stdlib.h>
30#include <stdio.h>
31#include <fcntl.h>
32#include <stddef.h>
33#include <string.h>
34#include <unistd.h>
35#include <getopt.h>
36#include <errno.h>
37#include <assert.h>
38
39static const char* gProgName = "dexlist";
40
41/* command-line args */
42static struct {
43    char*       argCopy;
44    const char* classToFind;
45    const char* methodToFind;
46} gParms;
47
48
49/*
50 * Return a newly-allocated string for the "dot version" of the class
51 * name for the given type descriptor. That is, The initial "L" and
52 * final ";" (if any) have been removed and all occurrences of '/'
53 * have been changed to '.'.
54 */
55static char* descriptorToDot(const char* str)
56{
57    size_t at = strlen(str);
58    char* newStr;
59
60    if (str[0] == 'L') {
61        assert(str[at - 1] == ';');
62        at -= 2; /* Two fewer chars to copy. */
63        str++; /* Skip the 'L'. */
64    }
65
66    newStr = (char*)malloc(at + 1); /* Add one for the '\0'. */
67    newStr[at] = '\0';
68
69    while (at > 0) {
70        at--;
71        newStr[at] = (str[at] == '/') ? '.' : str[at];
72    }
73
74    return newStr;
75}
76
77/*
78 * Position table callback; we just want to catch the number of the
79 * first line in the method, which *should* correspond to the first
80 * entry from the table.  (Could also use "min" here.)
81 */
82static int positionsCallback(void* cnxt, u4 address, u4 lineNum)
83{
84    int* pFirstLine = (int*) cnxt;
85    if (*pFirstLine == -1)
86        *pFirstLine = lineNum;
87    return 0;
88}
89
90
91/*
92 * Dump a method.
93 */
94void dumpMethod(DexFile* pDexFile, const char* fileName,
95    const DexMethod* pDexMethod, int i)
96{
97    const DexMethodId* pMethodId;
98    const DexCode* pCode;
99    const char* classDescriptor;
100    const char* methodName;
101    int firstLine;
102
103    /* abstract and native methods don't get listed */
104    if (pDexMethod->codeOff == 0)
105        return;
106
107    pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
108    methodName = dexStringById(pDexFile, pMethodId->nameIdx);
109
110    classDescriptor = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
111
112    pCode = dexGetCode(pDexFile, pDexMethod);
113    assert(pCode != NULL);
114
115    /*
116     * If the filename is empty, then set it to something printable
117     * so that it is easier to parse.
118     *
119     * TODO: A method may override its class's default source file by
120     * specifying a different one in its debug info. This possibility
121     * should be handled here.
122     */
123    if (fileName == NULL || fileName[0] == 0) {
124        fileName = "(none)";
125    }
126
127    firstLine = -1;
128    dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
129        pDexMethod->accessFlags, positionsCallback, NULL, &firstLine);
130
131    char* className = descriptorToDot(classDescriptor);
132    char* desc = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);
133    u4 insnsOff = pDexMethod->codeOff + offsetof(DexCode, insns);
134
135    if (gParms.methodToFind != NULL &&
136        (strcmp(gParms.classToFind, className) != 0 ||
137         strcmp(gParms.methodToFind, methodName) != 0))
138    {
139        goto skip;
140    }
141
142    printf("0x%08x %d %s %s %s %s %d\n",
143        insnsOff, pCode->insnsSize * 2,
144        className, methodName, desc,
145        fileName, firstLine);
146
147skip:
148    free(desc);
149    free(className);
150}
151
152/*
153 * Run through all direct and virtual methods in the class.
154 */
155void dumpClass(DexFile* pDexFile, int idx)
156{
157    const DexClassDef* pClassDef;
158    DexClassData* pClassData;
159    const u1* pEncodedData;
160    const char* fileName;
161    int i;
162
163    pClassDef = dexGetClassDef(pDexFile, idx);
164    pEncodedData = dexGetClassData(pDexFile, pClassDef);
165    pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
166
167    if (pClassData == NULL) {
168        fprintf(stderr, "Trouble reading class data\n");
169        return;
170    }
171
172    if (pClassDef->sourceFileIdx == 0xffffffff) {
173        fileName = NULL;
174    } else {
175        fileName = dexStringById(pDexFile, pClassDef->sourceFileIdx);
176    }
177
178    /*
179     * TODO: Each class def points at a sourceFile, so maybe that
180     * should be printed out. However, this needs to be coordinated
181     * with the tools that parse this output.
182     */
183
184    for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
185        dumpMethod(pDexFile, fileName, &pClassData->directMethods[i], i);
186    }
187
188    for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
189        dumpMethod(pDexFile, fileName, &pClassData->virtualMethods[i], i);
190    }
191
192    free(pClassData);
193}
194
195/*
196 * Process a file.
197 *
198 * Returns 0 on success.
199 */
200int process(const char* fileName)
201{
202    DexFile* pDexFile = NULL;
203    MemMapping map;
204    bool mapped = false;
205    int result = -1;
206    UnzipToFileResult utfr;
207
208    utfr = dexOpenAndMap(fileName, NULL, &map, true);
209    if (utfr != kUTFRSuccess) {
210        if (utfr == kUTFRNoClassesDex) {
211            /* no classes.dex in the APK; pretend we succeeded */
212            result = 0;
213            goto bail;
214        }
215        fprintf(stderr, "Unable to process '%s'\n", fileName);
216        goto bail;
217    }
218    mapped = true;
219
220    pDexFile = dexFileParse((u1*)map.addr, map.length, kDexParseDefault);
221    if (pDexFile == NULL) {
222        fprintf(stderr, "Warning: DEX parse failed for '%s'\n", fileName);
223        goto bail;
224    }
225
226    printf("#%s\n", fileName);
227
228    int i;
229    for (i = 0; i < (int) pDexFile->pHeader->classDefsSize; i++) {
230        dumpClass(pDexFile, i);
231    }
232
233    result = 0;
234
235bail:
236    if (mapped)
237        sysReleaseShmem(&map);
238    if (pDexFile != NULL)
239        dexFileFree(pDexFile);
240    return result;
241}
242
243
244/*
245 * Show usage.
246 */
247void usage(void)
248{
249    fprintf(stderr, "Copyright (C) 2007 The Android Open Source Project\n\n");
250    fprintf(stderr, "%s: dexfile [dexfile2 ...]\n", gProgName);
251    fprintf(stderr, "\n");
252}
253
254/*
255 * Parse args.
256 */
257int main(int argc, char* const argv[])
258{
259    int result = 0;
260    int i;
261
262    /*
263     * Find all instances of the fully-qualified method name.  This isn't
264     * really what dexlist is for, but it's easy to do it here.
265     */
266    if (argc > 3 && strcmp(argv[1], "--method") == 0) {
267        gParms.argCopy = strdup(argv[2]);
268        char* meth = strrchr(gParms.argCopy, '.');
269        if (meth == NULL) {
270            fprintf(stderr, "Expected package.Class.method\n");
271            free(gParms.argCopy);
272            return 2;
273        }
274        *meth = '\0';
275        gParms.classToFind = gParms.argCopy;
276        gParms.methodToFind = meth+1;
277        argv += 2;
278        argc -= 2;
279    }
280
281    if (argc < 2) {
282        fprintf(stderr, "%s: no file specified\n", gProgName);
283        usage();
284        return 2;
285    }
286
287    /*
288     * Run through the list of files.  If one of them fails we contine on,
289     * only returning a failure at the end.
290     */
291    for (i = 1; i < argc; i++)
292        result |= process(argv[i]);
293
294    free(gParms.argCopy);
295    return result;
296}
297