CPPCON2021 - Debugging Techniques

CPPCON2021 - Debugging Techniques

一开始讲了些bug的危害,比如每年花费多少人物力去debug,历史上的重大bug云云。

演讲有如下主题

  • what are bugs?
  • what is debugging?
  • challenges when debugging
  • a simple process for debugging
  • some recommendations

首要事情就是,以下内容都是建议和观点,并非规则或条例。对于每个人的适应性不同,请酌情取舍。

What are defects?

那演讲首先对bugs,defects做出了一些阐述和理解,演讲者用其他方面去理解一个bug。

每个系统都是用来承担一组需求(requirements)的主体,而所谓的需求就是能够用来描述系统的操作环境,输入,功能,交互,输出,以及任何你所期望的行为的事物。这些需求并非总是显式说明的,他们会隐含,存在在系统内部。

于是,一个系统的缺陷就是系统对其需求展现出的不一致性(non-conformity),有些缺陷甚至违反了系统的需求。

在c++中,我们认为“违反”分为前置条件违反(precondition violation)、后置条件违反(post-condition violation)或恒定违反(invariant violation)

What is Debugging

wikipedia解释道:

Debugging是发现、解决程序、软件或系统中的bug的过程。

但是wikipedia的定义需要几个前提(假设)

  • 首先,你需要能够分辨出程序的正确行为和非正确行为
  • 你有能力观察程序的输出,这样你才能知道程序是否正确
  • 你能修改源码或者其他数据
  • 你能够创建并测试更新后的程序

顺便还有一些术语:

  • non-conformity(不一致性):是不满足一个或多个程序的需求
  • defect(缺陷):defect是造成不一致性的非正常程序数据(比如一段代码,输入,设置,依赖……)
  • symptom(症状):symptom是你能够观察到的defect
  • deterministic defect(确定性缺陷):确定性缺陷是在确定的条件下不会改变程序症状的一类缺陷。比如在一样的输入,一样的环境下,你每次运行程序都会发现完全的相同的症状。
  • non-deterministic defect(非确定性缺陷):与确定性缺陷行为相反,即便给出一致的条件,程序每次运行都有可能出现不同的症状。这种缺陷是我们最棘手的。
  • context(上下文):上下文指的是出现缺陷的程序所处的整个环境。
  • problem report(问题报告):问题报告描述了一些上下文中出现的症状。
  • analogous context(相似上下文):原始上下文中能够复现出症状的部分上下文。
  • lab(实验室环境):你对上下文拥有绝对控制权的设置环境。
  • field(旷野环境):你对上下文没有绝对控制权的设置环境。

Debugging challenges

  • 问题报告可能是“无用”的。比如报告可能是不充分的,可能造成误导,可能对症状有错误的描述,缺少关于版本,配置,平台等信息。
  • 问题报告并非总是反应实际问题。比如非定义行为并不总是缺陷。
  • 收集程序运行的状态数据可能是困难的。
  • 症状并不总是指出错误发生的原因。程序出错的地方和症状发生的地方可能存在一段空间和时间上的距离。
  • 症状和缺陷可能高度相关,这代表着当你正在debugging时,程序的症状可能因为你的修复行为而改变,从而产生新的症状。
  • 当然,修一个bug可能新产生100个bug
  • 最糟糕,他有可能根本无法修复。这种情况往往有如下原因:无法获得程序的输出,在field的条件下进行debugging是无法修复的等。

The Debugging Process

理论上,debugging的过程应该有如下模型:

Product MyJob::Debug(const Product& curr, const Problem& issue){
	CharacterizeAndReproduceProblem(curr,issue); //复现bug
	
	LocatePorblem(curr);//定位bug
	
	ClassifyProblem(curr);//确定bug类型
	
	UnderstandProblem(curr);//理解bug出现的原因

	Product next = RepairProblem(curr); //修复bug
	
	return next; //提交修复后版本。
}

那实际肯定不能和理论相比,在实际中有一些步骤的并行的。

在实际中,debugging的过程有如下模型:

bool MyJob:Debug(consr Product& curr, const Problem& issue){
	ReviewProblemReport(curr,issue); //检查问题报告
	CharavterizeAndReproduceProblem(curr);
	auto next = Clone(curr); //克隆一个复制进行测试
	
	//不断迭代
	while(ReproduceProblem(next)&&ResourceAvailableToRepair(next)){
		auto insight = async(launch::async, &MyJob::UnderstandProblem, this, next); //理解程序的线程
		auto location = async(launch::async, &MyJob::LocateProblem, this, next);//定位问题的线程
		auto category = async(launch::async, &MyJob::ClassifyProblem, this, next);//分类问题的线程
		WaitFor(insight, location, category);
		
		next = AttemptToRepair(next);//尝试修复
	}
	return (ProbelmFixed())?Deliver(next),true;
}
  • characterizing and Reproducing a Problem:

    表述并复现问题,表述是确定症状发生时的上下文,你需要确定版本号,平台,申请的资源,接口,配置数据等以及获取那些你能够创建相似上下文的信息

    复现问题,你需要反复运行程序来获得问题报告中的症状。然后通过已有的测试展现症状。

  • Understand the Problem-

    理解问题需要你有足够的知识来定位缺陷的位置。不过注意,缺陷可能不再你认为的地方,所以做好质疑所有代码的准备。

  • Locate the Problem

    良好的开发模式能够更好的定位缺陷。所以再你构建程序的时候最好经常测试你所添加的程序,确保它有一个良好的基底。

    当你添加了新代码时,不要让以前的测试失败。

    如果你正在定位缺陷位置,使用追踪日志,你需要再程序执行的时候生成输出来描述程序的运行状态。使用调试和分析工具

    • 编译器:尤其注意warning部分
    • 静态代码分析工具(cppcheck,coverity)
    • 交互式调试器(gdb,msvc,udb,lldb...)
    • Time-travel Debuggers(gdb,rr,udb,liverecorder...)
    • sanitizers(asan,tsan,ubsan...)
    • 运行程序分析器(valgrind,callgrind,helgrind...)
    • 其他领域(wireshark,SQL analyzers)

    添加断言(assertion)可以预测程序运行时的某些布尔值(条件表达式)。一般在调用函数后面添加,用来验证前置条件和后置条件

    演讲还提到了debugging的方法——二分,说白了就是通过二分的方法不断确定缺陷的位置。不过听起来好像不太好用,这种方法只有当你确保只有一个缺陷才能使用,要不然就变成了那个经典图书管理员丢了一大群的段子。

    另外,特殊情况,你可以把问题变得更糟,目的是为了让故障发生的更加频繁。

    对于确定性的问题,通过查看日志,添加断言,使用调试器。对于非确定性问题,需要查看日志,建立一个用来debug的版本,查看是否出现相同的问题,添加断言来验证恒定的地方,然后理解问题。

  • Classifying a Problem

    缺陷有如下类别

    • 语法错误
    • 实现错误:比如数据结构,算法是正确的,但是底层的数据结构是错误的。前置条件/后置条件违反
    • 逻辑错误:算法在逻辑上有错误,边界情况验证失败,设计失误(添加新需求)
    • 配置错误
  • Repairing the Problem

    你需要通过测试来验证你修复的代码是否正确,这代表着所有旧的测试和新的测试都必须通过。

    尽量每次改变保证最小范围的修改。

  • Delivering the Fix

    记得写一份好的文档,详尽记录你是如何发现问题,通过什么样的方式定位到问题,使用了什么工具,如何复现问题,问题是什么类别的,如何修改了代码以及对代码进行大概的说明,

posted @ 2022-06-21 13:53  ᴮᴱˢᵀ  阅读(104)  评论(0)    收藏  举报