2017-11-21 8 views
0

編集(ソリューション)関数の2番目の呼び出しでセグメンテーションフォルトが発生しましたか?

私は-fsanitize =アドレス& valgrindのとデバッグのアドバイスに従ってきました。私は-fsanitize(以前は聞いたことがない)を使用し、問題が何であるかを知っただけで、別の関数のデストラクタの呼び出しが残り、オブジェクトが2度破壊されていました。この時点でメモリは完全に危険にさらされていました。

お手数をおかけしていただきありがとうございます。


私はソケット(CouchDBのは、HTTP APIを持っているApacheのことで、データベースである)を使用してのCouchDBと話をするC++のコードを書いています。私はそれに対処するためのクラス全体を作成しました。それは基本的に接続して閉じるソケットクライアントです。

私の関数の1つは、HTTPリクエストを送信して応答を読み取り、それを処理することです。最初の呼び出しで正常に動作しますが、2回目に呼び出すと失敗します。

しかし、失敗した場所では一貫性がなく、時には文字列関数の中のSEGFAULTであり、それ以外のときは戻り値のSIGABORTです。私はそれが墜落した線に信号を送りました->

そして、最悪の部分は、実際には10回目の「秒」の時間だけ実行されると失敗するということです。説明:クラスがインスタンス化されてソケットが作成されると、sendRequestが8回呼び出されます(すべての作業、常に)、ソケットを閉じます。次に、ソケットサーバーを制御する別のクラスがあります。ソケットサーバーはコマンドを受け取り、コマンドを実行するリモートユーザーオブジェクトを作成し、リモートユーザーコマンドはCouchDBクラスを呼び出してDBを操作します。コマンドが最初に要求されると動作しますが、2番目のコマンドが失敗してプログラムがクラッシュします。

追加情報:short int httpcode行のgdbトレースでは、substrのクラッシュが表示され、SIGABORTクラッシュトレースではfree()の問題が示されます。

私はすでに何度もデバッグしましたが、文字列とバッファをインスタンス化する場所と方法についていくつか変更を加えましたが、私は失われています。何度もうまく動作するのは誰でも知っていますが、その後の呼び出しでクラッシュしますか?時々

CouchDB::response CouchDB::sendRequest(std::string req_method, std::string req_doc, std::string msg) 
{ 
    std::string responseBody; 
    char buffer[1024]; 
    // zero message buffer 
    memset(buffer, 0, sizeof(buffer)); 

    std::ostringstream smsg; 
    smsg << req_method << " /" << req_doc << " HTTP/1.1\r\n" 
     << "Host: " << user_agent << "\r\n" 
     << "Accept: application/json\r\n" 
     << "Content-Length: " << msg.size() << "\r\n" 
     << (msg.size() > 0 ? "Content-Type: application/json\r\n" : "") 
     << "\r\n" 
     << msg; 

    /*std::cout << "========== Request ==========\n" 
       << smsg.str() << std::endl;*/ 

    if (sendData((void*)smsg.str().c_str(), smsg.str().size())) { 
     perror("@CouchDB::sendRequest, Error writing to socket"); 
     std::cerr << "@CouchDB::sendRequest, Make sure CouchDB is running in " << user_agent << std::endl; 
     return {-1, "ERROR"}; 
    } 

    // response 
    int len = recv(socketfd, buffer, sizeof(buffer), 0); 

    if (len < 0) { 
     perror("@CouchDB::sendRequest, Error reading socket"); 
     return {-1, "ERROR"}; 
    } 
    else if (len == 0) { 
     std::cerr << "@CouchDB::sendRequest, Connection closed by server\n"; 
     return {-1, "ERROR"}; 
    } 

    responseBody.assign(buffer); 
    // HTTP code is the second thing after the protocol name and version 
-> short int httpcode = std::stoi(responseBody.substr(responseBody.find(" ") + 1)); 

    bool chunked = responseBody.find("Transfer-Encoding: chunked") != std::string::npos; 
    /*std::cout << "========= Response =========\n" 
       << responseBody << std::endl;*/ 
    // body starts after two CRLF 
    responseBody = responseBody.substr(responseBody.find("\r\n\r\n") + 4); 

    // chunked means that the response comes in multiple packets 
    // we must keep reading the socket until the server tells us it's over, or an error happen 
    if (chunked) { 
     std::string chunkBody; 
     unsigned long size = 1; 

     while (size > 0) { 
      while (responseBody.length() > 0) { 
       // chunked requests start with the size of the chunk in HEX 
       size = std::stoi(responseBody, 0, 16); 
       // the chunk is on the next line 
       size_t chunkStart = responseBody.find("\r\n") + 2; 
       chunkBody += responseBody.substr(chunkStart, size); 
       // next chunk might be in this same request, if so, there must have something after the next CRLF 
       responseBody = responseBody.substr(chunkStart + size + 2); 
      } 

      if (size > 0) { 
       len = recv(socketfd, buffer, sizeof(buffer), 0); 

       if (len < 0) { 
        perror("@CouchDB::sendRequest:chunked, Error reading socket"); 
        return {-1, "ERROR"}; 
       } 
       else if (len == 0) { 
        std::cerr << "@CouchDB::sendRequest:chunked, Connection closed by server\n"; 
        return {-1, "ERROR"}; 
       } 

       responseBody.assign(buffer); 
      } 
     } 
     // move created body from chunks to responseBody 
->  responseBody = chunkBody; 
    } 
    return {httpcode, responseBody}; 
} 

上記とそれを呼び出す関数SIGABORT

bool CouchDB::find(Database::db db_type, std::string keyValue, std::string &value) 
{ 
    if (!createSocket()) { 
     return false; 
    } 
    std::ostringstream doc; 
    std::ostringstream json; 
    doc << db_name << db_names[db_type] << "/_find"; 
    json << "{\"selector\":{" << keyValue << "},\"limit\":1,\"use_index\":\"index\"}"; 
-> CouchDB::response status = sendRequest("POST", doc.str(), json.str()); 
    close(socketfd); 

    if (status.httpcode == 200) { 
     value = status.body; 
     return true; 
    } 
    return false; 
} 

あなたはについての質問かもしれませんいくつかのビット:

  • CouchDB::responsestruct {httpcode: int, body: std::string}
  • CouchDB::dbあるenumです異なるデータベースを選択する
  • すべてのバイトが、それはあなたの int len = recv(socketfd, buffer, sizeof(buffer), 0);バッファ内の最後の '\0'を上書きするかもしれない作り
+0

エラーが30分後にプログラムがクラッシュするような方法でメモリを破壊するなど、まったく別の場所ではないことをどのように知っていますか? – user4581301

+0

@ user4581301私はこれについて考えなかった。私は何かがあるかどうかを確認するために、問題の可能性があるクラスの1つをいくつか変更する必要があります。もしあなたが私がどのようにこれを見つけることができるかについてアドバイスがあれば教えてください。 – user8977154

+0

正直言って、あなたがコードの2つのスニペットを与えてくれて、私がここにいるのは、提供されたコードを入力するときにプログラムが正常であることを知る方法がないので、現在私の疑惑は、あなたが思っているほど多くのデータを返さない 'recv'呼び出しの行に沿っています。失敗したり切断されたりしないrecvは、0から 'sizeof(buffer)'まで任意の量を返すことができるので、最初のrecvには、有効な整数またはチャンクタグを見つけて読み出すために、また、次の 'recv'で見つけることが期待されるデータを含めることもできます。 – user4581301

答えて

0

に送信されるまで

  • sendDataはバイトとして何かを送信します。 1つは、sizeof(buffer) - 1を使用するように誘惑されるかもしれませんが、あなたのストリームにnullバイトを受け取っているかもしれないので、これは間違っています。だから、これを代わりに:responseBody.assign(buffer, len);。あなたがあなたのエラーチェックの中で行うことを確認した後でのみ、これはもちろんlen >= 0を実行してください。

    recvに電話するすべての場所で行う必要があります。しかし、readの代わりにrecvを使用している理由は、あなたがフラグを使用していないためです。

    また、私のやり方では、あなたのバッファmemsetは無意味です。また、使用する直前にバッファを宣言する必要があります。あなたが何かをやったのかどうかを知るためには、関数の半分を読まなければなりませんでした。もちろん、あなたはそれを2度目に使用することになります。

    あなたのエラー処理はどちらの場合も基本的に同じですから、私はそれをした関数を作成します。繰り返さないでください。

    最後に、あなたはfindの結果で素早く遊ばれます。あなたは実際にあなたが探しているものを見つけることができず、代わりにstring::nposを得るかもしれません、そして、それはまたあなたに興味深い問題を引き起こすでしょう。

    もう1つのことは、gccまたはclangを使用している場合は、-fsanitize=address(または他のサニタイズオプションのいくつかが文書化されています)を試してください。そして/またはvalgrindの下で実行します。あなたのメモリエラーは、クラッシュしているコードから遠いかもしれません。それらはあなたがそれに近づくのを助けるかもしれません。

    そして最後のメモです。あなたのロジックは完全に台無しです。あなたは読書データとあなたの構文解析を分けて、それぞれのために異なるステートマシンを保有しなければなりません。あなたの最初の読書がどれほど読まれても、HTTPヘッダー全体を取得するという保証はありません。あなたのヘッダーが特定のサイズよりも小さいという保証はありません。

    あなたは、あなたが読んでいる以上のことを読んで、それをエラーとみなすまで、またはヘッダーの最後にCR LN CR LNが得られるまで、読書を続けなければなりません。

    これらの最後のビットではコードがクラッシュすることはありませんが、特に特定のトラフィックシナリオで偽のエラーが発生する可能性があります。つまり、テストでは表示されない可能性があります。

  • +0

    ありがとう@Onnifarious前に 'len'を' assign'に使っていましたが、エラーはまだ残っていましたので、私はそれを抜き出してみました(アイデアがありませんでした)、貼り付けたスニペットはこの変更でした。私はそれらを戻し、何かが起こったかどうかを見るために 'read'も使います。私は 'find'についてまだ気にしなかったので、チェックはしません。 – user8977154

    +0

    @jack_mustang - 'read'と' recv'を使用してもクラッシュには違いはありません。あなたのコードの見直しから、私が提案した方法で 'assign'を使用すると、クラッシュを引き起こすエラーは見られません。だからそれは他の場所になければならない。メモリエラーはそのようなことがあります。あなたは間違いを1つの場所にし、クラッシュは一見無関係な場所で起こります。 'valgrind'と' -fsanitize'オプションを使います。エラーの実際の原因を追跡するのに役立ちます。 – Omnifarious

    +0

    ありがとう、私はすでにvalgrindをインストールしています(これまでにチェックする時間はありませんでしたが、今私はそれを覚えなくてはいけません:D)、消毒剤を見て、それは別の場所かもしれません。 – user8977154

    関連する問題