面试问题
问题描述:
1. 第一道题目:在多线程和大量并发环境下,如果有一个平均运行一百万次出现一次的bug, 你如何调试这个bug。 题主对于这种比较大的工程没有接触过所以没有这方面的经验,我回答的是:首先要试图重现这个bug,在重现bug时,需要保留当时的一些状态信息,然后进行调试,依次确定与这个bug有关的模块(加桩和驱动)。听到我的回答后,面试官说这个bug很难重现,这个时候你要怎么处理或者重现呢。 我回答说:那么这个bug出现的概率比较小,如果没有造成太大的影响能不能忽略。 然后面试官说那10W次出现一次呢, 之后我就没有了想法。
2. 第二道题目的题意我不理解,题目是面试官给我的试卷上面的。题目描述:有一个类指针,指向类实例化的对象,在这个对象程序的运行过程中,程序崩溃了,后来发现是这个类指针的虚函数表被破坏了,现在如何定位这个问题。我把"如何定位这个问题"理解的是如何定位这个bug所在,然后我回答:知道虚函数表被破坏了,那么问题不是在这里吗,可以依次确定是不是调用了应该调用的虚函数。然后面试官说虚函数表里面的内容没有问题,之后我有点蒙了,我回答说那是不是指向虚函数表的指针被破坏了。面试官说你可能没有理解题意,所以我比较郁闷,是不是还有另外的原因。
回答一:
对于第一个问题而言,解决Bug,第一步就是重现,第二步定位以及Reduce,第三步再来解。所以,不管百万次还是十万次,首先要重现出来,然后找出重现出来的计算机状态。计算机不会欺骗人,每一个问题出来肯定是有原因的,唯一要做的就是如何把这个计算机状态信息还原出来,你可以使用log跟踪等,怎么纪录还原都是工程师的选择。而若能把相关的状态信息拿到,剩下的就是定位是哪里的问题,而这时候最好的就是模拟和Reduce,把问题缩小,排除其它信息干扰。模拟与Reduce成功以后,再想办法解决,然后再来估计解决问题的难度与成本问题等,有些BUG我们是知道,但是解决太麻烦了,影响也不大,就放着。
对于第二个问题而言,需要先限定编译器和环节,比如,virtual table 在 Linux 下 GCC 4.9 的实现就是放在read only 段 .rodata,怎么可能被修改?好,就算可以被修改,我第一反应就是上GDB与Valgrind,被破坏的原因很多,你不让我调,我怎么跟你继续说下去,不如直接给我代码,我调给你看? 那你首先准备一个这样的代码?
对于第二个问题而言,需要先限定编译器和环节,比如,virtual table 在 Linux 下 GCC 4.9 的实现就是放在read only 段 .rodata,怎么可能被修改?好,就算可以被修改,我第一反应就是上GDB与Valgrind,被破坏的原因很多,你不让我调,我怎么跟你继续说下去,不如直接给我代码,我调给你看? 那你首先准备一个这样的代码?
回答二:
1. 先问清楚 bug 是属于哪一类。崩溃?数据不一致?然后可能要在问更多的资料,才能考虑用什么方法。题主说的尽量保留状态是对的。然后可能要针对相关的软件部分,看看能否有测试能重现问题,逐步收窄范围。另一方面,从管理上应该要考虑 bug 的严重性与成本/时间的问题。如果最终能找出问题,需要研究怎样防范相似的 bug。
2. 虽然不确定问题是否准确,但如果有对象的内容被越界写入,不能只考虑该对象(若只考虑该对象就是错误地收窄检查范围),程序中任何地方也可导致该问题。首先看看能否用工具检测,例如 valgrind 之类,成功的话可以很快定位问题。如果错误写入的位置是固定的,可以用调试器的硬件断点,当写入该数据时看调用栈。但如果真的没有合适的工具可以运行,而又有固定的出错情况,那么可以尝试写一个函数去检测数据的合法性,然后在代码中插入该测试,看看能否逐步收窄范围。
2. 虽然不确定问题是否准确,但如果有对象的内容被越界写入,不能只考虑该对象(若只考虑该对象就是错误地收窄检查范围),程序中任何地方也可导致该问题。首先看看能否用工具检测,例如 valgrind 之类,成功的话可以很快定位问题。如果错误写入的位置是固定的,可以用调试器的硬件断点,当写入该数据时看调用栈。但如果真的没有合适的工具可以运行,而又有固定的出错情况,那么可以尝试写一个函数去检测数据的合法性,然后在代码中插入该测试,看看能否逐步收窄范围。
回答三:
因为这些都是假设性问题,所以一般都只能说排查问题的思路:
问题一:
1:其实100万分之一的几率并不算很低。一个繁忙一点的服务,每秒一万个请求是很正常的(如果业务简单,还能更高)。所以百万分之一的复现概率,相当于在压力测试下几分钟就能出一次了。所以在道理上,如果有服务以这个几率出现bug,让它上线本身就是测试部门的失职。
2:反过来说,既然几分钟就能复现,那就在测试环境里压就好了,想怎么改就怎么改,想怎么打log就怎么打log。
3:假定测试环境的压测真的没出来,真的线上出现这样的问题(例如说压测的用例没有覆盖到)。首先考虑的是回滚服务到旧版,以确保正常业务不出问题。然后在线上环境提取某台机,用类似tcpcopy这样的软件导一份数据到测试环境中,来测试这个有问题的版本。这时候,既然是测试环境,你想怎么办都行(不影响线上服务)。
4:到了真正去分析bug了,那首先确定bug的类型。例如说如果能core dump的话相对好办一些,因为可以从core dump大概能看出或者猜出一些东西来。而如果并不core dump,而只是导致数据错乱的话,那就要做数据出入口的校验等。
5:在各顶级模块的入口出口打log,定位发生bug时所在的模块,然后逐步收敛。
6:确定这些模块以及关联影响的模块,单独提取这些模块出来,进行测试或者code review,最终确定问题。
问题二:
1:这类bug,一般都涉及越界操作。
2:一般这类bug,大多数都是可以通过静态code review解决的。既然知道是那个类,盯着用到这些类的实例的地方就行(包括这些类实例的前后几个对象,因为有可能是它们越界)。
3:一些工具,例如valgrind、purify之类的,也有可能有帮助(不一定肯定有用,但一般还是挺有用的)。
问题一:
1:其实100万分之一的几率并不算很低。一个繁忙一点的服务,每秒一万个请求是很正常的(如果业务简单,还能更高)。所以百万分之一的复现概率,相当于在压力测试下几分钟就能出一次了。所以在道理上,如果有服务以这个几率出现bug,让它上线本身就是测试部门的失职。
2:反过来说,既然几分钟就能复现,那就在测试环境里压就好了,想怎么改就怎么改,想怎么打log就怎么打log。
3:假定测试环境的压测真的没出来,真的线上出现这样的问题(例如说压测的用例没有覆盖到)。首先考虑的是回滚服务到旧版,以确保正常业务不出问题。然后在线上环境提取某台机,用类似tcpcopy这样的软件导一份数据到测试环境中,来测试这个有问题的版本。这时候,既然是测试环境,你想怎么办都行(不影响线上服务)。
4:到了真正去分析bug了,那首先确定bug的类型。例如说如果能core dump的话相对好办一些,因为可以从core dump大概能看出或者猜出一些东西来。而如果并不core dump,而只是导致数据错乱的话,那就要做数据出入口的校验等。
5:在各顶级模块的入口出口打log,定位发生bug时所在的模块,然后逐步收敛。
6:确定这些模块以及关联影响的模块,单独提取这些模块出来,进行测试或者code review,最终确定问题。
问题二:
1:这类bug,一般都涉及越界操作。
2:一般这类bug,大多数都是可以通过静态code review解决的。既然知道是那个类,盯着用到这些类的实例的地方就行(包括这些类实例的前后几个对象,因为有可能是它们越界)。
3:一些工具,例如valgrind、purify之类的,也有可能有帮助(不一定肯定有用,但一般还是挺有用的)。