代码介绍
libcp.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libcp.h"
int mycopy(char *a, int len)
{
char *tmp = (char *)malloc(1024 * sizeof(char));
memset(tmp, 10, 1024);
memcpy(a, tmp, len);
free(tmp);
return 0;
}
libcp.h
#ifndef _C_TEST_H_
#define _C_TEST_H_
#ifdef __cplusplus
extern "C" {
#endif
int mycopy(char *a, int len);
#ifdef __cplusplus
};
#endif
#endif
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libcp.h"
/*
* 主函数,简单测试
* 测试 core文件,
* 测试 堆栈内存信息
*/
int main(void) {
int i, n, ret;
char *a = NULL;
n = 20;
// a = (char *)malloc(n * sizeof(char));
ret = mycopy(a, 20);
printf("ret = %d\n", ret);
for(i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
编译
编译动态库
gcc -g -fPIC -shared -o libcp.so libcp.c
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
编译主程序
gcc -g -rdynamic -Wall main.c -o main.out -L. -lcp
-g 是一个编译选项,即在源代码编译的过程中起作用,让gcc把更多调试信息(也就包括符号信息)收集起来并将存放到最终的可执行文件内。
-rdynamic 用来通知链接器将所有符号添加到动态符号表中;如果不加-rdynamic选项,可能会看不到动态库里的符号表;
xxx@xxx:~/gdb$ readelf -s main.out
Symbol table '.dynsym' contains 21 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mycopy
7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
9: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 25 _edata
10: 0000000000601038 0 NOTYPE GLOBAL DEFAULT 25 __data_start
11: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 26 _end
12: 0000000000601038 0 NOTYPE WEAK DEFAULT 25 data_start
13: 0000000000400930 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
14: 00000000004008b0 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
15: 0000000000400730 42 FUNC GLOBAL DEFAULT 14 _start
16: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
17: 0000000000400826 137 FUNC GLOBAL DEFAULT 14 main
18: 00000000004006a8 0 FUNC GLOBAL DEFAULT 11 _init
19: 0000000000400920 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
20: 0000000000400924 0 FUNC GLOBAL DEFAULT 15 _fini
运行程序./main.out,提示error while loading shared libraries: libcp.so: cannot open shar
ed object file: No such file or directory,
/etc/ld.so.cache 文件搜寻要链接的动态库的。而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的。(注意, /etc/ld.so.conf 中并不必包含 /lib 和 /usr/lib,ldconfig程序会自动搜索这两个目录)
如果把 libcp.so 所在的路径添加到 /etc/ld.so.conf 中,再以root权限运行 ldconfig 程序,更新 /etc/ld.so.cache ,main.out运行时,就可以找到 libcp.so。
但不太希望改变系统的东西,所以暂时声明一个环境变量。
export LD_LIBRARY_PATH=~/gdb:$LD_LIBRARY_PATH
环境准备
开启coredump
在~/.bashrc最后添加
ulimit -c unlimited
在/etc/sysctl.conf最后添加
kernel.core_pattern = ./core_%t_%p_%e
kernel.core_uses_pid = 1
core_pattern模板中使用变量见下面的列表:
%p所dump进程的进程ID
%u所dump进程的实际用户ID
%g所dump进程的实际组ID
%s导致本次core dump的信号
%t core dump的时间 (由1970年1月1日计起的秒数)
%h主机名
%e程序文件名
sysctl -p /etc/sysctl.conf
执行使其生效
GDB调试代码
案例1
1、执行程序
xxx@xxx:~/gdb$ ./main.out
Segmentation fault (core dumped)
2、使用gdb进行调试
xxx@xxx:~/hxc/gdb$ gdb main.out core_1583756703_39387_main.out
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main.out...done.
[New LWP 39387]
Core was generated by `./main.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 __memcpy_sse2_unaligned ()
at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:37
37 ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S: No such file or directory.
(gdb) bt ==============>查看栈信息
#0 __memcpy_sse2_unaligned ()
at ../sysdeps/x86_64/multiarch/memcpy-sse2-unaligned.S:37
#1 0x00007f65d1eba7dc in mycopy (a=0x0, len=30) at libcp.c:12
#2 0x000000000040084e in main () at main.c:18
(gdb) f 1 =============>切换栈层
#1 0x00007f65d1eba7dc in mycopy (a=0x0, len=30) at libcp.c:12
12 memcpy(a, tmp, len);
(gdb) i f =============>打印该栈层的具体信息
Stack level 1, frame at 0x7ffd889693c0:
rip = 0x7f65d1eba7dc in mycopy (libcp.c:12); saved rip = 0x40084e
called by frame at 0x7ffd889693f0, caller of frame at 0x7ffd88969390
source language c.
Arglist at 0x7ffd889693b0, args: a=0x0, len=30
Locals at 0x7ffd889693b0, Previous frame's sp is 0x7ffd889693c0
Saved registers:
rbp at 0x7ffd889693b0, rip at 0x7ffd889693b8
(gdb)
通过调试我们发现段错误的原因是我们将一个空指针传入memcpy函数。
补充:段错误的原因:
- 没有权限,如往const修饰的内存中写数据;
- 读一个已经释放了的内存地址;
- 往一个指向0的内存写数据;
- 访问的局部变量没有初始化。
案例2
编译动态库时忘记使用-g选项;
将main.c进行修改
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libcp.h"
/*
* 主函数,简单测试
* 测试 core文件,
* 测试 堆栈内存信息
*/
int main(void) {
int i, n, ret;
char *a = NULL;
n = 20;
a = (char *)malloc(n * sizeof(char));
ret = mycopy(a, 30); // =============》这里进行了修改
printf("ret = %d\n", ret);
for(i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
1、执行程序
xxx@xxx:~/gdb$ ./main.out
*** Error in `./main.out': munmap_chunk(): invalid pointer: 0x0000000001a7a030 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f6587d747e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7f6587d81698]
libcp.so(mycopy+0x58)[0x7f65880c77e8]
./main.out(main+0x39)[0x40089f]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f6587d1d830]
./main.out(_start+0x29)[0x400799]
2、使用gdb调试
eason@eason-ubuntu:~/hxc/gdb$ gdb main.out core_1583758294_39765_main.out
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main.out...done.
[New LWP 39765]
Core was generated by `./main.out'.
Program terminated with signal SIGABRT, Aborted.
#0 0x00007f948c598428 in __GI_raise (sig=sig@entry=6)
at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 0x00007f948c598428 in __GI_raise (sig=sig@entry=6)
at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007f948c59a02a in __GI_abort () at abort.c:89
#2 0x00007f948c5da7ea in __libc_message (do_abort=do_abort@entry=2,
fmt=fmt@entry=0x7f948c6f3ed8 "*** Error in `%s': %s: 0x%s ***\n")
at ../sysdeps/posix/libc_fatal.c:175
#3 0x00007f948c5e7698 in malloc_printerr (ar_ptr=0x0, ptr=<optimized out>,
str=0x7f948c6f3f00 "munmap_chunk(): invalid pointer", action=<optimized out>)
at malloc.c:5006
#4 munmap_chunk (p=<optimized out>) at malloc.c:2842
#5 __GI___libc_free (mem=<optimized out>) at malloc.c:2963
#6 0x00007f948c92d7e8 in mycopy () from libcp.so
#7 0x000000000040089f in main () at main.c:18
(gdb) f 6
#6 0x00007f948c92d7e8 in mycopy () from libcp.so
(gdb) disassemble =====================>反汇编代码
Dump of assembler code for function mycopy:
0x00007f948c92d790 <+0>: push %rbp
0x00007f948c92d791 <+1>: mov %rsp,%rbp
0x00007f948c92d794 <+4>: sub $0x20,%rsp
0x00007f948c92d798 <+8>: mov %rdi,-0x18(%rbp)
0x00007f948c92d79c <+12>: mov %esi,-0x1c(%rbp)
0x00007f948c92d79f <+15>: mov $0x400,%edi
0x00007f948c92d7a4 <+20>: callq 0x7f948c92d670 <malloc@plt>
0x00007f948c92d7a9 <+25>: mov %rax,-0x8(%rbp)
0x00007f948c92d7ad <+29>: mov -0x8(%rbp),%rax
0x00007f948c92d7b1 <+33>: mov $0x400,%edx
0x00007f948c92d7b6 <+38>: mov $0xa,%esi
0x00007f948c92d7bb <+43>: mov %rax,%rdi
0x00007f948c92d7be <+46>: callq 0x7f948c92d650 <memset@plt>
0x00007f948c92d7c3 <+51>: mov -0x1c(%rbp),%eax
0x00007f948c92d7c6 <+54>: movslq %eax,%rdx
0x00007f948c92d7c9 <+57>: mov -0x8(%rbp),%rcx
0x00007f948c92d7cd <+61>: mov -0x18(%rbp),%rax
0x00007f948c92d7d1 <+65>: mov %rcx,%rsi
0x00007f948c92d7d4 <+68>: mov %rax,%rdi
0x00007f948c92d7d7 <+71>: callq 0x7f948c92d660 <memcpy@plt>
0x00007f948c92d7dc <+76>: mov -0x8(%rbp),%rax
0x00007f948c92d7e0 <+80>: mov %rax,%rdi
0x00007f948c92d7e3 <+83>: callq 0x7f948c92d640 <free@plt>
=> 0x00007f948c92d7e8 <+88>: mov $0x0,%eax
0x00007f948c92d7ed <+93>: leaveq
0x00007f948c92d7ee <+94>: retq
End of assembler dump.
(gdb) shell echo free@plt |c++filt ====================>去掉函数的名词修饰
free@plt
(gdb)
当我们编译时没有打开调试-g或者-rdynamic选项,gdb调试时看不到代码,这时即使执行addr2line也无法找到代码
xxx@xxx:~/gdb$ addr2line -Cif -e main.out 0x7f948c92d7e8
??
??:0
可以使用disassemble打开该帧函数的反汇编代码,**=>**箭头出就是出错的地方。
补充:产生SIGABRT的原因:
- double free/free 没有初始化的地址或者错误的地址
- 堆越界
- assert
GDB其他技巧
info命令
info args 查看当前函数参数值
info locals 看当前函数栈上值信息
info registers 表示查看寄存器值.
x命令
查看内存,例如**x /23dw a 意思是 查看 从a地址开始 23个 4字节 有符号十进制数 输出.
用gdb查看内存格式:
x /nfu ptr
说明
x 是 examine 的缩写
n表示要显示的内存单元的个数
f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
i 指令地址格式
c 按字符格式显示变量。
f 按浮点数格式显示变量。
u表示一个地址单元的长度
b表示单字节,
h表示双字节,
w表示四字节,
g表示八字节
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char) and s(string).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)
ptr 表示从那个地址开始
多线程调试
info threads 查看所有运行的线程信息
thread 3表示切换到第三个线程, info threads 第一列id 就是 thread 切换的id.
set scheduler-locking on 开始多线程单独调试。设置 set scheduler-locking off 关闭,又会回到你调试这个, 其它线程不阻塞.