linux 静态链接库和动态连接库
一:起因
(1)也许我们非常熟悉Windows下的VC6.0 和 CodeBlocks的调试工具 —— 界面化的调试,但是你是否想过你的每一个按键或者快捷键的背后指令是什么,让我们一起走进Linux的gcc动态调试工具GDB
(2)程序调试无非就是:debug(gcc -g -o target source);设置断点(b n);观察变脸(info locals);观察特定值(PRint expr);执行(start);执行下一行(next);进入子函数(step);执行到当前函数结尾(finish);查看源代码(list)等等操作
(3)gcc 的过程:C程序的编辑 用到vim; 程序的编译和运行用到gcc; 程序的调试用到gdb (kdbg是可视化的调试工具)
二:实例讲解
(1)调试步骤分为两步
1)编译时一定到 加上 -g 产生调试信息,让调试信息包含在可执行文件中exe —— 命令行下:
[plain] view plain copy gcc -g -o target source
%20 2)把可执行文件exe加载到调试环境中: %20
[plain] view%20plain copy gdb target
%20 3)%20gdb为调试命令提示符,即可键入调试命令
(2)调试常用命令
%20 1)基本调试命令(重要)
%20 %20 %20 %20list%20行号:列出产品从第几行开始的源代码;%20 %20list%20函数名:列出某个函数的源代码
start:开始执行程序,停在main函数第一行语句前面等待命令;%20 next(n):执行下一列语句
step(s):执行下一行语句,如果有函数调用则进入到函数中%20 ;breaktrace(或bt):查看各级函数调用及参数
frame(f)%20帧编号:选择栈帧%20;%20 info(i)%20locals:查看当前栈帧局部变量的值
finish:执行到当前函数返回,然后挺下来等待命令%20 ;%20print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数
set%20var:修改变量的值%20 ;quit:退出gdb
%20 2)高级调试命令
%20 %20 break(b)%20行号:在某一行设置断点%20;break%20函数名:在某个函数开头设置断点%20; break...if...:设置条件断点
continue(或c):从当前位置开始连续而非单步执行程序%20 ;%20 delete%20breakpoints:删除所有断点%20 ;delete%20breakpoints%20n:删除序号为n的断点
disable%20breakpoints:禁用断点%20;%20 enable%20breakpoints:启用断点
info(或i)%20breakpoints:参看当前设置了哪些断点;%20;%20 %20run(或r):从开始连续而非单步执行程序
display%20变量名:跟踪查看一个变量,每次停下来都显示它的值%20 ;undisplay:取消对先前设置的那些变量的跟踪
%203)格式化输入查看变量%20print(p)
输出格式:一般来说,GDB会根据变量的类型输出变量的值。但你也可以自定义GDB的输出的格式。例如,你想输出一个整数的十六进制,或是二进制来查看这个整型变量的中的位的情况。要做到这样,你可以使用GDB的数据显示格式:
x%20按十六进制格式显示变量。 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 d%20按十进制格式显示变量。
u%20按十六进制格式显示无符号整型。%20 %20 %20 %20 o%20按八进制格式显示变量。
t%20按二进制格式显示变量。%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 a%20按十六进制格式显示变量。
c%20按字符格式显示变量。%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 f%20按浮点数格式显示变量。
启动GDB后,首先就是要设置断点,程序中断后才能调试。在gdb中,断点通常有三种形式:
断点(BreakPoint):
在代码的指定位置中断,这个是我们用得最多的一种。设置断点的命令是break,它通常有如下方式:
break%20<function> 在进入指定函数时停住break%20<linenum> 在指定行号停住。break%20+/-offset 在当前行号的前面或后面的offset行停住。offiset为自然数。break%20filename:linenum 在源文件filename的linenum行处停住。break%20...%20if%20<condition> ...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break%20if%20i=100,表示当i为100时停住程序。可以通过info%20breakpoints%20[n]命令查看当前断点信息。此外,还有如下几个配套的常用命令:
delete 删除所有断点delete%20breakpoint%20[n] 删除某个断点disable%20breakpoint%20[n] 禁用某个断点enable%20breakpoint%20[n] 使能某个断点观察点(WatchPoint):
在变量读、写或变化时中断,这类方式常用来定位bug。
watch%20<expr> 变量发生变化时中断rwatch%20<expr> 变量被读时中断awatch%20<expr> %20变量值被读或被写时中断可以通过info watchpoints [n]命令查看当前观察点信息
捕捉点(CatchPoint):
捕捉点用来补捉程序运行时的一些事件。如:载入共享库(动态链接库)、C++的异常等。通常也是用来定位bug。
捕捉点的命令格式是:catch <event>,event可以是下面的内容
throw %20C++抛出的异常时中断catch %20C++捕捉到的异常时中断exec 调用系统调用exec时(只在某些操作系统下有用)fork 调用系统调用fork时(只在某些操作系统下有用)vfork 调用系统调用vfork时(只在某些操作系统下有用)load%20或%20load%20<libname>%20 载入共享库时(只在某些操作系统下有用)unload%20或%20unload%20<libname> 卸载共享库时(只在某些操作系统下有用)另外,还有一个tcatch%20<event>,功能类似,不过他只设置一次捕捉点,当程序停住以后,应点被自动删除。
捕捉点信息的查看方式和代码断点的命令是一样的,这里就不多介绍了。
在特定线程中中断
你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作。
break%20<linespec>%20thread%20<threadno>break%20<linespec>%20thread%20<threadno>%20if%20...linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过"info%20threads"命令来查看正在运行程序中的线程信息。如果你不指定thread%20<threadno>则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如:
%20(gdb)%20break%20frik.c:13%20thread%2028%20if%20bartab%20>%20lim
当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。
恢复程序运行和单步调试
在gdb中,和调试步进相关的命令主要有如下几条:
continue 继续运行程序直到下一个断点(类似于VS里的F5)next 逐过程步进,不会进入子函数(类似VS里的F10)setp 逐语句步进,会进入子函数(类似VS里的F11)until 运行至当前语句块结束finish 运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)PS:这些命令大部分可以简写为第一个字母,在日常使用过程中,往往只会输入第一个字符即可执行该命令,我标红的即是通常的使用方式。这几条命令使用非常频繁,并且可以带一些附加参数以实现高级功能,需要熟练掌握。
大概三步走~~~(%20#%20./core_dump_run运行时产生段错误,但是编译阶段gcc%20–o%20[]不报错)
第一步:设置core文件大小为无限(默认为0,即不产生)如果想让系统在信号中断造成的错误时产生core文件,我们需要在shell中按如下设置:#设置core文件大小为无限ulimit%20-c%20unlimited#设置core文件大小为1000kbulimit%20–c%201000
#查看设置的大小
ulimit%20–c
[@sjs_37_33%20t_core_gdb]#%20ulimit-c
100000
第二步:#gcc(g++)-g%20-o%20core_dump_run%20core_dump_test.c,并运行#./core_dump_run产生core.pid文件
第三步:#gdb%20core_dump_runcore.pid 即可调试 (gdb)start%20-à nextà bk直接到错误之处
附加:对于第三步中提示信息太少的,但是提供了错误地址的可以反编译:objdump
#objdump%20–d%20 core_dump_run
代码:
[cpp] view%20plain copy 1 #include <stdio.h> 2 3 const char str[5] = "test"; 4 5 void core_test() 6 { 7 //str[4]='T'; 8 unsigned char *ptr = 0x00; // ·Ç·¨µÄ 9 *ptr = 0x00; 10 printf("str=:%s,str[1]=%d, [str[7]=%d]/n",str,str[1],str[7]); 11 } 12 13 14 int main() 15 { 16 core_test(); 17 return 0; 18 }(3)gdb多线程调试
先介绍一下GDB多线程调试的基本命令。
info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。前面有*的是当前调试的线程。
thread ID 切换当前调试的线程为指定ID的线程。
break thread_test.c:123 thread all 在所有线程中相应的行上设置断点
thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。
thread apply all command 让所有被调试线程执行GDB命令command。
set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。off不锁定任何线程,也就是所有线程都执行,这是默认值。 on只有当前被调试程序会执行。 step在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。
gdb对于多线程程序的调试有如下的支持:
· 线程产生通知:在产生新的线程时, gdb会给出提示信息
(gdb) rStarting program: /root/thread [New Thread 1073951360 (LWP 12900)] [New Thread 1082342592 (LWP 12907)]---以下三个为新产生的线程[New Thread 1090731072 (LWP 12908)][New Thread 1099119552 (LWP 12909)]
· 查看线程:使用info threads可以查看运行的线程。
(gdb) info threads 4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? () 3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? () 2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()* 1 Thread 1073951360 (LWP 12931) main (argc=1,argv=0xbfffda04) at thread.c:21(gdb)
注意,行首的蓝色文字为gdb分配的线程号,对线程进行切换时,使用该该号码,而不是上文标出的绿色数字。
另外,行首的红色星号标识了当前活动的线程
· 切换线程:使用 threadTHREADNUMBER 进行切换,THREADNUMBER 为上文提到的线程号。下例显示将活动线程从 1 切换至 4。
(gdb) info threads 4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? () 3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? () 2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()* 1 Thread 1073951360 (LWP12931) main (argc=1, argv=0xbfffda04) at thread.c:21(gdb) thread 4[Switching to thread 4 (Thread 1099119552 (LWP 12940))]#0 0xffffe002 in ?? ()(gdb) info threads* 4 Thread 1099119552 (LWP12940) 0xffffe002 in ?? () 3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? () 2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? () 1 Thread 1073951360 (LWP 12931) main (argc=1,argv=0xbfffda04) at thread.c:21(gdb)
后面就是直接在你的线程函数里面设置断点,然后continue到那个断点,一般情况下多线程的时候,由于是同时运行的,最好设置 setscheduler-locking on
这样的话,只调试当前线程
三:个人心得
(0)记录个人成长中的收获的点点滴滴,哪怕跌倒再次爬起来,也是一种难得的经验 —— 我一直为自己前几年没有留下成长记忆而苦恼,如今时时刻刻的督促自己写一写自己的声音。
(1)哪怕一周花一到两个小时的时间,整理一下自己当下的心得和学习所得;从短期看:这会加深自己对知识的理解;从长远来看:虽然花费了一两个小时,但是最终你是赚了的。
(2)也许我们都会有类似的经历 —— 我们今天遇到的难题,前几天刚刚遇到过,处理过;但是,遗憾的是,自己忘记如何处理的了,更悲催的是,我们都忘了是哪一个模块里的;仅仅记得刚刚处理过这个问题;纠结纠结 ~~~浪费同样的时间处理一个相似的问题。
新闻热点
疑难解答