-
-
[原创]linux gdb调试方法
-
2022-4-13 19:12 16375
-
一、gdb常用调试命令一览表
功能 | 命令 | 简写命令 | 例子 | 备注 |
查看命令的帮助 | help 命令名字 | help info break | ||
调节代码窗口高度 | winheight src -5 | wh 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 + [变量][表达式] 当变量或表达式值改变时即停住程序。 | ①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 按十六进制格式显示变量。 | p | 例1十进制打印nNum: (gdb) p nNum 例2十六进制打印nNum: (gdb) p /x nNum 例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 | 例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文件,定位崩溃点。
a)gdb 程序名 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
命令(注意扩住find命令的不是单引号.....,,就是ESC下面那个,和~同一个键)
gdb `find /root/projects/sosrc -type d -printf '-d %p '` testSO.out
2.7源码调试在别的机器上编译的程序,方法2(在B机器上编译,在A机器上调试)。
命令:readelf appName -p .debug_str|grep /或者readelf appName -p .debug_str|grep cpp
set substitute-path from_path to_path
比如 list显示的源码是 /home/aaa/1.cpp
那么设置了 set substitute-path /home/aaa/ /home/bbb/
2.8 调试汇编代码
(2)单步执行命令
si //step into 进入函数内部
ni //next 不进入函数内部
打印内存: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
例子2:x/ni 地址
2.9 gdb使用符号表调试release程序
(1)生成debug版本,strip出release版本发给客户
strip -g program_debug -o program_release
gdb --symbol=program_debug -exec=program_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 2 | detach掉编号为2的进程 |
kill inferiors 2 | kill掉编号为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当前值 |
set follow-fork-mode parent
只调试父进程,子进程继续运行(GDB默认)。
set follow-fork-mode child
只调试子进程,父进程继续运行。
- 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位置
(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
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百分比
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
执行命令: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 ()
该命令生成core文件core.70957
执行命令:strace -T -r -c -p 70957
-c参数显示统计信息,去掉此参数可以查看每个系统调用话费的时间及返回值。
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 火焰图调查性能瓶颈位置
b) 下载生成火焰图工具(最好新建一个文件夹,例如test,然后cd test,把火焰图文件夹FlameGraph下载到test文件夹中)
执行命令:git clone https://github.com/brendangregg/FlameGraph.git
执行命令: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图形。
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.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进程
内存泄露的报告就在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虚拟机自动化脱壳的方法