静的アナラむザヌを䜿甚したValgrind Dynamic Analyzerコヌドの確認

静的および動的分析の友情 静的分析が動的分析よりも優れおいるこずを瀺すために、この蚘事はたったく曞かれおいないずすぐに蚀わなければなりたせん。 そのような声明は虚停であり、逆も同様です。 静的および動的分析ツヌルは盞互に補完し合うものであり、互いに競合するこずはありたせん。 それらずそれらには長所ず短所がありたす。 動的アナラむザヌでは怜出できない゚ラヌもあれば、静的゚ラヌを怜出できないものもありたす。 したがっお、この蚘事は、2぀の方法論の比范ずしおではなく、PVS-Studioの機胜の別のデモずしおのみ扱う必芁がありたす。



動的および静的コヌド分析の方法論



プログラムの゜ヌスコヌドには、゚ラヌの特定に圹立぀ヒントが含たれおいたす。 簡単な䟋を考えおみたしょう。



char *str = foo(); if (str == '\0')
      
      





ポむンタヌをnullptr 、 NULL、たたは少なくずも0 、぀たり文字リテラル'\ 0'ず比范しないのは奇劙です。 この奇劙なこずに基づいお、静的コヌドアナラむザヌは、ポむンタヌが0であるのではなく、文字列が空であるこずを本圓に確認したいず考えおいる可胜性がありたす。 ぀たり 行の先頭に終端れロがあるこずを確認したかったのですが、誀っおポむンタヌを逆参照するのを忘れおいたした。 ほずんどの堎合、これは本圓に間違いであり、正しいコヌドは次のようになりたす。



 char *str = foo(); if (*str == '\0')
      
      





コンパむル䞭に、そのような情報は倱われ、動的アナラむザヌはこの゚ラヌを怜出できたせん。 動的アナラむザヌの芳点からは、ポむンタヌのNULLがチェックされたす 。心配する必芁はありたせん。



動的アナラむザヌのもう1぀の匱点は、゚ラヌを含むコヌドを実行する必芁があるこずです。 たた、コヌドの倚くのセクションでは、これを行うのは非垞に困難です。 実際のアプリケヌションから取埗したコヌドの䟋を䜿甚しお説明したす。



 ADOConnection* piTmpConnection = NULL; hr = CoCreateInstance( CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDataSourceLocator, (void**)&dlPrompt ); if( FAILED( hr ) ) { piTmpConnection->Release(); dlPrompt->Release( ); return connstr; }
      
      





CoCreateInstance関数が倱敗した堎合、 piTmpConnection nullポむンタヌは逆参照されたす。 実際、 piTmpConnection-> Releaseずいう行がありたす。 接続がただ䜜成されおいないため、単に䞍芁です。



CoCreateInstance関数が゚ラヌステヌタスを返す堎合、状況を゚ミュレヌトする必芁があるため、動的アナラむザヌを䜿甚しおこのような状況を識別するこずは問題です。 これは簡単ではありたせん。



玔粋に理論的には、静的アナラむザヌはコヌドに関する情報を持っおいるため、動的アナラむザヌよりも倚くの゚ラヌを怜出できたす。 実際には、静的アナラむザヌの機胜は、䜿甚可胜なメモリヌの量ず蚱容可胜なランタむムによっお制限されたす。 ぀たり、静的アナラむザヌは、考えられるすべおの入力デヌタでコヌドがどのように機胜するかを考慮するこずができたす。 しかし、圌はクラスタヌで150幎間、さらに膚倧な量のメモリがむンストヌルされたす。



その結果、実際には、静的アナラむザヌは倚くのタむプの゚ラヌを怜出できたせん。 たずえば、倚くの関数間でポむンタヌが枡される堎合、リヌクに気付かない。 動的アナラむザヌは、コヌドの耇雑さに関係なく、そのようなタスクの優れた仕事をしたす。



怜蚌結果



静的コヌド分析の方法論を普及させるために、さたざたなプロゞェクトを定期的にチェックしおいたすが、Valgrindのようなプロゞェクトを枡すこずはできたせんでした。 間違いを芋぀けるこずは䞀皮の挑戊です。 これは高品質のテスト枈みプロゞェクトであり、Coverityアナラむザヌによっおもチェックされたす。 ずにかく、このコヌドはさたざたなツヌルを䜿甚する愛奜家によっおテストされたず確信しおいたす。 そのため、いく぀かの間違いを芋぀けるこずでさえ、倧成功です。



PVS-StudioアナラむザヌがValgrindプロゞェクトコヌドで芋぀けたものを芋おみたしょう。



 static void lk_fini(Int exitcode) { .... VG_(umsg)(" taken: %'llu (%.0f%%)\n", taken_Jccs, taken_Jccs * 100.0 / total_Jccs ?: 1); .... }
      
      





è­Šå‘ŠPVS-StudioV502「」挔算子は、予想ずは異なる方法で動䜜する可胜性がありたす。 「」挔算子の優先順䜍は、「/」挔算子よりも䜎くなっおいたす。 lk_main.c 1014



オペレヌタヌ非垞に陰湿であり、非垞に慎重に䜿甚する必芁がありたす。 このトピックに぀いおは、 小さな本の第4章で詳しく説明したした。 このコヌドが疑わしい理由を考えおみたしょう。



プログラマヌはれロ陀算から身を守りたいず思ったようです。 したがっお、倉数total_Jccsが0に等しい堎合、 陀算は1で実行する必芁がありたす。 コヌドは次のように機胜するこずが蚈画されおいたした。



 taken_Jccs * 100.0 / (total_Jccs ?: 1)
      
      





ただし、挔算子の優先順䜍は、乗算および陀算挔算子の優先順䜍よりも䜎くなっおいたす。 したがっお、匏は次のように評䟡されたす。



 (taken_Jccs * 100.0 / total_Jccs) ?: 1
      
      





ただし、おそらくコヌドは意図したずおりに機胜したす。 ずにかく、これが圓おはたる堎合は、括匧を远加しお、他のプログラマヌが将来゚ラヌがあるかどうか疑問に思わないようにするこずをお勧めしたす。



 static Bool doHelperCall (....) { .... UInt nVECRETs = 0; .... vassert(nVECRETs == (retTy == Ity_V128 || retTy == Ity_V256) ? 1 : 0); .... }
      
      





è­Šå‘ŠPVS-StudioV502「」挔算子は、予想ずは異なる方法で動䜜する可胜性がありたす。 「」挔算子の優先順䜍は、「==」挔算子よりも䜎くなっおいたす。 host_arm_isel.c 795



興味深いケヌス。 The ?:挔算子は誀っお䜿甚されおいたすが、コヌドは正しいです。



チェックは次のように機胜するはずだず考えられおいたした。



 nVECRETs == ((retTy == Ity_V128 || retTy == Ity_V256) ? 1 : 0)
      
      





ただし、次のように機胜したす。



 (nVECRETs == (retTy == Ity_V128 || retTy == Ity_V256)) ? 1 : 0
      
      





おもしろいこずに、よく芋るず、これらのチェックが同等であるこずがわかりたす。 結果は同じになりたす。



同様のチェックはこちらです





 typedef ULong DiOffT; typedef struct { Bool fromC; DiOffT off; SizeT size; SizeT used; UChar data[]; } CEnt; static Bool is_sane_CEnt (....) { .... CEnt* ce = img->ces[i]; .... if (!(ce->size == CACHE_ENTRY_SIZE)) goto fail; if (!(ce->off >= 0)) goto fail; // <= if (!(ce->off + ce->used <= img->real_size)) goto fail; .... }
      
      





PVS-Studio譊告V547匏 'ce-> off> = 0'は垞にtrueです。 笊号なしの型の倀は垞に> = 0です。image.c 147



offメンバヌは笊号なしの型の倉数です。぀たり、垞にれロ以䞊です。 したがっお、条件Ce-> off> = 0は垞にfalseです。



 static void sdel_Counts ( Counts* cts ) { memset(cts, 0, sizeof(Counts)); free(cts); }
      
      





PVS-Studio譊告V597コンパむラヌは、 'cts'オブゞェクトのフラッシュに䜿甚される 'memset'関数呌び出しを削陀できたした。 プラむベヌトデヌタを消去するには、memset_s関数を䜿甚する必芁がありたす。 cg_merge.c 324



どうやら、Valgrind自䜓の゚ラヌの怜玢を簡玠化するために、メモリは解攟される前にれロで埋められたす。 ただし、リリヌスバヌゞョンでは、コンパむラはmemset関数呌び出しを削陀する可胜性がありたす。これは、 free関数が呌び出されるたでバッファヌが䜿甚されなくなるためです。



メモリがリセットされない可胜性がある同様の堎所





 static Bool dis_AdvSIMD_scalar_shift_by_imm(DisResult* dres, UInt insn) { .... ULong nmask = (ULong)(((Long)0x8000000000000000ULL) >> (sh-1)); .... }
      
      





PVS-Studio譊告V610䞍特定の動䜜。 シフト挔算子「>>」を確認しおください。 巊のオペランド 'Long0x8000000000000000ULL'は負です。 guest_arm64_toIR.c 9428



右にシフトされたオペランドの倀が負の堎合、結果の倀は実装に䟝存したす実装定矩。 したがっお、私たちは危険なコヌドを扱っおいたす。



ここで、 NULLがチェックされる前にポむンタヌの逆参照が行われる状況を考えたす 。



 PRE(xsm_op) { struct vki_xen_flask_op *op = (struct vki_xen_flask_op *)ARG1; PRINT("__HYPERVISOR_xsm_op ( %u )", op->cmd); // <= PRE_MEM_READ("__HYPERVISOR_xsm_op", ARG1, sizeof(vki_uint32_t) + sizeof(vki_uint32_t)); if (!op) // <= return; .... }
      
      





PVS-Studio譊告V595 nullptrに察しお怜蚌される前に、「op」ポむンタヌが䜿甚されたした。 行を確認しおください350、360。syswrap-xen.c 350



同様のケヌス





 Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) { .... if (inrw && sdynbss_present) { vg_assert(di->sbss_present); sdynbss_present = False; vg_assert(di->sbss_svma + di->sbss_size == svma); di->sbss_size += size; .... } else // <= if (inrw && !di->sbss_present) { di->sbss_present = True; di->sbss_svma = svma; di->sbss_avma = svma + inrw->bias; .... }
      
      





PVS-Studio譊告V705「else」ブロックが忘れられおいるかコメントアりトされおいる可胜性があり、そのためプログラムの操䜜ロゞックが倉曎されおいたす。 readelf.c 2231



elseキヌワヌドは、このコヌドでは非垞に疑わしく芋えたす。 コヌドは、その䜜業のロゞックに埓っお調敎されおいたせん。 さらに、 else行の埌に空の行が続きたす。 これは、倱敗したコヌドのリファクタリングの結果に盎面しおいるず仮定し、 それ以倖は䞍芁です。



 static Bool doHelperCallWithArgsOnStack (....) { .... if (guard) { if (guard->tag == Iex_Const && guard->Iex.Const.con->tag == Ico_U1 && guard->Iex.Const.con->Ico.U1 == True) { /* unconditional -- do nothing */ } else { goto no_match; //ATC cc = iselCondCode( env, guard ); } } .... }
      
      





PVS-Studio譊告V779到達䞍胜コヌドが怜出されたした。 ゚ラヌが存圚する可胜性がありたす。 host_arm_isel.c 461



コヌドの行



 cc = iselCondCode( env, guard );
      
      





実行されなかった



 void reset_valgrind_sink(const char *info) { if (VG_(log_output_sink).fd != initial_valgrind_sink.fd && initial_valgrind_sink_saved) { VG_(log_output_sink).fd = initial_valgrind_sink.fd; VG_(umsg) ("Reset valgrind output to log (%s)\n", (info = NULL ? "" : info)); } }
      
      





PVS-Studio譊告V547匏 'void *0'は垞にfalseです。 server.c 110



アナラむザヌの譊告は奇劙に芋え、説明が必芁です。



次の匏に興味がありたす。



 (info = NULL ? "" : info))
      
      





NULLマクロはvoid *0に展開され 、次のようになりたす。



 (info = ((void *) 0) ? "" : info))
      
      





挔算子の優先床は=挔算子の優先床よりも高いため、蚈算は次のようになりたす。



 (info = (((void *) 0) ? "" : info)))
      
      





挔算子の条件void *0が奇劙に芋えるこずに同意したす。これはPVS-Studioアナラむザヌが譊告するものです。 どうやら、私たちはタむプミスを扱っおおり、コヌドは次のようになっおいるはずです。



 (info == NULL ? "" : info))
      
      





そしお今日の最埌のコヌド



 void genReload_TILEGX ( /*OUT*/ HInstr ** i1, /*OUT*/ HInstr ** i2, HReg rreg, Int offsetB ) { TILEGXAMode *am; vassert(!hregIsVirtual(rreg)); am = TILEGXAMode_IR(offsetB, TILEGXGuestStatePointer()); switch (hregClass(rreg)) { case HRcInt64: *i1 = TILEGXInstr_Load(8, rreg, am); break; case HRcInt32: *i1 = TILEGXInstr_Load(4, rreg, am); break; default: ppHRegClass(hregClass(rreg)); vpanic("genReload_TILEGX: unimplemented regclass"); break; } }
      
      





PVS-Studio譊告V751パラメヌタヌ「i2」は関数本䜓内では䜿甚されたせん。 host_tilegx_defs.c 1223



ここでは、他の同様の関数で行われたように、 i2でNULLを曞き蟌むのを忘れおいたず思いたす。



 *i1 = *i2 = NULL;
      
      





同様の゚ラヌはこちらです



V751パラメヌタヌ 'i2'は関数本䜓内では䜿甚されたせん。 host_mips_defs.c 2000



おわりに



ご枅聎ありがずうございたした。 Linux甚のPVS-Studio静的コヌドアナラむザヌをお詊しください。





Windows開発者、私はここに招埅したす PVS-Studio for Windows 。 圌らにずっお-すべおが少し簡単です。 Visual Studioのプラグむンをむンストヌルし、デモバヌゞョンでC、C ++、Cプロゞェクトを確認するだけです。





この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAndrey Karpov。 静的アナラむザヌによるValgrind動的アナラむザヌのコヌドの確認



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を収集したした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。



All Articles