27 września 2015

[clang] Dynamiczna i statyczna analiza programów

Coś co chciałem wypróbować już bardzo dawno temu, a mianowicie narzędzie do statycznej i dynamicznej analizy kodu/programów. Zachęcił mnie do tego przede wszystkim wykład z GoginNative 2013: The Care and Feeding of C++’s Dragons (0:51h, 1:05h). Najwyraźniej funkcjonalność którą dostarcza kompilator (clang), została wydzielona do osobnego narzędzia jakim jest scan-build, choć nie eksperymentowałem z tym rozwiązaniem za dużo. Kilka przydatnych linków:

Clang Static Analyzer

Narzędzie korzysta ze zbioru reguł, które wciąż się rozrastają. Można skorzystać z domyślnych ustawień lub samodzielnie wskazać pod jakim kątem ma być przeskanowany kod. Przykład z dzieleniem przez zero, zaczerpnięty z dokumentacji.
#include <iostream>

using namespace std;

int test(int z) {
    if (z == 0)
        return 1 / z;
    return -1;
}

int main() {
    int i = 63;
    std::cin >> i;
    test(i);
    return 0;
}
W pierwszych dwóch linijkach dwa alternatywne wywołania:
clang++ -std=c++14 --analyze -Xanalyzer -analyzer-output=text main.cpp
# inna wersja
clang++ -std=c++14 --analyze -Xanalyzer -analyzer-checker=core.DivideZero -Xanalyzer -analyzer-output=text main.cpp
Wynik:
main.cpp:7:18: warning: Division by zero
        return 1 / z;
                 ^
main.cpp:14:10: note: Passing value via 1st parameter 'z'
    test(i);
         ^
main.cpp:14:5: note: Calling 'test'
    test(i);
    ^~~~~~~
main.cpp:6:9: note: Assuming 'z' is equal to 0
    if (z == 0)
        ^~~~~~
main.cpp:6:5: note: Taking true branch
    if (z == 0)
    ^
main.cpp:7:18: note: Division by zero
        return 1 / z;
               ~~^~~
1 warning generated.
Niestety przykłady które stworzyłem samodzielnie, już nie podają takich wyników. Testy przeprowadzałem na wersji 3.6.0 oraz 3.8.0. No nic, rozwiązanie i tak jest interesujące, liczę że w przyszłości narzędzie się rozwinie.
#include <iostream>

using namespace std;

int test2(int z) {
    return 1 / z;
}

int test3(int z) {
    if (z > 15)
        return 1;
    return 1 / z;
}

int main() {
    int i = 63;
    std::cin >> i;

    test2(i);
    test3(i);

    return 0;
}

Sanitizer

Innym narzędziem, które istnieje w clang-u jest sanitizer, a właściwie ich kolekcja. Analiza przeprowadzana jest dynamicznie, więc najlepsze efekty można uzyskać, jeżeli kod był tworzony razem z testami. Sprawdziłem jedynie AddressSanitizer, ale w dokumentacji można odnaleźć informacji o innych:
#include <iostream>

using namespace std;

int main() {
    char tab1[5];
    for(int i = 0; i <= 5; ++i)
        tab1[i] = 'x';
    return 0;
}

Wywołanie:
clang++ -std=c++14 -fsanitize=address main.cpp
./a.out
Rezultat:
=================================================================
==5748==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd47e300b5 at pc 0x0000004dc5ad bp 0x7ffd47e30010 sp 0x7ffd47e30008
WRITE of size 1 at 0x7ffd47e300b5 thread T0
    #0 0x4dc5ac  (/home/beru/synetizer_test/a.out+0x4dc5ac)
    #1 0x7fcefcca4a3f  (/lib/x86_64-linux-gnu/libc.so.6+0x20a3f)
    #2 0x4350e8  (/home/beru/synetizer_test/a.out+0x4350e8)

Address 0x7ffd47e300b5 is located in stack of thread T0 at offset 53 in frame
    #0 0x4dc32f  (/home/beru/synetizer_test/a.out+0x4dc32f)

  This frame has 3 object(s):
    [32, 36) ''
    [48, 53) 'tab1' <== Memory access at offset 53 overflows this variable
    [80, 84) 'i'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
Shadow bytes around the buggy address:
  0x100028fbdfc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdfd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdfe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100028fbe010: f1 f1 f1 f1 04 f2[05]f2 f2 f2 04 f3 00 00 00 00
  0x100028fbe020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==5748==ABORTING

Brak komentarzy:

Prześlij komentarz