2017-06-22 77 views
5

Code like this是未定義的行爲,因爲它訪問不在範圍內的局部變量(其生存期已結束)。檢測對範圍外變量的訪問

int main() { 
    int *a; 
    { 
     int b = 42; 
     a = &b; 
    } 
    printf("%d", *a); // UB! 
    return 0; 
} 

我的問題:是否有自動檢測這樣的錯誤的好技術?它似乎應該是可檢測的(當變量超出範圍時,將堆棧空間的部分標記爲不可用,然後在訪問該空間時發出抱怨),但Valgrind 3.10,Clang 4的AddressSanitizer和UndefinedBehaviorSanitizer以及GCC 6的AddressSanitizer和UndefinedBehaviorSanitizer都不會抱怨。

+0

我敢打賭,Purify的確如此,但我不知道現在Valgrind和其他免費工具如何激增。 –

+0

C和C++不是同一種語言。這些日子他們非常不同。 – tambre

+2

@FrançoisAndrieux*問題工具在Stack Overflow中顯式地脫離主題*但是可能會通過「** How **可以檢測超出範圍變量的訪問權限」的簡單更改。我不認爲提問者是一個noob ... –

回答

4

沒有特殊的編譯器的支持,非侵入性的內存調試如Valgrind的可檢測訪問堆棧已經超出範圍幀,但不適用於功能範圍。這是因爲編譯器(通常)allocate all the memory for a stack frame in a single pass *。因此,爲了檢測對相同函數內超出範圍變量的訪問,我們需要特定的編譯器檢測來「毒化」超出範圍但其封閉幀仍然有效的變量。

由ubsan AddressSanitizer,在最新版本的叮噹聲和gcc的可使用的技術,是replace stack access with access to specially allocated memory

爲了堆棧存儲器來實現隔離,我們需要促進棧堆。 __asan_stack_malloc(real_stack, frame_size)從線程本地堆狀結構(假堆棧)分配虛假幀(frame_size字節)。每一個僞造的框架都是未經過中毒的,然後在儀器化的功能代碼中中毒。 __asan_stack_free(fake_stack, real_stack, frame_size)毒害整個假框架並釋放它。使用和輸出的

實施例:

$ g++ -std=c++11 a.cpp -fsanitize=address && env ASAN_OPTIONS='detect_stack_use_after_return=1' ./a.out 
ERROR: AddressSanitizer: stack-use-after-scope on address 0x7fd0e8300020 at pc 0x000000400c1b bp 0x7fff5b45ecf0 sp 0x7fff5b45ece8 
READ of size 4 at 0x7fd0e8300020 thread T0 
    #0 0x400c1a in main (a.out+0x400c1a) 
    #1 0x7fd0ebe18d5c in __libc_start_main (/lib64/libc.so.6+0x1ed5c) 
    #2 0x400a48 (a.out+0x400a48) 

Address 0x7fd0e8300020 is located in stack of thread T0 at offset 32 in frame 
    #0 0x400b26 in main (a.out+0x400b26) 

    This frame has 1 object(s): 
    [32, 36) 'b' <== Memory access at offset 32 is inside this variable 

注意,因爲它是昂貴的它必須請求都在編譯時(-fsanitize=address)和運行時(ASAN_OPTIONS='detect_stack_use_after_return=1')。關於最低版本;它適用於gcc 7.1.0和clang trunk,但顯然沒有任何發佈的clang版本,所以如果你想使用發佈的編譯器,你必須使用gcc。


*考慮這兩個功能編譯(例如,通過在GCC -O0)到相同的機器代碼,所以沒有辦法**用於非侵入式調試器存儲器告訴他們之間的區別:

int f() { 
    int* a; 
    { 
     int b = 42; 
     a = &b; 
    } 
    return *a; 
} 

int g() { 
    int* a; 
    int b = 42; 
    a = &b; 
    return *a; 
} 

**嚴格來說,如果調試符號可用,調試器可以跟蹤變量進出的範圍。但通常如果你有調試符號可用,你有源代碼,所以可以用儀器重新編譯程序。

+0

'VALGRIND_MAKE_MEM_NOACCESS'可以是用於使用工具「memcheck」在'valgrind'中明確標記內存無效。當然,自動化解決方案通常要好得多。 –

+0

非常有用。謝謝。我做了一些進一步的測試,看起來GCC 7.1和Clang 5.x的開發版本都可以在不設置'detect_stack_use_after_return = 1'的情況下在本地範圍之後檢測堆棧的使用情況(但離開函數後不一定使用堆棧)。這包括來自http://apt.llvm.org/的Clang 5.x官方開發版本,所以我至少不必親自檢查並構建它。 –

4

是的。 Lint就是爲此而設計的。我們在嵌入式系統和汽車系統中使用它很多。 You can use the online demo to test out how well it would work for you.在特定情況下,它的規則MISRA:2012:18.6.

樣品試驗


FlexeLint for C/C++ (Unix) Vers. 9.00L, Copyright Gimpel Software 1985-2014 
--- Module: misra3.c (C) 
     _ 
    1 int main() { 
misra3.c 1 Note 970: Use of modifier or type 'int' outside of a typedef [MISRA 2012 Directive 4.6, advisory] 
misra3.c 1 Note 9075: external symbol 'main(void)' defined without a prior declaration [MISRA 2012 Rule 8.4, required] 
      _ 
    2  int *a; 
misra3.c 2 Note 970: Use of modifier or type 'int' outside of a typedef [MISRA 2012 Directive 4.6, advisory] 
    3  { 
       _ 
    4   int b = 42; 
misra3.c 4 Note 970: Use of modifier or type 'int' outside of a typedef [MISRA 2012 Directive 4.6, advisory] 
         _ 
    5   a = &b; 
misra3.c 5 Info 733: Assigning address of auto variable 'b' to outer scope symbol 'a' [MISRA 2012 Rule 18.6, required] 
    6  } 
      _ 
    7  printf("%d", *a); // UB! 
misra3.c 7 Info 718: Symbol 'printf' undeclared, assumed to return int [MISRA 2012 Rule 17.3, mandatory] 
misra3.c 7 Warning 586: function 'printf' is deprecated. [MISRA 2012 Rule 21.6, required] 
misra3.c 7 Info 746: call to function 'printf()' not made in the presence of a prototype 
    8  return 0; 
        _ 
    9 } 

misra3.c 9 Info 783: Line does not end with new-line 
misra3.c 9 Note 954: Pointer variable 'a' (line 2) could be declared as pointing to const [MISRA 2012 Rule 8.13, advisory] 

/// Start of Pass 2 /// 

--- Module: misra3.c (C) 
    1 int main() { 
    2  int *a; 
    3  { 
    4   int b = 42; 
    5   a = &b; 
    6  } 
    7  printf("%d", *a); // UB! 
    8  return 0; 
    9 } 

--- Global Wrap-up 

Warning 526: Symbol 'printf()' (line 7, file misra3.c) not defined 
Warning 628: no argument information provided for function 'printf()' (line 7, file misra3.c) 
+0

它產生了太多的垃圾,所以提示的值是值得懷疑的 – Slava

+0

@Slava通常我們有「已知的安全使用」覆蓋,禁用某些規則,保持SNR的可行性。另外,如果我們想要任何投入生產的關鍵任務RTOS系統,我們通常對代碼遵守保險/法律要求,以符合MISRA等標準。 – DevNull

+1

感謝上帝我沒有這個官僚主義廢話在我們已經有的 – Slava