释放tcpClientStruct后成员pcb地址异常引发HardFault问题求助
问题描述
基于lwIP库开发程序,使用STM32F769I-DISCO开发板作为TCP客户端连接TCP服务器(目前用Hercules Setup Utility模拟服务器)。编写了tcpClientCloseConnection()函数,用于关闭TCP连接时释放自定义结构体tcpClientStruct及lwIP的TCP协议控制块tcp_pcb,逻辑为先释放结构体内部的tcp_pcb(调用tcp_close()),再释放结构体本身。
当前问题:释放tcpClientStruct后,tcpClientStruct->pcb不再为NULL,地址从0x0变为随机值。再次调用tcpClientCloseConnection()时,检查tcp_pcb是否为NULL的判断会通过,尝试关闭该无效的tcp_pcb,最终导致程序进入HardFault_Handler()。
可以通过先检查自定义结构体是否为NULL来避免操作内部tcp_pcb,但疑惑的是:tcp_pcb本身为什么不会保持NULL?
相关代码
main.c
//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
//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
//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...
原因分析与解决方案
核心原因
出现的现象是野指针访问+内存释放后的非法读写:
- 调用
mem_free(*clientStr)后,该结构体指向的内存块被归还给lwIP内存池,这块内存后续可能被其他内存分配操作覆盖,或被内存池自身标记为空闲块(修改内存内容)。 - 虽然释放前将
(*clientStr)->pcb设为NULL,但释放结构体后,(*clientStr)->pcb所在的内存已不属于你的程序,后续值被修改是正常行为。 - 更关键的是:调用
tcpClientCloseConnection时传入&((*tcpClientInfo)->pcb),当*clientStr被释放并设为NULL后,(*tcpClientInfo)->pcb属于对NULL指针的解引用,是非法访问。
修复方案
方案1:简化函数逻辑,避免重复传递指针
tcpClientCloseConnection无需同时传入tcp_pcb指针和结构体指针,直接通过结构体指针访问内部pcb即可,彻底避免悬空指针问题:
void tcpClientCloseConnection(tcpClientStruct **clientStr) { if (clientStr != NULL && *clientStr != NULL) { // 先处理TCP控制块 if ((*clientStr)->pcb != NULL) { tcp_close((*clientStr)->pcb); (*clientStr)->pcb = NULL; } // 处理数据包缓冲区 if ((*clientStr)->packetBuffer != NULL) { mem_free((*clientStr)->packetBuffer); (*clientStr)->packetBuffer = NULL; } // 释放结构体并置空 mem_free(*clientStr); *clientStr = NULL; } }
调用时仅需传递结构体指针:
tcpClientCloseConnection(tcpClientInfo);
方案2:保留原参数,增加结构体有效性判断
若必须保留原参数结构,需先判断结构体是否有效,再处理内部tcp_pcb:
void tcpClientCloseConnection(struct tcp_pcb **tcpPCB, tcpClientStruct **clientStr) { // 仅当结构体有效时,才处理pcb(避免访问悬空指针) if (clientStr != NULL && *clientStr != NULL && tcpPCB != NULL && *tcpPCB != NULL) { tcp_close(*tcpPCB); *tcpPCB = NULL; } // 释放结构体逻辑不变 if (clientStr != NULL && *clientStr != NULL) { if ((*clientStr)->packetBuffer != NULL) { mem_free((*clientStr)->packetBuffer); (*clientStr)->packetBuffer = NULL; } mem_free(*clientStr); *clientStr = NULL; } }
额外注意事项
- lwIP的
tcp_close()会自动回收tcp_pcb,无需手动调用mem_free()释放,你在tcpPCBReinit中调用mem_free(*tcpPCB)属于错误操作,可能导致双重释放或内存损坏。 - 所有涉及结构体的访问,必须先检查指针是否为NULL,避免野指针解引用。
内容的提问来源于stack exchange,提问作者inferjus
相关产品推荐
相关产品推荐

