Ошибка сегментации при втором вызове функции?

Изменить (решение)

Я последовал совету отладки с -fsanitize=address & valgrind. Я использовал только -fsanitize (о котором я никогда раньше не слышал) и выяснил, в чем проблема, остался вызов деструктора в другой функции, и объект был уничтожен дважды. Память была полностью поставлена ​​под угрозу в этот момент.

Большое спасибо за помощь и другие рекомендации тоже.


Я пишу код на C++ для общения с CouchDB с использованием сокетов (CouchDB - это база данных от Apache, которая имеет HTTP API). Я создал целый класс для работы с ним, и это в основном клиент для сокетов, который подключается и закрывается.

Одной из моих функций является отправка HTTP-запроса, а затем чтение ответа и работа с ним, он отлично работает при первом вызове, но не работает, когда я вызываю его во второй раз.

Но это противоречиво, когда это терпит неудачу, иногда это SEGFAULT внутри него в одной из строковых функций, в других случаях это SIGABORT в возвращении. Я сигнализировал линии, где он разбился с ->

И хуже всего то, что он дает сбой только тогда, когда работает "второй" раз, который на самом деле 10-й раз. Объяснение: Когда создается экземпляр класса, создается сокет, sendRequest называется 8 раз (все работает, всегда) закрываю сокет. Затем у меня есть другой класс, который управляет сервером сокетов, который получает команды и создает объект удаленного пользователя, который выполняет команду, а команда удаленного пользователя вызывает класс 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::db является enum выбирать разные базы данных
  • sendData отправляет только байты, пока не отправлены все байты

1 ответ

Решение

Сделай это int len = recv(socketfd, buffer, sizeof(buffer), 0); может быть перезаписать последний '\0' в вашем буфере. Можно было бы испытать желание использовать sizeof(buffer) - 1 но это было бы неправильно, так как вы могли бы получить нулевые байты в вашем потоке. Итак, сделайте это вместо этого: responseBody.assign(buffer, len);, Делайте это, конечно, только после того, как убедитесь, len >= 0, что вы делаете в своих проверках ошибок.

Вы должны делать это в любом месте, где вы звоните recv, Хотя, почему вы используете recv вместо read вне меня, так как вы не используете ни один из флагов.

Кроме того, ваш буфер memset Бессмысленно, если вы делаете это по-моему. Вы также должны объявить свой буфер прямо перед его использованием. Мне пришлось прочитать половину вашей функции, чтобы понять, что вы с ней сделали. Хотя, конечно, вы используете его во второй раз.

Черт возьми, поскольку ваша обработка ошибок в основном идентична в обоих случаях, я бы просто сделал функцию, которая это сделала. Не повторяйся.

Наконец, вы играете быстро и свободно с результатом find, Вы можете не найти то, что ищете, и получить string::npos вместо этого, и это также вызовет у вас интересные проблемы.

Другое дело, попробуйте -fsanitize=address (или некоторые другие параметры очистки, задокументированные там), если вы используете gcc или clang. И / или запустить его под Valgrind. Ошибка памяти может быть далека от сбоя кода. Это может помочь вам приблизиться к этому.

И последнее замечание. Ваша логика полностью запуталась. Вы должны разделить свои данные чтения и анализ и сохранить разные конечные автоматы для каждого. Нет никакой гарантии, что ваше первое чтение получит весь заголовок HTTP, независимо от того, насколько велико это чтение. И нет никакой гарантии, что ваш заголовок меньше определенного размера.

Вы должны продолжать читать, пока не прочитаете больше, чем хотите, для заголовка и не сочтете это ошибкой, или пока не получите CR LN CR LN в конце заголовка.

Эти последние биты не приведут к сбою кода, но приведут к ложным ошибкам, особенно в определенных сценариях трафика, что означает, что они, скорее всего, не будут отображаться при тестировании.

Другие вопросы по тегам