使用调试器
关于调试器的第一个简明提示:总是使用调试器。
一个调试侦探故事
调试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
#include <math.h>
#include <stdio.h> //size_t
typedef struct meanvar {double mean, var;} meanvar;
meanvar mean_and_var(const double *data){
long double avg = 0,
avg2 = 0;
long double ratio;
size_t count= 0;
for(size_t i=0; !isnan(data[i]); i++){
ratio = count/(count+1);
count ++;
avg *= ratio;
avg2 *= ratio;
avg += data[i]/(count +0.0);
avg2 += pow(data[i], 2)/(count +0.0);
}
return (meanvar){.mean = avg,
.var = avg2 - pow(avg, 2)}; //E[x^2] - E^2[x]
}
int main(){
double d[] = { 34124.75, 34124.48,
34124.90, 34125.31,
34125.05, 34124.98, NAN};
meanvar mv = mean_and_var(d);
printf("mean: %.10g var: %.10g\n", mv.mean, mv.var*6/5.);
double d2[] = { 4.75, 4.48,
4.90, 5.31,
5.05, 4.98, NAN};
mv = mean_and_var(d2);
mv.var *= 6./5;
printf("mean: %.10g var: %.10g\n", mv.mean, mv.var);
}
|
使用CFLAGS="-g -Wall -std=gnu11 -O3" make stddev编译代码,gdb stddev_bugged调试代码。
Q: 这个程序是做什么的?
A: run命令运行程序。
1
2
3
4
|
(gdb) r
mean: 5687.496667 var: 194085710
mean: 0.83 var: 4.1334
[Inferior 1 (process 22734) exited normally]
|
Q: main函数中的代码符合我们的输出吗?
A: list命令显示源代码
Q: 我们如何查看mean_and_var发生了什么?
A: 我们想要程序在mean_and_var暂停,因此我们在那里设置一个断点:
1
2
|
(gdb) b mean_and_var
Breakpoint 1 at 0x400820: file stddev_bugged.c, line 16.
|
Q: data是我们想的那样吗?
A: 我们可以通过print简写为p查看data:
1
2
|
(gdb) p *data
$2 = 34124.75
|
我们只得到第一个元素,但GDB有一个特殊的@语法打印数组元素。打印10个元素[LLDB: mem read -tdouble -c10 data]:
1
2
3
4
|
(gdb) p *data@10
$3 = {34124.75, 34124.480000000003, 34124.900000000001, 34125.309999999998,
34125.050000000003, 34124.980000000003, nan(0x8000000000000), 0,
4.9406564584124654e-324, 2.0732560687596022e-317}
|
注意表达式前面的星号,没有它,我们会得到10个地址。
Q: 这和main发送的匹配吗?
A: 我们可以通过bt回溯:
1
2
3
|
(gdb) bt
#0 mean_and_var (data=data@entry=0x7fffffffdd50) at stddev_bugged.c:16
#1 0x0000000000400520 in main () at stddev_bugged.c:38
|
让我们看看data在frame 1是什么,首先切换到frame 1:
1
2
3
|
(gdb) f 1
#1 0x0000000000400520 in main () at stddev_bugged.c:38
38 meanvar mv = mean_and_var(d);
|
在这一层,data数组叫d:
1
2
3
|
(gdb) p *d@7
$5 = {34124.75, 34124.480000000003, 34124.900000000001, 34125.309999999998,
34125.050000000003, 34124.980000000003, nan(0x8000000000000)}
|
我们可以返回frame 0通过f 0或与栈关联的移动:
1
2
3
|
(gdb) down
#0 mean_and_var (data=data@entry=0x7fffffffdd50) at stddev_bugged.c:16
16 meanvar mean_and_var(const double *data){
|
注意up和down参考数字顺序。由bt产生的列表,数字最小的层在最上面,up走向数字大的层,down走向数字小的层。
Q: 这是线程的问题吗?
A: 我们可以得到一个线程列表通过info threads[LLDB: thread list]:
1
2
3
4
|
(gdb) info threads
Id Target Id Frame
* 1 process 6125 "stddev_bugged" mean_and_var (
data=data@entry=0x7fffffffdd50) at stddev_bugged.c:16
|
如果有多个线程,我们可以使用thread num(GDB)或thread select num(LLDB)切换过去。GDB用户可以在.gdbinit添加set print thread-events off关闭每一个线程烦人的通知。
Q: mean_and_var在做什么?
A: 我们可以重复步进程序的下一行:
1
2
3
4
|
(gdb) n
18 avg2 = 0;
(gdb) n
16 meanvar mean_and_var(const double *data){
|
没有输入敲击回车重复上一个命令。还有其他的步进选项(snuc)。使用步进调试代码将会花费很多时间。且函数中有个循环,我们可以在循环中间设置一个断点:
1
2
|
(gdb) b 25
Breakpoint 2 at 0x400715: file stddev_bugged.c, line 25.
|
现在我们有2个断点,可以使用info break查看断点列表。
1
2
3
4
5
6
7
|
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004006c0 in mean_and_var
at stddev_bugged.c:16
breakpoint already hit 1 time
2 breakpoint keep y 0x0000000000400715 in mean_and_var
at stddev_bugged.c:25
|
不再需要mean_and_var头部的断点了,可以禁止它[LLDB: break dis 1]:
你可以重新激活一个断点enable 1(GDB)或break enable 1(LLDB)。如果你不再需要一个断点可以删除它,del 1(GDB)或break del 1(LLDB)。