简体   繁体   English

检测对超出范围的变量的访问

[英]Detecting access to out-of-scope variables

Code like this is undefined behavior, because it accesses a local variable that's no longer in scope (whose lifetime has ended). 像这样的代码是未定义的行为,因为它访问不再在范围内的本地变量(其生命周期已经结束)。

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

My question: Are there good techniques for automatically detecting bugs like this? 我的问题:有没有很好的技术可以自动检测这样的错误? It seems like it ought to be detectable (mark portions of stack space as unusable when variables go out of scope, then complain if that space is accessed), but Valgrind 3.10, Clang 4's AddressSanitizer and UndefinedBehaviorSanitizer, and GCC 6's AddressSanitizer and UndefinedBehaviorSanitizer all don't complain. 它似乎应该是可检测的(当变量超出范围时将堆栈空间的部分标记为不可用,然后在访问该空间时抱怨),但Valgrind 3.10,Clang 4的AddressSanitizer和UndefinedBehaviorSanitizer,以及GCC 6的AddressSanitizer和UndefinedBehaviorSanitizer都不要不要抱怨

Without special compiler support, non-intrusive memory debuggers such as Valgrind can detect access to stack frames that have gone out of scope, but not to scopes within functions. 如果没有特殊的编译器支持,非侵入式内存调试器(如Valgrind)可以检测对超出范围的堆栈帧的访问,但不能检测到函数内的范围。 This is because compilers (typically) allocate all the memory for a stack frame in a single pass *. 这是因为编译器(通常) 在一次通过 *中为堆栈帧分配所有内存 Therefore to detect access to out of scope variables within the same function, we need specific compiler instrumentation to "poison" variables that have gone out of scope but whose enclosing frame is still valid. 因此,为了检测对同一函数中范围外变量的访问,我们需要特定的编译器工具来“毒化”超出范围但其封闭框架仍然有效的变量。

The technique used by the ubsan AddressSanitizer , available in recent versions of clang and gcc, is to replace stack access with access to specially allocated memory : 最新版本的clang和gcc中提供的ubsan AddressSanitizer使用的技术是用访问特殊分配的内存替换堆栈访问

In order to implemented quarantine for the stack memory we need to promote stack to heap. 为了实现堆栈内存的隔离,我们需要将堆栈升级到堆。 [...] __asan_stack_malloc(real_stack, frame_size) allocates a fake frame (frame_size bytes) from a thread-local heap-like structure (fake stack). [...] __asan_stack_malloc(real_stack, frame_size)从线程局部堆状结构(伪堆栈)中分配伪帧(frame_size字节)。 Every fake frame comes unpoisoned and then the redzones are poisoned in the instrumented function code. 每个假框架都是未经处理的,然后红色区域在已检测的功能代码中被中毒。 __asan_stack_free(fake_stack, real_stack, frame_size) poisons the entire fake frame and deallocates it. __asan_stack_free(fake_stack, real_stack, frame_size)中毒整个假帧并解除分配。

Example of usage and output: 使用和输出示例:

$ 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

Note that because it is expensive it must be requested both at compile time ( -fsanitize=address ) and at run time ( ASAN_OPTIONS='detect_stack_use_after_return=1' ). 请注意,因为它很昂贵,所以必须在编译时( -fsanitize=address )和运行时( ASAN_OPTIONS='detect_stack_use_after_return=1'ASAN_OPTIONS='detect_stack_use_after_return=1' Regarding minimum versions; 关于最低版本; it works with gcc 7.1.0 and clang trunk, but apparently not any released versions of clang, so if you want to use a released compiler you'll have to use gcc. 它适用于gcc 7.1.0和clang trunk,但显然不是任何已发布的clang版本,所以如果你想使用已发布的编译器,你必须使用gcc。


* Consider that these two functions compile (eg by gcc at -O0 ) to identical machine code, so there is no way** for a non-intrusive memory debugger to tell the difference between them: *考虑到这两个函数编译(例如通过gcc at -O0 )到相同的机器代码,因此非侵入式内存调试器无法告诉它们之间的区别:

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

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

** Strictly speaking, if debug symbols are available a debugger could track variables going in and out of scope. **严格地说,如果调试符号可用,调试器可以跟踪进出范围的变量。 But generally if you have debug symbols available you have the source code, so can recompile the program with instrumentation. 但通常如果您有可用的调试符号,则您拥有源代码,因此可以使用检测重新编译该程序。

Yes. 是。 Lint is designed for this. Lint就是为此而设计的。 We use it a lot in embedded systems and automotive systems. 我们在嵌入式系统和汽车系统中经常使用它。 You can use the online demo to test out how well it would work for you. 您可以使用在线演示来测试它对您的效果如何。 In your specific case, its rule MISRA:2012:18.6. 在您的具体案例中,其规则MISRA:2012:18.6.

Sample Run 样品运行


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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM