Как рассчитывается виртуальный адрес памяти процесса? - PullRequest
0 голосов
/ 31 марта 2019

Я пишу следующую программу для проверки структуры памяти процесса:

#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". Например, виртуальный адрес 2-го сегмента памяти равен 0xc9eb8, показанным readelf, но в памяти процесса он рассчитывается как 0x6c9000. Как этот расчет сделан?

Я знаю, что компоновщик указывает 0x400000 в качестве начального адреса первого сегмента памяти, а память процесса показывает адрес, выровненный по размеру страницы (4 КБ) (например, 0xc9e2e выровнен по 0xca000 плюс 0x400000) , Я думаю, что это как-то связано с колонкой «Выровнять», обозначенной readelf Однако чтение заголовка ELF меня смущает:

   p_align   This member holds the value to which the segments are
             aligned in memory and in the file.  Loadable process seg‐
             ments must have congruent values for p_vaddr and p_offset,
             modulo the page size.  Values of zero and one mean no
             alignment is required.  Otherwise, p_align should be a pos‐
             itive, integral power of two, and p_vaddr should equal
             p_offset, modulo p_align.

В частности, что означает последнее предложение?:

В противном случае p_align должен быть положительной, интегральной степенью двойки, а p_vaddr должен равняться p_offset, по модулю p_align.

О какой формуле расчета идет речь?

Большое спасибо!

Ответы [ 2 ]

0 голосов
/ 31 марта 2019

Это означает, что для других , чем загружаемые сегменты, т. Е. Без 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, это потому, что сегмент данных и сегмент кода идут сразу после друг друга вфайл без заполнения - это экономит много дискового пространства и на самом деле помогает также экономить оперативную память , потому что исполняемый файл занимает меньше места в кешах блочных устройств!

0 голосов
/ 31 марта 2019

Отображение адреса ЦП имеет гранулярность "страница", 4K все еще является очень распространенным размером страницы. /proc/$pid/maps показывает вам сопоставления ОС, но не показывает, какие адреса интересует процесс в отображаемых диапазонах. Ваш процесс заботится только о том, что начинается со смещения eb8 на первой отображаемой странице, но ЦП (и, следовательно, ОС, управляющая им для вас) не может быть обеспокоен отображением в байтовую гранулярность, и компоновщик знает это, поэтому он устанавливает файл диска с блоками размером с процессорную страницу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...