2016-08-09 9 views
3

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

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

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

.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?

+2

Почти полностью не по теме, если вы никогда [читайте это, не смотрите на него] (http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html) - это отлично читал. –

+0

Обратите внимание, что загрузчик ELF заботится только о * сегментах * исполняемого файла. Во многих случаях есть отображение 1: 1, например, раздел '.text' (после ссылки) является единственным в текстовом сегменте. Компилятор объединяет разделы, такие как '.rodata', в' .text'. Кроме того, «куча» на самом деле не вещь, которая существует и является скорее концепцией (выделения с mmap (MAP_ANONYMOUS) не смежны с «brk»).Я не уверен, считают ли люди BSS и статические данные частью кучи. Также не уверен, что Linux ставит исходный 'brk' сразу после BSS. –

ответ

2

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

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


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

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

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

Предположительно другие разделы в текстовом сегменте до раздела .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 00
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. BTW, в Linux по умолчанию для данных также могут быть исполняемыми.)

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


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. TL: DR: если вы переключитесь на .bss вручную, используйте .zero или .skip, а не .comm или .lcomm.


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

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

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

+0

Я создаю его с 'как break.S -o break.o && ld -dynamic-linker /lib/ld-linux.so.2 -o break break.o -lc' внутри виртуальной машины Debian 3.5 i386 (Host Ubuntu 15.10 64 бит). –

+0

@ saga.x: Да, это эквивалентно 'gcc -m32 -nostartfiles'. Зачем вам беспокоиться о 32-битной виртуальной машине? Просто 'gcc -m32', или' as' и 'ld' [с правильными аргументами] (http://stackoverflow.com/questions/36861903/assembling-32-bit-binaries-on-a-64-bit- system-gnu-toolchain/36901649 # 36901649) в вашей системе Ubuntu, как я объясняю в этом ответе, который я связал. Запуск 32-битного кода в 64-битном ядре работает безупречно, а в пакеты Ubuntu multilib входят все необходимые 32-битные библиотеки. –

+0

Хорошо, я установил пакет 'gcc-multilib' и построил его с' gcc -m32 -nostartfiles', он работает. Я также искал что-то об ASLR, и если я выполняю роль root 'sysctl -w kernel.randomize_va_space = 0', то адрес точки останова никогда не изменяется, он фиксируется на 0x804a001, который является тем же адресом ошибки сегментации, которую я получил. Я должен прочитать намного больше о том, как работает Linux, и управление памятью действительно лучше понимает эту тему, очень интересно, но я новичок в этом. Спасибо за ответ! –

 Смежные вопросы

  • Нет связанных вопросов^_^