コードのクラッシュを分析するには、実際のCの第一人者の助けが必要です。クラッシュを修正するためではありません。簡単に修正できますが、完全に不可能に思えるので、修正する前に、このクラッシュがどのように発生する可能性があるかを理解したいと思います。
このクラッシュはお客様のマシンでのみ発生し、このユーザーのデータベースのコピーを取得できないため、ローカルで再現することはできません(したがって、デバッガーを使用してコードをステップ実行することはできません)。私の会社では、コードの数行を変更してこの顧客のカスタムビルドを作成することもできません(したがって、printf行を追加してコードを再度実行させることはできません)。もちろん、顧客はデバッグシンボル。言い換えれば、私のデバグ能力は非常に限られています。それでも、クラッシュを突き止めて、デバッグ情報を取得することができました。しかし、その情報を見てからコードを見ると、プログラムフローが問題の行に到達する方法を理解できません。その行に到達するずっと前に、コードがクラッシュしているはずです。私はここで完全に迷子になっています。
関連するコードから始めましょう。それは非常に小さなコードです:
// ... code above skipped, not relevant ...
if (data == NULL) return -1;
information = parseData(data);
if (information == NULL) return -1;
/* Check if name has been correctly \0 terminated */
if (information->kind.name->data[information->kind.name->length] != '\0') {
freeParsedData(information);
return -1;
}
/* Copy the name */
realLength = information->kind.name->length + 1;
*result = malloc(realLength);
if (*result == NULL) {
freeParsedData(information);
return -1;
}
strlcpy(*result, (char *)information->kind.name->data, realLength);
// ... code below skipped, not relevant ...
もう終わりです。strlcpyでクラッシュします。実行時にstrlcpyが実際にどのように呼び出されるかさえわかります。strlcpyは、実際には次のパラメーターで呼び出されます。
strlcpy ( 0x341000, 0x0, 0x1 );
これを知っていると、strlcpyがクラッシュする理由はかなり明白です。NULLポインタから1文字を読み取ろうとしますが、もちろんクラッシュします。また、最後のパラメーターの値は1であるため、元の長さは0である必要があります。私のコードには明らかにバグがあり、名前データがNULLであるかどうかをチェックできません。私はこれを修正できます、問題ありません。
私の質問は
、そもそもこのコードがどのようにしてstrlcpyに到達できるのかということです。
このコードがifステートメントでクラッシュしないのはなぜですか?
私は自分のマシンでローカルにそれを試しました:
int main (
int argc,
char ** argv
) {
char * nullString = malloc(10);
free(nullString);
nullString = NULL;
if (nullString[0] != '\0') {
printf("Not terminated\n");
exit(1);
}
printf("Can get past the if-clause\n");
char xxx[10];
strlcpy(xxx, nullString, 1);
return 0;
}
このコードにifステートメントが渡されることはありません。ifステートメントでクラッシュしますが、これは間違いなく予想されます。
それで、name-> dataが本当にNULLの場合、最初のコードがクラッシュせずにそのifステートメントを渡すことができる理由を誰かが考えることができますか?これは私には全く不思議です。決定論的ではないようです。
重要な追加情報:
2つのコメントの間のコードは本当に完全であり、何も残されていません。さらに、アプリケーションはシングルスレッドであるため、バックグラウンドでメモリを予期せず変更する可能性のあるスレッドは他にありません。これが発生するプラットフォームは、PPC CPU(G4、何らかの役割を果たす可能性がある場合)です。そして、誰かが「種類」について疑問に思う場合、これは「情報」に「種類」という名前の「ユニオン」が含まれ、名前が再び構造体であるためです(種類はユニオンであり、可能なすべてのユニオン値は異なるタイプの構造体です)。しかし、これはすべてここでは実際には重要ではありません。
ここでのアイデアに感謝します。単なる理論ではなく、この理論がお客様に本当に当てはまることを確認できる方法があれば、さらに感謝しています。
解決
私はすでに正しい答えを受け入れましたが、誰かがGoogleでこの質問を見つけた場合に備えて、実際に起こったことは次のとおりです。
ポインタは、すでに解放されているメモリを指しています。メモリを解放しても、すべてがゼロになったり、プロセスがメモリを一度にシステムに戻したりすることはありません。そのため、メモリが誤って解放されたとしても、正しい値が含まれていました。「 ifcheck 」を実行した時点では、問題のポインタはNULLではありません。
そのチェックの後、mallocを呼び出して新しいメモリを割り当てます。ここでmallocが正確に何をするかはわかりませんが、mallocまたはfreeを呼び出すたびに、プロセスの仮想アドレス空間のすべての動的メモリに広範囲にわたる結果が生じる可能性があります。malloc呼び出しの後、ポインターは実際にはNULLです。どういうわけか、malloc(またはmallocが使用するシステムコール)は、ポインタ自体が配置されている(ポインタが指すデータではなく、ポインタ自体がダイナミックメモリにある)すでに解放されているメモリをゼロにします。そのメモリをゼロにすると、ポインタの値は0x0になります。これは、私のシステムではNULLに等しく、strlcpyが呼び出されると、もちろんクラッシュします。
したがって、この奇妙な動作を引き起こす本当のバグは、私のコードのまったく別の場所にありました。決して忘れないでください:解放されたメモリはその値を保持しますが、それはどのくらいの間あなたのコントロールを超えています。アプリにすでに解放されたメモリにアクセスするというメモリのバグがあるかどうかを確認するには、解放される前に解放されたメモリが常にゼロになっていることを確認してください。OS Xでは、実行時に環境変数を設定することでこれを行うことができます(何も再コンパイルする必要はありません)。もちろん、これはプログラムをかなり遅くしますが、あなたはそれらのバグをずっと早く捕まえるでしょう。