一点debug思路

debug思路

*/–>

debug思路

记录下还记得的几次debug思路,和工具的使用,供大家参考。

1 龙芯 exfat/samba 文件系统下,所有文件大小都为0

龙芯下通过深度文件管理器看到的exfat以及samba系统的文件大小都是0,但文件名以及
目录结构都是一样。

解决过程
先简单花几分钟进行以下操作,收集更多信息。

  1. dh -hT 查看两者都是fuseblk文件系统,并非正常的fat格式。
  2. 从命令行观察结果发现现象一致。(排除文管的问题)
  3. 拷贝/创建新文件在文件系统上,发现一样为0。
  4. 直接打开文件,无法正常显示文件内容。
  5. 拷贝文件到其他文件系统,发现文件内容恢复正常。(说明数据并没有丢失)

准备debug。

  1. 验证问题出在user态还是kernel态。 通过 strace -e stat ,跟踪发现syscall时已经是错误的结果。
  2. 说明问题是从kernel里出的,因为其他文件系统都没问题,所以VFS subsystem的可能性不大。
    结合之前看到的都是fuse文件系统。此时停下稍微熟悉下fuse文件机制,找一些教程,写一个最简单的
    fuse文件系统,看是否也会出现此问题。
  3. 简单补充知识后会理解,fuse在kernel里只是做了一些中转的工作,实际的文件系统还是在用户态。
    因为时间原因没有测试fuse hello world,而是直接对exfat的代码结构进行熟悉。
  4. 通过 dpkg -l | grep exfat; dpkg -L exfat-fuse 会快速了解到实际的核心代码在 /sbin/mount.exfat-fuse
  5. 此时搭建调试环境。
    1. 下载exfat-fuse,不做任何修改使用 dpkg-buildpackage 进行无脑编译。(为了后面重新编译时有cache等)
    2. 编译时,考虑如何加快测试。 U盘虽然是别人的,但来回挂载、卸载容易弄坏我的USB口,所以改用
      dd,mkfs,losetup,mount 的方式,编写一个脚本配合 exfat-fuse 的 make。做到修改代码后一键重新编译,挂载,观察
      的效果。 (减少试错的成本)
  6. 进入源码 ag stat , 找到几个可能的关键函数。全部加上 printf ,打印文件大小。发现exfat内部获取文件大小时是正确的,
    但向kernel通讯时发送的文件大小缺是错误的。 (先确认文件大小是从哪里开始出错的)

    此时可以确定锁定了问题范围,得出初步结论: 问题不在kernel的fuse文件系统,但因为samba和exfat都有这个问题,因此
    仅修复这两个软件还不足以保证所有的问题都被解决。(担忧2秒钟,继续debug)

  7. 关键代码如下

    static int fuse_exfat_getattr(const char* path, struct stat* stbuf)
    {
        struct exfat_node* node;
        int rc;
    
        exfat_debug("[%s] %s", __func__, path);
    
        rc = exfat_lookup(&ef, &node, path); 
        if (rc != 0)
            return rc;
        //stbuf是一个标准的struct stat结构,为了传递给kernel使用的
        //node是exfat内部的inode结构,此时node对应的size已经是正确的文件大小了。
        exfat_stat(&ef, node, stbuf); //在exfat_stat内部时stbuf也被初始化为正确的文件大小了。
        //当exfat_stat返回时,stbuf.st_size错误的变成了0。
        exfat_put_node(&ef, node);
        return 0;
    }
    

    继续各种加printf,失败几次后陷入困境。开始使用gdb断点跟踪,在exfat_stat最后一条指令执行时stbuf->st_size
    的值依旧是正确的。可一旦执行下一条指令进入fuse_exfat_getattr后stbuf->st_size就变成0了。
    依次确认以下信息

    1. 两者的stbuf地址是否一样。(一样)
    2. 是否有其他线程或signal handler改写了这块内存。(通过info thread发现只有一个线程。通过handle SIGXX发现此时没有收到任何可疑信号)

    回过头再次确认exfat-fuse在x86上是正常的(虽然已经询问过,但还是手动确认下比较靠谱),且
    samba出现类似现象,说明不太可能是exfat-fuse的bug。

    单条CPU指令(RET)过后stbuf->st_size值就变动了。 编写一个exfat_stat_snyh函数,只做stbuf->st_size的存储操作,进行观察。
    反汇编后发现store存储的偏移似乎不对。回过来使用ptype打印stbuf. 以及info types stat观察实际的定义。(c/cpp里面的header
    文件包含太多太多的宏定义,最好通过gdb info types去观察实际用到的最终定义)。
    多个地方都指向了st_size的offset是不对的。(实际在反汇编,以及info types的时候就已经暴露了,但因为没有太仔细,多耽误了一会)
    然后使用offsetof(struct stat, st_size)在两处进行打印,确认st_size确实是不一致的。

  8. 此时已经定位到了准确的问题,剩下的就是进行原因追踪,以及知识补充。后续发现是_LARGEFILE64_SOURCE的问题,这个属于

  LFS Extension 如果之前知道这个,类似问题就很容易排查了。

2 申威 启用llvm后,集显和A卡独显无法使用同一份二进制

申威下对mesa开启llvm支持后(不太完整的llvm,无CPU架构支持),r600等基于gallium框架的驱动就
可以使用llvmpipe进行IR优化了。但带来的问题是没有独显的机器使用这份二进制就无法启动Xorg
因此目前的折中方案是,对不同硬件的系统,编译出两套版本。mesa/xorg加起来有十几个包,维护起来
很麻烦。

拿到问题后,先补充知识,继续熟悉下llvm和mesa相关体系。(和问题无直接关系)

开始debug。(A卡独显的机器)

  1. 查看 /var/log/Xorg.0.log 确认加载的驱动信息。
  2. 使用 sudo cat /proc/$(pgrep Xorg)/maps | awk '{print $NF}' | grep dri | sort | uniq 看实际
    的dri加载情况。 发现使用的是radeonsi_dri.so。 然后用ldd观察确实出现llvm字样。 (先确认独显上llvm相关支持
    确实加载了)
  3. 因为A卡独显本身也没问题,所以简单的观察确认下信息后换到集显的机器。
  4. 准备直接拿A卡独显的二进制包安装到集显机器上进行观察。不过没有硬件环境,所以继续在A卡独显机器上进行观察。
  5. 集显机器会使用swrast_dri.so这个dri驱动,因此观察下。发现异常swrast_dri.so也加载了llvm。
  6. 按当时的理解来说,swrast是老式的mesa驱动,里面不应该有llvm相关的代码。然后通过 md5sum /usr/lib/$arch/dri/*
    观察发现swrast_dri.so和radeonsi_dri.so内容一样,也就是使用的是gallium框架。不合理呀。
  7. 下载对应源码,观察编译脚本,发现with_gallium_drivers里最终包含了swrast。因此将此项去掉,将swrast恢复到
    with_dri_drivers里去。
  8. 因为源码本身没有做任何变动,因此判断大概率会解决此问题,且没有硬件环境,因此直接交付。 经最终测试是可以预期工作。

8.1 交付前使用glxinfo|grep renderer发现是使用的llvmpipe,使用 GALLIUM_DRIVER=softpipe 进行测试发现可以正常工作。

  1. 因为出现一个知识冲突,个人理解里src/mesa/swrast这个代码树在src/mesa下那一定就是老式的架构。llvm只会出现在新的
    gallium里。 然后还有一个异常,找了半天没有看到llvmpipe_dri.so这个驱动,但renderer里显示的确实使用的是llvmpipe呀。
  2. 继续搜索llvm相关关键字,熟悉mesa源码,配合项目编译脚本很快了解新的信息。mesa,gallium里的llvmpipe,softpipe以及
    mesa/swrast最终体现的名字都是swrast_dri.so。但具体使用什么是通过编译参数以及环境变量来决定的。

3 x86 仓库的docker无法正常启动

很久没用docker了,然后有一条商店服务器要进行一个简单的更新,结果对应的docker死活起不来。
想到可能是版本问题,因为仓库的比较老,以前都是需要从其他地方下载才能用新版本。

  1. google搜索docker download,艰难的下载后测试发现问题依旧。
  2. docker info观察版本。感觉有点新。
  3. 删除全部的docker images以及文件系统上残留的docker相关数据
  4. 从daocloud.io下载docker包。
  5. 问题依旧,但比之前表现的现象要好点。
  6. 手动启动dockerd加debug信息,观察依旧没头绪,启动containerd的时候就是死活起不来。
  7. 再次全部清理数据,安装仓库的版本,发现仓库有docker.io和docker-engine,且docker-runc的版本在
    docker info里汇报的有问题,然后观察dpkg -l 发现确实仓库里打包的docker组件又出现不匹配的情况。
  8. 找仓库维护人员要求更新docker。
  9. 此时觉得应该就是这个问题,忙别的去了。
  10. 想想还是不对,一天快过去了,docker还没跑起来,继续从头重新跟踪。
  11. 继续观察出错信息,观察docker的各组件情况。突然发现docker-containerd竟然是从/usr/local/bin
    启动的。一口老血,想喷自己。 /usr/local/bin是很久前做测试时编译的版本。所以不论我怎么换docker的
    版本,怎么清理数据。最终docker-containerd和dockerd的版本肯定匹配不上,所以老是启动container时
    就报错。

4 总结

  1. x86下docker的这个是反面例子,直接假设了问题出在版本上,没有认真观察数据,来回浪费时间。
  2. 拿到问题后,先考虑涉及到的组件,考虑如何缩小问题范围。(一般直接用print,先定位在哪个组件里)
  3. 范围缩小后,先使用工具或编写一些脚本减少试错成本。(很重要,影响都后面调试的心情和效率)
  4. 结合实际观察到的数据,进行思考。一层一层的考虑,不要一下子钻太深。
  5. 问题解决后,尽量回过头把还未解决的”信息不一致”解释下。
  6. 问题解决后,把涉及到的体系知识再了解下。

5 一点小技巧

  • 若项目的编译脚本太复杂了,就使用 dpkg-buildpackage -nc -b 来加快重复编译时间。
    但尽量可以做到只重复编译某一个模块的代码。若是so文件则编辑/etc/ld.so.conf或LD_PRELOAD或在/usr/lib下建立
    软链接到源码目录里。加快编译,测试的速度。
  • autoxxx机制下,一般实际的.so文件在.libs目录下。
  • gdb里面ptypes,info types,info func都是很有用的命令,尽快熟悉。此外还有info signal,handle等在遇到信号
    处理时需要使用。
  • 多用strace观察kernel和user态间的交互。 strace -c 观察所有涉及到的syscall。 stracec -y 让fd相关信息更直接。
    strace -T, strace -r 观察时间相关的信息。 此外任何情况下都加上 -f 选项。
    有需要可以自行编译strace,加上unwind的支持。
  • 平时和修完bug后多补充自己的体系知识。

Created: 2018-11-02 Fri 14:02

Validate

2 条思考于 “一点debug思路

发表评论

电子邮件地址不会被公开。 必填项已用*标注