文章目录
- 一、基础知识
- 二、strace
- 1.基础知识
- 2.strace:跟踪系统调用来让开发者知道一个程序在后台做什么事情
- (1)strace基本用法
- (2)strace跟踪信号传递
- (3)统计系统调用:strace -c XXXX
- (5)输出到其他文件:strace -o XXX
- (6)每个系统调用所花费的时间:strace -T XXX
- (7)记录系统调用发生的时间:strace -t XXX
- (8)strace追踪现有进程:strace -p PID
- (10)strace调试程序
- 三、gdb
- 1.基础知识
- (2)gcc和g++的-g参数:将调试信息加到可执行文件中
- 2.gdb基本用法
- 3.用gdb分析coredump文件
- (1)gdb分析coredump文件的基础知识
- (b)coredump 文件的存储路径
- (c)有时候,执行程序提示Segmentation fault,但是没生成 coredump 文件,解决手段如下:
- (i)ulimit -c 命令可以查看 coredump 文件大小的最大值。
- (ii)当前用户执行对应程序的用户具有对写人 core 目录的写权限以及有足够的空间
- (iii)产生 coredump 文件的常见原因
- (2)用gdb定位coredump文件
- readelf -h XXX
- objdump -x
- gdb XXX
- 三、top:实时显示各进程的资源占用状态,类似于windows资源管理器
- 1.top命令的结果说明
- 2.top命令的总结
- 四、ps:当前在运行的进程的快照
- 1.top与ps的区别
- 2.kill 命令用于杀死进程
- (1)Linux上进程的5种状态
- (2)ps 工具标识进程的 5 种状态码
- 3.ps命令的常用参数
- (1)ps 命令常用参数
- (3)显示指定用户信息 :ps -u XXX
- (4)显示所有进程信息,连同命令行 :ps -ef
- (5)ps 与 grep 常用组合用法, 查找特定进程 :ps -ef | grep XXX
- (6)将目前登人的 PID 与相关信息列示出来 :ps -I
- (7)列出目前所有的正在内存当中的程序:ps aux
- 五、Valgrind:内存分析工具
- 1.Valgrind简介
- (3)Memcheck的内存检查原理
- (4)Valgrind 安装
- (5)Valgrind 使用
- (a)用 Valgrind 来分析代码的内存使用
- (b)用 Valgrind 来分析使用未初始化程序的内存
- (c)用 Valgrind 来分析内存读写越界
- (d)内存覆盖
- (e)动态内存管理错误
- (i)3种常见的内存分配方式:静态存储、栈上分配、堆上分配
- (ii)常见的内存动态管理错误有:
- (f)内存泄漏
- 2.Linux程序的内存空间布局
- (1)典型的 Linux 的运行中的C程序的内存空间布局
- (2)堆与栈的区别
- 查看栈大小的限制:ulimit -a
- 修改栈的大小:ulimit -s
- (3)eg来分析变量和内存的地址
一、基础知识
调试的方法一般分为以下两种:
- (1)在程序中插入打印语句,优点是能够显示程序的动态过程,比较容易检查源程序的
有关信息 。 缺点是效率低,可能输入大量无关的数据,发现错误具有偶然性 。 - (2)借助调试工具。 目前大多数程序设计语言都有专门的调试工具,比如 C++的调试工具有 GDB ,可以用这些工具来分析程序的动态行为 。
二、strace
1.基础知识
(1)应用程序是不能直接访问 Linux 内核的 。 它既不能访问内核所占内存空间,也不能调用内核函数。
具体做法是:
- 应用程序可以跳转到 system_call 的内核位置,内核会检查系统调用号,这个号码会告诉内核进程正在请求哪种服务。
- 然后,它查看系统调用表,找到所调用的内核函数人口地址,调用该函数,然后返回到进程。
(2)所有操作系统在其内核都有一些内建的函数,一般称 Linux 系统上的这些函数为“系统调用”( system call )。 这些函数代表了用户空间到内核空间的一种转换。
eg:在用户 空间调用 open 函数,在内核空 间则会调用 sys_open 。
(3)系统调用的错误码:系统调用并不直接返回错误码,而是将错误码放入一个名为 errno
的全局变量中 。
- errn不同数值所代表 的错误消息定义在 errno.h 中,你也可以通过命令”man 3 errn。”来查看它们 ;
- 需要注意的是 , errno 的值只在函数发生错误时设置,如果函数不发生错误, errno 的值
就无定义 ,并不会被置为 0
2.strace:跟踪系统调用来让开发者知道一个程序在后台做什么事情
(1)strace基本用法
(a)输入一个数,并输出这个数 。
#include <iostream>
using namespace;
int main(void)
{
int a;
cin>>a;
cout<<a<<endl;
return 0;
}
(b)然后用 g++ test.cpp -o test 编译一下,得到一个可执行文件 test ,执行结果如图 5-1 所示 。
(c)strace ./test
- 每一行都是一次系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。
-整体执行过程如下:
执行细节如下:
(i)
(ii)如果出错,数组等其他其他情况,strace 常用的系统调用会出现的结果
(2)strace跟踪信号传递
- 还是先输入命令“ strace ./test ”,等待的时候不要输入任何东西,然后打开另外一个窗口,输入命令“ killall test ;
- strace 中的结果显示 test 进程“+++ killed by SIGTERM +++” 。 事实上,命令 kiillall test ,就是杀死所有名为 test 的进程。
(3)统计系统调用:strace -c XXXX
- 过使用参数- c ,它还能将进程所有的系统调用做一个统计分析井返回 。
- (4)用 strace 统计 scanf输入 printf 输出
(5)输出到其他文件:strace -o XXX
(6)每个系统调用所花费的时间:strace -T XXX
(7)记录系统调用发生的时间:strace -t XXX
(8)strace追踪现有进程:strace -p PID
(10)strace调试程序
调试的代码如下:
#include <iostream>
#include <fstream>
#include <stdlib.h>
using namespace std;
int main(){
char buffer[256];
ifstream in("input.txt");
if (! in.is_open()){
cout << "Error opening file"<<endl;
exit (1);
}
while (!in.eof()){
in.getline (buffer,100);
cout << buffer << endl;
}
return 0;
}
(a)代码运行及解释
例 5.3 中的程序是从当前目录下的 input.txt 中读取内容,然后把它输出 。 当执行./test 命令,结果如图 5-11 所示。
结果提示 Error opening file ,也就是读取文件时出现了异常,那究竟是什么异常呢,我们可以继续用 strace 来定位 。
执行 strace ./test 命令后,结果如图 5-12 所示(只截取了后面部分)。
三、gdb1.基础知识
(1)gdb 是 gcc 的调试工具,主要用于 C 和 C++这两种语言编写的程序,主要功能有以下4个:
- ①启动程序,可以按照用户自定义的要求随心所欲地运行程序,
- ②可让被调试的程序在指定的断点处停住;
- ③当程序被停住时,可以检查此时程序中运行的状态;
- ④动态地改变程序的执行环境 。
(2)gcc和g++的-g参数:将调试信息加到可执行文件中
(3)启动gdb的方法
2.gdb基本用法
(1)需要调试的cpp代码如下:
#include<iostream>
using namespace std;
int func(int n){
int result=0;
for(int i=1;i<=n;i++){
result+=i;
}
return result;
}
int main(){
int arr[10];
arr[0]=0;
arr[1]=1;
for(int i=2;i<10;i++){
arr[i]=arr[i-1]+arr[i-2];
}
cout<<"arr[9]"<<arr[9]<<endl;
cout<<"func(9)"<<func(9)<<endl;
return 0;
}
- 用g++ -g test.cpp -o test命令编译程序,注意这里要加上也。 程序的执行结果如图 5-14所示 。
- 接下来用 gdb 调试程序,输入 gdb test 命令,启动 gdb 。 执行结果如图 5-15 所示 。
- 输入” l “后( l 命令相当于 list),从第一行开始列出源码,结果如图 5-16 所示 。再按下 Enter 键,表示重复上一次命令,结果如图 5-17 所示 。
- 执行“ b 15”,表示设置在源码 15 行处设置断点,执行“ b func ”,表示设置断点在函数func 入口处,执行“ info break ,,,表示查看断点的信息,如图 5” 18 所示 。
- 执行 r 命令,表示运行程序, run 命令简写,如图 5-19 所示 。图 5-19 表示,程序停在了断点处 。
输入“ n ”,表示单条语句执行, next 命令简写,如图 5-20 所示 。
输入“ p i”“ p arr[i]”,分别打印变量 i 和变量 a盯[i]的值,如图 5-21 所示 。
- 输入“ bt ”,查看函数堆栈,如图 5-22 所示 。
- 输入“ finish ,退出函数,如图 5-23 所示 。
- 程序结束时如图 5-24 所示 。输入“ q ”,结束调试,如图 5-25 所示 。
(2)上述一共用了以下这些命令 。
3.用gdb分析coredump文件
(1)gdb分析coredump文件的基础知识
(a)core ,又称之为 coredump 文件,是 UNIX/Linux 操作系统的一种机制。coredump 文件含有当进程被终止时内存、 CPU 寄存器和各种函数调用堆械信息等,可以供后续开发人员进行调试 。
(b)coredump 文件的存储路径
更改 coredump 文件的存储位置的操作如下:
(c)有时候,执行程序提示Segmentation fault,但是没生成 coredump 文件,解决手段如下:
(i)ulimit -c 命令可以查看 coredump 文件大小的最大值。
(ii)当前用户执行对应程序的用户具有对写人 core 目录的写权限以及有足够的空间
保证以上两点后,再执行文件,就会发现 coredump 时的提示变成了如图 5-29 所示的情况。
提示 Segmentation fault (core dumped),接着就只需去 cat /proc/sys/kernel/core_pattern 输
出的目录下找到该 coredump 文件即可 。
(iii)产生 coredump 文件的常见原因
(2)用gdb定位coredump文件
(a)非法访问内存的eg
#include <stdio.h>
int main(){
int b=1;
int* a;
*a=b;
return 0;
}
- 用 g++ -g -o test test.cpp 编译该文件,得到 test 这个可执行文件。 执行 ./test 命令后,结果如图 5-30 所示 。
readelf -h XXX
- 对程序进行 coredump 后,把 coredump 文件放到当前目录下以备分析,先来看下该 coredump文件的 ELF 头部(readelf -h直接从字面意思去记命令),如图 5-31 所示 。但是 core 文件中没有符号表信息,无法进行调试
objdump -x
- 用 objdump 命令查看 coredump文件的符号表
指令:objdump -x core.test . 13093 I tail
gdb XXX
- 接着来看下是 core 在哪了 。 执行 gdb test core.test.13093 命令后结果如图33 所示 。
1.top命令的结果说明
2.top命令的总结
- 比较准确地说, top 命令提供了实时地对系统处理器的状态监视。 它将显示系统中 CPU
最“敏感”的任务列表。 该命令可以按 CPU 使用 、 内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定 。 - 输入“ q”,则退出 top 命令。
1.top与ps的区别
- Linux 中的 ps (process status )命令列出的是当前在运行的进程的快照,就是执行 ps 命
令的那个时刻的那些进程,如果想要动态地显示进程信息,就可以使用 top 命令 。 - ps 命令提供进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程
时间监控,应该用 top 命令 。
2.kill 命令用于杀死进程
(1)Linux上进程的5种状态
(2)ps 工具标识进程的 5 种状态码
3.ps命令的常用参数
(1)ps 命令常用参数
- 命令格式是: ps[参数] 。 命令功能是用来显示当前进程的状态 。
- 常用参数如下
- (2)eg
#include<iostream>
using namespace std;
int main(){
for(int i=0;i<100;i++){
cout<<"i:"<<i<<endl;
sleep(5);
}
return 0;
}
程序的执行结果如图 5-36 所示。例 5.7 中,是利用 for 循环打印一个数字,每打一次就休眠缸,一共打印 100 次。
(3)显示指定用户信息 :ps -u XXX
其中的 test 进程,正是刚刚手动运行的那个 。
(4)显示所有进程信息,连同命令行 :ps -ef
(5)ps 与 grep 常用组合用法, 查找特定进程 :ps -ef | grep XXX
(6)将目前登人的 PID 与相关信息列示出来 :ps -I
(7)列出目前所有的正在内存当中的程序:ps aux
1.Valgrind简介
(1)
- Valgrind 是一套 Linux 下的开放源代码的仿真调试工具的集合。
- Valgrind 由内核以及基于内核的其他调试工具组成。 内核类似于一个框架,它模拟了一个 CPU 环境,并提供服务给其他工具;而其他工具则类似于插件,利用内核提供的服务完成各种特定的内存调试任务 。
(2)Valgrind 体系结构图
(3)Memcheck的内存检查原理
(4)Valgrind 安装
(5)Valgrind 使用
(a)用 Valgrind 来分析代码的内存使用
#include<iostream>
#include<stdlib.h>
using namespace std;
void func(){
int *x=(int *)malloc( 10 * sizeof ( int ) ) ;
x[10]=0;
}
int main(){
func();
cout<<"done"<<endl;
return 0;
}
总结:
该程序有 2 个问题:
- ① fun 函数中动态申请的堆内存没有释放;
- ②对堆内存的访问越界 。
(b)用 Valgrind 来分析使用未初始化程序的内存
- 对于位于程序中不同段的变量,其初始值是不同的,全局变量和静态变量初始值为 0;
- 而局部变量和动态申请的变量,其初始值为随机值。
eg说明:数组 a 是局部变量,其初始值为随机值,而在初始化时并没有给其所有数组成员初始化,如此在接下来使用这个数组时就潜在有内存问题。
#include<iostream>
using namespace std;
int main(){
int a[5];
int i,s=0;
a[0]=a[1]=a[3]=a[4]=0;
for(i=0;i<5;i++)
s=s+a[i];
if(s==33)
cout<<"sum is 33"<<endl;
else
cout<<"sum is not 33"<<endl;
return 0;
}
(c)用 Valgrind 来分析内存读写越界
- 内存读写越界是指访问了没有权限访问的内存地址空间,比如访问数组时越界、对动态内存访问时超出了申请的内存大小范围 。
eg说明:
下面的程序例 5.11 就是一个典型的数组越界问题。pt 是一个局部数组变量,其大小为 4, p 初始指向 pt 数组的起始地址,但在对 p 循环叠加后,p 超出了 pt 数组的范围,如果此时再对 p 进行写操作,那么后果将不可预期。
#include<stdlib.h>
#include<iostream>
using namespace std;
int main(){
int len=4;
int *pt=(int *)malloc(len*sizeof(int));//堆内存的大小是16byte,即4个int的大小
int *p=pt;
for(int i=0;i<len;i++)
p++;
*p=5;//非法写
cout<<"the value of p is "<<*p<<endl;//非法读
return 0;
}
(d)内存覆盖
- **C 语言的强大和可怕之处在于其可以直接操作内存,**比如 strcpy , stmcpy 、 memcpy, strcat 等,这些函数有一个共同的特点就是需要设置源地址(src)和目标地址(dst),且 src 和 dst 指向的地址不能发生重叠,否则结果将不可预期 。
eg如下:
(e)动态内存管理错误
(i)3种常见的内存分配方式:静态存储、栈上分配、堆上分配
- 全局变量属于静态存储,它们是在编译时就被分配了存储空间;
- 函数内的局部变量属于栈上分配;
- 最灵活的内存使用方式是:堆上分配,也叫做内存动态分配, 常见的内存动态分配函数包括:malloc、alloc、realloc、new等,动态释放函数包括:free和delete等
(ii)常见的内存动态管理错误有:
(iii)eg如下:
#include<iostream>
#include<stdlib.h>
int main(){
int i;
char *p= (char *)malloc(10);
char *pt=p;
for(i=0;i<10;i++){
p[i]='z';
}
delete p;//按照理解,这是第11行
pt[1]='x';//按照理解,这是第12行
free(pt);//按照理解,这是第13行
return 0;
}
执行以下命令:/home/sharexu/software/valgrind/bin/valgrind …tool=memcheck ./test
这时候可以顺便用 GDB 来逐行看下究竟是哪一行导致程序处于 coredump 状态的 。
先在程序第 10 、 11 、 12 、 13 、 14 行设置断点,如图 5-53 所示 。
- 然后运行程序,可见是在程序第 13 行时程序 coredump 了, free(pt)语句释放了无效的内存。
程序非法读写内存都不一定会出现 coredump ,但释放无效内存则一定会出现 coredump。
(f)内存泄漏
- 内存泄漏memory leak指的是,在程序中动态申请的内存,在使用完后既没有释放,也无法被程序的其他部分访问。
- 防止内存泄漏除了要从良好的编程开始,还要加强单元测试unite test
eg:
tree.h的代码如下:
#ifndef _TREE_
#define _TREE_
typedef struct _node{
struct _node *l;
struct _node *r;
char v;
}node;
node *mk(node *l, node *r, char val);
void nodefr(node *n);
#endif
tree.cpp的代码如下:
#include<stdlib.h>
#include"tree.h"
node *mk(node *l,node *r,char val){
node *f=(node *)malloc(sizeof(*f));
f->l=l;
f->r=r;
f->v=val;
return f;
}
void nodefr(node *n){
if(n){
nodefr(n->l);
nodefr(n->r);
free(n);
}
}
test.cpp 的代码是:
#include<iostream>
#include"tree.h"
int main(){
node *tree1,*tree2,*tree3;
tree1=mk(mk(mk(0,0,'3'),0,'2'),0,'1');
tree2=mk(0,mk(0,mk(0,0,'6'),'5'),'4');
tree3=mk(mk(tree1,tree2,'8'),0,'7');
return 0;
}
makefile 的代码是:
test: test.o tree.o
g++ -g -o test test.o tree.o
tree.o:tree.cpp tree.h
g++ -g -c tree.cpp -o tree.o
test.o:test.cpp
g++ -g -c test.cpp -o test.o
运行结果如下:
解释如下:
2.Linux程序的内存空间布局
(1)典型的 Linux 的运行中的C程序的内存空间布局
(2)堆与栈的区别
查看栈大小的限制:ulimit -a
修改栈的大小:ulimit -s
(3)eg来分析变量和内存的地址
#include<stdio.h>
#include<stdlib.h>
int g1=0, g2=0, g3=0;
int max(int i)
{
int m1=0,m2,m3=0,*p_max;
static int n1_max=0,n2_max,n3_max=0;
p_max = (int*)malloc(10);
printf("打印max程序地址\n");
printf("in max: %x\n\n",max);
printf("打印max传入参数地址\n");
printf("in max: %x\n\n",&i);
printf("打印max函数中静态变量地址\n");
printf("%x\n",&n1_max); //打印各本地变量的内存地址
printf("%x\n",&n2_max);
printf("%x\n\n",&n3_max);
printf("打印max函数中局部变量地址\n");
printf("%x\n",&m1); //打印各本地变量的内存地址
printf("%x\n",&m2);
printf("%x\n\n",&m3);
printf("打印max函数中malloc分配地址\n");
printf("%x\n\n",p_max); //打印各本地变量的内存地址
if(i) return 1;
else return 0;
}
int main(int argc, char **argv)
{
static int s1=0, s2, s3=0;
int v1=0, v2, v3=0;
int *p;
p = (int*)malloc(10);
printf("打印各全局变量(已初始化)的内存地址\n");
printf("%x\n",&g1); //打印各全局变量的内存地址
printf("%x\n",&g2);
printf("%x\n\n",&g3);
printf("======================\n");
printf("打印程序初始程序main地址\n");
printf("main: %x\n\n", main);
printf("打印主参地址\n");
printf("argv: %x\n\n",argv);
printf("打印各静态变量的内存地址\n");
printf("%x\n",&s1); //打印各静态变量的内存地址
printf("%x\n",&s2);
printf("%x\n\n",&s3);
printf("打印各局部变量的内存地址\n");
printf("%x\n",&v1); //打印各本地变量的内存地址
printf("%x\n",&v2);
printf("%x\n\n",&v3);
printf("打印malloc分配的堆地址\n");
printf("malloc: %x\n\n",p);
printf("======================\n");
max(v1);
printf("======================\n");
printf("打印子函数起始地址\n");
printf("max: %x\n\n",max);
return 0;
}