记一次 OpenIPMI core的分析

简介:
  1. 现象 

    gdb对OpenIPMI core的分析表明 domain_id 未被初始化好就被使用,具体信息如下:

    Thread debugging using libthread_db enabled]
    > Using host libthread_db library "/lib64/libthread_db.so.1".
    > Core was generated by `/usr/lsd/bin/tcmeoer'.
    > Program terminated with signal 11, Segmentation fault.
    > #0  0x00007f2e87731ab2 in ipmi_domain_iterate_entities () from 
    > /lib64/libOpenIPMI.so.0
    > Missing separate debuginfos, use: debuginfo-install 
    > OpenIPMI-libs-2.0.19-11.el7.x86_64 gdbm-1.10-8.el7.x86_64 
    > glib2-2.46.2-4.el7.x86_64 glibc-2.17-78.el7.x86_64 
    > hiredis-0.12.1-1.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 
    > krb5-libs-1.12.2-14.el7.x86_64 libcom_err-1.42.9-7.el7.x86_64 
    > libffi-3.0.13-11.el7.x86_64 libgcc-4.8.3-9.el7.x86_64 
    > libselinux-2.2.2-6.el7.x86_64 libstdc++-4.8.3-9.el7.x86_64 
    > openssl-libs-1.0.1e-42.el7.x86_64 pciutils-libs-3.2.1-4.el7.x86_64 
    > pcre-8.32-14.el7.x86_64 python-libs-2.7.5-16.el7.x86_64 
    > xz-libs-5.1.2-9alpha.el7.x86_64 zlib-1.2.7-13.el7.x86_64
    > (gdb) bt
    > #0  0x00007f2e87731ab2 in ipmi_domain_iterate_entities () at 
    > /lib64/libOpenIPMI.so.0
    > #1  0x00000000004e47e3 in sensor_check_timeout (cb_data=0x0, 
    > id=0x2ed5ce0) at src/sysmon.c:300
    > #2  0x00007f2e874e7735 in process_timers () at 
    > /lib64/libOpenIPMIposix.so.0
    > #3  0x00007f2e874e8d0b in sel_select_loop () at 
    > /lib64/libOpenIPMIposix.so.0
    > #4  0x00007f2e872df12d in operation_loop () at 
    > /lib64/libOpenIPMIpthread.so.0
    > #5  0x00000000004e4a40 in sensor_thread (data=0x2ee8100) at 
    > src/sysmon.c:416
    > #6  0x00007f2e88ac8df5 in start_thread () at /lib64/libpthread.so.0
    > #7  0x00007f2e85df61ad in clone () at /lib64/libc.so.6
    > (gdb) p domain_id.domain
    > $1 = (ipmi_domain_t *) 0x0
    > (gdb) p handle_entity
    > $2 = {void (ipmi_entity_t *, void *)} 0x4e477a <handle_entity>
    >
    > == related source code is as below -==
    > static void handle_entity(ipmi_entity_t * ent, void *b_data)


    其中domain_id使用的地方在c文件头就有定义:
    ipmi_domain_id_t domain_id;
    如果它没有初始化话,默认很可能为0. 这个domain_id 它会被后面的timer线程周期调用:

    void sensor_check_timeout(void *cb_data, os_hnd_timer_id_t * id)
    {

        ipmi_domain_iterate_entities(domain_id.domain, handle_entity, NULL);

        os_hnd->start_timer(os_hnd,
                            check_timer, &check_timeout, sensor_check_timeout, NULL);
    }

    其中 ipmi_domain_iterate_entities()必须要求第一个参数domain_id.domain 不为空,否则 domain→entities会产生segment fault.

    int ipmi_domain_iterate_entities(ipmi_domain_t      *domain,
                     ipmi_entity_ptr_cb handler,
                     void               *cb_data)
    {
        CHECK_DOMAIN_LOCK(domain);

        ipmi_entities_iterate_entities(domain->entities, handler, cb_data);
        return 0;
    }

    一般情况正常情况下,domain_id.domain 它在OpenIPMI初始化的时候通过调用setup_done()被初始化,如下面的代码所示:
    rv = ipmi_open_domain("", &con, 1, setup_done, NULL, NULL, NULL, NULL, 0, &domain_id);
        if (rv) {
            LOGLIB_TRACE( "ipmi_init_domain: %s\n", strerror(rv));
            pthread_mutex_unlock(&ipmi_mutex);
            return -4;
        }
    void
    setup_done(ipmi_domain_t * domain,
               int err,
               unsigned int conn_num,
               unsigned int port_num, int still_connected, void *user_data)
    {
        int rv;


        domain_id = ipmi_domain_convert_to_id(domain);
        LOGLIB_TRACE("domain: %p", domain);

        /* Register a callback functin entity_change. When a new entity
           is created, entity_change is called */
        rv = ipmi_domain_add_entity_update_handler(domain, entity_change, domain);
        if (rv) {
            LOGLIB_ERROR("ipmi_domain_add_entity_update_handler return error: %d\n", rv);
            return;
        }
    }


    2. 原因分析

    根据日志,并没有发现有异常提前退出的地方:

     rv = ipmi_parse_args(&curr_arg, argc, argv, &args);
        if (rv) {
            LOGLIB_ERROR(
                    "Error parsing command arguments, argument %d: %s\n",
                    curr_arg, strerror(rv));
            usage();
            return -2;
        }

        pthread_mutex_lock(&ipmi_mutex);
        rv = ipmi_args_setup_con(args, os_hnd, NULL, &con);
        if (rv) {
            LOGLIB_ERROR( "ipmi_ip_setup_con: %s", strerror(rv));
            pthread_mutex_unlock(&ipmi_mutex);
            return -3;
        }

        rv = ipmi_open_domain("", &con, 1, setup_done, NULL, NULL, NULL, NULL, 0, &domain_id);
        if (rv) {
            LOGLIB_TRACE( "ipmi_init_domain: %s\n", strerror(rv));
            pthread_mutex_unlock(&ipmi_mutex);
            return -4;
        }

        LOGLIB_INFO("domain: %p", domain_id.domain);

        rv = init_alert_shmbuf(ACCESS_EVENTS_MEMORY);
        if (rv != 0) {
            LOGLIB_ERROR( "init_alert_shmbuf: %x\n", rv);
            pthread_mutex_unlock(&ipmi_mutex);
            leave();
        }

        rv = os_hnd->alloc_timer(os_hnd, &check_timer);
        if (rv) {
            LOGLIB_ERROR( "alloc_timer: %x\n", rv);
            fprintf(stdout, "alloc_timer: %x\n", rv);
            pthread_mutex_unlock(&ipmi_mutex);
            //rv = ipmi_args_setup_con(args, os_hnd, sel, &con);
            leave();
        }
     /*
         * Wait until setup_done() thread has been ready, otherwise later timer
         * thread sensor_check_timeout may core
         */
        while(domain_id.domain == NULL) {
            LOGLIB_INFO("Waiting ipmi smi connection ready...\n");
            sleep(5);
        }

        /* Extend the delay to pool BMC sensors to ensure the setup_done() finished */
        check_timeout.tv_sec = 10;
        check_timeout.tv_usec = 0;
        os_hnd->start_timer(os_hnd,
                            check_timer, &check_timeout, sensor_check_timeout, NULL);

        rv = os_hnd->create_thread(os_hnd, 0, sensor_thread, os_hnd);
        if (rv) {
            LOGLIB_ERROR( "create sensor_thread: %x\n", rv);
            pthread_mutex_unlock(&ipmi_mutex);
            return -5;
        }

      上面所有可能异常退出的地方都没有发现打印信息,因此可以判断程序没有异常退出。奇怪的是上面gdb信息表明,domain_id.domain这个指针为空,就说明它还没有被初始化。但是setup_done()已经早早让 ipmi_open_domain()去执行了,难道它没有被执行完而timer线程就已经执行了?

    3. 代码证实

    通过研究open_ipmi_domain()函数源代码,可以看到这个函数最后一个参数为空和不为空的处理差异:

    为空的时候: domain_id被放入到一个初始化队列里面,这样就导致setup_done()可能延迟很晚执行。
    之所以这么说,是因为open_ipmi_domain()会调用:
    rv = ipmi_domain_add_connect_change_handler(domain,
                                con_change_handler,
                                con_change_cb_data);

    而 ipmi_domain_add_connect_change_handler()实现如下:
    int ipmi_domain_add_connect_change_handler(ipmi_domain_t      *domain,
                           ipmi_domain_con_cb handler,
                           void               *cb_data)
    {
        if (locked_list_add(domain->con_change_handlers, handler, cb_data))
        return 0;
        else
        return ENOMEM;
    }


可以看到回调函数被放到一个list里面,被统一的scheduler调度执行。因此很可能这个open_ipmi_domain()函数返回之后, setup_done()都没有得到执行。


而当open_ipmi_domain()最后一个参数不为空的时候:

domin_id会在open_ipmi_domain()返回之前初始化完成,这个可以由open_ipmi_domain()中的这两行证实:

if (new_domain)
    *new_domain = ipmi_domain_convert_to_id(domain);

通过上面的分析,再结合我们自己写的openIPMI代码,可以看到确实存在timer线程已经开始执行但setup_done()还没有开始执行的可能,这就会导致段错误。

4.实验验证

根据上面的分析,把上面open_ipmi_domain()的最后一个参数设置成了我们期望用到的domain_id的地址,理论上就不会再出现上面的段错误了。修改完了代码之后,反复重复同样的测试,整晚上都没有重现那个问题。

5.经验教训

虽然上面的这部分代码是参考 标准OpenIPMI-2.0.21/sample/sample2.c实现的,但实际在BMC比较繁忙的时候还会出现段错误。可见,开源的代码并不能(当然也没有义务)保证没有隐患。另外,在调试这个问题的过程中,刚开始由于对OpenIPMI源代码太多的恐惧感和陌生感,迟迟没有深入阅读被引用到的open_ipmi_domain()的具体实现,导致花费了比较多的时间去猜测和使用,但总达不到看完了open_ipmi_domain()之后恍然大悟的快感。因此今后碰到类似问题,一定要深入源代码、勇于阅读相关的代码,哪怕它最不熟悉,它的执行逻辑一定是确定的,虽然我们的猜测可能不确定。





















本文转自存储之厨51CTO博客,原文链接: http://blog.51cto.com/xiamachao/1932723,如需转载请自行联系原作者


相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
6月前
|
数据采集 机器学习/深度学习 数据格式
在使用 Core ML 时,有哪些注意事项?
在使用 Core ML 时,有哪些注意事项?
94 1
32 # core 模块使用
32 # core 模块使用
54 0
|
3月前
|
缓存 数据库连接 API
Entity Framework Core——.NET 领域的 ORM 利器,深度剖析其最佳实践之路
【8月更文挑战第28天】在软件开发领域,高效的数据访问与管理至关重要。Entity Framework Core(EF Core)作为一款强大的对象关系映射(ORM)工具,在 .NET 开发中扮演着重要角色。本文通过在线书店应用案例,展示了 EF Core 的核心特性和优势。我们定义了 `Book` 实体类及其属性,并通过 `BookStoreContext` 数据库上下文配置了数据库连接。EF Core 提供了简洁的 API,支持数据的查询、插入、更新和删除操作。
111 0
|
5月前
|
开发框架 .NET 对象存储
【.NET Core】深入理解异步编程模型(APM)
【.NET Core】深入理解异步编程模型(APM)
110 1
|
存储 Kubernetes 监控
使用 Seldon Core 服务模型
当您构建机器学习驱动的产品时,弄清楚如何弥合模型与其他一切之间的差距至关重要。 例如,也许你有一个很好的推荐模型,但在我们能够将这些推荐呈现给客户之前,这个模型并没有多大用处。 这就是模型服务的用武之地。在本文中,我们将了解如何使用 Seldon Core 为模型提供服务,这是一个为速度和大规模而构建的开源服务框架,能够一次运行 1000 多个模型。 我将讨论一些让 Seldon 在这个领域独一无二的东西,以及在你的项目中使用和反对使用它的原因。 这是关于 Seldon Core 系列的第一部分。 除了模型服务的基础知识,在以后的部分中,我们将使用 Alibi Detect 监控 Seldon
Core ML 框架详细解析
Core ML框架详细解析(一) —— Core ML基本概览Core ML框架详细解析(二) —— 获取模型并集成到APP中Core ML框架详细解析(三) —— 利用Vision和Core ML对图像进行分类Core ML框架详细解析(四) —— ...
2069 0
|
NoSQL Shell Linux