[I.2]个人作业:软件案例分析
[I.2]个人作业:软件案例分析
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2025春季软件工程(罗杰、任健) |
这个作业的要求在哪里 | [I.2]个人作业:软件案例分析 |
我在这个课程的目标是 | 在PSP中精进个人代码技术,在TSP中提高团队合作凝聚力 |
这个作业在哪个具体方面帮助我实现目标 | 分析市场上的软件案例,全面地了解软件工程的原理在好/坏软件上的作用 |
0. 选题介绍
本次作业选择开源项目进行分析,我选择面向C/C++/C#/Swift的一款调试工具LLDB进行分析。LLDB全称Lowel-Level Debugger, 是苹果公司开发的一款开源、高性能的源代码级调试器,作为Clang和LLVM项目的一部分,LLDB为提供了丰富的功能和灵活的接口,使得开发者能够有效地定位和修复代码中的bug。与之类似的调试工具还有gdb等经典的调试工具,我选择gdb进行比较分析。
参考材料:
1. 调研、评测
1.1 软件评测
1.1.1 软件使用
LLDB是一款较为新兴的源代码调试工具,由于已经具有GDB调试C/C++代码的经验,我选择直接使用lldb工具并使用help
命令来查看具体有哪些调试指令,lldb具有的调试命令如下输出:

下面针对几个常见的指令进行使用:
- breakpoint:设置断点
- step:步进(进入语句中调用的函数)
- stepi:汇编指令步进
- next:步过(跳过语句中调用的函数)
- nexti:汇编指令步过
- run:运行调试目标
- print:打印变量的值
- file:指定调试文件
- quit/exit:退出LLDB调试器
- gui:居然提供了GUI调试页面!
我编写了一个简单的但又不失复杂性的CPP程序来实践调试流程,内容是首先创建一棵7个节点的满二叉树,而后进行层序遍历,输出每一层的结果,其中使用了new等内存分配关键字,可以作为调试过程中查看内存分配的目标:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef struct Tnode {
int data;
struct Tnode *left, *right;
} Tnode, *Tree;
void InitTree(Tree &T) {
T = new Tnode{1, nullptr, nullptr};
Tnode *left = new Tnode{2, nullptr, nullptr};
Tnode *right = new Tnode{3, nullptr, nullptr};
T->left = left;
T->right = right;
left = new Tnode{4, nullptr, nullptr};
right = new Tnode{5, nullptr, nullptr};
T->left->left = left;
T->left->right = right;
left = new Tnode{6, nullptr, nullptr};
right = new Tnode{7, nullptr, nullptr};
T->right->left = left;
T->right->right = right;
}
vector<vector<int>> LevelOrder(const Tree T) {
if (!T)
return vector<vector<int>>{};
vector<vector<int>> res;
queue<Tnode *> q;
q.push(T);
while (!q.empty()) {
int size = q.size();
vector<int> row;
while (size--) {
Tnode *node = q.front();
q.pop();
row.push_back(node->data);
if (node->left)
q.push(node->left);
if (node->right)
q.push(node->right);
}
res.push_back(row);
}
return res;
}
int main() {
Tree T = nullptr;
InitTree(T);
vector<vector<int>> res = LevelOrder(T);
for (int i = 0; i < res.size(); i++) {
cout << "Row " << i + 1 << ": ";
for (int j = 0; j < res[i].size(); j++) {
cout << res[i][j] << " ,";
}
cout << endl;
}
return 0;
}
编译指令:
clang++ -g test.cpp -o test.out
-
调试过程:
-
lldb: 进入调试
-
file test.out:选择调试文件
-
首先在InitTree函数尾部打断点,之后运行
run
,停在函数尾部,使用命令查看内存分配情况breakpoint set --file test.cpp --line 24
-
查看内存分配情况:
memory read <address>
-
使用continue指令继续运行代码,在层序遍历处打断点,使用逐步调试和输出的方式进行测试。
-
打印当前队列头节点的值
-
使用n/s逐步调试,空格默认重复上一次的指令
-
-
体验一下GUI页面
-
1.1.2 软件分析
通过写一个具体的CPP程序来体验LLDB调试工具,我的主观感受上从GDB到LLDB的技术跨度不大,可以说会使用GDB就可以无伤上手LLDB,毕竟还有help嘛!LLDB丰富的调试指令可以为用户提供细致精确的调试体验,可以满足我当前的技术水平和工程规模的调试需求。
作为一个调试工具,与传统的调试工具GDB相比,我认为LLDB是在一定程度上超过了GDB的使用体验的,主要在以下几方面:
- 在用户界面上:
- 相比GDB,LLDB在断点处提供了更加丰富的上下文代码信息,便于用户查看
- LLDB提供了GUI页面,而GDB只提供了TUI页面,LLDB相对更加用户友好
- 在调试功能上:
- LLDB兼容了GDB的基本指令模式,技术跨度小
- LLDB的指令比GDB更加丰富,
help
指令说明详细
- 在情怀上(误):
- 谁会拒绝用LLVM全家桶呢?
1.1.3 改进意见
LLDB的官方文档中介绍了GDB到LLDB的指令映射,这是让我惊喜的一点,这体现了软件工程中对于用户体验的考量,让使用GDB的老家伙们乐于使用LLDB作为调试工具而不用从头摸索。作为建议的一点是:建议加入专门介绍调试指令的文档,目前的文档只详细介绍了breakpoint(断点指令)的详细使用,对于其他指令则缺乏介绍。同时,建议美化GUI页面,我喜欢花花绿绿的页面!
1.1.4 用户调研
- 采访对象的背景:选择王德庆老师软件工程班的沈锎同学进行采访,沈锎同学经常编写Rust/C/C++代码,具有丰富的代码编写和调试经验,我选择将问题以调查问卷的方式发送给沈锎同学,方便进行系统作答。


-
下面是沈锎同学的答案:
-
请问你对调试工具的需求是什么?
基本功能:设置并管理断点,并支持多样的断点类型。要能够支持单步调试,包括逐行、逐过程、逐指令等情形。可以实时查看变量的值,可以对变量或寄存器进行监控。能够查看函数调用栈信息,并能动态切换栈帧、查看栈变量。允许在调试过程中计算任意表达式的值。
性能需求:能够快速启动、加载、索引,在进行单步操作时减少卡顿。
用户体验:提供简洁美观的用户界面,常用操作可以进行提示,并且拥有详实准确的文档。
-
请问你目前实际使用的调试工具是什么?
调试 C 语言程序会使用 GDB,调试 Rust 程序会使用 LLDB.
-
请问你在使用LLDB时,会遇到哪些问题或者亮点?
亮点:相比 GDB 而言,LLDB 的 TUI 界面比较美观,对 Rust 的支持比较好,能够正确解析 Rust 的栈帧。同时,LLDB 能够支持通过 Python 脚本进行交互,非常现代,并且有效。
问题:LLDB 在进行调试的时候,有时会遇到卡顿,甚至在一些极端情况下,比如目标程序异常退出时,可能出现一些假死,着给实际使用带来了一些困扰。此外,LLDB 的用户群体相对较少,在使用过程中遇到问题时能够找到的资料较少。
-
从用户体验的角度来说,你认为LLDB还有哪些地方需要改进?
命令的智能化提示:能够对用户输入的指令进行补全,甚至可以根据上下文进行只能提示。
调试信息的优化:进一步优化调试信息的展示方式,对于输出内容较长时,可以简化输出内容,或者对重点内容进行突出渲染。
丰富的社区支持:增加更多对于”常见问题“的支持与帮助,这个也许需要软件开发者进行社区的建设与维护。
-
你认为与LLDB相比GDB在数据量/界面/准确度/用户体验上具有哪些优势或劣势?
(界面)LLDB 在界面上有很大的优势,显示更加现代,通过 ASCII 形成一些图案,更加美观且指示清晰。GDB 的界面则更加紧凑,却又略显简陋。
(准确度)LLDB 在诸如 Rust 这样的新兴语言的调试中有优势,对于栈帧等内容解析的准确度上更有优势。但据说在某些复杂 C/C++ 程序或特殊场景的处理上,LLDB 的表现距离 GDB 还有一定的距离。
(用户体验)对于我而言 GDB 有使用基础,因此对于其基础指令有深刻的印象,使用相对而言还是要简单一些。LLDB 的指令更加新颖,有一些新鲜的体验,但需要时间去适应。同时,如前面所述,GDB 的用户群体广泛,拥有大量的教程和资源,在这方面,LLDB 会有一些劣势。
-
请你为LLDB评价:
a)非常不推荐
b)不推荐
c)一般
=> d)好,不错 <=
e)非常推荐
-
1.1.5 评测结论
- a) 非常不推荐
- b) 不推荐
- c) 一般
- d) 好,不错
- e) 非常推荐
我的结论是:d)好,不错
2.Bug分析和提交
作为一个调试工具,我在使用LLDB进行代码调试过程中发现了一些bug. 我的系统环境:
- Archlinux内核版本:6.13.6
- LLVM/Clang/LLDB版本:19.1.7
2.1 Bug1
2.1.1 bug复现
-
可复现性:必然触发
-
调试的代码:
int main() { int var = 0; return 0; }
-
编译指令:
clang test.c -O1 -g -static
-
调试过程如下:
-
可以发现,在成功修改
var
变量的值后,再次打印var
值却显示原来的值
2.1.2 bug分析
通过查看DWARF区域的内存情况,我们可以观察到var
变量的属性
<2><32>: Abbrev Number: 3 (DW_TAG_variable)
<33> DW_AT_const_value : 0
<34> DW_AT_name : (indexed string: 0x5): var
<35> DW_AT_decl_file : 0
<36> DW_AT_decl_line : 2
<37> DW_AT_type : <0x3c>
这个变量有一个DW_AT_const_value属性,这就是造成它不可被修改的原因。
可能成因:回顾大三上学期完成的SysY Compiler的项目经验, 从编译系统的角度分析这个问题,我认为这个bug的可能成因是在函数分配栈内存时发生了错误,错误地为变量分配了const的属性。
bug的严重性:在程序员调试代码过程中,修改变量值的场景不在少数,修改变量值的错误会很大程度的影响程序员的调试体验,但是在一定程度上可以被程序员及时发现,不会造成很大的损失。另外在此处我们针对调试工具给出发生bug的严重等级评价:
- 五颗星:这些bug会导致调试工具完全无法使用,或者导致调试过程中出现严重的数据丢失或安全问题。例如,调试工具在启动时崩溃,或者调试过程中导致被调试程序的数据损坏。
- 四颗星:这些bug会严重影响调试工具的主要功能,可能导致调试过程无法继续。例如,频繁的崩溃,或者关键功能(如设置断点、查看变量)无法正常工作。
- 三颗星:这些bug会显著影响调试工具的使用体验,但通常有解决方法或替代方案。例如,调试过程中偶尔出现的崩溃,或者某些复杂表达式无法正确求值。
- 二颗星:这些bug可能会影响某些特定功能的使用,但不会导致调试工具完全无法使用。例如,某些特定情况下的表达式求值错误,或者某些特定平台上的兼容性问题。
- 一颗星:这些bug通常不会影响调试工具的主要功能,只是一些小的瑕疵或不便。例如,某些命令的输出格式不正确,或者某些功能的文档不够详细。
这个bug我对它的评价是:三颗星。
软件团队为何未能修复? LLDB隶属于LLVM开源社区,LLVM project是一个庞大的编译系统开源项目,对于庞大的编译系统来说,在编译某些程序时产生bug是可能的,或者说是可以忍受的、可以理解的。我认为这次bug的发生可能是因为开发人员对于边缘情况没有考虑到位,同时测试人员没有进行周到的测试。
2.1.3 bug改进建议
在断点处p var = 10
应当可以正常的修改变量的值。
2.1.4 bug反馈
我选择在Github上的LLVM代码库中向开发者提出issue,链接🔗
2.2 Bug2
2.2.1 bug复现
-
可复现性:必然触发
-
调试的代码:
class Test { static const int a[]; }; const int Test::a[] = {1}; int a(int val) { return val; } int main(int argc, char const *argv[]) { return a(2); // break here }
-
编译指令:
clang++ test2.cpp -stdlib=libc++ -g -o test2.out
-
调试过程如下:
-
解释:我们在Test命名空间(namespace)中创建了变量a,这个变量我们在main函数中应当是不可见的,即我们在main函数中调用a(1),LLDB应当认为我们在调用函数a,而通过LLDB的报错信息我们可以发现LLDB认为我们是对a这个Test命名空间中的变量进行了
()
重载,这明显是错误的。
2.2.2 bug分析
可能成因:LLDB中对于命名空间的解析以及变量作用域的范围解析出现问题,以至于我们要调用函数a,LLDB却认为我们是在对Test命名空间中的a变量进行了()
重载。
bug的严重性:三星。
软件团队为何未修复? 我认为是因为开发人员的疏忽和测试人员测试不全面造成的。
2.2.3 bug改进建议
这里应当调用函数a, 传参1。
2.2.4 bug反馈
我选择在Github上的LLVM代码库中向开发者提出issue,链接🔗
2.分析
2.1 工作量分析
下图为LLDB开源代码的文件结构:

将代码clone到本地并使用代码统计工具cloc来统计代码行数,总代码行数为3w+行数。对于一个6人左右的计算机专业的大学毕业生,在开发层面上我们估计每个人每天大概能开发500行左右代码,总体开发时间在10天左右,加上5天测试等时间,共需要15天时间。
2.2 软件质量分析
目前市场上面向C/C++的调试工具主要为GDB和LLDB。与GDB相比,LLDB具有以下优势:
- 通常在性能和内存使用方面表现更好,特别是在处理大型项目时。
- 使用更现代的代码基础和优化技术,提供更快的调试体验。
- 提供与 GDB 类似的调试功能,支持断点、观察点、单步执行、堆栈跟踪、变量查看和修改等。
- 在多线程调试和远程调试方面表现更好,提供更稳定的体验。
- 提供命令行界面(CLI),支持多种图形用户界面(GUI)前端,如 Xcode、Visual Studio Code 等。
- 命令行界面设计更现代,命令更直观,易于学习和使用。
综合来看我愿意把LLDB排在第一名的位置。
对LLDB开发团队的建议:改进稳定性、错误处理和恢复机制。
3.建议和规划
3.1 市场现状
3.1.1 市场概况
调试工具市场主要面向软件开发人员,包括专业开发者、软件工程师、系统管理员、计算机科学学生、业余编程爱好者等。知名技术平台Stack Overflow
有超过400万活跃的开发者用户,全球的开发者数量要远超这个数字,用户群体庞大。
3.1.2 竞争产品
LLDB的主要竞争产品包括GDB、Visual Studio Debugger、WinDbg等。
3.1.2 产品定位
- LLDB:由LLVM开发团队开发的一款开源的新兴的调试工具,主要应用在MacOS平台。优势是开源代码,功能强大;劣势在于作为一款新兴的工具,还没得到广泛的推广。
- GDB:优势在于历史悠久,成熟稳定,社区支持广泛,广泛应用于Linux平台;劣势在于性能和易用性上略逊于LLDB。
- Visual Studio Debugger:集成在Visual Studio IDE中,主要应用于Windows平台。优势在于与IDE紧密集成,用户无需配置开箱即用,体验好;劣势在于跨平台方面没有支持。
- WinDbg:主要用于Windows平台的内核调试。优势在于强大的内核调试功能;劣势在于学习曲线陡峭,用户界面不友好,难以推广。
3.2 市场与产品生态
3.2.1 核心用户群
核心用户群是软件开发人员:
- 年龄18~45岁之间
- 学历或专业上具有计算机科学与技术或软件工程相关专业背景
- 收入中等偏上
- 爱好编程和技术,追求代码的艺术
- 表面需求:使用代码调试工具调试代码
- 潜在需求:提高开发效率和代码质量
3.2.2 用户群体关系
开发人员之间通过在线技术论坛,例如Stack Overflow
、CSDN
等或其他技术社区以及社交媒体进行技术交流,可以为LLDB建立开放的在线技术交流社区,通过社区支持和用户反馈不断提高产品体验,从而与已有的LLVM系列构成完整的生态链。
3.2.3 产品生态
LLDB已有较为良好的产品生态,LLDB与Clang编译器、LLVM工具链紧密集成;可以利用这些关系构建产品生态,通过提供完整的开发工具链,向linux平台抢占更多的用户。
3.3 产品规划
3.3.1 新功能设计
智能断点功能:智能断点功能是一个集成在LLDB调试器中的高级功能,可以自动化、智能化地管理断点,从而帮助用户更快地定位问题,提高用户调试效率和用户体验。
智能断点管理系统可以显著提高调试效率和用户体验,特别是在处理大型项目和复杂调试场景时。相比其他功能,智能断点管理系统能够直接解决用户在调试过程中遇到的痛点,如手动管理断点繁琐、条件断点设置复杂、断点管理缺乏灵活性等问题。通过自动化和智能化的断点管理,用户可以更高效地定位和解决问题,从而提高开发效率。
NABCD分析:
- N(Need):现代调试工具用户在面对大型项目和复杂调试场景时,不断需要调试工具,还对调试器提出了智能化需求。
- A(Approach):通过设计一个智能断点功能,提供自动设置断点、条件断点、断点分组、断点历史记录等功能,帮助用户更高效地管理断点。
- B(Benefit):提高用户的调试效率和体验,减少手动定位bug并打断点的时间,帮助程序员更快地解决bug。
- C(Competitors):相比GDB和Visual Studio Debugger等工具,我们的智能断点功能在减少用户调试时间、提高用户调试体验上具有巨大优势。
- D(Delivery):依托目前广泛的LLVM系列编译器的广大用户群体,通过LLVM技术社区进行推广和用户反馈,不断改进。
3.3.2 角色配置
- 项目经理:1人
- 开发人员:3名
- 测试人员:1名
3.3.3 团队计划
- 第1周:开需求分析会,明确智能断点功能的需求,确定智能断点功能的技术路线、实现方案
- 第2~6周:开发人员实现核心功能自动设置断点,同时测试人员编写测试用例进行全面测试
- 第7~10周:开发人员完成辅助功能条件断点、断点分组、断点历史记录等,测试人员跟进测试
- 第11~12周:测试人员继续进行功能测试,开发人员编写技术文档以及用户使用手册
- 第13~14周:在LLVM技术社区发布alpha版本并进行推广,开发人员进行性能优化,测试人员跟进测试
- 第15周:发布beta版本,接收社区反馈对产品进行不断改进。
- 第16周:正式发布具有智能断点功能的LLDB调试器,收集用户反馈,修改bug,构想新功能,准备下一个版本。