2011-10-19 28 views
7

我需要調試一個醜陋而巨大的數學C庫,可能曾經由f2c產生過。代碼濫用本地靜態變量,不幸的是它似乎利用了這些自動初始化爲0的事實。如果它的入口函數被兩次相同的輸入調用,它會給出不同的結果。如果我卸載庫並重新加載它,它會正常工作。它需要很快,所以我想擺脫加載/卸載。跟蹤未初始化的靜態變量

我的問題是如何用valgrind或任何其他工具發現這些錯誤,而無需手動遍歷整個代碼。

我正在尋找聲明本地靜態變量的位置,先讀取,稍後再寫入。由於靜態變量有時會通過指針進一步傳遞,所以問題更加複雜(是的 - 它非常難看)。

我明白,有人可能會爭辯說,像這樣的錯誤不應該被自動工具檢測到,因爲在某些情況下,這正是預期的行爲。但是,有沒有辦法讓自動初始化的局部靜態變量「髒」?

+0

有趣的問題,但valgrind不知道「本地」或「宣佈」。我認爲這必須通過代碼分析來完成,而不是可執行分析。 – aschepler

+0

我們使用PC Lint,但它產生了很多警告的地獄,所以找到真正的熱點有時像在黑暗中釣魚。 –

回答

5

魔鬼在細節,但是這可能會爲你工作:

首先,得到Frama-C。如果你使用的是Unix,你的發行版可能會有一個包。該軟件包不會是最後一個版本,但它可能足夠好,如果以這種方式安裝它,它會爲您節省一些時間。

說你的例子如下,只有這麼多的大,這不是明顯的什麼是錯的:

int add(int x, int y) 
{ 
    static int state; 
    int result = x + y + state; // I tested it once and it worked. 
    state++; 
    return result; 
} 

類型像這樣的命令:

frama-c -lib-entry -main add -deps ugly.c 

選項-lib-entry -main add表示「看功能add 」。選項-deps計算函數依賴關係。你會在日誌中找到這些「函數依賴」:

[from] Function add: 
    state FROM state; (and default:false) 
    \result FROM x; y; state; (and default:false) 

此列出實際投入add結果依賴的對象,從這些輸入計算出的實際輸出,包括讀取靜態變量並修改。在使用之前正確初始化的靜態變量通常不會作爲輸入出現,除非分析器在讀取之前無法確定它始終被初始化。

該日誌顯示state作爲\result的依賴關係。如果您期望返回的結果僅依賴於參數(意味着具有相同參數的兩個調用會產生相同的結果),則此變量可能存在錯誤,其變量爲state

上述行中顯示的另一個提示是該功能修改了state

這可能有幫助。選項意味着分析器不會假定任何非常量靜態變量在調用分析函數時保留了它的值,因此對於您的代碼可能太不精確。有辦法可以解決這個問題,但是你是否想要花時間學習這些方法取決於你。

編輯:這是一個比較複雜的例子:

void initialize_1(int *p) 
{ 
    *p = 0; 
} 

void initialize_2(int *p) 
{ 
    *p; // I made a mistake here. 
} 

int add(int x, int y) 
{ 
    static int state1; 
    static int state2; 

    initialize_1(&state1); 
    initialize_2(&state2); 

    // This is safe because I have initialized state1 and state2: 
    int result = x + y + state1 + state2; 

    state1++; 
    state2++; 
    return result; 
} 

在這個例子中,相同的命令產生的結果:

[from] Function initialize_1: 
     state1 FROM p 
[from] Function initialize_2: 
[from] Function add: 
     state1 FROM \nothing 
     state2 FROM state2 
     \result FROM x; y; state2 

你所看到的initialize_2是依賴一個空列表,意味着該功能不分配任何東西。我會通過顯示一個明確的消息而不僅僅是一個空的列表來使這個例子更加清晰。如果您知道initialize_1,initialize_2或或add應該執行的任何功能,則可以將此先驗知識與分析結果進行比較,並看到initialize_2add有什麼錯誤。

第二次編輯:現在我的例子顯示initialize_1奇怪的東西,所以也許我應該解釋一下。變量state1取決於p,因爲p用於寫入state1,並且如果p已經不同,則最終值state1本應不同。下面是最後一個例子:

int t[10]; 

void initialize_index(int i) 
{ 
    t[i] = 1; 
} 

int main(int argc, char **argv) 
{ 
    initialize_index(argv[1][0]-'0'); 
} 

隨着命令frama-c -deps t.c,計算了initialize_index的依賴關係是:

[from] Function initialize_index: 
     t[0..9] FROM i (and SELF) 

這意味着每個單元的取決於i如果i是(它可被修改該特定小區的索引)。每個小區還可以保持它的價值(如果i表示另一個單元格):這表明,在最新版本的(and SELF)提及並表明與以前的版本更加晦澀(and default:true)

+0

謝謝!這聽起來很有用。我不會嘗試它,因爲我已經用下面最原始的方法發現了我的錯誤。順便說一句,Frama-C可以通過指針傳遞變量嗎?在我的情況下,靜態信息會被髮送,並且它們可能會以不同的功能進行初始化。 – takbal

+0

@takbal是的,它可以,但是在指針存在的情況下,您可能需要描述更精確地調用函數的內存狀態。我會用更完整的例子來編輯我的答案。在http://frama-c.com/download/frama-c-value-analysis.pdf第2.5.2節中,該分析用於檢查完整加密散列函數的依賴性,指針和所有(但不是因爲代碼乾淨而且沒有什麼可說的,所以這是最顯着的例子。 –

1

我不知道有任何圖書館爲你做這件事,但我會研究使用正則表達式來找到它們。像

rgrep「static \ s * int」path/to/src/root | grep -v = | grep -v「(」

這應該返回所有聲明沒有等號的靜態int變量,並且最後一個管道應該刪除任何帶括號的東西(擺脫funcions)。有一個很好的變化,這將不會爲你準備工作,但玩grep可能是最快的方式跟蹤這個。

當然,一旦你找到一個工程,你可以用所有其他類型的變量替換int來搜索HTH

+0

一個恥辱,但幾乎所有的變量都聲明爲靜態的,沒有初始化...它可能是f2c的錯誤行爲。 – takbal

0

我的問題是如何發現這些錯誤...

但是這些不是錯誤:將靜態變量初始化爲0的期望是完全有效的,正如向其分配一些其他值。

因此要求一個工具,它會自動找到-錯誤不可能產生令人滿意的結果。

從您的描述看,somefunc()第一次調用時會返回正確的結果,並且在後續調用時會返回錯誤結果。

調試這種問題的最簡單方法是並排放置兩個GDB會話:一個是新加載的(將計算正確的答案),另一個是「第二個迭代」(將計算錯誤的答案)。然後「並行」地通過兩個會話,並查看他們的計算或控制流開始發散的位置。

由於通常可以將問題有效地分爲兩半,所以通常不需要很長時間就可以找到問題。錯誤總是重現是最容易找到的。去做就對了。

+0

正如我在原文中所說的,我明白從編譯器/鏈接器的角度來看,它不是一個錯誤。問題更多的是如何將這種用法轉化爲可以自動檢測的錯誤。 – takbal

1

靜態代碼分析工具非常適合尋找典型的編程錯誤,如使用未初始化的變量。Here是免費工具列表。C.

不幸的是,我不能推薦列表中的任何工具。我只熟悉兩種商業產品CoverityKlocwork。 Coverity非常好(而且很貴)。 Klocwork是如此(但更便宜)。

+0

我曾嘗試夾板,但沒有說有用。 – takbal

+0

爲什麼不聲明所有局部變量初始化爲零?這是一個很大的代碼庫嗎? – Miguel

+0

是的,它是巨大的。你是對的,我當然可以做一些正則表達式技巧來把所有的定義轉化爲基於0的init,甚至是NaN。這是我最後還沒有嘗試過的一條路。 – takbal

1

我在最後做了什麼就是通過'#define static'從代碼中刪除所有靜態限定符。這會將未初始化的靜態用法變爲無效使用,並且我正在尋找的濫用類型可以通過工具找到。

在我的實際情況下,這足以確定錯誤的位置,但在更一般的情況下,如果靜態實際上正在做重要的事情,應該進行改進,通過在代碼失敗時逐漸重新添加「靜態」繼續。