本文主要记录在使用 RT-Thread Netdev 组件的时候遇到的一个结构体不匹配的 bug。
背景
本次 bug 只要涉及 4 个文件:
- netdev.h:定义了 struct netdev数据结构
- netdev.c:netdev 源码实现,这里主要涉及 netdev_low_level_set_link_status 接口
- netif.c:lwip 网卡相关接口,这里主要涉及 netif_set_link_up 接口
- ethernetif.c:lwip 网卡初始化部分代码。这里注册 netdev 设备,涉及 netdev_add 接口
netdev 数据结构如下:
struct netdev
{
    rt_slist_t list; 
    char name[RT_NAME_MAX];                            /* network interface device name */
    ip_addr_t ip_addr;                                 /* IP address */
    ip_addr_t netmask;                                 /* subnet mask */
    ip_addr_t gw;                                      /* gateway */
    ip_addr_t dns_servers[NETDEV_DNS_SERVERS_NUM];     /* DNS server */
    uint8_t hwaddr_len;                                /* hardware address length */
    uint8_t hwaddr[NETDEV_HWADDR_MAX_LEN];             /* hardware address */
    uint16_t flags;                                    /* network interface device status flag */
    uint16_t mtu;                                      /* maximum transfer unit (in bytes) */
    const struct netdev_ops *ops;                      /* network interface device operations */
    netdev_callback_fn status_callback;                /* network interface device flags change callback */
    netdev_callback_fn addr_callback;                  /* network interface device address information change callback */
#ifdef RT_USING_SAL
    void *sal_user_data;                               /* user-specific data for SAL */
#endif /* RT_USING_SAL */
    void *user_data;                                   /* user-specific data */
};另外,运行程序的设备没有调试接口,只有 UART!只能通过串口打日志!
bug 现象
ethernetif.c 中中调用 static int netdev_add(struct netif *lwip_netif) 注册一个 netdev 设备(malloc 方式创建一个设备),并基于 lwip netif 初始化 netdev 数据结构。
此时,netdev 数据结构中的各成员初值配置无误,一切正常。
当 lwip 网卡初始化完成,设置 linkup 状态,调用函数 void netif_set_link_up(struct netif *netif)。netif_set_link_up 函数再调用 void netdev_low_level_set_link_status(struct netdev *netdev, rt_bool_t is_up) 接口,设置 netdev 设备 linkup 状态。
在 netdev_low_level_set_link_status 函数中操作 netdev 数据结构成员,然后就出现了问题!
问题是 netdev 数据结构成员的值与初始化的值不一样!
分析
一般这种问题,我们会理所当然地认为 netdev 数据结构所在的内存块被 xxx 写穿了。
然后就有了下面的一通操作:
- 对该数据结构所有可能被写穿的地方进行了排查,无解! 
- 加 log,检查 netdev 数据结构从哪里开始出现问题。这个时候发现有的 log 打印输出是对的(ethernetif.c 中是对的),有的地方是错的(netdev.c 中是错的)! 
- 猜想:代码优化所致? 
- 编译器优化?加 - volatile,不行!
- 设置局部优化,依旧不行! - #pragma GCC push_options #pragma GCC optimize ("O0") /* TODO Your code */ #pragma GCC pop_options
- dump netdev 数据结构所在内存(乍一看,内存中的数据好像是正确的) 
- 回顾:内存数据正确,编译器优化问题也排除了,内存写穿的可能性很小!奇了怪了 
- 经同事提醒,可能引入了另外一个 netdev 数据结构!思索了下,不太可能存在数据成员一致的另外一个 netdev 结构体。 
- 既然没办法,就验证下正常 log 输出的地方和异常 log 输出的地方的数据结构的大小是否一致,数据成员的地址是否一致即可 
- 验证结果惊喜!两边数据结构大小不一致!Why? 
- 再次检查 netdev 数据结构,并 dump netdev 数据结构内存,发现了猫腻(结构体最后一个成员的位置不对,没有在内存的最后一块地址)。那么说明结构体确实不匹配 
- 检查发现,netdev 结构中使用的 ip_addr_t 存在问题 
在使用 ipv4 的时候,是 ip4_addr_t;在使用 ipv6 的时候,是 ip6_addr_t;
检查我的配置,发现 lwipopts.h 同时开启了 IPV4 和 IPV6,而 RT-Thread 的 Netdev 只支持 IPV4。
So,那么问题明朗了!总结见下。
- 总结
在 ethernetif.c 中创建的 netdev 设备使用的 ip_addr_t 为:
typedef struct _ip_addr {
  union {
    ip6_addr_t ip6;
    ip4_addr_t ip4;
  } u_addr;
  u8_t type;
} ip_addr_t;在 netdev.c 中,使用的 ip_addr_t 为:
typedef struct ip4_addr
{
    uint32_t addr;
} ip4_addr_t;
typedef ip4_addr_t ip_addr_t;因此,ethernetif.c 和 netdev.c 操作的数据结构不一致,所以在 ethernetif.c 中打印输出的数据成员是正确的(因为在 ethernetif.c 中创建的 netdev 设备对象)。
解决方案,升级 RT-Thread Netdev 组件,以支持 IPV6 和 IPV4 共用的情况。
总结
这个问题确实很坑!以我现在的经验,在没有调试器的情况下,定位这类问题相对困难。因此,通过本文记录下分析这类问题可能的手段。(中间我还反汇编看了下汇编代码,然并卵,没有用)
本文的 bug 分析过程直接耦合我的应用场景,如果你遇到上述的 BUG 现象,那么能记得检查结构体是否存在不一致的情况。很有可能您就避免了整个上述的分析过程(都是泪 :cry: )。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!
