Идентификация констант и смещений
Микропроцессоры серии 80x86 поддерживают операнды трех типов: регистр, непосредственное значение, непосредственный указатель. Тип операнда явно задается в специальном поле машинной инструкции, именуемом mod , поэтому никаких проблем в идентификации типов операндов не возникает. Регистр — ну, все мы знаем, как выглядят регистры; указатель по общепринятому соглашению заключается в квадратные скобки, а непосредственное значение записывается без них. Например:
Читайте также - RegVPS - лучший виртуальный частный сервер (VPS). Веб-хосты занимают золотую середину между недорогим, но менее надежным общим хостингом и мощным, но дорогим выделенным хостингом. Для многих предприятий VPS-хостинг - это идеальный баланс стоимости и мощности, vps в США Лос Анджелес по доступным ценам.
MOV ECX, EAX; ← регистровые операнды
MOV ECX, 0x666; ← левый операнд регистровый, правый — непосредственный
MOV [0x401020], EAX; ← левый операнд — указатель, правый — регистр
Кроме этого, микропроцессоры серии 80x86 поддерживают два вида адресации памяти: непосредственную и косвенную. Тип адресации определяется типом указателя. Если операнд — непосредственный указатель, то и адресация непосредственна. Если же операнд‑указатель — регистр, то такая адресация называется косвенной. Например:
MOV ECX,[0x401020] ← непосредственная адресация
MOV ECX, [EAX]← косвенная адресация
Для инициализации регистрового указателя разработчики микропроцессора ввели специальную команду, вычисляющую значение адресного выражения addr и присваивающую его регистру REG , — LEA REG, [addr] . Например:
LEA EAX, [0x401020] ; Регистру EAX присваивается значение указателя 0x401020
MOV ECX, [EAX]; Косвенная адресация — загрузка в ECX двойного слова,
; расположенного по смещению 0x401020
Правый операнд команды LEA всегда представляет собой ближний (near) указатель (исключение составляют случаи использования LEA для сложения констант — подробнее об этом см. в одноименном пункте). И все было бы хорошо... да вот, оказывается, внутреннее представление ближнего указателя эквивалентно константе того же значения. Отсюда LEA EAX, [0x401020] равносильно MOV EAX, 0x401020 . В силу определенных причин MOV значительно обогнал в популярности LEA , практически вытеснив последнюю инструкцию из употребления.
Отказ от LEA породил фундаментальную проблему ассемблирования — проблему OFFSET’a. В общих чертах ее суть заключается в синтаксической неразличимости констант и смещений (ближних указателей). Конструкция MOV EAX, 0x401020 может грузить в EAX и константу, равную 0x401020 (пример соответствующего C-кода: a=0x401020 ), и указатель на ячейку памяти, расположенную по смещению 0x401020 (пример соответствующего C-кода: a=&x ). Согласись, a=0x401020 совсем не одно и то же, что a=&x ! А теперь представь, что произойдет, если в повторно ассемблированной программе переменная х окажется расположена по иному смещению, а не 0x401020 ? Правильно — программа рухнет, ибо указатель a по‑прежнему указывает на ячейку памяти 0x401020 , но здесь теперь «проживает» совсем другая переменная!
Почему переменная может изменить свое смещение? Основных причин тому две. Во‑первых, язык ассемблера неоднозначен и допускает двоякую интерпретацию. Например, конструкции ADD EAX, 0x66 соответствуют две машинные инструкции: 83 C0 66 и 05 66 00 00 00 длиной три и пять байт соответственно. Транслятор может выбрать любую из них, и не факт, что ту же самую, которая была в исходной программе (до дизассемблирования). Неверно «угаданный» размер вызовет смещение всех остальных инструкций, а вместе с ними и данных. Во‑вторых, смещение не замедлит вызвать модификацию программы (разумеется, речь идет не о замене JZ на JNZ , а о настоящей адаптации или модернизации), и все указатели тут же «посыплются».
Вернуть работоспособность программы помогает директива offset . Если MOV EAX, 0x401020 действительно загружает в EAX указатель, а не константу, по смещению 0x401020 следует создать метку, именуемую, скажем, loc_401020 . Также нужно MOV EAX, 0x401020 заменить на MOV EAX, offset loc_401020 . Теперь указатель EAX связан не с фиксированным смещением, а с меткой!
А что произойдет, если предварить директивой offset константу, ошибочно приняв ее за указатель? Программа откажет или станет работать некорректно. Допустим, число 0x401020 выражало собой объем бассейна, в который вода втекает через одну трубу, а вытекает через другую. Если заменить константу указателем, то объем бассейна станет равен... смещению метки в заново ассемблированной программе и все расчеты полетят к черту.
Таким образом, очень важно определить типы всех непосредственных операндов, и еще важнее определить их правильно. Одна ошибка может стоить программе жизни (в смысле работоспособности), а в типичной программе тысячи и десятки тысяч операндов!
Отсюда возникает два вопроса:
- Как вообще определяют типы операндов?
- Можно ли их определять автоматически (или на худой конец хотя бы полуавтоматически)?
Типы операндов
Перейти обратно к новости
|