本文学习自狄泰软件学院 唐佐林老师的 操作系统课程,本文图片来源于唐佐林老师课程PPT,只用于个人笔记学习
GNU为gcc编译器提供了辅助工具集
- 1 addr2line 定位程序段错误位置
- 2 strip 剔除调试信息
- 3 ar 打包解压目标文件 到 静态链接库
- 4 nm 列出目标文件中的标识符 地址 段 标识符
- 5 objdump 查看文件详细信息
- 6 File off
- 7 桌面环境下执行可执行文件过程 ()
- 8 嵌入式环境下执行可执行文件 之 nand flash
- 9 嵌入式环境下执行可执行文件 之 nor flash
待解决问题:
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ addr2line 00000000004008d3 -f -e test.out
func
??:? // 正常应该提示文件名以及所在文件的行号,感觉是环境问题,后面再解决吧
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
测试程序
func.c
#include <stdio.h>
int* g_pointer;//空指针 0
void func()
{
//向一个未定义的全局指针所指向的地址中写 一个int值,该值为 字符串常量的地址
//全局指针是空,指向0地址处,那么向0地址写值会引起程序的崩溃,此处只是为了测试
*g_pointer = (int)"D.T.Software";
return;
}
test.c
#include <stdio.h>
int g_global = 0;
int g_test = 1;
extern int* g_pointer;
extern void func();
int main(int argc, char *argv[])
{
printf("&g_global = %p\n", &g_global);
printf("&g_test = %p\n", &g_test);
printf("&g_pointer = %p\n", &g_pointer);
printf("g_pointer = %p\n", g_pointer);
printf("&func = %p\n", &func);
printf("&main = %p\n", &main);
func();
return 0;
}
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ gcc -g test.c func.c -o test.out^M
func.c: In function ‘func’:
func.c:7:15: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
*g_pointer = (int)"D.T.Software";
^
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ./test.out
&g_global = 0x601044
&g_test = 0x601038
&g_pointer = 0x601048
g_pointer = (nil)
&func = 0x4005c3
&main = 0x400526
Segmentation fault (core dumped)
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
结果很明显 全局指针的值为空 最后调用func发生了段错误。
那么问题来了,如果是一个很大的项目,代码有几十万行,运行的时候 程序产生了段错误 退出,我们该怎么定位问题?
addre2line!!!
1 开启 core dump 选项 用于记录程序崩溃的最后一刻的内存状态,寄存器状态
ulimit -c unlimited
2 gcc -g test.c func.c -o test.out ,注意 如果想使用 addr2line来定位问题,可执行程序test.out 必须是调试版本的,即 gcc -g 在编译时添加调试信息
./test.out 运行程序 生成崩溃时的 core文件,记录程序崩溃场景,包括内存布局和寄存器值
3 读取core文件, 获取IP寄存器值(0x08048000)
dmesg core
会有非常多信息,直接看最后一行
[164495.545723] test.out[15554]: segfault at 0 ip 00000000004008d3 sp 00007ffd919370e0 error 6 in test.out[400000+1000]
意思是 ip寄存器 在访问 地址00000000004005d3的时候 产生的段错误
4
addr2line 00000000004008d3 -f -e test.out
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ addr2line 00000000004008d3 -f -e test.out
func
??:? // 正常应该提示文件名以及所在文件的行号,感觉是环境问题,后面再解决吧
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
这里出现了异常,正常应该提示文件名以及所在文件的行号,感觉是环境问题,后面再解决吧
注意,在执行 ulimit -c unlimited 之后 再执行 可执行过程序 段错误后会提示已经产生了 core文件
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ulimit -c unlimited
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ gcc -g test.c func.c -o test.out
func.c: In function ‘func’:
func.c:7:15: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
*g_pointer = (int)"D.T.Software";
^
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ./test.out
&g_global = 0x601044
&g_test = 0x601038
&g_pointer = 0x601048
g_pointer = (nil)
&func = 0x4005c3
&main = 0x400526
Segmentation fault (core dumped) //注意 这里的段错误后面括号中的 内容,表明已经产生了一个 core文件
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ls
Segmentation fault (core dumped) //注意 这里的段错误后面括号中的 内容,表明已经产生了一个 core文件
很明显 test.out 经 strip剔除调试信息之后 文件大小由之前的 8760 变成了 6312
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ls -l test.out
-rwxrwxr-x 1 mhr mhr 8760 Jan 26 01:40 test.out
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ strip test.out
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ls -l test.out
-rwxrwxr-x 1 mhr mhr 6312 Jan 26 02:20 test.out
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
注意 经 strip剔除调试信息的可执行程序 不能用addr2line命令,因为已经没有了调试信息。
所以我们在调试的时候 尽量用 gcc -g 编译,即添加调试信息。
ar crs libname.a x.o y.o :将 后续所有 x.o y.o目标文件 打包到 libname.a静态链接库中
相反可以解压 查看目标静态链接库中 都有哪些 .o 目标文件
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ gcc -c test.c -o test.o
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ gcc -c func.c -o func.o
func.c: In function ‘func’:
func.c:7:18: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
*g_pointer = (int)"D.T.Software";
^
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ls *.o
func.o test.o
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ar crs libtest.a test.o func.o
mhr@ubuntu:~/Desktop/system/qianzhuan/6$ ls *.a
libtest.a
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
mhr@ubuntu:~/Desktop/system/qianzhuan/6$
这里的 .o文件只是目标文件,并不是可执行文件,也就说我们只是编译了还有没链接,既然没有链接,那么有一些符号的最终地址是没办法最终确定的,如func.o中的 func ,T代表代码段,前面的地址不是最终地址,而是在当前 func.o文件中 代码段的起始地址的偏移。而 g_pointer 前面的 C 代表 在编译的时候不知道 g_pointer 这个标识符该位于什么段中,4 代表 g_pointer 占用四个字节的内存。
其中的 func 前面的U 代表未定义的,即在 test 中使用 func 但是 不知道 func是什么,只有链接之后才知道。同样 g_pointer 也一样。g_global 前面的 B 代表 BSS段,前面的地址为0 即位于 bss段的首位,g_test这个标识符为数据段,相对于数据段 (date段)的 起始偏移为0,即date段的第一个标识符。
链接后在看:
gcc func.o test.o -o test.out
nm 一下 : nm test.out
此时 是链接之后 所有的标识符的地址就都确认了,可以运行可执行程序验证一下标识符地址。这就是虚拟地址。
虚存地址:即前面所说的虚拟内存的地址
加载地址:加载到内存中的地址
接下来 objdump 一下已经链接成功的 test.out
问题: .out文件打印的 vma 虚存地址 和 lma 加载地址 是一样的。
问题:当前环境下的加载地址是什么? 当前上下文中 可执行程序 是如何加载到内存中运行的?
1 桌面环境下执行可执行文件
test.out 是可执行文件,当运行可执行文件的时候,会发生如下三个事情:
1 为 test.out创建一个进程,并且分配虚存
2 将 test.out 中各个段 拷贝到虚存中。段的详细信息在 test.out中,file off 指的是相应的段在文件的哪一个偏移处,根据 file off 将对应的段 拷贝到 虚存中对应的位置。这个对应的位置就是各个段的起始虚地址,这个地址是编译时得到的,编译的时候会得到各个段的起始虚地址,都保存在 test.out中。即 在可执行程序运行的时候,会将各个段从可执行文件中加载虚存中的虚地址处,该虚地址是编译的时候确定的,代表了加载的目标地址,即加载地址。而运行地址指的是代码在内存当中实际的运行地址,指的是实地址。
3 执行应用程序
嵌入式环境下执行可执行文件 之 nand flash
nand flash :只能存储程序,不能够执行。
RAM :随机存取存储器,也叫主存,是与CPU直接交换数据的内部存储器。
嵌入式环境下执行可执行文件 之 nor flash
nor flash :存储的代码可以执行
可能没有虚存地址