Я работаю над программой, которая использует библиотеку lwIP и превращает STM32F769I-DISCO в TCP-клиент, который подключается к TCP-серверу (на данный момент эту роль выполняет Утилита настройки Hercules). Я создал простую функцию tcpClientCloseConnection(), которая освобождает пользовательскую структуру tcpClientStruct и блок управления протоколом TCP (структуру tcp_pcb из lwIP), когда TCP-соединение должно быть закрыто. Сначала он освобождает tcp_pcb внутри tcpClientStruct (функция tcp_close() освобождает его), затем tcpClientStruct.
Проблема в том, что когда tcpClientStruct освобождается, значение tcpClientStruct->pcb больше не равно NULL, а его адрес меняется с 0x0. По этой причине, когда tcpClientCloseConnection() вызывается снова, он возвращает false, когда проверяет, имеет ли tcp_pcb значение NULL, и пытается закрыть его, в результате чего программа попадает в функцию HardFault_Handler().
Я мог бы проверить, равна ли моя пользовательская структура NULL, и не пытаться освободить внутри нее tcp_pcb, если это не так, но разве tcp_pcb все равно не должен быть NULL? Меня смущает поведение моей программы, и я не знаю, в чем может быть причина этого. Я включу функции инициализации на случай, если причина проблемы может быть внутри них.
Заранее благодарим за помощь и предложения!
//main.c
//...
#define SERVER_PORT (33333)
#define SERVER_IP ("192.168.0.103")
ip_addr_t serverIP;
tcpClientStruct *tcpClientInfo=NULL;
//...
int main(void) {
//...
ipaddr_aton(SERVER_IP, &serverIP);
//...
while(1) {
//...
uint8_t conn=verifyConnectionProblems(&tcpClientInfo, SERVER_PORT, serverIP);
//...
}
}
//tcpClient.h
typedef struct {
uint8_t state; //current connection state
uint8_t retries;
struct tcp_pcb *pcb; //pointer to the current tcp_pcb
struct pbuf *packetBuffer; //pointer to received/to be transmitted data
} tcpClientStruct;
//...
//tcpClient.c
int8_t verifyConnectionProblems(tcpClientStruct **tcpClientInfo, uint16_t serverPort, ip_addr_t serverIP) {
//...
tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
//...
}
void tcpClientCloseConnection(struct tcp_pcb **tcpPCB, tcpClientStruct **clientStr) {
if (tcpPCB!=NULL && *tcpPCB!=NULL) {
tcp_close(*tcpPCB);
*tcpPCB=NULL;
}
if (clientStr!=NULL && *clientStr!=NULL) { //free clientStruct
if ((*clientStr)->packetBuffer!=NULL) {
mem_free((*clientStr)->packetBuffer);
(*clientStr)->packetBuffer=NULL;
}
asm("NOP"); //tcpClientInfo addr=0x20005d24 <ram_heap+8>, tcpClientInfo->pcb addr=0x0
mem_free(*clientStr);
asm("NOP"); //tcpClientInfo addr=0x20005d24 <ram_heap+8>, tcpClientInfo->pcb addr=0x0
*clientStr=NULL;
asm("NOP"); //tcpClientInfo addr=0, tcpClientInfo->pcb 0xf73d6e6a
}
}
//initialising functions
uint8_t serverDisconnected=0;
int8_t tcpPCBReinit(struct tcp_pcb **tcpPCB) {
if (tcpPCB!=NULL && *tcpPCB!=NULL) {
mem_free(*tcpPCB);
*tcpPCB=NULL;
}
*tcpPCB=tcp_new(); //create new TCP protocol control block
if (*tcpPCB==NULL) {
return ERR_MEM;
}
return ERR_OK;
}
int8_t tcpClientStructReinit(tcpClientStruct **clientStr) {
if (*clientStr!=NULL) {
if ((*clientStr)->packetBuffer!=NULL) {
mem_free((*clientStr)->packetBuffer);
(*clientStr)->packetBuffer=NULL;
}
mem_free(*clientStr);
*clientStr=NULL;
}
*clientStr=(tcpClientStruct *)mem_malloc(sizeof(tcpClientStruct));
if (*clientStr==NULL) {
return ERR_MEM;
} else {
tcpPCBReinit(&(*clientStr)->pcb);
if ((*clientStr)->pcb==NULL) {
mem_free(*clientStr);
*clientStr = NULL;
return ERR_MEM;
}
(*clientStr)->state=CLIENT_STATUS_NONE;
(*clientStr)->retries=0;
(*clientStr)->packetBuffer=NULL;
serverDisconnected=0;
}
return ERR_OK;
}
int8_t tcpClientInit(const ip_addr_t *ipAddr, uint16_t port, tcpClientStruct **clientStr) {
int8_t errorClientStrReinit=tcpClientStructReinit(clientStr);
if (errorClientStrReinit!=ERR_OK) {
return errorClientStrReinit;
}
tcp_arg((*clientStr)->pcb, (*clientStr)); //specifies the argument that should be passed to all other callback functions
tcp_err((*clientStr)->pcb, tcpClientErrorCallback);
//connect to the server
int8_t connected=tcp_connect((*clientStr)->pcb, ipAddr, port, tcpClientConnectedCallback); //connect to host and invoke callback once connected successfully
if (connected!=ERR_OK) {
serverDisconnected=1;
}
else {
serverDisconnected=0;
}
return connected;
}
//rest of functions...
verifyConnectionProblems
проверяет это *tcpClientInfo != NULL
перед тем, как вызвать tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
?
@IanAbbott Нет, это не так. Чеки только в tcpClientCloseConnection(...)
.
Таким образом, первый параметр tcpClientCloseConnection
может иметь значение &(((tcpClientInfo *)NULL)->pcb)
, что, очевидно, недопустимо.
@IanAbbott Каков правильный способ решения этой проблемы? Должен ли я сначала проверить самую общую структуру (в данном случае tcpClientInfo
), затем ее члены (tcp_pcb
), а затем освободить эти объекты в обратном порядке? Также можно ли проверить, являются ли объекты NULL внутри функции?
Проверить наличие NULL-указателей легко, но не так просто (невозможно) проверить недопустимые указатели, отличные от NULL.
@inferjus «Проблема в том, что когда tcpClient Struct освобождается, tcpClient Struct->pcb больше не равен NULL» --> Почему вы хотите получить доступ к tcpClientStruct->pcb
после того, как tcpClientStruct
освобожден?
@chux-ReinstateMonica Нет, я просто хочу проверить, имеет ли он значение NULL, поэтому я не закрываю pcb
более одного раза.
@inferjus «Нет» и «Я просто хочу проверить, равно ли оно NULL» неясно. Заметить, что «tcpClientStruct->pcb больше не имеет значения NULL», можно только обратившись к tcpClientStruct->pcb
, который, как вы говорите, уже свободен. Я понимаю, вы хотите проверить .pcb
, но почему проверка после того, как tcpClientStruct
станет бесплатной, а не раньше?
закрытие закрытого соединения - это UB. Убедитесь, что вы не закрываете его более одного раза. Не полагайтесь на библиотечные функции.
Я думаю, проблема в том, что verifyConnectionProblems
иногда может вызвать tcpClientCloseConnection
с недопустимым, но ненулевым значением указателя в первом аргументе (для параметра tcpPCB
).
verifyConnectionProblems
вызывается в цикле:
while(1) {
//...
uint8_t conn=verifyConnectionProblems(&tcpClientInfo, SERVER_PORT, serverIP);
//...
}
verifyConnectionProblems
могу вызвать tcpClientCloseConnection
:
tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
ОП подтвердил, что verifyConnectionProblems
не проверяет значение *tcpClientInfo
перед приведенным выше вызовом tcpClientCloseConnection
.
Предполагая, что соединение еще не закрыто, вызов tcpClientCloseConnection
установит *tcpClientInfo
в NULL
через параметр clientStr
(второй параметр tcpClientCloseConnection
):
mem_free(*clientStr);
*clientStr=NULL;
В следующий раз, когда будет вызван verifyConnectionProblems
, он может снова вызвать tcpClientCloseConnection
клиента:
tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
Если *tcpClientInfo
по-прежнему NULL
, первый аргумент вызова tcpClientCloseConnection
, заданный выражением &((*tcpClientInfo)->pcb)
, является недопустимым значением указателя, поскольку он основан на нулевом значении указателя. Поскольку член pcb
находится с ненулевым смещением от начала типа tcpClientStruct
, обычно значение указателя будет недопустимым, но не нулевым. Первый аргумент передается первому параметру tcpPCB
.
tcpClientCloseConnection
выполняет некоторую проверку правильности параметра tcpPCB
:
if (tcpPCB!=NULL && *tcpPCB!=NULL) {
Если tcpPCB
имеет недопустимое, но ненулевое значение, первая часть теста (tcpPCB!=NULL
) будет истинной, но вторая часть теста (*tcpPCB!=NULL
) будет разыменовывать недопустимый указатель, что приведет к неопределенному поведению.
Мы не знаем, что делают другие тесты verifyConnectionProblems
, прежде чем принять решение о вызове tcpClientCloseConnection
, но они должны, по крайней мере, избегать вызова с недействительными, ненулевыми указателями. В частности, необходимо проверить, что *tcpClientInfo
не является значением указателя, отличным от нуля.
Разыменование указателя на освобожденную память приводит к неопределенному поведению.