При использовании мнемоники MOV для загрузки / копирования строки в регистр памяти в MASM символы сохраняются в обратном порядке?

Я хочу знать, приводит ли использование инструкции MOV для копирования строки в регистр к сохранению строки в обратном порядке. Я узнал, что когда MASM сохраняет строку в переменной, определенной как слово или выше (dw и более крупные размеры), строка сохраняется в обратном порядке. То же самое происходит, когда я копирую строку в регистр?

На основании этих вопросов (об инструкции SCAS и о присвоение строк и символов переменным в MASM 32) Я предположил следующее:

  1. Когда MASM загружает строку в переменную, он загружает ее в обратном порядке, то есть последний символ в строке сохраняется в самом нижнем адресе памяти (начале) строковой переменной. Это означает присвоение переменной str следующего вида: str dd "abc" заставляет MASM сохранять строки как «cba», что означает, что «c» находится в самом нижнем адресе памяти.
  2. При определении переменной как str db "abc" MASM рассматривает str как массив символов. Пытаясь сопоставить индекс массива с адресом памяти str, MASM сохранит "a" по наименьшему адресу памяти str.
  3. По умолчанию инструкции SCAS и MOVS выполняются с начального (младшего) адреса целевой строки, то есть строки, хранящейся в регистре EDI. Они не «всплывают» и не применяют правило «последний пришел - первым ушел» к адресам памяти, с которыми они работают, перед выполнением.
  4. MASM всегда обрабатывает символьные массивы и строки в регистрах памяти одинаково. Перемещение массива символов 'a', 'b', 'c' в EAX аналогично перемещению "abc" в EAX.

Когда я передаю байтовый массив arLetters с символами 'a', 'b' и 'c' в переменную двойного слова strLetters с помощью MOVSD, я считаю, что буквы копируются в strLetters в обратном порядке, т. Е. Сохраняются как "cba". Когда я использую mov eax, "abc", буквы также сохраняются в обратном порядке?

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

.data?
strLetters dd ?,0

.data
arLetters db "abcd"

.code

start:
mov ecx, 4
lea esi, arLetters
lea edi, strLetters
movsd
;This stores the string "dcba" into strLetters.

mov ecx, 4
lea edi, strLetters
mov eax, "dcba" 
repnz scasd
jz close
jmp printer
;strLetters is not popped as "abcd" and is compared as "dcba".

printer:
print "No match.",13,10,0
jmp close

close:
push 0
call ExitProcess

end start

Я ожидаю, что строка "dcba" будет сохранена в EAX "как есть" - с 'd' в самом нижнем адресе памяти EAX - поскольку MASM обрабатывает перемещение строк в регистры, отличные от присвоения строк переменным. MASM скопировал 'a', 'b', 'c' 'd' "в strLetters как" dcba ", чтобы гарантировать, что если strLetters выскочил, строка будет отправлена ​​/ выпущена в правильном порядке (" abcd "). Если REP MOVSB инструкция использовалась вместо MOVSD, strLetters будет содержать "abcd" и будет выдаваться / выдаваться как "dcba". Однако, поскольку использовалось MOVSD и инструкции SCAS или MOVS не выдавали строки перед выполнением, приведенный выше код должен установить нулевой флаг, верно?


person Joachim Rives    schedule 09.08.2019    source источник
comment
Когда вы сохраняете строку символов dcba в регистре, она сохраняется в обратном порядке, потому что MASM предполагает, что вы хотите, чтобы она сохранялась в формате с прямым порядком байтов, где последний символ сохраняется первым, а 1-й символ сохраняется последним. Таким же образом, если вы загрузите 0x12345678 в EAX, а затем сохраните его в памяти, байты в памяти появятся в порядке 0x78, 0x56, 0x34, 0x12. Это поведение зависит от ассемблера, но MASM - это тот, который меняет местами байты.   -  person Michael Petch    schedule 09.08.2019
comment
Хранение строки с помощью директивы db - это особый случай, когда строка рассматривается как последовательность байтов, хранящихся в том же порядке.   -  person Michael Petch    schedule 09.08.2019
comment
Если бы вы поместили строку в память с помощью dd "dcba", она была бы сохранена как 'a', 'b', 'c', 'd'. Специальная обработка строки происходит только тогда, когда целевой размер составляет один байт (например, db). EAX - это 32-битный регистр (а не размер байта), поэтому он сохраняет его в обратном порядке.   -  person Michael Petch    schedule 09.08.2019
comment
Основное правило MASM: если целью строки не является что-то размером с байт, символы сохраняются в обратном порядке.   -  person Michael Petch    schedule 09.08.2019
comment
Еще одна особенность, которая может быть интересна, - что происходит, когда вы сохраняете строку в адресате, размер которого превышает один байт, но строка меньше целевого размера? Например - что делает mov eax, "12"? Что хранится в 2 байтах, которые не используются? EAX будет содержать байты в обратном порядке, как «2», «1», но 2 неиспользуемых байта заполнены значением 0 (NUL) в конце. Если вы сохранили EAX в памяти, вы обнаружите, что на выходе получается байт '2', '1', 0, 0   -  person Michael Petch    schedule 09.08.2019
comment
Итак, это означает, что инструкция MOVSD помещает значения abcd в EAX так же, как присваивает abcd переменной, состоящей из двух слов, верно? Я предполагаю, что mov ebx, DWORD PTR [db_str] передает байты так же, как MOVSD.   -  person Joachim Rives    schedule 12.08.2019


Ответы (2)


Не используйте строки в контекстах, где MASM ожидает 16-битное или большее целое число. MASM преобразует их в целые числа таким образом, что меняет порядок символов при хранении в памяти. Поскольку это сбивает с толку, лучше избегать этого и использовать только строки с директивой DB, которая работает должным образом. Не используйте строки, содержащие больше символов, в качестве непосредственных значений.

Память имеет порядок байтов, регистры - нет.

У регистров нет адресов, и бессмысленно говорить о порядке байтов в регистре. На 32-битном процессоре x86 регистры общего назначения, такие как EAX, содержат 32-битные целочисленные значения. Вы можете концептуально разделить 32-битное значение на 4 байта, но пока оно находится в регистре, нет значимого порядка байтов.

Только когда в памяти существуют 32-битные значения, 4 байта, из которых они состоят, имеют адреса и, следовательно, порядок. Поскольку процессоры x86 используют порядок байтов с прямым порядком байтов, что означает наименее значимый байт из 4 bytes - это первый байт. Самая значимая часть становится последним байтом. Всякий раз, когда x86 загружает или сохраняет 16-битное или большее значение в или из памяти, он использует порядок байтов с прямым порядком байтов. (Исключением является инструкция MOVBE, которая специально использует порядок байтов с прямым порядком байтов при загрузке и сохранении значений.)

Итак, рассмотрим эту программу:

    .MODEL flat

    .DATA
db_str  DB  "abcd"
dd_str  DD  "abcd"
num DD  1684234849

    .CODE
_start: 
    mov eax, "abcd"
    mov ebx, DWORD PTR [db_str]
    mov ecx, DWORD PTR [dd_str]
    mov edx, 1684234849
    mov esi, [num]
    int 3

    END _start

После сборки и компоновки он преобразуется в последовательность байтов примерно так:

.text section:
  00401000: B8 64 63 62 61 8B 1D 00 30 40 00 8B 0D 04 30 40  ,[email protected]@
  00401010: 00 BA 61 62 63 64 8B 35 08 30 40 00 CC           .º[email protected]
  ...
.data section:
  00403000: 61 62 63 64 64 63 62 61 61 62 63 64              abcddcbaabcd

(В Windows раздел .data обычно размещается в памяти после раздела .text.)

DB и DD по-разному обрабатывают строки

Итак, мы видим, что директивы DB и DD, помеченные db_str и dd_str, генерируют две разные последовательности байтов для одной и той же строки "abcd". В первом случае MASM генерирует последовательность байтов, которую мы ожидали, 61h, 62h, 63h и 64h, значения ASCII для a, b, c и d соответственно. Для dd_str хотя последовательность байтов обратная. Это связано с тем, что в директиве DD в качестве операндов используются 32-разрядные целые числа, поэтому строка должна быть преобразована в 32-разрядное значение, и MASM в конечном итоге меняет порядок символов в строке, когда результат преобразования сохраняется в памяти.

В памяти строки и числа - это просто байты

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

(Менее очевидно то, как десятичное значение 1684234849 было преобразовано в ту же последовательность байтов, что и сгенерированная директивой DB. Это уже 32-битное значение, его просто нужно преобразовать в последовательность байтов с помощью MASM. Неудивительно, что ассемблер это делает поэтому используется тот же порядок байтов с прямым порядком байтов, который использует ЦП. Это означает, что первый байт является наименее значимой частью 1684234849, которая имеет то же значение, что и буква ASCII a (1684234849% 256 = 97 = 61h). Последний байт - это наиболее значимая часть числа, которая является значением ASCII d (1684234849/256/256/256 = 100 = 64h).

Немедленное обрабатывает строки так же, как DD

Посмотрев более внимательно на значения в разделе .text с помощью дизассемблера, мы можем увидеть, как последовательность байтов, хранящихся в нем, будет интерпретироваться как инструкции при выполнении ЦП:

  00401000: B8 64 63 62 61     mov         eax,61626364h
  00401005: 8B 1D 00 30 40 00  mov         ebx,dword ptr ds:[00403000h]
  0040100B: 8B 0D 04 30 40 00  mov         ecx,dword ptr ds:[00403004h]
  00401011: BA 61 62 63 64     mov         edx,64636261h
  00401016: 8B 35 08 30 40 00  mov         esi,dword ptr ds:[00403008h]
  0040101C: CC                 int         3

Здесь мы видим, что MASM сохранил байты, составляющие непосредственное значение в инструкции mov eax, "abcd", в том же порядке, что и в директиве dd_str DD. Первый байт непосредственной части инструкции в памяти - 64h, значение ASCII d. Причина в том, что с 32-битным регистром назначения эта инструкция MOV использует 32-битное немедленное выполнение. Это означает, что MASM необходимо преобразовать строку в 32-битное целое число, и в итоге порядок байтов изменится на обратный, как это было с dd_str. MASM также обрабатывает десятичное число, переданное как непосредственное значение mov ecx, 1684234849, так же, как это было с директивой DD, в которой использовалось то же число. 32-битное значение было преобразовано в такое же представление с прямым порядком байтов.

В памяти инструкции тоже просто байты

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

Значения в регистрах отражают порядок памяти

Выполняя программу в отладчике и проверяя регистры после достижения инструкции точки останова (int 3), мы можем увидеть, что на самом деле оказалось в регистрах:

eax=61626364 ebx=64636261 ecx=61626364 edx=64636261 esi=64636261 edi=00000000
eip=0040101c esp=0018ff8c ebp=0018ff94 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
image00000000_00400000+0x101c:
0040101c cc              int     3

Теперь мы видим, что первая и третья инструкции загрузили другое значение, чем другие инструкции. Эти две инструкции включают случаи, когда MASM преобразовал строку в 32-битное значение и в конечном итоге изменил порядок символов в памяти. Дамп регистра подтверждает, что обратный порядок байтов в памяти в памяти приводит к загрузке разных значений в регистры.

Но на самом деле регистры не имеют порядка байтов

Теперь вы, возможно, смотрите на дамп регистра выше и думаете, что только EAX и ECX находятся в правильном порядке, со значением ASCII для a, 61h первым и и значением ASCII для d, 64h последним. Этот MASM, меняющий порядок строк в памяти, фактически заставил их загружаться в регистры в правильном порядке. Но, как я уже сказал, в регистрах нет порядка следования байтов. Число 61626364 - это то, как отладчик представляет значение при отображении его в виде последовательности символов, которые вы можете прочитать. Символы 61 идут первыми в представлении отладчика, потому что наша система нумерации помещает наиболее значимую часть числа слева, и мы читаем слева направо, так что это первая часть. Однако, как я уже говорил ранее, процессоры x86 имеют прямой порядок байтов, что означает, что наименее значимая часть идет первой в памяти. Это означает, что первый байт в памяти становится наименее значимой частью значения в регистре, которая отображается отладчиком как две крайние правые шестнадцатеричные цифры числа, потому что это наименее значимая часть числа в нашей системе счисления.

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

Простое копирование строк не изменит их порядок.

Надеюсь, к настоящему времени также должно быть ясно, что загрузка строки в регистр происходит только концептуально. Строка преобразуется ассемблером в последовательность байтов, которая при загрузке в 32-разрядный регистр обрабатывается в памяти как 32-разрядное целое число с прямым порядком байтов. Когда 32-битное значение в регистре сохраняется в памяти, 32-битное значение преобразуется в последовательность байтов, которые представляют значение в формате с прямым порядком байтов. Для процессора ваша строка - это просто 32-битное целое число, которое оно загружает и сохраняет в памяти и из памяти.

Это означает, что если значение, загруженное в EAX в программе-примере, сохраняется в памяти с чем-то вроде mov [mem], eax, тогда 4 байта, хранящиеся в mem, будут в том же порядке, что и в байтах, составляющих непосредственное значение mov eax, "abcd". То есть в том же обратном порядке, 64h, 63h, 62h, 61h, в котором MASM помещает их в байты, составляющие немедленные.

Но почему? Я не знаю, просто не делай этого

Теперь, что касается того, почему MASM меняет порядок строк при преобразовании их в 32-битные целые числа, я не знаю, но мораль здесь не в том, чтобы использовать строки как непосредственные или любой другой контекст, где их нужно преобразовать в целые числа. Ассемблеры непоследовательны в том, как они конвертируют строковые литералы в целые числа. (Аналогичная проблема возникает в том, как компиляторы C преобразуют символьные литералы, такие как 'abcd', в целые числа.)

SCASD и MOVSD не особенные

С инструкциями SCASD или MOVSD ничего особенного не происходит. SCASD обрабатывает четыре байта, на которые указывает EDI, как 32-битное значение с прямым порядком байтов, загружает его в безымянный временный регистр, сравнивает временный регистр с EAX, а затем добавляет или вычитает 4 из EDI в зависимости от флага DF. MOVSD загружает 32-битное значение из памяти, на которую указывает ESI, в безымянный временный регистр, сохраняет во временном регистре 32-битную ячейку памяти, на которую указывает EDI, а затем обновляет ESI и EDI в соответствии с флагом DF. (Порядок байтов не имеет значения для MOVSD, поскольку байты никогда не используются как 32-битные значения, но порядок не изменяется.)

Я бы не стал думать о SCASD или MOVSD как о FIFO или LIFO, потому что в конечном итоге это зависит от того, как вы их используете. MOVSD можно так же легко использовать как часть реализации очереди FIFO, как и стек LIFO. (Сравните это с PUSH и POP, которые теоретически могут независимо использоваться как часть реализации структуры данных FIFO или LIFO, но вместе могут использоваться только для реализации стека LIFO.)

person Ross Ridge    schedule 09.08.2019
comment
MASM меняет порядок байтов в символьной константе, потому что она устарела. Самый новый ассемблер этого не делает, см. euroassembler.eu/eadoc/#CharNumbers - person vitsoft; 10.08.2019
comment
Регистры имеют битовый порядок. shl edx, 1 сдвигает биты из верхней части одного байта в нижнюю часть другого байта, поэтому мы действительно знаем логическое расположение бит / байтов в регистре. (младший бит справа). Но да, у них нет порядка байтов или какой-либо связи с порядком байтов в памяти. - person Peter Cordes; 10.08.2019
comment
@vitsoft: согласился. В NASM такие вещи, как push "/bin", работают должным образом, производя байты в памяти в порядке их исходного кода (Символьные константы NASM). Он просто помещает исходные байты в прямой порядок байтов (с выравниванием по правому краю). Очевидно, это кажется более полезным, поскольку MASM изначально сделал другой выбор и не мог измениться без нарушения совместимости с существующим исходным кодом MASM. Так что да, +1, этот ответ документирует только MASM, а не другие ассемблеры, и хорошо с этим справляется. Если вам нужны удобные строковые константы, используйте другой ассемблер. - person Peter Cordes; 10.08.2019
comment
@PeterCordes Что ж, это дает нам левую и правую стороны в зависимости от имени инструкции, но не говорит нам, что первое - левое или правое. Для этого вам нужно посмотреть на битовые инструкции (например, BT, BSF), в которых самый младший бит нумеруется как 0, что делает самый правый бит первым битом. Некоторые процессоры с обратным порядком байтов (но не все) нумеруют самый старший бит как 0, делая самый левый бит первым. - person Ross Ridge; 10.08.2019
comment
Разве это не просто PowerPC, у которого MSB = 0? Я думал, что это считалось просто безумием / умопомрачительным, особенно если рассматривать PPC64 (т.е. типичный для PowerPC), не связанный с прямым порядком байтов. Нумерация битов не меняется, когда PowerPC работает в режиме прямого порядка байтов. (Разве powerpc первого поколения не поддерживает режим прямого порядка байтов?) - person Peter Cordes; 10.08.2019
comment
Википедия @PeterCordes сообщает, что S / 390 и PA-RISC также используют MSB 0: en.wikipedia.org / wiki / Bit_numbering # Usage - person Ross Ridge; 10.08.2019
comment
Да ладно, снова IBM для S / 390. Но Wiki представляет это как нечто большее, чем просто диковинку IBM, включая, по крайней мере, PA-RISC, как вы говорите. Тем не менее, я ранее говорил о том, что сдвиги влево / вправо, перемещающие биты между байтами, многое говорят о том, как они логически расположены внутри регистра; разные биты были бы смежными, если бы было что-то вроде порядка байтов. Однако хорошо, что left и right - просто произвольные имена. - person Peter Cordes; 10.08.2019
comment
Когда выполняются инструкции MOVS и SCAS, они не меняют порядок, не так ли? Я имею в виду, что когда они переходят во временный регистр, они предполагают, что переданная им целочисленная строка уже малозначительна, и перемещают ее как есть? - person Joachim Rives; 12.08.2019
comment
@JoachimRives Я думал, что говорю об этом прямо. SCASD обрабатывает четыре байта, на которые указывает EDI, как 32-битное значение с прямым порядком байтов, ... Порядок байтов не имеет значения для MOVSD, поскольку байты никогда не используются как 32-битные значения, но порядок не изменяется . - person Ross Ridge; 12.08.2019

См. @ RossRidge's answer для очень подробного описания того, как работает MASM. Этот ответ сравнивает его с NASM, что может сбить с толку, если вас интересует только MASM.


mov ecx, 4 составляет четыре двойных слова = 16 байтов при использовании с repne scasd.

Проще было бы опустить rep и просто использовать scasd.

Или даже проще cmp dword ptr [strLetters], "dcba".

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

И да, для MASM очевидно, что вам действительно нужно "dcba", чтобы получить желаемый порядок байтов при использовании строки в качестве целочисленной константы, потому что MASM обрабатывает первый символ как «наиболее значимый» и помещает его последним в 32-битном немедленном виде.


NASM и MASM здесь очень разные. В NASM mov dword [mem], 'abcd' производит 'a', 'b', 'c', 'd' в памяти. то есть побайтовый порядок памяти соответствует исходному порядку. См. символьные константы NASM. Многосимвольные константы просто выравниваются по правому краю в 32-битном прямом порядке прямого байта с байтами строки в исходном порядке.

e.g.

objdump -d -Mintel disassembly
   c7 07 61 62 63 64       mov    DWORD PTR [rdi], 0x64636261

Источник NASM: mov dword [rdi], "abcd"
Источник MASM: mov dword ptr [rdi], "dcba"
Источник GAS: AFAIK невозможно с многосимвольным строковым литералом. Вы могли бы сделать что-нибудь вроде $'a' + ('b'<<8) + ...

Я согласен с предложением Росса избегать использования многосимвольных строковых литералов в MASM, кроме как операнда для db. Если вы хотите, чтобы в качестве непосредственных элементов были удобные и разумные многосимвольные литералы, используйте NASM или EuroAssembler (https://euroassembler.eu/eadoc/#CharNumbers)


Кроме того, не используйте jcc и jmp, просто используйте je close, чтобы провалиться или нет.

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

person Peter Cordes    schedule 10.08.2019