blob: 6fed7aaf4633950bbb663a59e56730c2dc07a49d [file] [log] [blame]
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001/*
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 */
The Android Open Source Project99409882009-03-18 22:20:24 -070016
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080017/*
18 * The "dexdump" tool is intended to mimic "objdump". When possible, use
19 * similar command-line arguments.
20 *
Andy McFaddena2ee53b2009-05-05 16:52:10 -070021 * TODO: rework the "plain" output format to be more regexp-friendly
22 *
23 * Differences between XML output and the "current.xml" file:
24 * - classes in same package are not all grouped together; generally speaking
25 * nothing is sorted
26 * - no "deprecated" on fields and methods
27 * - no "value" on fields
28 * - no parameter names
29 * - no generic signatures on parameters, e.g. type="java.lang.Class<?>"
30 * - class shows declared fields and methods; does not show inherited fields
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080031 */
32#include "libdex/DexFile.h"
33#include "libdex/DexCatch.h"
34#include "libdex/DexClass.h"
35#include "libdex/DexProto.h"
36#include "libdex/InstrUtils.h"
37#include "libdex/SysUtil.h"
38#include "libdex/CmdUtils.h"
39
40#include "dexdump/OpCodeNames.h"
41
42#include <stdlib.h>
43#include <stdio.h>
44#include <fcntl.h>
45#include <string.h>
46#include <unistd.h>
47#include <getopt.h>
48#include <errno.h>
49#include <assert.h>
50
51static const char* gProgName = "dexdump";
52
53static InstructionWidth* gInstrWidth;
54static InstructionFormat* gInstrFormat;
55
Andy McFaddena2ee53b2009-05-05 16:52:10 -070056typedef enum OutputFormat {
57 OUTPUT_PLAIN = 0, /* default */
58 OUTPUT_XML, /* fancy */
59} OutputFormat;
60
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080061/* command-line options */
62struct {
Andy McFadden0198b142009-04-02 14:48:56 -070063 bool checksumOnly;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080064 bool disassemble;
65 bool showFileHeaders;
66 bool showSectionHeaders;
Andy McFadden2124cb82009-03-25 15:37:39 -070067 bool ignoreBadChecksum;
The Android Open Source Project99409882009-03-18 22:20:24 -070068 bool dumpRegisterMaps;
Andy McFaddena2ee53b2009-05-05 16:52:10 -070069 OutputFormat outputFormat;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080070 const char* tempFileName;
Andy McFaddena2ee53b2009-05-05 16:52:10 -070071 bool exportsOnly;
72 bool verbose;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -080073} gOptions;
74
75/* basic info about a field or method */
76typedef struct FieldMethodInfo {
77 const char* classDescriptor;
78 const char* name;
79 const char* signature;
80} FieldMethodInfo;
81
82/*
83 * Get 2 little-endian bytes.
84 */
85static inline u2 get2LE(unsigned char const* pSrc)
86{
87 return pSrc[0] | (pSrc[1] << 8);
88}
89
90/*
The Android Open Source Project99409882009-03-18 22:20:24 -070091 * Get 4 little-endian bytes.
92 */
93static inline u4 get4LE(unsigned char const* pSrc)
94{
95 return pSrc[0] | (pSrc[1] << 8) | (pSrc[2] << 16) | (pSrc[3] << 24);
96}
97
98/*
Andy McFaddena2ee53b2009-05-05 16:52:10 -070099 * Converts a single-character primitive type into its human-readable
100 * equivalent.
101 */
102static const char* primitiveTypeLabel(char typeChar)
103{
104 switch (typeChar) {
105 case 'B': return "byte";
106 case 'C': return "char";
107 case 'D': return "double";
108 case 'F': return "float";
109 case 'I': return "int";
110 case 'J': return "long";
111 case 'S': return "short";
112 case 'V': return "void";
113 case 'Z': return "boolean";
114 default:
115 return "UNKNOWN";
116 }
117}
118
119/*
120 * Converts a type descriptor to human-readable "dotted" form. For
121 * example, "Ljava/lang/String;" becomes "java.lang.String", and
122 * "[I" becomes "int[]". Also converts '$' to '.', which means this
123 * form can't be converted back to a descriptor.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800124 */
125static char* descriptorToDot(const char* str)
126{
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700127 int targetLen = strlen(str);
128 int offset = 0;
129 int arrayDepth = 0;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800130 char* newStr;
131
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700132 /* strip leading [s; will be added to end */
133 while (targetLen > 1 && str[offset] == '[') {
134 offset++;
135 targetLen--;
136 }
137 arrayDepth = offset;
138
139 if (targetLen == 1) {
140 /* primitive type */
141 str = primitiveTypeLabel(str[offset]);
142 offset = 0;
143 targetLen = strlen(str);
144 } else {
145 /* account for leading 'L' and trailing ';' */
146 if (targetLen >= 2 && str[offset] == 'L' &&
147 str[offset+targetLen-1] == ';')
148 {
149 targetLen -= 2;
150 offset++;
151 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800152 }
153
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700154 newStr = malloc(targetLen + arrayDepth * 2 +1);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800155
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700156 /* copy class name over */
157 int i;
158 for (i = 0; i < targetLen; i++) {
159 char ch = str[offset + i];
160 newStr[i] = (ch == '/' || ch == '$') ? '.' : ch;
161 }
162
163 /* add the appropriate number of brackets for arrays */
164 while (arrayDepth-- > 0) {
165 newStr[i++] = '[';
166 newStr[i++] = ']';
167 }
168 newStr[i] = '\0';
169 assert(i == targetLen + arrayDepth * 2);
170
171 return newStr;
172}
173
174/*
175 * Converts the class name portion of a type descriptor to human-readable
176 * "dotted" form.
177 *
178 * Returns a newly-allocated string.
179 */
180static char* descriptorClassToDot(const char* str)
181{
182 const char* lastSlash;
183 char* newStr;
184 char* cp;
185
186 /* reduce to just the class name, trimming trailing ';' */
187 lastSlash = strrchr(str, '/');
188 if (lastSlash == NULL)
189 lastSlash = str + 1; /* start past 'L' */
190 else
191 lastSlash++; /* start past '/' */
192
193 newStr = strdup(lastSlash);
194 newStr[strlen(lastSlash)-1] = '\0';
195 for (cp = newStr; *cp != '\0'; cp++) {
196 if (*cp == '$')
197 *cp = '.';
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800198 }
199
200 return newStr;
201}
202
203/*
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700204 * Returns a quoted string representing the boolean value.
205 */
206static const char* quotedBool(bool val)
207{
208 if (val)
209 return "\"true\"";
210 else
211 return "\"false\"";
212}
213
214static const char* quotedVisibility(u4 accessFlags)
215{
216 if ((accessFlags & ACC_PUBLIC) != 0)
217 return "\"public\"";
218 else if ((accessFlags & ACC_PROTECTED) != 0)
219 return "\"protected\"";
220 else if ((accessFlags & ACC_PRIVATE) != 0)
221 return "\"private\"";
222 else
223 return "\"package\"";
224}
225
226/*
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800227 * Count the number of '1' bits in a word.
228 *
229 * Having completed this, I'm ready for an interview at Google.
230 *
231 * TODO? there's a parallel version w/o loops. Performance not currently
232 * important.
233 */
234static int countOnes(u4 val)
235{
236 int count = 0;
237
238 while (val != 0) {
239 val &= val-1;
240 count++;
241 }
242
243 return count;
244}
245
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800246/*
247 * Flag for use with createAccessFlagStr().
248 */
249typedef enum AccessFor {
250 kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2,
251 kAccessForMAX
252} AccessFor;
253
254/*
255 * Create a new string with human-readable access flags.
256 *
257 * In the base language the access_flags fields are type u2; in Dalvik
258 * they're u4.
259 */
260static char* createAccessFlagStr(u4 flags, AccessFor forWhat)
261{
262#define NUM_FLAGS 18
263 static const char* kAccessStrings[kAccessForMAX][NUM_FLAGS] = {
264 {
265 /* class, inner class */
266 "PUBLIC", /* 0x0001 */
267 "PRIVATE", /* 0x0002 */
268 "PROTECTED", /* 0x0004 */
269 "STATIC", /* 0x0008 */
270 "FINAL", /* 0x0010 */
271 "?", /* 0x0020 */
272 "?", /* 0x0040 */
273 "?", /* 0x0080 */
274 "?", /* 0x0100 */
275 "INTERFACE", /* 0x0200 */
276 "ABSTRACT", /* 0x0400 */
277 "?", /* 0x0800 */
278 "SYNTHETIC", /* 0x1000 */
279 "ANNOTATION", /* 0x2000 */
280 "ENUM", /* 0x4000 */
281 "?", /* 0x8000 */
282 "VERIFIED", /* 0x10000 */
283 "OPTIMIZED", /* 0x20000 */
284 },
285 {
286 /* method */
287 "PUBLIC", /* 0x0001 */
288 "PRIVATE", /* 0x0002 */
289 "PROTECTED", /* 0x0004 */
290 "STATIC", /* 0x0008 */
291 "FINAL", /* 0x0010 */
292 "SYNCHRONIZED", /* 0x0020 */
293 "BRIDGE", /* 0x0040 */
294 "VARARGS", /* 0x0080 */
295 "NATIVE", /* 0x0100 */
296 "?", /* 0x0200 */
297 "ABSTRACT", /* 0x0400 */
298 "STRICT", /* 0x0800 */
299 "SYNTHETIC", /* 0x1000 */
300 "?", /* 0x2000 */
301 "?", /* 0x4000 */
302 "MIRANDA", /* 0x8000 */
303 "CONSTRUCTOR", /* 0x10000 */
304 "DECLARED_SYNCHRONIZED", /* 0x20000 */
305 },
306 {
307 /* field */
308 "PUBLIC", /* 0x0001 */
309 "PRIVATE", /* 0x0002 */
310 "PROTECTED", /* 0x0004 */
311 "STATIC", /* 0x0008 */
312 "FINAL", /* 0x0010 */
313 "?", /* 0x0020 */
314 "VOLATILE", /* 0x0040 */
315 "TRANSIENT", /* 0x0080 */
316 "?", /* 0x0100 */
317 "?", /* 0x0200 */
318 "?", /* 0x0400 */
319 "?", /* 0x0800 */
320 "SYNTHETIC", /* 0x1000 */
321 "?", /* 0x2000 */
322 "ENUM", /* 0x4000 */
323 "?", /* 0x8000 */
324 "?", /* 0x10000 */
325 "?", /* 0x20000 */
326 },
327 };
328 const int kLongest = 21; /* strlen of longest string above */
329 int i, count;
330 char* str;
331 char* cp;
332
333 /*
334 * Allocate enough storage to hold the expected number of strings,
335 * plus a space between each. We over-allocate, using the longest
336 * string above as the base metric.
337 */
338 count = countOnes(flags);
339 cp = str = (char*) malloc(count * (kLongest+1) +1);
340
341 for (i = 0; i < NUM_FLAGS; i++) {
342 if (flags & 0x01) {
343 const char* accessStr = kAccessStrings[forWhat][i];
344 int len = strlen(accessStr);
345 if (cp != str)
346 *cp++ = ' ';
347
348 memcpy(cp, accessStr, len);
349 cp += len;
350 }
351 flags >>= 1;
352 }
353 *cp = '\0';
354
355 return str;
356}
357
358
359/*
360 * Dump the file header.
361 */
362void dumpFileHeader(const DexFile* pDexFile)
363{
364 const DexHeader* pHeader = pDexFile->pHeader;
365
366 printf("DEX file header:\n");
367 printf("magic : '%.8s'\n", pHeader->magic);
368 printf("checksum : %08x\n", pHeader->checksum);
369 printf("signature : %02x%02x...%02x%02x\n",
370 pHeader->signature[0], pHeader->signature[1],
371 pHeader->signature[kSHA1DigestLen-2],
372 pHeader->signature[kSHA1DigestLen-1]);
373 printf("file_size : %d\n", pHeader->fileSize);
374 printf("header_size : %d\n", pHeader->headerSize);
375 printf("link_size : %d\n", pHeader->linkSize);
376 printf("link_off : %d (0x%06x)\n",
377 pHeader->linkOff, pHeader->linkOff);
378 printf("string_ids_size : %d\n", pHeader->stringIdsSize);
379 printf("string_ids_off : %d (0x%06x)\n",
380 pHeader->stringIdsOff, pHeader->stringIdsOff);
381 printf("type_ids_size : %d\n", pHeader->typeIdsSize);
382 printf("type_ids_off : %d (0x%06x)\n",
383 pHeader->typeIdsOff, pHeader->typeIdsOff);
384 printf("field_ids_size : %d\n", pHeader->fieldIdsSize);
385 printf("field_ids_off : %d (0x%06x)\n",
386 pHeader->fieldIdsOff, pHeader->fieldIdsOff);
387 printf("method_ids_size : %d\n", pHeader->methodIdsSize);
388 printf("method_ids_off : %d (0x%06x)\n",
389 pHeader->methodIdsOff, pHeader->methodIdsOff);
390 printf("class_defs_size : %d\n", pHeader->classDefsSize);
391 printf("class_defs_off : %d (0x%06x)\n",
392 pHeader->classDefsOff, pHeader->classDefsOff);
393 printf("data_size : %d\n", pHeader->dataSize);
394 printf("data_off : %d (0x%06x)\n",
395 pHeader->dataOff, pHeader->dataOff);
396 printf("\n");
397}
398
399/*
400 * Dump a class_def_item.
401 */
402void dumpClassDef(DexFile* pDexFile, int idx)
403{
404 const DexClassDef* pClassDef;
405 const u1* pEncodedData;
406 DexClassData* pClassData;
407
408 pClassDef = dexGetClassDef(pDexFile, idx);
409 pEncodedData = dexGetClassData(pDexFile, pClassDef);
410 pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
411
412 if (pClassData == NULL) {
413 fprintf(stderr, "Trouble reading class data\n");
414 return;
415 }
416
417 printf("Class #%d header:\n", idx);
418 printf("class_idx : %d\n", pClassDef->classIdx);
419 printf("access_flags : %d (0x%04x)\n",
420 pClassDef->accessFlags, pClassDef->accessFlags);
421 printf("superclass_idx : %d\n", pClassDef->superclassIdx);
422 printf("interfaces_off : %d (0x%06x)\n",
423 pClassDef->interfacesOff, pClassDef->interfacesOff);
424 printf("source_file_idx : %d\n", pClassDef->sourceFileIdx);
425 printf("annotations_off : %d (0x%06x)\n",
426 pClassDef->annotationsOff, pClassDef->annotationsOff);
427 printf("class_data_off : %d (0x%06x)\n",
428 pClassDef->classDataOff, pClassDef->classDataOff);
429 printf("static_fields_size : %d\n", pClassData->header.staticFieldsSize);
430 printf("instance_fields_size: %d\n",
431 pClassData->header.instanceFieldsSize);
432 printf("direct_methods_size : %d\n", pClassData->header.directMethodsSize);
433 printf("virtual_methods_size: %d\n",
434 pClassData->header.virtualMethodsSize);
435 printf("\n");
436
437 free(pClassData);
438}
439
440/*
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700441 * Dump an interface that a class declares to implement.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800442 */
443void dumpInterface(const DexFile* pDexFile, const DexTypeItem* pTypeItem,
444 int i)
445{
446 const char* interfaceName =
447 dexStringByTypeIdx(pDexFile, pTypeItem->typeIdx);
448
Andy McFaddena2ee53b2009-05-05 16:52:10 -0700449 if (gOptions.outputFormat == OUTPUT_PLAIN) {
450 printf(" #%d : '%s'\n", i, interfaceName);
451 } else {
452 char* dotted = descriptorToDot(interfaceName);
453 printf("<implements name=\"%s\">\n</implements>\n", dotted);
454 free(dotted);
455 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800456}
457
458/*
459 * Dump the catches table associated with the code.
460 */
461void dumpCatches(DexFile* pDexFile, const DexCode* pCode)
462{
463 u4 triesSize = pCode->triesSize;
464
465 if (triesSize == 0) {
466 printf(" catches : (none)\n");
467 return;
468 }
469
470 printf(" catches : %d\n", triesSize);
471
472 const DexTry* pTries = dexGetTries(pCode);
473 u4 i;
474
475 for (i = 0; i < triesSize; i++) {
476 const DexTry* pTry = &pTries[i];
477 u4 start = pTry->startAddr;
478 u4 end = start + pTry->insnCount;
479 DexCatchIterator iterator;
480
481 printf(" 0x%04x - 0x%04x\n", start, end);
482
483 dexCatchIteratorInit(&iterator, pCode, pTry->handlerOff);
484
485 for (;;) {
486 DexCatchHandler* handler = dexCatchIteratorNext(&iterator);
487 const char* descriptor;
488
489 if (handler == NULL) {
490 break;
491 }
492
493 descriptor = (handler->typeIdx == kDexNoIndex) ? "<any>" :
494 dexStringByTypeIdx(pDexFile, handler->typeIdx);
495
496 printf(" %s -> 0x%04x\n", descriptor,
497 handler->address);
498 }
499 }
500}
501
502static int dumpPositionsCb(void *cnxt, u4 address, u4 lineNum)
503{
504 printf(" 0x%04x line=%d\n", address, lineNum);
505 return 0;
506}
507
508/*
509 * Dump the positions list.
510 */
511void dumpPositions(DexFile* pDexFile, const DexCode* pCode,
512 const DexMethod *pDexMethod)
513{
514 printf(" positions : \n");
515 const DexMethodId *pMethodId
516 = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
517 const char *classDescriptor
518 = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
519
520 dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
521 pDexMethod->accessFlags, dumpPositionsCb, NULL, NULL);
522}
523
524static void dumpLocalsCb(void *cnxt, u2 reg, u4 startAddress,
525 u4 endAddress, const char *name, const char *descriptor,
526 const char *signature)
527{
528 printf(" 0x%04x - 0x%04x reg=%d %s %s %s\n",
529 startAddress, endAddress, reg, name, descriptor,
530 signature);
531}
532
533/*
534 * Dump the locals list.
535 */
536void dumpLocals(DexFile* pDexFile, const DexCode* pCode,
537 const DexMethod *pDexMethod)
538{
539 printf(" locals : \n");
540
541 const DexMethodId *pMethodId
542 = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
543 const char *classDescriptor
544 = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
545
546 dexDecodeDebugInfo(pDexFile, pCode, classDescriptor, pMethodId->protoIdx,
547 pDexMethod->accessFlags, NULL, dumpLocalsCb, NULL);
548}
549
550/*
551 * Get information about a method.
552 */
553bool getMethodInfo(DexFile* pDexFile, u4 methodIdx, FieldMethodInfo* pMethInfo)
554{
555 const DexMethodId* pMethodId;
556
557 if (methodIdx >= pDexFile->pHeader->methodIdsSize)
558 return false;
559
560 pMethodId = dexGetMethodId(pDexFile, methodIdx);
561 pMethInfo->name = dexStringById(pDexFile, pMethodId->nameIdx);
562 pMethInfo->signature = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);
563
564 pMethInfo->classDescriptor =
565 dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
566 return true;
567}
568
569/*
570 * Get information about a field.
571 */
572bool getFieldInfo(DexFile* pDexFile, u4 fieldIdx, FieldMethodInfo* pFieldInfo)
573{
574 const DexFieldId* pFieldId;
575
576 if (fieldIdx >= pDexFile->pHeader->fieldIdsSize)
577 return false;
578
579 pFieldId = dexGetFieldId(pDexFile, fieldIdx);
580 pFieldInfo->name = dexStringById(pDexFile, pFieldId->nameIdx);
581 pFieldInfo->signature = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
582 pFieldInfo->classDescriptor =
583 dexStringByTypeIdx(pDexFile, pFieldId->classIdx);
584 return true;
585}
586
587
588/*
589 * Look up a class' descriptor.
590 */
591const char* getClassDescriptor(DexFile* pDexFile, u4 classIdx)
592{
593 return dexStringByTypeIdx(pDexFile, classIdx);
594}
595
596/*
597 * Dump a single instruction.
598 */
599void dumpInstruction(DexFile* pDexFile, const DexCode* pCode, int insnIdx,
600 int insnWidth, const DecodedInstruction* pDecInsn)
601{
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800602 const u2* insns = pCode->insns;
603 int i;
604
605 printf("%06x:", ((u1*)insns - pDexFile->baseAddr) + insnIdx*2);
606 for (i = 0; i < 8; i++) {
607 if (i < insnWidth) {
608 if (i == 7) {
609 printf(" ... ");
610 } else {
611 /* print 16-bit value in little-endian order */
612 const u1* bytePtr = (const u1*) &insns[insnIdx+i];
613 printf(" %02x%02x", bytePtr[0], bytePtr[1]);
614 }
615 } else {
616 fputs(" ", stdout);
617 }
618 }
619
620 if (pDecInsn->opCode == OP_NOP) {
621 u2 instr = get2LE((const u1*) &insns[insnIdx]);
622 if (instr == kPackedSwitchSignature) {
623 printf("|%04x: packed-switch-data (%d units)",
624 insnIdx, insnWidth);
625 } else if (instr == kSparseSwitchSignature) {
626 printf("|%04x: sparse-switch-data (%d units)",
627 insnIdx, insnWidth);
628 } else if (instr == kArrayDataSignature) {
629 printf("|%04x: array-data (%d units)",
630 insnIdx, insnWidth);
631 } else {
632 printf("|%04x: nop // spacer", insnIdx);
633 }
634 } else {
635 printf("|%04x: %s", insnIdx, getOpcodeName(pDecInsn->opCode));
636 }
637
638 switch (dexGetInstrFormat(gInstrFormat, pDecInsn->opCode)) {
639 case kFmt10x: // op
640 break;
641 case kFmt12x: // op vA, vB
642 printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
643 break;
644 case kFmt11n: // op vA, #+B
645 printf(" v%d, #int %d // #%x",
646 pDecInsn->vA, (s4)pDecInsn->vB, (u1)pDecInsn->vB);
647 break;
648 case kFmt11x: // op vAA
649 printf(" v%d", pDecInsn->vA);
650 break;
651 case kFmt10t: // op +AA
652 case kFmt20t: // op +AAAA
653 {
654 s4 targ = (s4) pDecInsn->vA;
655 printf(" %04x // %c%04x",
656 insnIdx + targ,
657 (targ < 0) ? '-' : '+',
658 (targ < 0) ? -targ : targ);
659 }
660 break;
661 case kFmt22x: // op vAA, vBBBB
662 printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
663 break;
664 case kFmt21t: // op vAA, +BBBB
665 {
666 s4 targ = (s4) pDecInsn->vB;
667 printf(" v%d, %04x // %c%04x", pDecInsn->vA,
668 insnIdx + targ,
669 (targ < 0) ? '-' : '+',
670 (targ < 0) ? -targ : targ);
671 }
672 break;
673 case kFmt21s: // op vAA, #+BBBB
674 printf(" v%d, #int %d // #%x",
675 pDecInsn->vA, (s4)pDecInsn->vB, (u2)pDecInsn->vB);
676 break;
677 case kFmt21h: // op vAA, #+BBBB0000[00000000]
678 // The printed format varies a bit based on the actual opcode.
679 if (pDecInsn->opCode == OP_CONST_HIGH16) {
680 s4 value = pDecInsn->vB << 16;
681 printf(" v%d, #int %d // #%x",
682 pDecInsn->vA, value, (u2)pDecInsn->vB);
683 } else {
684 s8 value = ((s8) pDecInsn->vB) << 48;
685 printf(" v%d, #long %lld // #%x",
686 pDecInsn->vA, value, (u2)pDecInsn->vB);
687 }
688 break;
689 case kFmt21c: // op vAA, thing@BBBB
690 if (pDecInsn->opCode == OP_CONST_STRING) {
691 printf(" v%d, \"%s\" // string@%04x", pDecInsn->vA,
692 dexStringById(pDexFile, pDecInsn->vB), pDecInsn->vB);
693 } else if (pDecInsn->opCode == OP_CHECK_CAST ||
694 pDecInsn->opCode == OP_NEW_INSTANCE ||
695 pDecInsn->opCode == OP_CONST_CLASS)
696 {
697 printf(" v%d, %s // class@%04x", pDecInsn->vA,
698 getClassDescriptor(pDexFile, pDecInsn->vB), pDecInsn->vB);
699 } else /* OP_SGET* */ {
700 FieldMethodInfo fieldInfo;
701 if (getFieldInfo(pDexFile, pDecInsn->vB, &fieldInfo)) {
702 printf(" v%d, %s.%s:%s // field@%04x", pDecInsn->vA,
703 fieldInfo.classDescriptor, fieldInfo.name,
704 fieldInfo.signature, pDecInsn->vB);
705 } else {
706 printf(" v%d, ??? // field@%04x", pDecInsn->vA, pDecInsn->vB);
707 }
708 }
709 break;
710 case kFmt23x: // op vAA, vBB, vCC
711 printf(" v%d, v%d, v%d", pDecInsn->vA, pDecInsn->vB, pDecInsn->vC);
712 break;
713 case kFmt22b: // op vAA, vBB, #+CC
714 printf(" v%d, v%d, #int %d // #%02x",
715 pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u1)pDecInsn->vC);
716 break;
717 case kFmt22t: // op vA, vB, +CCCC
718 {
719 s4 targ = (s4) pDecInsn->vC;
720 printf(" v%d, v%d, %04x // %c%04x", pDecInsn->vA, pDecInsn->vB,
721 insnIdx + targ,
722 (targ < 0) ? '-' : '+',
723 (targ < 0) ? -targ : targ);
724 }
725 break;
726 case kFmt22s: // op vA, vB, #+CCCC
727 printf(" v%d, v%d, #int %d // #%04x",
728 pDecInsn->vA, pDecInsn->vB, (s4)pDecInsn->vC, (u2)pDecInsn->vC);
729 break;
730 case kFmt22c: // op vA, vB, thing@CCCC
731 if (pDecInsn->opCode >= OP_IGET && pDecInsn->opCode <= OP_IPUT_SHORT) {
732 FieldMethodInfo fieldInfo;
733 if (getFieldInfo(pDexFile, pDecInsn->vC, &fieldInfo)) {
734 printf(" v%d, v%d, %s.%s:%s // field@%04x", pDecInsn->vA,
735 pDecInsn->vB, fieldInfo.classDescriptor, fieldInfo.name,
736 fieldInfo.signature, pDecInsn->vC);
737 } else {
738 printf(" v%d, v%d, ??? // field@%04x", pDecInsn->vA,
739 pDecInsn->vB, pDecInsn->vC);
740 }
741 } else {
742 printf(" v%d, v%d, %s // class@%04x",
743 pDecInsn->vA, pDecInsn->vB,
744 getClassDescriptor(pDexFile, pDecInsn->vC), pDecInsn->vC);
745 }
746 break;
747 case kFmt22cs: // [opt] op vA, vB, field offset CCCC
748 printf(" v%d, v%d, [obj+%04x]",
749 pDecInsn->vA, pDecInsn->vB, pDecInsn->vC);
750 break;
751 case kFmt30t:
752 printf(" #%08x", pDecInsn->vA);
753 break;
754 case kFmt31i: // op vAA, #+BBBBBBBB
755 {
756 /* this is often, but not always, a float */
757 union {
758 float f;
759 u4 i;
760 } conv;
761 conv.i = pDecInsn->vB;
762 printf(" v%d, #float %f // #%08x",
763 pDecInsn->vA, conv.f, pDecInsn->vB);
764 }
765 break;
766 case kFmt31c: // op vAA, thing@BBBBBBBB
767 printf(" v%d, \"%s\" // string@%08x", pDecInsn->vA,
768 dexStringById(pDexFile, pDecInsn->vB), pDecInsn->vB);
769 break;
770 case kFmt31t: // op vAA, offset +BBBBBBBB
771 printf(" v%d, %08x // +%08x",
772 pDecInsn->vA, insnIdx + pDecInsn->vB, pDecInsn->vB);
773 break;
774 case kFmt32x: // op vAAAA, vBBBB
775 printf(" v%d, v%d", pDecInsn->vA, pDecInsn->vB);
776 break;
777 case kFmt35c: // op vB, {vD, vE, vF, vG, vA}, thing@CCCC
778 {
779 /* NOTE: decoding of 35c doesn't quite match spec */
780 fputs(" {", stdout);
781 for (i = 0; i < (int) pDecInsn->vA; i++) {
782 if (i == 0)
783 printf("v%d", pDecInsn->arg[i]);
784 else
785 printf(", v%d", pDecInsn->arg[i]);
786 }
787 if (pDecInsn->opCode == OP_FILLED_NEW_ARRAY) {
788 printf("}, %s // class@%04x",
789 getClassDescriptor(pDexFile, pDecInsn->vB), pDecInsn->vB);
790 } else {
791 FieldMethodInfo methInfo;
792 if (getMethodInfo(pDexFile, pDecInsn->vB, &methInfo)) {
793 printf("}, %s.%s:%s // method@%04x",
794 methInfo.classDescriptor, methInfo.name,
795 methInfo.signature, pDecInsn->vB);
796 } else {
797 printf("}, ??? // method@%04x", pDecInsn->vB);
798 }
799 }
800 }
801 break;
802 case kFmt35ms: // [opt] invoke-virtual+super
803 case kFmt35fs: // [opt] invoke-interface
804 {
805 fputs(" {", stdout);
806 for (i = 0; i < (int) pDecInsn->vA; i++) {
807 if (i == 0)
808 printf("v%d", pDecInsn->arg[i]);
809 else
810 printf(", v%d", pDecInsn->arg[i]);
811 }
812 printf("}, [%04x] // vtable #%04x", pDecInsn->vB, pDecInsn->vB);
813 }
814 break;
815 case kFmt3rc: // op {vCCCC .. v(CCCC+AA-1)}, meth@BBBB
816 {
817 /*
818 * This doesn't match the "dx" output when some of the args are
819 * 64-bit values -- dx only shows the first register.
820 */
821 fputs(" {", stdout);
822 for (i = 0; i < (int) pDecInsn->vA; i++) {
823 if (i == 0)
824 printf("v%d", pDecInsn->vC + i);
825 else
826 printf(", v%d", pDecInsn->vC + i);
827 }
828 if (pDecInsn->opCode == OP_FILLED_NEW_ARRAY_RANGE) {
829 printf("}, %s // class@%04x",
830 getClassDescriptor(pDexFile, pDecInsn->vB), pDecInsn->vB);
831 } else {
832 FieldMethodInfo methInfo;
833 if (getMethodInfo(pDexFile, pDecInsn->vB, &methInfo)) {
834 printf("}, %s.%s:%s // method@%04x",
835 methInfo.classDescriptor, methInfo.name,
836 methInfo.signature, pDecInsn->vB);
837 } else {
838 printf("}, ??? // method@%04x", pDecInsn->vB);
839 }
840 }
841 }
842 break;
843 case kFmt3rms: // [opt] invoke-virtual+super/range
844 case kFmt3rfs: // [opt] invoke-interface/range
845 {
846 /*
847 * This doesn't match the "dx" output when some of the args are
848 * 64-bit values -- dx only shows the first register.
849 */
850 fputs(" {", stdout);
851 for (i = 0; i < (int) pDecInsn->vA; i++) {
852 if (i == 0)
853 printf("v%d", pDecInsn->vC + i);
854 else
855 printf(", v%d", pDecInsn->vC + i);
856 }
857 printf("}, [%04x] // vtable #%04x", pDecInsn->vB, pDecInsn->vB);
858 }
859 break;
860 case kFmt3inline: // [opt] inline invoke
861 {
862#if 0
863 const InlineOperation* inlineOpsTable = dvmGetInlineOpsTable();
864 u4 tableLen = dvmGetInlineOpsTableLength();
865#endif
866
867 fputs(" {", stdout);
868 for (i = 0; i < (int) pDecInsn->vA; i++) {
869 if (i == 0)
870 printf("v%d", pDecInsn->arg[i]);
871 else
872 printf(", v%d", pDecInsn->arg[i]);
873 }
874#if 0
875 if (pDecInsn->vB < tableLen) {
876 printf("}, %s.%s:%s // inline #%04x",
877 inlineOpsTable[pDecInsn->vB].classDescriptor,
878 inlineOpsTable[pDecInsn->vB].methodName,
879 inlineOpsTable[pDecInsn->vB].methodSignature,
880 pDecInsn->vB);
881 } else {
882#endif
883 printf("}, [%04x] // inline #%04x", pDecInsn->vB, pDecInsn->vB);
884#if 0
885 }
886#endif
887 }
888 break;
889 case kFmt51l: // op vAA, #+BBBBBBBBBBBBBBBB
890 {
891 /* this is often, but not always, a double */
892 union {
893 double d;
894 u8 j;
895 } conv;
896 conv.j = pDecInsn->vB_wide;
897 printf(" v%d, #double %f // #%016llx",
898 pDecInsn->vA, conv.d, pDecInsn->vB_wide);
899 }
900 break;
901 case kFmtUnknown:
902 break;
903 default:
904 printf(" ???");
905 break;
906 }
907
908
909 putchar('\n');
910
911}
912
913/*
914 * Dump a bytecode disassembly.
915 */
916void dumpBytecodes(DexFile* pDexFile, const DexMethod* pDexMethod)
917{
918 const DexCode* pCode = dexGetCode(pDexFile, pDexMethod);
919 const u2* insns;
920 int insnIdx;
921 FieldMethodInfo methInfo;
922 int startAddr;
923 char* className = NULL;
924
925 assert(pCode->insnsSize > 0);
926 insns = pCode->insns;
927
928 getMethodInfo(pDexFile, pDexMethod->methodIdx, &methInfo);
929 startAddr = ((u1*)pCode - pDexFile->baseAddr);
930 className = descriptorToDot(methInfo.classDescriptor);
931
932 printf("%06x: |[%06x] %s.%s:%s\n",
933 startAddr, startAddr,
934 className, methInfo.name, methInfo.signature);
935
936 insnIdx = 0;
937 while (insnIdx < (int) pCode->insnsSize) {
938 int insnWidth;
939 OpCode opCode;
940 DecodedInstruction decInsn;
941 u2 instr;
942
943 instr = get2LE((const u1*)insns);
944 if (instr == kPackedSwitchSignature) {
945 insnWidth = 4 + get2LE((const u1*)(insns+1)) * 2;
946 } else if (instr == kSparseSwitchSignature) {
947 insnWidth = 2 + get2LE((const u1*)(insns+1)) * 4;
948 } else if (instr == kArrayDataSignature) {
949 int width = get2LE((const u1*)(insns+1));
950 int size = get2LE((const u1*)(insns+2)) |
951 (get2LE((const u1*)(insns+3))<<16);
952 // The plus 1 is to round up for odd size and width
953 insnWidth = 4 + ((size * width) + 1) / 2;
954 } else {
955 opCode = instr & 0xff;
956 insnWidth = dexGetInstrWidthAbs(gInstrWidth, opCode);
957 if (insnWidth == 0) {
958 fprintf(stderr,
959 "GLITCH: zero-width instruction at idx=0x%04x\n", insnIdx);
960 break;
961 }
962 }
963
964 dexDecodeInstruction(gInstrFormat, insns, &decInsn);
965 dumpInstruction(pDexFile, pCode, insnIdx, insnWidth, &decInsn);
966
967 insns += insnWidth;
968 insnIdx += insnWidth;
969 }
970
971 free(className);
972}
973
974/*
975 * Dump a "code" struct.
976 */
977void dumpCode(DexFile* pDexFile, const DexMethod* pDexMethod)
978{
979 const DexCode* pCode = dexGetCode(pDexFile, pDexMethod);
980
981 printf(" registers : %d\n", pCode->registersSize);
982 printf(" ins : %d\n", pCode->insSize);
983 printf(" outs : %d\n", pCode->outsSize);
984 printf(" insns size : %d 16-bit code units\n", pCode->insnsSize);
985
986 if (gOptions.disassemble)
987 dumpBytecodes(pDexFile, pDexMethod);
988
989 dumpCatches(pDexFile, pCode);
990 /* both of these are encoded in debug info */
991 dumpPositions(pDexFile, pCode, pDexMethod);
992 dumpLocals(pDexFile, pCode, pDexMethod);
993}
994
995/*
996 * Dump a method.
997 */
998void dumpMethod(DexFile* pDexFile, const DexMethod* pDexMethod, int i)
999{
1000 const DexMethodId* pMethodId;
1001 const char* backDescriptor;
1002 const char* name;
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001003 char* typeDescriptor = NULL;
1004 char* accessStr = NULL;
1005
1006 if (gOptions.exportsOnly &&
1007 (pDexMethod->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0)
1008 {
1009 return;
1010 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001011
1012 pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
1013 name = dexStringById(pDexFile, pMethodId->nameIdx);
1014 typeDescriptor = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);
1015
1016 backDescriptor = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
1017
1018 accessStr = createAccessFlagStr(pDexMethod->accessFlags,
1019 kAccessForMethod);
1020
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001021 if (gOptions.outputFormat == OUTPUT_PLAIN) {
1022 printf(" #%d : (in %s)\n", i, backDescriptor);
1023 printf(" name : '%s'\n", name);
1024 printf(" type : '%s'\n", typeDescriptor);
1025 printf(" access : 0x%04x (%s)\n",
1026 pDexMethod->accessFlags, accessStr);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001027
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001028 if (pDexMethod->codeOff == 0) {
1029 printf(" code : (none)\n");
1030 } else {
1031 printf(" code -\n");
1032 dumpCode(pDexFile, pDexMethod);
1033 }
1034
1035 if (gOptions.disassemble)
1036 putchar('\n');
1037 } else if (gOptions.outputFormat == OUTPUT_XML) {
1038 bool constructor = (name[0] == '<');
1039
1040 if (constructor) {
1041 char* tmp;
1042
1043 tmp = descriptorClassToDot(backDescriptor);
1044 printf("<constructor name=\"%s\"\n", tmp);
1045 free(tmp);
1046
1047 tmp = descriptorToDot(backDescriptor);
1048 printf(" type=\"%s\"\n", tmp);
1049 free(tmp);
1050 } else {
1051 printf("<method name=\"%s\"\n", name);
1052
1053 const char* returnType = strrchr(typeDescriptor, ')');
1054 if (returnType == NULL) {
1055 fprintf(stderr, "bad method type descriptor '%s'\n",
1056 typeDescriptor);
1057 goto bail;
1058 }
1059
1060 char* tmp = descriptorToDot(returnType+1);
1061 printf(" return=\"%s\"\n", tmp);
1062 free(tmp);
1063
1064 printf(" abstract=%s\n",
1065 quotedBool((pDexMethod->accessFlags & ACC_ABSTRACT) != 0));
1066 printf(" native=%s\n",
1067 quotedBool((pDexMethod->accessFlags & ACC_NATIVE) != 0));
1068
1069 bool isSync =
1070 (pDexMethod->accessFlags & ACC_SYNCHRONIZED) != 0 ||
1071 (pDexMethod->accessFlags & ACC_DECLARED_SYNCHRONIZED) != 0;
1072 printf(" synchronized=%s\n", quotedBool(isSync));
1073 }
1074
1075 printf(" static=%s\n",
1076 quotedBool((pDexMethod->accessFlags & ACC_STATIC) != 0));
1077 printf(" final=%s\n",
1078 quotedBool((pDexMethod->accessFlags & ACC_FINAL) != 0));
1079 // "deprecated=" not knowable w/o parsing annotations
1080 printf(" visibility=%s\n",
1081 quotedVisibility(pDexMethod->accessFlags));
1082
1083 printf(">\n");
1084
1085 /*
1086 * Parameters.
1087 */
1088 if (typeDescriptor[0] != '(') {
1089 fprintf(stderr, "ERROR: bad descriptor '%s'\n", typeDescriptor);
1090 goto bail;
1091 }
1092
1093 char tmpBuf[strlen(typeDescriptor)+1]; /* more than big enough */
1094 int argNum = 0;
1095
1096 const char* base = typeDescriptor+1;
1097
1098 while (*base != ')') {
1099 char* cp = tmpBuf;
1100
1101 while (*base == '[')
1102 *cp++ = *base++;
1103
1104 if (*base == 'L') {
1105 /* copy through ';' */
1106 do {
1107 *cp = *base++;
1108 } while (*cp++ != ';');
1109 } else {
1110 /* primitive char, copy it */
1111 if (strchr("ZBCSIFJD", *base) == NULL) {
1112 fprintf(stderr, "ERROR: bad method signature '%s'\n", base);
1113 goto bail;
1114 }
1115 *cp++ = *base++;
1116 }
1117
1118 /* null terminate and display */
1119 *cp++ = '\0';
1120
1121 char* tmp = descriptorToDot(tmpBuf);
1122 printf("<parameter name=\"arg%d\" type=\"%s\">\n</parameter>\n",
1123 argNum++, tmp);
1124 free(tmp);
1125 }
1126
1127 if (constructor)
1128 printf("</constructor>\n");
1129 else
1130 printf("</method>\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001131 }
1132
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001133bail:
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001134 free(typeDescriptor);
1135 free(accessStr);
1136}
1137
1138/*
1139 * Dump a static (class) field.
1140 */
1141void dumpSField(const DexFile* pDexFile, const DexField* pSField, int i)
1142{
1143 const DexFieldId* pFieldId;
1144 const char* backDescriptor;
1145 const char* name;
1146 const char* typeDescriptor;
1147 char* accessStr;
1148
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001149 if (gOptions.exportsOnly &&
1150 (pSField->accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) == 0)
1151 {
1152 return;
1153 }
1154
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001155 pFieldId = dexGetFieldId(pDexFile, pSField->fieldIdx);
1156 name = dexStringById(pDexFile, pFieldId->nameIdx);
1157 typeDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->typeIdx);
1158 backDescriptor = dexStringByTypeIdx(pDexFile, pFieldId->classIdx);
1159
1160 accessStr = createAccessFlagStr(pSField->accessFlags, kAccessForField);
1161
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001162 if (gOptions.outputFormat == OUTPUT_PLAIN) {
1163 printf(" #%d : (in %s)\n", i, backDescriptor);
1164 printf(" name : '%s'\n", name);
1165 printf(" type : '%s'\n", typeDescriptor);
1166 printf(" access : 0x%04x (%s)\n",
1167 pSField->accessFlags, accessStr);
1168 } else if (gOptions.outputFormat == OUTPUT_XML) {
1169 char* tmp;
1170
1171 printf("<field name=\"%s\"\n", name);
1172
1173 tmp = descriptorToDot(typeDescriptor);
1174 printf(" type=\"%s\"\n", tmp);
1175 free(tmp);
1176
1177 printf(" transient=%s\n",
1178 quotedBool((pSField->accessFlags & ACC_TRANSIENT) != 0));
1179 printf(" volatile=%s\n",
1180 quotedBool((pSField->accessFlags & ACC_VOLATILE) != 0));
1181 // "value=" not knowable w/o parsing annotations
1182 printf(" static=%s\n",
1183 quotedBool((pSField->accessFlags & ACC_STATIC) != 0));
1184 printf(" final=%s\n",
1185 quotedBool((pSField->accessFlags & ACC_FINAL) != 0));
1186 // "deprecated=" not knowable w/o parsing annotations
1187 printf(" visibility=%s\n",
1188 quotedVisibility(pSField->accessFlags));
1189 printf(">\n</field>\n");
1190 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001191
1192 free(accessStr);
1193}
1194
1195/*
1196 * Dump an instance field.
1197 */
1198void dumpIField(const DexFile* pDexFile, const DexField* pIField, int i)
1199{
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001200 dumpSField(pDexFile, pIField, i);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001201}
1202
1203/*
1204 * Dump the class.
The Android Open Source Project99409882009-03-18 22:20:24 -07001205 *
1206 * Note "idx" is a DexClassDef index, not a DexTypeId index.
Andy McFaddend18aff32009-05-06 10:19:16 -07001207 *
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001208 * If "*pLastPackage" is NULL or does not match the current class' package,
1209 * the value will be replaced with a newly-allocated string.
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001210 */
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001211void dumpClass(DexFile* pDexFile, int idx, char** pLastPackage)
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001212{
1213 const DexTypeList* pInterfaces;
1214 const DexClassDef* pClassDef;
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001215 DexClassData* pClassData = NULL;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001216 const u1* pEncodedData;
1217 const char* fileName;
1218 const char* classDescriptor;
1219 const char* superclassDescriptor;
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001220 char* accessStr = NULL;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001221 int i;
1222
1223 pClassDef = dexGetClassDef(pDexFile, idx);
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001224
1225 if (gOptions.exportsOnly && (pClassDef->accessFlags & ACC_PUBLIC) == 0) {
1226 //printf("<!-- omitting non-public class %s -->\n",
1227 // classDescriptor);
1228 goto bail;
1229 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001230
1231 pEncodedData = dexGetClassData(pDexFile, pClassDef);
1232 pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
1233
1234 if (pClassData == NULL) {
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001235 printf("Trouble reading class data (#%d)\n", idx);
1236 goto bail;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001237 }
1238
1239 classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001240
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001241 /*
1242 * For the XML output, show the package name. Ideally we'd gather
1243 * up the classes, sort them, and dump them alphabetically so the
1244 * package name wouldn't jump around, but that's not a great plan
1245 * for something that needs to run on the device.
1246 */
1247 if (!(classDescriptor[0] == 'L' &&
1248 classDescriptor[strlen(classDescriptor)-1] == ';'))
1249 {
1250 /* arrays and primitives should not be defined explicitly */
1251 fprintf(stderr, "Malformed class name '%s'\n", classDescriptor);
1252 /* keep going? */
1253 } else if (gOptions.outputFormat == OUTPUT_XML) {
1254 char* mangle;
1255 char* lastSlash;
1256 char* cp;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001257
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001258 mangle = strdup(classDescriptor + 1);
1259 mangle[strlen(mangle)-1] = '\0';
1260
1261 /* reduce to just the package name */
1262 lastSlash = strrchr(mangle, '/');
1263 if (lastSlash != NULL) {
1264 *lastSlash = '\0';
1265 } else {
1266 *mangle = '\0';
1267 }
1268
1269 for (cp = mangle; *cp != '\0'; cp++) {
1270 if (*cp == '/')
1271 *cp = '.';
1272 }
1273
1274 if (*pLastPackage == NULL || strcmp(mangle, *pLastPackage) != 0) {
1275 /* start of a new package */
1276 if (*pLastPackage != NULL)
1277 printf("</package>\n");
1278 printf("<package name=\"%s\"\n>\n", mangle);
1279 free(*pLastPackage);
1280 *pLastPackage = mangle;
1281 } else {
1282 free(mangle);
1283 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001284 }
1285
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001286 accessStr = createAccessFlagStr(pClassDef->accessFlags, kAccessForClass);
1287
1288 if (pClassDef->superclassIdx == kDexNoIndex) {
1289 superclassDescriptor = NULL;
1290 } else {
1291 superclassDescriptor =
1292 dexStringByTypeIdx(pDexFile, pClassDef->superclassIdx);
1293 }
1294
1295 if (gOptions.outputFormat == OUTPUT_PLAIN) {
1296 printf("Class #%d -\n", idx);
1297 printf(" Class descriptor : '%s'\n", classDescriptor);
1298 printf(" Access flags : 0x%04x (%s)\n",
1299 pClassDef->accessFlags, accessStr);
1300
1301 if (superclassDescriptor != NULL)
1302 printf(" Superclass : '%s'\n", superclassDescriptor);
1303
1304 printf(" Interfaces -\n");
1305 } else {
1306 char* tmp;
1307
1308 tmp = descriptorClassToDot(classDescriptor);
1309 printf("<class name=\"%s\"\n", tmp);
1310 free(tmp);
1311
1312 if (superclassDescriptor != NULL) {
1313 tmp = descriptorToDot(superclassDescriptor);
1314 printf(" extends=\"%s\"\n", tmp);
1315 free(tmp);
1316 }
1317 printf(" abstract=%s\n",
1318 quotedBool((pClassDef->accessFlags & ACC_ABSTRACT) != 0));
1319 printf(" static=%s\n",
1320 quotedBool((pClassDef->accessFlags & ACC_STATIC) != 0));
1321 printf(" final=%s\n",
1322 quotedBool((pClassDef->accessFlags & ACC_FINAL) != 0));
1323 // "deprecated=" not knowable w/o parsing annotations
1324 printf(" visibility=%s\n",
1325 quotedVisibility(pClassDef->accessFlags));
1326 printf(">\n");
1327 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001328 pInterfaces = dexGetInterfacesList(pDexFile, pClassDef);
1329 if (pInterfaces != NULL) {
1330 for (i = 0; i < (int) pInterfaces->size; i++)
1331 dumpInterface(pDexFile, dexGetTypeItem(pInterfaces, i), i);
1332 }
1333
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001334 if (gOptions.outputFormat == OUTPUT_PLAIN)
1335 printf(" Static fields -\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001336 for (i = 0; i < (int) pClassData->header.staticFieldsSize; i++) {
1337 dumpSField(pDexFile, &pClassData->staticFields[i], i);
1338 }
1339
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001340 if (gOptions.outputFormat == OUTPUT_PLAIN)
1341 printf(" Instance fields -\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001342 for (i = 0; i < (int) pClassData->header.instanceFieldsSize; i++) {
1343 dumpIField(pDexFile, &pClassData->instanceFields[i], i);
1344 }
1345
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001346 if (gOptions.outputFormat == OUTPUT_PLAIN)
1347 printf(" Direct methods -\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001348 for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
1349 dumpMethod(pDexFile, &pClassData->directMethods[i], i);
1350 }
1351
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001352 if (gOptions.outputFormat == OUTPUT_PLAIN)
1353 printf(" Virtual methods -\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001354 for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
1355 dumpMethod(pDexFile, &pClassData->virtualMethods[i], i);
1356 }
1357
1358 // TODO: Annotations.
1359
1360 if (pClassDef->sourceFileIdx != kDexNoIndex)
1361 fileName = dexStringById(pDexFile, pClassDef->sourceFileIdx);
1362 else
1363 fileName = "unknown";
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001364
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001365 if (gOptions.outputFormat == OUTPUT_PLAIN) {
1366 printf(" source_file_idx : %d (%s)\n",
1367 pClassDef->sourceFileIdx, fileName);
1368 printf("\n");
1369 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001370
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001371 if (gOptions.outputFormat == OUTPUT_XML) {
1372 printf("</class>\n");
1373 }
1374
1375bail:
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001376 free(pClassData);
1377 free(accessStr);
1378}
1379
The Android Open Source Project99409882009-03-18 22:20:24 -07001380
1381/*
1382 * Advance "ptr" to ensure 32-bit alignment.
1383 */
1384static inline const u1* align32(const u1* ptr)
1385{
1386 return (u1*) (((int) ptr + 3) & ~0x03);
1387}
1388
Andy McFadden10351272009-03-24 21:30:32 -07001389
1390/*
1391 * Dump a map in the "differential" format.
1392 *
1393 * TODO: show a hex dump of the compressed data. (We can show the
1394 * uncompressed data if we move the compression code to libdex; otherwise
1395 * it's too complex to merit a fast & fragile implementation here.)
1396 */
1397void dumpDifferentialCompressedMap(const u1** pData)
1398{
1399 const u1* data = *pData;
1400 const u1* dataStart = data -1; // format byte already removed
1401 u1 regWidth;
1402 u2 numEntries;
1403
1404 /* standard header */
1405 regWidth = *data++;
1406 numEntries = *data++;
1407 numEntries |= (*data++) << 8;
1408
1409 /* compressed data begins with the compressed data length */
1410 int compressedLen = readUnsignedLeb128(&data);
1411 int addrWidth = 1;
1412 if ((*data & 0x80) != 0)
1413 addrWidth++;
1414
1415 int origLen = 4 + (addrWidth + regWidth) * numEntries;
1416 int compLen = (data - dataStart) + compressedLen;
1417
1418 printf(" (differential compression %d -> %d [%d -> %d])\n",
1419 origLen, compLen,
1420 (addrWidth + regWidth) * numEntries, compressedLen);
1421
1422 /* skip past end of entry */
1423 data += compressedLen;
1424
1425 *pData = data;
1426}
1427
The Android Open Source Project99409882009-03-18 22:20:24 -07001428/*
1429 * Dump register map contents of the current method.
1430 *
1431 * "*pData" should point to the start of the register map data. Advances
1432 * "*pData" to the start of the next map.
1433 */
1434void dumpMethodMap(DexFile* pDexFile, const DexMethod* pDexMethod, int idx,
1435 const u1** pData)
1436{
1437 const u1* data = *pData;
1438 const DexMethodId* pMethodId;
1439 const char* name;
1440 int offset = data - (u1*) pDexFile->pOptHeader;
1441
1442 pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
1443 name = dexStringById(pDexFile, pMethodId->nameIdx);
1444 printf(" #%d: 0x%08x %s\n", idx, offset, name);
1445
1446 u1 format;
1447 int addrWidth;
1448
1449 format = *data++;
1450 if (format == 1) { /* kRegMapFormatNone */
1451 /* no map */
1452 printf(" (no map)\n");
1453 addrWidth = 0;
1454 } else if (format == 2) { /* kRegMapFormatCompact8 */
1455 addrWidth = 1;
1456 } else if (format == 3) { /* kRegMapFormatCompact16 */
1457 addrWidth = 2;
Andy McFadden10351272009-03-24 21:30:32 -07001458 } else if (format == 4) { /* kRegMapFormatDifferential */
1459 dumpDifferentialCompressedMap(&data);
1460 goto bail;
The Android Open Source Project99409882009-03-18 22:20:24 -07001461 } else {
1462 printf(" (unknown format %d!)\n", format);
Andy McFadden10351272009-03-24 21:30:32 -07001463 /* don't know how to skip data; failure will cascade to end of class */
1464 goto bail;
The Android Open Source Project99409882009-03-18 22:20:24 -07001465 }
1466
1467 if (addrWidth > 0) {
1468 u1 regWidth;
1469 u2 numEntries;
1470 int idx, addr, byte;
1471
1472 regWidth = *data++;
1473 numEntries = *data++;
1474 numEntries |= (*data++) << 8;
1475
1476 for (idx = 0; idx < numEntries; idx++) {
1477 addr = *data++;
1478 if (addrWidth > 1)
1479 addr |= (*data++) << 8;
1480
1481 printf(" %4x:", addr);
1482 for (byte = 0; byte < regWidth; byte++) {
1483 printf(" %02x", *data++);
1484 }
1485 printf("\n");
1486 }
1487 }
1488
Andy McFadden10351272009-03-24 21:30:32 -07001489bail:
The Android Open Source Project99409882009-03-18 22:20:24 -07001490 //if (addrWidth >= 0)
1491 // *pData = align32(data);
1492 *pData = data;
1493}
1494
1495/*
1496 * Dump the contents of the register map area.
1497 *
1498 * These are only present in optimized DEX files, and the structure is
1499 * not really exposed to other parts of the VM itself. We're going to
1500 * dig through them here, but this is pretty fragile. DO NOT rely on
1501 * this or derive other code from it.
1502 */
1503void dumpRegisterMaps(DexFile* pDexFile)
1504{
1505 const u1* pClassPool = pDexFile->pRegisterMapPool;
1506 const u4* classOffsets;
1507 const u1* ptr;
1508 u4 numClasses;
1509 int baseFileOffset = (u1*) pClassPool - (u1*) pDexFile->pOptHeader;
1510 int idx;
1511
1512 if (pClassPool == NULL) {
1513 printf("No register maps found\n");
1514 return;
1515 }
1516
1517 ptr = pClassPool;
1518 numClasses = get4LE(ptr);
1519 ptr += sizeof(u4);
1520 classOffsets = (const u4*) ptr;
1521
1522 printf("RMAP begins at offset 0x%07x\n", baseFileOffset);
1523 printf("Maps for %d classes\n", numClasses);
1524 for (idx = 0; idx < (int) numClasses; idx++) {
1525 const DexClassDef* pClassDef;
1526 const char* classDescriptor;
1527
1528 pClassDef = dexGetClassDef(pDexFile, idx);
1529 classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
1530
1531 printf("%4d: +%d (0x%08x) %s\n", idx, classOffsets[idx],
1532 baseFileOffset + classOffsets[idx], classDescriptor);
1533
1534 if (classOffsets[idx] == 0)
1535 continue;
1536
1537 /*
1538 * What follows is a series of RegisterMap entries, one for every
1539 * direct method, then one for every virtual method.
1540 */
1541 DexClassData* pClassData;
1542 const u1* pEncodedData;
1543 const u1* data = (u1*) pClassPool + classOffsets[idx];
1544 u2 methodCount;
1545 int i;
1546
1547 pEncodedData = dexGetClassData(pDexFile, pClassDef);
1548 pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
1549 if (pClassData == NULL) {
1550 fprintf(stderr, "Trouble reading class data\n");
1551 continue;
1552 }
1553
1554 methodCount = *data++;
1555 methodCount |= (*data++) << 8;
1556 data += 2; /* two pad bytes follow methodCount */
1557 if (methodCount != pClassData->header.directMethodsSize
1558 + pClassData->header.virtualMethodsSize)
1559 {
1560 printf("NOTE: method count discrepancy (%d != %d + %d)\n",
1561 methodCount, pClassData->header.directMethodsSize,
1562 pClassData->header.virtualMethodsSize);
1563 /* this is bad, but keep going anyway */
1564 }
1565
1566 printf(" direct methods: %d\n",
1567 pClassData->header.directMethodsSize);
1568 for (i = 0; i < (int) pClassData->header.directMethodsSize; i++) {
1569 dumpMethodMap(pDexFile, &pClassData->directMethods[i], i, &data);
1570 }
1571
1572 printf(" virtual methods: %d\n",
1573 pClassData->header.virtualMethodsSize);
1574 for (i = 0; i < (int) pClassData->header.virtualMethodsSize; i++) {
1575 dumpMethodMap(pDexFile, &pClassData->virtualMethods[i], i, &data);
1576 }
1577
1578 free(pClassData);
1579 }
1580}
1581
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001582/*
1583 * Dump the requested sections of the file.
1584 */
1585void processDexFile(const char* fileName, DexFile* pDexFile)
1586{
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001587 char* package = NULL;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001588 int i;
1589
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001590 if (gOptions.verbose) {
1591 printf("Opened '%s', DEX version '%.3s'\n", fileName,
1592 pDexFile->pHeader->magic +4);
1593 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001594
The Android Open Source Project99409882009-03-18 22:20:24 -07001595 if (gOptions.dumpRegisterMaps) {
1596 dumpRegisterMaps(pDexFile);
1597 return;
1598 }
1599
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001600 if (gOptions.showFileHeaders)
1601 dumpFileHeader(pDexFile);
1602
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001603 if (gOptions.outputFormat == OUTPUT_XML)
1604 printf("<api>\n");
1605
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001606 for (i = 0; i < (int) pDexFile->pHeader->classDefsSize; i++) {
1607 if (gOptions.showSectionHeaders)
1608 dumpClassDef(pDexFile, i);
1609
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001610 dumpClass(pDexFile, i, &package);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001611 }
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001612
1613 /* free the last one allocated */
1614 if (package != NULL) {
1615 printf("</package>\n");
1616 free(package);
1617 }
1618
1619 if (gOptions.outputFormat == OUTPUT_XML)
1620 printf("</api>\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001621}
1622
1623
1624/*
1625 * Process one file.
1626 */
1627int process(const char* fileName)
1628{
1629 DexFile* pDexFile = NULL;
1630 MemMapping map;
1631 bool mapped = false;
1632 int result = -1;
1633
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001634 if (gOptions.verbose)
1635 printf("Processing '%s'...\n", fileName);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001636
1637 if (dexOpenAndMap(fileName, gOptions.tempFileName, &map, false) != 0)
1638 goto bail;
1639 mapped = true;
1640
Andy McFadden2124cb82009-03-25 15:37:39 -07001641 int flags = kDexParseVerifyChecksum;
1642 if (gOptions.ignoreBadChecksum)
1643 flags |= kDexParseContinueOnError;
1644
1645 pDexFile = dexFileParse(map.addr, map.length, flags);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001646 if (pDexFile == NULL) {
1647 fprintf(stderr, "ERROR: DEX parse failed\n");
1648 goto bail;
1649 }
1650
Andy McFadden0198b142009-04-02 14:48:56 -07001651 if (gOptions.checksumOnly) {
1652 printf("Checksum verified\n");
1653 } else {
1654 processDexFile(fileName, pDexFile);
1655 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001656
1657 result = 0;
1658
1659bail:
1660 if (mapped)
1661 sysReleaseShmem(&map);
1662 if (pDexFile != NULL)
1663 dexFileFree(pDexFile);
1664 return result;
1665}
1666
1667
1668/*
1669 * Show usage.
1670 */
1671void usage(void)
1672{
1673 fprintf(stderr, "Copyright (C) 2007 The Android Open Source Project\n\n");
Andy McFadden0198b142009-04-02 14:48:56 -07001674 fprintf(stderr,
Andy McFaddend18aff32009-05-06 10:19:16 -07001675 "%s: [-c] [-d] [-f] [-h] [-i] [-l layout] [-m] [-t tempfile] dexfile...\n",
The Android Open Source Project99409882009-03-18 22:20:24 -07001676 gProgName);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001677 fprintf(stderr, "\n");
Andy McFadden0198b142009-04-02 14:48:56 -07001678 fprintf(stderr, " -c : verify checksum and exit\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001679 fprintf(stderr, " -d : disassemble code sections\n");
1680 fprintf(stderr, " -f : display summary information from file header\n");
1681 fprintf(stderr, " -h : display file header details\n");
Andy McFadden2124cb82009-03-25 15:37:39 -07001682 fprintf(stderr, " -i : ignore checksum failures\n");
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001683 fprintf(stderr, " -l : output layout, either 'plain' or 'xml'\n");
The Android Open Source Project99409882009-03-18 22:20:24 -07001684 fprintf(stderr, " -m : dump register maps (and nothing else)\n");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001685 fprintf(stderr, " -t : temp file name (defaults to /sdcard/dex-temp-*)\n");
1686}
1687
1688/*
1689 * Parse args.
1690 *
1691 * I'm not using getopt_long() because we may not have it in libc.
1692 */
1693int main(int argc, char* const argv[])
1694{
1695 bool wantUsage = false;
1696 int ic;
1697
1698 memset(&gOptions, 0, sizeof(gOptions));
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001699 gOptions.verbose = true;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001700
1701 while (1) {
Andy McFaddend18aff32009-05-06 10:19:16 -07001702 ic = getopt(argc, argv, "cdfhil:mt:");
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001703 if (ic < 0)
1704 break;
1705
1706 switch (ic) {
Andy McFadden0198b142009-04-02 14:48:56 -07001707 case 'c': // verify the checksum then exit
1708 gOptions.checksumOnly = true;
1709 break;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001710 case 'd': // disassemble Dalvik instructions
1711 gOptions.disassemble = true;
1712 break;
1713 case 'f': // dump outer file header
1714 gOptions.showFileHeaders = true;
1715 break;
1716 case 'h': // dump section headers, i.e. all meta-data
1717 gOptions.showSectionHeaders = true;
1718 break;
Andy McFadden2124cb82009-03-25 15:37:39 -07001719 case 'i': // continue even if checksum is bad
1720 gOptions.ignoreBadChecksum = true;
1721 break;
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001722 case 'l': // layout
1723 if (strcmp(optarg, "plain") == 0) {
1724 gOptions.outputFormat = OUTPUT_PLAIN;
1725 } else if (strcmp(optarg, "xml") == 0) {
1726 gOptions.outputFormat = OUTPUT_XML;
1727 gOptions.verbose = false;
1728 gOptions.exportsOnly = true;
1729 } else {
1730 wantUsage = true;
1731 }
1732 break;
The Android Open Source Project99409882009-03-18 22:20:24 -07001733 case 'm': // dump register maps only
1734 gOptions.dumpRegisterMaps = true;
1735 break;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001736 case 't': // temp file, used when opening compressed Jar
Andy McFaddena2ee53b2009-05-05 16:52:10 -07001737 gOptions.tempFileName = optarg;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001738 break;
1739 default:
1740 wantUsage = true;
1741 break;
1742 }
1743 }
1744
1745 if (optind == argc) {
1746 fprintf(stderr, "%s: no file specified\n", gProgName);
1747 wantUsage = true;
1748 }
1749
Andy McFadden0198b142009-04-02 14:48:56 -07001750 if (gOptions.checksumOnly && gOptions.ignoreBadChecksum) {
1751 fprintf(stderr, "Can't specify both -c and -i\n");
1752 wantUsage = true;
1753 }
1754
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001755 /* initialize some VM tables */
1756 gInstrWidth = dexCreateInstrWidthTable();
1757 gInstrFormat = dexCreateInstrFormatTable();
1758
1759 if (wantUsage) {
1760 usage();
1761 return 2;
1762 }
1763
Andy McFadden0198b142009-04-02 14:48:56 -07001764 int result = 0;
1765 while (optind < argc) {
1766 result |= process(argv[optind++]);
1767 }
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001768
1769 free(gInstrWidth);
1770 free(gInstrFormat);
1771
Andy McFadden0198b142009-04-02 14:48:56 -07001772 return (result != 0);
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001773}
Andy McFadden0198b142009-04-02 14:48:56 -07001774