You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

释放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...

原因分析与解决方案

核心原因

出现的现象是野指针访问+内存释放后的非法读写:

  1. 调用mem_free(*clientStr)后,该结构体指向的内存块被归还给lwIP内存池,这块内存后续可能被其他内存分配操作覆盖,或被内存池自身标记为空闲块(修改内存内容)。
  2. 虽然释放前将(*clientStr)->pcb设为NULL,但释放结构体后,(*clientStr)->pcb所在的内存已不属于你的程序,后续值被修改是正常行为。
  3. 更关键的是:调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:09:54