[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] Сегмент СС и [ebp*2] Сегмент DS.

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

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

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]

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

в руководстве 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],

Возможно, NASM и YASM упустили этот угловой случай, потому что модели с плоской памятью почти универсальны вне 16-битного кода. (И 16-битные режимы адресации отличаются и не поддерживают масштабные коэффициенты. Хотя вы можете использовать 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 терминов / компонентов является необязательным.Но требуется хотя бы один из дисплеев или базовый регистр. (См. Также Ссылка на содержимое ячейки памяти. (Режимы адресации x86)).

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

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

Кодировка, которая будет означатьebpбез смещения фактически означает disp32 без базы reg, поэтому 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 - это управляющий код для байта SIB, а index=ESP в байте SIB означает отсутствие индекса, что позволяет вам кодировать[esp + 12].)

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

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.

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