Подтвердить что ты не робот

Что внутри стека?

Если я запустил программу, просто как

#include <stdio.h>
int main(int argc, char *argv[], char *env[]) {
  printf("My references are at %p, %p, %p\n", &argc, &argv, &env);
}

Мы видим, что эти области находятся в стеке. Но что еще есть? Если мы запустили цикл по всем значениям в Linux 3.5.3 (например, до segfault), мы увидим некоторые странные числа, а вид двух регионов, разделенных кучей нулей, возможно, чтобы попытаться предотвратить перезапись переменных среды случайно.

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

Как мы можем отличить конец каждого кадра, где находятся параметры, где канарейка, если компилятор добавил один, обратный адрес, статус CPU и т.д.

4b9b3361

Ответ 1

Содержимое стека в основном:

  • Независимо от того, какая ОС переходит к программе.
  • Рамки вызовов (также называемые кадрами стека, областями активации,...)

Что передает ОС в программу? Типичный * nix передаст среду, аргументы в программу, возможно, некоторую вспомогательную информацию и указатели на них, которые будут переданы в main().

В Linux вы увидите:

  • a NULL
  • имя файла для программы.
  • строки окружения
  • строки аргументов (включая argv[0])
  • заполнение с нулями
  • массив auxv, используемый для передачи информации из ядра в программу
  • указывает на строки среды, заканчивающиеся указателем NULL
  • указывает на строки аргументов, заканчивающиеся указателем NULL
  • argc

Затем ниже находятся кадры стека, которые содержат:

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

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

Ответ 2

Без какого-либо знания оверлея вы видите только биты или цифры. Хотя некоторые из регионов подвержены специфике машины, большое количество деталей довольно стандартное.

Если вы не переместились слишком далеко за пределы вложенной программы, вы, вероятно, смотрите на часть стека вызовов. С некоторыми обычно считающимися "небезопасными" C вы можете написать функции fun, которые получают доступ к функциональным переменным несколько "вызовов" выше, даже если эти переменные не были "переданы" функции, как написано в исходном коде.

Стол вызовов - это хорошее место для запуска, так как сторонние библиотеки должны быть вызваны программами, которые еще не написаны. Таким образом, он довольно стандартизирован.

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

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

PS. Вы можете считать эту память "стеком", но через некоторое время вы увидите, что на самом деле это просто большая панель доступной памяти, причем одна ее часть считается стеком...

Ответ 3

Содержимое стека будет меняться в зависимости от архитектуры ABI, компилятора и, возможно, различных параметров и параметров компилятора.

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

Помните также, что компилятору не нужно инициализировать стек и, конечно, не "очистить его", когда он закончит с ним, поэтому, когда он будет выделен процессу или потоку, он может содержать любое значение - даже при например, SDRAM не будет содержать какого-либо конкретного или прогнозируемого значения, если физический адрес RAM ранее использовался другим процессом с момента включения или даже ранее вызванной функции в том же процессе, содержимое будет иметь все, что осталось от этого процесса в этом. Так что просто смотреть на сырой стек не очень много говорит.

Обычно общий кадр стека может содержать адрес, с которым будет переключаться элемент управления, когда функция вернется, значения всех переданных параметров и значение всех автоматических локальных переменных в функции. Однако ARM ABI, например, передает первые четыре аргумента функции в регистры R0-R3 и сохраняет возвращаемое значение функции листа в регистре LR, поэтому во всех случаях это не так просто, как "типичная" реализация я предположили.

Ответ 4

Детали очень зависят от вашей среды. Операционная система обычно определяет ABI, но это фактически применяется только для системных вызовов.

Каждый язык (и каждый компилятор, даже если они скомпилируют один и тот же язык) на самом деле может делать некоторые вещи по-другому.

Однако существует какое-то общесистемное соглашение, по крайней мере, в смысле взаимодействия с динамически загружаемыми библиотеками.

Тем не менее, детали сильно меняются.

Очень простой "праймер" может быть http://kernelnewbies.org/ABI

Очень подробная и полная спецификация, на которую вы могли бы взглянуть, чтобы получить представление об уровне сложности и деталях, которые связаны с определением ABI, - "Бинарный интерфейс приложения V System Application Application AMD64" http://www.x86-64.org/documentation/abi.pdf