如何计算过程存储器的虚拟地址?

时间:2019-03-31 15:56:38

标签: c linux virtual-memory

我正在编写以下程序来检查进程的内存布局:

#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>

#define CHAR_LEN 255

char filepath[CHAR_LEN];
char line[CHAR_LEN];
char address[CHAR_LEN];
char perms[CHAR_LEN];
char offset[CHAR_LEN];
char dev[CHAR_LEN];
char inode[CHAR_LEN];
char pathname[CHAR_LEN];

int main() {
  printf("Hello world.\n");

  sprintf(filepath, "/proc/%u/maps", (unsigned)getpid());
  FILE *f = fopen(filepath, "r");

  printf("%-32s %-8s %-10s %-8s %-10s %s\n", "address", "perms", "offset",
         "dev", "inode", "pathname");
  while (fgets(line, sizeof(line), f) != NULL) {
    sscanf(line, "%s%s%s%s%s%s", address, perms, offset, dev, inode, pathname);
    printf("%-32s %-8s %-10s %-8s %-10s %s\n", address, perms, offset, dev,
           inode, pathname);
  }

  fclose(f);
  return 0;
}

我将程序编译为gcc -static -O0 -g -std=gnu11 -o test_helloworld_memory_map test_helloworld_memory_map.c -lpthread。我首先运行readelf -l test_helloworld_memory_map并获得:

Elf file type is EXEC (Executable file)
Entry point 0x400890
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000c9e2e 0x00000000000c9e2e  R E    200000
  LOAD           0x00000000000c9eb8 0x00000000006c9eb8 0x00000000006c9eb8
                 0x0000000000001c98 0x0000000000003db0  RW     200000
  NOTE           0x0000000000000190 0x0000000000400190 0x0000000000400190
                 0x0000000000000044 0x0000000000000044  R      4
  TLS            0x00000000000c9eb8 0x00000000006c9eb8 0x00000000006c9eb8
                 0x0000000000000020 0x0000000000000050  R      8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x00000000000c9eb8 0x00000000006c9eb8 0x00000000006c9eb8
                 0x0000000000000148 0x0000000000000148  R      1

 Section to Segment mapping:
  Segment Sections...
   00     .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit .stapsdt.base __libc_thread_subfreeres .eh_frame .gcc_except_table
   01     .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs
   02     .note.ABI-tag .note.gnu.build-id
   03     .tdata .tbss
   04
   05     .tdata .init_array .fini_array .jcr .data.rel.ro .got

然后,我运行程序并获取:

address                          perms    offset     dev      inode      pathname
00400000-004ca000                r-xp     00000000   fd:01    12551992   /home/zeyuanhu/share/380L-Spring19/lab3/src/test_helloworld_memory_map
006c9000-006cc000                rw-p     000c9000   fd:01    12551992   /home/zeyuanhu/share/380L-Spring19/lab3/src/test_helloworld_memory_map
006cc000-006ce000                rw-p     00000000   00:00    0          /home/zeyuanhu/share/380L-Spring19/lab3/src/test_helloworld_memory_map
018ac000-018cf000                rw-p     00000000   00:00    0          [heap]
7ffc2845c000-7ffc2847d000        rw-p     00000000   00:00    0          [stack]
7ffc28561000-7ffc28563000        r--p     00000000   00:00    0          [vvar]
7ffc28563000-7ffc28565000        r-xp     00000000   00:00    0          [vdso]
ffffffffff600000-ffffffffff601000 r-xp     00000000   00:00    0          [vsyscall]

我对为什么内存段的虚拟地址与“ / proc / [pid] / maps”中显示的虚拟地址不同感到困惑。例如,第二个内存段的虚拟地址为0xc9eb8所示的readelf,但在过程内存中,其虚拟地址计算为0x6c9000。该计算如何完成?

我知道链接器将0x400000指定为第一个内存段的起始地址,并且进程内存显示的地址与页面大小(4K)对齐(例如,0xc9e2e与{{1}对齐) }加上0xca000)。我认为这与0x400000显示的“对齐”列有关。但是,读ELF header会让我感到困惑:

readelf

具体来说,最后一句话是什么意思?:

  

否则,p_align应该是2的正整数幂,并且p_vaddr应该等于p_offset,以p_align为模。

它在说什么计算公式?

非常感谢!

1 个答案:

答案 0 :(得分:0)

这意味着对于除可加载段之外的其他 ,即没有LOAD的段,偏移量的最后n位必须与虚拟段的最后n相匹配地址;并且p_align字段的值为1 << n

例如,堆栈说它可以放在任何地方,只是地址需要对齐16。

要进行可加载,必须至少 页面对齐。以您的示例中的第二个为例:

               Offset             VirtAddr

LOAD           0x00000000000c9eb8 0x00000000006c9eb8 0x00000000006c9eb8
               0x0000000000001c98 0x0000000000003db0  RW     200000

鉴于页面大小为4096,偏移的后12位必须与虚拟地址的后12位相同。这是因为动态链接器通常使用mmap将页面直接从文件映射到内存中,并且这只能是页面粒度的。因此,实际上,动态链接器确实从文件映射了该范围的第一部分。

006c9000-006cc000                rw-p     000c9000   fd:01    12551992    
 /home/zeyuanhu/share/380L-Spring19/lab3/src/test_helloworld_memory_map

进一步看到文件大小小于虚拟大小-在其他映射中,其余数据将被零映射:

006cc000-006ce000                rw-p     00000000   00:00    0                  
 /home/zeyuanhu/share/380L-Spring19/lab3/src/test_helloworld_memory_map

如果您读取0x00000000006c9000 - 0x00000000006c9eb7处的字节,则应该看到与0x00000000004c9000 - 0x00000000006c9eb7处的字节完全相同的字节,这是因为数据段和代码段在文件中紧接彼此而没有填充-这样可以节省大量磁盘空间,并且实际上也可以帮助节省内存,因为可执行文件在块设备缓存中占用的空间更少!