1. 程式人生 > >修改,編譯,GDB除錯openjdk8原始碼(docker環境下)

修改,編譯,GDB除錯openjdk8原始碼(docker環境下)

在上一章《在docker上編譯openjdk8》裡,我們在docker容器內成功編譯了openjdk8的原始碼,有沒有讀者朋友產生過這個念頭:“能不能修改openjdk原始碼,構建一個與眾不同的jdk“,今天我們就來閱讀一些openjdk的原始碼,再嘗試做些小改動並驗證。

我們先編譯openjdk:
首先通過命令git clone [email protected]:zq2599/centos7_build_openjdk8.git下載構建映象所需的檔案,下載後開啟控制檯進入centos7_build_openjdk8目錄,執行

docker build -t bolingcavalryopenjdk
:0.0.1 .

這樣就構建好了映象檔案,再執行啟動docker容器的命令(命令中的引數“–security-opt seccomp=unconfined”有特殊用處,稍後會講到):

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

然後執行以下命令進入容器的控制檯:

docker exec -it jdk001 /bin/bash

進入容器的控制檯後執行以下兩個命令開始編譯:

./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做除錯。

現在開始看原始碼吧,本次分析的目標是針對我們熟悉的java -version命令,當我們在終端敲下這個命令的時候,jvm到底做了些什麼呢?

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

這裡寫圖片描述

尋找程式入口

第一步就是把程式的入口和原始碼對應起來,先要找到入口main函式,步驟如下:
1. 在docker容器內的/usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/bin目錄下,執行命令以下命令可以進入GDB的命令列模式:

gdb --args ./java -version

效果如下圖,可以看到已進入GDB命令列模式,可以繼續輸入GDB命令了:

這裡寫圖片描述

輸入b main命令,在main函式打斷點,此時GDB會返回斷點位置的資訊,如下圖,main函式的位置在/usr/local/openjdk/jdk/src/share/bin/main.c, line 97:

這裡寫圖片描述

再輸入l命令可以列印原始碼,如下圖:

這裡寫圖片描述

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

這裡寫圖片描述

順序閱讀程式碼

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

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

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

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

這個引數和Docker的安全機制有關,具體的文件連結在這裡,請讀者們自行參悟,本人的英文太差就不獻醜了,簡單的說就是Docker有個Seccomp filtering功能,以伯克萊封包過濾器(Berkeley Packet Filter,縮寫BPF)的方式允許使用者對容器內的系統呼叫(syscall)做自定義的“allow”, “deny”, “trap”, “kill”, or “trace”操作,由於Seccomp filtering的限制,在預設的配置下,會導致我們在用GDB的時候run失敗,所以在執行docker run的時候加入–security-opt seccomp=unconfined這個引數,可以關閉seccomp profile的功能;

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

這裡寫圖片描述

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

遺留問題已經澄清,可以繼續跟蹤程式碼了,之前我們已經在GDB輸入了”b mian”,給main函式打了斷點,現在輸入”r”開始執行,然後就會看到main函式的斷點已經生效,輸入”n”可以跟蹤程式碼執行到了哪一行,如下圖:

這裡寫圖片描述

原來程式碼執行的位置分別是97,122,123,125這四行,和下圖的原始碼完全對應上了:

這裡寫圖片描述

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

main.c的main函式中,呼叫JLI_Launch函式,在Sublime text3中,將滑鼠放置在”JLI_Launch”位置,會彈出一個小視窗,上面是JLI_Launch函式的宣告和定義的兩個連結,如下圖:

這裡寫圖片描述

點選第一個連結,跳轉到JLI_Launch函式的定義位置:

//根據環境變數初始化debug標誌位,後續的日誌是否會列印靠這個debug標誌控制了
    InitLauncher(javaw);
    //如果設定了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);
    }  //如果設定debug標誌位,就列印命令列引數,並加入額外引數

    //選擇jre版本,在jar包的manifest檔案或者命令列中都可以對jre版本進行設定
    SelectVersion(argc, argv, &main_class); 

    /*
    設定一些引數,例如jvmpath的值被設定成jdk所在目錄下的“lib/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的起始時間,在載入結束後可以得到並打印出載入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);
    }

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

到這裡先不要繼續往下讀,我們進ParseArguments函式中去看看:

這裡寫圖片描述

如上圖紅框所示,解析到”-version”引數的時候,會將printVersion變數設定為JNI_TRUE並立即返回。

繼續閱讀JLI_Launch函式:

//如果有-jar引數,就會根據引數設定classpath
    if (mode == LM_JAR) {
        SetClassPath(what);
    }

    //新增一個用於HotSpot虛擬機器的引數"-Dsun.java.command"
    SetJavaCommandLineProp(what, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    //新增一個引數-Dsun.java.launcher=SUN_STANDARD,這樣JVM就知道是他的建立者的身份
    SetJavaLauncherProp();

    //獲取當前程序ID,放入引數-Dsun.java.launcher.pid中,這樣JVM就知道是他的建立者的程序ID
    SetJavaLauncherPlatformProps();

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

接下來在JVMInit函式中,ContinueInNewThread函式中會呼叫ContinueInNewThread0函式,並且把JavaMain函式做為入參傳遞給ContinueInNewThread0,ContinueInNewThread0的程式碼如下:

//如果指定了執行緒棧的大小,就在此設定到執行緒屬性變數attr中
    if (stack_size > 0) {
      pthread_attr_setstacksize(&attr, stack_size);
    }

    //建立執行緒,外部傳入的JavaMain也在此傳給子執行緒,子執行緒建立成功後,會先執行JavaMain(也就是continuation引數)
    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      //子執行緒建立成功後,當前執行緒在此以阻塞的方式等待子執行緒結束
      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..
      */
      //若建立子執行緒失敗,在當前執行緒直接執行外面傳入的JavaMain函式
      rslt = continuation(args);
    }

    //不再使用執行緒屬性,將其銷燬
    pthread_attr_destroy(&attr);

在閱讀ContinueInNewThread0函式原始碼的時候遇見了下圖紅框中的註釋,這是我見過的最優秀的註釋(僅代表個人見解),當我看到pthread_create被呼叫時就在想“建立執行緒失敗會怎樣?”,然後這個註釋出現了,告訴我“如果因為某些原因(例如記憶體溢位)導致建立執行緒失敗,當前執行緒還會繼續執行JavaMain,但是在後續的操作中依然有可能發生錯誤,例如JNI_CreateJavaVM函式會建立一些新的執行緒,因此,在當前執行緒執行JavaMain只是做一次嘗試”。

這裡寫圖片描述

在恰當的位置將問題說清楚,並對後續發展做適當的提示,好的程式碼加上好的註釋真是讓人受益匪淺。

接著上面的分析,在新的執行緒中JavaMain函式會被呼叫,這個函式內容如下:

//windows和linux下,RegisterThread是個空函式,mac有實現
    RegisterThread();

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

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

    //呼叫java類的靜態方法(sun.launcher.LauncherHelper.showSettings),列印jvm的設定資訊
    if (showSettings != NULL) {
        ShowSettings(env, showSettings);
        CHECK_EXCEPTION_LEAVE(1);
    }

    /*
    呼叫java類的靜態方法(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,所以在執行完PrintJavaVersion後,會呼叫LEAVE()函式使虛擬機器與當前執行緒分離,然後就是執行緒結束,程序結束。

此時,我們應該聚焦PrintJavaVersion函式,來看看平時執行”java -version”的內容是怎麼產生的。

進入PrintJavaVersion函式,內容並不多,但能學到c語言的jvm是如何執行java類中的靜態方法的,如下:

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

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

    /*
    由於命令列引數中沒有-showVersion引數,所以extraLF不等於JNI_TRUE,所以此處呼叫的是sun.misc.Version.print方法,如果命令是"java -showVersion",那麼呼叫的就是pringlin方法了
    */
    NULL_CHECK(print = (*env)->GetStaticMethodID(env,
                                                 ver,
                                                 (extraLF == JNI_TRUE) ? "println" : "print",
                                                 "()V"
                                                 )
              );

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

讀到這裡,本次閱讀原始碼的工作似乎要結束了,但事情沒那麼簡單,讀者們請在openjdk資料夾下搜尋Version.java檔案,雖然能搜到幾個Version.java,可是包路徑符合sun/misc/Version.java的檔案只有一個,而這個Version.java的上層目錄是test目錄,不是src目錄,顯然只是測試程式碼,並不是上面的PrintJavaVersion函式中呼叫的Version類:

這裡寫圖片描述

現在問題來了,真正的Version類到底在哪呢?

剛才搜尋Version.java檔案的時候,我們搜的是下載openjdk原始碼解壓之後的資料夾,現在我們回到docker容器中的/usr/local/openjdk目錄下,輸入find ./ -name Version.java試試,結果如下圖,在build目錄下,發現了四個sun/misc/Version.java檔案:

這裡寫圖片描述

在上圖中,sun/misc/Version.java檔案一共有四個,後三個Version.java檔案的路徑中帶有get_profile_1,get_profile_2這類的路徑,此處猜測是在某些場景或者設定的前提下才會產生(實在對不起各位讀者,這是我的猜測,具體原因至今還麼搞清楚,有知道的請告訴一些,謝謝啦),所以這裡我們還是聚焦第一個檔案吧:

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

Version.java這個檔案,在下載的原始碼中沒有,而編譯成功後的build目錄下卻有,並且檔案的路徑中有gensrc這個目錄,顯然是在編譯過程中產生的,好吧,我們從Makefile中去尋找答案去:
在Makefile檔案中,會呼叫Main.gmk,如下圖:

這裡寫圖片描述

Main.gmk中會呼叫BuildJdk.gmk,如下圖:

這裡寫圖片描述

BuildJdk.gmk中會呼叫GenerateSources.gmk,如下圖:

這裡寫圖片描述

GenerateSources.gmk中會呼叫GensrcMisc.gmk,如下圖:

這裡寫圖片描述

開啟GensrcMisc.gmk檔案後,一切都一目瞭然了,如下圖中的程式碼所示,以/src/share/classes/sun/misc/Version.java.template檔案作為模板,通過sed命令將Version.java.template檔案中的一些佔位符替換成已有的變數,替換了佔位符之後的檔案就是Version.java

這裡寫圖片描述

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

@@launcher[email protected]@ 替換成 $(LAUNCHER_NAME)
@@java[email protected]@ 替換成 $(RELEASE)
@@java[email protected]@ 替換成 $(FULL_VERSION)
@@java[email protected]@ 替換成 $(RUNTIME_NAME)
@@java[email protected]@ 替換成 $(call profile_version_name, [email protected])

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

這裡寫圖片描述

果然有五個佔位符,然後有個靜態方法public static void init(),裡面把佔位符對應的內容設定到全域性屬性中去了。

終於搞清楚了,原來Version.java源自Version.java.template檔案,在編譯構建的時候被生成,生成的時候Version.java.template檔案中的佔位符被替換成對應的變數。

現在,在docker容器裡,執行命令vi /usr/local/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/sun/misc
,開啟Version.java看看吧,如下圖:

這裡寫圖片描述

果然全部被替換了,再配合static程式碼塊中的init方法,也就意味著這個類被載入的時候,應用就有了這三個全域性的屬性: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 =”OpenJDK 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檔案中呼叫common/autoconf/configure
common/autoconf/configure中呼叫autogen.sh
autogen.sh中有如下操作:

這裡寫圖片描述

把configure.ac中的內容做替換後輸出到generated-configure.sh,其中用到了autoconfig做配置

configure.ac中呼叫basics.m4
basics.m4中呼叫spec.gmk.in
spec.gmk.in中明確寫出了JDK_VERSION,RUNTIME_NAME這些變數的定義,如下圖:

這裡寫圖片描述

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

這裡寫圖片描述

變數的來源梳理完畢,接著看程式碼吧,sun.misc.Version類的print方法,如下圖,一如既往的簡答明瞭,將一些全域性屬性取出然後打印出來:

這裡寫圖片描述

至此,java -version命令對應的原始碼分析完畢,簡答的總結一下,就是入口的main函式中,通過呼叫java的Version類的print靜態方法,將一些變數打印出來,這些變數是通過autoconfig輸出到自動生成的java原始碼中的;

既然已經讀懂了原始碼,現在該親自動手實踐一下啦,這裡我們做兩個改動,記得是在docker容器中用vi工具去改

修改Version.java.template檔案,讓java -version在執行的時候多輸出一行程式碼,如下圖紅框位置:

這裡寫圖片描述

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

這裡寫圖片描述

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

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

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

這裡寫圖片描述

至次,本次閱讀,修改,除錯和編譯openjdk8的實踐就結束了,其實JavaMain函式做了很多事情,這次只是看到其中列印資訊的那一部分而已,後面的載入class,執行java類等都還沒有看到,有興趣的讀者可以先對java的類載入做個初步瞭解,再繼續閱讀JavaMain函式,相信您會有更多收穫的。

相關推薦

修改編譯GDB除錯openjdk8原始碼(docker環境)

在上一章《在docker上編譯openjdk8》裡,我們在docker容器內成功編譯了openjdk8的原始碼,有沒有讀者朋友產生過這個念頭:“能不能修改openjdk原始碼,構建一個與眾不同的jdk“,今天我們就來閱讀一些openjdk的原始碼,再嘗試做些小改

4.前端基於react,後端基於.net core2.0的開發之路(4) 前端打包編譯路由模型服務

hub 解決 路徑 export routes run 部署 service 後端 1.簡要的介紹 學習react,首先學習的就是javascript,然後ES6,接著是jsx,通常來說如果有javascript的基礎,上手非常快,但是真正要搭建一個前端工程化項目,還是有很

實驗三 程式設計編譯連線跟蹤

實驗內容: (1)將下面的程式儲存為t1.asm檔案,將其生成可執行檔案t1.exe assume  cs:codesg codesg  segment       mov  ax , 2000H      

linuxgdb除錯檢視原始碼

 GDB是GNU開源組織釋出的一個強大的UNIX下的程式除錯工具。或許,各位比較喜歡那種圖形介面方式的,像VC、BCB等IDE的除錯,但如果你是在UNIX平臺下做軟體,你會發現GDB這個除錯工具有比VC、BCB的圖形化偵錯程式更強大的功能。所謂“寸有所長,尺有所短”就是這個道理。 &nb

實驗 3 程式設計編譯連線跟蹤

一、實驗目的 1.  掌握組合語言源程式(8086 dos 彙編)編寫→彙編→連結→除錯的方法 2.  加深對 1-4 章基礎知識的理解   二、實驗準備 1. 結合第 4 章課件和教材,學習/複習完整彙編源程式編寫→彙編→連線→執行→除錯

nginx模組_使用gdb除錯nginx原始碼

轉載地址:https://www.cnblogs.com/yjf512/archive/2012/05/10/2494635.html 工欲善其事必先利其器,如何使用除錯工具一步步除錯nginx是瞭解nginx的重要手段。 熟悉gdb的使用 這裡就不說了,谷歌一搜一堆,這裡

C 編譯: 使用 gdb 除錯

gdb是the GNU Debugger的簡稱。它是一款UNIX平臺的偵錯程式(debugger),可用於為C, C++, Objective-C, Java, Fortran等程式debug。 在gdb中,你可以通過設定斷點(break point)來控制程式執行的進度,並檢視斷點時的變數和函式呼叫狀況,

c編輯器之clion安裝編譯控制檯亂碼修復

1. 下載clion,注意安裝的時候選擇的目錄不能有任何中文字型。只能英文+數字 2.安裝完畢後,開啟軟體,選擇License Server,輸入:http://xidea.online 3.啟用完

【轉】GDB除錯opencore原始碼

1 首先在終端執行:emulator –show-kernel -memory 1024開啟模擬器2 開啟另一個終端,執行:adb shell進入模擬器shell,3 在模擬器shell中執行 ps mediaserver檢視程序mediaserver的PID4 檢視PID後

c語言編譯過程詳解預處理編譯彙編連結(乾貨滿滿)

鍥子 我們在各自的電腦上寫下程式碼,得明白我們程式碼究竟是如何產生的,不想了解1,0什麼的,但這幾個環節必須掌握吧。 我們的程式碼會經過這4個環節,從而形成最終檔案,c語言作為編譯語言,用來向計算機發出指令。讓程式設計師能夠準確地定義計算機所需要使用的資料,並精確地定義在

Spark on yarn Intellij ide 安裝編譯打包叢集執行 詳解

說明:已經安裝好hadoop2.2.0 完全分佈,scala,spark已安裝好,環境配置完畢;主機為hadoop-master,hadoop-slave 一.intellij 安裝(centos6.5系統) 步驟一。 1.將上述兩個安裝

Linux(centos/fedora/redhat/ubuntu....)如何安裝rpm,deb檔案如何解壓編譯安裝*.tar.gz檔案

RPM格式軟體包的安裝 1.簡介 幾乎所有的Linux發行版本都使用某種形式的軟體包管理安裝、更新和解除安裝軟體。與直接從原始碼安裝相比,軟體包管理易於安裝和解除安裝;易於更新已安裝的軟體包;易於保護配置檔案;易於跟蹤已安裝檔案。 RPM全稱是Red Hat Package Manager(Red Hat

php_screw安裝使用編譯問題說明

安裝步驟: 1. 下載原始碼:wget http://nchc.dl.sourceforge.net/project/php-screw/php-screw/1.5/php_screw-1.5.tar.gz 2. 解壓縮:tar zxvf php_screw-1.5.t

SO檔案的編寫編譯使用方法

(1)SO檔案簡介 linux下的.so檔案為共享庫,相當於windows下的dll檔案。在系統目錄/usr/lib/下,我們可以看到很多應用程式庫檔案(常用的動態連結庫和軟體包的配置檔案)。 (2)SO檔案編譯方法 A. SO檔案沒有main 我

深入理解預編譯編譯彙編連結的過程——之編譯和使用(連結)庫——物件和靜態庫

當你完成了程式碼開發,想把這個程式碼給別人用,但是又不希望別人看到原始碼,就要給別人一個庫和標頭檔案,庫和標頭檔案是配合的,缺一不可。 或者過程相反,你從別人那裡拿到一個庫和標頭檔案來使用。 那麼如何編譯生成一個庫給他人,如何使用從他人那裡拿到的庫呢? 範例1:我們想把Li

程式的生成過程預處理編譯彙編連結

當我們使用VS或者其他編譯器對我們所寫的程式進行執行時,在下面會出現,編譯、連結等等顯示,那麼到底什麼是編譯,連結?我們所寫的程式碼到底是怎樣變成可執行程式的?接下來就為大家解釋,程式是如何產生的。 示例一個大家都見過的程式列印:hello world!(檔名

基於CSerialPort修改類的串列埠除錯助手原始碼(支援中文、自動儲存等)

QQ技術交流群:129518033最新進展:CSerialPort串列埠類最新修正版2017-12-16本串列埠工具整合目前多數串列埠工具的優秀功能於一身,採用CSerialPort類進行編寫,並在此基礎上對該類進行了改進。本工具免費試用,永不過期。並且本著奉獻的精神,公開本

Eclipse開發編譯打包常見問題總結------持續更新

fig 打jar jdk版本 -a 客戶 找到 找不到 color spa 在使用Eclipse開發,編譯,打包常見問題如下: 1、 保證本地開發的客戶端與服務端使用的jdk版本一致 2、 保證本地開發的客戶端與服務端使用的依賴jar包版本一致(比如本地thrift

通過Hadoop安全部署經驗總結開發出以下十大建議以確保大型和復雜多樣環境的數據信息安全。

同時 數據集 環境 部署 集群 暴露 用戶 控制 特定 通過Hadoop安全部署經驗總結,開發出以下十大建議,以確保大型和復雜多樣環境下的數據信息安全。 1、先下手為強!在規劃部署階段就確定數據的隱私保護策略,最好是在將數據放入到Hadoop之前就確定好保護策略。  2、確

Docker 環境一鍵安裝Kafaka 集群

doc nec sts 說明 端口號 官方 必要條件 style 開源 一、必要條件 1.1 安裝docker 1.2 安裝docker compose 二、一鍵安裝kafka集群 1 version: ‘3.5‘ 2 services: 3 zookee