Pages

Saturday, 26 September 2015

你碰到过的最难调的 Bug 是什么样的?


传输数据时,对字符串进行判断,为空就不下发配置,非空就下发配置。
然后我获取字符串a后,发现打印出来是””
程序整个过了无数遍,没发现任何问题。
找同事帮忙测试,一切正常
为什么a明明是空的,却判断为非空呢?
为什么a你这么任性的要表现自己的存在感呢?
后来迷迷糊糊的敲了这么一行指令:
print len(a)
返回结果:2
在敲这行指令之前,我在上知乎,嘲笑隔壁那个把ID起成null的答主。
小的是和另外几千人给一个工业母机做软件的。这个母鸡差不多5亿人民币一台,一般大宗生产需要个百十来台满足产量需求。
一台母鸡停产一秒就亏150多人民币。一天上海一套房。
一个母鸡精度会突然下降100%,检测精度的传感器报警,导致母鸡停止下蛋。按照故障查处协议,发现重启系统就好了。然后这个情况一周出现一次,不固定。每次出现就损失一台奔驰E系…
虚拟母鸡模拟,根本不会出现这么极端的问题。各种静态动态分析控制精度的源代码,都没有问题。硬件部门排查相关传感器,也没问题。在内部真实母鸡上还是无法重现。绝望之下还查阅了当地地震纪录…
最后客服建议客户重新安排维护时间来避免独立重启。赔送了客户付费应用。并说开发部门对这个问题需要从长计议。
三个月过去了,某天在食堂偶然听到坐对桌的硬件部门人扯淡他们有个供应商刚汇报了他家寄存器发现了bug,在某一个地址上写不进去,但也不报警啊!
一看出事母鸡果然用的是那个批次的板子…别的母鸡虽然也用那个板子,但不会遇上那个地址…
曾经做过一个项目,一个云计算机房调试的时候碰到一个谁都说不清楚的数据调包BUG,最后老板说实在搞不定那就把出问题环节的设备换了!然后10万新设备换完之后,然并卵!问题依然存在!然后老板绿着脸说请专家,跑到地区的电信机房请来工程师和设备开发工程师还有浙大一个教授!两个工程师都说一个个环节检测换设备!一个月后问题依然存在!那位叫网络通信工程的教授终于开口说了一句:我四年交给学生最主要的本事就是:实在查不出什么问题那就重启,重启是最能有效解决网络异常的手段!然后就bug没有发生了!
网络狗,搞运维的时候曾经帮某银行定位过网银问题,很逗。
银行访问自己的网银都可以通过内网通道,这是背景。某领导一天在行内通过互联网登陆个人网银,发现页面半天打不开,叫人调查。
各部门测试半天,我靠!好多电脑打开网银登陆页面都得十秒左右,时好时坏。当时行内部署了比较全面的性能监控平台,监控了包括网络 应用 交易各种性能,最外侧监控触角覆盖到了运营商入口,但当时看各种指标都正常,而且也没接到什么用户投诉。
我们搞虚拟机各种测,发现从行内第一次访问基本都会有问题,从其他地方只是偶尔出现。用虚拟机测试加抓包折腾了好久,终于找到了问题。网银登陆页面使用的某国外厂商的证书,首次访问需要去更新证书吊销列表,而厂商域名对应的十多个IP呢,其中一个被墙了…只要被解析到该IP,访问就会出问题。
不了解该厂商的权重怎么配的,但是国内多数用户不会被解析到该地址,而且厂商自己不知道那个IP被墙了。于是该行自己的互联网就成了最典型的倒霉蛋…
后来联系该证书厂商,屏蔽掉那个被墙的地址之后果然就好了
之前是做电商的,某较大的电商平台,突然有一天,C2C的店主反馈,看到的订单不是自己的,看到后台的商品列表也不是自己的
当时在睡午觉,看到这个问题,立马吓醒了,平时5个投诉就是一个故障单,那还都是一点体验上的小问题,这种订单混乱,商品混乱的错误,真是要紧急死了
于是,主管,总监都来看这个问题,一群大佬在后面看着,赶紧找最近几天的发布,测试情况,一个个回退,一个个检查,最后都无法解决问题,要知道时间一分一秒过去,半个小时还解决不了就要出大事了
后续又有用户来投诉,直接电话联系,远程控制电脑,发现操作起来巨慢,于是顺口问了一下用户的网络是什么网络。
结果他说是:“某城宽带”,一瞬间,有点感觉了,继续问其他几个投诉的客户都是“某城宽带”,然后我们打电话到那个宽带的运营商,得到的回复是“年底了,为了省流量,他们做了一部分缓存”
他们做了缓存
做了缓存
缓存

你没猜错,他们把POST和GET动态请求也缓存了,就是说你提交了一个POST修改商品的请求,他从环缓存里面随便丢个回复给用户,用户感觉修改成功了,其实请求根本没到我们这边
几年前吧,一个客户想装一套车辆管理系统,整个实现起来不难,基本上就是RFID读卡继电器控制道闸机抬杆,根据两个地感线圈检测到车辆的压过地感的顺序 做出入判断,最后落杆。
安装完成,检测,一切正常,收工! 然后第三天客户晚上打电话给我了,说系统老是自动抬杆。
深夜,绝对没车过,但是系统老检测到有车,隔几分钟就自己抬一次。白天没有,就深夜有!
想不通啊,上位机程序是我一个人写的,抬杆的触发条件就两个啊,一个是读到卡信号,一个是防止砸到车,会检测地感上有没车辆啊。
然后客户那边一个领导(就是打电话给我的那人)跑过来和我抽了根烟聊聊,他问我,地感是怎么感应车辆的,其实我也不大清楚,毕竟我不做硬件,但是总不能这样回答客户不知道是吧,好在大方向上知道一点,客户也不懂,于是胡诌大概是这样的,电磁感应你知道不?其实就是地下有个线圈,通电不就不就有磁力了么,你车是铁的,磁场不就改变了么,然后我探测到磁场的改变,就知道有车了啊。
当时那大叔脸都青了!那客户的大楼是新建的,以前是公墓,每次都是大半夜的时候!!!而且还提示有什么东西进进出出的,你还看不见!!!嗯你懂的!!!
以前做windows技术支持,一直调试crash dump。就我个人的体验,有关线程安全的dump是最难调试的,来无影去无踪,看到的就是一坨已经被破坏的现场(dump),然后你需要在大脑中还原案发经过。
有一天香港某大公司(名字不透露了)上了一个case,他们自己的一个应用在生产环境中会莫名奇妙地crash。当时我就想:你自己应用crash找我们干什么,肯定是你自己代码问题,而你还不给我看你的代码!
所以几个难点:
第一,没有客户代码。
第二:客户用的系统是NT4!NT4什么概念?就是没有pdb文件的,符号文件只能对应到函数入口,对应不到具体的源代码行号。你只能把整个函数的汇编都读懂才能知道crash的地点是在做什么事。
第三:只有dump,不可能设断点调式,因为根本不知道如何重现。
反正就这么读了几百上千行的汇编(此处省略两千字),最后定位crash的地点,能看到进到EnterCriticalSection的api之后发现这个CRITICAL_SECTION结构其实已经坏了,然后就挂了。从heap结构可以看出似乎那个heap block已经被用作它用了,所以有可能那个CRITICAL_SECTION已经被delete了。
然后看谁管理这个CRITICAL_SECTION的, 发现是msvcrt,还是VC5的。好吧去找代码,还好那个代码是找得到的。然后就把所以处理这个CRITICAL_SECTION的代码全部找出来,把所有代码在不同线程中的不同执行顺序都排列出来,最后发现在某一个特殊的执行次序下会有一个race condition,导致这个CRITICAL_SECTION会被过早delete掉。还好一开始就怀疑是线程安全问题,入手方向没错。
好吧,最后居然是vc runtime的bug。当初错怪客户了。
调完这个bug的副作用就是之后看到汇编就想吐。看看现在c#的调试那根本不是事。 
评论里信誓旦旦要小护士的兄弟,你真觉得长着一张工程师脸的你能搞定么? 木有高富帅的命,却得了高富帅的病……洗洗睡吧亲,一个成功的工程师是注孤生的……
XXXXXXXXXXXXXXXXXXX
网络硬件相关
现象:
某医院部署的网络,不定期会有半夜断网或者不稳定情况,但天亮就会恢复,客户投诉抱怨。
调试过程:
现场查看全部网络硬件正常,查看log发现有一台汇聚交换机有反复重启动作,在重启前有高温告警。于是重点关注该机器。
该机器放在一个机柜中,机柜在一个小储藏间的角落里,储藏间不大,一边还摆着张破沙发,正好可以坐着用电脑调机器,但是实在查不出什么可疑情况会导致过热,因为投诉等级较高,于是连夜蹲守。
第一夜无事。
第二夜无事,到半夜,忽然进来个小护士,吓一跳,说,哟怎么有人啊,然后就走了。一夜无事。
第三夜无事,到半夜,又来个小护士,探头看一眼走了。一夜无事。
第四夜无事。
于是告诉院方,发现问题马上打电话,回家。
第五夜出事,赶到时已是早上,网络已经正常,查看log发现还是过热告警重启,时间在半夜3点多。联想到前几天的小护士,于是问院方半夜是否有人进入,答一些值夜班的护士会偶尔在里面休息。
于是找到进去的小护士,问是否动交换机,答没有,问进去后做了些什么动作,答只是睡觉。再追问,除此之外呢?答:就是那个排风扇太吵,睡觉的时候把电源拔了。
她把机柜的冷却排风扇电源拔了!
她把机柜的冷却排风扇电源拔了!
她把机柜的冷却排风扇电源拔了!
她以为就是个通气风扇!
居然睡醒走了还知道再插回去 〒_〒
你有胆拔插头你倒是别插回去啊…
EEEEEEEEEEE分EE割EEEEEEEEEEEEE
再说一个吧。
研发的一块新电路板,调试正常,往机箱里面装,装上螺丝拧好后不上电了,没有电压,确认是电源短路保护。
把板子拆下来,又能用了。
装上去,又不能用了。
跟白鹿原里白孝文在窑洞里穿裤子一样。
机箱是金属并且接地的,检查了全部连接,电源肯定木有碰到地,但是用万用表量的明明就是电源地短路,而且就是裸板能用,带机壳就短路,于是怀疑螺丝。
螺丝都拧上就短路,都拆下来就正常。
然后挨个拧螺丝,定位到某个螺丝。
那个螺丝一拧上就短路。
但是电路板正面反面都是地,螺丝本来拧上去就是为了接地用的,怎么会把电源短路了呢……
这tmd不科学啊。
仔细端详该螺丝孔,发现内壁有些黑,凑近闻略有焦味。心里大概有数了,一查pcb图,果然,6层电路板,内层电源层的铺铜几乎直接铺到了螺丝孔,安全距离只留了一点点。
其实本来也没什么,螺丝只是固定用的,不会和螺丝孔内侧有什么触碰,好死不死的那块板子那个螺丝孔公差偏大,螺丝拧上去是没有完全对齐的,直接卡到了螺丝孔内壁……使劲一拧,就像刀一样切了进去,碰到了内层电源。
所以,所有灾难,都是一连串小概率事件的巧合扎堆,搞科学,也得信命。 
差不多10年前,我们做了一个ARM核的芯片,据说还是国内第一批用ARM7做的,还挺高端,带有很多安全功能,当然安全就意味着难以调试,整个系统全部打散,不能分块。俺负责前端设计,系统,硬件软件驱动等杂七杂八一堆工作。
然后芯片流出来了,封装回来,几天几夜的调试,功能都正常了,那个高兴呀,第一个芯片就成功,奖金有了!
不过做稳定性测试时候有一个问题一直困扰着,这系统总是莫名其妙的有时候启动不起来,概率有个百分之几左右。上电就是不LOAD。而一旦起来之后,就很容易了。
反正功能设计,硬件,驱动都是俺的,那就调呗,软件,硬件,电路,仿真,研究了好几天,抓狂,无解。又整个系统不能分块,我都开始怀疑是不是ARM核的问题。。
又做了一个不断重启的测试系统,不断啪啪响上电断电,针对上电的情况作了统计。得出结论就是,上午不启动的概率高,下午不启动的概率低,晚上不启动的概率高,深夜不启动的概率低。。。。。和饥饿程度快挂钩了。。。
那时候那个抓狂啊,怀疑是什么干扰的,连屏蔽房,隔离电源啥都整出来了。就是没头绪,而公司给客户演示的时间快到了,要是现场挂掉就丢脸了,心里那个急啊。那段时间,每个深夜,公司里就是我座位上啪啪啪的声音——继电器的啪啪声。
接下来一个周日测试,公司空调坏了,汗流浃背,脾气极坏,几乎就要摔板子了。不过发现这天运气非常好,成功概率很高。没头绪,直接抽上烟,看着板子发呆,不知那根神经搭错,直接把烟头对着芯片戳上去!咱第一个亲生芯片!!如果不行了就掐死它!!!结果发现怪了,戳了烟头,启动哗哗的,每次都OK。遂怀疑是尼古丁过敏或者是温度原因。拿着烙铁烫着它,每次必成。于是送进高低温箱,做温度曲线测试,发现环境温度40度以上,成功概率极高,刚好碰见今天加班没空调,平均温度高,所以表现良好。而启动起来因为系统一发热,所以后面启动就容易了,温度一凉下来表现惨不忍睹,敢情这芯片是非洲来的。
有了方向就好说,先解决DEMO,给领导好看。遂做了一个电热丝发热电路,贴在芯片上,用单稳开关控制,一上电就加热,然后不断自动啪啪啪对芯片重启,一旦芯片重启成功了就断开发热电路和重启电路。进入正常运行情况。系统搭起来一测,效果杠杠的!!!基本都能保证几秒钟内就能启动,公司上下一片赞誉。 于是,领导拿着这套带着电炉丝的系统去做报告,销售拿着这个电炉丝Demo去给客户演示,取得极好成功,老板都在准备后续的销售计划了。俺心里急啊,总不能出货产品也带着电炉丝吧。。。。
静下心好好分析,和温度有关,又是随机故障,应该很可能是哪个地方悬空,存在不定态的问题,外面的电路是不可能了,前端模拟加入随机量也不能重现,那很大可能是后端的人搞的鬼,遂拉来后端人员(暂且称为C公司),检查扫描链和测试电路,果然发现有一个寄存器没有初始化复位。于是后面的情况就简单了,往扫描链中灌入一串数据,把未知量洗出来。成功!!!
所以我们第一代的产品,主芯片旁有一个奇怪的芯片。据线人报告,有竞争对手和盗版者都认为这是安全反盗版电路,因为拆掉这一块,系统工作就时不时的异常,抓不到规律,可能包含短时间正版验证,长时间正版验证,随机正版验证等高精尖反盗版措施。反正无法破解。。。。
俺笑而不语。图样图森破。:)
—————————————————
至于说为什么寄存器没有初始化复位没检查出来,我也不知道,这是人家C公司做的后端,他们的软件自己加进去的电路。而据说这C公司虽然牛,但那时候后端服务还是新的,软件也是新的,刚进国内,给我们一个特惠价做白老鼠。。。 
—-
08年的时候,我所在的公司调试三星的一款新的arm9 CPU,型号是S3C2416,是S3C2450的简配版。开发板刚入手的时候还是热乎的,因为三星的这个芯片刚刚出来,国内的代理商一共就几块开发板。各公司评估开发板都是分时使用的,只能预约几天。开发板入手的时候,三星那面连BSP都没有准备好,没有test code,没有u-boot,没有linux-kernel,甚至连Spec都是错误百出。还好我公司虽然小,研发能力在本地区还算不差,没有的东西可以自己移植。
公司急着要出新品,在没有完全验证处理器的情况下,已经layout好了PCB,并且去打样了(当时竞争确实比较激烈,400M主频处理器而且这么低的价格绝对非常有诱惑力,所以公司决定冒这个险了)。在没黑没白的工作两周后,硬件和软件做的都差不多稳定了。这时候经理说,功能上问题不大了,我们来调一调休眠时的功耗吧(我们的产品一直以待机时极低功耗作为产品的卖点之一)。然而这却是噩梦的开始……
公司的指标是待机时休眠电流500uA~800uA(电源电压4V)之间。以前所有的产品都在这个范围之内,三星方面的技术支持也明确表示,他们的解决方案达到这个指标。
在我们调试过程中发现,整个系统休眠时的功耗在1800uA左右,一直降不下来。我们重新核对了所有的IO和外围电路的所有连接,以及IO口的电平配制,都没有问题。这时,我们决定测试每一个单元的功耗,用电流表分别串联进每一个外围电路,每个单元都很正常,就是系统总体偏大1000uA。
我们连flash和ram的待机电流都测过了,仍然正常。好了,通过排除法已经确定了就是CPU的功耗过大。但是在开发板上调试休眠的时候,CPU功耗却是正常的。
我们怀疑是开发板上CPU批号和我们自己拿到的CPU样品的批号之间有区别导致的,因为三星那面也在同步修正CPU的BUG,所以我们“大胆地”把开发板上的CPU用风枪吹下来,换到我们的PCB上,把我们的CPU贴到了开发板上进行交叉验证。结果是开发板仍然功耗正常,我们自己的板子上功耗偏大,还是大了1000uA。
CPU周边的核心电路设计出现了问题!这是我们一致的判断!但是问题出在哪里,我们反复核对开发板的原理图和我们自己板子的原理图,简直就是一模一样!因为整个核心电路这部分就是从开发板上抄过来的,实在没有什么可比对的。我们转而又去怀疑PCB的问题了。
我是做系统移植和软件的,纯电气的问题我就无能为力了。闲着没事,我就反复检查我在linux中对系统休眠的IO引脚配置。然后挂着电流表做反复测试。电流表也对的起我,每次都是那个数。在一次系统待机的时候,我实在忍无可忍,一把抓起了板子。突然之间,电流表的读数飞快下降,降到了300uA!我松开手电流表的读数就又爬回来了。我把我这个惊奇的发现告诉了同事——一个硬件工程师。同事说可能是哪儿摸短路了,让我试试还能不能唤醒系统。我给了一个外部中断,系统神奇的正常唤醒了!
“难道这就是问题?”,我想重现一下。但是再次在待机的时候抓起电路板的时候,读数并没有显著发生变化。“可能是手法不好”,我这么想着,用手在板子上继续抚摸着。果然!当我的手指按到PCB中的某一个位置时,电流又降了下来!反复试了几次,都是这样,就是在我手指按压的这一片,只要是用手指按着,电流就正常!
这回同事开始重视了,打开PCB图,拿着电路图和万用表,查查我摸的到底是那块电路。硬件工程师觉得不可思议,因为我摸的部分并没有连接任何的电路——焊盘是空的。他于是用万用表的表笔去检查是不是PCB制版的问题,测一下这些空焊盘到底哪一个有电压。但是万用表中没有读数,这块都没有电。但是当万用表的表笔落在一处空焊盘的时候,电流表的读数又降下来了!
这可是重大发现,我们对照了一下电路图。这处空焊盘是CPU中USB-Host模块的D+信号。由于我们的产品不需要USB的主机功能,所以这一块儿没有做任何处理。多亏了画原理图和PCB的同事,多留了一手,把USB Host的引脚都在PCB上做了个引出。谁也没想到是这个引脚出现了问题,辛亏这个信号引出来了,要是没有引出来,一辈子也查不出问题。我们给D+信号加了一个下拉电阻后,系统的功耗瞬间正常了。
事后分析,三星自己开发板上有USB-Host的功能,所以USB-Host的外围电路也是完备的,所以功耗不会有问题。但是我们自己的产品上不使用USB-Host功能,没有相关外围电路,所以出了问题。这是因为在CPU休眠的时候,D+信号内部被悬空了!一句话,是三星CPU自己的BUG。我们修改了我们的PCB,增加了一个下拉电阻,同时将问题反馈给了三星。
一个月后,当我们的产品量产时,三星也及时的解决了这个问题。那个下拉电阻也不需要再贴上去了。
最后用手指头找到了CPU的BUG,不知道这算不算是最难调的。
反正这么多年了,这个经历留给我的印象是最深的。 
不算是最难,但是很有意思的一个bug。
我们系统里有大量的VR (virtual route),每个VR都有一堆的防火墙规则,大约几百条吧。所以总共有几十W条防火墙规则要在系统启动的时候写入到系统里面去。
你要问为什么不提前写好,那是困为我们用的是cluster,都没有硬盘的。没地方写啊。所有的软件都要在启动的时候安装一遍,系统配置要在启动的时候写入一次。
以为背景。
平时用着好好的,大家都很开心。直到有一天香港的有个客户心血来潮一下子配了几百个VR,配了就配了吧,用的好好的。大家照样很开心。
直到有一天他们做了个作死的事情。
他们决定要把系统重启下。
话说我也不知道他们为毛闲的要重启系统,因为我们的设计目标是除非升级是不需要重启的。可能只是有某个人手贱想试试这个从来没用过的功能吧。
然后我们系统就启动不起来了。 启动持续了好几个小时都启动不起来。
这不正常。
于是抓log,看现场。
各种narrow down,最后发现erlang gen_server (你可以认为是一个dispatcher)的队列里留着几W条未处理的消息,这个gen_server是被活生生堵死的。然后查查查,发现 iptables写规则实在太慢了。我们的代码又是一条一条写的,每次写iptables都要初始化一下,结果几百个VR,几十W条firewall规则的写入请求堵在了gen_server的队列里面,然后挂了。
知道了原因以为好修了,一个进程写不动,我们为每个VR开个进程不就行了?我们为自己的聪明才智感动不以。
事实证明我们too young too simple,
我们为几百个VR开了几百个gen_server进程大家一起努力往linux里面写。
然后
iptables挂了。再然后
linux内核挂了。
内核挂了
挂了
于是只好想办法把iptables写个driver集成到erlang里面去,去restore的方法来做。这样就跳过了每条规则初始化的时间。虽然还是需要几个小时再启动,但是好歹能启动了。
什么再后来?
再后来我们把VR的驱动改了个底朝天,搞定了。