Что делает следующая инструкция ассемблера x86?
call dword ptr ds:[00923030h]
Я подозреваю, что это косвенный вызов, но как именно он вычисляет адрес вызова?
Что делает следующая инструкция ассемблера x86?
call dword ptr ds:[00923030h]
Я подозреваю, что это косвенный вызов, но как именно он вычисляет адрес вызова?
[EDIT] Обновлено
Всякий раз, когда вы видите операнд памяти, похожий на ds:0x00923030
, это сегментно-относительный режим адресации. Фактический адрес, на который ссылается tp, находится по линейному адресу 0x00923030 относительно базового адреса сегментного регистра ds
.
Сегментация памяти в архитектуре x86 несколько сбивает с толку, и я думаю, что Википедия хорошо объясняет это. .
По сути, x86 имеет ряд специальных регистров сегмента: cs
(сегмент кода), ds
(сегмент данных), es
, fs
, gs
, и ss
(сегмент стека). Каждое обращение к памяти связано с определенным сегментным регистром. Обычно вы не указываете сегментный регистр, и в зависимости от того, как осуществляется доступ к памяти, используется сегментный регистр по умолчанию. Например, регистр cs
используется для чтения инструкций.
Каждый сегментный регистр имеет определенный базовый адрес и лимит. Базовый адрес определяет физический адрес, которому соответствует линейный адрес 0x00000000, а ограничение определяет максимально допустимый линейный адрес для этого сегмента. Например, если базовый адрес равен 0x00040000, а ограничение равно 0x0000FFFF, то допустимыми будут только линейные адреса от 0x00000000 до 0x0000FFFF, а соответствующие физические адреса будут от 0x00040000 до 0x0004FFFF.
Таким образом, физический адрес, по которому находится вызываемая подпрограмма, определяется базовым адресом, хранящимся в сегментном регистре ds
, плюс 0x00923030. Но мы еще не закончили — в инструкции есть слово ptr
. Это добавляет дополнительный уровень косвенности, поэтому фактической целью подпрограммы является адрес, хранящийся в ячейке ds:0x00923030
.
В синтаксисе AT&T (принятом ассемблером GNU) инструкция будет записана следующим образом:
lcall *ds:0x00923030
Подробную информацию о том, что делает инструкция, см. в справочнике 80386. руководство. Этот конкретный вариант инструкции - "CALL r/m16"
(непрямой вызов рядом с регистром/непрямой памятью).
ds:
используется по умолчанию для режимов адресации данных, отличных от ebp
или esp
в качестве базовых регистров. Этот ответ также подразумевает, что call ds:1234
будет иметь меньше косвенных уровней, т. е. может выполнять EIP=1234 вместо загрузки нового значения EIP из адреса памяти ds:1234
. Но это не имеет смысла; ds:
в ds:1234
уже означает, что это операнд источника памяти (данных).
- person Peter Cordes; 28.12.2019
call r/m32
, а не 16
. dслово — 32 бита, слово — 16. Большинство этих ошибок незначительны, и под ними скрывается несколько хороший ответ, но ответ на связанный дубликат довольно хорош.
- person Peter Cordes; 28.12.2019
Этот конкретный код операции выполняет вызов через виртуальный адрес (здесь 32-битный), находящийся в местоположении, на которое указывает логический адрес ds:[00923030h]
.
Логический адрес состоит из двух компонентов:
Обратите внимание, что приведенный выше расчет относится к линейному адресу, не физическому (см. руководства Intel volume 3a, рисунок 2.2), который затем транслируется с помощью стандартного механизма пейджинга по 4 КБ, т.е. адрес состоит из указателя на каталог страниц, таблицы страниц и смещения на выбранную страницу . Имейте в виду, однако, что все основные потоковые операционные системы используют так называемую модель плоской памяти, что означает, что все селекторы сегментов указывают на адрес 0x00000000 с ограничением, установленным на 0xFFFFFFFF, поэтому вы можете выполнять приведение между всеми сегментами и в конечном итоге привести для (простой) эксплуатации переполнения буфера.
Инструкция на ассемблере, которую вы дали, скорее всего, будет вызовом через таблицу адресов импорта (см. эта отличная статья для более подробной информации) исполняемого файла, т.е. очень маловероятно, что это порядковый вызов подпрограммы.
Подобный код генерируется компиляторами, потому что конечный виртуальный адрес импортированной функции из внешняя dll вообще не может быть известна во время компиляции (из-за перебазирования dll). Используя такую вызывающую конструкцию, загрузчик ОС может вставить правильный виртуальный адрес в адресный указатель с помощью логического адреса, и компилятору все равно не нужно заботиться о том, какой адрес имеет конечная функция.
call dword ptr ds:[00923030h]
, что означает call
какое-то значение в указателе 00923939h
в data segment
IIRC, он берет значение регистра DS (и сдвигает его влево на 4 бита), добавляет к этому непосредственно заданное значение, извлекает значение двойного слова из полученной ячейки памяти, которое становится адресом для вызова. (EDIT: это верно для 16-битного реального режима, для защищенного режима см. другие ответы.)
word ptr
.
- person Peter Cordes; 28.12.2019