Указывает ли [ebp * 2] сегмент DS или SS?

IDM сообщает, что операция памяти использует сегмент SS, если EBP используется в качестве базового регистра. В результате [ebp + esi] и [esi + ebp] ссылаются на сегменты SS и DS соответственно. См. Документ NASM: 3.3 Фактический адрес.

В том же разделе выше NASM упомянул, как сгенерировать более короткий машинный код, заменив [eax*2] на [eax+eax].

Однако NASM также генерирует [ebp + ebp] для [ebp*2] (т.е. без базового регистра).

Я подозреваю, что [ebp+ebp] ссылается на сегмент SS, а [ebp*2] ссылается на сегмент DS.

Я задал NASM этот вопрос. Они думают, что [ebp*2] и [ebp+ebp] - одно и то же, но для меня это не имеет смысла. Очевидно, [ebp+ebp] (ebp как базовый регистр) ссылается на сегмент SS. Если они одинаковы, [ebp*2 также должна ссылаться на SS. Это означает, что на SS ссылаются, пока ebp является базовым или индексным регистром, что, в свою очередь, означает, что и [ebp + esi], и [esi + ebp] ссылаются на сегменты SS, поэтому они должны быть одинаковыми.

Кто-нибудь знает, какой сегмент [ebp*2] использует?


person wildpie    schedule 08.04.2018    source источник
comment
Обратите внимание: если бы вы поддержали свое утверждение, согласно IDM, ссылкой так же, как вы это сделали с документами NASM, вы бы сами давно это поняли.   -  person Ben Voigt    schedule 08.04.2018
comment
Эта оптимизация NASM ([ebp*2] - ›[ebp+ebp]) предполагает плоскую модель памяти, в которой ss и ds эквивалентны, что имеет место во всех основных основных операционных системах x86. Это интересный угловой случай, потому что режим чистой [idx*2] адресации без регистра или 32-битной абсолютной базы также очень необычен (за исключением LEA для копирования и сдвига). Обычно люди используют настоящие указатели вместо того, чтобы имитировать адресную память по словам, масштабируя их на 2 или что-то еще, что вы делаете.   -  person Peter Cordes    schedule 09.04.2018
comment
Я задал этот вопрос NASM. Вы имеете в виду, что задавали разработчикам NASM? Или что вы собрали код с помощью NASM и / или разобрали с помощью ndisasm, чтобы увидеть, что думала сама программа? Поскольку информация, которую вы получили, была неправильной: [esi + ebp] использует ds. И если вы предполагаете, что ss и ds взаимозаменяемы, вы должны оптимизировать [ebp + esi] до [esi + ebp], чтобы избежать необходимости в disp8 = 0. (EBP как базовый регистр кодируется только с помощью disp8 или disp32; кодировка, которая будет означать EBP + на самом деле отсутствие смещения означает, что существует disp32 без базового регистра (но потенциально это индекс).   -  person Peter Cordes    schedule 09.04.2018
comment
Звучит как отличный повод предпочесть синтаксис AT&T.   -  person R.. GitHub STOP HELPING ICE    schedule 09.04.2018
comment
@PeterCordes: Первоначально он спросил на старом (несуществующем) форуме NASM, который был на Sourceforge sourceforge.net/p/nasm/discussion/167169/thread/18e79c06. У него возникла проблема с активацией электронной почты на nasm.us   -  person Michael Petch    schedule 09.04.2018
comment
@BenVoigt не совсем так, см. Мой комментарий к sep roland.   -  person wildpie    schedule 09.04.2018
comment
@PeterCordes, спасибо за ваши комментарии, очень хороший отзыв о плоской модели памяти, которую использует ОС x86. я делал упор на правильность ассемблера. Я пишу простой ассемблер, поэтому это предположение ко мне не относится, но это очень хороший момент.   -  person wildpie    schedule 09.04.2018
comment
@PeterCordes одно но, nasm можно использовать для написания кода для любых целей. не рекомендуется предполагать, что программа, которую он создает, всегда запускается под какой-либо операционной системой. с его помощью можно было записать загрузочную информацию и затем перейти в защищенный режим. nasm должен предлагать режим без этого предположения. Я снова запускаю nasm без опции -f elf32. это выбирает формат корзины. он по-прежнему оптимизирует [ebp * 2] в [ebp + ebp]. не уверен, что именно означает формат bin, но, по крайней мере, это не elf32, где nasm может разумно предположить, что код будет работать в системе unix.   -  person wildpie    schedule 09.04.2018
comment
В самом деле, любое предположение о плоской модели памяти должно быть необязательным. Это просто объясняет, почему на него не обратили внимания, поскольку NASM делает это даже для [symbol + ebp*2]. bin - это простой двоичный файл, не имеющий никаких сведений о том, что вы можете делать с результирующим машинным кодом. например использовать его как исполняемый файл .COM, загрузочный сектор или встраивать в другое место. (Режим по умолчанию для bin - bits 16, то есть 16-битный режим.)   -  person Peter Cordes    schedule 09.04.2018


Ответы (2)


В руководстве Intel показано, как на рисунке 3-11 речь идет о Offset = Base + (Index * Scale) + Displacement:

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

  • Регистр ESP не может использоваться как индексный регистр.
  • Когда регистр ESP или EBP используется как базовый, сегмент SS является сегментом по умолчанию. В всех остальных случаях сегмент DS является сегментом по умолчанию.

Это означает, что NASM ошибается, когда меняет [ebp*2] на [ebp+ebp] (чтобы избежать 32-битного смещения).

[ebp*2] использует DS, потому что ebp не используется в качестве базового
[ebp+ebp] использует SS, потому что один из ebp используется в качестве базового

Тогда будет лучше указать, что вы не хотите, чтобы такое поведение NASM было.
Пока авторы NASM не осознают свою ошибку, вы можете отключить это поведение (где EBP используется в качестве индекса), написав:

[NoSplit ebp*2]

person Sep Roland    schedule 08.04.2018
comment
Спасибо за подтверждение. Эта цитата фактически находится на следующей странице, которую я читал (Таблица 3-5 Правила выбора сегмента по умолчанию). Он не упоминает во всех остальных случаях часть. Если бы я прочитал еще одну страницу. Спасибо, - person wildpie; 09.04.2018
comment
@MichaelPetch принял, я довольно часто просматриваю stackoverflow, но это мой первый вопрос. - person wildpie; 09.04.2018
comment
@wildpie: вообще никаких проблем. Я заметил, что это был ваш первый вопрос, так что это было просто к вашему сведению. Спасибо, что нашли время принять ответ. - person Michael Petch; 09.04.2018
comment
Правильнее было бы сказать, что вам нужно отключить это поведение, а не то, что вам не нужна оптимизация, когда ss vs ds имеет значение, и вы используете ebp. В любом другом случае это не меняет поведения (другие регистры по-прежнему используют ds, а esp не может быть индексом, поэтому [esp*2] в любом случае не кодируется). - person Peter Cordes; 09.04.2018

Действительно, варианты оптимизации NASM непоследовательны, если предположить, что ss и ds взаимозаменяемы (т. Е. Плоская модель памяти) при разделении [ebp*2] на [ebp+ebp] для сохранения 3 байтов (disp32 против disp8), но не оптимизации [ebp + esi] в [esi + ebp], чтобы избежать ошибки disp8.

в руководстве NASM даже упоминается другой сегмент по умолчанию, что противоречит вывод, который вы сделали из неверной информации о [0 + ebp*2] и [0+ebp+ebp*1].)

EBP или ESP в качестве базового регистра подразумевают SS, в противном случае значение по умолчанию - DS. Когда в режиме адресации NASM используются два регистра, первый является базовым, если вы не написали [ebp*1 + esi], явно применяя масштабный коэффициент к первому. Индексный регистр никогда не подразумевает сегмент, что имеет смысл, если вы думаете о замысле проекта: индекс относительно сегмента: смещение, заданное базовым регистром или абсолютным disp32.

Как написано, [ebp*2] - это режим индексированной адресации, неявно требующий 4 байта нулей в качестве 32-битных смещений. Вы можете заставить NASM закодировать его таким образом с помощью [nosplit ebp*2].

em> может использовать 32-битные режимы адресации в 16-битном коде, чтобы воспользоваться преимуществами масштабных коэффициентов и более широкого выбора регистров, даже в чисто реальном режиме, а не в " нереальный "режим, который позволяет установите пределы сегмента достаточно высокими, чтобы можно было использовать смещения> 2 ^ 16.)

Все основные 32- и 64-разрядные ОС x86 используют плоскую модель памяти, где SS и DS являются взаимозаменяемыми, что делает эту оптимизацию безопасной в этих ОС, когда вы не делаете ничего странного. Сегментация иногда использовалась для создания неисполняемых стеков, прежде чем это поддерживалось таблицами страниц, но это все еще плоская модель памяти. (64-битный код фиксирует базовый / предел для CS / DS / ES / SS, поэтому такая оптимизация всегда безопасна, если только SS не является полностью непригодным для использования сегментом, например, возможно, защищенным от записи, если это возможно.)

Тем не менее, любое предположение о плоской модели памяти должно быть необязательным. Это ошибка в NASM и YASM. Им следует либо уважать разницу между SS и DS, либо в полной мере использовать преимущества плоской модели памяти, чтобы помочь программистам, которые не помнят, в каких режимах адресации требуются «скрытые» дополнительные байты, например, оптимизация [ebp+esi] без смещения в [esi+ebp]. Предпочтительно должна быть опция или директива, сообщающая ассемблеру, что он может предполагать, что SS и DS - это одно и то же.

Операнды LEA всегда могут воспользоваться преимуществом, потому что LEA работает только со смещенной частью адреса, поэтому сегменты не имеют значения. (И это будет наиболее распространенный вариант использования такого режима адресации, как [ebp*2] без смещения: использование этого адреса в качестве адреса памяти могло бы имитировать память с адресной памятью? Это просто странно, обычно в качестве одного из компонентов адреса есть реальный указатель. )


Основные сведения о 32/64-разрядных режимах адресации x86:

Помимо 64-битной относительной адресации RIP, 32/64-битные режимы адресации представляют собой любое подмножество disp0/8/32 + base_reg + idx_reg*1/2/4/8, где каждый из 3 терминов / компонентов является необязательным. Но требуется по крайней мере один из регистра disp32 или базового. (См. Также Ссылка на содержимое ячейки памяти. ( режимы адресации x86)).

[disp32=0 + ebp*2] (с disp32 = ноль) имеет сегмент по умолчанию = DS. Вы можете получить эту кодировку в NASM из [nosplit ebp*2], а адреса типа [ebp*4] не могут быть разделены.

[ebp + ebp + disp8=0] имеет сегмент по умолчанию = SS, потому что EBP используется как базовый регистр.

Кодировка, которая будет означать ebp без смещения, на самом деле означает disp32 без базового регистра, поэтому disp32 фактически является базой (подразумевая сегментный регистр DS, потому что база не EBP или ESP). Это случай с байтом SIB или без него, поэтому [ebp + ebp*1] все равно нужно закодировать с помощью disp8 = 0. В других регистрах такой проблемы нет, поэтому обычно при разделении сохраняется 4 байта вместо 3 для EBP. (За исключением r13, который использует ту же кодировку ModR / M, что и RBP, я полагаю, что части оборудования декодирования не требуется дополнительный бит из префикса REX.)

ESP не может быть индексным регистром, поэтому [esp*2] невозможно кодировать с разделением или без него. Таким образом, особый случай оптимизации NASM только влияет на EBP*2. (base = ESP - это escape-код для байта SIB, а index = ESP в байте SIB означает отсутствие индекса, что позволяет кодировать [esp + 12].)

Но, к сожалению, NASM / YASM разделяет EBP*2, даже если есть константа, которой все равно требуется disp32, например [symbol + ebp*2], где она не сохраняет никаких байтов и фактически снижает производительность LEA (но не загружает / сохраняет) на ЦП семейства Sandybridge. Трехкомпонентный lea eax, [symbol + ebp + ebp*1] медленнее, чем двухкомпонентный lea eax, [symbol + ebp*2]: более высокая задержка и пропускная способность 1 на такт вместо 2. Согласно http://agner.org/optimize/, они будут одинаково медленными на AMD Bulldozer / Ryzen, потому что масштабированный индекс делает «медленный LEA» даже с двумя компонентами.

IDK, если какие-либо старые процессоры лучше работают с немасштабируемым индексом и режимами трехкомпонентной адресации, для LEA или для фактических операндов памяти.


Поведение NASM и YASM:

 $ nasm -felf32 -g -Fdwarf foo.asm
 $ objdump -drwC -Mintel -S foo.o | sed 's/DWORD PTR//'
 # (edited to put the NASM source line's addressing mode onto the same line as the disassembler output, instead of separate lines)
00000000 <sym-0x2c>:
   0:   8b 04 2e                mov    eax, [esi+ebp*1]         ; [esi+ebp]
   3:   8b 44 35 00             mov    eax, [ebp+esi*1+0x0]     ; [ebp + esi]
   7:   8b 04 2e                mov    eax, [esi+ebp*1]         ; [ebp*1 + esi]
   a:   8b 44 2d 00             mov    eax, [ebp+ebp*1+0x0]     ; [ebp*2]
   e:   8b 04 6d 00 00 00 00    mov    eax, [ebp*2+0x0]         ; [nosplit ebp*2]
  15:   8b 45 00                mov    eax, [ebp+0x0]           ; [ebp*1]   ; "split" into base=ebp with no SIB byte
  18:   8b 04 2d 00 00 00 00    mov    eax, [ebp*1+0x0]         ; [nosplit ebp*1]
  1f:   8b 84 2d d2 04 00 00    mov    eax, [ebp+ebp*1+0x4d2]   ; [ebp*2 + 1234]   ; bad split for LEA, neutral on modern CPUs for load/store
  26:   8b 85 15 cd 5b 07       mov    eax, [ebp+0x75bcd15]     ; [ebp*1 + 123456789]
sym:       ; using a symbol reference instead of a numeric constant doesn't change anything
  2c:   8b 84 2d 2c 00 00 00    mov    eax, [ebp+ebp*1+0x2c]    2f: R_386_32    .text   ; [ebp*2 + sym]
  33:   8b 84 2d 2c 00 00 00    mov    eax, [ebp+ebp*1+0x2c]    36: R_386_32    .text   ; [sym + ebp*2]

YASM кодирует все эти случаи идентично NASM.

person Peter Cordes    schedule 10.04.2018