14 августа 2012 г.

Задачка №16

Объяснить поведение кода.

Эта задачка - в некотором смысле, продолжение предыдущей.

Предположим, у нас есть такой очень простой код:
procedure DoSomething;
begin
end;

procedure Work;
begin
  ...
  DoSomething; // <-
  ... 
end;
При некоторых условиях в отмеченной строке возбуждается исключение Access Violation.

Ваша задача - объяснить, как такое может быть.

При этом:
  • Код присутствует в памяти и имеет правильные атрибуты защиты (PAGE_EXECUTEREAD).
  • Это простая функция, не метод объекта.
  • Функция вызывается напрямую (нет полиморфизма/опосредованного вызова, т.е. там просто "asm call DoSomething end").
  • Код вызывающего и вызываемого находится в одном модуле, в вопросе нет DLL.
  • Это не аппаратная проблема.
  • Нет никаких внешних воздействий (отладчик, другие потоки, системный код).
Иными словами, возбуждение Access Violation напрямую связано с тем, как делается вызов функции на x86. Что бы это могло быть? И как тогда эта задачка связана с предыдущей?

Ответ.

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

  1. Рискну предположить что причиной может быть зацикленная рекурсия, в следствие чего - переполнение стека и попытка вписать данные за его пределы.

    ОтветитьУдалить
  2. ммм... а ответ на предыдущую будет ?

    ОтветитьУдалить
  3. Хотя это крючкотворство, но в контексте функции Work DoSomething может быть переменно процедурного типа. Это вписывается в "asm call DoSomething end", хотя и будет опосредованным вызовом :-)))

    ОтветитьУдалить
  4. Может DoSomething быть функцией с ring 0, а Work - функцией Ring 3 ? Хотя это бы не относилось к VirtualProtect из прошлой задачи.

    ОтветитьУдалить
  5. А посты про сериализацию уже закончились?

    ОтветитьУдалить
  6. Планирую закончить, но после серии о плагинах. Про плагины ещё две статьи осталось.

    ОтветитьУдалить
  7. Из всего возможного могу представить себе только проезд по памяти... Неужели может быть что-то кроме?..

    ОтветитьУдалить
  8. Инструкция CALL сохраняет в стек адрес возврата. На мой взгляд, это единственное место, в котором она может сломаться с Access Violation - нет места в стеке и регистр SP испортился.
    Страницы под стек выделяются по мере надобности, поэтому Access Violation может быть и нормальным поведением, ОС перехватит и выделит новую страницу.

    ОтветитьУдалить
  9. Нормальный компилятор этот вызов-пустышку вообще должен выкидывать в release.

    ОтветитьУдалить
  10. В одной из статей у автора (или в переводах) довольно чётко обозначена одна из особенностей архитектуры x86: инструкции процессора имеют переменный размер.

    Я в ассемблере не силён, но мне кажется, что проблема в следующем.

    Если процедура DoSomething; пуста, то она содержит всего одну команду - ret, которая занимает 1 байт.

    В предыдущей задачке: по адресу, где расположена эта процедура, делается попытка вставить команду jmp XX, которая уже будет занимать как минимум два байта: сама команда + смещение.

    Соответственно мы затираем какую-то другую инструкцию и получаем AV.

    Исправить это можно, наверное, так:
    procedure DoSomething;
    asm
    nop;nop;nop;
    end;

    Возможно nop-операций надо больше, помнится там есть разновидности jmp near и jmp far..

    ОтветитьУдалить
  11. В целом и общем, согласен с предыдущим постом. Не делился своим мнением ранее по причине сомнений, ибо в асме также не силен (сорри за офтоп). ИМХО, nop-операций нужно 4, не могу объяснить, почему.

    ОтветитьУдалить
  12. Это отличные две задачки. Вроде и простая проблема, и даже наводку дали второй задачкой, но даже не подумал о том что функция может быть меньше JMP.

    Николай Зверев, вы где ответы "подсматриваете"? 13-ую раскусили, 15 и 16 тоже :)

    ОтветитьУдалить
  13. MrShoor, я ответы не подсматриваю :)
    Это, скорее, знания + смекалка:
    а) несколько лет назад я "подсмотрел" как работает библиотека FastCode - там как раз JMP и используется при замещении функций типа FillChar, Pos, CompareStr... уже тогда у меня была мысль, что в общем случае такое делать не безопасно;
    б) как раз на днях читал о разных архитектурах, подсказка об особенности x86 подтверждает догадку;
    в) отладчик IDE Delphi в помощь.

    > 13-ую раскусили, 15 и 16 тоже :)
    эээ... 15ю я не кусал, не успел, а к 13й я там и так подробно расписал ход мыслей. Кстати, привычку вчитываться в каждый символ кода (не важно какого языка программирования) очень полезно иметь программисту.

    ОтветитьУдалить
  14. хмм ответ про длину JMP показался слишком ожидаемым, тем более, что не было сказано про хак из предыдущего примера, было сказано об особенности x86...
    Если это таки верный ответ, то задача поставлена коайне неточно :(

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

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

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

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

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

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