Loading... 仅摘录常用且不容易记忆的命令。 # 1 gdb ## 1.1 查看内存 ![image.png](https://zclll.com/usr/uploads/2022/06/3354381991.png) 复制自[C语言中文网](http://c.biancheng.net/)。 64位机器上一般这样查看内存: ```cpp x /[n]xg [address] ``` `n`视情况而定。 ## 1.2 其他常用指令[gdb_list] * `ptype`:查看对象详细类型,`whatis`是简短类型。 * `bt`:打印现行调用栈 * `layout split`:对比查看源代码即汇编 * `set print pretty on`:分散打印结构体 * `call`/`print`:可以执行函数 * `set print object on`:**按照派生类型打印对象** * `watch <arg> [thread <tid>]`:当变量改变的时候中断(可以针对特定线程) # 2 GCC开发者选项 详细列表可见[这里](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html),提供了大量功能用于程序分析。 ## 2.1 查看类的情况(包括虚表) 目前主要用到的是查看虚表(vtable)的命令,是 ```powershell g++ -fdump-lang-class[='filename'] source.cpp ``` 注意到这个命令的名字和gcc版本有关: > Since GCC version 8.0 at least these two options were replaced: > `-fdump-class-hierarchy` ---> `-fdump-lang-class` > `-fdump-translation-unit` ---> `-fdump-lang-raw` 在linux下,不指定`filename`会直接生成`xxx.cpp.yyy.class`文件,里面就包含了所有类的虚表,如 类`A`如下: ```cpp class A { public: virtual void a() { cout << "A a()" << endl; } virtual void b() { cout << "A b()" << endl; } virtual void c() { cout << "A c()" << endl; } int x, y; }; ``` 其对应的结果就是: ```plaintext Vtable for A A::_ZTV1A: 5 entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI1A) 16 (int (*)(...))A::a 24 (int (*)(...))A::b 32 (int (*)(...))A::c Class A size=16 align=8 base size=16 base align=8 A (0x0x7f74cb191f60) 0 vptr=((& A::_ZTV1A) + 16) ``` ## 全部中间文件 命令是 ```powershell g++ -fdump-tree-all xxx.cpp ``` 该命令会产生大量中间文件,包括代码优化、debug…… 当然,把`all`改成特定的选项,就可以产生特定的中间文件了,例如`gimple`产生三地址中间代码。 # 3 追踪系统调用---strace[1] `strace`命令可以用于追踪系统调用,给程序计时……等等。它既可以追踪活跃进程,也可以唤起程序并追踪。为了调试程序目的,以下只介绍针对应用程序的。 `strace`存在大量的参数,具体使用时可以使用`help`参数查看。 ## 3.1 按顺序追踪所有调用和时间 一个例子是 ```powershell strace -o ./a.log -Ttte trace=all ./a ``` 这里需要注意的是`-o output`**必须位于实参之前**。 * `-T`含义为输出所有系统调用的时间, * `-tt`为输出调用发生的绝对时间戳(`-ttt`是使用UNIX时间), * `-e trace=xxx`指明要追踪的系统调用类型,`all`为全部。 具体可用参数可以查阅参考文献。其他常用的参数包括: * `-f`监控fork出来的进程, * `-e`可以根据状态(status),信号(singal)或者对应访问的文件……等等因素去选择特定的系统调用去监控,详情可以查阅`--help`帮助。 # 4 编译与链接——readelf与objdump[2] ## 4.1 gcc编译链接指令 `-E`表示只进行了预处理器工作;`-S`表示只编译不汇编,生成汇编代码;`-c`表示汇编后不链接,需要用`ld`(GNU链接器)或者`g++`等手动进行链接工作。 那么我们在组织项目的时候,如果出现符号之类的问题,就需要通过`readelf`命令去查看了,见下例: ## 4.2 readelf查看段表、符号表等 `readelf -a`可以用来查看所有段表,符号表等。 ### 例1 查看代码所属段 假设我们有这样一段代码 ```cpp int arr[10000]; int main() { int i, j = 1; } ``` 然后我们想要知道这些变量到底属于那个段,那么我们就可以使用`readelf`指令查看所有段表: `readelf -a a.out >> aelf.log`然后打开`aelf.log`即可发现: ![image.png](https://zclll.com/usr/uploads/2022/09/1113936901.png) data和bss段的起始位置。并且可以在符号表中找到实体`arr`和`i`的地址: ![image.png](https://zclll.com/usr/uploads/2022/09/3766287211.png) 由此印证了初始化的全局变量是在data段,未初始化的位于bss段。 ## 4.3 objdump `-r`查看重定向表,`-d`查看汇编找到具体代码位置。 ### 例2 链接期间重定向符号 在另一篇文章《编译、链接与装载(程序员自我修养笔记)》(还没写)中,我会详细叙述这一过程。这里举一个小例子,由以下三个文件组成: a.cpp: ```cpp #include "h.h" #include <iostream> int main() { std::cout<<f(1)<<std::endl; return 0; } ``` h.h: ```cpp int f(int); ``` h.cpp: ```cpp int f(int x) { return x*10; } ``` 然后分析步骤如下: 1. 使用`g++ -c a.cpp h.h h.cpp`命令编译、汇编但不链接它们,于是产生了`a.o`,`h.o`,`h.h.gch`三个文件; 2. 我们使用`readelf -a ./a.o >> ./a.log`文件查看链接前`a.o`的符号表,发现`f`存在于重定向表中:![image.png](https://zclll.com/usr/uploads/2022/09/3304810820.png) ,且当前符号表中为其设置的跳转地址为0:![image.png](https://zclll.com/usr/uploads/2022/09/3874202705.png),可以通过`objdump -d h.o`命令看到`h.o`中`f`的地址确实未设置:![image.png](https://zclll.com/usr/uploads/2022/09/3993964862.png) 3. 使用`g++ a.o h.o`将其链接,然后通过`readelf -a ./a.out >> ./a2.log`产生新的段文件, 4. 可以看到`f`跳转地址已经被设置好:![image.png](https://zclll.com/usr/uploads/2022/09/2282715315.png) 5. 通过`objdump -d a.out`查看链接好的可执行文件的汇编,可以看到跳转地址是相符的`124b`:![image.png](https://zclll.com/usr/uploads/2022/09/3752113810.png) 6. 然后找到代码中的`f`,验证其地址确实为`124b`:![image.png](https://zclll.com/usr/uploads/2022/09/822092892.png) 那么,如果出现另一个`int f(int)`的定义会如何呢?我们到专门讲链接过程的文章中再详细论述。 [gdb_list]: https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-watchpoint-on-specified-thread.html [2]: https://www.zhihu.com/question/27386057 [1]: https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316692.html © 允许规范转载 打赏 赞赏作者 赞 2 如果觉得我的文章对你有用,请随意赞赏
1 条评论
2022/9/19更新