2017-11-21 14 views
0

편집 (솔루션)함수의 두 번째 호출에서 세그먼트 화 오류가 발생 했습니까?

나는 -fsanitize = 주소 & Valgrind의와 디버깅의 조언을 따랐습니다. 나는 단지 -fsanitize (전에는 들어 보지 못했던 것)를 사용하여 문제가 무엇인지 알아 냈습니다. 다른 함수에서 소멸자에 대한 호출이 있었고 객체가 두 번 파괴되었습니다. 이 시점에서 기억은 완전히 위험에 처했습니다.

도움과 다른 권장 사항에 대해서도 고마워.


나는 (CouchDB를가 HTTP API를 가지고 아파치에 의해 데이터베이스입니다) CouchDB를 소켓을 사용하여 이야기 ++ C의 코드를 쓰고 있어요. 나는 그것을 다룰 전체 클래스를 만들었고 기본적으로 연결하고 닫는 소켓 클라이언트입니다.

내 기능 중 하나는 HTTP 요청을 보낸 다음 응답을 읽고이를 사용하여 첫 번째 호출에서는 정상적으로 작동하지만 두 번째 호출에서는 실패합니다.

그러나 실패 곳은 일관성이다 때때로 그것은 반환에 SIGABORT의 문자열 함수, 다른 시간 중 하나 그것의 내부는 segfault입니다. 나는 그것이 추락 한 곳에서 신호를 보냈습니다. ->

최악의 경우는 실제로는 10 번째 시간 인 "두 번째"시간에만 실행될 때만 실패한다는 것입니다. 설명 : 클래스가 인스턴스화되면 소켓이 생성되고 sendRequest이 8 번 호출됩니다 (항상 작동합니다), 소켓을 닫습니다. 그럼 난 명령을 수신하고 명령을 실행하는 원격 사용자 객체를 생성하는 소켓 서버를 제어하는 ​​또 다른 클래스가, 원격 사용자 명령은 다음 DB를 조작 할 수있는 CouchDB를 클래스를 호출합니다. 명령이 처음 요청되면 작동하지만 두 번째 명령이 실패하고 프로그램이 중단됩니다.

추가 정보 : 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::response이는 struct {httpcode: int, body: std::string}
  • CouchDB::dbenum입니다 다른 데이터베이스를 선택하는 방법
  • 모든 바이트가 int len = recv(socketfd, buffer, sizeof(buffer), 0); 당신의 버퍼의 마지막 '\0'을 덮어 수 있습니다 확인
+0

오류가 완전히 다른 곳에서 발생하지 않는다는 것을 어떻게 알 수 있습니까? 예를 들어 30 분 후에 프로그램이 충돌하는 방식으로 메모리가 손상 될 수 있습니다. – user4581301

+0

@ user4581301 나는 이것에 대해 생각하지 않았다. 문제가 될 수있는 클래스 중 하나에서 변경 사항을 수정해야합니다. 내가 어떻게 찾을 수 있는지에 대한 조언이 있으면 알려줘. – user8977154

+0

솔직히 말해서 대부분 스 니키 (snarky)였습니다. 왜냐하면 두 가지 코드 스 니펫을 제공했기 때문에 여기서 제공된 코드를 입력 할 때 프로그램이 정상적으로 작동한다는 것을 알 수있는 방법이 없습니다. 현재 나의 의심은 당신이 생각하는만큼 많은 데이터를 반환하지 않는'recv' 콜의 행에 더 가깝다. 실패하거나 연결을 끊지 않는 recv는 0부터'sizeof (buffer)'까지 모든 값을 반환 할 수 있으므로 첫 번째 recv에는 유효한 정수 또는 청크 된 태그를 찾고 읽는 데 너무 적은 양이 포함될 수 있습니다. 또한 다음'recv'에서 찾을 것으로 예상되는 데이터를 포함 할 수 있습니다. – user4581301

답변

0

을 전송 될 때까지 16,

  • sendData 만 바이트로 아무것도를 보냅니다. 하나는 sizeof(buffer) - 1을 사용하려는 유혹을받을 수 있지만 스트림에서 null 바이트를 가져 오는 것은 잘못된 것입니다. 따라서 대신 다음을 수행하십시오. responseBody.assign(buffer, len);. 당신이 당신의 오류 검사에서 할 수 있는지 len >= 0을 변경 한 후에 만 ​​물론이 작업을 수행.

    recv으로 전화하는 모든 곳에서해야합니다. 비록 당신이 깃발을 사용하고 있지 않기 때문에 read 대신에 recv을 사용하는 이유는 저 밖에 있습니다.

    또한 내 방식으로 수행하면 memset 버퍼가 무의미합니다. 사용하기 바로 전에 버퍼를 선언해야합니다. 나는 당신이 그걸로 무엇을했는지 알아 내기 위해 당신의 기능의 절반을 읽어야했다. 물론 두 번째로 사용하게됩니다.

    지옥, 오류 처리는 기본적으로 두 경우 모두 동일하므로이 작업을 수행하는 기능을 만들 것입니다. 반복하지 마십시오.

    마지막으로, 당신은 find의 결과로 빠르고 느슨한 게임을합니다. 실제로 원하는 것을 찾지 못했을 수도 있고 string::npos을 다시 얻을 수도 있습니다. 그러면 흥미로운 문제가 발생할 수도 있습니다.

    gcc 또는 clang을 사용하는 경우 -fsanitize=address (또는 기타 위생 옵션 중 일부는 문서화되어 있음)을 시도하십시오. 그리고/또는 valgrind에서 실행하십시오. 메모리 오류가 충돌하는 코드와 멀리 떨어져있을 수 있습니다. 그것들은 당신이 그것에 가까이 다가가는 것을 도울 것입니다.

    마지막으로주의하십시오. 당신의 논리는 완전히 엉망입니다. 독서 데이터와 파싱을 분리하고 각기 다른 상태 머신을 유지해야합니다. 읽기가 아무리 큰 경우에도 첫 번째 읽기가 전체 HTTP 헤더를 얻는다는 보장은 없습니다. 그리고 귀하의 헤더가 특정 크기보다 작다는 보장은 없습니다.

    머리말을 읽을 의향이있는 것 이상을 읽을 때까지 또는 머리말의 끝에 CR LN CR LN이 나타날 때까지 오류라고 생각할 때까지 계속 읽어야합니다.

    마지막 비트가 발생해도 코드가 손상되지는 않지만 특히 트래픽 시나리오에서 의사 오류가 발생하게되어 테스트에서 나타나지 않을 가능성이 높습니다.

  • +0

    고마워요 @Omnifarious 이전에'len'을'assign'에 사용했는데 오류가 여전히 있었기 때문에 그것을 꺼내려고했습니다 (아이디어가 없음). 붙여 넣은 스 니펫은이 변경 사항과 함께있었습니다. 나는 그들을 돌려 보낼 것이고, 어떤 일이 일어나는지보기 위해'read'도 사용할 것입니다. 나는 아직'찾기 '에 대해 신경 쓰지 않았으므로 수표가 없다. – user8977154

    +0

    @jack_mustang -'read'와'recv'를 사용하면 충돌에 영향을 미치지 않습니다. 귀하의 코드를 검토 한 결과, 내가 제안한 방식으로'assign'을 사용한다면, 충돌을 일으킬 수있는 오류를 볼 수 없습니다. 그래서 다른 곳에 있어야합니다. 메모리 오류는 그렇게 될 수 있습니다. 당신은 실수 한 곳을 만든다. 충돌은 다른 겉으로는 무관 한 곳에서 일어난다. 'valgrind'와'-fsanitize' 옵션을 사용하십시오. 오류의 실제 원인을 추적하는 데 도움을 줄 수 있습니다. – Omnifarious

    +0

    고맙습니다. 저는 이미 valgrind를 설치하고 있습니다. (전에 확인해 본 적이 없었으니 이제는 배워야합니다 : D) sanitize를 보게 될 것입니다. – user8977154