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 * Command-line DEX optimization and verification entry point.
19 *
20 * There are three ways to launch this:
21 * (1) From the VM.  This takes a dozen args, one of which is a file
22 *     descriptor that acts as both input and output.  This allows us to
23 *     remain ignorant of where the DEX data originally came from.
24 * (2) From installd or another native application.  Pass in a file
25 *     descriptor for a zip file, a file descriptor for the output, and
26 *     a filename for debug messages.  Many assumptions are made about
27 *     what's going on (verification + optimization are enabled, boot
28 *     class path is in BOOTCLASSPATH, etc).
29 * (3) On the host during a build for preoptimization. This behaves
30 *     almost the same as (2), except it takes file names instead of
31 *     file descriptors.
32 *
33 * There are some fragile aspects around bootclasspath entries, owing
34 * largely to the VM's history of working on whenever it thought it needed
35 * instead of strictly doing what it was told.  If optimizing bootclasspath
36 * entries, always do them in the order in which they appear in the path.
37 */
38#include "Dalvik.h"
39#include "libdex/OptInvocation.h"
40
41#include "cutils/log.h"
42#include "cutils/process_name.h"
43
44#include <fcntl.h>
45#include <stdbool.h>
46#include <stdlib.h>
47#include <stdio.h>
48#include <string.h>
49
50static const char* kClassesDex = "classes.dex";
51
52
53/*
54 * Extract "classes.dex" from zipFd into "cacheFd", leaving a little space
55 * up front for the DEX optimization header.
56 */
57static int extractAndProcessZip(int zipFd, int cacheFd,
58    const char* debugFileName, bool isBootstrap, const char* bootClassPath,
59    const char* dexoptFlagStr)
60{
61    ZipArchive zippy;
62    ZipEntry zipEntry;
63    size_t uncompLen;
64    long modWhen, crc32;
65    off_t dexOffset;
66    int err;
67    int result = -1;
68    int dexoptFlags = 0;        /* bit flags, from enum DexoptFlags */
69    DexClassVerifyMode verifyMode = VERIFY_MODE_ALL;
70    DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED;
71
72    memset(&zippy, 0, sizeof(zippy));
73
74    /* make sure we're still at the start of an empty file */
75    if (lseek(cacheFd, 0, SEEK_END) != 0) {
76        ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName);
77        goto bail;
78    }
79
80    /*
81     * Write a skeletal DEX optimization header.  We want the classes.dex
82     * to come just after it.
83     */
84    err = dexOptCreateEmptyHeader(cacheFd);
85    if (err != 0)
86        goto bail;
87
88    /* record the file position so we can get back here later */
89    dexOffset = lseek(cacheFd, 0, SEEK_CUR);
90    if (dexOffset < 0)
91        goto bail;
92
93    /*
94     * Open the zip archive, find the DEX entry.
95     */
96    if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) {
97        ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);
98        goto bail;
99    }
100
101    zipEntry = dexZipFindEntry(&zippy, kClassesDex);
102    if (zipEntry == NULL) {
103        ALOGW("DexOptZ: zip archive '%s' does not include %s",
104            debugFileName, kClassesDex);
105        goto bail;
106    }
107
108    /*
109     * Extract some info about the zip entry.
110     */
111    if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,
112            &modWhen, &crc32) != 0)
113    {
114        ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);
115        goto bail;
116    }
117
118    uncompLen = uncompLen;
119    modWhen = modWhen;
120    crc32 = crc32;
121
122    /*
123     * Extract the DEX data into the cache file at the current offset.
124     */
125    if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) {
126        ALOGW("DexOptZ: extraction of %s from %s failed",
127            kClassesDex, debugFileName);
128        goto bail;
129    }
130
131    /* Parse the options. */
132    if (dexoptFlagStr[0] != '\0') {
133        const char* opc;
134        const char* val;
135
136        opc = strstr(dexoptFlagStr, "v=");      /* verification */
137        if (opc != NULL) {
138            switch (*(opc+2)) {
139            case 'n':   verifyMode = VERIFY_MODE_NONE;          break;
140            case 'r':   verifyMode = VERIFY_MODE_REMOTE;        break;
141            case 'a':   verifyMode = VERIFY_MODE_ALL;           break;
142            default:                                            break;
143            }
144        }
145
146        opc = strstr(dexoptFlagStr, "o=");      /* optimization */
147        if (opc != NULL) {
148            switch (*(opc+2)) {
149            case 'n':   dexOptMode = OPTIMIZE_MODE_NONE;        break;
150            case 'v':   dexOptMode = OPTIMIZE_MODE_VERIFIED;    break;
151            case 'a':   dexOptMode = OPTIMIZE_MODE_ALL;         break;
152            case 'f':   dexOptMode = OPTIMIZE_MODE_FULL;        break;
153            default:                                            break;
154            }
155        }
156
157        opc = strstr(dexoptFlagStr, "m=y");     /* register map */
158        if (opc != NULL) {
159            dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;
160        }
161
162        opc = strstr(dexoptFlagStr, "u=");      /* uniprocessor target */
163        if (opc != NULL) {
164            switch (*(opc+2)) {
165            case 'y':   dexoptFlags |= DEXOPT_UNIPROCESSOR;     break;
166            case 'n':   dexoptFlags |= DEXOPT_SMP;              break;
167            default:                                            break;
168            }
169        }
170    }
171
172    /*
173     * Prep the VM and perform the optimization.
174     */
175
176    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,
177            dexoptFlags) != 0)
178    {
179        ALOGE("DexOptZ: VM init failed");
180        goto bail;
181    }
182
183    //vmStarted = 1;
184
185    /* do the optimization */
186    if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,
187            modWhen, crc32, isBootstrap))
188    {
189        ALOGE("Optimization failed");
190        goto bail;
191    }
192
193    /* we don't shut the VM down -- process is about to exit */
194
195    result = 0;
196
197bail:
198    dexZipCloseArchive(&zippy);
199    return result;
200}
201
202/*
203 * Common functionality for normal device-side processing as well as
204 * preoptimization.
205 */
206static int processZipFile(int zipFd, int cacheFd, const char* zipName,
207        const char *dexoptFlags)
208{
209    char* bcpCopy = NULL;
210
211    /*
212     * Check to see if this is a bootstrap class entry. If so, truncate
213     * the path.
214     */
215    const char* bcp = getenv("BOOTCLASSPATH");
216    if (bcp == NULL) {
217        ALOGE("DexOptZ: BOOTCLASSPATH not set");
218        return -1;
219    }
220
221    bool isBootstrap = false;
222    const char* match = strstr(bcp, zipName);
223    if (match != NULL) {
224        /*
225         * TODO: we have a partial string match, but that doesn't mean
226         * we've matched an entire path component. We should make sure
227         * that we're matching on the full zipName, and if not we
228         * should re-do the strstr starting at (match+1).
229         *
230         * The scenario would be a bootclasspath with something like
231         * "/system/framework/core.jar" while we're trying to optimize
232         * "/framework/core.jar". Not very likely since all paths are
233         * absolute and end with ".jar", but not impossible.
234         */
235        int matchOffset = match - bcp;
236        if (matchOffset > 0 && bcp[matchOffset-1] == ':')
237            matchOffset--;
238        ALOGV("DexOptZ: found '%s' in bootclasspath, cutting off at %d",
239            zipName, matchOffset);
240        bcpCopy = strdup(bcp);
241        bcpCopy[matchOffset] = '\0';
242
243        bcp = bcpCopy;
244        ALOGD("DexOptZ: truncated BOOTCLASSPATH to '%s'", bcp);
245        isBootstrap = true;
246    }
247
248    int result = extractAndProcessZip(zipFd, cacheFd, zipName, isBootstrap,
249            bcp, dexoptFlags);
250
251    free(bcpCopy);
252    return result;
253}
254
255/* advance to the next arg and extract it */
256#define GET_ARG(_var, _func, _msg)                                          \
257    {                                                                       \
258        char* endp;                                                         \
259        (_var) = _func(*++argv, &endp, 0);                                  \
260        if (*endp != '\0') {                                                \
261            ALOGE("%s '%s'", _msg, *argv);                                   \
262            goto bail;                                                      \
263        }                                                                   \
264        --argc;                                                             \
265    }
266
267/*
268 * Parse arguments.  We want:
269 *   0. (name of dexopt command -- ignored)
270 *   1. "--zip"
271 *   2. zip fd (input, read-only)
272 *   3. cache fd (output, read-write, locked with flock)
273 *   4. filename of zipfile being optimized (used for debug messages and
274 *      for comparing against BOOTCLASSPATH; does not need to be
275 *      accessible or even exist)
276 *   5. dexopt flags
277 *
278 * The BOOTCLASSPATH environment variable is assumed to hold the correct
279 * boot class path.  If the filename provided appears in the boot class
280 * path, the path will be truncated just before that entry (so that, if
281 * you were to dexopt "core.jar", your bootclasspath would be empty).
282 *
283 * This does not try to normalize the boot class path name, so the
284 * filename test won't catch you if you get creative.
285 */
286static int fromZip(int argc, char* const argv[])
287{
288    int result = -1;
289    int zipFd, cacheFd;
290    const char* zipName;
291    char* bcpCopy = NULL;
292    const char* dexoptFlags;
293
294    if (argc != 6) {
295        ALOGE("Wrong number of args for --zip (found %d)", argc);
296        goto bail;
297    }
298
299    /* skip "--zip" */
300    argc--;
301    argv++;
302
303    GET_ARG(zipFd, strtol, "bad zip fd");
304    GET_ARG(cacheFd, strtol, "bad cache fd");
305    zipName = *++argv;
306    --argc;
307    dexoptFlags = *++argv;
308    --argc;
309
310    result = processZipFile(zipFd, cacheFd, zipName, dexoptFlags);
311
312bail:
313    return result;
314}
315
316/*
317 * Parse arguments for a preoptimization run. This is when dalvikvm is run
318 * on a host to optimize dex files for eventual running on a (different)
319 * device. We want:
320 *   0. (name of dexopt command -- ignored)
321 *   1. "--preopt"
322 *   2. zipfile name
323 *   3. output file name
324 *   4. dexopt flags
325 *
326 * The BOOTCLASSPATH environment variable is assumed to hold the correct
327 * boot class path.  If the filename provided appears in the boot class
328 * path, the path will be truncated just before that entry (so that, if
329 * you were to dexopt "core.jar", your bootclasspath would be empty).
330 *
331 * This does not try to normalize the boot class path name, so the
332 * filename test won't catch you if you get creative.
333 */
334static int preopt(int argc, char* const argv[])
335{
336    int zipFd = -1;
337    int outFd = -1;
338    int result = -1;
339
340    if (argc != 5) {
341        /*
342         * Use stderr here, since this variant is meant to be called on
343         * the host side.
344         */
345        fprintf(stderr, "Wrong number of args for --preopt (found %d)\n",
346                argc);
347        return -1;
348    }
349
350    const char* zipName = argv[2];
351    const char* outName = argv[3];
352    const char* dexoptFlags = argv[4];
353
354    if (strstr(dexoptFlags, "u=y") == NULL &&
355        strstr(dexoptFlags, "u=n") == NULL)
356    {
357        fprintf(stderr, "Either 'u=y' or 'u=n' must be specified\n");
358        return -1;
359    }
360
361    zipFd = open(zipName, O_RDONLY);
362    if (zipFd < 0) {
363        perror(argv[0]);
364        return -1;
365    }
366
367    outFd = open(outName, O_RDWR | O_EXCL | O_CREAT, 0666);
368    if (outFd < 0) {
369        perror(argv[0]);
370        goto bail;
371    }
372
373    result = processZipFile(zipFd, outFd, zipName, dexoptFlags);
374
375bail:
376    if (zipFd >= 0) {
377        close(zipFd);
378    }
379
380    if (outFd >= 0) {
381        close(outFd);
382    }
383
384    return result;
385}
386
387/*
388 * Parse arguments for an "old-style" invocation directly from the VM.
389 *
390 * Here's what we want:
391 *   0. (name of dexopt command -- ignored)
392 *   1. "--dex"
393 *   2. DALVIK_VM_BUILD value, as a sanity check
394 *   3. file descriptor, locked with flock, for DEX file being optimized
395 *   4. DEX offset within file
396 *   5. DEX length
397 *   6. filename of file being optimized (for debug messages only)
398 *   7. modification date of source (goes into dependency section)
399 *   8. CRC of source (goes into dependency section)
400 *   9. flags (optimization level, isBootstrap)
401 *  10. bootclasspath entry #1
402 *  11. bootclasspath entry #2
403 *   ...
404 *
405 * dvmOptimizeDexFile() in dalvik/vm/analysis/DexOptimize.c builds the
406 * argument list and calls this executable.
407 *
408 * The bootclasspath entries become the dependencies for this DEX file.
409 *
410 * The open file descriptor MUST NOT be for one of the bootclasspath files.
411 * The parent has the descriptor locked, and we'll try to lock it again as
412 * part of processing the bootclasspath.  (We can catch this and return
413 * an error by comparing filenames or by opening the bootclasspath files
414 * and stat()ing them for inode numbers).
415 */
416static int fromDex(int argc, char* const argv[])
417{
418    int result = -1;
419    bool vmStarted = false;
420    char* bootClassPath = NULL;
421    int fd, flags, vmBuildVersion;
422    long offset, length;
423    const char* debugFileName;
424    u4 crc, modWhen;
425    char* endp;
426    bool onlyOptVerifiedDex = false;
427    DexClassVerifyMode verifyMode;
428    DexOptimizerMode dexOptMode;
429
430    if (argc < 10) {
431        /* don't have all mandatory args */
432        ALOGE("Not enough arguments for --dex (found %d)", argc);
433        goto bail;
434    }
435
436    /* skip "--dex" */
437    argc--;
438    argv++;
439
440    /*
441     * Extract the args.
442     */
443    GET_ARG(vmBuildVersion, strtol, "bad vm build");
444    if (vmBuildVersion != DALVIK_VM_BUILD) {
445        ALOGE("DexOpt: build rev does not match VM: %d vs %d",
446            vmBuildVersion, DALVIK_VM_BUILD);
447        goto bail;
448    }
449    GET_ARG(fd, strtol, "bad fd");
450    GET_ARG(offset, strtol, "bad offset");
451    GET_ARG(length, strtol, "bad length");
452    debugFileName = *++argv;
453    --argc;
454    GET_ARG(modWhen, strtoul, "bad modWhen");
455    GET_ARG(crc, strtoul, "bad crc");
456    GET_ARG(flags, strtol, "bad flags");
457
458    ALOGV("Args: fd=%d off=%ld len=%ld name='%s' mod=%#x crc=%#x flg=%d (argc=%d)",
459        fd, offset, length, debugFileName, modWhen, crc, flags, argc);
460    assert(argc > 0);
461
462    if (--argc == 0) {
463        bootClassPath = strdup("");
464    } else {
465        int i, bcpLen;
466        char* const* argp;
467        char* cp;
468
469        bcpLen = 0;
470        for (i = 0, argp = argv; i < argc; i++) {
471            ++argp;
472            ALOGV("DEP: '%s'", *argp);
473            bcpLen += strlen(*argp) + 1;
474        }
475
476        cp = bootClassPath = (char*) malloc(bcpLen +1);
477        for (i = 0, argp = argv; i < argc; i++) {
478            int strLen;
479
480            ++argp;
481            strLen = strlen(*argp);
482            if (i != 0)
483                *cp++ = ':';
484            memcpy(cp, *argp, strLen);
485            cp += strLen;
486        }
487        *cp = '\0';
488
489        assert((int) strlen(bootClassPath) == bcpLen-1);
490    }
491    ALOGV("  bootclasspath is '%s'", bootClassPath);
492
493    /* start the VM partway */
494
495    /* ugh -- upgrade these to a bit field if they get any more complex */
496    if ((flags & DEXOPT_VERIFY_ENABLED) != 0) {
497        if ((flags & DEXOPT_VERIFY_ALL) != 0)
498            verifyMode = VERIFY_MODE_ALL;
499        else
500            verifyMode = VERIFY_MODE_REMOTE;
501    } else {
502        verifyMode = VERIFY_MODE_NONE;
503    }
504    if ((flags & DEXOPT_OPT_ENABLED) != 0) {
505        if ((flags & DEXOPT_OPT_ALL) != 0)
506            dexOptMode = OPTIMIZE_MODE_ALL;
507        else
508            dexOptMode = OPTIMIZE_MODE_VERIFIED;
509    } else {
510        dexOptMode = OPTIMIZE_MODE_NONE;
511    }
512
513    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) {
514        ALOGE("VM init failed");
515        goto bail;
516    }
517
518    vmStarted = true;
519
520    /* do the optimization */
521    if (!dvmContinueOptimization(fd, offset, length, debugFileName,
522            modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
523    {
524        ALOGE("Optimization failed");
525        goto bail;
526    }
527
528    result = 0;
529
530bail:
531    /*
532     * In theory we should gracefully shut the VM down at this point.  In
533     * practice that only matters if we're checking for memory leaks with
534     * valgrind -- simply exiting is much faster.
535     *
536     * As it turns out, the DEX optimizer plays a little fast and loose
537     * with class loading.  We load all of the classes from a partially-
538     * formed DEX file, which is unmapped when we're done.  If we want to
539     * do clean shutdown here, perhaps for testing with valgrind, we need
540     * to skip the munmap call there.
541     */
542#if 0
543    if (vmStarted) {
544        ALOGI("DexOpt shutting down, result=%d", result);
545        dvmShutdown();
546    }
547#endif
548
549    free(bootClassPath);
550    ALOGV("DexOpt command complete (result=%d)", result);
551    return result;
552}
553
554/*
555 * Main entry point.  Decide where to go.
556 */
557int main(int argc, char* const argv[])
558{
559    set_process_name("dexopt");
560
561    setvbuf(stdout, NULL, _IONBF, 0);
562
563    if (argc > 1) {
564        if (strcmp(argv[1], "--zip") == 0)
565            return fromZip(argc, argv);
566        else if (strcmp(argv[1], "--dex") == 0)
567            return fromDex(argc, argv);
568        else if (strcmp(argv[1], "--preopt") == 0)
569            return preopt(argc, argv);
570    }
571
572    fprintf(stderr,
573        "Usage:\n\n"
574        "Short version: Don't use this.\n\n"
575        "Slightly longer version: This system-internal tool is used to\n"
576        "produce optimized dex files. See the source code for details.\n");
577
578    return 1;
579}
580