动态库
Linux 下动态链接库(shared object file,共享对象文件)的文件后缀为.so
,它是一种特殊的目标文件(object file),可以在程序运行时被加载(链接)进来。使用动态链接库的优点是:程序的可执行文件更小,便于程序的模块化以及更新,同时,有效内存的使用效率更高。
创建一个动态链接库,可使用 GCC 的-shared
选项。输入文件可以是源文件、汇编文件或者目标文件。另外还得结合-fPIC
选项。-fPIC 选项作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code);这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc选项-g与-rdynamic的异同
-g ,是一个调试选项,是一个编译选项,即在源代码编译的过程中起作用,让gcc把更多调试信息(也就包括符号信息)收集起来并将存放到最终的目标文件内。调试信息本地格式(stabs, COFF, XCOFF,或DWARF)(`-gstabs+ ', `-gstabs', `-gxcoff+ ', `-gxcoff', `-gdwarf+ ',或
`-gdwarf' )
-rdynamic 却是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。
添加-rdynamic选项后,.dynsym表就包含了所有的符号,不仅是已使用到的外部动态符号,还包括本程序内定义的符号,比如bar、foo、baz等。
[root@www c]# gcc -O0 -rdynamic -o t.rd t.c
[root@www c]# readelf -s t.rd
Symbol table '.dynsym' contains 20 entries:
Symbol table '.symtab' contains 67 entries:
[root@www c]# strip t.rd [root@www c]# readelf -s t.rd
简单总结一下-g选项与-rdynamic选项的差别:
1,-g选项新添加的是调试信息(一系列.debug_xxx段),被相关调试工具,比如gdb使用后,可以被strip掉。(可以用strip a.exe)
2,-rdynamic选项新添加的是动态连接符号信息,用于动态连接功能,比如dlopen()系列函数、backtrace()系列函数使用,不能被strip掉,即强制strip将导致程序无法执行:
[root@www c]# ./t.rd test[root@www c]# strip -R .dynsym t.rd [root@www c]# ./t.rd
3 .symtab表在程序加载时会被加载器 丢弃 ,gdb等调试工具由于可以直接访问到磁盘上的二进制程序文件:
[root@www c]# gdb t.g -q
Reading symbols from /home/work/dladdr/c/t.g...done.
因此可以使用所有的调试信息,这包括.symtab表;
而backtrace()系列函数作为程序执行的逻辑功能,无法去读取磁盘上的二进制程序文件,因此只能使用.dynsym表。
其它几个工具可以动态指定查看,比如nm、objdump:
[root@www c]# nm t.rd nm: t.rd: no symbols [root@www c]# nm -D t.rd 0000000000400848 R _IO_stdin_used w _Jv_RegisterC
[root@www c]# objdump -T t.rd t.rd: file format elf64-x86-64 DYNAMIC SYMBOL TABLE: 0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 printf
4,-rdynamic选项不产生任何调试信息,因此在一般情况下,新增的附加信息比-g选项要少得多。除非是完全的静态连接,否则即便是没有加-rdynamic选项,程序使用到的外部动态符号,比如前面示例里的printf,也会被自动加入到.dynsym表。
makefile编写
[fdj@hs-192-168-33-206 tst]$ cat makefile
INCDIR = -I .
CC = g++
CFLAGS = -c -g
LFLAGS = -rdynamic
#so LFLAGS = -shared -fPIC
TARGET = demo
OBJS=testDst.o US_Time_Until.o
all: cleanobj $(TARGET)
testDst.o:testDst.cpp
$(CC) $(CFLAGS) testDst.cpp $(INCDIR)
US_Time_Until.o:US_Time_Until.cpp
$(CC) $(CFLAGS) US_Time_Until.cpp $(INCDIR)
$(TARGET):$(OBJS)
$(CC) $(LFLAGS) -o $(TARGET) $(OBJS)
cleanobj:
rm -f *.o
clean:cleanobj
clean all
gdb -q —————— 启动gdb时不输出版权说明
[root@VM-4-7-centos ~]# gdb -q a.out
Reading symbols from /root/a.out...done.
(gdb)
exe 启动gdb的方式
不带参数启动方式 gdb a.exe 或者 gdb + file a.exe
带参数启动方式 gdb --args a.exe pa1 pa2 或 gdb a.exe + run pa1 pa2 或者 gdb a.exe + set args pa1 pa2 + run
gdb 附加已运行进程 gdb + attach pid 或者 gdb -p pid ; (不影响附加后进程运行,且退出gdb, 使用dettach)
-g编译的程序去掉编译的调试信息,可以用strip a.exe
print /x /d /u /o /t(二进制) /f /c(字符形式)
ptype 打印变量类型
whatis 变量或表达式 ------- 显示变量或表达式的数据类型
(gdb) whatis buf
type = char [3]
(gdb) ptype buf
type = char [3]
修改变量的值 p data = 100
p data 打印显示100
set variable 变量=值 ------ 给变量赋值
(gdb) set buf[0]='a'
(gdb) p buf
$1 = "a\345\377"
(gdb) p buf[1]='b'
$2 = 98 'b'
(gdb) p buf
$3 = "ab\377"
(gdb)
打印全局变量 p 'xxx.cpp'::st_um
打印内存块内容,如果只用print *pData则只能打印一个pData类型的内存块,如果是char*则是一个字符,如果是char*[10] 类型则是10个字符。下面假设其是char* pData
要打印一段内容需要使用 print *pData@length eg: p *pData@20
(加餐,gdb默认最多打印200字符,要打印超过200个字符需要先 设置 set print elements 0,然后再调用 p *pData@210 打印内存块中的210个字符)——置为0表示没有限制
examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:
x/<n/f/u> <addr>
n、f、u是可选的参数。
n是一个正整数,表示需要显示的内存单元的个数,也就是说从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义。
f 表示显示的格式,参见下面。如果地址所指的是字符串,那么格式可以是s,如果是指令地址,那么格式可以是i。
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字 节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
<addr>表示一个内存地址。
注意:严格区分n和u的关系,n表示单元个数,u表示每个单元的大小。
n/f/u三个参数可以一起使用。例如:x/10tb buf
命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示输出三个单位,u表示按无符号十进制显示。
监视点变量或监测变量设置 watch data
rwatch data (data值被读取则中止)和 awatch =(watch + rwatch)读写则中止
删除变量检测
(gdb) info watch
Num Type Disp Enb Address What
2 hw watchpoint keep y buf
breakpoint already hit 2 times
(gdb) delete 2
(gdb) info watch
No watchpoints.
watch变量的使能和不能设置
(gdb) watch buf
Hardware watchpoint 3: buf
(gdb) info watch
Num Type Disp Enb Address What
3 hw watchpoint keep y buf
(gdb) disable 3
(gdb) info watch
Num Type Disp Enb Address What
3 hw watchpoint keep n buf
(gdb) enable 3
(gdb) i watch
Num Type Disp Enb Address What
3 hw watchpoint keep y buf
(gdb)
enable once 3使能一次,触发后disable
enable delete 3 使能,触发后删除
观察点设置,观察变量 display data
watch和display设置监測点的差别:
watch监測仅仅有当监測的变量值发生变化时才显示变量,并且旧值和新值都会显示。
display监測每一次执行命令都会显示变量的值,仅仅会显示变量的最新值。
查看和删除display
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1: y buf
(gdb) undisplay 1
不能和使能display
(gdb) display buf
2: buf = 0x7fffffffe4e0
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y buf
(gdb) disable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: n buf
(gdb) enable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y buf
设置条件断点 break filenum if condi==2
忽略断点n次 ignore
info break
$1xxxx
$2 xxx
ignore 2 10 //忽略断点命中10次
next 单步执行程序,遇到函数调用不会进入
step 单步执行程序,遇到函数调用会进入
next cnt 一次执行多步
step cnt 一次执行多步,会进入函数
nexti // nexti/stepi --------------- 单步运行一条机器指令
(gdb) ni
0x00000000004005c9 11 printf("44\n");
(gdb) n
44
12 print();
stepi 指令形式执行
(gdb) si
0x00000000004005a6 4 printf("xxxx");
(gdb)
0x00000000004005ab 4 printf("xxxx");
(gdb)
0x0000000000400470 in printf@plt ()
until ====循环体最后一句时,执行完循环后在循环外停止
until line ====停止在某行
jump location 可以修改代码执行逻辑,执行原本不可能执行到的代码,
(gdb) jump print()
Line 4 is not in `main()'. Jump anyway? (y or n) y
Continuing at 0x4005f1.
Breakpoint 1, print () at main.cpp:4
4 printf("xxxx");
(gdb) jump 4
Line 4 is not in `main()'. Jump anyway? (y or n) y
Continuing at 0x4005f1.
Breakpoint 1, print () at main.cpp:4
4 printf("xxxx");
finish 立即执行完函数后返回
return直接返回,不会执行下面的代码,可以指定函数返回值
disassemble
查看栈信息 bt (backtrace)
$1 ssss休息休息
$2 xxxx西溪湿地
切换栈编号 frame 2
$2 xxxx西溪湿地
===========gdbserver =============
server端启动
[root@VM-4-7-centos ~]# gdbserver 127.0.0.1:8818 a.out
或gdbserver 127.0.0.1:8818 attach 28678
Process a.out created; pid = 28678
Listening on port 8818
Remote debugging from host 127.0.0.1
print %
Detaching from process 28678
客户端启动
[root@VM-4-7-centos ~]# gdb
(gdb) target remote 127.0.0.1:8818
Remote debugging using 127.0.0.1:8818
warning: Could not load vsyscall page because no executable was specified
0x00007ffff7ddc140 in ?? ()
(gdb) l
No symbol table is loaded. Use the "file" command.
(gdb) file a.out
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from /root/a.out...done.
(gdb) l
1 #include <stdio.h>
2
3 int main(){
4 int a = 100;
5 printf("print %%\n");
6 return 0;
7 }
(gdb) b 5
Breakpoint 1 at 0x40056c: file m.cpp, line 5.
(gdb) c
Continuing.
Breakpoint 1, main () at m.cpp:5
5 printf("print %%\n");
(gdb) n
6 return 0;
(gdb) detach
Detaching from program: /root/a.out, process 28678
Ending remote debugging.
[Inferior 1 (process 28678) detached]
(gdb) quit
============================================