首页
社区
课程
招聘
[原创]linux gdb调试方法
2022-4-13 19:12 16375

[原创]linux gdb调试方法

2022-4-13 19:12
16375

一、gdb常用调试命令一览表


功能

命令

简写命令

例子

备注

查看命令的帮助help 命令名字
help info break
调节代码窗口高度winheight src -5wh src -5
源代码窗口高度-5行
切换窗口

①查看窗口:info win

②切换到src窗口:fs src

③切换到下一个窗口:fs next

①i win

②fs src

③fs n



查看elf文件是否有符号readelf -S appName|grep debug


查看依赖的库readelf -a xxx.so | grep "Shared"


查看静态库有哪些.o文件readelf -d xxx.a


显示程序的源代码路径

readelf appName -p .debug_str|grep /

或者

readelf call_update.out -p .debug_str|grep .cpp




显示程序的符号nm appName


调试程序app,并传递参数

gdb app

set args 10



显示传递给main函数的参数

show args



显示源代码,默认10行

list

l

l 行号        默认生成10行,当指定行号时,会生成以指定行号为中间的共10行代码

设置断点

①break 文件名:行号

②break 文件名:函数名

③break 行号

③break 函数名

b

例1行号下断点

b main.cpp:6

例2 函数名下断点

b   point.cpp:point::print


设置条件断点

break [break-args] if (condition)


①std::string等于某个值命中断点,这个经过本人验证:

b cppName:20 if strcmp(strName.c_str(),"api-ms") == 0

b cppName:20 if i == 160

b main if argc > 1

b 180 if   (pszName == NULL && i < 0)

b test.c:34 if   (x & y) == 1

b myfunc if i % (j + 3) != 0

⑦b 44 if   strlen(mystring) == 0


设置指定线程的断点

①break <linespec> thread <threadno>

②break   <linespec> thread <threadno> if ...


①break frik.c:13 thread 28 if bartab >   lim

linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过"info   threads"命令来查看

设置内存断点

①watch + [变量][表达式]  当变量或表达式值改变时即停住程序。
  ②rwatch + [变量][表达式] 当变量或表达式被读时,停住程序。
  ③awatch + [变量][表达式] 当变量或表达式被读或被写时,停住程序。


①watch *(int*)0x22cbc0


运行

run

r


整个调试过程,只能运行一次run命令,第二次运行run命令,调试器会询问你是否从新开始调试。如果要运行到下一个断点,不能用run,必须使用continue或者其简写c

运行到下一个断点

continue

c


相当于VS里的F5

运行到下一个断点,输入c,不能输入r,输入run的话会重新开始调试,

执行到指定行until

until 行号until一般在小范围内跳动

单步执行,不能入函数

next

n


相当于vs的F10

单步执行,进入函数

step

s


相当于vs的F11,step/finish组合

跳出函数

finish


相当于vs的Shfit +   F11,函数完整执行后返回,step/finish组合,注意不能使用简写f。f是frame的缩写

跳过当前函数后面的语句直接返回,返回值可以自定义

return 

return 119

step/return组合

查看函数调用堆栈

backtrace

bt



查看设置的断点

info break

i b


注意i和b之间有个空格

查看寄存器的值

info register

i r


注意i和r之间有个空格

查看栈帧信息

①info frame

②info frame 栈帧编号

i f


注意i和f之间有个空格

查看当前stack frame局部变量

info locals

i local


注意不能缩写成i l

查看程序中的源代码文件info sources


删除断点

delete [breakpoints num] [range...]

d

例1:删除序号为5的断点

d 5

例2:删除序号1到4的断点

d 1-4


删除断点(删除指定行的断点,作用范围为1行)

①clear function

②clear   filename:function

③clear   line_number

④clear filename:line_number



禁用断点

disable 断点编号




启用断点

enable 断点编号




打印单个变量值

①print /format 变量名

/x 按十六进制格式显示变量。
  /d 按十进制格式显示变量。
  /u 按十六进制格式显示无符号整型。
  /o 按八进制格式显示变量。
  /t 按二进制格式显示变量。
  /a 按十六进制格式显示变量。
  /c 按字符格式显示变量。
  /f 按浮点数格式显示变量。

p

例1十进制打印nNum:

(gdb) p nNum
  $2 = 100

例2十六进制打印nNum:

(gdb) p /x nNum
  $2 = 0x64

例3计算常量表达式的值:

(gdb) p /x   0x22cb70+0x50


打印数组

p 数组第一个元素@打印长度

p

例1打印int数组的前10个元素:

(gdb) p   nScore[0]@10

例2打印int数组的前10个元素:

(gdb) p   *nScore@10

例3以16进制打印数组前10个元素。

(gdb) p /x   *nScore@10

①set print array 命令,以便能够在显示数组时更方便查看

②如果打印std::string显示不全,可以设置打印的数组元素个数为0,意味着不限制字符个数

set print elements 0   

打印结构体

p  结构体变量a

p  *结构体变量指针





例1:打印结构体变量jack

(gdb) set print null-stop

(gdb) set print pretty

(gdb) p jack


例2:打印结构体变量指针pKevin指向的结构体的内容


(gdb) p *pKevin

例3:打印结构体成员

(gdb) p jack.nId


①set print null-stop 命令,设置字符串的显示规则,即遇到结束符时停止显示

②set print pretty

美观显示

打印内存

打印内存:x  /nfu   addr (以f格式打印n个u类型存储单元的以addr开头的内存值)

格式f: o(octal)  x(hex) d(decimal) u(unsigned decimal)   t(binary) f(float) a(address) i(instruciton) c(char) s(string)

字节u: b(byte)  h(halfword)  w(word)    g(gaint, 8 bytes)


例1:以二进制显示addr处开始的256个字节

x    /256xb addr

例2:以十六进制显示addr处开始的256个字节

x    /256xw addr


常显示某变量

display 变量如果我们需要在调试中一直显示某个变量的值,那么就需要display命令了
删除常显示


1、info display

2、undisplay 常显示变量编号


修改变量的值

set variable key = value
  set var key = value


例1 :修改char数组

set var   chArr="hello lily"

例2:修改int变量

set var nNum=102

例3:修改int数组的某个元素

set var nScore[2]=998


修改并查看变量的值

print 变量名=值


例1:修改并打印char数组

p   chArr="sal"

例2:修改并打印int变量值

p nNum=666

例3:修改并打印int数组的某个元素值

p nScore[0]=666


查看线程信息,并切换线程

info thread

thread 线程编号

i  thread

t 线程编号



显示调用堆栈并切换栈帧、并查看局部变量

bt

frame 栈帧编号

f

①切换到栈帧1

f 1

②查看栈帧1的局部变量

info local

首先使用bt命令显示调用堆栈,

再使用f命令切换栈帧,切换栈帧仅仅是因为gdb没有ui界面,相当于点击vs调用堆栈的某个栈帧。没有发生线程切换

再使用info local查看函数的局部变量。

显示源代码窗口

wh     显示源代码窗口

Ctrl + L  代码窗口清屏(,ctrl+L后按下回车键)

Ctrl + X+ A   退出代码窗口




显示已加载的模块信息

a)显示所有已经加载的动态库信息

 info shared

b)显示某个已经加载的动态库信息

 info shared libtgcDefender.so




给动态库加载模块sharedlibrary myso


清屏

!clear




查看当前调试位置where



反汇编disassemble




将某段内存 dump 保存到文件中dump binary memory file start_addr end_addr
dump binary memory my_binary_file.bin 0x22fd8a 0x22fd8a+450

(gdb) help dump binary memory

Write contents of memory to a raw binary file.

Arguments are FILE START STOP. Writes the contents of memory

within the range [START .. STOP) to the specified FILE in binary format.



结束调试

quit

q





二、常见调试场景

以下调试源代码:

                                              

2.1普通调试

(1)开始调试,并设置传递给main函数的参数

命令:gdb 要调试的程序名

set args 参数1  参数2 ......

2)设置、查看断点,并运行命中断点。

  

3)查看并修改变量的值

4)显示指定内存地址的内容


5)单步执行命令n(不进入函数)和s(进入子函数)

6)下断点,并查看断点列表,运行命中断点,切换栈帧并查看局部变量


(7)删除断点

2.2附加调试

1)查看要调试的程序pid

命令:ps -aux | grep attachDemo


2)附加进程调试

执行gdb attach pid即可调试正在运行的程序,

执行:$ gdb attach 29231,若执行gdb attach时提示:” ptrace: Operation not permitted”,则执行:$ sudo gdb attach 29231

3)在工作者线程中下断点

4)运行命中断点

2.3多线程死锁调试

1)编写以下死锁代码,文件名字deadLock.cpp

// deadLock.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
 
#include <thread>
#include <iostream>
#include <mutex>
using namespace std;
mutex _mutex1;
mutex _mutex2;
int date1;
int date2;
 
int do_work_1()
{
    cout << "thread_1 start" << endl;
    lock_guard<mutex> locker1(_mutex1);
    date1++;
    this_thread::sleep_for(chrono::seconds(1));
     lock_guard<mutex> locker2(_mutex2);
    date2++;
    cout << "thread_1 end" << endl;
   return 0;
}
 
 
int do_work_2()
{
    cout << "thread_2 start" << endl;
    lock_guard<mutex> locker2(_mutex2);
    date2++;
    this_thread::sleep_for(chrono::seconds(1));
    lock_guard<mutex> locker1(_mutex1);
     date1++;
     cout << "thread_2 end" << endl;
    return 0;
}
 
int main()
{
    thread t1(do_work_1);
    thread t2(do_work_2);
    t1.join();
    t2.join();
    cout << "end" << endl;
    return 0;
}

2)编译文件

g++ deadLock.cpp -g -o deadlock -std=c++11 -pthread

3)调试

gdb deadlock

4)执行r,产生死锁

5 按下ctrl + C 使程序中断。

查看当前主线程的栈帧(bt)

查看栈帧3停的位置

初步判断是产生了死锁,因为42行的join没有执行。

查看线程信息,命令info thread,简写i thread*号打头的为当前线程。

查看一下当前的线程,切换到2号线程,切换线程指令thread tid t tid)并查看对应的栈帧。死锁在19

查看锁的拥有者命令:p mutex名字

切换到3#线程,切换线程指令thread tid t tid)并查看对应的栈帧。死锁在32

2.4 coredump调试

1 设置core dump文件生成的方法

a)在终端中输入ulimit -c 如果结果为0,说明当程序崩溃时,系统并不能生成core dump
b
使用ulimit -c unlimited命令,开启core dump功能,使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte)。若ulimit -c unlimited,则表示core文件的大小不受 限制。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文件的时候,gdb会提示错误。

c/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core

2)编写以下死锁代码,文件名字main.cpp

// main.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
 
#include <iostream>
using namespace std;
 
int Div(int a, int b)
{
    int nDiv = a / b;
    return nDiv;
}
 
int main()
{
    int a = 100;
    int b = 0;
    int nDiv = 0;
 
    std::cout << "enter main" << std::endl;
    nDiv = Div(a, b);
    std::cout << "nDiv=" << nDiv << std::endl;
}

3)运行程序,生成dump

4)调试core dump文件,定位崩溃点。

agdb 程序名  coredump文件名

b)输入bt命令,查看崩溃的调用堆栈

2.5内存断点

1)编写以下测试代码

#include <stdio.h>
#include <string.h>
 
int main()
{
    int i, j;
    char buf[256] = { 0 };
    char* pp = buf;
 
    printf("buf addr= 0x%x\n", buf);
 
    for (i = 0; i < 16; i++)
    {
        printf("addr = 0x%x~0x%x\n", pp + i * 16, pp + i * 16 + 15);
        for (j = 0; j < 16; j++)
            *(pp + i * 16 + j) = i * 16 + j;
    }
    printf("ASCII table:\n");
 
    for (i = 0; i < 16; i++)
    {
      for (j = 0; j < 16; j++)
         printf("%c  ", *(pp + i * 16 + j));
       printf("\n");
    }
    return 0;
}

2)调试程序,在第11行下断点(buf数组初始化为0),命中11行断点。

3)查看下内存断点之前的变量buf的值,然后在buf的第80个元素buf[80]处下内存断点,并运行。

2.6源码调试在别的机器上编译的程序、方法1(在B机器上编译,在A机器上调试)。

    场景:动态库文件libadd.so在B机器上编译的。要在A机器上源码调试libadd.so

1)gdb调试testSO.out,并递归指定源码路径

        命令(注意扩住find命令的不是单引号.....,,就是ESC下面那个,和~同一个键)

                gdb `find /root/projects/sosrc -type d -printf '-d %p '` testSO.out

2)显示gdb搜索的源码路径

   命令:show dir

3)在动态库中下断点,并命中动态库中的断点

2.7源码调试在别的机器上编译的程序,方法2(在B机器上编译,在A机器上调试)。

1)查看程序的源代码路径

   命令:readelf appName -p .debug_str|grep /或者readelf appName -p .debug_str|grep cpp

                        


2)替换源代码路径

      set substitute-path from_path  to_path

      比如 list显示的源码是   /home/aaa/1.cpp

      那么设置了  set substitute-path /home/aaa/   /home/bbb/

                (把源代码拷贝到调试机器上)


                (替换源代码路径,源码调试成功)

      

2.8 调试汇编代码

1)进入汇编调试界面

      layout src:显示源代码窗口

      layout asm:显示反汇编窗口


2单步执行命令

      si   //step into 进入函数内部

      ni  //next  不进入函数内部

3)查看函数对应的汇编代码

     打印内存:x  /nfu   addr (以f格式打印n个u类型存储单元的以addr开头的内存值)

                格式f: 

                        o(octal)  

                        x(hex) d(decimal) 

                        u(unsigned decimal)   

                        t(binary) f(float) 

                        a(address) 

                        i(instruciton指令,即汇编代码) 

                        c(char) 

                        s(string)

               类型:

                        字节u: b(byte)  

                        h(halfword)  

                        w(word)    

                        g(gaint, 8 bytes


        例子1:x/ni 函数名

        例子2:x/ni 地址

                            (查看main函数的前5条汇编指令

                                                 查看某个地址处的汇编代码

                                                       (查看当前要执行的汇编代码

2.9 gdb使用符号表调试release程序

(1)生成debug版本,strip出release版本发给客户

        命令:

                strip -g program_debug -o program_release

         然后通过Debug版本进行调试release版本程序:

              gdb --symbol=program_debug -exec=program_release

(2)使用符号文件调试release版本程序

        生成符号文件:objcopy --only-keep-debug program_debug debug_symbol

       通过符号文件调试release程序:

                   gdb ./app_release  -s  app.symbol

       通过符号文件调试coreDmp文件:    

                   gdb ./app core.xxxxx -s app.symbol

2.11 如何查看一个elf文件是否有调试信息(-g选项)

      通过命名readelf -S 看看有没有.debug段就可以确定elf文件是否具有调试信息:

      命令格式:readelf -S appName

      或者:readelf -S appName |grep debug

   (1).debug :  存放调试信息,如果有该段说明编译时,添加了-g选项

   (2).symtab:  存放符号表,保存着所有的符号,如果有此段,说明可用strip分离

2.12 gdb调试多进程

   多进程调试参考:https://www.cnblogs.com/liuhanxu/p/17011777.html


命令作用
info inferiors查看所有进程
inferiors 2切换到编号为2的进程
detach inferiors 2detach掉编号为2的进程
kill inferiors 2kill掉编号为2的进程
set follow-fork-mode parent只调试父进程(GDB默认)
set follow-fork-mode child只调试子进程
show follow-fork-mode查看follow-fork-mode当前值
set detach-on-fork on只调试一个进程,父进程或子进程(GDB默认)
set detach-on-fork off

同时调试父子进程,另一个进程阻塞在fork位

置,同时调试父子进程,如果follow-fork-mode是parent,则gdb跟踪父进程,子进程阻塞在fork位置。如果follow-fork-mode是child,则gdb跟踪子进程,父进程阻塞在fork位置。此时用户可以根据调试情况在父进程和子进程之间来回切换调试

show detach-on-fork查看detach-on-fork当前值
set schedule-multiple off只有当前进程会执行,其他进程挂起(GDB默认)
set schedule-multiple on所有的进程都会正常执行
show schedule-multiple查看schedule-multiple当前值


follow-fork-mode选项

  set follow-fork-mode parent只调试父进程,子进程继续运行(GDB默认)。

  set follow-fork-mode child只调试子进程,父进程继续运行。

  1.   child调试子进程,父进程不受影响
      我们需要调试子进程,所以在启动GDB后,set follow-fork-mode child
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
    perror("fork()");
    }
    if(pid == 0)
    {
        printf("this is child,pid = %d\n",getpid());
    }
    else
    {
        printf("this is parent,pid = %d\n",getpid());
    }
    exit(0);
}

(1)调试父进程

(2)显示默认的 follow-fork-mode 配置和显示默认的 detach-on-fork  配置

    follow-fork-mode默认只调试父进程(GDB默认)

    detach-on-fork默认为on:只调试一个进程,父进程或子进程

(3)设置 follow-fork-mode 为 child和设置 detach-on-fork off 为 off

     set follow-fork-mode child: 只调试子进程

     set detach-on-fork off:同时调试父子进程,另一个进程阻塞在fork位置


(4)设置捕获点中断

(5)运行程序,命中fork

(6)info inferiors显示程序运行的进程

     当前运行在子进程

(7)切换到1号进程(父进程),执行1号进程(父进程)


2.13 定位cpu占用率的代码位置

    (1)拷贝以下代码,创建gstack文件(没有扩展名),使用dos2unix命令,转换为linux格式

                拷贝之前,先看看自己/usr/bin下优米诶有gstack文件,有的话就不用复制以下代码并拷贝了

执行命令chmod 777 gstack, 

执行命令:cp gstack  /usr/bin/

#!/bin/sh
if test $# -ne 1; then
	echo "Usage: `basename $0 .sh` <process-id>" 1>&2
	exit 1
fi
 
if test ! -r /proc/$1; then
	echo "Process $1 not found." 1>&2
	exit 1
fi
 
# GDB doesn't allow "thread apply all bt" when the process isn't
# threaded; need to peek at the process to determine if that or the
# simpler "bt" should be used.
 
backtrace="bt"
if test -d /proc/$1/task ; then
	# Newer kernel; has a task/ directory.
	if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then
	backtrace="thread apply all bt"
	fi
elif test -f /proc/$1/maps ; then
	# Older kernel; go by it loading libpthread.
	if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then
	backtrace="thread apply all bt"
	fi
fi
 
GDB=${GDB:-/usr/bin/gdb}
 
if $GDB -nx --quiet --batch --readnever > /dev/null 2>&1; then
	readnever=--readnever
else
	readnever=
fi
 
# Run GDB, strip out unwanted noise.
$GDB --quiet $readnever -nx /proc/$1/exe $1 <<EOF 2>&1 | 
set width 0
set height 0
set pagination no
$backtrace
EOF
/bin/sed -n \
	-e 's/^\((gdb) \)*//' \
	-e '/^#/p' \
	-e '/^Thread/p'
#end

(2)top命令查出cpu占有率高的进程id

            执行命令:top

              这里调查的70957进程

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
70894 root      20   0  156880   5568   4228 S  16.6  0.1   1:09.35 sshd                                                                        
 70957 root      20   0   25172   1136    948 S   3.7  0.0   0:14.75 cleanupDB.out                                                               
 56208 root      20   0       0      0      0 S   0.7  0.0   0:06.96 kworker/2:0                                                                 
 69769 root      20   0       0      0      0 S   0.7  0.0   0:02.92 kworker/3:2                                                                 
	18 root      20   0       0      0      0 S   0.3  0.0   0:13.50 ksoftirqd/2

(3)用top -H -p pid命令查看进程内各个线程占用的CPU百分比

            执行命令:top -H -p 70957

           第一列是两个线程id,70958这个线程占用的cpu比较高

            控制台输出:

PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND  
70958 root      20   0   25172   1136    948 S  3.7  0.0   0:18.52 cleanupDB.out                                                                
70957 root      20   0   25172   1136    948 S  0.0  0.0   0:00.00 cleanupDB.out

(4)使用gstack命令查看进程中各线程的函数调用栈

            执行命令:gstack 70957 > gstack.log

            在gstack.log中查找线程ID为70958,由于函数栈会暴露函数细节,因此只显示了两个函数桢,线程ID 70958对应线程号是2

Thread 2 (Thread 0x7fde4a882700 (LWP 70958)):
#0  0x00007fde4a972bbd in write () from /lib64/libc.so.6
#1  0x00007fde4a8fd2f3 in _IO_new_file_write () from /lib64/libc.so.6
#2  0x00007fde4a8feb0e in __GI__IO_do_write () from /lib64/libc.so.6
#3  0x00007fde4a8fda50 in __GI__IO_file_xsputn () from /lib64/libc.so.6
#4  0x00007fde4a8f27e2 in fwrite () from /lib64/libc.so.6
#5  0x00007fde4b1ffbb5 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) () from /lib64/libstdc++.so.6
#6  0x00007fde4b1ffeb7 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) () from /lib64/libstdc++.so.6
#7  0x000000000040242c in funA() ()
#8  0x000000000040243c in test() ()
#9  0x0000000000403a0b in void std::_Bind_simple<void (*())()>::_M_invoke<>(std::_Index_tuple<>) ()
#10 0x0000000000403965 in std::_Bind_simple<void (*())()>::operator()() ()
#11 0x00000000004038fe in std::thread::_Impl<std::_Bind_simple<void (*())()> >::_M_run() ()
#12 0x00007fde4b21e330 in execute_native_thread_routine () from /lib64/libstdc++.so.6
#13 0x00007fde4b478ea5 in start_thread () from /lib64/libpthread.so.0
#14 0x00007fde4a981b0d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7fde4ba96740 (LWP 70957)):
#0  0x00007fde4a972b5d in read () from /lib64/libc.so.6
#1  0x00007fde4a8fed54 in __GI__IO_file_underflow () from /lib64/libc.so.6
#2  0x00007fde4a8fff22 in __GI__IO_default_uflow () from /lib64/libc.so.6
#3  0x00007fde4a8fa8fa in getchar () from /lib64/libc.so.6
#4  0x0000000000402464 in main ()

(5)使用gcore命令转存进程映像及内存上下文

            执行命令:gcore 70957

   该命令生成core文件core.70957


(6)用strace命令查看系统调用和花费的时间


           执行命令:strace -T -r -c -p 70957

-c参数显示统计信息,去掉此参数可以查看每个系统调用话费的时间及返回值。

(7)用gdb调试core文件,并线程切换到2号线程

          gcore和实际的core dump时产生的core文件几乎一样,只是不能用gdb进行某些动态调试

          切换到2号线程,并查看其调用堆栈

(gdb) gdb yourAppName core.70957
(gdb) thread 2
[Switching to thread 2 (Thread 0x7fde4a882700 (LWP 70958))]
(gdb) bt

2.14 火焰图调查性能瓶颈位置

(1)安装perf和可视化生成器

        a) yum方式安装perf

            执行命令:yum install perf -y    

        b) 下载生成火焰图工具(最好新建一个文件夹,例如test,然后cd test,把火焰图文件夹FlameGraph下载到test文件夹中)

            执行命令:git clone https://github.com/brendangregg/FlameGraph.git  

(2)生成监视记录文件perf.data

             执行命令:perf record -F 99 -p 7778 -g -- sleep 60

备注:
	上述代码中perf record表示记录,-F 99表示每秒99次,-p 7778是进程号,即对哪个进程进行分析,
	-g表示记录调用栈,sleep 30则是持续30秒,-a 表示记录所有cpu调用。更多参数可以执行perf --help查看。
	perf.data文件生成后,表示采集完成。最好是在火焰图的目录下进行采集,方便转换成SVG图形。

(3)生成火焰图

             执行以下3条命令:

                          perf script -i perf.data &> perf.unfold                        //生成脚本文件

                        ./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded              

                        ./FlameGraph/flamegraph.pl perf.folded > perf.svg 

备注:	
	y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,
	顶部就是正在执行的函数,下方都是它的父函数。
	x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。
	注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
	火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。

2.15 perf分析高CPU进程

    2.15.1.perf top

        分析命令 :perf top -g -p XXX

        -g 开启调用关系分析,-p 指定 进程号 XXX

        类似于 top,它能够实时显示占用 CPU 时钟最多的函数或者指令,因此可以用来查找热点函数

        第一列 Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示。

        第二列 Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared Object),

        如内核、进程名、动态链接库名、内核模块名等。

        第三列 Object ,是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动

        态链接库,而 [k] 则表示内核空间。

        最后一列 Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。

2.16 strace跟踪系统调用

	50.1 获取命令帮助
		strace -h

	50.2 直接在命令前面加上strace命令就可以跟踪命令的执行过程了
		例如strace跟踪ls -h命令
		直接在命令前面加上strace命令就可以跟踪命令的执行过程了,会详细的显示命令过程中执行的命令、
		系统调用、进程接收的信号等。
		命令: strace ls -h
		以下为strace跟踪ls -h命令的输出结果:
			user01@user01-VMware-Virtual-Platform:~/桌面$ strace ls -h
			execve("/usr/bin/ls", ["ls", "-h"], 0x7ffcfd9e9198 /* 54 vars */) = 0
			brk(NULL)                               = 0x55df001ad000
			arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcaa5e71d0) = -1 EINVAL (无效的参数)
			access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)

	50.3 strace命令跟踪统计结果简洁显示
		使用-c参数可以显示strace命令的统计结果,显示命令执行了哪些系统调用信号、耗时、各系统调用次数等。
		命令:strace  -c ls -h
		以下为使用上述命令的输出结果:
			user01@user01-VMware-Virtual-Platform:~/桌面$ strace  -c ls -h
			1  2  cn.lanxin.desktop  fc-linux-install-x86_64  install_conf.toml
			% time     seconds  usecs/call     calls    errors syscall
			------ ----------- ----------- --------- --------- ----------------
			 21.48    0.001213         121        10         1 openat
			 20.75    0.001172          43        27           mmap
			  9.45    0.000534          59         9           mprotect
			  8.87    0.000501          62         8           pread64
			  7.49    0.000423          38        11           close
			  7.47    0.000422          42        10           fstat
	50.4 查看openat等系统调用类型(筛选查看指定的系统调用)
		strace默认查看所有调用信号,使用-e参数可以查看指定类型的调用,减少输出信息。
		支持的信号包括:trace, abbrev, verbose, raw, signal, read, write, fault, inject, kvm等
		命令:strace -e openat ls -h
		以下为使用上述命令的输出结果:
			openat(AT_FDCWD, "/dev/cur_gl", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
			openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
			openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
			1  1.txt  2  cn.lanxin.desktop	fc-linux-install-x86_64  install_conf.toml
			+++ exited with 0 +++
			
	50.5、显示时间戳
		strace输出默认是没有时间信息的,使用-t或者-tt参数我们可以打印命令执行的时间,精确到毫秒。
		命令:strace -t -e openat ls -h
		以下为使用上述命令的输出结果:
			root@user01-VMware-Virtual-Platform:/home/user01/桌面# strace -t -e openat ls -h
			11:41:45 openat(AT_FDCWD, "/dev/cur_gl", O_RDONLY|O_CLOEXEC) = -1 ENOENT (没有那个文件或目录)
			11:41:45 openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
			11:41:45 openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
			11:41:45 openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
			1  1.txt  2  cn.lanxin.desktop	fc-linux-install-x86_64  install_conf.toml
			11:41:45 +++ exited with 0 +++

	50.6、将strace结果输出到指定文件
		 如果希望保存strace命令执行的结果,我们可以使用-o参数将内容输出到指定文件。
		 命令:strace -t -o ./strace.log ls -h
		 
		 
	50.7、跟踪正在运行的进程
		 如果是排查已经正在运行的进程的问题,我们可以使用-p参数跟踪指定进程的系统信号调用。
		a)查询要跟踪的进程pid命令:ps -ef|grep 进程名
		b)跟踪正在运行的进程
			1、显示时间戳
				strace -t -p 进程pid
			2、或者命令(显示时间戳、跟踪fork):					
				strace -t  -f -p 进程pid
			3、获取命令(显示时间戳、系统调用栈回溯、保存到文件)
				strace -t -o ./strace_tgclient.log -k -p 进程pid
			4、统计进程的系统调用统计信息							
			        strace  -c -p 进程pid

2.17 perf命令使用

			perf有很多子命令,以为为常用的命令:
				perf list:      查看当前软硬件环境支持的性能事件
				perf stat:     分析指定程序的性能概况
				perf top:     实时显示系统/进程的性能统计信息
				perf record:记录一段时间内系统/进程的性能事件
				perf report:读取perf record生成的perf.data文件,并显示分析数据
			2.1 perf list命令
				命令功能:查看当前软硬件环境支持的性能事件
				一般执行命令:perf list
			
			2.2 perf stat命令
				命令功能:		分析指定程序的性能概况
				一般执行命令:	perf stat -a -p 进程pid
				默认事件简介:
						Task-clock(msec):CPU利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO
						Context-switches:进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的
						Cache-misses:程序运行过程中总体的 cache 利用情况,如果该值过高,说明程序的 cache 利用不好
						CPU-migrations:表示进程31404运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行
						Cycles:处理器时钟,一条机器指令可能需要多个 cycles
						Instructions: 机器指令数目
						IPC:是 Instructions/Cycles 的比值,该值越大越好,说明程序充分利用了处理器的特性
						Cache-references: cache命中的次数
						Cache-misses: cache失效的次数
						Page-faults:缺页异常的次数。当应用程序请求的页面尚未建立、请求的页面不在内存中,或者请求的页面虽然在内存中,但物理地址和虚拟地址的映射关系尚未建立时,都会触发一次缺页异常。另外TLB不命中,页面访问权限不匹配等情况也会触发缺页异常。
						Branches:遇到的分支指令数
						Branch-misses:预测错误的分支指令数
				命令参数
						-e <event>:指定性能事件(可以是多个,用,分隔列表)
						-p <pid>:   指定待分析进程的 pid(可以是多个,用,分隔列表)
						-t <tid>;:    指定待分析线程的 tid(可以是多个,用,分隔列表)
						-a:从所有 CPU 收集系统数据
						-d:打印更详细的信息,可重复 3 次
							-d:L1 和 LLC data cache
							-d -d:dTLB 和 iTLB events
							-d -d -d:增加 prefetch events
						-r <n>;:  重复运行命令 n 次,打印平均值。n 设为 0 时无限循环打印
						-c <cpu-list>:只统计指定 CPU 列表的数据,如:0,1,3或1-2
						-A:与-a选项联用,不要将 CPU 计数聚合
						-I <N msecs>:每隔 N 毫秒打印一次计数器的变化,N 最小值为 100 毫秒

			2.3 perf top命令
				命令功能:实时显示系统/进程的性能统计信息
				一般执行命令:perf  top  -a  -g  -p 进程pid,动态监视指定进程
				常用命令参数:
						-e <event>:指明要分析的性能事件。
						-p <pid>:仅分析目标进程及其创建的线程。
						-k <path>:带符号表的内核映像所在的路径。
						-K:不显示属于内核或模块的符号。
						-U:不显示属于用户态程序的符号。
						-d <n>:界面的刷新周期,默认为2s。
						-g:得到函数的调用关系图。
			2.4 perf record命令
				命令功能:收集一段时间内的性能事件到文件 perf.data,随后需要用perf report命令分析
				一般执行命令:
						perf record -F 999 -a -p xxxx -g -- sleep 30
						备注:
							# record 表示记录cpu的执行数据
							# -F:采样频率(次/秒)
							# -p:进程号
							# -g:输出调用栈数据
							# -a:所有cpu
							# -- sleep:本次采样总时长(秒)
				常用命令行参数:
					-e <event>:指定性能事件(可以是多个,用,分隔列表)
					-p <pid>:指定待分析进程的 pid(可以是多个,用,分隔列表)
					-t <tid>:指定待分析线程的 tid(可以是多个,用,分隔列表)
					-u <uid>:指定收集的用户数据,uid为名称或数字
					-a:从所有 CPU 收集系统数据
					-g:开启 call-graph (stack chain/backtrace) 记录
					-C <cpu-list>:只统计指定 CPU 列表的数据,如:0,1,3或1-2
					-r <RT priority>:perf 程序以SCHED_FIFO实时优先级RT priority运行这里填入的数值越大,进程优先级越高(即 nice 值越小)
					-c <count>: 事件每发生 count 次采一次样
					-F <n>:每秒采样 n 次
					-o <output.data>:指定输出文件output.data,默认输出到perf.data
		2.5、perf report命令
			命令功能:读取perf record生成的perf.data文件,并显示分析数据
				perf report 有许多控制命令,使用perf report -h查看
				不带任何参数运行时,默认在当前目录查找perf.data文件并统计输出
				perf report -i perf.data > perf.txt把结果写入文本文件

2.18 valgrind定位内存泄漏

    1、安装valgrind
		sudo yum install valgrind #centos
		sudo apt-get install valgrind #ubuntu
	2、检测内存泄漏
		valgrind --tool=memcheck --leak-check=full ./test.out
		
		编译源程序加上-g参数
		如果你的程序是会正常退出的程序,
		那么当程序退出的时候valgrind自然会输出内存泄漏的信息。
		如果程序是个守护进程,我们 只要在别的终端下杀死valgrind进程
		kill -TERM valgrind进程的PID
		这样我们的程序(./a.out)就被kill了

2.19 valgrind定位守护进程内存泄漏

      查看进程是否存在内存泄露,用top命令要查看进程的RES列是否增长就行,这一列的意义是进程占用的物理内存

      通过top命令查看cpu和内存排名

                通过 top 命令,Shift + M 来按照 %MEM 排序,Shift + p 按照 %CPU 排序

                top命令输出的每列的说明:

                        PID:进程的ID

                        USER:进程所有者

                        PR:进程的优先级别,越小越优先被执行

                        NInice:值

                        VIRT:进程占用的虚拟内存

                        RES:进程占用的物理内存

                        SHR:进程使用的共享内存

                        S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数

                        %CPU:进程占用CPU的使用率

                        %MEM:进程使用的物理内存和总内存的百分比

                        TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。

                        COMMAND:进程启动命令名称

      查看某一进程占用的内存,可采用命令:cat /proc/要查看的进程的pid/status

      VmRSS:  表示占用的物理内存,如果程序存在内存泄露的话,这个值会增大。

       以下是同构valgrind工具定位内存泄露的步骤。

           2.19.1 先杀掉要测试的程序Hello

                kill 内存泄露的程序pid


          2.19.2 把hello程序挂到valgrind上,注意修改/opt/mm/hello路径为自己要调查的泄露程序的路径。

valgrind --tool=memcheck --leak-check=full --log-file=valgrind_report_$(date +%F-%T).txt /opt/mm/hello -l trace  >> hello_out.txt 2>&1

          2.19.3 等到内存有增长时,优雅的终止valgrind进程

               kill -TERM valgrind进程的pid

               或者kill -15 valgrind进程的pid

               内存泄露的报告就在valgrind_report_$(date +%F-%T).txt文件中

2.20 gcore抓core分析调用堆栈

	1、抓取进程的core文件
		gcore 进程pid
	2、把进程的所有线程调用堆栈保存到文件中
		a)gdb 程序名  coredump文件名
		b)set logging file 日志文件名字.log
		c)set logging on
		d)thread apply all bt
		e)set logging off

2.21 查看某运行进程的信息

	1、动态查看一个进程加载的动态库(三个命令任何一个都可以)
		cat /proc/<PID>/maps|awk '{print $6}'|grep '\.so'|sort|uniq
		lsof -p <PID>|awk '{print $NF}'|grep '\.so'|sort|uniq
		pmap -p <PID>|awk '{print $4}'|grep '\.so'|sort|uniq

	2、查看进程加载了哪些so
		cat /proc/你的程序pid/maps
		cat /proc/你的程序pid/maps |grep test
	3、查看进程的环境变量
		cat  /proc/你的程序pid/environ
		cat  /proc/你的程序pid/environ |grep LD_
	4、pmap可以看到进程实际使用的内存
		pmap -d pid

2.22 Linux 程序运行时查找SO顺序

	1、gcc 编译时指定的运行时库路径 -Wl,-rpath
	2、环境变量 LD_LIBRARY_PATH
	3、ldconfig 缓存 /etc/ld.so.cache
	4 系统默认库位置 /lib /usr/lib
	备注:
	     开发时,设置LIBRARY_PATH,以便gcc能够找到编译时需要的动态链接库
	     发布时,设置LD_LIBRARY_PATH,以便程序加载运行时能够自动找到需要的动态链接库。	

2.23  gcc 编译程序时查找SO顺序

	1、gcc 编译时参数-L指定的路径
	2、环境变量 LIBRARY_PATH
	3、系统默认库位置 /lib /usr/lib

2.24 gdb调试关闭print或者cout的输出

终于找到了 完全禁用输出:
set print frame-arguments none 

仅打印标量值并忽略数组 & 结构
set print frame-arguments scalars 

要重新打开打印:
set print frame-arguments all




[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2024-1-30 13:53 被sanganlei编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 3762
活跃值: (5495)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 2022-4-13 21:57
2
0
verygood
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 2022-4-13 23:42
3
0
感谢分享,mark
游客
登录 | 注册 方可回帖
返回