真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

小編給大家分享一下docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

我們提供的服務(wù)有:網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、寧蒗ssl等。為成百上千企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的寧蒗網(wǎng)站制作公司

我們先編譯openjdk: 首先通過命令git clone git@github.com:zq2599/centos7_build_openjdk8.git下載構(gòu)建鏡像所需的文件,下載后打開控制臺進入centos7_build_openjdk8目錄,執(zhí)行

docker build -t bolingcavalryopenjdk:0.0.1 .

這樣就構(gòu)建好了鏡像文件,再執(zhí)行啟動docker容器的命令(命令中的參數(shù)“–security-opt seccomp=unconfined”有特殊用處,稍后會講到):

docker run --name=jdk001 --security-opt seccomp=unconfined -idt bolingcavalryopenjdk:0.0.1

然后執(zhí)行以下命令進入容器的控制臺:

docker exec -it jdk001 /bin/bash

進入容器的控制臺后執(zhí)行以下兩個命令開始編譯:

./configure --with-debug-level=slowdebug
make all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK CONF=linux-x86_64-normal-server-slowdebug

以上就是編譯openjdk的步驟了,請大家開始編譯吧,因為等會兒會用到,我們要用編譯好的jdk做調(diào)試。

現(xiàn)在開始看源碼吧,本次分析的目標(biāo)是針對我們熟悉的java -version命令,當(dāng)我們在終端敲下這個命令的時候,jvm到底做了些什么呢?

整個分析驗證的流程是這樣的:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

準(zhǔn)備工作: 在容器內(nèi)通過vim看源碼是很不方便的,所以我這里是在電腦上復(fù)制了一份openjdk的源碼(下載地址:http://www.java.net/download/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip ),用sublime text3打開openjdk源碼,真正到了要修改的時候再去docker容器里通過vi修改。

尋找程序入口

第一步就是把程序的入口和源碼對應(yīng)起來,先要找到入口main函數(shù),步驟如下:

  1. 在docker容器內(nèi)的/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/bin目錄下,執(zhí)行命令以下命令可以進入GDB的命令行模式:

gdb --args ./java -version

效果如下圖,可以看到已進入GDB命令行模式,可以繼續(xù)輸入GDB命令了:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

輸入b main命令,在main函數(shù)打斷點,此時GDB會返回斷點位置的信息,如下圖,main函數(shù)的位置在/usr/local/openjdk/jdk/src/share/bin/main.c, line 97:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

再輸入l命令可以打印源碼,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

在容器外的電腦上,通過sublime text3或者其他ide打開main.c,如下圖,開始讀代碼吧:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

順序閱讀代碼

main函數(shù)中的代碼并不多,但有幾個宏定義會擾亂我們思路,從字面上看#ifdef _WIN32這樣的宏應(yīng)該是windows平臺下才會生效的,但總不能每次都靠字面推斷,此時打斷點單步執(zhí)行是最直接的方法,但是在打斷點之前,我們先解決前面遺留的一個問題吧,此問題挺重要的

還記得我們啟動docker容器的命令么:

docker run --name=jdk001 --security-opt seccomp=unconfined -idt bolingcavalryopenjdk:0.0.1

命令中的–security-opt seccomp=unconfined參數(shù)有什么用?為何要留在打斷點之前再次提到這個參數(shù)?

這個參數(shù)和Docker的安全機制有關(guān),具體的文檔鏈接在這里,請讀者們自行參悟,本人的英文太差就不獻丑了,簡單的說就是Docker有個Seccomp filtering功能,以伯克萊封包過濾器(Berkeley Packet Filter,縮寫B(tài)PF)的方式允許用戶對容器內(nèi)的系統(tǒng)調(diào)用(syscall)做自定義的“allow”, “deny”, “trap”, “kill”, or “trace”操作,由于Seccomp filtering的限制,在默認(rèn)的配置下,會導(dǎo)致我們在用GDB的時候run失敗,所以在執(zhí)行docker run的時候加入–security-opt seccomp=unconfined這個參數(shù),可以關(guān)閉seccomp profile的功能;

我之前不知道seccomp profile的限制,用命令docker run –name=jdk001 -idt bolingcavalryopenjdk:0.0.1啟動了容器,編譯可以成功,但是在用GDB調(diào)試的時候出了問題,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

上圖中,黃框中的“進入GDB”和“b main”(添加斷點)兩個命令都能正常執(zhí)行,但是紅框中的”r”(運行程序)命令在執(zhí)行的時候提示錯誤“Error disabling address space randomization: Operation not permitted”,在執(zhí)行”n”(單步執(zhí)行)命令的時候提示程序不在運行中。

遺留問題已經(jīng)澄清,可以繼續(xù)跟蹤代碼了,之前我們已經(jīng)在GDB輸入了”b mian”,給main函數(shù)打了斷點,現(xiàn)在輸入”r”開始執(zhí)行,然后就會看到main函數(shù)的斷點已經(jīng)生效,輸入”n”可以跟蹤代碼執(zhí)行到了哪一行,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

原來代碼執(zhí)行的位置分別是97,122,123,125這四行,和下圖的源碼完全對應(yīng)上了:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

有了GDB神器,可以愉快的閱讀源碼了:

main.c的main函數(shù)中,調(diào)用JLI_Launch函數(shù),在Sublime text3中,將鼠標(biāo)放置在”JLI_Launch”位置,會彈出一個小窗口,上面是JLI_Launch函數(shù)的聲明和定義的兩個鏈接,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

點擊第一個鏈接,跳轉(zhuǎn)到JLI_Launch函數(shù)的定義位置:

//根據(jù)環(huán)境變量初始化debug標(biāo)志位,后續(xù)的日志是否會打印靠這個debug標(biāo)志控制了
    InitLauncher(javaw);
    //如果設(shè)置了debug,就會打印一些輔助信息 
    DumpState();     
    if (JLI_IsTraceLauncher()) {
        int i;
        printf("Command line args:\n");
        for (i = 0; i < argc ; i++) {
            printf("argv[%d] = %s\n", i, argv[i]);
        }
        AddOption("-Dsun.java.launcher.diag=true", NULL);
    }  //如果設(shè)置debug標(biāo)志位,就打印命令行參數(shù),并加入額外參數(shù)

    //選擇jre版本,在jar包的manifest文件或者命令行中都可以對jre版本進行設(shè)置
    SelectVersion(argc, argv, &main_class); 

    /*
    設(shè)置一些參數(shù),例如jvmpath的值被設(shè)置成jdk所在目錄下的“l(fā)ib/amd64/server/l”子目錄,再加上宏定義JVM_DLL的值"libjvm.so",即:/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server/libjvm.so
    */
    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));

    //記錄加載libjvm.so的起始時間,在加載結(jié)束后可以得到并打印出加載libjvm.so的耗時                        
    ifn.CreateJavaVM = 0;
    ifn.GetDefaultJavaVMInitArgs = 0;

    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }

    //加載/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server/libjvm.so
    if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }

    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
    }

    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
             (long)(jint)Counter2Micros(end-start));

    ++argv;
    --argc;

    if (IsJavaArgs()) {
        /* Preprocess wrapper arguments */
        TranslateApplicationArgs(jargc, jargv, &argc, &argv);
        if (!AddApplicationOptions(appclassc, appclassv)) {
            return(1);
        }
    } else {
        //classpath處理
        /* Set default CLASSPATH */
        cpath = getenv("CLASSPATH");
        if (cpath == NULL) {
            cpath = ".";
        }
        SetClassPath(cpath);
    }

    //解析命令行的參數(shù)
    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
    {
        return(ret);
    }

到這里先不要繼續(xù)往下讀,我們進ParseArguments函數(shù)中去看看:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

如上圖紅框所示,解析到”-version”參數(shù)的時候,會將printVersion變量設(shè)置為JNI_TRUE并立即返回。

繼續(xù)閱讀JLI_Launch函數(shù):

//如果有-jar參數(shù),就會根據(jù)參數(shù)設(shè)置classpath
    if (mode == LM_JAR) {
        SetClassPath(what);
    }

    //添加一個用于HotSpot虛擬機的參數(shù)"-Dsun.java.command"
    SetJavaCommandLineProp(what, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    //添加一個參數(shù)-Dsun.java.launcher=SUN_STANDARD,這樣JVM就知道是他的創(chuàng)建者的身份
    SetJavaLauncherProp();

    //獲取當(dāng)前進程ID,放入?yún)?shù)-Dsun.java.launcher.pid中,這樣JVM就知道是他的創(chuàng)建者的進程ID
    SetJavaLauncherPlatformProps();

    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);

接下來在JVMInit函數(shù)中,ContinueInNewThread函數(shù)中會調(diào)用ContinueInNewThread0函數(shù),并且把JavaMain函數(shù)做為入?yún)鬟f給ContinueInNewThread0,ContinueInNewThread0的代碼如下:

//如果指定了線程棧的大小,就在此設(shè)置到線程屬性變量attr中
    if (stack_size > 0) {
      pthread_attr_setstacksize(&attr, stack_size);
    }

    //創(chuàng)建線程,外部傳入的JavaMain也在此傳給子線程,子線程創(chuàng)建成功后,會先執(zhí)行JavaMain(也就是continuation參數(shù))
    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      //子線程創(chuàng)建成功后,當(dāng)前線程在此以阻塞的方式等待子線程結(jié)束
      pthread_join(tid, &tmp);
      rslt = (int)tmp;
    } else {
     /*
      * Continue execution in current thread if for some reason (e.g. out of
      * memory/LWP)  a new thread can't be created. This will likely fail
      * later in continuation as JNI_CreateJavaVM needs to create quite a
      * few new threads, anyway, just give it a try..
      */
      //若創(chuàng)建子線程失敗,在當(dāng)前線程直接執(zhí)行外面?zhèn)魅氲腏avaMain函數(shù)
      rslt = continuation(args);
    }

    //不再使用線程屬性,將其銷毀
    pthread_attr_destroy(&attr);

在閱讀ContinueInNewThread0函數(shù)源碼的時候遇見了下圖紅框中的注釋,這是我見過的最優(yōu)秀的注釋(僅代表個人見解),當(dāng)我看到pthread_create被調(diào)用時就在想“創(chuàng)建線程失敗會怎樣?”,然后這個注釋出現(xiàn)了,告訴我“如果因為某些原因(例如內(nèi)存溢出)導(dǎo)致創(chuàng)建線程失敗,當(dāng)前線程還會繼續(xù)執(zhí)行JavaMain,但是在后續(xù)的操作中依然有可能發(fā)生錯誤,例如JNI_CreateJavaVM函數(shù)會創(chuàng)建一些新的線程,因此,在當(dāng)前線程執(zhí)行JavaMain只是做一次嘗試”。

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

在恰當(dāng)?shù)奈恢脤栴}說清楚,并對后續(xù)發(fā)展做適當(dāng)?shù)奶崾?,好的代碼加上好的注釋真是讓人受益匪淺。

接著上面的分析,在新的線程中JavaMain函數(shù)會被調(diào)用,這個函數(shù)內(nèi)容如下:

//windows和linux下,RegisterThread是個空函數(shù),mac有實現(xiàn)
    RegisterThread();

    //記錄當(dāng)前時間,統(tǒng)計JVM初始化耗時的時候用到
    start = CounterGet();

    //調(diào)用libjvm.so庫中的CreateJavaVM方法初始化虛擬機
    if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }

    //調(diào)用java類的靜態(tài)方法(sun.launcher.LauncherHelper.showSettings),打印jvm的設(shè)置信息
    if (showSettings != NULL) {
        ShowSettings(env, showSettings);
        CHECK_EXCEPTION_LEAVE(1);
    }

    /*
    調(diào)用java類的靜態(tài)方法(sun.misc.Version.print),打?。?
    1.java版本信息
    2.java運行時版本信息
    3.java虛擬機版本信息
    */
    if (printVersion || showVersion) {
        PrintJavaVersion(env, showVersion);
        CHECK_EXCEPTION_LEAVE(0);
        if (printVersion) {
            LEAVE();
        }
    }

讀到這里可以不用讀后面的代碼了,因為printVersion變量為true,所以在執(zhí)行完P(guān)rintJavaVersion后,會調(diào)用LEAVE()函數(shù)使虛擬機與當(dāng)前線程分離,然后就是線程結(jié)束,進程結(jié)束。

此時,我們應(yīng)該聚焦PrintJavaVersion函數(shù),來看看平時執(zhí)行”java -version”的內(nèi)容是怎么產(chǎn)生的。

進入PrintJavaVersion函數(shù),內(nèi)容并不多,但能學(xué)到c語言的jvm是如何執(zhí)行java類中的靜態(tài)方法的,如下:

static void
PrintJavaVersion(JNIEnv *env, jboolean extraLF)
{
    jclass ver;
    jmethodID print;

    //從bootStrapClassLoader中查找sun.misc.Version
    NULL_CHECK(ver = FindBootStrapClass(env, "sun/misc/Version"));

    /*
    由于命令行參數(shù)中沒有-showVersion參數(shù),所以extraLF不等于JNI_TRUE,所以此處調(diào)用的是sun.misc.Version.print方法,如果命令是"java -showVersion",那么調(diào)用的就是pringlin方法了
    */
    NULL_CHECK(print = (*env)->GetStaticMethodID(env,
                                                 ver,
                                                 (extraLF == JNI_TRUE) ? "println" : "print",
                                                 "()V"
                                                 )
              );

    (*env)->CallStaticVoidMethod(env, ver, print);
}

讀到這里,本次閱讀源碼的工作似乎要結(jié)束了,但事情沒那么簡單,讀者們請在openjdk文件夾下搜索Version.java文件,雖然能搜到幾個Version.java,可是包路徑符合sun/misc/Version.java的文件只有一個,而這個Version.java的上層目錄是test目錄,不是src目錄,顯然只是測試代碼,并不是上面的PrintJavaVersion函數(shù)中調(diào)用的Version類:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

現(xiàn)在問題來了,真正的Version類到底在哪呢?

剛才搜索Version.java文件的時候,我們搜的是下載openjdk源碼解壓之后的文件夾,現(xiàn)在我們回到docker容器中的/usr/local/openjdk目錄下,輸入find ./ -name Version.java試試,結(jié)果如下圖,在build目錄下,發(fā)現(xiàn)了四個sun/misc/Version.java文件:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

在上圖中,sun/misc/Version.java文件一共有四個,后三個Version.java文件的路徑中帶有g(shù)et_profile_1,get_profile_2這類的路徑,此處猜測是在某些場景或者設(shè)置的前提下才會產(chǎn)生(實在對不起各位讀者,這是我的猜測,具體原因至今還么搞清楚,有知道的請告訴一些,謝謝啦),所以這里我們還是聚焦第一個文件吧:

/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/sun/misc/Version.java

Version.java這個文件,在下載的源碼中沒有,而編譯成功后的build目錄下卻有,并且文件的路徑中有g(shù)ensrc這個目錄,顯然是在編譯過程中產(chǎn)生的,好吧,我們從Makefile中去尋找答案去: 在Makefile文件中,會調(diào)用Main.gmk,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

Main.gmk中會調(diào)用BuildJdk.gmk,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

BuildJdk.gmk中會調(diào)用GenerateSources.gmk,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

GenerateSources.gmk中會調(diào)用GensrcMisc.gmk,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

打開GensrcMisc.gmk文件后,一切都一目了然了,如下圖中的代碼所示,以/src/share/classes/sun/misc/Version.java.template文件作為模板,通過sed命令將Version.java.template文件中的一些占位符替換成已有的變量,替換了占位符之后的文件就是Version.java

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

我們可以看到一共有五個占位符被替換:

@@launcher_name@@ 替換成 $(LAUNCHER_NAME)
@@java_version@@ 替換成 $(RELEASE)
@@java_runtime_version@@ 替換成 $(FULL_VERSION)
@@java_runtime_name@@ 替換成 $(RUNTIME_NAME)
@@java_profile_name@@ 替換成 $(call profile_version_name, $@)

先看看Version.java.template中是什么:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

果然有五個占位符,然后有個靜態(tài)方法public static void init(),里面把占位符對應(yīng)的內(nèi)容設(shè)置到全局屬性中去了。

終于搞清楚了,原來Version.java源自Version.java.template文件,在編譯構(gòu)建的時候被生成,生成的時候Version.java.template文件中的占位符被替換成對應(yīng)的變量。

現(xiàn)在,在docker容器里,執(zhí)行命令vi /usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/sun/misc ,打開Version.java看看吧,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

果然全部被替換了,再配合static代碼塊中的init方法,也就意味著這個類被加載的時候,應(yīng)用就有了這三個全局的屬性:java.version,java.runtime.version,java.runtime.name

搞清楚了Version.java的來龍去脈,還剩一個小問題要搞清楚,在GensrcMisc.gmk文件中,用sed命令替換Version.java.template文件中的占位符的時候,那些用來替換占位符的變量是哪里來的呢?或者說Version.java文件中java_version =”1.8.0-internal-debug”,java_runtime_name =”O(jiān)penJDK Runtime Environment”,java_runtime_version = “1.8.0-internal-debug-_2017_04_21_04_39-b00”這些表達式中的和”1.8.0-internal-debug”,“OpenJDK Runtime Environment””,“1.8.0-internal-debug-_2017_04_21_04_39-b00”究竟來自何處? 這時候最簡單的辦法就是用”RELEASE”,”FULL_VERSION”,”RUNTIME_NAME”去做全局搜索,很快就能查出來,我這來梳理一下吧:

openjdk/configure文件中調(diào)用common/autoconf/configure common/autoconf/configure中調(diào)用autogen.sh autogen.sh中有如下操作:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

把configure.ac中的內(nèi)容做替換后輸出到generated-configure.sh,其中用到了autoconfig做配置

configure.ac中調(diào)用basics.m4 basics.m4中調(diào)用spec.gmk.in spec.gmk.in中明確寫出了JDK_VERSION,RUNTIME_NAME這些變量的定義,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

PRODUCT_NAME和PRODUCT_SUFFIX是autoconfig的配置項,在openjdk/common/autoconf/version-numbers文件中定義,這是個autoconfig的配置文件,如下圖:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

變量的來源梳理完畢,接著看代碼吧,sun.misc.Version類的print方法,如下圖,一如既往的簡答明了,將一些全局屬性取出然后打印出來:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

至此,java -version命令對應(yīng)的源碼分析完畢,簡答的總結(jié)一下,就是入口的main函數(shù)中,通過調(diào)用java的Version類的print靜態(tài)方法,將一些變量打印出來,這些變量是通過autoconfig輸出到自動生成的java源碼中的;

既然已經(jīng)讀懂了源碼,現(xiàn)在該親自動手實踐一下啦,這里我們做兩個改動,記得是在docker容器中用vi工具去改

修改Version.java.template文件,讓java -version在執(zhí)行的時候多輸出一行代碼,如下圖紅框位置:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

修改/usr/local/openjdk/common/autoconf/version-numbers,修改PRODUCT_SUFFIX的值,根據(jù)之前的理解,PRODUCT_SUFFIX修改后,輸出的runtime name會有變化,改動如下:

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

改動完畢,回到/usr/local/openjdk目錄下,執(zhí)行下面兩行命令,開始編譯:

./configure --with-debug-level=slowdebug
make all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK CONF=linux-x86_64-normal-server-slowdebug

編譯結(jié)束后,去/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/bin目錄執(zhí)行./java -version,得到的輸出如下圖,可以看到我們的改動已經(jīng)生效了

docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼

看完了這篇文章,相信你對“docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼”有了一定的了解,如果想了解更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!


文章名稱:docker環(huán)境下如何修改,編譯,GDB調(diào)試openjdk8源碼
文章來源:http://weahome.cn/article/iioepi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部