Open4

Arduinoをclangの静的解析に掛けたい

okuokuokuoku

Arduinoはgccでビルドされるのが前提なため、gccでビルドするしか無い。しかし、できればclangの静的解析を活用したい。double-freeのようなアホを事前に抽出できる意義はそれなりに有るのではないかと感じている。

okuokuokuoku

手動で試す

前回の 手動Arduino で、Arduinoスケッチをビルドするのに必要なコマンドラインはライブラリ類を含めて既知になったので、

  1. 一旦 gcc でスケッチをプリプロセスする
  2. プリプロセスして出てきたソースコードをclangに掛ける

の手順でできるかどうか試してみた。

問題のあるソースコード

#include <Arduino.h>

#include "FreeRTOS.h"
/* OpenOCD stub */
const volatile UBaseType_t uxTopUsedPriority = configMAX_PRIORITIES - 1U;

void setup() {
    (void) uxTopUsedPriority;
  pinMode(LED_BUILTIN, OUTPUT);
}

void act(void* p){
    free(p);
}

void loop() {
  void* p;
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
  p = malloc(12);
  act(p);
  free(p);
}
  • 静的解析 (--analyze オプションを渡す)
clang -target arm-none-eabihf --analyze out.cpp
../blink.cpp:24:3: warning: Attempt to free released memory [unix.Malloc]
  free(p);
  ^~~~~~~
1 warning generated.

静的解析は正しくdouble-freeを検出できている。

  • 警告 (-S オプションを渡す)
clang -Wall -Wextra -target arm-none-eabihf -S out.cpp
In file included from ../blink.cpp:1:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/Arduino.h:41:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/WVariant.h:23:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk/nrf.h
:164:
../Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk/compiler_abstraction.h:154:
16: warning:
      variable 'sp' is uninitialized when used here [-Wuninitialized]
        return sp;
               ^~
../Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk/compiler_abstraction.h:153:
29: note:
      initialize the variable 'sp' to silence this warning
        register unsigned sp __asm("sp");
                            ^
                             = 0
In file included from ../blink.cpp:1:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/Arduino.h:58:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/HardwarePWM.h:39:
In file included from ../Adafruit_nRF52_Arduino/cores/nRF5/common_inc.h:47:
../Adafruit_nRF52_Arduino/cores/nRF5/verify.h:64:18: warning: format string is
      not a string literal (potentially insecure) [-Wformat-security]
        ::printf(_fstr(_status));
                 ^~~~~~~~~~~~~~
../Adafruit_nRF52_Arduino/cores/nRF5/verify.h:64:18: note: treat the string as
      an argument to avoid this
        ::printf(_fstr(_status));
                 ^
                 "%s",
2 warnings generated.

-Wuninitialized 警告は謎だな。。

compiler_abstraction.h
    #define GET_SP()                gcc_current_sp()

    static inline unsigned int gcc_current_sp(void)
    {
        register unsigned sp __ASM("sp");
        return sp;
    }

これでuninitializedになっちゃうのはちょっと不味くない。。?

okuokuokuoku

gccとclangでasmの解釈が違う

試しに:

int another(int a, int b);

static inline unsigned int gcc_current_sp(void) {
    register unsigned sp __asm("sp");
    return sp;
}

int
work(void){
    return another(gcc_current_sp(), gcc_current_sp());
}

-O3 でビルドしてみた。

  • gcc
work:
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 0, uses_anonymous_args = 0
        @ link register save eliminated.
        mov     r1, sp
        mov     r0, sp
        b       another
  • clang
work:
        .fnstart
        b       another
.Lfunc_end0:
        .size   work, .Lfunc_end0-work
        .cantunwind
        .fnend

clangの出力には mov 命令が無い。つまり、 asm 文によるレジスタアクセスができていないことがわかる。... まぁすげぇそもそも論として、このような方法でレジスタをフェッチすること自体に移植性が無いので仕方ない気もするが。。

clangのドキュメント によると:

clang only supports global register variables when the register specified is non-allocatable (e.g. the stack pointer).

で、これはgccで言うところの Local register variable は含んでない。ということかな。。

okuokuokuoku

作戦を考える

とりあえず↑のような問題はsuppression file的な奴を用意できれば解決するとして、それを適用するためには機械可読な出力がツールチェインから出てきて欲しい。。。が clangの SARIF サポートは開発中 で今のところStaticAnalyzerしかサポートしていない。

まず前処理段階が本当にできるのかを確認する必要があるな。つまり、

  1. ビルド時のコマンドラインの収集 -- これは 前作った奴 がある。
  2. 収集したコマンドラインデータをCMakeから読める形に整形する -- まぁ単純な.tsvとかテキストファイルにすれば十分だろう
  3. (コマンドラインに現われるパスを正規化する -- 今回は実装しない)
  4. (正規化されたコマンドラインを元にコンパイラ起動を一意に識別するhashを用意する -- 今回は実装しないで単純にコマンドラインを連結してソート、連番を振る)
  5. コマンドラインを書き換えて gccで プリプロセスする。出力ファイル名としてはhashを使う。
  6. プリプロセス結果を clangで 静的解析する

正規化はちょっと良いアイデアがない。。ビルドディレクトリとかはCIの都合で固定できない可能性があるから正規化を前提にした方が良いと思うんだけど。。