C Debugger

非常大程度上参考了这篇文章, 但是我的建议是 RTFM.

众所周知, 笔者的操作系统课烂得无法让人忍受, 并且笔者使用的是M1芯片的MacOS, 如果没有 LLDB, 笔者将难以进行这类底层代码的debug. 是时候学习一些 LLDB 的操作了.

1. Clang

Clang 是一个CC++Objective-CObjective-C++编程语言的编译器前端。它采用了LLVM作为其后端,由LLVM2.6开始,一起发布新版本。它的目标是提供一个GCC (GNU Compiler Collection) 的替代品,支持了GNU编译器大多数的编译设置以及非官方语言的扩展。作者是Chris Lattner,在苹果公司的赞助支持下进行开发,而源代码许可是使用类BSD的伊利诺伊大学厄巴纳-香槟分校开源码许可.

Clang 项目包括 Clang 前端和 Clang 静态分析器等.

在Mac上, 编译运行 C 语言的指令是

1
2
$ clang <NAME>.c # 默认得到a.out文件
$ ./a.out

在这样的情况下生成的 a.out 文件, lldb a.out也会正常启动,但是无法顺利断点. 执行lldb a.out并顺利打断点需要使用clang-g flag

1
clang -g main.c

此时会生成一个名为a.out.dSYM,此后就能顺利执行lldb a.out (注意不是 lldb a.out.dSYM)了

2. Run

在输入 lldb a.out 之后, 会进入lldb的调试界面. 如下:

此时输入 r 或者 run, 程序可以开始运行 (直到遇到错误或者断点处停下).

2.1. 设置断点

1
2
3
4
5
6
7
8
9
10
(lldb) breakpoint set -f <NAME>.c -l <LINE_NUM> 
# 或者是
(lldb) br s -f <NAME>.c -l <LINE_NUM>
# 或者是
(lldb) b <NAME>.c: <LINE_NUM>
# 或者是
(lldb) b <NAME>.c: <LINE_NUM>

## 或者是
(lldb) br <FUNC_NAME> # 这一指令会将断点设在函数体内第一行.

这里 <NAME>.c 表示 C 文件的名字(注意不是 a.out), <LINE_NUM> 表示想要将断点设在 C 文件的第几行

如果编译时没有加上 -g flag, 此时将遇到这样的报错

2.2. 删除断点

1
2
(lldb) br list #这条指令将会按照顺序展示所有的断点
(lldb) br del <BREAKPOINT_NUM> #这条指令将会删去第 <BREAKPOINT_NUM> 条断点

3. Control Flow

只有三种平凡的用法

1
2
3
(lldb) s(tep) # 单步执行, 如果遇到函数则会进入函数体
(lldb) n(ext) # 下一步, 如果遇到函数则跳过, 不会进入函数体内
(lldb) c(ontinue) # 直接运行到下一个断点处

4. Variables

打印出某个变量的值

1
(lldb) p <VAR_NAME>

考虑到作用域, 在一个 C 项目中很可能出现同名的变量, 这个时候需要选择好帧栈.

1
(lldb) bt # 展示所有的栈
1
2
3
(lldb) frame select <FRAME_NAME> #选择特定的帧栈
# 或者是
(lldb) fr s <FRAME_NAME> #选择特定的帧栈
1
2
3
(lldb) frame variable # 展示当前帧栈内所有的变量
# 或者是
(lldb) fr variable

5. Watch Point

watchpoint 与breakpoint有类似之处但是也有区别, watchpoint 是当监视的变量被修改时, 则停止运行. breakpoint 是当程序运行到特定的位置时, 则停止运行.

当开始 run 之后, 进行如下的输入.

1
2
3
(lldb)watchpoint set variable <GLOBAL_VAR> 
# 或者是
(lldb)w s v <GLOBAL_VAR>

此时 continue, 可以继续执行.

1
2
3
(lldb)watchpoint list
# 或者是
(lldb)w list

这样的指令可以监视所有的watchpoint

6. Kill

直接输入 kill 会杀死当前的进程 (每个 run 对应一个进程).