Члену структуры присваивается новый адрес после освобождения структуры

Я работаю над программой, которая использует библиотеку 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...

Разыменование указателя на освобожденную память приводит к неопределенному поведению.

Ian Abbott 03.09.2024 12:28

verifyConnectionProblems проверяет это *tcpClientInfo != NULL перед тем, как вызвать tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);?

Ian Abbott 03.09.2024 12:39

@IanAbbott Нет, это не так. Чеки только в tcpClientCloseConnection(...).

inferjus 03.09.2024 12:42

Таким образом, первый параметр tcpClientCloseConnection может иметь значение &(((tcpClientInfo *)NULL)->pcb), что, очевидно, недопустимо.

Ian Abbott 03.09.2024 12:44

@IanAbbott Каков правильный способ решения этой проблемы? Должен ли я сначала проверить самую общую структуру (в данном случае tcpClientInfo), затем ее члены (tcp_pcb), а затем освободить эти объекты в обратном порядке? Также можно ли проверить, являются ли объекты NULL внутри функции?

inferjus 03.09.2024 12:54

Проверить наличие NULL-указателей легко, но не так просто (невозможно) проверить недопустимые указатели, отличные от NULL.

Ian Abbott 03.09.2024 13:01

@inferjus «Проблема в том, что когда tcpClient Struct освобождается, tcpClient Struct->pcb больше не равен NULL» --> Почему вы хотите получить доступ к tcpClientStruct->pcb после того, как tcpClientStruct освобожден?

chux - Reinstate Monica 03.09.2024 13:19

@chux-ReinstateMonica Нет, я просто хочу проверить, имеет ли он значение NULL, поэтому я не закрываю pcb более одного раза.

inferjus 03.09.2024 13:35

@inferjus «Нет» и «Я просто хочу проверить, равно ли оно NULL» неясно. Заметить, что «tcpClientStruct->pcb больше не имеет значения NULL», можно только обратившись к tcpClientStruct->pcb, который, как вы говорите, уже свободен. Я понимаю, вы хотите проверить .pcb, но почему проверка после того, как tcpClientStruct станет бесплатной, а не раньше?

chux - Reinstate Monica 03.09.2024 13:39

закрытие закрытого соединения - это UB. Убедитесь, что вы не закрываете его более одного раза. Не полагайтесь на библиотечные функции.

0___________ 03.09.2024 13:41
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, проблема в том, что 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 не является значением указателя, отличным от нуля.

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