GDBは難しいナットであるこずが刀明したした

Gdb GDBは、これを行うのが難しいツヌルです。 もちろん、ブルックスが蚀うように、「゜フトりェア補品開発の品質は、無限のテストではなく、適切な蚭蚈によっお達成されたす。」 ただし、適切な蚭蚈では、論理゚ラヌ、タむプミス、NULLポむンタヌなどから保護されたせん。 ここで、GDBなどのコヌドデバッグツヌルが圹立ちたす。 私の目暙は、初期段階で倚くの゚ラヌを怜出する静的コヌドアナラむザヌが、それほど有甚なツヌルではないこずを瀺すこずです。 テストおよびデバッグフェヌズの前であっおも、コヌドで゚ラヌを修正すれば、はるかに優れおいたす。 静的コヌド分析の利点を実蚌するために、PVS-Studioアナラむザヌを䜿甚しおGDB内の゚ラヌを調べおみたしょう。



はじめに



GCCのチェックに関する最近の良い蚘事の埌、私は同時にGDBに関する蚘事を曞くこずにしたした。 今回は、蚘事を曞くのが難しくなりたした。 どうやらプロゞェクトのサむズに圱響したす。 ただし、゜ヌスコヌドのサむズを比范するこずは私にずっお困難です。 どちらのプロゞェクトにも、デヌタのある倧きなテヌブルを含むファむルがありたす。 これらは、コヌドサむズずコヌドの行数に倧きく貢献したす。 たずえば、GDBプロゞェクトには、サむズが5メガバむトのi386-tbl.hファむルがあり、次の圢匏のテヌブルが含たれおいたす。







gdb配列










GCCプロゞェクトの実際のコヌドは、GDBコヌドのサむズよりも数倍倧きいず思いたす。 GCCを確認するず、蚘事の十分な゚ラヌを簡単に収集でき、レポヌトをすばやく調べお、疑わしいものを掘り䞋げるこずはしたせんでしたが、郚倖者がコヌドスニペットを理解するこずは困難でした。 GDBの堎合、よく芋る必芁がありたしたが、それでも疑わしい堎所はほずんど芋぀かりたせんでした。



確認する



GDBバヌゞョン7.11.1の゜ヌスコヌドが怜蚌されたした。 コヌドは、Linuxで実行されおいるPVS-Studioのバヌゞョンによっお怜蚌されたした。



簡単なリファレンス。 PVS-Studioは、C、C ++、Cで蚘述された゜ヌスコヌドの゚ラヌを怜出する商甚の静的アナラむザヌです。 LinuxおよびWindowsで動䜜したす。



PVS-Studio静的アナラむザヌを䜿甚しおGDBをテストするには、いく぀かの簡単な手順を実行する必芁がありたす。



0ドキュメントを読む「 LinuxでPVS-Studioを実行する方法 」 アナラむザヌをアセンブリシステムに統合せずに、プロゞェクトをすばやく確認できる方法を遞択したした。



1公匏リポゞトリから最新の゜ヌスコヌドをダりンロヌドしたす。



$ git clone git://sourceware.org/git/binutils-gdb.git
      
      





2PVS-Studio.cfg構成ファむル、぀たりoutput-fileおよびsourcetree-rootパラメヌタヌを倉曎したす。 私の堎合



 exclude-path = /usr/include/ exclude-path = /usr/lib64/ lic-file = /home/andr/PVS-Studio.lic output-file = /home/andr/gdb.log sourcetree-root = /home/andr/binutils-gdb
      
      





3ダりンロヌドしたディレクトリに移動したす。



 $ cd binutils-gdb
      
      





4Makefileを䜜成したす。



 $ ./configure
      
      





5gdbビルドずPVS-Studioアナラむザヌを実行したす。



 $ pvs-studio-analyzer trace -- make -j3
      
      





6分析を実行したすPVS-Studio.cfg構成ファむルぞのパスを指定



 $ pvs-studio-analyzer analyze --cfg /home/andr/PVS-Studio.cfg
      
      





分析が正垞に完了するず、gdb.logログファむルがホヌムディレクトリに衚瀺され、PVS-Studio Standaloneナヌティリティを䜿甚しおWindowsで衚瀺できたす。 これは私がよく知っおいるので、たさに私がやったこずです。



Linuxでレポヌトを調べたい堎合は、PVS-Studio配垃キットに゜ヌスコヌドが含たれおいるコンバヌタヌナヌティリティplog-converterが圹立ちたす。 ナヌティリティは* .plogファむルをさたざたな圢匏に倉換できたす ドキュメントを参照。 たた、コンバヌタを自分のニヌズに合わせお調敎するこずもできたす。



重芁な テキスト゚ディタで* .logを盎接開いお芋ないでください。 ひどいものになりたす。 このファむルには、倚くの冗長で重耇した情報が含たれおいたす。 そしお、それがこれらのファむルがずおも倧きい理由です。 たずえば、ある皮の譊告がhファむルに適甚される堎合、このhファむルがcppファむルに含たれおいる回数ず同じ回数だけファむルに衚瀺されたす。 PVS-Studio Standaloneたたはplog-converterを䜿甚するず、これらのツヌルはそのような重耇を自動的に削陀したす。



* .logファむルをQtタスクリストファむル圢匏に倉換した埌、Qt Creatorでレポヌトを衚瀺するず䟿利だずしたしょう。 次に、plog-converterナヌティリティを次のように䜿甚する必芁がありたす。



 $ plog-converter -t tasklist -o /home/andr/gdb.tasks -r /home/andr/binutils-gdb/ -a GA:1,2,3 /home/andr/gdb.log
      
      





最初はGA1,2を䜿甚した方が良いでしょう。 3぀すべおの譊告レベルを含めお、アナラむザヌの日付を開始するこずはお勧めできたせん。



このコマンドを実行するず、 gdb.tasksレポヌトファむルがホヌムディレクトリに衚瀺され、Qt Creatorを䜿甚しお衚瀺できたす。







Qt CreatorおよびPVS-Studioログ









plog-converterオプションを衚瀺したす。



 $ plog-converter --help
      
      





怜蚌結果



先ほど蚀ったように、今回はPVS-Studioアナラむザヌの機胜を実蚌する゚ラヌをほずんど芋぀けるこずができたせんでした。 私が思うのは、GDBプロゞェクトの゜ヌスコヌドの高品質ず、それ自䜓がプログラマヌである膚倧な数のナヌザヌによっお十分にテストされおいるため、プログラムの平均的なナヌザヌよりも泚意深い、芁求が厳しいずいう事実にありたす。



それにもかかわらず、私が面癜いず思うものを芋おみたしょう。 比范関数の゚ラヌから始めたしょう。 この゚ラヌバグを新しい゚ラヌパタヌンで匷調衚瀺したす。 私は倚くのプロゞェクトでこのような゚ラヌに遭遇し、すぐにこのトピックに関する新しい蚘事を曞くこずを蚈画しおいたす。これは「 最埌の行の効果 」 の粟神に沿ったものです。



無効な比范関数



 static int psymbol_compare (const void *addr1, const void *addr2, int length) { struct partial_symbol *sym1 = (struct partial_symbol *) addr1; struct partial_symbol *sym2 = (struct partial_symbol *) addr2; return (memcmp (&sym1->ginfo.value, &sym1->ginfo.value, sizeof (sym1->ginfo.value)) == 0 && sym1->ginfo.language == sym2->ginfo.language && PSYMBOL_DOMAIN (sym1) == PSYMBOL_DOMAIN (sym2) && PSYMBOL_CLASS (sym1) == PSYMBOL_CLASS (sym2) && sym1->ginfo.name == sym2->ginfo.name); }
      
      





è­Šå‘ŠPVS-Studio V549 'memcmp'関数の最初の匕数は2番目の匕数ず同じです。 psymtab.c 1580



memcmpの最初ず2番目の匕数は同じです。 明らかにここで圌らは曞きたかった



 memcmp (&sym1->ginfo.value, &sym2->ginfo.value, sizeof (sym1->ginfo.value))
      
      





正しく機胜する䞍正なコヌド



静的コヌドアナラむザヌは、プログラムの゜ヌスコヌドを操䜜し、人の芳点から間違いがあった堎所を芋぀けるこずができたす。 興味深いこずに、゚ラヌにもかかわらず、このコヌドは幞運のために完党に正しく機胜したす。 これらの興味深いケヌスの1぀を芋おみたしょう。



 struct event_location * string_to_explicit_location (const char **argp, ....) { .... /* It is assumed that input beginning with '-' and a non-digit character is an explicit location. "-p" is reserved, though, for probe locations. */ if (argp == NULL || *argp == '\0' || *argp[0] != '-' || !isalpha ((*argp)[1]) || ((*argp)[0] == '-' && (*argp)[1] == 'p')) return NULL; .... }
      
      





PVS-Studio è­Šå‘Š  V528 「char」型ぞのポむンタヌが「\ 0」倀ず比范されるのは奇劙です。 おそらく意味** argp == '\ 0'。 location.c 527



このコヌドでは次のこずに興味がありたす。



 .... const char **argp .... if (argp == NULL || *argp == '\0' || *argp[0] != '-'
      
      





リテラル'\ 0'は、空の文字列をチェックするかどうかを確認するずきに䜿甚される終端のれロです。 これを行うには、文字列を含むバッファの最初の芁玠を確認し、終端にれロがある堎合、文字列は空ず芋なされたす。 これはたさにプログラマヌがここでやりたかったこずです。 しかし、圌はargp倉数が文字ぞのポむンタヌではなく、ポむンタヌぞのポむンタヌであるこずを考慮したせんでした。



したがっお、正しいチェックは次のようになりたす。



 *argp[0] == '\0'
      
      





たたは



 **argp == '\0'
      
      





ただし、このコヌドを曞くず



 if (argp == NULL || *argp[0] == '\0' || *argp[0] != '-'
      
      





それは危険です。 nullポむンタヌのチェックをもう1぀远加する必芁がありたす。



 if (argp == NULL || *argp == NULL || *argp[0] == '\0' || *argp[0] != '-'
      
      





これでコヌドは正しいです。 ただし、冗長であるこずに泚意しおください。 最初の文字がダッシュ「-」でない堎合、文字が䜕であるかは関係ありたせん。 ずにかく、終端れロたたはその他の文字がありたす。 したがっお、次のようにコヌドを簡玠化できたす。



 if (argp == NULL || *argp == NULL || *argp[0] != '-'
      
      





この正しいコヌドは元々あったコヌドず同等であるこずに泚意しおください



 if (argp == NULL || *argp == '\0' || *argp[0] != '-'
      
      





党䜓の違いは0の曞き蟌み方法にあり、最初の堎合はNULLです。 2番目は'\ 0'です。 実際、これは同じこずであり、コヌドは同じように動䜜したす。



おもしろい コヌドが誀っお蚘述されたずいう事実にもかかわらず、完党に正しく動䜜したす。



䞍適切なバッファサむズの蚈算



 extern void read_memory (CORE_ADDR memaddr, gdb_byte *myaddr, ssize_t len); void java_value_print (....) { .... gdb_byte *buf; buf = ((gdb_byte *) alloca (gdbarch_ptr_bit (gdbarch) / HOST_CHAR_BIT)); .... read_memory (address, buf, sizeof (buf)); .... }
      
      





PVS-Studio譊告 V579 read_memory関数は、ポむンタヌずそのサむズを匕数ずしお受け取りたす。 間違いかもしれたせん。 3番目の匕数を調べたす。 jv-valprint.c 111



この゚ラヌは、おそらくリファクタリング䞭に発生したした。 ある時点で、コヌドは次のようなものであるこずを提案しようず思いたす。



 gdb_byte buf[gdbarch_ptr_bit (gdbarch) / HOST_CHAR_BIT)]; .... read_memory (address, buf, sizeof (buf));
      
      





次に、 sizeof挔算子がバッファのサむズを正しく蚈算したした。 次に、圌らはalloca関数を䜿甚しおバッファにメモリを割り圓お始めたした。 その結果、 sizeofbuf挔算子はバッファヌのサむズではなく、ポむンタヌのサむズを蚈算したす。



正しいコヌドは次のようになりたす。



 gdb_byte *buf; const size_t size = gdbarch_ptr_bit (gdbarch) / HOST_CHAR_BIT; buf = ((gdb_byte *) alloca (size)); .... read_memory (address, buf, size);
      
      





しかし、これで終わりではありたせん。最も興味深いのは、さらに私たちを埅っおいるこずです。 私は、゚ラヌの本質ずそれがどのように発生する可胜性があるかに぀いお説明し始めるこずにしたした。 以䞋にさらにいく぀かの行を衚瀺するず、すべおがより興味深いものになりたす。



 read_memory (address, buf, sizeof (buf)); address += gdbarch_ptr_bit (gdbarch) / HOST_CHAR_BIT; /* FIXME: cagney/2003-05-24: Bogus or what. It pulls a host sized pointer out of the target and then extracts that as an address (while assuming that the address is unsigned)! */ element = extract_unsigned_integer (buf, sizeof (buf), byte_order);
      
      





ご芧のずおり、このコヌドに䜕か問題があるこずに最初に気づいたわけではありたせん。 少なくずも2003幎以来、このコヌドにぱラヌが残っおいたす。 圌女がただ矯正されおいない理由は明らかではありたせん。



私の理解では、このコメントは次の行を参照しおいたす。



 element = extract_unsigned_integer (buf, sizeof (buf), byte_order);
      
      





extract_unsigned_integer関数を呌び出すず、䞊蚘ず同じ゚ラヌが発生したした。



PVS-Studioアナラむザヌは、次の行にも譊告を発行したす。V579 extract_unsigned_integer関数は、ポむンタヌずそのサむズを匕数ずしお受け取りたす。 間違いかもしれたせん。 2番目の匕数を調べたす。 jv-valprint.c 117



アナラむザヌは、 java_value_print関数コヌドでさらに2぀の譊告を生成したす。





ダブル割り圓お



 FILE * annotate_source (Source_File *sf, unsigned int max_width, void (*annote) (char *, unsigned int, int, void *), void *arg) { .... bfd_boolean new_line; .... for (i = 0; i < nread; ++i) { if (new_line) { (*annote) (annotation, max_width, line_num, arg); fputs (annotation, ofp); ++line_num; new_line = FALSE; } new_line = (buf[i] == '\n'); fputc (buf[i], ofp); } .... }
      
      





PVS-Studio譊告 V519 「new_line」倉数には連続しお2回倀が割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください253、256。source.c 256



文字列new_line = FALSE; 意味がありたせん。 この盎埌に、 new_line倉数の倀が別の倀に倉曎されたす。 ぀たり 次のコヌドスニペットは非垞に疑わしいものです。



  new_line = FALSE; } new_line = (buf[i] == '\n');
      
      





どうやらここで䜕らかの論理的な間違いが行われたようです。 たたは、最初の割り圓おは䞍芁であり、削陀できたす。



タむプミス



 int handle_tracepoint_bkpts (struct thread_info *tinfo, CORE_ADDR stop_pc) { int ipa_trace_buffer_is_full; CORE_ADDR ipa_stopping_tracepoint; int ipa_expr_eval_result; CORE_ADDR ipa_error_tracepoint; .... if (ipa_trace_buffer_is_full) trace_debug ("lib stopped due to full buffer."); if (ipa_stopping_tracepoint) trace_debug ("lib stopped due to tpoint"); if (ipa_stopping_tracepoint) trace_debug ("lib stopped due to error"); .... }
      
      





PVS-Studio譊告 V581互いに䞊んでいる「if」挔算子の条件匏は同䞀です。 行を確認しおください4535、4537。tracepoint.c 4537



ipa_stopping_tracepoint倉数がTRUEの堎合、2぀のデバッグメッセヌゞが出力されたす。



 lib stopped due to tpoint lib stopped due to error
      
      





私はコヌドがどのように機胜するかに぀いお詳しくはありたせんが、埌者の堎合、条件はipa_stopping_tracepoint倉数ではなくipa_error_tracepoint倉数を䜿甚する必芁があるように思われたす。 コヌドは次のようになりたす。



 if (ipa_trace_buffer_is_full) trace_debug ("lib stopped due to full buffer."); if (ipa_stopping_tracepoint) trace_debug ("lib stopped due to tpoint"); if (ipa_error_tracepoint) trace_debug ("lib stopped due to error");
      
      





breakステヌトメントは忘れられたす



叀兞的な間違い。 スむッチ内のある堎所では、 break ステヌトメントは忘れられたす。



 static debug_type stab_xcoff_builtin_type (void *dhandle, struct stab_handle *info, int typenum) { .... switch (-typenum) { .... case 8: name = "unsigned int"; rettype = debug_make_int_type (dhandle, 4, TRUE); break; case 9: name = "unsigned"; rettype = debug_make_int_type (dhandle, 4, TRUE); case 10: name = "unsigned long"; rettype = debug_make_int_type (dhandle, 4, TRUE); break; .... } .... }
      
      





PVS-Studio譊告V519「name」倉数には倀が2回連続しお割り圓おられたす。 おそらくこれは間違いです。 行を確認3433、3436。stabs.c 3436



「unsigned long」タむプず「unsigned long」タむプのどちらを䜿甚しおいるかに関係なく、「unsigned long」ずいう名前をタむプに割り圓おたす。



正しいコヌドは次のずおりです。



 case 9: name = "unsigned"; rettype = debug_make_int_type (dhandle, 4, TRUE); break;
      
      





も぀れたケヌス



次のコヌドでは、2぀のcaseの間にbreakステヌトメントがないため 、 alt倉数に倀が2回割り圓おられたす。 しかし、コメントによるず、プログラマヌは特にbreakを䜿甚しおいたせん。 私が理解できないコヌドを考えおみたしょう



 static int putop (const char *in_template, int sizeflag) { int alt = 0; .... switch (*p) { .... case '{': alt = 0; if (intel_syntax) { while (*++p != '|') if (*p == '}' || *p == '\0') abort (); } /* Fall through. */ case 'I': alt = 1; continue; .... } }
      
      





PVS-Studio譊告V519「alt」倉数には連続しお2回倀が割り圓おられたす。 おそらくこれは間違いです。 チェック行14098、14107。i386-dis.c 14107



だから、コメント/ *倱敗。 * /は、ここでbreakステヌトメントが䞍芁であるこずを瀺したす。 しかし、 alt倉数に倀0が割り圓おられる理由は明らかではありたせん。ずにかく、倉数の倀は1に眮き換えられたす。 さらに、これら2぀の割り圓おの間では、 alt倉数は䞀切䜿甚されたせん。 明確ではありたせん...



ここでは、論理゚ラヌたたは最初の割り圓おを削陀する必芁がありたす。



おわりに



Linux甚の長生きPVS-Studio ご芧のずおり、静的解析の利点は、オヌプンなWindowsプロゞェクトだけでなく、オヌプンプログラムのLinuxコミュニティにも圹立ちたす。 すぐに、実瞟のあるプロゞェクトに関する蚘事のリストに 、Linuxの䞖界のプログラムに関する蚘事が倚数远加されるず思いたす。







PVS-Studio for Linux










面癜いものを芋逃さないために、 @ Code_Analysisの twitterに登録しおください 。





この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAndrey karpov。 GDB-クラックするのが難しいナットPVS-Studioで芋぀かったバグはわずかです。



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




All Articles