Префикс сегмента при использовании указателей в качестве параметров функции - PullRequest
2 голосов
/ 01 февраля 2011

У меня вопрос ассемблера / c. Я только что прочитал о префиксах сегментов, например, ds: varX и так далее. Префикс важен для расчета логического адреса. Я также читал, что по умолчанию это "ds", и как только вы используете регистр ebp для вычисления адреса, используется "ss". Для кода "cs" по умолчанию. Это все имеет смысл. Теперь у меня есть следующее в c:

int x; // some static var in ds

void test(int *p){
...
*p =5;

}

... main(){

test(&x);
//now x is 5
}

Если вы сейчас думаете о реализации тест-функции ... вы получите указатель на x в стеке. Если вы хотите разыменовать указатель, вы сначала получите значение указателя (адрес x) из стека и сохраните его, например, в eax. Затем вы можете разыменовать eax, чтобы изменить значение x. Но как c-компилятор узнает, ссылается ли данный указатель (адрес) на память в стеке (например, если я вызываю test из другой функции и выдвигаю адрес локальной переменной в качестве параметра для test) или сегмент данных? Как рассчитывается полный логический адрес? Функция не может знать, к какому сегменту относится данное смещение адреса ..?!

Ответы [ 6 ]

3 голосов
/ 01 февраля 2011

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

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

Для начинающих, по понятным причинам, не имеет значения , какой регистр сегмента вы используете, пока регистр сегмента содержит правильное значение.Например, если регистры DS и ES содержат одно и то же значение внутри, то DS:<offset> будет указывать на то же место в памяти, что и ES:<offset>.

В так называемой «крошечной» модели памяти, например, все регистры сегментов содержали одно и то же значение, т.е. все - код, данные, стек - помещалось бы в одном сегменте (которыйименно поэтому он назывался "крошечный").В этой модели памяти каждый указатель был просто смещением в этом сегменте, и, конечно, просто не имело значения, какой регистр сегментов использовать с этим смещением.

В «больших» моделях памяти вы могли иметь отдельные сегментыдля кода (CS), стека (SS) и данных (DS).Но в таких моделях памяти указатель обычно содержит и смещение и сегментную часть адреса внутри него.В вашем примере указатель p на самом деле будет двухкомпонентным объектом, содержащим одновременно значение сегмента и значение смещения.Чтобы разыменовать такой указатель, компилятор сгенерирует код, который будет считывать значения как сегмента, так и смещения из p и использовать их оба.Например, значение сегмента будет считано в регистр ES, а значение смещения будет считано в регистр si.Затем код получит доступ к ES:[di], чтобы прочитать значение *p.

Существуют также «промежуточные» модели памяти, когда код будет храниться в одном сегменте (CS), тогда как данные и стек будут обахранятся в другом сегменте, поэтому DS и SS будут содержать одно и то же значение.Очевидно, что на этой платформе не было необходимости различать DS и SS.

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

1 голос
/ 01 февраля 2011

На машине с моделью сегментированной памяти реализация C должна выполнить одно из следующих действий, чтобы соответствовать:

  • Сохранить полный адрес (с сегментом) в каждом указателе, ИЛИ
  • Убедитесь, что все адреса стека, которые будут использоваться для переменных, адреса которых взяты, могут быть доступны через сегмент данных, либо по одному и тому же относительному адресу, либо через некоторое магическое смещение, которое компилятор может применять при получении адреса локальных переменных, ИЛИ
  • Не использовать стек для локальных переменных, адреса которых взяты, и выполнять скрытый malloc / free при каждом входе / выходе функции (со специальной обработкой для longjmp!).

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

1 голос
/ 01 февраля 2011

АндрейT описал то, что произошло в дни DOS. В наши дни современные операционные системы используют так называемую модель плоской памяти (или, скорее, нечто очень похожее), в которой все сегменты (защищенный режим) настроены так, что они могут получить доступ ко всему адресному пространству (то есть: они имеют базу 0 и предел = все адресное пространство).

0 голосов
/ 01 февраля 2011

В x86 прямое использование стека будет использовать сегмент стека, но косвенное использование рассматривает его как сегмент данных. Это можно увидеть, если разобрать разыменование указателя и записать в указатель секции стека. В x86 cs, ss и ds обрабатываются практически одинаково (по крайней мере, в неядерных режимах) из-за линейной адресации. в справочниках Intel также должен быть раздел по сегментной адресации

0 голосов
/ 01 февраля 2011

Поскольку вы перемещаете адрес в eax до разыменования, по умолчанию используется сегмент ds. Однако, как отметил Николай, в коде уровня пользователя все сегменты, вероятно, указывают на один и тот же адрес.

0 голосов
/ 01 февраля 2011

Сегментация - это устаревший артефакт 16-разрядного процессора Intel 8086.В действительности вы, вероятно, работаете в виртуальной памяти , где все является просто линейным адресом.Скомпилируйте с флагом -S и посмотрите получившуюся сборку.

...