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