一、Linux ELF文件内部结构(Executable Linkable Format)
Linux程序源代码被编译器编译以后,生成的目标文件/共享库文件/可执行文件(.o/.so/exe)主要分为两种段:程序指令段(.text)和程序数据段(.data, .bss)。
| file header | .text segment | .data segment | .bss segment | other segment |
(1) file header: 文件头描述了整个文件的文件属性;如是否可执行、是静态链接还是动态链接及入口地址、目标硬件、目标操作系统等等信息。段表描述了各个段在文件的偏移位置及段的属性。
(2) .text 代码段:代码段用来存放可执行文件的操作指令,代码段需要防止在运行时被非法修改,因此只准许读取操作,是不可写的。
(3) .data 初始化数据段:保存已经初始化的全局变量和局部静态变量
(4) .bss 未初始化数据段:保存未初始化的全局变量和局部静态变量。默认值都为0,为什么不直接放在.data段呢? 因为都是0,在.data段分配空间并存放数据0是没必要的。程序运行的时候它们确实是要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和。所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,并没有具体内容。
二、Linux进程内存布局结构(进程虚拟地址空间)
三、GDB调试基础
Sample: tst.c
[jackhen@172-5-5-236 gdb]$ ls -l
总用量 4
-rw-rw-r-- 1 jackhen jackhen 363 1月 29 09:54 tst.c
[jackhen@172-5-5-236 gdb]$ gcc -g tst.c -o tst
[jackhen@172-5-5-236 gdb]$ gdb tst
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/jackhen/Space/tmp/xb/gdb/tst...done.
(gdb)
(gdb) b 22
Breakpoint 1 at 0x400501: file tst.c, line 22.
(gdb) break func
Breakpoint 2 at 0x4004cb: file tst.c, line 12.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400501 in main at tst.c:22
2 breakpoint keep y 0x00000000004004cb in func at tst.c:12
(gdb)
(gdb) bt
#0 func (n=250) at tst.c:12
#1 0x000000000040054f in main (argc=1, argv=0x7fffffffe5c8) at tst.c:30
(gdb) backtrace
#0 func (n=250) at tst.c:12
#1 0x000000000040054f in main (argc=1, argv=0x7fffffffe5c8) at tst.c:30
(gdb)
//常用指令
(1) l或者list -->列出源码(List specified function or line), 如list 0; list main; list func
(2) \Enter回车 -->继续执行上一次运行的指令
(3) b或者break -->断点
(4) info b -->查看断点信息
(5) r或者run -->运行
(6) c或者continue -->继续运行
(7) bt或者backtrace -->查看堆栈
(8) n或next -->跳过,相当于visual studio的F10(逐过程)
(9) s或step -->进入,相当于visual studio的F11(逐语句)
(10) disable 1 -->禁用1号断点
(11) delete 1 -->删除1号断点
(12) help -->帮助命令,查阅gdb所有命令
(13) info register -->查看寄存器
(14) info thread -->查看线程,显示当前可调试的所有线程
(15) thread apply all bt -->查看所有线程的所有堆栈
(16) thread apply all command -->让所有被调试线程执行gdb指令(command)
(17) thread apply tid1 tid2 command -->让一个或者多个线程执行gdb指令(commnad)
(18) q或quit -->退出gdb
(19) tab键|两次tab键 -->显示命令|显示所有命令
(20) disas -->反汇编
(21) info proc status -->查看进程状态
(22) watch <variable> -->查看变量的动态变化过程
(A) 编译程序时需要增加-g参数,把调试信息加到可执行文件中。如果没有-g,将看不到程序具体的函数名/变量名,所代替的全是运行时的内存地址。
(B) gdb <program>; gdb <program> core; gdb <program> <pid>
四、常见问题(句柄泄露、内存泄露、死锁、死循环、段错误)
(1) 句柄泄露(文件描述符泄露) {Sample: handlefd.c}
原因:进程调用系统打开文件后,没有释放已经打开的文件句柄(文件描述符)
查看进程默认所能打开的文件句柄最大数:ulimit -n (默认为1024, 当然可以自行修改)
通过/proc/<pid>/fd/ 查看该<pid>进程打开的所有句柄
(2) 内存泄露(用valgrind工具检测Linux程序内存问题) {Sample: memleak.c}
valgrind [valgrind-options] [your-program] [your-program-options]
--tool=<toolname> [default: memcheck]
valgrind是一套Linux下且开源的仿真调试工具集,有内核Core和基于内核的调试工具组成;内核模拟了一个CPU环境,并提供服务给调试工具,调试工具类似于插件,利用内核提供的服务完成特定的内存调试任务。
valgrind调试工具包括:memcheck,callgrind,cachegrind,helgrind,massif,extension
<1> memcheck: 重量级的内存检测器,能够发现绝大多数内存错误情况,如未初始化的内存,内存访问越界等等
<2> callgrind: 检测程序中函数调用过程中出现的问题
<3> cachegrind: 检测程序中缓存使用出现的问题
<4> helgrind: 检测多线程程序中出现的竞争问题
<5> massif: 检测程序中堆栈使用中出现的问题
<6> extension: 利用core提供的功能,自己编写特定的内存调试工具
[jackhen@172-5-5-236 gdb]$ gcc -g memleak.c -o memleak
[jackhen@172-5-5-236 gdb]$ valgrind -v --leak-check=full ./memleak 1
(3) 死锁 / 死循环 {Sample: deadlock.c}
Linux进程的5种状态
1. 运行(正在运行或在运行队列中等待)
2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)
3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)
4. 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)
5. 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)
ps工具标识进程的5种状态码: {ps -aux}
D 不可中断 uninterruptible sleep (usually IO)
R 运行 runnable (on run queue)
S 中断 sleeping
T 停止 traced or stopped
Z 僵死 a defunct ("zombie") process
注: 其它状态还包括W(无驻留页), <(高优先级进程), N(低优先级进程), L(内存锁页).
[jackhen@172-5-5-236 gdb]$ gcc -g deadlock.c -o deadlock -lpthread
[jackhen@172-5-5-236 NetReactor_tcp_server]$ ps aux -L | grep jackhen
jackhen 34893 34893 0.0 2 0.0 16464 544 pts/1 Sl+ 15:57 0:00 ./deadlock
jackhen 34893 34894 0.0 2 0.0 16464 544 pts/1 Sl+ 15:57 0:00 ./deadlock
死循环:
<1>、循环内有类似sleep的函数,不过由于代码bug一直无法跳出循环。(CPU不高,但是程序已失去响应)
<2>、循环内处于异常繁忙的情况,也就是会出现CPU持续特别高的情况。
(4) 段错误 {Sample: segment.c}
产生段错误就是访问了错误的内存段。
1)访问系统数据区,尤其是往系统保护的内存地址写数据,最常见就是给一个指针以0地址
2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域
[jackhen@172-5-5-236 gdb]$ gcc -g segment.c -o segment
[jackhen@172-5-5-236 gdb]$ ./segment
段错误 (core dumped)
[jackhen@172-5-5-236 gdb]$ gdb ./segment
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/jackhen/Space/tmp/xb/gdb/segment...done.
(gdb) r
Starting program: /home/jackhen/Space/tmp/xb/gdb/segment
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400484 in dummy_func () at segment.c:13
13 *ptr = 0x00;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.x86_64
(gdb)
对于段错误这种问题。可以分析以下原因:
<1>:指针非法,比如使用没有初始化的指针(没有为此指针指向的对象分配空间),或着Free掉之后再次使用。
<2>:数组访问越界,访问的元素下标超过数组围长
<3>:缓存溢出,对于这种while(1) {do}的程序,这个问题最容易发生,多次sprintf或着strcat有可能将某个buff填满,溢出,所以每次使用前,最好memset一下,不过要是一开始就是段错误,而不是运行了一会儿出现的,(3)的可能性就比较小。