инициализированный сегмент данных в двоичных файлах C, работающих под Windows - PullRequest
3 голосов
/ 27 августа 2010

Я давно пытаюсь понять, как обрабатывается память программ под ОС (я использую Windows, но, думаю, в Linux это будет так же или очень близко).

Пока чтоЯ знаю (в основном благодаря вам, пользователи stackoverflow), что локальные переменные хранятся в стеке.Теперь я, наконец, понимаю, почему.Итак, все в порядке.

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

Итак, моя идея заключается в том, что глобальныйПеременные находятся в конце программного кода.После последней инструкции.Почему я думаю, что так может быть?Кроме того, вам не нужно тратить лишнюю память и процессорное время.Потому что переменные и их значения по умолчанию будут скопированы ОС в оперативную память при выполнении.

Почему я думаю, что это возможно?Потому что, если я не ошибаюсь, в современных операционных системах x86 каждая программа получает свое собственное адресное пространство, начиная с 0. Таким образом, скомпилированный очень легко знает адрес глобальной переменной.потому что он знает длину программы, поэтому он может вычислить свою позицию в своем адресном пространстве.

Почему я думаю, что это может быть неправильно?Поскольку я уже думал, почему локальные переменные создаются в стеке таким же образом.И когда у вас есть некоторые подпрограммы в формате ELF, у вас есть предварительно скомпилированные подпрограммы только с неразрешенными адресами для переменных.

Также в одной статье я читал, что выделение памяти с помощью malloc расширяет кучу.И потому, что я использую кучу как пробел после кода программы, возникнет ошибка, потому что она вырастет в стек.В противном случае стек должен был бы находиться в конце адресного пространства процесса, но это было бы ужасной тратой памяти.

Я попытался описать свою точку зрения настолько, насколько мог, поэтому я надеюсь, что вы понимаетегде я сделал несколько ошибок, и помогите мне пополнить знания, которые мне не хватает.Спасибо.

Ответы [ 2 ]

5 голосов
/ 27 августа 2010

Использование памяти программой не зависит от языка, на котором написана эта программа. Вы можете написать код, который использует тонну памяти в C #, вы можете сделать то же самое в C.

Тем не менее, я постараюсь ответить на некоторые из ваших вопросов:

как хранятся и обрабатываются глобальные переменные

Им даны адреса памяти. Когда вы используете глобальный, компилятор просто использует этот известный адрес. (Что, ты ожидал сложного ответа?)

что глобальные переменные находятся в конце программного кода

На некоторых архитектурах это может иметь место, но это не обязательно. В Windows (с использованием формата файла Portable Executable) они никак не связаны и могут быть сопоставлены с совершенно разными произвольными местоположениями. На самом деле, скорее всего, это не относится к более поздним архитектурам, которые препятствуют размещению кода там, где находятся данные (в целях безопасности - вы не хотите, чтобы переполнение буфера позволяло перезаписывать код вашей программы)

если я не ошибаюсь, в современных операционных системах x86 каждая программа получает свое собственное адресное пространство, начиная с 0

Вы не ошибаетесь в теории, но вы ошибаетесь на самом деле. Даже если бы линкеры действительно могли это сделать, мало кто бы это сделал, просто потому что 0 используется как константа null. Однако обычно проблема заключается в том, что существуют динамические библиотеки или другие элементы, которые потребляют части адресного пространства вашего процесса, задолго до того, как ваш код будет фактически загружен. (Например, ссылка на файл, в котором находится ваш код, или блок памяти, содержащий командную строку, переданную вашей программе)

Я прочитал, что выделение памяти с помощью malloc увеличивает кучу

Ну, вы предполагаете, что есть только одна куча. По крайней мере, в Windows каждая DLL обычно имеет свою собственную кучу, и вы можете создавать кучи на лету / по желанию. Классические объяснения кучи в курсах информатики обычно предполагают систему, в которой нет базовой операционной системы или виртуальной памяти в игре.


Вы путаете память с адресным пространством здесь для современных процессоров. Адресные местоположения вещей часто имеют мало общего с тем, где они физически хранятся. Статья в Википедии о Виртуальная память может сделать ваши вещи более понятными. Удачи!


EDIT:

exe-файл PE на самом деле содержит информацию о глобальных переменных, которую можно отличить от других данных

Не совсем. Формат файла PE имеет раздел, в котором хранятся статические данные, и эта область файла отображается в памяти. Код знает, где находится этот большой кусок, где находятся конкретные глобалы, которые вы ищете.

То, что Os фактически отображает их, скажем, «лучшее» доступное пространство

Современные процессоры используют модель с плоской памятью. Так же легко получить доступ к любому адресу, как и к любому другому.

Я всегда думал, что скомпилированный код больше не изменяется ОС во время выполнения

Это не так (по большей части причина, по которой это может измениться, - целая банка червей самих по себе). Чтобы получить доступ к глобальному, сам код должен знать базовый адрес 1055 *, по которому он загружен. Он может вычислить, где блок данных файла PE загружается из этого, по большей части. Тем не менее, компиляторы могут размещать глобальные переменные практически везде; тот факт, что в спецификации PE есть место для инициализированных данных, не означает, что компилятор должен его использовать (например, MingGW не использует эту область).

Во-первых, содержит ли .exe информацию о необходимом размере стека?

Да, есть настройки, управляющие как зарезервированным размером стека, так и фиксированным размером стека. Поскольку переполнение стека можно безопасно обрабатывать в Windows, обычно размер стека составляет всего 1 МБ; на * nix машинах обычно 8 МБ или больше.

И ограничен ли размер стека?

Не настолько, насколько я знаю.Это сказал;Есть, конечно, практические ограничения.Прежде всего, адресное пространство, зарезервированное для стека, не будет использоваться ни для чего, кроме стека.Существуют также большие части адресного пространства, которые зарезервированы ядром для различных целей;не говоря уже о реальном коде и данных, с которыми работает ваша программа.Если вы используете более 1 МБ стека, вам следует подумать об использовании выделенного кучи стека для ваших данных и переключиться на итеративное решение или серьезно переосмыслить работу вашей программы.1 МБ стека - это намного больше, чем обычно используется.

И, во-вторых, есть ли какая-либо статья, о которой вы сейчас пишете, которая содержит эту информацию и / или информацию о том, что такое контент в формате PE, как это происходит?закодирован, чтобы его можно было увидеть при разборке .exe файла

Вы можете прочитать спецификацию PE: http://www.microsoft.com/whdc/system/platform/firmware/pecoff.mspx


В современных системах вы можете по существу забыть о точном знании, где кодсегменты и данные расположены как физически, так и виртуально.Процессор не заботится и не применяет ничего подобного, поэтому нет причин, по которым любая операционная система или программа вынуждена использовать какую-либо организацию памяти.В частности, концепция кучи в Windows сильно отличается от того, что обычно преподается на курсах информатики.В Windows (и других современных ОС) куча - это не что иное, как куча памяти, выделенная ОС.Местоположение этой памяти, однако, полностью изменчиво.Запросите ОС для одного блока, вы можете получить его по адресу 0x00005556, а следующий блок - по 0xFFFF890.Нет никаких причин для различия, потому что процессор под ним просто не заботится.

0 голосов
/ 27 августа 2010

В типичной современной системе виртуальной памяти адресное пространство программы обычно состоит из следующих разделов:

  • Код: помечен как только для чтения. Обычно начинается не с 0, а с более высокого адреса. Вам ничего не нужно по адресу 0 или около него, подумайте о нулевых указателях.
  • Данные только для чтения: обычно сразу после кода, поскольку они доступны только для чтения.
  • Чтение / запись данных: обычно после роды, но начинается с новой страницы, так как она должна быть прочитана / записана.
  • BSS (унифицированные статические переменные): обычно после данных. Содержимое этого раздела не берется из исполняемого файла, исполняемый файл просто сообщает размер, а загрузчик инициализирует его нулями.
  • Куча: обычно после bss в Unix можно развернуть вызов brk () / sbrk ().
  • Стек: обычно начинается в некоторой области памяти, далеко от кучи, и растет вниз.

С точки зрения управления памятью они сгруппированы в код (code + rodata), данные (data + bss + heap) и сегменты стека.

Динамические библиотеки имеют свой собственный код и сегменты данных, и есть другие разделы, используемые динамическим компоновщиком (GOT, PLT, ...), отладчики, ...

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