2009-02-05 5 views
10

Что делает следующая инструкция ассемблера x86?В чем смысл инструкции x86 «call dword ptr ds: [00923030h]»?

call dword ptr ds:[00923030h] 

Это косвенный вызов, который я подозреваю, но точно как он вычисляет адрес для вызова?

+0

Возможный дубликат [Что делает «DS: \ [40207A \]» означает в сборе?] (Http://stackoverflow.com/questions/3819699/what-does-ds40207a-mean-in-assembly) –

ответ

16

[EDIT] Обновлен

Всякий раз, когда вы видите операнд памяти, который выглядит как ds:0x00923030, это режим адресации сегмента относительной. Фактический адрес, на который ссылается tp, находится по линейному адресу 0x00923030 относительно базового адреса регистра сегмента ds.

Сегментация памяти в архитектуре x86 несколько сбивает с толку, и я думаю, что Wikipedia неплохо объясняет это.

В основном, 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 reference manual. Этот конкретный вариант команды: "CALL r/m16" (косвенный косвенный косвенный косвенный косвенный космос).

+0

Не вполне, я думаю, есть косвенная связь. Таким образом, это должно быть: Физический адрес, по которому вызывается подпрограмма, задается значением на базовом адресе, хранящимся в регистре сегмента ds плюс 0x00923030. –

+0

Селектор сегментов не указывает на физический адрес, как вы говорите, а на линейный адрес. Вместо этого физический адрес получается на последнем шаге, когда логический адрес уже разрешен для линейного адреса. – newgre

+0

Спасибо. Замечательно, что люди готовы поделиться своим опытом в определенной области с другими. Ты спас мне много времени. – 2009-02-06 14:19:24

2

IIRC, он принимает значение регистра DS (и сдвигает его влево на 4 бита), добавляет к этому немедленному заданному значению, извлекает значение dword из результирующей ячейки памяти, которая становится адресом для вызова. (EDIT: это верно для 16-битного реального режима, для защищенного режима см. Другие ответы.)

+0

это неверно, смещение не добавляется к значению регистра ds. Это фактически логический адрес. – newgre

+0

Вы правы, я думал о реальном режиме. –

9

Этот конкретный код операции совершает вызов через виртуальный адрес (32bit здесь), находящийся в местоположении, на которое указывает логический адрес ds:[00923030h].
Логический адрес состоит из двух компонентов:

  1. A 16 бит селектор сегмента, DS в этом случае, который является в основном индекс в (глобальный/локальный) таблице дескрипторов под управлением операционной системы. Такой селектор также содержит информацию о правах доступа для данного сегмента, который проверяется при доступе (текущий уровень привилегий, CPL)
  2. 32-битовое смещение
    Конечный адрес затем вычисляется следующим образом: базовый адрес сгружен с селектором + смещение

Пожалуйста, обратите внимание, что приведенное выше вычисление обозначает линейный адрес, не физической (см Intel руководства volume 3a, рисунок 2.2), который затем транслируется через стандартный механизм 4KB пейджинга, т.е. адрес состоит из индекса на страницу каталога, таблицу страниц и смещение на выбранную страницу. Имейте в виду, что все операционные системы основного потока используют так называемый модуль памяти с плоской памятью, что означает, что все сегментные селектора указывают на адрес 0x00000000 с пределом, установленным на 0xFFFFFFFF, что является причиной того, что вы можете использовать между всеми сегментами и в конечном итоге привести (легкой) эксплуатации переполнения буфера.

Ассемблер, который вы указали, скорее всего, будет вызван через таблицу адресов импорта (см. this отличная статья) для исполняемого файла, то есть маловероятно, что это вызов подпрограммы ординал.
Код, подобный этому, испускается компиляторами, потому что окончательный виртуальный адрес импортированной функции из внешней DLL не может быть известен вообще во время компиляции (из-за перезагрузки dll). Используя такую ​​вызывающую конструкцию, загрузчик ОС может вставить правильный виртуальный адрес по указателю адреса по логическому адресу, а компилятору не нужно заботиться о том, какой адрес конечная функция имеет в любом случае.

+0

Спасибо. Это действительно код, созданный компилятором для вызова виртуальной функции. Ваш ответ очень помог и спас мне много времени. – 2009-02-06 14:21:41