《jdk8u源码分析》5.4.ExecJRE

src/windows/bin/java_md.c::ExecJRE

/*
 * Given a path to a jre to execute, this routine checks if this process
 * is indeed that jre.  If not, it exec's that jre.
 *
 * We want to actually check the paths rather than just the version string
 * built into the executable, so that given version specification will yield
 * the exact same Java environment, regardless of the version of the arbitrary
 * launcher we start with.
 */
void
ExecJRE(char *jre, char **argv) {
    jint     len;
    char    path[MAXPATHLEN + 1];
	//program name:java
    const char *progname = GetProgramName();

    /*
     * Resolve the real path to the currently running launcher.
     */
    //获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载,存入path变量中
    len = GetModuleFileName(NULL, path, MAXPATHLEN + 1);
    if (len == 0 || len > MAXPATHLEN) {
        JLI_ReportErrorMessageSys(JRE_ERROR9, progname);
        exit(1);
    }

    //ExecJRE: old: C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe
    JLI_TraceLauncher("ExecJRE: old: %s\n", path);
	//ExecJRE: new: C:\Tools\Java\open-jre1.8.0_202
    JLI_TraceLauncher("ExecJRE: new: %s\n", jre);


    /*
     * If the path to the selected JRE directory is a match to the initial
     * portion of the path to the currently executing JRE, we have a winner!
     * If so, just return.
     */
    //判断项目中是否包含jre运行时环境
    //比较jre, path,比较长度:strlen(jre),如果相等直接return
    if (JLI_StrNCaseCmp(jre, path, JLI_StrLen(jre)) == 0)
        return;                 /* I am the droid you were looking for */

    /*
     * If this isn't the selected version, exec the selected version.
     */
    //拼接java命令绝对路径:C:\Tools\Java\open-jre1.8.0_202\bin\java.exe
    JLI_Snprintf(path, sizeof(path), "%s\\bin\\%s.exe", jre, progname);

    /*
     * Although Windows has an execv() entrypoint, it doesn't actually
     * overlay a process: it can only create a new process and terminate
     * the old process.  Therefore, any processes waiting on the initial
     * process wake up and they shouldn't.  Hence, a chain of pseudo-zombie
     * processes must be retained to maintain the proper wait semantics.
     * Fortunately the image size of the launcher isn't too large at this
     * time.
     *
     * If it weren't for this semantic flaw, the code below would be ...
     *
     *     execv(path, argv);
     *     JLI_ReportErrorMessage("Error: Exec of %s failed\n", path);
     *     exit(1);
     *
     * The incorrect exec semantics could be addressed by:
     *
     *     exit((int)spawnv(_P_WAIT, path, argv));
     *
     * Unfortunately, a bug in Windows spawn/exec impementation prevents
     * this from completely working.  All the Windows POSIX process creation
     * interfaces are implemented as wrappers around the native Windows
     * function CreateProcess().  CreateProcess() takes a single string
     * to specify command line options and arguments, so the POSIX routine
     * wrappers build a single string from the argv[] array and in the
     * process, any quoting information is lost.
     *
     * The solution to this to get the original command line, to process it
     * to remove the new multiple JRE options (if any) as was done for argv
     * in the common SelectVersion() routine and finally to pass it directly
     * to the native CreateProcess() Windows process control interface.
     */
    {
        char    *cmdline;
        char    *p;
        char    *np;
        char    *ocl;
        char    *ccl;
        char    *unquoted;
        DWORD   exitCode;
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        /*
         * The following code block gets and processes the original command
         * line, replacing the argv[0] equivalent in the command line with
         * the path to the new executable and removing the appropriate
         * Multiple JRE support options. Note that similar logic exists
         * in the platform independent SelectVersion routine, but is
         * replicated here due to the syntax of CreateProcess().
         *
         * The magic "+ 4" characters added to the command line length are
         * 2 possible quotes around the path (argv[0]), a space after the
         * path and a terminating null character.
         */
        //命令行返回数据结构:char *argv[],查看MSDN《Parsing C Command-Line Arguments》
        ocl = GetCommandLine();
        np = ccl = JLI_StringDup(ocl);
        //丢弃第一个参数:argv[0] = java
        p = nextarg(&np);               /* Discard argv[0] */
        cmdline = (char *)JLI_MemAlloc(JLI_StrLen(path) + JLI_StrLen(np) + 4);
        if (JLI_StrChr(path, (int)' ') == NULL && JLI_StrChr(path, (int)'\t') == NULL)
            cmdline = JLI_StrCpy(cmdline, path);
        else
            cmdline = JLI_StrCat(JLI_StrCat(JLI_StrCpy(cmdline, "\""), path), "\"");

        while (*np != (char)0) {                /* While more command-line */
        	//获取命令行参数arg[i](i >= 1),处理反斜杠转义字符
            p = nextarg(&np);
            if (*p != (char)0) {                /* If a token was isolated */
            	//移除双引号
                unquoted = unquote(p);
                //拼接以'-'开始的命令行参数
                if (*unquoted == '-') {         /* Looks like an option */
		            //argv[7] = -classpath
		            //argv[8] = xx1.jar;xx2.jar;...
		            //argv[9] = com.johnjoe.study.Test
		            //argv[10] = -cp
		            //argv[11] = xxx.jar
		            //因为argv中-classpath和-cp的特殊处理
                    if (JLI_StrCmp(unquoted, "-classpath") == 0 ||
                      JLI_StrCmp(unquoted, "-cp") == 0) {       /* Unique cp syntax */
                      	//拼接命令行参数
                        cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
                        p = nextarg(&np);
                        if (*p != (char)0)      /* If a token was isolated */
                        	//拼接参数配置的lib(jar)包目录
                            cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
                        //过滤参数:-version, -jre-restrict-search, -no-jre-restrict-search
                        //其余参数全部原样拼接
                    } else if (JLI_StrNCmp(unquoted, "-version:", 9) != 0 &&
                      JLI_StrCmp(unquoted, "-jre-restrict-search") != 0 &&
                      JLI_StrCmp(unquoted, "-no-jre-restrict-search") != 0) {
                        cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
                    }
                } else {                        /* End of options */
                	//拼接当前循环取出的命令行参数
                    cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
                    //拼接剩余命令行参数并退出循环
                    cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), np);
                    JLI_MemFree((void *)unquoted);
                    break;
                }
                JLI_MemFree((void *)unquoted);
            }
        }
        JLI_MemFree((void *)ccl);

        if (JLI_IsTraceLauncher()) {
            np = ccl = JLI_StringDup(cmdline);
            p = nextarg(&np);//p与cmdline相等
			//ReExec Command: C:\Tools\Java\open-jre1.8.0_202\bin\java.exe (C:\Tools\Java\open-jre1.8.0_202\bin\java.exe)
            printf("ReExec Command: %s (%s)\n", path, p);
            //ReExec Args: -jar test.jar
            printf("ReExec Args: %s\n", np);
            JLI_MemFree((void *)ccl);
        }
        //刷新stdou, stderr流缓冲区
        (void)fflush(stdout);
        (void)fflush(stderr);

        /*
         * The following code is modeled after a model presented in the
         * Microsoft Technical Article "Moving Unix Applications to
         * Windows NT" (March 6, 1994) and "Creating Processes" on MSDN
         * (Februrary 2005).  It approximates UNIX spawn semantics with
         * the parent waiting for termination of the child.
         */
        memset(&si, 0, sizeof(si));
        si.cb =sizeof(STARTUPINFO);
        memset(&pi, 0, sizeof(pi));
        
		//启动新的进程执行命令行
        if (!CreateProcess((LPCTSTR)path,       /* executable name */
          (LPTSTR)cmdline,                      /* command line */
          (LPSECURITY_ATTRIBUTES)NULL,          /* process security attr. */
          (LPSECURITY_ATTRIBUTES)NULL,          /* thread security attr. */
          (BOOL)TRUE,                           /* inherits system handles */
          (DWORD)0,                             /* creation flags */
          (LPVOID)NULL,                         /* environment block */
          (LPCTSTR)NULL,                        /* current directory */
          (LPSTARTUPINFO)&si,                   /* (in) startup information */
          (LPPROCESS_INFORMATION)&pi)) {        /* (out) process information */
            JLI_ReportErrorMessageSys(SYS_ERROR1, path);
            exit(1);
        }

		//等待执行完成,超时时间设置为INT.MAX_VALUE: 0xFFFFFFFF
        if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED) {
        	//获取退出代码
            if (GetExitCodeProcess(pi.hProcess, &exitCode) == FALSE)
                exitCode = 1;
        } else {
            JLI_ReportErrorMessage(SYS_ERROR2);
            exitCode = 1;
        }

		//关闭线程、进程句柄
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        
		//终止新的进程
        exit(exitCode);
    }
}

src/windows/bin/java_md.c::nextarg

/*
 * Local helper routine to isolate a single token (option or argument)
 * from the command line.
 *
 * This routine accepts a pointer to a character pointer.  The first
 * token (as defined by MSDN command-line argument syntax) is isolated
 * from that string.
 *
 * Upon return, the input character pointer pointed to by the parameter s
 * is updated to point to the remainding, unscanned, portion of the string,
 * or to a null character if the entire string has been consummed.
 *
 * This function returns a pointer to a null-terminated string which
 * contains the isolated first token, or to the null character if no
 * token could be isolated.
 *
 * Note the side effect of modifying the input string s by the insertion
 * of a null character, making it two strings.
 *
 * See "Parsing C Command-Line Arguments" in the MSDN Library for the
 * parsing rule details.  The rule summary from that specification is:
 *
 *  * Arguments are delimited by white space, which is either a space or a tab.
 *
 *  * A string surrounded by double quotation marks is interpreted as a single
 *    argument, regardless of white space contained within. A quoted string can
 *    be embedded in an argument. Note that the caret (^) is not recognized as
 *    an escape character or delimiter.
 *
 *  * A double quotation mark preceded by a backslash, \", is interpreted as a
 *    literal double quotation mark (").
 *
 *  * Backslashes are interpreted literally, unless they immediately precede a
 *    double quotation mark.
 *
 *  * If an even number of backslashes is followed by a double quotation mark,
 *    then one backslash (\) is placed in the argv array for every pair of
 *    backslashes (\\), and the double quotation mark (") is interpreted as a
 *    string delimiter.
 *
 *  * If an odd number of backslashes is followed by a double quotation mark,
 *    then one backslash (\) is placed in the argv array for every pair of
 *    backslashes (\\) and the double quotation mark is interpreted as an
 *    escape sequence by the remaining backslash, causing a literal double
 *    quotation mark (") to be placed in argv.
 * 
 * 在MSDN Library转换规则明细查看“C命令行参数转换”。根据规范,总结规则如下:
 * 
 * * 参数被空格(' ')或制表符('\t')等空白符号分割。
 * 
 * * 被双引号包围起来的字符串被认为是一个参数,无论里面是否包含空白符号(' ' | '\t')。
 *   被引起来的字符串可以嵌入到参数中。注意:插入符(^)不会被当作转义符或分隔符。
 * 
 * * 双引号前面加一个反斜杠,组成转义字符,转义成单个双引号(")。
 * 
 * * 反斜杠按字面解释,除非放在双引号之前。
 * 
 * * 如果偶数个反斜杠后面跟一个双引号, 则每对反斜杠组成转义字符,
 *   转义成单个反斜杠存储在 argv 数组中。
 * 
 * * 如果奇数个反斜杠后面跟一个双引号, 则每对反斜杠组成转义字符,
 *   转义成单个反斜杠存储在 argv 数组中。双引号作为字符串分隔符。
 */

// ******** 移除第一个命令行参数的斜杠 *********
static char*
nextarg(char** s) {
    char    *p = *s;
    char    *head;
    int     slashes = 0;//斜杠数量
    int     inquote = 0;//是否在引号中

    /*
     * Strip leading whitespace, which MSDN defines as only space or tab.
     * (Hence, no locale specific "isspace" here.)
     */
    //移除最前面的空白字符(' ' | '\t'), 并设为返回结果
    while (*p != (char)0 && (*p == ' ' || *p == '\t'))
        p++;
    head = p;                   /* Save the start of the token to return */

    /*
     * Isolate a token from the command line.
     */
    //根据空白字符(' ' | '\t')逐个剥离命令行参数
    while (*p != (char)0 && (inquote || !(*p == ' ' || *p == '\t'))) {
        if (*p == '\\' && *(p+1) == '"' && slashes % 2 == 0)
            p++;
        else if (*p == '"')
            inquote = !inquote;
        slashes = (*p++ == '\\') ? slashes + 1 : 0;
    }

    /*
     * If the token isolated isn't already terminated in a "char zero",
     * then replace the whitespace character with one and move to the
     * next character.
     * 如果剩余的命令行未以(char)0结束,用(char)0替换空白字符并将指针移动到下一个字符。???
     */
    //如果字符串指针首字符不为空字符,指针后移一个字符并赋值为空字符 (Dev C++执行抛异常)
    if (*p != (char)0)
        *p++ = (char)0;

    /*
     * Update the parameter to point to the head of the remaining string
     * reflecting the command line and return a pointer to the leading
     * token which was isolated from the command line.
     */
    //s指向剥离一个命令行参数后的字符串
    *s = p;
    return (head);
}

src/windows/bin/java_md.c::unquote

/*
 * Local helper routine to return a string equivalent to the input string
 * s, but with quotes removed so the result is a string as would be found
 * in argv[].  The returned string should be freed by a call to JLI_MemFree().
 *
 * The rules for quoting (and escaped quotes) are:
 *
 *  1 A double quotation mark preceded by a backslash, \", is interpreted as a
 *    literal double quotation mark (").
 *
 *  2 Backslashes are interpreted literally, unless they immediately precede a
 *    double quotation mark.
 *
 *  3 If an even number of backslashes is followed by a double quotation mark,
 *    then one backslash (\) is placed in the argv array for every pair of
 *    backslashes (\\), and the double quotation mark (") is interpreted as a
 *    string delimiter.
 *
 *  4 If an odd number of backslashes is followed by a double quotation mark,
 *    then one backslash (\) is placed in the argv array for every pair of
 *    backslashes (\\) and the double quotation mark is interpreted as an
 *    escape sequence by the remaining backslash, causing a literal double
 *    quotation mark (") to be placed in argv.
 */
static char*
unquote(const char *s) {
    const char *p = s;          /* Pointer to the tail of the original string */
    char *un = (char*)JLI_MemAlloc(JLI_StrLen(s) + 1);  /* Ptr to unquoted string */
    char *pun = un;             /* Pointer to the tail of the unquoted string */
	//移除双引号并返回
    while (*p != '\0') {
        if (*p == '"') {
        	//双引号直接跳过,指向下一字符
            p++;
        } else if (*p == '\\') {
        	//JLI_StrSpn从第一个字符开始匹配,此处可以直接简写成1
        	//如果反斜杠后紧跟双引号直接跳过双引号,只输出反斜杠
            const char *q = p + JLI_StrSpn(p,"\\");
            if (*q == '"')
                do {
                    *pun++ = '\\';
                    p += 2;
                 } while (*p == '\\' && p < q);
            else
                while (p < q)
                    *pun++ = *p++;
        } else {
        	//其它字符直接输出
            *pun++ = *p++;
        }
    }
    //将pun指针的值设为空串
    *pun = '\0';
    return un;
}
上一篇:养猪日记 2022.3.2


下一篇:UVa 247 Calling Circles (传递闭包)