学会用gdb 调试程序,查看程序的运行过程,各个变量的值
-----------------------------------------------------------------------------------------------------------------------
golang原生的数据结构map,由于是通过hash方式实现的,不支持并发写入,但是在golang很多并发场景中,不可避免的需要写入map,下面介绍两种解决map并发写入的实现方式:
- sync.Mutex互斥锁(通过加锁解锁解决map不能并发写入的问题)
- chan (通过管道来解决map并发的问题),chan的存在完美解决goroutine之间的通信以及数据race问题,但是它的性能如何呢,下面让我们来测一下
首先先上代码:
代码很简洁,我就不多解释了。
看一下执行效率
可以看到,chan的实现方式其性能并不理想,比sync.Mutex的实现方式的效率低了大约25%左右。
chan的效率为什么不理想呢,其实原因很简单,下面看一下它的数据结构就一目了然了
packagemainimport("fmt")funcmain() {s := "hello, world!"b := []byte(s)c := make(chanint, 1)c<-1fmt.Println(s, b, <-c)}
我们可以看到channel的实现是通过互斥锁和数组的方式实现的,而且还有一些其他字段的同步,因此它的效率是不会比直接用互斥锁更快的。
很多看上去很高端很简洁的东西,其实是编辑器和runtime在后面为我们码农实现,当然与之相来是额外的内存和时间开销。
--------------------------------------------------------------------------------------------------------------
. gdb 运行初始化命令 -x usercmd
在.gdbinit 初始化命令中, 可以用#(同bash 注释符合)来注释
可以用source 在启动gdb 后运行gdb 命令文件
可以用file 在启动gdb 后再加载文件
2. gdb 传递命令行 参数
a. run
b. set args
可用 show args 查看
3. gdb 中宏的操作
默认编译的时候,调试过程是看不见宏的值的。编译时候需要给选项。-g3
gcc -g3 -o test.c test
gdb test
察看宏(macro)命令: p macroname(...)
就像察看一个普通变量一样。如果只想看宏的展开形势,就用如下命令:
macro expand macroname(...) 查看展开形式
还可以用info macro macroname 查看宏定义。
4. gdb 临时断点: tbreak
5. gdb 设置显示选项 .
例如 set print pretty
打印结构变量类型. ptype struct 类型名
----------------------------------------
6. 让GDB记录输出信息到文件
----------------------------------------
(gdb) set logging on -> 打开记录功能。
(gdb) set logging off -> 关闭记录功能。
(gdb) show logging -> 显示记录功能中每个选项的设置。
Future logs will be written to gdb.txt. ---> 表示log 没有打开
Currently logging to "gdb.txt". --> 表示log 已经打开
----------------------------------------
7. gdb 调试运行的进程
gdb -p <进程号>
或者:
gdb
attach <进程号>
----------------------------------------
下面不常用的功能,供了解
set logging file -> 改变记录文件,默认记录文件是gdb.txt。
set logging overwrite [on|off] -> 默认这个项目是关闭,
也就是以添加的形式将记录信息写入文件,这样之前文件中的信息不会被覆盖掉。
注意:如果设置这个选项的时候记录功能已经打开,需要关闭记录功能再重新打开才能起作用。
set logging redirect [on|off] -> 设置输出信息只记录到文件不作显示。
默认这个项目是关闭的,GDB将输出信息到终端和记录文件。如果打开这个项目,GDB将只输出信息到记录文件。
注意:如果设置这个选项的时候记录功能已经打开,需要关闭记录功能再重新打开才能起作用。
注意:记录功能只能记录GDB的输出信息,被调试程序的输出信息仍然会输出到终端。
---------------------------------------
7. 为什么没有core文件生成呢?
---------------------------------------
有时候程序down了, 但是core文件却没有生成.
core文件的生成跟你当前系统的环境设置有关系, 可以用下面的语句设置一下, 然后再运行程序便生成了core文件.
ulimit -c unlimited
gdb a.out core
----------------------------------------
8. gdb加载内核模块符号信息:
add-symbol-file <module_file>ADDR
例:
root@ubuntu:/home/hjj/software/uxfs_now/kern# cat debug
set remotebaud 115200
target remote /dev/ttyS1
add-symbol-file uxfs.ko 0xe0886000 -s .exit.text 0xe0888404
-------------------------------------------------
附录:gdb 常用调试命令
-------------------------------------------------
(gdb) l :(字母l)列出源码
(gdb) b n :在第n行处设置断点
(gdb) b func:在函数func()的入口处设置断点
(gdb) 条件断点:条件可以是任何合法的c 表达式。 例如 b n if val1==val2
当已经设置了断点,可以用condition 命令对断点号添加条件, 例: condition 2 val1==val2 , 注意,没有if 单词
当对变量的改变更感兴趣时,可以用watch 命令
(gdb) info break: 查看断点信息
(gdb) r:运行程序
(gdb) n:单步执行
(gdb) s:单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
(gdb) c:继续运行
(gdb) p 变量 :打印变量的值 也能够修改变量的值(用 = 赋值) // 打印寄存器值。 p $eax
(gdb) x/nfu 显示内存 // n为个数,f 为格式,u为每单元长度
(gdb) bt:查看函数堆栈
(gdb) finish:退出函数
(gdb) display 每次中断或单步都显示你关心的变量
(gdb)undisplay <编号>
(gdb) shell 命令行:执行shell命令行
(gdb) set args 参数:指定运行时的参数
(gdb) show args:查看设置好的参数
(gdb)info program: 来查看程序的是否在运行,进程号,被暂停的原因。 // 打印寄存器数组, info reg, 简写 i reg
(gdb)clear 行号n:清除第n行的断点
(gdb)delete 断点号n:删除第n个断点
(gdb)disable 断点号n:暂停第n个断点
(gdb)enable 断点号n:开启第n个断点
----------------------------------------
gdb 条件调试
----------------------------------------
“break ... if cond" 命令
例: b fun() if i==20
ignore bnum count
watch var // 此时监测var的变化
----------------------------------------
gdb 多线程调试
----------------------------------------
info threads
thread ID 来切换
set scheduler-locking off|on|step 估计实际使用过多线程调试的人都可以发现,
在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,
怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
off 不锁定任何线程,也就是所有线程都执行,这是默认值。
on 只有当前被调试程序会执行。
设置 on 之后, step 在单步的时候, 只有当前线程会执行。
next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)。
1.gdb有个很友好的调式界面,就是layout方式调试,可以让你边调试变查看源码,
但是layout方式有时候有点bug,不过基本上还可以使用,
可以用ctrl+x和ctrl+a 来切换。可能layout会造成控制台花屏,使用ctrl+L清屏。
----------------------------------------
GDB堆栈跟踪的方法
----------------------------------------
程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,
最近调用的函数在 0 号帧中(“底部”帧)。要打印堆栈,发出命令 'bt'('backtrace' [回溯] 的缩写):
(gdb) bt
#0 0x80483ea in wib (no1=8, no2=8) at eg1.c:7
#1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
此结果显示了在 main() 的第 21 行中调用了函数 wib()(只要使用 'list 21' 就能证实这一点),而且 wib() 在 0 号帧中,
main() 在 1 号帧中。由于 wib() 在 0 号帧中,那么它就是执行程序时发生算术错误的函数。
实际上,发出 'info locals' 命令时,gdb 会打印出当前帧中的局部变量,缺省情况下,这个帧中的函数就是被中断的函数(0 号帧)。
可以使用命令 'frame' 打印当前帧。要查看 main 函数(在 1 号帧中)中的变量,可以发出 'frame 1' 切换到 1 号帧,
然后发出 'info locals' 命令:
(gdb) frame 1
#1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
21 result = wib(value, div);
(gdb) info locals
value = 8
div = 8
result = 4
i = 2
total = 6
此信息显示了在第三次执行 "for" 循环时(i 等于 2)发生了错误,此时 "value" 等于 "div"。
可以通过如上所示在 'frame' 命令中明确指定号码,或者使用 'up' 命令在堆栈中上移以及 'down' 命令在堆栈中下移来切换帧。
要获取有关帧的进一步信息,如它的地址和程序语言,可以使用命令 'info frame'。
gdb 堆栈命令可以在程序执行期间使用,也可以在 core 文件中使用,因此对于复杂的程序,可以在程序运行时跟踪它是如何转到函数的。
查看栈信息
当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。 当你的程序调用了一个函数,
函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。
下面是一些查看函数调用栈信息的GDB命令:
Backtrace,bt 打印当前的函数调用栈的所有信息。如:
(gdb) bt
#0 func (n=250) at tst.c:6
#1 0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
从上可以看出函数的调用栈信息:__libc_start_main --> main() --> func()
backtrace , bt n是一个正整数,表示只打印栈顶上n层的栈信息。
backtrace <-n> ,bt <-n> -n表一个负整数,表示只打印栈底下n层的栈信息。
如果你要查看某一层的信息,你需要在切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。
frame ,f n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。
up 表示向栈的上面移动n层,可以不打n,表示向上移动一层。
down 表示向栈的下面移动n层,可以不打n,表示向下移动一层。
上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令:
select-frame 对应于 frame 命令。
up-silently 对应于 up 命令。
down-silently 对应于 down 命令。
查看当前栈层的信息,你可以用以下GDB命令:
frame 或 f 会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。
info frame,info f 这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内内地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。如:
(gdb) info f
Stack level 0, frame at 0xbffff5d4:
eip = 0x804845d in func (tst.c:6); saved eip 0x8048524
called by frame at 0xbffff60c
source language c.
Arglist at 0xbffff5d4, args: n=250
Locals at 0xbffff5d4, Previous frame's sp is 0x0
Saved registers:
ebp at 0xbffff5d4, eip at 0xbffff5d8
info args 打印出当前函数的参数名及其值。
info locals 打印出当前函数中所有局部变量及其值。
info catch 打印出当前的函数中的异常处理信息。
gdb 跟踪远程调试命令交互过程
"set debug remote 1" 将显示gdb 远程调试的过程。
gdb 内存断点设置。
rwatch, watch, awatch 分别代表读,写,读写内存断点,用的是硬件断点。
------------------------------------------------------------------
gdbtui gdb 图形化接口。方便调试
启动gdb, 按ctrl-x cta-a 也可进入tui 模式
------------------------------------------------------------------
help layout
layout src
layout asm
layout split
help winheight
winheight src +5
winheight src -4
help focus
focus cmd
focus src
focus next
简记为
fs next
fs src
fs cmd
--------------------------------------------------------------------------------
help info
Generic command for showing things about the program being debugged.
关于被调试程序的一些信息
help show
Generic command for showing things about the debugger
关于debugger状态的一些信息
--------------------------------------------------------------------------------
常用命令:
set args
file
source
set logging overwrite on
set logging on
set print pretty
show logging
set pagination off // 不要出现 Type to continue 的提示信息
info functions
info variables
list
list [函数名称]
与调试控制相关的命令
- continue 继续运行程序直到下一个断点(类似于VS里的F5)
- next 逐过程步进,不会进入子函数(类似VS里的F10)
- step 逐语句步进,会进入子函数(类似VS里的F11)
- until 运行至当前语句块结束
- finish 运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)
s : step in
fin: step out, 跳出函数
until 行号。 可用于跳出循环,加快了调试速度。
----------------------------------------
gdb 调试跟踪多进程程序
----------------------------------------
gdb只能跟踪一个进程(默认是跟踪父进程),而不能同时跟踪多个进程,
可以设置gdb跟踪父进程还是子进程, 命令如下:
set follow-fork-mode parent 跟踪父进程, 默认
set follow-fork-mode child 跟踪子进程