X 86_64にC関数宣言が書かれていません.

7882 ワード

<!--&菗13;
/*-->ボルト[CDATA[/**<]!
html{font-famimily:Times,serif;font-size:12 pt;}&菗13
.title{text-align:center]&唵13;
.todo{color=red}&{13;
.done{color=green;&菵13;
.font-weight:normal)&唵13
.target{}&{13;
.timestamp{color=ble bebebebe}&唵13;
.timestamp-kwd{color=ble 5 f 9 ea 0}&菗13;
.right{magin-left:atot;margin-right:0 px;text-align:right;&唵13;
left{magin-left:0 px;margin-right:atot;text-align:left;&唵13;
センター{margin-left:atot;margin-right:atot;text-align:center;
p.verse{magin-left:3%}&菗13;
pre{&唵13;
border:1 pt solid落AEBCC;&啝13;
background-カラー:(菗F 3 F 5 F 7)&33846;13;
padding:5 pt;
font-family:courier、monoospace;
font-size:90%&獠13;
overflow:atot;&啣13;
}&{13;
table{border-collappse}&唵13;
td,th{vertical-align:top;}&萶13;
th.right{text-align:center]&唵13;
th.left{text-align:center]&萶13;
th.cnter{text-align:center]&唵13;
td.right{text-align:right]&唵13;
td.left{text-align:left;}&萶13;
td.cnter{text-align:center]&萶13;
dt{font-weight:bold]&獠13;
div.figur{padding:0.5 em;}&菗13;
div.figure p{text-align:center]&萶13;
div.inlineatask{&苊13;
padding:10 px;
border:2 px sold gray;&腣13;
margin:10 px;&铉13;
background:菗ffffffcc;&{13;
}&{13;
textarea{overflow-x:aut;}&呭13;
ラインnr{font-size:smaaller}&唵13;
.code-highlighted{background-flash 00;}&菗13;
.org-info-jsuinfo-navigation{border-style:none;}&萶13;
啜org-info-js sone-label{font-size:10 px;font-weight:bold;&唵13;
white-space:nowrap;&噫13;
.org-info-jscarch-highlight{background-カラー:菗菗ffff00;色:嗳嗵嗳13;
font-weight:bold;
-->
<!--&萓13;
/*--><![CDATA[/*>]!
pre{background-カラー:(zhi 817 A 7 B;)&菗13;
img{border:1 px solid gray]&唵13;
-->
X 86_64にC関数宣言が書かれていないため、バグが発生しました.
私のブログ:http://blog.striveforfreedom.net
Table of Constets
  • 1概要
  • 2崩壊につながるコードと解決の考え方
  • 2.1崩壊につながるコード
  • 2.2解決の考え方
  • 3まとめ
  • 1概要
    最近、Cで書いたソースプログラムを修正するには、いくつかの関数を追加する必要があります.怠けて関数声明を書いていないので、プログラムが崩壊してしまいました.最後に原因を究明するのに時間がかかりました.元々は関数声明を書いていないので、このバグはX 86_64に代表的だと思います.ここに記録します.
    2崩壊につながるコードと解決の考え方
    2.1崩壊につながるコード
    崩壊の原因となるコードは簡略化された後、大体このような形になります.
    //foo.c
    #include <stdlib.h>
    #include "bar.h"
    
    static const char* value = NULL;
    void set_value(const char* p)
    {
        value = p;
    }
    
    const char* get_value()
    {
        return value;
    }
    
    int main(int argc, char* argv[])
    {
        char p[] = "abcd";
        set_value(p);
        failed_func();
    
        return 0;
    }
    
    //bar.c
    #include "bar.h"  //    ,    bar.h ,       failed_func   。
    
    char failed_func(void)
    {
        const char* p = get_value();
        return *p;  //    
    }    
    
    プログラムはfailedyufunc関数に実行するたびに、コメントの行でクラッシュします.
    2.2解決の考え方
    一見、これらの関数は簡単で、何の問題があるのか全然分かりません.なぜ崩壊するのですか?関数failedyufuncで次のブレークポイントを使います.ステップ関数getvalueでは、戻り値は当初セットされていた値ですが、getuvalue関数が戻ってきたら、ポインタpの値を調べます.これはおかしいです.簡単な関数を使って呼び出したが、このような奇異な結果がありました.C言語レベルでは何の違いがあるかは分かりません.そこで、アセンブルコードを調べて、set disassemble-next-line onを使って、もう一度関数getvalueに入りました.この関数はレジスタraxの値を設定してそのまま戻ってきました.raxの値は最初にセットされた.の値は、この関数には明らかに問題がありません.関数failedufuncに戻ると、関数getuvalueのcalqコマンドを呼び出した直後に、Axの値をシンボル拡張し、raxの値を32桁高く設定します.(eaxの最高位の値に依存します)、その後のコマンドはraxが指すメモリにアクセスするコマンドです.このコマンドは直接崩壊をもたらしました.raxの値はgetuvalueが設定した値ではないからです.ここで重要なのはcltq命令です.なぜgccでこのような指令が発生したのですか?理由はC 89に暗黙的な宣言ルールがあるからです.関数を呼び出す必要がありますが、関数のプロトタイプが見つからない場合、コンパイラは陰的な声明を提供します.カイン宣言は関数の戻り値の種類をintと仮定します.C 99はすでにこの規則を削除しました.関数の呼び出しには関数宣言が必要です.しかし、gccは古いコードに対応するために、C 99を強制的に実行していないかもしれません.警告を与えました.この例ではg 99です.ccは関数getuvalueのプロトタイプが見つからないので、関数getuvalueの戻り値の種類はintであると仮定し、X 86_64上intは32ビットでポインタは64ビットであるため、関数getuvalueを返してポインタpに与えるのは、32ビットの有記号数を64ビットに付与するのと同じです.C言語では、与えられた表式の2つのタイプが異なると、等号の右側のタイプが等号の左側のタイプに変わると規定されています.32ビットの有記号intを64ビットの符号なしに変換すると、コンパイラはシンボル拡張命令cltqを生成し、このコードはX 86では崩壊しない.X 86上のintとポインタは32ビットなので、コンパイラはシンボル拡張命令を生成しない.
    上記の例のコードを設計する時、もう一つの小さなtrickがあります.このコードを初めて設計した時、main関数で関数setuvalueに送るパラメータはこう定義します.
    const char* p = "abcd";
    
    しかし、このようにすれば、プログラムは崩壊しません.文字列定数は通常コードと一緒にコードセグメントに入れられますが、通常コードセグメントはより低いメモリアドレスにロードされます.そこで、cltq命令が実行される前にrax値が32桁高いのが0で、実行後のraxの高さは32桁ですか?それとも0ですか?rax値は変更されていません.プログラムは崩壊しません.その後、スタックは一般的に高いメモリアドレスにあると思います.
    char p[] = "abcd";
    
    スタックのアドレスは通常0 x 1000000より大きくなりますので、cltqコマンドを実行した後、rax値の高い32ビットは全部1です.この時のrax値は大きな仮想アドレスを表しています.アクセスすると、エラーが発生します.
    3まとめ
    実はこのBUGは完全に回避できます.コンパイル時gccは明らかな警告を与えました.warning:initialization makes pointer from integer without a cast.この警告はすでに問題の本質を説明しました.一つの整数値でポインタを初期化します.-Wallオプションを加えると、もう一つの関数が宣言されていない警告があります.この二つの警告が見えたら、すぐに解決できます.私はいつもプログラムを書いています.コンパイルのオプションは全部-Wall、-Werrorのものです.今回はソースプログラムを修正して、関数声明を書いていません.加えて、このソースプログラム自体から発生した警告が多すぎて、コンパイラからの関数声明が見つからないという警告が水没しました.警告の山のように、気づかずに、多くの時間をかけて原因を明らかにすることができました.このことが私に教えてくれたのは、どうしても関数声明を書き続けなければならないということです.警告を無視してはいけません.必ず最初から警告を消滅させなければなりません.警告が多くなると、警告を取り除く意欲がありません.