linux - 开发调试方法

linux 调试

  • gdb
  • htop
  • linux 程序运行文件
  • 段错误调试
  • memstat
  • 内存调试
  • 模拟 http 服务
  • 其它

gdb

  1. 编译: 必须使用 “-g” 选项
  2. 设置显示参数(查看:show print address)
    (1)打开地址输出:set print address on
    (2)打开数组显示:set print array on
       [1] 设置size:set print elements 200
       [2] 数组元素的下标:set print array-indexes on
    (3)字符串结束符停止:set print null-stop on
    (4)结构体分级显示:set print pretty on
    (5)字符显示开关:set print sevenbit-strings
  3. 查看源码(回车键继续)
    (1)显示中间代码:l
    (2)显示指定程序行:l 15
    (3)显示程序行的范围:l 5, 15
    (4)显示函数:l func_test
  4. 断点
    (1)指定程序行:b 43
    (2)显示函数:b func_test
    (3)显示某文件某行:gdb_test.c:47
    (4)显示函数:l func_test
    (5)查看断点:info b
    (6)删除断点(识别序号):delete 1
    (7)删除所有断点:delete b
    (8)条件断点:break 46 if testsize==100
  5. 查看变量信息
    (1)查看变量:print var_test
    (2)查看所有局部变量: info locals
    (3)查看全局和静态变量: info variables
    (4)查看当前函数参数: info args
    (5)查看结构体变量
       [1] p Book1.author
       [2] p Book1->author
    (6)查看数组变量
       [1] 地址:p title
       [2] 内容:p *title@20
    (7)查看变量/函数类型(int/char):whatis
    (8)查看堆栈信息,局部变量:bt full
  6. 查看寄存器
    (1)除浮点寄存器外: info reg
    (2)当前运行的指令地址:p $ip
    (3)当前堆栈地址:p $sp
  7. 环境变量
    (1)定义自己的变量,保存调试程序中的运行数据:set $var
       [1] set $i = 0
       [2] print bar[$i++]->contents
       [3] 敲回车,重复执行,实现bar数组的打印
    (2)查看所有的环境变量:show convenience
  8. 操作
    (1)执行完当前函数,不执行完整个程序: finish
    (2)执行N次下一步:n 10
    (3)一行行执行,会进入函数内部:step
    (4)运行至某行:until 行号
    (5)表达式发生变化时暂停:watch 表达
    (5)表达式被访问、改变时暂停:awatch 表达
    (5)表达式被访问时暂停:rwatch 表达
  9. attach 进程
    (1)程序已经运行可以使用attach命令
    (2)gdb attach pid
  10. cgdb, 调试时进行代码的同步显示

htop

  1. htop 展界面展示
    linux - 开发调试方法_第1张图片
  2. 查找线程的信息:cd /proc/进程ID/task/线程ID
  3. 查找线程的堆栈信息:cat /proc/进程ID/task/线程ID/stack
  4. 信息简介:
    (1)PID/USER/PRI:进行的标识号 / 运行此进程的用户 / 进程的优先级
    (2)NI:进程的优先级别值(默认0)
    (3)VIRT/RES:进程占用的虚拟内存值 / 进程占用的物理内存值
    (4)SHR:进程占用的共享内存值
    (5)S:进程的运行状况,R 运行, S 休眠待唤醒, Z 僵死状态
    (6)CPU%:进程占用的CPU使用率
    (7)MEM%:该进程占用的物理内存和总内存的百分比
    (8)TIME+:该进程启动后占用的总的CPU时间
    (9)COMMAND:启动命令名称
  5. setup(F2)配置
    (1)Meters, 设定顶端的显示信息(左右两侧)
       [1] Left column 表示左侧的显示的信息
       [2] Right column表示右侧显示的信息
       [3] Available meters表示可加选项,F5加到左侧,F6加到右侧
       [4] 选定信息的显示方式,有Bar、Text等,可自行设置
    linux - 开发调试方法_第2张图片
    (2)Display options,选择要显示的内容
       [1] 按空格, x表示显示,选择完后,按F10保存
    linux - 开发调试方法_第3张图片

linux 程序运行文件

  1. /proc/[pid]/status 文件
    (1) VmPeak: 虚拟内存的峰值
    (2) VmSize 当前使用的虚拟内存的大小
    (3) VmLck: 锁住的物理内存的大小(锁住的物理内存不能交换到硬盘)
    (4) VmHWM: 所使用的物理内存的峰值
    (5) VmRSS: 当前使用的物理内存的大小
    (6) VmData: 占用的数据段大小
    (7) VmStk: 进程占用的栈大小
    (8) VmExe: 进程占用的代码段大小(不包括库)
    (9) VmLib: 进程所加载的动态库所占用的内存大小(可能与其它进程共享)
    (10) VmPTE: 进程占用的页表大小(交换表项数量)
    (11) VmSwap: 进程所使用的交换区的大小

段错误调试

  1. 列出二进制文件中的符号表:gcc-nm a.out 或 gcc-nm -n (找到对应的地址)
    (1) A: 表示符号所对应的值是绝对的且在以后的连接过程中也不会改变
    (2) B或b: 表示符号位于未初始化的数据段(.bss段)中
    (3) C: 表示没有被初始化的公共符号
    (4) D或d: 表示符号位于初始化的数据段(.data段)中
    (5) N: 表示符号是调试用的
    (6) p: 表示符号位于一个栈回溯段中
    (7) R或r: 表示符号位于只读数据段(.rdata段)中
    (8) T或t: 表示符号位于代码段(.text段)中
    (9) U: 表示符号没有被定义
  1. 通过1获得的地址找到对应的文件和行号
    (1) addr2line -e a.out 000000000000090a
    (2) addr2line -e a.out 000000000000090a -f --demangle=gnu-v3 (可找到对应的函数)
  1. 定位段错误发生的库文件:ldd 命令
    (1)查看二进制程序的共享链接库依赖,包括库的名称、起始地址
  1. objdump 找发生段错误的地址
    (1) objdump -d bin_test > testDump
    (2) objdump -DCl bin_test > testDump
    (3) testDump 文件中查找发生段错误的地址
       [1] grep -n -A 10 -B 10 “0x0804f57f” ./testDump
  1. dmesg 段错误信息
    (1) [1077690.984042] a.out[27969]: segfault at 1 ip 00007f8bb6ae55a1 sp 00007ffdeeca7568 error 4 in libc-2.27.so[7f8bb6957000+1e7000]
    (2) 简介
       [1] 系统当前时间,进程名字及pid,segfault at 引起故障的地址
       [2] ip 指令的内存地址, sp 堆栈指针地址, 及栈顶指针
       [3] [7f8bb6957000+1e7000] 对象崩溃时映射的虚拟内存起始地址和大小
    (3) 错误码:error 后边的错误码可用来判断错误原因
       [1] error number是由三个字位组成的,从高到底分别为bit2 bit1和bit0,所以它的取值范围是0~7.
       [2] bit2: 1是用户态程序内存访问越界,0是内核态程序内存访问越界
       [3] bit1: 1是写操作导致内存访问越界,0是读操作导致内存访问越界
       [4] bit0: 1没有足够的权限访问非法地址的内容,0表示访问的非法地址根本没有对应的页面,也就是无效地址
    (4) 计算偏移量
       [1] 程序出错在libc-2.27.so中,指令地址00007f8bb6ae55a1,ibc-2.27.so起始映射地址7f8bb6957000
       [2] 计算指令的偏移地址:00007f8bb6ae55a1 - 7f8bb6957000 = 18E5A1
       [3] objdump -D /usr/lib/libsqlite3.so.0.8.6 > libtmp
       [4] addr2line -e /usr/lib/libsqlite3.so.0.8.6 18E5A1
    (5) 程序每次运行加载动态库的地址是不一样的,首先要把进程的memory map找来。
       [1]在Linux下,进程的memory map可以在/proc//maps文件中得到,
       [2] 在这个文件中找到动态链接库的基地址,
       [3]将backtrace中的地址 – 动态链接库的基地址,得到偏移地址offset address,
       [4]最后addr2line -C -f -e

memstat

  1. memstat -p
    (1) 展示每个库虚拟内存情况的命令

内存调试

1.GNU的 mtrace

/**
用法:
		1、 加入头文件 
		2、 在需要内存泄漏检查的代码的开始调用 void mtrace(), 结束调用 void muntrace();
		        一般情况不要调用 muntrace, 让程序自然结束, 因为有些内存释放代码要到 muntrace 之后运行。
		3、 用 debug 模式编译检查代码 (-g 或-ggdb)
		4、 设置环境变量 MALLOC_TRACE 保存log: export MALLOC_TRACE=/home/work/test/mtrace.log
		5、 运行程序,  mtrace a.out $MALLOC_TRACE
		6、 nm -n a.out
		7、 addr2line -e a.out 0x8694
**/
#include 
#include 
#include 

int main(intargc, char *argv[] )
{
		  int*p, *q ;

		  mtrace( ) ;

		  p = malloc( sizeof( int) ) ;
		  q = malloc( sizeof( int) ) ;
		  printf("p = %p\nq = %p\n", p, q ) ;
		  *p = 1 ;
		  *q = 2 ;
		  free( p ) ;
		  return 0 ;
}

2.malloc and free 钩子函数

/**
用法:
		1、 加入头文件 
		2、 gcc -g mtrace.c
		3、 ./a.out > test.log 2>&1
		4、 perl mtrace.pl test.log
		5、 addr2line -e a.out  0x00000000000009b0
**/
#include 
#include 
#include 
#include 
#include 

static void* (* old_malloc_hook) (size_t,const void *);
static void (* old_free_hook)(void *,const void *);
static void my_init_hook(void);
static void* my_malloc_hook(size_t,const void*);
static void my_free_hook(void*,const void *);

static void my_init_hook(void)
{
	old_malloc_hook = __malloc_hook;
	old_free_hook = __free_hook;
	__malloc_hook = my_malloc_hook;
	__free_hook = my_free_hook;
}
static void* my_malloc_hook(size_t size,const void *caller)
{
	void *result;
	__malloc_hook = old_malloc_hook;
	result = malloc(size);
	old_malloc_hook = __malloc_hook;
	printf("@@@ %p + %p 0x%x\n",caller,result,(unsigned long int)size);
	__malloc_hook = my_malloc_hook;
return result;
}
static void my_free_hook(void *ptr,const void *caller)
{
	__free_hook = old_free_hook;
	free(ptr);
	old_free_hook = __free_hook;
	printf("@@@ %p - %p\n",caller,ptr);
	__free_hook = my_free_hook;
}
char *p[10];

int func1()
{
	int i=0;
	for(i=0;i<10;i++)  p[i]=(char *)malloc(i);
	return 0;
}
int func2()
{
	int i=0;
	for(i=0;i<9;i++)  free(p[i]);
	return 0;
}
int main()
{
	my_init_hook();
	func1();
	func2();
	pause();
	return 0;
}
#! /usr/bin/perl
my $log = shift (@ARGV);
open flog ,"<$log" or die "cannot open $log:$!";
while()
{
	if(/^@@@/)
	{
		@items = split/\s+/;
		if($items[2] eq '+')
		{
			$size=hex(substr($items[4],2));
			$memory{$items[3]}=$size;
			$caller{$items[3]}=$items[1];
		}
		if($items[2] eq '-')
		{
			delete $memory{$items[3]};
			delete $caller{$items[3]};
		}
	}
}
foreach $key(sort keys %memory)
{
	print "$key $memory{$key} $caller{$key}\n";
}

3.valgrind

3.1 memcheck
(1) 探测程序中内存管理存在的问题。它检查所有对内存的读/写操作,并截取所有的malloc/new/free/delete 调用。
(2) memcheck 工具能够探测到以下问题:
   使用未初始化的内存
   读/写已经被释放的内存
   读/写内存越界
   读/写不恰当的内存栈空间
   内存泄漏
   malloc/new/new[]和 free/delete/delete[]不匹配

3.2 cachegrind
(1) cachegrind 是一个 cache 剖析器。它模拟执行 CPU 中的 L1, D1 和 L2 cache, 因此它能很精确的指出代码中的 cache 未命中。可以打印出 cache 未命中的次数,内存引用和发生 cache 未命中的每一行代码,每一个函数,每一个模块和整个程序的摘要。
(2) cachegrind 通过 CPUID 自动探测机器的 cache 配置.

3.3 helgrind
(1) helgrind 查找多线程程序中的竞争数据。
(2) helgrind 查找内存地址,那些被多于一条线程访问的内存地址,但是没有使用一致的锁就会被查出。这表示这些地址在多线程间访问的时候没有进行同步,很可能会引起很难查找的时序问题。

3.4 使用方法
(1) 非侵入式:
valgrind --tool=tool_name program_name
valgrind --tool=memcheck ls -l
(2) 检查内存泄漏:
valgrind --tool=memcheck --leak-check=yes ls -l
valgrind --track-fds=yes --leak-check=full --undef-value-errors=yes ./a.out

3.5 案例

#include 
#include 
#include 

static void mem_leak1(void)
{
    char *p = malloc(1);
}

static void mem_leak2(void)
{
    FILE *fp = fopen("test.txt", "w");
}

static void mem_overrun1(void)
{
    char *p = malloc(1);
    *(short*)p = 2;

    free(p);
}

static void mem_overrun2(void)
{
    char array[5];
    strcpy(array, "hello");
}

static void mem_double_free(void)
{
    char *p = malloc(1);
    free(p);
    free(p);
}

static void mem_free_wild_pointer(void)
{
    char *p;
    free(p);
}


int main()
{
    mem_leak1();
    mem_leak2();
    mem_overrun1();
    mem_overrun2();
    mem_double_free();
    mem_free_wild_pointer();

    return 0;
}

// valgrind --tool=memcheck --leak-check=yes ./test

/** 结果输出:
==12250== LEAK SUMMARY:
==12250==    definitely lost: 1 bytes in 1 blocks
==12250==    indirectly lost: 0 bytes in 0 blocks
==12250==      possibly lost: 0 bytes in 0 blocks
==12250==    still reachable: 552 bytes in 1 blocks
==12250==         suppressed: 0 bytes in 0 blocks

“definitely lost”:确认丢失。程序中存在内存泄露,应尽快修复。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存则会报这个错误。

“indirectly lost”:间接丢失。当使用了含有指针成员的类或结构时可能会报这个错误。这类错误无需直接修复,他们总是与”definitely lost”一起出现,只要修复”definitely lost”即可。

“possibly lost”:发现了一个指向某块内存中部的指针,而不是指向内存块头部, 只是怀疑有内存泄漏

“still reachable”:可以访问,未丢失但也未释放。如果程序是正常结束的,那么它可能不会造成程序崩溃,但长时间运行有可能耗尽系统资源。如果程序是崩溃(如访问非法的地址而崩溃)而非正常结束的,则应当暂时忽略它,先修复导致程序崩溃的错误,然后重新检测。 

“suppressed”:已被解决。出现了内存泄露但系统自动处理了。可以无视这类错误。

1) Value1, Value2, Value4, Value8, Value16:代表1-16字节的未初始化变量的使用
2) Cond (or its old name, Value0):表示未初始化的cpu条件变量
3) Addr1, Addr2, Addr4, Addr8, Addr16:表示1-16字节的不可addressable的内存访问
4) Jump:表示跳转到一个不可addressable的地方
5) Param:表示系统调用syscall的参数错误,这个类型需要另外一行指定syscall的那个参数
6) Free:表示不匹配的内存释放
7) Overlap:表示在memcpy时source和destination有重叠
8) Leak:表示内存泄漏
**/

4.gcc 的内存检查工具 ASAN
5.free命令
(1) free命令的所有输出值都是从/proc/meminfo中读出的

模拟 http 服务

  1. 模拟http服务:sudo busybox httpd -f -p 8080
  2. 下载:wget http://192.168.1.228:8080/test.avi

其它

  1. 清缓存: echo 3 > /proc/sys/vm/drop_caches
  2. uboot设置dts
    fdt set /reserved-memory/linux,isp_cma size <0x55d4a80>
    fdt pr /reserved-memory/linux,isp_cma
    boot
  3. 查找对应的安装包
    (1) apt-cache search latex
    (2) apt-cache show latex
  4. kill 僵尸进程
    (1) ps -A -ostat,ppid,pid |grep -e ‘^[Zz]’
    (2) kill -HUP 僵尸进程父ID
  5. mount
    (1) mount -t ext4 /dev/data /mnt
    (2)mount -o rw,remount /
  6. 查询信息
    (1) ls -l data/*.jpg | awk ‘{print $NF}’ # 查看行数(比实际值多一)
    (2) ls -l | grep “^d” | wc -l # 查询目录个数
    (3) ls -lR | grep “^-” | wc -l # 查询文件夹下文件个数

你可能感兴趣的:(linux,系统,linux,运维,服务器)