本节介绍基本的工具使用,如gcc,objdump和gdb
- 基本gcc命令使用
其中hello.c,hello.i,hello.s都是文本文件,hello.o和hello文件都是二进制文件。那么文本文件和二进制文件有什么区别。
- objdump工具
objdump可以对可重定位目标文件和可执行目标文件进行反汇编。
新建bse/cpp文件,内容如下:
#include <iostream>
using namespace std;
int getSum(int left, int right)
{
int sum = left + right;
sum*=2;
return sum;
}
int main()
{
int a = 100;
int b = 200;
int sum = getSum(a, b);
cout << "result = " << sum << endl;
return 0;
}
编译生成可执行文件
g++ base.cpp -g -o base
使用objdump文件并加上-S命令把cpp的代码也加入到反汇编的结果中。如下图截取一部分。
注意上面的反汇编地址都是虚拟地址!
3.GDB工具
GDB工具能对程序进程调试,用起来非常方便,这里总结gdb工具使用的方法。
3.1 生成带有调试信息的可执行文件
使用gcc或者g++生成可执行文件(包括so文件等)时,加上-g就可以了,-g能够把源代码的信息比如行号对应起来,这样后续调试的时候就知道出问题代码的具体位置了, 比如新建一个程序main.cpp
#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
using namespace std;
typedef struct OtherInfo {
bool isMarried;
bool isStudent;
string address;
} OtherInfo;
typedef struct UserInfo {
char name[20];
int age;
char gender;
OtherInfo otherInfo;
} UserInfo;
int main()
{
UserInfo Peter{"Peter", 23, 'M', {true, false, "Canada"}};
while (true) {
cout << "hello world"<<endl;
sleep(1);
}
return 0;
}
上面的代码只是声明了相关结构体并赋值,现在用g++生成可执行文件。
g++ -std=c++11 -g -o testMain main.cpp
上面指定使用c++11,并且有-g带入调试信息生成了可执行文件testMain.
3.2 gdb 加载可执行文件
有两个方法加载可执行文件,一个是gdb后面直接写可执行文件的名字,另一个是先进入gdb程序,使用file指令加载可执行文件。
1) 方法1
输入
gdb testMain
得到的结果如下:
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 testMain...done.
(gdb)
上图已经成功加载了testMain这个可执行文件
2)方法2
输入gdb后用file指令加载可执行文件
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".
(gdb) file testMain
Reading symbols from testMain...done.
(gdb)
3.3 gdb调试正在运行的进程
有时候想要调试的进程已经在运行了,这个时候可以通过gdb attach 进程pid或者gdb - 进程pid的方法调试这个可执行文件。
比如运行上面的可执行文件testMain
输入:
./testMain
现在有一个testMain进程在运行了,这个进程在终端不断打印hello world。现在如果想要调试这个进程的话首先找到这个进程的pid。
开启另一个终端,输入:
ps axuf|grep testMain|grep -v grep|awk {'print $2'}
输出得到:
6557
这个就是进程的pid。
使用gdb attach 进程pid的方法调试进程,输入
gdb attach 6557
结果可能报如下错误(若无错误更好)。
For help, type "help".
Type "apropos word" to search for commands related to "word"...
-: 没有那个文件或目录.
Attaching to process 6432
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: 不允许的操作.
切到root用户并修改文件/etc/sysctl.d/10-ptrace.conf中的kernel.yama.ptrace_scope = 1为kernel.yama.ptrace_scope = 0
使用gdb attach 进程pid的方法调试进程。
sudo gdb attach 6557
输出结果:
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"...
attach: 没有那个文件或目录.
Attaching to process 6557
Reading symbols from /home/aitian/at/test/testMain...(no debugging symbols found)...done.
Reading symbols from /usr/lib/x86_64-linux-gnu/libstdc++.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.23.so...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libm-2.23.so...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.23.so...done.
done.
0x00007f7b6747a2f0 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: 没有那个文件或目录.
(gdb) c
Continuing.
还有一种简单的方法是输入
sudo gdb - 6557
两种方法的结果是一样的。
3.4 最常用指令:
下断点(b);
单步运行n(跳过函数);
单步运行s(进入函数);
执行一条机器指令si;
继续(c);停止(ctrl+c);
打印变量(p)
注意:
rip寄存器(eip是32位的)保存了下一条将要执行的执行的地址。
i r:显示所有寄存器的内容
i r rip:显示指定寄存器的内容,比如这里的rip
x命令用于查看存储单元的内容,后面跟上存储单元的个数+显示的形式+存储单元的字节数。
比如x/8xb 0xfffd02bc 以16进制的形式,每个单元一个字节,一共8个字节
x/2xw 0xfffd02bc 以16进制形式,每个单元4个字节,一共2个字节
x命令时注意:
d 按十进制格式显示
x 按十六进制格式显示
a 按十六进制格式显示
u 按十六进制格式显示无符号整型
o 按八进制格式显示
t 按二进制格式显示
c 按字符格式显示
f 按浮点数格式显示
b表示单字节
h表示双字节
w表示四字节
g表示八字节
X86-64有16个64位寄存器,分别是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。其中:
%rax 作为函数返回值使用。
%rsp 栈指针寄存器,指向栈顶
%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
%rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改
%r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值.
举例如下:
(gdb) b main
Breakpoint 1 at 0x4008bb: file base.cpp, line 13.
(gdb) r
Starting program: /home/aitian/myProject/OS/day1/base
Breakpoint 1, main () at base.cpp:13
13 int a = 100;
(gdb) n
14 int b = 200;
(gdb) i r
rax 0x4008b3 4196531
rbx 0x0 0
rcx 0xc0 192
rdx 0x7fffffffd8a8 140737488345256
rsi 0x7fffffffd898 140737488345240
rdi 0x1 1
rbp 0x7fffffffd7b0 0x7fffffffd7b0
rsp 0x7fffffffd7a0 0x7fffffffd7a0
r8 0x7ffff7dd4ac0 140737351862976
r9 0x7ffff7dc9780 140737351817088
r10 0x32f 815
r11 0x7ffff76c5280 140737344459392
r12 0x4007a0 4196256
r13 0x7fffffffd890 140737488345232
r14 0x0 0
r15 0x0 0
rip 0x4008c2 0x4008c2 <main()+15>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) x/10i 0x4008c2
=> 0x4008c2 <main()+15>: movl $0xc8,-0x8(%rbp)
0x4008c9 <main()+22>: mov -0x8(%rbp),%edx
0x4008cc <main()+25>: mov -0xc(%rbp),%eax
0x4008cf <main()+28>: mov %edx,%esi
0x4008d1 <main()+30>: mov %eax,%edi
0x4008d3 <main()+32>: callq 0x400896 <getSum(int, int)>
0x4008d8 <main()+37>: mov %eax,-0x4(%rbp)
0x4008db <main()+40>: mov $0x4009f4,%esi
0x4008e0 <main()+45>: mov $0x601080,%edi
0x4008e5 <main()+50>: callq 0x400760 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
(gdb) disassemble /m main
Dump of assembler code for function main():
12 {
0x00000000004008b3 <+0>: push %rbp
0x00000000004008b4 <+1>: mov %rsp,%rbp
0x00000000004008b7 <+4>: sub $0x10,%rsp
13 int a = 100;
0x00000000004008bb <+8>: movl $0x64,-0xc(%rbp)
14 int b = 200;
=> 0x00000000004008c2 <+15>: movl $0xc8,-0x8(%rbp)
15 int sum = getSum(a, b);
0x00000000004008c9 <+22>: mov -0x8(%rbp),%edx
0x00000000004008cc <+25>: mov -0xc(%rbp),%eax
0x00000000004008cf <+28>: mov %edx,%esi
0x00000000004008d1 <+30>: mov %eax,%edi
0x00000000004008d3 <+32>: callq 0x400896 <getSum(int, int)>
0x00000000004008d8 <+37>: mov %eax,-0x4(%rbp)
16 cout << "result = " << sum << endl;
0x00000000004008db <+40>: mov $0x4009f4,%esi
0x00000000004008e0 <+45>: mov $0x601080,%edi
0x00000000004008e5 <+50>: callq 0x400760 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x00000000004008ea <+55>: mov %rax,%rdx
0x00000000004008ed <+58>: mov -0x4(%rbp),%eax
0x00000000004008f0 <+61>: mov %eax,%esi
0x00000000004008f2 <+63>: mov %rdx,%rdi
0x00000000004008f5 <+66>: callq 0x400710 <_ZNSolsEi@plt>
0x00000000004008fa <+71>: mov $0x400780,%esi
0x00000000004008ff <+76>: mov %rax,%rdi
0x0000000000400902 <+79>: callq 0x400770 <_ZNSolsEPFRSoS_E@plt>
17 return 0;
0x0000000000400907 <+84>: mov $0x0,%eax
18 }
0x000000000040090c <+89>: leaveq
0x000000000040090d <+90>: retq
End of assembler dump.
(gdb) i r rip
rip 0x4008c2 0x4008c2 <main()+15>