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

Linux下libmodbus多线程双释放崩溃问题求助

Libmodbus多线程TCP客户端断开服务器后触发double free崩溃问题

在Linux环境下基于libmodbus开发TCP/IP主从站程序,采用多线程方式同时连接3台不同的Modbus服务器读取数据。所有服务器在线时程序运行正常,但断开任意一台服务器后,终端会抛出free(): double free detected in tcache 2并提示Aborted (core dumped),且服务器断开时的错误信息无法正常打印。

相关代码

main.c

/*
 * Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include <stdio.h>
#include <pthread.h>

#include <errno.h>
#include <modbus.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


// clang-format off
#ifdef _WIN32
# include <winsock2.h>
#else
# include <sys/socket.h>
#endif

/* For MinGW */
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
// clang-format on

//#include "test-server.h"
#include "client-server.h"
pthread_mutex_t lock;

int main(int argc, char *argv[])
{
    pthread_t server, client,client1,client2;
    pthread_mutex_init(&lock, NULL);

    pthread_create(&server, NULL, &server_thread, NULL);
    pthread_create(&client, NULL, &client_thread, NULL);
    pthread_create(&client1, NULL, &client_1_thread, NULL);
    pthread_create(&client2, NULL, &client_2_thread, NULL);

    pthread_join(server, NULL);
    pthread_join(client, NULL);
    pthread_join(client1, NULL);
    pthread_join(client2, NULL);
    pthread_mutex_destroy(&lock);

    return 5;
}

client-server.c

#include <stdio.h>
#include <pthread.h>

#include <errno.h>
#include <modbus.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


// clang-format off
#ifdef _WIN32
# include <winsock2.h>
#else
# include <sys/socket.h>
#endif

/* For MinGW */
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
// clang-format on

#include "test-server.h"
#include "client-server.h"

extern pthread_mutex_t lock;

uint16_t CS_Buffer[50];
 
enum {
    TCP,
    TCP_PI,
    RTU
};

void *server_thread(void *arg)
{
    int s = -1;
    modbus_t *ctx;
    modbus_mapping_t *mb_mapping;
    int rc;
    int i;
    int use_backend;
    uint8_t *query;
    int header_length;
    char *ip_or_device;
    uint16_t tab_reg[32];

    ctx = modbus_new_tcp("192.168.1.200", 1502);
    query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);
    
    header_length = modbus_get_header_length(ctx);
    modbus_set_debug(ctx, TRUE);

    mb_mapping = modbus_mapping_new_start_address(UT_BITS_ADDRESS,
                                                  UT_BITS_NB,
                                                  UT_INPUT_BITS_ADDRESS,
                                                  UT_INPUT_BITS_NB,
                                                  UT_REGISTERS_ADDRESS,
                                                  UT_REGISTERS_NB_MAX,
                                                  UT_INPUT_REGISTERS_ADDRESS,
                                                  UT_INPUT_REGISTERS_NB);
    if (mb_mapping == NULL) {
        fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
    }

    /* Initialize input values that's can be only done server side. */
    modbus_set_bits_from_bytes(
        mb_mapping->tab_input_bits, 0, UT_INPUT_BITS_NB, UT_INPUT_BITS_TAB);

    /* Initialize values of INPUT REGISTERS */
    for (i = 0; i < UT_INPUT_REGISTERS_NB; i++) {
        mb_mapping->tab_input_registers[i] = UT_INPUT_REGISTERS_TAB[i];
    }
    printf("Starting Server!\n");
    s = modbus_tcp_listen(ctx, 1);
    modbus_tcp_accept(ctx, &s);
    
    while(1)
    { 
        do {
            rc = modbus_receive(ctx, query);
            /* Filtered queries return 0 */
        } while (rc == 0);

        /* The connection is not closed on errors which require on reply such as
           bad CRC in RTU. */
        if (rc == -1 && errno != EMBBADCRC) {
            /* Quit */
        }

        /* Special server behavior to test client */
        if (query[header_length] == 0x03) {
            /* Read holding registers */
            if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3) ==
                UT_REGISTERS_NB_SPECIAL) {
                printf("Set an incorrect number of values\n");
                MODBUS_SET_INT16_TO_INT8(
                    query, header_length + 3, UT_REGISTERS_NB_SPECIAL - 1);
            } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1) ==
                       UT_REGISTERS_ADDRESS_SPECIAL) {
                printf("Reply to this special register address by an exception\n");
                modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY);
            } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1) ==
                       UT_REGISTERS_ADDRESS_INVALID_TID_OR_SLAVE) {
                const int RAW_REQ_LENGTH = 5;
                uint8_t raw_req[] = {(use_backend == RTU) ? INVALID_SERVER_ID : 0xFF,
                                     0x03,
                                     0x02,
                                     0x00,
                                     0x00};

                printf("Reply with an invalid TID or slave\n");
                modbus_send_raw_request(ctx, raw_req, RAW_REQ_LENGTH * sizeof(uint8_t));
            } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1) ==
                       UT_REGISTERS_ADDRESS_SLEEP_500_MS) {
                printf("Sleep 0.5 s before replying\n");
                usleep(500000);
            } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1) ==
                       UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS) {
                uint8_t req[] = "\x00\x1C\x00\x00\x00\x05\xFF\x03\x02\x00\x00";
                int req_length = 11;
                int w_s = modbus_get_socket(ctx);
                if (w_s == -1) {
                    fprintf(stderr, "Unable to get a valid socket in special test\n");
                }

                req[1] = query[1];
                for (i = 0; i < req_length; i++) {
                    printf("(%.2X)", req[i]);
                    usleep(5000);
                    rc = send(w_s, (const char *) (req + i), 1, MSG_NOSIGNAL);
                    if (rc == -1) {
                    }
                }
            }
        }
       
        rc = modbus_reply(ctx, query, rc, mb_mapping);
        pthread_mutex_lock(&lock);
        mb_mapping->tab_registers[0] = CS_Buffer[0];
        printf("Server hoalding reg %u", mb_mapping->tab_registers[0]);
        pthread_mutex_unlock(&lock);
        if (rc == -1) {
        }
    }

    modbus_close(ctx);
    modbus_free(ctx);
    return NULL;
}

void *client_thread(void *arg)
{
    modbus_t *ctx;
    int rc;
    uint16_t tab_reg[32];
    while(1)
    {
        ctx = modbus_new_tcp("192.168.1.153", 502);
        if (modbus_connect(ctx) == -1) {
            fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
            modbus_free(ctx);
        }
   
        rc = modbus_read_registers(ctx, 1, 9, tab_reg);
        if (rc == -1) {
            fprintf(stderr, "%s\n", modbus_strerror(errno));
        } else {
            int i;
            for (i=0; i < 2; i++) {
                printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
                pthread_mutex_lock(&lock);
                CS_Buffer[i] = tab_reg[i];
                pthread_mutex_unlock(&lock);
            }
        }

        sleep(3);
    } 
    modbus_close(ctx);
    modbus_free(ctx);
    return NULL;
}

void *client_1_thread(void *arg)
{
    modbus_t *ctx;
    int rc;
    uint16_t tab_reg[32];
    while(1)
    {
        ctx = modbus_new_tcp("192.168.1.153", 503);
        if (modbus_connect(ctx) == -1) {
            fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
            modbus_free(ctx);
        }
   
        rc = modbus_read_registers(ctx, 1, 9, tab_reg);
        if (rc == -1) {
            fprintf(stderr, "%s\n", modbus_strerror(errno));
        } else {
            int i;
            for (i=0; i < 2; i++) {
                printf("reg_1[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
                pthread_mutex_lock(&lock);
                pthread_mutex_unlock(&lock);
            }
        }

        sleep(3);
        modbus_close(ctx);
        modbus_free(ctx);
    } 
    return NULL;
}

void *client_2_thread(void *arg)
{
    modbus_t *ctx;
    int rc;
    uint16_t tab_reg[32];
    while(1)
    {
        ctx = modbus_new_tcp("192.168.1.153", 504);
        if (modbus_connect(ctx) == -1) {
            fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
            modbus_free(ctx);
        }
   
        rc = modbus_read_registers(ctx, 1, 9, tab_reg);
        if (rc == -1) {
            fprintf(stderr, "%s\n", modbus_strerror(errno));
        } else {
            int i;
            for (i=0; i < 2; i++) {
                printf("reg_2[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
                pthread_mutex_lock(&lock);
                pthread_mutex_unlock(&lock);
            }
        }

        sleep(3);
        modbus_close(ctx);
        modbus_free(ctx);
    } 
    return NULL;
}

解决思路

1. 修复double free问题

  • 问题根源:client_thread中,连接失败时已调用modbus_free(ctx)释放上下文,但循环未终止,后续会执行循环外的modbus_free(ctx),导致重复释放;同时连接失败后未跳过modbus_read_registers调用,操作已释放的悬空指针触发内存错误。
  • 修复方案:将modbus_close和modbus_free移至循环内部,确保每个ctx只被释放一次;连接失败后添加continue跳过后续无效操作:
    void *client_thread(void *arg)
    {
        modbus_t *ctx;
        int rc;
        uint16_t tab_reg[32];
        while(1)
        {
            ctx = modbus_new_tcp("192.168.1.153", 502);
            if (modbus_connect(ctx) == -1) {
                int err = errno;
                fprintf(stderr, "Connection failed: %s\n", modbus_strerror(err));
                modbus_free(ctx);
                sleep(3);
                continue;
            }
       
            rc = modbus_read_registers(ctx, 1, 9, tab_reg);
            if (rc == -1) {
                int err = errno;
                fprintf(stderr, "%s\n", modbus_strerror(err));
            } else {
                int i;
                for (i=0; i < 2; i++) {
                    printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
                    pthread_mutex_lock(&lock);
                    CS_Buffer[i] = tab_reg[i];
                    pthread_mutex_unlock(&lock);
                }
            }
    
            modbus_close(ctx);
            modbus_free(ctx);
            sleep(3);
        } 
        return NULL;
    }
    
    此修改需同步应用到client_1_thread和client_2_thread。

2. 修复错误信息打印异常

  • 问题根源:线程间errno可能被覆盖,导致错误信息不准确。
  • 修复方案:在调用modbus_strerror前保存当前errno值,避免被其他线程修改。

3. 补充服务器线程资源释放

server_thread中query和mb_mapping未释放,可在退出循环后添加:

free(query);
modbus_mapping_free(mb_mapping);

内容的提问来源于stack exchange,提问作者Nagaraj JP

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 05:16:24