linux下程序的虚拟地址由来

linux下程序的虚拟地址由来

Posted by lwk on June 17, 2021

日常编写程序之后,在运行的时候都有对应的虚拟地址空间,例如在程序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 image image 从上面两张图可以看到t.o还没有分配虚拟地址,而t已经分配了地址,即程序在编译生成目标文件没有分配虚拟地址空间,而是在链接阶段分配的虚拟地址空间。

另外,64位操作系统程序分配(加载)的虚拟起始地址为0x400000,32位系统程序分配(加载)的虚拟起始地址为0x8048000。所以我们通过readelf和objdump看到的虚拟地址空间都是0x*400的样式。 image

接下来通过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看下对应变量的虚拟地址空间:

image

函数func1的虚拟地址是0x40052d,和上面objdump看到的结果保持一致;变量a的虚拟地址是0x7fffffffe38c

因此,进程的虚拟地址是在链接阶段生成。