О расположении памяти программ в Linux

У меня есть несколько вопросов о структуре памяти программы в Linux. Я знаю из различных источников (я читаю «Программирование с нуля»), что каждый раздел загружается в свою область памяти. Сначала текстовый раздел загружается по виртуальному адресу 0x8048000, сразу после этого загружается раздел данных, затем - раздел bss, затем куча и стек.

Чтобы поэкспериментировать с макетом я сделал эту программу в сборке. Сначала он печатает адреса некоторых меток и вычисляет точку останова системы. Затем он входит в бесконечный цикл. Цикл увеличивает указатель и затем пытается получить доступ к памяти по этому адресу, в какой-то момент ошибка сегментации завершит работу программы (я сделал это намеренно).

Это программа:

.section .data

start_data:
str_mem_access:
.ascii "Accessing address: 0x%x\n\0"
str_data_start:
.ascii "Data section start at: 0x%x\n\0"
str_data_end:
.ascii "Data section ends at: 0x%x\n\0"
str_bss_start:
.ascii "bss section starts at: 0x%x\n\0"
str_bss_end:
.ascii "bss section ends at: 0x%x\n\0"
str_text_start:
.ascii "text section starts at: 0x%x\n\0"
str_text_end:
.ascii "text section ends at: 0x%x\n\0"
str_break:
.ascii "break at: 0x%x\n\0"
end_data:

.section .bss

start_bss:
.lcomm buffer, 500
.lcomm buffer2, 250
end_bss:

.section .text
start_text:

.globl _start
_start:

# print address of start_text label
pushl $start_text
pushl $str_text_start
call printf
addl $8, %esp
# print address of end_text label
pushl $end_text
pushl $str_text_end
call printf
addl $8, %esp
# print address of start_data label
pushl $start_data
pushl $str_data_start
call printf
addl $8, %esp
# print address of end_data label
pushl $end_data
pushl $str_data_end
call printf
addl $8, %esp
# print address of start_bss label
pushl $start_bss
pushl $str_bss_start
call printf
addl $8, %esp
# print address of end_bss label
pushl $end_bss
pushl $str_bss_end
call printf
addl $8, %esp
# get last usable virtual memory address
movl $45, %eax
movl $0, %ebx
int $0x80

incl %eax # system break address
# print system break
pushl %eax
pushl $str_break
call printf
addl $4, %esp

movl $start_text, %ebx

loop:
# print address
pushl %ebx
pushl $str_mem_access
call printf
addl $8, %esp

# access address
# segmentation fault here
movb (%ebx), %dl

incl %ebx

jmp loop

end_loop:
movl $1, %eax
movl $0, %ebx
int $0x80

end_text:

И это соответствующие части вывода (это Debian 32bit):

text section starts at: 0x8048190
text section ends at: 0x804823b
Data section start at: 0x80492ec
Data section ends at: 0x80493c0
bss section starts at: 0x80493c0
bss section ends at: 0x80493c0
break at: 0x83b4001
Accessing address: 0x8048190
Accessing address: 0x8048191
Accessing address: 0x8048192
[...]
Accessing address: 0x8049fff
Accessing address: 0x804a000
Violación de segmento

Мои вопросы:

1) Почему моя программа начинается с адреса 0x8048190 вместо 0x8048000? При этом я предполагаю, что инструкция с меткой "_start" загружается не первой, так что находится между адресами 0x8048000 и 0x8048190?

2) Почему существует разрыв между концом текстового раздела и началом раздела данных?

3) Начальный и конечный адреса bss совпадают. Я предполагаю, что два буфера хранятся где-то еще, это правильно?

4) Если точка останова системы находится в 0x83b4001, почему я получаю ошибку сегментации ранее в 0x804a000?

 Peter Cordes09 авг. 2016 г., 19:05
Обратите внимание, что загрузчик ELF заботится только осегменты исполняемого файла. Во многих случаях есть отображение 1: 1, например.text раздел (после ссылки) является единственным в текстовом сегменте. Линкер объединяет такие разделы, как.rodata в.text, Кроме того, «куча» на самом деле не вещь, которая существует, а скорее концепция (распределение с помощью mmap (MAP_ANONYMOUS) не является смежным сbrk). Я не уверен, считают ли люди BSS и статические данные частью кучи. Также не уверен, если Linux ставит начальныйbrk сразу после BSS.
 Lynn Crumbling09 авг. 2016 г., 18:51
Почти полностью не по теме, если вы никогда непрочитайте это, посмотрите на это - это отличное чтение.

Ответы на вопрос(1)

Решение Вопроса

Я предполагаю, что вы строите это сgcc -m32 -nostartfiles segment-bounds.S или аналогичный, так что у вас есть 32-битный динамический двоичный файл. (Вам не нужно-m32 если вы на самом деле используете 32-битную систему, но большинство людей, которые хотят проверить это, будут иметь 64-битные системы.)

Моя 64-битная система Ubuntu 15.10 выдает несколько отличные от вашей программы числа по нескольким причинам, но общая модель поведения такая же. (Другое ядро, или простоASLRобъясняет это. Адрес brk сильно отличается, например, от таких значений, как0x9354001 или же0x82a8001)

1) Почему моя программа начинается с адреса 0x8048190 вместо 0x8048000?

Если вы строите статический двоичный файл, ваш_start будет в 0x8048000.

Мы можем видеть изreadelf -a a.out тот0x8048190 это начало раздела .text. Но это не в начале текстового сегмента, который отображается на странице. (страницы 4096B, и Linux требует, чтобы отображения были выровнены по границам 4096B позиции файла, поэтому с файлом, расположенным таким образом, это было бы невозможноexecve на карту_start к началу страницы. Я думаю, что столбец Off - это позиция в файле.)

Предположительно другие разделы в текстовом сегменте перед.text раздел - это данные только для чтения, которые необходимы динамическому компоновщику, поэтому имеет смысл поместить их в память на той же странице.

## part of readelf -a output
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048114 000114 000013 00   A  0   0  1
  [ 2] .note.gnu.build-i NOTE            08048128 000128 000024 00   A  0   0  4
  [ 3] .gnu.hash         GNU_HASH        0804814c 00014c 000018 04   A  4   0  4
  [ 4] .dynsym           DYNSYM          08048164 000164 000020 10   A  5   1  4
  [ 5] .dynstr           STRTAB          08048184 000184 00001c 00   A  0   0  1
  [ 6] .gnu.version      VERSYM          080481a0 0001a0 000004 02   A  4   0  2
  [ 7] .gnu.version_r    VERNEED         080481a4 0001a4 000020 00   A  5   1  4
  [ 8] .rel.plt          REL             080481c4 0001c4 000008 08  AI  4   9  4
  [ 9] .plt              PROGBITS        080481d0 0001d0 000020 04  AX  0   0 16
  [10] .text             PROGBITS        080481f0 0001f0 0000ad 00  AX  0   0  1         ########## The .text section
  [11] .eh_frame         PROGBITS        080482a0 0002a0 000000 00   A  0   0  4
  [12] .dynamic          DYNAMIC         08049f60 000f60 0000a0 08  WA  5   0  4
  [13] .got.plt          PROGBITS        0804a000 001000 000010 04  WA  0   0  4
  [14] .data             PROGBITS        0804a010 001010 0000d4 00  WA  0   0  1
  [15] .bss              NOBITS          0804a0e8 0010e4 0002f4 00  WA  0   0  8
  [16] .shstrtab         STRTAB          00000000 0010e4 0000a2 00      0   0  1
  [17] .symtab           SYMTAB          00000000 001188 0002b0 10     18  38  4
  [18] .strtab           STRTAB          00000000 001438 000123 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

2) Почему существует разрыв между концом текстового раздела и началом раздела данных?

Почему бы и нет? Они должны находиться в разных сегментах исполняемого файла, поэтому отображаются на разных страницах. (Текст доступен только для чтения и является исполняемым, и может быть MAP_SHARED. Данные доступны для чтения и записи и должны быть MAP_PRIVATE. Кстати, в Linux по умолчанию данные также являются исполняемыми.)

Оставляя пробел, у динамического компоновщика остается место для сопоставления текстового сегмента общих библиотек рядом с текстом исполняемого файла. Это также означает, что индекс массива вне пределов в разделе данных с большей вероятностью может стать причиной ошибки. (Более ранний и шумный сбой всегда легче отлаживать).

3) Начальный и конечный адреса bss совпадают. Я предполагаю, что два буфера хранятся где-то еще, это правильно?

Это интересно. Они в BSS, но IDK, почему текущая позиция не зависит.lcomm этикетки. Вероятно, они идут в другом подразделе до ссылки, так как вы использовали.lcomm вместо.comm, Если я использую использовать.skip или же.zero чтобы зарезервировать место, я получаю ожидаемые вами результаты:

.section .bss
start_bss:
#.lcomm buffer, 500
#.lcomm buffer2, 250
buffer:  .skip 500
buffer2: .skip 250
end_bss:

.lcomm помещает вещи в BSS, даже если вы не переключаетесь на этот раздел. то есть, ему все равно, какой будет текущий раздел, и, возможно, его не волнует или не влияет на текущую позицию в.bss раздел есть. TL: DR: когда вы переключаетесь на.bss вручную, используйте.zero или же.skipне.comm или же.lcomm.

4) Если точка останова системы находится в 0x83b4001, почему я получаю ошибку сегментации ранее в 0x804a000?

Это говорит нам о том, что между сегментом текста и brk есть несопоставленные страницы. (Ваш цикл начинается сebx = $start_text, так что это происходит с ошибкой на первой неотображенной странице после текстового сегмента). Помимо дыры в виртуальном адресном пространстве между текстом и данными, возможно, есть и другие дыры за сегментом данных.

Защита памяти имеет степень детализации страницы (4096B), поэтому первым ошибочным адресом всегда будет первый байт страницы.

 Peter Cordes11 авг. 2016 г., 02:42
@ saga.x: Да, вы можете отключить ASLR, но вам обычно не нужна эта повторяемость между запусками при отладке с помощью gdb и / proc / pid / maps. Интересно, что он использует тот же brk, что и ваше 32-битное ядро ​​без ASLR. Между 32- и 64-разрядными ядрами существуют различия: хотя в IIRC 32-разрядные процессы в 64-разрядном ядре могут использовать все 4 ГБ виртуального адресного пространства, но 32-разрядные ядра резервируют верхние 1 или 2 ГБ каждого виртуального процесса. адресное пространство для отображения памяти ядра во время системных вызовов. (Таким образом, вы можете выделить до 3GiB только в 32-битном процессе на 32-битном ядре.)
 saga.x11 авг. 2016 г., 02:15
Хорошо, я установилgcc-multilib пакет и построил его сgcc -m32 -nostartfiles, оно работает. Я также искал что-то о ASLR, и если я выполняю от имени пользователя rootsysctl -w kernel.randomize_va_space=0 адрес точки останова никогда не меняется, он зафиксирован по адресу 0x804a001, который является тем же адресом ошибки сегментации, которую я получил. Я должен прочитать намного больше о том, как работает Linux и управление памятью, чтобы действительно лучше понять эту тему, очень интересно, но я новичок в этом. Спасибо за ответ!
 Peter Cordes11 авг. 2016 г., 02:50
@ saga.x: Кроме того, я многое узнал о Linux, прочитав справку по различным параметрам, когда конфигурировал / компилировал свое собственное ядро ​​в тот день, когда компьютеры работали медленно, а диск / оперативная память были маленькими, и это того стоило (P233MMX с 64 МБ ОЗУ, я думаю :) (например, была возможность выбрать, хотите ли вы ядро ​​2G: 2G: разделение пользователя или 1: 3.)
 saga.x11 авг. 2016 г., 01:32
Я строю это сas break.S -o break.o && ld -dynamic-linker /lib/ld-linux.so.2 -o break break.o -lc внутри виртуальной машины Debian 3.5 i386 (Хост - Ubuntu 15.10 64bit).
 Peter Cordes11 авг. 2016 г., 02:44
И да, есть куча вещей, чтобы понять! Я использовал Linux в качестве настольного компьютера с тех пор, как существовала AMD64 (почти 20 лет), поэтому я смог постепенно разбираться со многими вещами, вместо того, чтобы испытывать всю сложность сразу. Я уже знал много вещей, прежде чем я начал серьезно возиться с АСМ. Во всяком случае, ваши вопросымного лучше, чем обычно скучно: «Я ничего не понимаю в asm, но я написал эту программу. Отладка для меня» - вопросы, которые мы часто видим на SO. Следите за интересными вопросами :)
 Peter Cordes11 авг. 2016 г., 01:38
@ saga.x: Да, это эквивалентноgcc -m32 -nostartfiles, Зачем вам беспокоиться о 32-битной виртуальной машине? Простоgcc -m32, или жеas а такжеld с правильными аргументами в вашей системе Ubuntu, как я объясняю в этом ответе, который я связал. Выполнение 32-битного кода на 64-битном ядре работает безупречно, а мультибиблиотечные пакеты Ubuntu включают все необходимые 32-битные библиотеки.

Ваш ответ на вопрос