13 февраля 2016 г.

Задачка №21

Есть ли в этом коде баг?

function ReadVMWareFlags: Cardinal; assembler;
asm
  // сохранили регистры, которые мы сейчас будем использовать
  // (eax, edx и ecx сохранять не нужно, 
  // поскольку соглашение вызова позволяет их менять)
  push ebx

  // задаём параметры чтения ("волшебные" значения)
  xor ebx, ebx
  mov eax, 'VMXh' // $564D5868
  mov ecx, 0Ah
  mov dx, 'VX'    // $5856

  // читаем из I/O порта
  in eax, dx

  // результат - в Result
  mov eax, ebx

  // восстановили сохранённые регистры
  pop ebx
end;

function IsRunningVMWare: Boolean;
const
  CVMWARE_FLAG = $564D5868;
begin
  try
    Result := (ReadVMWareFlags = CVMWARE_FLAG);
  except
    Result := False;
  end;
end;
Этот код предназначен для определения запущены ли мы под виртуальной машиной VMWare или нет. Для этого код читает из некоего порта ввода-вывода процессора x86 некое значение (с помощью команды x86 in). Если прочитанное значение равно сигнатуре VMWare - мы запущены под ней.

Если же мы не запущены под VMWare, то попытка прочитать что-то из порта ввода-вывода процессора возбудит исключение EXCEPTION_PRIV_INSTRUCTION ($C0000096 - "Privileged Instruction"), поскольку такая операция доступна только режиму ядра, но никак не прикладной программе режима пользователя. В этом случае мы ловим исключение в блоке try/except.

Предположим, что код корректно определяет VMWare. Вопрос: есть ли здесь баг или всё в порядке? Вопрос именно про самый натуральный баг, не про "здесь можно улучшить".

В интернете полно вариантов этого кода. В том числе аналогичные алгоритмы для определения других виртуальных машин.

P.S. Задачка исключительно на знание справки Delphi. Не надо выдумывать что-то сверхсложное.

6 комментариев:

  1. Может, не баг, но для гарантии я бы добавил
    const
    CVMWARE_FLAG: cardinal = $564D5868;

    ОтветитьУдалить
  2. А для assembler-функций компилятор сделает корректный SEH-кадр?

    ОтветитьУдалить
    Ответы
    1. Вообще-то вопрос. Я не знаю, как там с этим делом у Delpi, а в C++ нет определять исключительно ассемблерные функции, можно только либо использовать вставки внутри "нормальных" функций, либо подлинковывать такие функции извне. И в обоих случаях ebx при броске останется не восстановленным. Подозреваю даже, что и при корректном SEH никто его не восстановит, но в Delphi могут быть свои правила.

      Удалить
    2. Чтобы компилятор сгенерировал информацию о раскрутке стэка, в начале функции нужны магические инструкции:
      push ebp
      mov ebp, esp

      Удалить
  3. Chaa прав.
    Более того это даже написано в документации http://docwiki.embarcadero.com/RADStudio/Seattle/en/PC-Mapped_Exceptions#Unwinding_Assembly_Routines
    Если в двух словах, работаете с исключениями в asm - значит должны гарантировать stack frames. А в ReadVMWareFlags ими и не пахнет.
    И после возникшего `Privileged instruction` регистры восстановятся не правильно.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.