Как программа выглядит в памяти?

Как программа (например, C или C++) расположена в памяти компьютера? Я немного знаю о сегментах, переменных и т. Д., Но в целом у меня нет четкого понимания всей структуры.

Поскольку структура в памяти может отличаться, давайте предположим консольное приложение C++ в Windows.

Некоторые указатели на то, что я после конкретно:

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

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

7 ответов

Решение

Может быть, это то, что вы ищете:

http://en.wikipedia.org/wiki/Portable_Executable

Формат файла PE - это двоичная файловая структура двоичных файлов Windows (.exe, .dll и т. Д.). По сути, они отображаются в памяти, как это. Более подробно описано здесь с объяснением того, как вы сами можете взглянуть на двоичное представление загруженных библиотек в памяти:

http://msdn.microsoft.com/en-us/magazine/cc301805.aspx

Редактировать:

Теперь я понимаю, что вы хотите узнать, как исходный код связан с двоичным кодом в файле PE. Это огромное поле.

Во-первых, вы должны понять основы компьютерной архитектуры, которые потребуют изучения общих основ кода сборки. Подойдет любой курс "Введение в компьютерную архитектуру". Литература включает, например, "Джон Л. Хеннесси и Дэвид А. Паттерсон. Компьютерная архитектура: количественный подход" или "Эндрю Таненбаум, Структурированная компьютерная организация".

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

Как только вы это поймете, сравнительно легко собрать кусочки:

Объект C++ содержит код и данные, то есть переменные-члены. Класс

class SimpleClass {
     int m_nInteger;
     double m_fDouble;

     double SomeFunction() { return m_nInteger + m_fDouble; }
}

будет 4 + 8 последовательных байтов в памяти. Что происходит, когда вы делаете:

SimpleClass c1;
c1.m_nInteger = 1;
c1.m_fDouble = 5.0;
c1.SomeFunction();

Сначала в стеке создается объект c1, т. Е. Указатель стека esp уменьшается на 12 байт, чтобы освободить место. Затем константа "1" записывается в адрес памяти esp-12, а константа "5.0" записывается в esp-8.

Затем мы вызываем функцию, которая означает две вещи.

  1. Компьютер должен загрузить часть двоичного PE-файла в память, которая содержит функцию SomeFunction(). SomeFunction будет находиться в памяти только один раз, независимо от того, сколько экземпляров SimpleClass вы создадите.

  2. Компьютер должен выполнить функцию SomeFunction (). Это означает несколько вещей:

    1. Вызов функции также подразумевает передачу всех параметров, часто это делается в стеке. SomeFunction имеет один (!) Параметр - указатель this, т. Е. Указатель на адрес памяти в стеке, в который мы только что записали значения "1" и "5.0".
    2. Сохраните текущее состояние программы, то есть текущий адрес инструкции, который является адресом кода, который будет выполнен, если SomeFunction вернет. Вызов функции означает помещение адреса возврата в стек и установку указателя инструкции (регистр eip) на адрес функции SomeFunction.
    3. Внутри функции SomeFunction старый стек сохраняется путем сохранения старого базового указателя (ebp) в стеке (push ebp) и назначения указателя стека новым базовым указателем (movebp, esp).
    4. Выполняется действительный двоичный код SomeFunction, который вызывает машинную инструкцию, которая преобразует m_nInteger в double и добавляет его в m_fDouble. m_nInteger и m_fDouble находятся в стеке, в ebp - x байтов.
    5. Результат сложения сохраняется в регистре и функция возвращает. Это означает, что стек отбрасывается, что означает, что указатель стека возвращается к базовому указателю. Базовый указатель устанавливается обратно (следующее значение в стеке), а затем указатель инструкции устанавливается на адрес возврата (снова следующее значение в стеке). Теперь мы вернулись в исходное состояние, но в некоторых регистрах скрывается результат SomeFunction ().

Я предлагаю вам создать такой простой пример и пройти разборку. В отладочной сборке код будет легко понять, и Visual Studio отображает имена переменных в представлении дизассемблирования. Посмотрите, что делают регистры esp, ebp и eip, где в памяти находится ваш объект, где находится код и т. Д.

Какой огромный вопрос!

Сначала вы хотите узнать о виртуальной памяти. Без этого больше ничего не будет иметь смысла. Короче говоря, указатели C/C++ не являются адресами физической памяти. Указатели являются виртуальными адресами. Существует специальная функция ЦП (MMU, блок управления памятью), которая прозрачно отображает их в физическую память. Только операционная система может настраивать MMU.

Это обеспечивает безопасность (нет значения указателя C/C++, которое вы можете сделать так, чтобы оно указывало на виртуальное адресное пространство другого процесса, если этот процесс не намеренно делится с вами памятью), и позволяет ОС делать некоторые действительно волшебные вещи, которые мы теперь принимаем как должное (например, прозрачно поменять часть памяти процесса на диск, а затем прозрачно загрузить ее обратно, когда процесс попытается ее использовать).

Адресное пространство процесса (или виртуальное адресное пространство, или адресуемая память) содержит:

  • огромная область памяти, зарезервированная для ядра Windows, к которой процессу нельзя прикоснуться;

  • области виртуальной памяти, которые "не отображены", то есть там ничего не загружено, этим адресам не назначена физическая память, и процесс завершится сбоем, если попытается получить к ним доступ;

  • разделяет различные модули (файлы EXE и DLL), которые были загружены (каждый из них содержит машинный код, строковые константы и другие данные); а также

  • любую другую память, выделенную процессом из системы.

Теперь обычно процесс позволяет библиотеке времени выполнения C или библиотекам Win32 выполнять большую часть супернизкоуровневого управления памятью, которое включает в себя настройку:

  • стек (для каждого потока), где хранятся локальные переменные и аргументы функций и возвращаемые значения; а также

  • куча, где выделяется память, если процесс вызывает malloc или делает new X,

Чтобы узнать больше о стеке, прочитайте о соглашениях о вызовах. Подробнее о структуре кучи читайте о реализациях malloc. В общем, стек на самом деле является стеком, структурой данных "первым пришел - первым вышел", содержащей аргументы, локальные переменные и случайный временный результат, и не намного. Поскольку программе легко писать прямо за концом стека (распространенная ошибка C/C++, после которой этот сайт называется), системные библиотеки обычно следят за тем, чтобы рядом со стеком находилась несопоставленная страница. Это приводит к мгновенному аварийному завершению процесса при возникновении такой ошибки, поэтому отладку намного проще (и процесс убивается до того, как он сможет нанести еще больший ущерб).

Куча на самом деле не куча в смысле структуры данных. Это структура данных, поддерживаемая библиотекой CRT или Win32, которая берет страницы памяти из операционной системы и распределяет их, когда процесс запрашивает небольшие объемы памяти через malloc и друзья. (Обратите внимание, что ОС не управляет этим на микроуровне; процесс может в значительной степени управлять своим адресным пространством, как ему угодно, если ему не нравится то, как это делает CRT.)

Процесс также может запрашивать страницы непосредственно из операционной системы, используя такой API, как VirtualAlloc или же MapViewOfFile,

Это еще не все, но мне лучше остановиться!

Еще одна хорошая иллюстрация http://www.cs.uleth.ca/~holzmann/C/system/memorylayout.pdf

Для понимания структуры фрейма стека вы можете обратиться к http://en.wikipedia.org/wiki/Call_stack

Он дает вам информацию о структуре стека вызовов, как локальные, глобальные и обратные адреса хранятся в стеке вызовов.

На самом деле - вы не продвинетесь далеко в этом вопросе хотя бы с небольшим количеством знаний в Ассемблере. Я бы порекомендовал реверсивный (учебный) сайт, например, OpenRCE.org.

Книга Стивенса "Расширенное программирование в Unix" содержит несколько страниц с точным ответом, если вы можете получить его. Конечно, вы должны владеть книгой.

Это может быть не самая точная информация, но MS Press предоставляет некоторые примеры глав книги " Внутри Microsoft® Windows® 2000", третье издание, содержащие информацию о процессах и их создании, а также изображения некоторых важных структур данных.

Я также наткнулся на этот PDF, который суммирует некоторые из приведенной выше информации в хорошем графике.

Но вся предоставленная информация - больше с точки зрения ОС, а не слишком подробно о аспектах приложения.

Другие вопросы по тегам