2017-06-22 51 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