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 39 static const char* gProgName = "dexlist"; 40 41 /* command-line args */ 42 static 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 */ 55 static 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 */ 82 static 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 */ 94 void 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 147 skip: 148 free(desc); 149 free(className); 150 } 151 152 /* 153 * Run through all direct and virtual methods in the class. 154 */ 155 void 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 */ 200 int 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 235 bail: 236 if (mapped) 237 sysReleaseShmem(&map); 238 if (pDexFile != NULL) 239 dexFileFree(pDexFile); 240 return result; 241 } 242 243 244 /* 245 * Show usage. 246 */ 247 void 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 */ 257 int 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