Ошибка сегментации при втором вызове функции?
Изменить (решение)
Я последовал совету отладки с -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 в конце заголовка.
Эти последние биты не приведут к сбою кода, но приведут к ложным ошибкам, особенно в определенных сценариях трафика, что означает, что они, скорее всего, не будут отображаться при тестировании.