Не используйте строки в контекстах, где 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
db
- это особый случай, когда строка рассматривается как последовательность байтов, хранящихся в том же порядке. - person Michael Petch   schedule 09.08.2019dd "dcba"
, она была бы сохранена как 'a', 'b', 'c', 'd'. Специальная обработка строки происходит только тогда, когда целевой размер составляет один байт (например,db
). EAX - это 32-битный регистр (а не размер байта), поэтому он сохраняет его в обратном порядке. - person Michael Petch   schedule 09.08.2019mov eax, "12"
? Что хранится в 2 байтах, которые не используются? EAX будет содержать байты в обратном порядке, как «2», «1», но 2 неиспользуемых байта заполнены значением 0 (NUL) в конце. Если вы сохранили EAX в памяти, вы обнаружите, что на выходе получается байт '2', '1', 0, 0 - person Michael Petch   schedule 09.08.2019MOVSD
помещает значения abcd в EAX так же, как присваивает abcd переменной, состоящей из двух слов, верно? Я предполагаю, чтоmov ebx, DWORD PTR [db_str]
передает байты так же, какMOVSD
. - person Joachim Rives   schedule 12.08.2019