本文主要描述在openwrt环境下,使用blobmsg_parse接口解析双层BLOBMSG_TYPE_TABLE出现的Segmentation fault问题。带解析的数据来源于ubus call system info返回的值。

问题描述

  • 前置条件:ubus call system info接口的返回值内容如下:

    $ubus call system info
    {
        "localtime": 1628263880,
        "uptime": 77629,
        "load": [
            0,
            704,
            576
        ],
        "memory": {
            "total": 233041920,
            "free": 51875840,
            "shared": 188416,
            "buffered": 0,
            "available": 96661504,
            "cached": 8239104
        },
        "swap": {
            "total": 0,
            "free": 0
        }
    }
    
  • 我的目的是获取memory中的available字段的值,从而得到系统中可以的内存大小。但是在解析第二层memory,BLOBMSG_TYPE_TABLE却出现了Segmentation fault。

  • 下面是出错的部分代码:

    // 定义第一层system info结构
    enum
    {
        DEF_SYS_LOCAL_TIME,
        DEF_SYS_UPTIME,
        DEF_SYS_LOAD,
        DEF_SYS_MEM,
        __SYS_INFO_MAXMUM
    };
    
    static const struct blobmsg_policy sys_info_policy[] = {
        [DEF_SYS_LOCAL_TIME] = {.name = "localtime", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_UPTIME] = {.name = "uptime", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_LOAD] = {.name = "load", .type = BLOBMSG_TYPE_ARRAY},
        [DEF_SYS_MEM] = {.name = "memory", .type = BLOBMSG_TYPE_TABLE},
    };
    
    /* 解析BLOBMSG_TYPE_ARRAY */
    enum
    {
        DEF_SYS_LOAD_ITEM1,
        DEF_SYS_LOAD_ITEM2,
        DEF_SYS_LOAD_ITEM3,
        __SYS_LOAD_ITEM_MAXMUM
    };
    
    static const struct blobmsg_policy sys_load_policy[] = {
        [DEF_SYS_LOAD_ITEM1] = {.type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_LOAD_ITEM2] = {.type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_LOAD_ITEM3] = {.type = BLOBMSG_TYPE_INT32},
    };
    
    // 定义第二层memory的结构
    enum
    {
        DEF_SYS_MEM_TOTAL,
        DEF_SYS_MEM_FREE,
        DEF_SYS_MEM_SHARED,
        DEF_SYS_MEM_BUFFERED,
        DEF_SYS_MEM_AVAILABLE,
        DEF_SYS_MEM_CACHED,
        __SYS_MEM_INFO_MAXMUM
    };
    
    static const struct blobmsg_policy sys_mem_info_policy[] = {
        [DEF_SYS_MEM_TOTAL] = {.name = "total", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_FREE] = {.name = "free", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_SHARED] = {.name = "shared", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_BUFFERED] = {.name = "buffered", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_AVAILABLE] = {.name = "available", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_CACHED] = {.name = "cached", .type = BLOBMSG_TYPE_INT32},
    };
    
    static void handle_sys_mem_cb(struct ubus_request *req, int type, struct blob_attr *msg)
    {
        if (msg != NULL)
        {
            struct blob_attr *tb[__SYS_INFO_MAXMUM];
            // 解析最外层
            blobmsg_parse(sys_info_policy, __SYS_INFO_MAXMUM, tb, blob_data(msg), blob_len(msg));
            // 打印localtime
            if (tb[DEF_SYS_LOCAL_TIME])
            {
                printf("%d---%u\n", __LINE__, blobmsg_get_u32(tb[DEF_SYS_LOCAL_TIME]));
            }
            for (int i = 0; i < __SYS_INFO_MAXMUM; i++)
            {
                printf("%d--%s\n", __LINE__, blobmsg_name(tb[i]));
                printf("%d--%d\n", __LINE__, blobmsg_type(tb[i]));
                printf("%d--%d\n", __LINE__, blobmsg_data_len(tb[i]));
                printf("%d--%d\n", __LINE__, blobmsg_len(tb[i]));
            }
            // 解析load
            if (tb[DEF_SYS_LOAD])
            {
                struct blob_attr *sub_tb[__SYS_LOAD_ITEM_MAXMUM];
                blobmsg_parse_array(sys_load_policy, ARRAY_SIZE(sys_load_policy), sub_tb, blobmsg_data(tb[DEF_SYS_LOAD]), blobmsg_data_len(tb[DEF_SYS_LOAD]));
                for (int i = 0; i < __SYS_LOAD_ITEM_MAXMUM; i++)
                {
                    printf("%d--%u\n", __LINE__, blobmsg_get_u32(sub_tb[i]));
                    printf("%d--%d\n", __LINE__, blobmsg_data_len(sub_tb[i]));
                    printf("%d--%d\n", __LINE__, blobmsg_len(sub_tb[i]));
                }
            }
            // 解析memory
            if (tb[DEF_SYS_MEM])
            {
                printf("%d--%d\n", __LINE__, blobmsg_type(tb[DEF_SYS_MEM]) == BLOBMSG_TYPE_TABLE);
                struct blob_attr *mem_attr[__SYS_MEM_INFO_MAXMUM];
                int iRet = blobmsg_parse(sys_mem_info_policy, ARRAY_SIZE(sys_mem_info_policy), mem_attr, blobmsg_data(tb[DEF_SYS_MEM]), blobmsg_data_len(tb[DEF_SYS_MEM]));
                printf("%d--%d\n", __LINE__, iRet);
                for (int i = 0; i < __SYS_MEM_INFO_MAXMUM; i++)
                {
                    printf("%d--%d\n", __LINE__, i);
                    printf("%d--%s\n", __LINE__, blobmsg_name(mem_attr[i])); //Segmentation fault
                    printf("%d--%ld\n", __LINE__, blobmsg_get_u32(mem_attr[i]));
                    printf("%d--%d\n", __LINE__, blobmsg_type(mem_attr[i]));
                    printf("%d--%d\n", __LINE__, blobmsg_data_len(mem_attr[i]));
                    printf("%d--%d\n", __LINE__, blobmsg_len(mem_attr[i]));
                }
            }
        }
    }
    
  • 当代码调试到98行时出现段错误。我查看了u32_t的大小范围是0~4294976295,很明显memory下的变量的值都在这个范围内,看起来是没有问题的,真是百思不得其解。

  • 最后我想到一个办法,那就是将tb[DEF_SYS_MEM]这个变量写入二进制文件中,查看一下具体内容。

排查步骤

  1. tb[DEF_SYS_MEM]写入二进制文件,采用下面的代码:

    FILE *fp = fopen("./mem.txt", "wb");
    if (fp != NULL)
    {
        fwrite(blobmsg_data(tb[DEF_SYS_MEM]), blobmsg_data_len(tb[DEF_SYS_MEM]), 1, fp);
        fclose(fp);
    }
    
  2. 使用WinHex工具插件文件的十六进制内容如下图: mem的十六进制图

  3. 根据位置对应关系可以看到每一条数据的开头是0x8400;然后接下来的2字节是blob_attr的长度为0x0014;再接下来的2字节是标题长度0x0005,也就是total的长度;再接下来的5字节就是total,还有1字节是total字符串的结尾’\0’;这一条数据的最后8个字节就是我们想要获取的数据

  4. 通过计算可以知道2+2+2+5+1+8=20=0x14。从这儿可以看出memory下的数据是采用8字节进行存储的,所以应该使用BLOBMSG_TYPE_INT64,而非之前使用的BLOBMSG_TYPE_INT32

确定原因

  • 通过上面的步骤逐步排查,可以确定是因为之前的代码中采用了BLOBMSG_TYPE_INT32来解析memory导致的问题。

    // 定义第二层memory的结构
    enum
    {
        DEF_SYS_MEM_TOTAL,
        DEF_SYS_MEM_FREE,
        DEF_SYS_MEM_SHARED,
        DEF_SYS_MEM_BUFFERED,
        DEF_SYS_MEM_AVAILABLE,
        DEF_SYS_MEM_CACHED,
        __SYS_MEM_INFO_MAXMUM
    };
    
    static const struct blobmsg_policy sys_mem_info_policy[] = {
        [DEF_SYS_MEM_TOTAL] = {.name = "total", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_FREE] = {.name = "free", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_SHARED] = {.name = "shared", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_BUFFERED] = {.name = "buffered", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_AVAILABLE] = {.name = "available", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_MEM_CACHED] = {.name = "cached", .type = BLOBMSG_TYPE_INT32},
    };
    // 需要修改如下
    enum
    {
        DEF_SYS_MEM_TOTAL,
        DEF_SYS_MEM_FREE,
        DEF_SYS_MEM_SHARED,
        DEF_SYS_MEM_BUFFERED,
        DEF_SYS_MEM_AVAILABLE,
        DEF_SYS_MEM_CACHED,
        __SYS_MEM_INFO_MAXMUM
    };
    
    static const struct blobmsg_policy sys_mem_info_policy[] = {
            [DEF_SYS_MEM_TOTAL] = {.name = "total", .type = BLOBMSG_TYPE_INT64},
            [DEF_SYS_MEM_FREE] = {.name = "free", .type = BLOBMSG_TYPE_INT64},
            [DEF_SYS_MEM_SHARED] = {.name = "shared", .type = BLOBMSG_TYPE_INT64},
            [DEF_SYS_MEM_BUFFERED] = {.name = "buffered", .type = BLOBMSG_TYPE_INT64},
        [DEF_SYS_MEM_AVAILABLE] = {.name = "available", .type = BLOBMSG_TYPE_INT64},
        [DEF_SYS_MEM_CACHED] = {.name = "cached", .type = BLOBMSG_TYPE_INT64},
    };
    
    // 同时取值时应该使用blobmsg_get_u64
    printf("%d--%ld\n",__LINE__, blobmsg_get_u64(mem_attr[i]));
    

解决方法

  • memory的所有字段都改为BLOBMSG_TYPE_INT64,同时采用blobmsg_get_u64获取值即可。

  • 下面是修改过后的所有代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include "libubox/uloop.h"
    #include "libubus.h"
    
    /*
    {
            "localtime": 1628263880,
            "uptime": 77629,
            "load": [
                    0,
                    704,
                    576
            ],
            "memory": {
                    "total": 233041920,
                    "free": 51875840,
                    "shared": 188416,
                    "buffered": 0,
                    "available": 96661504,
                    "cached": 8239104*40
            },
            "swap": {
                    "total": 0,
                    "free": 0
            }
    }
    */
    
    enum
    {
        DEF_SYS_LOCAL_TIME,
        DEF_SYS_UPTIME,
        DEF_SYS_LOAD,
        DEF_SYS_MEM,
        __SYS_INFO_MAXMUM
    };
    
    static const struct blobmsg_policy sys_info_policy[] = {
        [DEF_SYS_LOCAL_TIME] = {.name = "localtime", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_UPTIME] = {.name = "uptime", .type = BLOBMSG_TYPE_INT32},
        [DEF_SYS_LOAD] = {.name = "load", .type = BLOBMSG_TYPE_ARRAY},
        [DEF_SYS_MEM] = {.name = "memory", .type = BLOBMSG_TYPE_TABLE},
    };
    
    /* 解析BLOBMSG_TYPE_ARRAY */
    enum{
        DEF_SYS_LOAD_ITEM1,
        DEF_SYS_LOAD_ITEM2,
        DEF_SYS_LOAD_ITEM3,
        __SYS_LOAD_ITEM_MAXMUM
    };
    
    static const struct blobmsg_policy sys_load_policy[]={
        [DEF_SYS_LOAD_ITEM1]={.type=BLOBMSG_TYPE_INT32},
        [DEF_SYS_LOAD_ITEM2]={.type=BLOBMSG_TYPE_INT32},
        [DEF_SYS_LOAD_ITEM3]={.type=BLOBMSG_TYPE_INT32},
    };
    
    
    enum
    {
        DEF_SYS_MEM_TOTAL,
        DEF_SYS_MEM_FREE,
        DEF_SYS_MEM_SHARED,
        DEF_SYS_MEM_BUFFERED,
        DEF_SYS_MEM_AVAILABLE,
        DEF_SYS_MEM_CACHED,
        __SYS_MEM_INFO_MAXMUM
    };
    
    static const struct blobmsg_policy sys_mem_info_policy[] = {
            [DEF_SYS_MEM_TOTAL] = {.name = "total", .type = BLOBMSG_TYPE_INT64},
            [DEF_SYS_MEM_FREE] = {.name = "free", .type = BLOBMSG_TYPE_INT64},
            [DEF_SYS_MEM_SHARED] = {.name = "shared", .type = BLOBMSG_TYPE_INT64},
            [DEF_SYS_MEM_BUFFERED] = {.name = "buffered", .type = BLOBMSG_TYPE_INT64},
        [DEF_SYS_MEM_AVAILABLE] = {.name = "available", .type = BLOBMSG_TYPE_INT64},
        [DEF_SYS_MEM_CACHED] = {.name = "cached", .type = BLOBMSG_TYPE_INT64},
    };
    
    static void handle_sys_mem_cb(struct ubus_request* req, int type, struct blob_attr* msg)
    {
        if (msg != NULL)
        {
            struct blob_attr* tb[__SYS_INFO_MAXMUM];
            int localtime = 0;
            blobmsg_parse(sys_info_policy, __SYS_INFO_MAXMUM, tb, blob_data(msg), blob_len(msg));
            if(tb[DEF_SYS_LOCAL_TIME]){
                printf("%d---%u\n",__LINE__, blobmsg_get_u32(tb[DEF_SYS_LOCAL_TIME]));
            }
            for(int i=0;i<__SYS_INFO_MAXMUM;i++){
                printf("%d--%s\n",__LINE__, blobmsg_name(tb[i]));
                printf("%d--%d\n",__LINE__, blobmsg_type(tb[i]));
                printf("%d--%d\n",__LINE__, blobmsg_data_len(tb[i]));
                printf("%d--%d\n",__LINE__, blobmsg_len(tb[i]));
            }
            if(tb[DEF_SYS_LOAD]){
                struct blob_attr* sub_tb[__SYS_LOAD_ITEM_MAXMUM];
                blobmsg_parse_array(sys_load_policy, ARRAY_SIZE(sys_load_policy), sub_tb, blobmsg_data(tb[DEF_SYS_LOAD]), blobmsg_data_len(tb[DEF_SYS_LOAD]));
                for(int i=0; i<__SYS_LOAD_ITEM_MAXMUM;i++){
                    printf("%d--%u\n",__LINE__, blobmsg_get_u32(sub_tb[i]));
                    printf("%d--%d\n",__LINE__, blobmsg_data_len(sub_tb[i]));
                    printf("%d--%d\n",__LINE__, blobmsg_len(sub_tb[i]));
                }
            }
            if(tb[DEF_SYS_MEM]){
                printf("%d--%d\n", __LINE__, blobmsg_type(tb[DEF_SYS_MEM])==BLOBMSG_TYPE_TABLE);
                FILE* fp=fopen("./mem.txt", "wb");
                if(fp!=NULL){
                    fwrite(blobmsg_data(tb[DEF_SYS_MEM]), blobmsg_data_len(tb[DEF_SYS_MEM]), 1, fp);
                    fclose(fp);
                }
                struct blob_attr* mem_attr[__SYS_MEM_INFO_MAXMUM];
                int iRet=blobmsg_parse(sys_mem_info_policy, ARRAY_SIZE(sys_mem_info_policy), mem_attr, blobmsg_data(tb[DEF_SYS_MEM]), blobmsg_data_len(tb[DEF_SYS_MEM]));
                printf("%d--%d\n",__LINE__, iRet);
                for(int i=0;i<__SYS_MEM_INFO_MAXMUM;i++){
                    printf("%d--%d\n",__LINE__, i);
                    printf("%d--%s\n",__LINE__, blobmsg_name(mem_attr[i]));
                    printf("%d--%ld\n",__LINE__, blobmsg_get_u64(mem_attr[i]));
                    printf("%d--%d\n",__LINE__, blobmsg_type(mem_attr[i]));
                    printf("%d--%d\n",__LINE__, blobmsg_data_len(mem_attr[i]));
                    printf("%d--%d\n",__LINE__, blobmsg_len(mem_attr[i]));
                }
                // if(mem_attr[DEF_SYS_MEM_TOTAL]){
                //     printf("buffered:%d---%u\n",__LINE__, blobmsg_get_u32(mem_attr[DEF_SYS_MEM_AVAILABLE]));
                // }
                // if(mem_attr[DEF_SYS_MEM_AVAILABLE]){
                //     printf("available:%d---%u\n",__LINE__, blobmsg_get_u32(mem_attr[DEF_SYS_MEM_AVAILABLE]));
                // }
    
            }
    
        }
    }
    
    /*****************
    FUN:获取系统可用内存
    ARG1: signal_strength,字符串指针,用来保存返回值
    ARG2: len,signal_strength的长度
    RETURN:成功0、失败-1
    *****************/
    int get_sys_mem()
    {
        int iRet = -1;
        struct ubus_context* ubus_ctx = ubus_connect(NULL);
        uint32_t sys_mem_id;
        if (ubus_ctx != NULL)
        {
            if (ubus_lookup_id(ubus_ctx, "system", &sys_mem_id) != UBUS_STATUS_OK)
            {
                return iRet;
            }
            ubus_invoke(ubus_ctx, sys_mem_id, "info", NULL, handle_sys_mem_cb, NULL, 5000);
            ubus_free(ubus_ctx);
            iRet = 0;
        }
        return iRet;
    }
    
    int main(){
        get_sys_mem();
    }
    

总结

  • 针对这种二进制中含有字符串的数据,如果不知道其中的结构,可以使用fwrite将其写入文件然后使用WinHex工具查看其中的值。
  • 那么问题来了,如何提前判断该接口的返回值是64为还是32位呢?总不能每次调用接口都查看二进制数据吧!我觉得在调用接口之前接口文档中必须明确说明返回字段的类型。

fasf fas fkj f