国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Linux下的代碼淬火技術

2019-11-17 05:27:39
字體:
來源:轉載
供稿:網友
    我們知道,鐵匠經常把金屬工件加熱到一定溫度,然后忽然浸在水或油中使其冷卻,從而有效增加其硬度。類似的,軟件開發人員也可以采取某種手段,從而顯著提高軟件的可靠性和質量,我們形象地稱之為代碼淬火技術。     一、代碼淬火的好處    理想中,軟件淬火技術首先能夠預見代碼中的哪些地方可能出錯,然后,要么帶給我們一種能夠完全避免錯誤的方式編寫代碼,要么能及時識別出錯誤,從而更輕松地跟蹤源代碼。尤其是對于C語言,因為它不是一種安全語言,所以更需要軟件淬火技術來提高軟件的可靠性。本文將具體介紹linux下常用的代碼淬火方法。    二、常見的碼淬火技術    代碼淬火技術的形式各種各樣,我們這里要講的是幫助我們構建更加強健的代碼的各種技術。    1 .緩沖區溢出問題    緩沖區是一個非常嚴重的安全問題,最好的情況下可能導致軟件的行為錯亂;嚴重時將會導致被緩沖區利用程序所控制而執行任何攻擊者所想要執行的代碼。請看下面的示例代碼:
static char ourArray[100];
    ...
    int i;
    for ( i = 0 ; i < 100 ; i++ ) {
      ourArray[i] = (char)(0x30+i);
    }
    ourArray[i] = 0;    // 越界,危險!
    上例中,我們的賦值操作已經越過了數組的邊界。它并沒有在我們的數組內執行寫操作,它是在數組邊界之后的第一個字節內進行的寫操作。換句話說,數組之后的第一個對象,無論它是結構體也好,還是指針也罷,現在已經被破壞了。為了改變這種糟糕的情況,我們只需要養成一種良好的習慣,那就是使用符號常量來指示數組的邊界。就像在下例中一樣,我們創建了一個常量,來定義數組的長度。這里有一個小技巧,那就是假設我們需要的數組長度為A,那么在定義數組長度時,實際長度是A+1。這時,無論數組長度如何,可以肯定的是,數組[A]一定是該數組的邊界。換句話說,我們留了一個元素的余量。如下所示:
#define ARRAY_SIZE    100
    static char ourArray[ARRAY_SIZE+1];
    ...
    int i;
    for (i = 0 ; i < ARRAY_SIZE ; i++ ) {
      ourArray[i] = (char)(0x30+i);
    }
ourArray[ARRAY_SIZE] = 0;  //為數組的最后一個元素賦值
    2.檢驗返回值    現在,軟件中最常見的錯誤是忽略了返回值的檢驗。許多程序員在調用系統函數或用戶自定義的函數后,通常會盲目樂觀地認為這些函數會成功的執行。當我們打造經淬火處理的軟件時,應當檢查返回值,并且在返回失敗時,還要進行妥善處理。比如:
    ret =     if ( ret < 0 ) {
      ret = printf( "An error occured emitting value./n" );
    }
    3.檢查輸入/輸出數據    當我們開發應用時,無論涉及到用戶的輸入,或者是通過網絡進行輸入,一定要嚴密關切輸入數據的情況:比如對于給定的操作數據是否充分,或者收到的數據是否超過了預留的緩沖區空間。    4.為分支語句提供備選方案    對于switch語句來說,經常會遺漏default部分,這會導致無法預料的后果。舉例來說:
switch( mode ) {
      case
OperaTIONAL_MODE:
        /* 切換到運行模式進行處理 */
        break;
      case BUILT_IN_TEST_MODE:
        /* 切換到測試模式進行處理*/
        break;
    }


    假設現在我們又添加了一種模式,但是上面的代碼段沒有及時得到更新,這時假如執行了這段代碼,執行結果將無法預料。假如包括default部分,至少也能利用該部分在出現問題時通知調用者,即使在此放一個assert,也能在調試時捕捉當時的狀況。如下例所示:
switch( mode ) {
      case OPERATIONAL_MODE:
        /*切換到運行模式進行處理 */
        break;
      case BUILT_IN_TEST_MODE:
        /* 切換到測試模式進行處理 */
        break;
      default:
        assert(0);
        break;
}
    上述問題除了存在于switch語句外,還存在于if/then/else分支語句中。下面分別舉例說明:
float coefficient = 0.0;
    if (state == FIRST_STAGE) coefficient = 0.75;
    else if (state == SECOND_STAGE) coefficient = 1.25;
    作者的意圖很明顯,它是希望根據變量state的值的不同,也讓變量coefficient取不同的值。但是假如變量state的值被破壞(比如碰到上面提到的緩沖區溢出的破壞),或者使它取了一個預期之外的值,那么變量coefficient的值就會一直是0.0。假如這是用來計算付費的話,后果的嚴重可想而知。因此,else分支語句至少應該包含一個分支用來捕捉錯誤,例如:
float coefficient = 0.0;
    if (state == FIRST_STAGE) coefficient = 0.75;
    else if (state == SECOND_STAGE) coefficient = 1.25;
    else coefficient = SAFE_ COEFFICIENT;
    盡管許多情況下尾隨的else分支不是必須的,但這樣做無疑為我們的代碼加了一把保護傘,以免鑄成大錯。   4.自標識結構體    在強類型語言中,假如使用非法的數據類型將會導致運行時錯誤。但對于弱類型語言,比如C語言,在指針的傳遞和類型轉換過程中,很輕易把類型混淆。為此,我們引入類似于強類型語言中的運行時類型檢驗思想。    現在我們通過實例來講解自標識結構體,具體代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
            #include <assert.h>
           
            #define TARGET_MARKER_SIG       0xFAF32000
           
            typedef strUCt {
           
              unsigned int signature;
              unsigned int targetType;
              double       x, y, z;
           
            } targetMarker_t;
           
           
            #define INIT_TARGET_MARKER(ptr) /
                      ((( targetMarker_t *)ptr)->signature = TARGET_MARKER_SIG)
            #define CHECK_TARGET_MARKER(ptr) /
                      assert(((targetMarker_t *)ptr)->signature == /
                                TARGET_MARKER_SIG)
           
           
            void displayTarget( targetMarker_t *target )
            {
           
              /* 預先檢查 target結構體 */
              CHECK_TARGET_MARKER(target);
           
             printf( "Target type is %d/n", target->targetType );
           
              return;
            }
           
           
            int main()
            {
              void *object1, *object2;
           
              /* 新建兩個對象 */
              object1 = (void *)malloc( sizeof(targetMarker_t) );
              assert(object1);
              object2 = (void *)malloc( sizeof(targetMarker_t) );
              assert(object2);
              /*按照target marker結構體初始化object1 */
              INIT_TARGET_MARKER(object1);
           
              /* 嘗試顯示object1 */
              displayTarget( (targetMarker_t *)object1 );
           
              /* 嘗試顯示object2 */
              displayTarget( (targetMarker_t *)object2 );
           
              return 0;
            }

    在代碼的第6-12行,定義了我們的目標結構體,其中有一個專門的頭部,名為signature(識別標志),即該結構類型的運行時類型標識符。并且在第4行為該類型定義了一個識別標志,作為該結構類型的唯一描述符號。此外,代碼中還提供了兩個宏INIT_TARGET_MARKER和CHECK_TARGET_MARKER,分別用來初始化和檢驗該結構體中的識別標志。    繼續往后看,請注重代碼的34-54行,其中分配了兩個等同長度(targetMarker_t)的內存空間來供兩個對象使用,然后利用宏INIT_TARGET_MARKER將其中一個初始化,最后,分別利用displayTarget函數顯示。    對于第22-31行的displayTarget函數,首先調用CHECK_TARGET_MARKER來檢驗收到的對象的識別標志,假如該標志非法,assert就會發揮作用。當然,這里只是一個概念性的演示。  5.錯誤報告    對于錯誤報告而言,根據開發的應用類型的不同,需要采取不同的處理方法。例如,假如開發的是命令行工具的話,常用的方法是通過把錯誤消息遞給stderr來告知用用戶出錯情況。假如開發的是諸如嵌入式Linux應用之類的具有I/O能力的應用程序的話,錯誤報告的形式就多了,比如專門的日志或標準系統日志(syslog)。其中,syslog函數的原型如下所示:
    #include <syslog.h>
    void syslog( int priority, char *format, ... );
    對于syslog函數,我們需要提供優先級、格式化字符串等參數。它的用法類似于printf函數。優先級可以從這里選擇一個:LOG_EMERG、 LOG_ALERT、LOG_CRIT、LOG_ERR、LOG_WARNING、LOG_NOTICE、LOG_INFO或者 LOG_DEBUG。 下面以實例說明如何使用syslog函數為系統日志生成一個消息:
#include <syslog.h>
           
            int main()
            {
           
              syslog( LOG_ERR, "Unable to load configuration!" );
           
              return 0;
            }
    程序執行后,位于 /var/log/_messages的系統日志更新如下:
 Aug 16 21:33:16 Ian test: Unable to load configuration!
    本例中,test是上面列出的程序名,Ian是主機名。系統日志的好處是可以記錄許多的錯誤報告。憑借它,開發人員能夠了解消息在哪里生成的、哪一個能夠幫助理解問題等等。對于系統應用和守護進程來說,syslog的格外有用。    除此之外,錯誤報告還有一個注重事項,那就是報告的錯誤一定要具體。為了能夠讓用戶恰當的處理發生的錯誤,錯誤消息一定要唯一的標識出錯誤來。錯誤消息本身不能模棱兩可,或者具有歧義性。   6.降低代碼的復雜性    降低復雜性就等于降低出錯的機率,因為代碼越復雜,包含bug的可能性越大,并且找到bug的難度也越大。為此,我們可以將一個復雜的代碼段,分解成多個更輕易理解的幾段代碼。這樣一來,隨著代碼的可維護性的提高,軟件質量自然有很大提升。     7.自保護性函數    使用自保護性函數是一種有效的調試機制,它能保證軟件的正確性。這里的自保護意味著,當你寫一個函數的時候,必需審核該函數的輸入;并且在輸入數據處理成后,還要審核數據的輸出,從而確保沒有出錯。下面我們用一個實例函數加以說明:
STATUS_T checkRegisterStatus( REGISTER_T register, MODE_T *mode )
            {
              REGISTER_STS_T retStatus;
           
              /* 驗證輸入 */
              assert( validRegister( register ) );
              assert( validMode( mode ) );
           
           
              /*--------------------*/
              /*這里省略checkRegisterStatus 的內部處理部分 */
              /*--------------------*/
           
           
              /* 可能改變了模式,檢驗之 */
              assert( validMode( mode ) );
           
              return retStatus;
            }

    注重,假如表達是結果為非(即0),assert函數就會停止應用,并在標準輸出設備上生成一個錯誤消息。可以通過定義符號NDEBUG的方法來停用assert。    從上例中我們看到,函數首先通過驗證輸入來確保得到的數據是正確的,然后通過驗證輸出來保證它給出的數據也是正確的。根據具體情況,我們可能收到錯誤消息,即使這樣我們也能輕而易舉地發現錯誤之所在。另外,assert的作用不僅限于確保函數的輸入輸出的正確性,還能用來確保內部的一致性。所有應該在調試期間發現的嚴重錯誤,都可以利用assert來輕松處理。    8.優化調試輸出    太多的輸出能夠掩蓋錯誤,但輸出過少也會漏掉錯誤,因此我們需要尋找一個平衡點,使得提供的調試輸出和錯誤消息夠用,但又不會過量,這需要在實踐中具體把握。   9.內存調試技術    在Linux中,有許多程序庫都可以用來調試動態內存治理。最常見的 Electric Fence是一個功能強大并且能及時發現內存錯誤的庫,它不僅可以利用低層處理器的內存治理單元MMU的段故障來捕捉內存錯誤,而且還能偵察數組越界問題。    10.編譯器的支持    實際上,編譯器本身就是一個識別代碼問題的無價之寶,當構建程序時,一定要使用-Wall項來啟用報警功能。    此外,還可以利用-Werror把警告作為錯誤對待,從而停止對源文件進行進一步的編譯,這選項對于大型應用程序格外有用。當我們構建有多個源文件組成的應用程序時,我們可以將兩個選項組合使用。如下例所示:
    gcc -Wall -Werror test.c -o test
    假如我們想讓源代碼能夠兼容ANSI,我們還能利用編譯器來進行檢查,用法如下所示:
    gcc -ansi -pedantic test.c -o test
    確認變量已經初始化是非常有用的,除了使用報警選項外,還需要使用優化選項,因為只有經過優化的代碼才能使用數據流信息:
    gcc -Wall -O -Wuninitialized test.c -o test
    要想了解更多,可以參考gcc的main頁。    三、 小結    本文具體介紹了代碼淬火技術的概念以及Linux下常用的幾種代碼淬火方法。在下一篇文章中,我們將繼續介紹用來提高Linux應用程序安全性和可靠性的開源工具和代碼追蹤技術。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 射洪县| 小金县| 长岛县| 永仁县| 蚌埠市| 杂多县| 班玛县| 罗源县| 融水| 新巴尔虎左旗| 榕江县| 黑河市| 高阳县| 威远县| 柳江县| 青田县| 石林| 龙胜| 阳朔县| 阳高县| 惠州市| 九江市| 静安区| 阿瓦提县| 邯郸市| 马鞍山市| 涞水县| 乌拉特中旗| 鄂伦春自治旗| 岫岩| 奈曼旗| 龙门县| 秦皇岛市| 孙吴县| 突泉县| 商南县| 乌兰察布市| 大厂| 平利县| 庄河市| 扬中市|