前言
好久没写博客了,最近,上了一门公司内部的课程,觉得收获很大,所以,在博客写写,当成一个学习笔记。
首先Linux下的程序调试,这里只针对C/C++的程序,讲讲如何在编译阶段和运行阶段调试程序。后面将简单介绍一下几大性能分析工具。一、编译阶段调试工具
与Javascript和Python不同,C/C++是编译语言,需要编译成目标文件,再将目标文件链接成一个可执行文件。
说到这里,就得说说Linux的ELF文件格式,ELF(Executable and Linkable Format)即可执行连接文件格式(注:window编译器会一般生成coff格式),是Linux,SVR4和Solaris2.0默认的目标文件格式,目前标准接口委员会TIS已将ELF标准化为一种可移植的目标文件格式,运行于32-bit Intel体系微机上,可与多种操作系统兼容。分析elf文件有助于理解一些重要的系统概念,例如程序的编译和链接,程序的加载和运行等。ELF有三种格式:
可重定位文件:用户和其他目标文件一起创建可执行文件或者共享目标文件,例如lib*.a文件。
可执行文件:用于生成进程映像,载入内存执行,例如编译好的可执行文件a.out。 共享目标文件:用于和其他共享目标文件或者可重定位文件一起生成elf目标文件或者和执行文件一起创建进程映像,例如lib*.so文件。这里就不展开了,有兴趣的同学可以了解一下,Linux下,默认编译好的a.out,就是ELF格式的文件。1. readelf 和 objdump
可以通过接下来这两个工具,readelf和objdump都可以从二进制文件a.out中读取相应的信息并显示。 至于两者的区别 stackoverflow有人提到。#查看a.out的文件头部readelf -h a.out#查看a.out所有内容readelf -a a.out#查看a.out的反汇编代码objdump -S a.out
详细用法:
2.nm
用来列出目标文件的符号清单,这其中会出现符号的类型,符号的类型是以一个字母的形式显示的,小写字母表示这个符号是本地(local)的,而大写字母则表示这个符号是全局的(global,externel)。一般来说,类型有一下几种:T、D、B、U、W。各自的含义如下:T表示在代码段中定义的一般变量符号;D表示时初始化过的数据段;B表示初始化的数据段;U表示没有定义的,在这个库里面使用了,但是在其他库中定义的符号;W,weak的缩写,表示如果其他函数库中也有对这个符号的定义,则其他符号的定义可以覆盖这个定义。
当使用C/C++混编的程序,有时候,会发现找不到定义的情况,当使用了nm检查一下符号表,就一目了然了
#查看符号表函数签名nm -C ./a.out
3.strings
获取二进制文件里面的字符串常量,对于检查固定加密key值和某些常量的值十分有用,例如有时候一些变量,在测试环境和现网环境中,需要经常变化,里面一些变量,需要strings检查确认一下,是否为那个变量。
#在a.out二进制文件中,查找key字段strings a.out | grep “key="#在ls中,查找“http:”对应的信息strings /bin/ls | grep "http:"
4.strip
通过除去绑定程序和符号调试程序使用的信息,减少文件的大小,这个命令十分有用,可以帮助我们清除程序中不必要的标识符和调试信息,可以大大减少文件的大小,而不会影响使用,但是,strip之后的程序,就无法使用gdb进行调试了。所以千万不要对库文件使用。
#清除a.out的符号表和行号信息。strip a.out
二、运行阶段调试工具
1.gdb
大名鼎鼎的GDB,是程序运行时调试的一把利器,没有之一,如果要展开介绍,可以写多一篇博文了,这里简单带过,介绍一下。
首先,gdb调试的程序,需要在使用gcc命令编译的时候,加入-g 其中有几个很有用的命令: 1 bt:显示程序栈,当发现错误的时候,这个命令这个让你迅速定位到问题的所在。 2 c:继续执行 3 list:显示当前程序所断点位置的上下文,或者上次list位置之后的程序代码 4 step:进入函数内部,跟进函数gdb对于使用C++做CGI开发,也有很重要作用,当前台需要通过HTTP协议传一个文件的时候,单单模拟POST已经无法满足了,所以,最简单的方法可以通过gdb来调试正在运行的进程。
可以从页面发起一个请求,当后台正在执行CGI的时候,马上使用 ps -ef | egrep "a.cgi" 找到进程号PID,然后通过 gdb -p PID 来跟踪进程。或者也可以通过进入gdb后,通过attach PID来调试正在运行, 进入之后,可以通过bt,或者list来确定当前的运行位置。 当调试完毕,可以通过detach来断开连接,让被调试的进程正常运行。 之前,遇到一个bug,死活调试不出是什么原因导致,多加一个参数,程序就错误,看代码也没看出所以然,最后,还是通过这个方法,定位到是一个指针被重复释放了两次而导致的。当然如果,程序很快就执行完,可以通过在程序代码中,调用sleep()函数,延缓执行时间。 gdb 的详细方法,可以Man 或者 Info2.pmap
当想查看进程的内存分布情况或者目标文件各个段的大小,可以使用这个命令,这个命令与直接通过vim /proc/PID/maps是基本一样的。
3.Ldd
查看程序用到的动态链接库,这个命令其实是一个shell命令,显示出来的所以来的库文件,其实都是一个软连接。
三、性能分析工具
1.vmstat
vmstat是一个十分有用的Linux系统监控工具,使用vmstat命令可以得到关于进程、内存、内存分页、堵塞IO、traps及CPU活动的信息。
1 #每秒刷新一次结果 2 vmstat 12.strace
显示程序调用的系统调用 这里需要区分一下系统调用和C库函数:系统调用通常用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。
系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。 C库函数提供的文件操作函数如fopen, fread, fwrite, fclose, fflush, fseek等 库函数调用通常用于应用程序中对一般文件的访问。 库函数调用是系统无关的,因此可移植性好。3.ltrace
显示程序调用的动态库函数 1 ltarce -p PID4.sar
系统监控工具 类似vmstat
1 #检查IO使用情况 2 sar -d 1 100 3 #检查网卡使用情况 4 sar -n DEV 1 100 5 #检查CPU使用情况 6 sar -u 1 1005.iostat
监控IO性能工具
1 #1秒显示一次IO信息 2 iostat -x 16.top
这个命令,大家已经十分熟悉了,动态显示系统当前的进程和其他状况
7.gprof
显示各个函数的执行时间
必须在gcc 加上-pg -g 如果对C库进行性能分析,gcc -lc_p 程序必须通过正常途径退出,不能kill -9 千万不能先使用上文所介绍的Strip,那样就无法分析各个函数执行。 1 gprof -b ./a.out8.time
显示程序执行时间、其中用户态时间、其中内核态时间
但只能跟踪父进程10.netstat
监控网卡状态, 其中两个重要的表项 Send-Q 接受队列 和 Recv-Q 发送队列 ,Recv-Q:Socket接收缓存,满了(比如CPU太忙)就会丢包
1 #显示TCP协议的连接情况 2 netstat -t 3 #显示udp协议的连接情况 4 netstat -u 5 #显示所有进程和监听端口 6 netstat -lpn 7 #统计udp包 8 netstat -su 9 #实时显示upd包的信息 10 watch netstat -su11.Free
显示系统内存使用情况
命令就介绍完了,这只是一个入门的命令介绍,如果想深入可以通过Info,Man 和 Google,当然,最重要的还是自己要多动手。