日常编写程序之后,在运行的时候都有对应的虚拟地址空间,例如在程序crash或调试的时候都能看到一串十六进制的数字,这些十六进制的数字通常都是程序对应的虚拟地址,那虚拟地址是怎么来的呢?
首先我们编写一个简单的程序
#include <stdio.h>
int func1(int a, int b) {
return a+b;
}
void func() {
int a,b,c;
c = func1(a,b);
printf("c:%d\n", c);
}
int main() {
func();
return 0;
}
代码逻辑比较简单,核心功能就是a+b的结果赋给c并打印。
然后我们编译生成对应的目标文件和可执行文件:
gcc –o -g t.o –c t.c //生成目标文件t.o
gcc -o –g t t.o //生成可执行文件t
接下来通过readelf看下t.o和t 从上面两张图可以看到t.o还没有分配虚拟地址,而t已经分配了地址,即程序在编译生成目标文件没有分配虚拟地址空间,而是在链接阶段分配的虚拟地址空间。
另外,64位操作系统程序分配(加载)的虚拟起始地址为0x400000,32位系统程序分配(加载)的虚拟起始地址为0x8048000。所以我们通过readelf和objdump看到的虚拟地址空间都是0x*400的样式。
接下来通过objdump –lS t看下反汇编结果信息,由于编译的时候加了-g,因此能看到对应源码的行号信息。
000000000040052d <func1>:
func1():
/data0/src/tools/t.c:2
#include <stdio.h>
int func1(int a, int b) {
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 89 7d fc mov %edi,-0x4(%rbp)
400534: 89 75 f8 mov %esi,-0x8(%rbp)
/data0/src/tools/t.c:3
return a+b;
400537: 8b 45 f8 mov -0x8(%rbp),%eax
40053a: 8b 55 fc mov -0x4(%rbp),%edx
40053d: 01 d0 add %edx,%eax
/data0/src/tools/t.c:4
}
40053f: 5d pop %rbp
400540: c3 retq
0000000000400541 <func>:
func():
/data0/src/tools/t.c:5
void func() {
400541: 55 push %rbp
400542: 48 89 e5 mov %rsp,%rbp
400545: 48 83 ec 10 sub $0x10,%rsp
/data0/src/tools/t.c:7
int a,b,c;
c = func1(a,b);
400549: 8b 55 f8 mov -0x8(%rbp),%edx # 0x7fffffffe38c
40054c: 8b 45 fc mov -0x4(%rbp),%eax # 0x7fffffffe388
40054f: 89 d6 mov %edx,%esi
400551: 89 c7 mov %eax,%edi
400553: e8 d5 ff ff ff callq 40052d <func1> # 0x40052d
400558: 89 45 f4 mov %eax,-0xc(%rbp)
/data0/src/tools/t.c:8
printf("c:%d\n", c);
40055b: 8b 45 f4 mov -0xc(%rbp),%eax # 0x7fffffffe384
40055e: 89 c6 mov %eax,%esi
400560: bf 20 06 40 00 mov $0x400620,%edi
400565: b8 00 00 00 00 mov $0x0,%eax
40056a: e8 a1 fe ff ff callq 400410 <printf@plt>
/data0/src/tools/t.c:9
}
40056f: c9 leaveq
400570: c3 retq
0000000000400571 <main>:
main():
/data0/src/tools/t.c:10
int main() {
400571: 55 push %rbp
400572: 48 89 e5 mov %rsp,%rbp
/data0/src/tools/t.c:11
func();
400575: b8 00 00 00 00 mov $0x0,%eax
40057a: e8 c2 ff ff ff callq 400541 <func>
/data0/src/tools/t.c:12
return 0;
40057f: b8 00 00 00 00 mov $0x0,%eax
/data0/src/tools/t.c:13
}
400584: 5d pop %rbp
400585: c3 retq
400586: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40058d: 00 00 00
从上面反汇编结果可以看到,最左一列的400***代表的就是虚拟地址空间,在64位系统上就是0x400000开始的虚拟地址空间。
通过gdb看下对应变量的虚拟地址空间:
函数func1的虚拟地址是0x40052d,和上面objdump看到的结果保持一致;变量a的虚拟地址是0x7fffffffe38c
因此,进程的虚拟地址是在链接阶段生成。