Как лучше хэндлить брейкпойнты -- нужен совет

Тема в разделе "WASM.WIN32", создана пользователем Velheart, 6 апр 2010.

  1. Velheart

    Velheart New Member

    Публикаций:
    0
    Регистрация:
    2 июн 2008
    Сообщения:
    526
    Попробую описать что есть и что надо:
    У меня есть некоторый компонент-отладчик. Он должен предоставить интерфейс для отладки другим компонентам. В этом интерфейсе, в частности, есть следующие функции: SetBreakpoint, RemoveBreakpoint.
    После вызова SetBreakpoint при срабатывании брэйкпойнта, у клиента отладчика(а в моей реализации это просто класс-наследник) будет вызываться функция OnBreakpoint, и я хочу иметь возможность из нее вызывать RemoveBreakpoint. При любом другом исключении, в частности, при возникновении STATS_BREAKPOINT не из-за моей точки останова должна вызываться другая функция -- OnException. Собственно вопрос, как лучше реализовать функционал с установкой брэйкпойнтов? Я вижу 2 варианта, но не один из них мне не кажется очень хорошим:

    Общее поведение:
    Храним список "наших" адресов. При возникновении STATUS_BREAKPOINT находим, что адрес исключения наш.
    Вызываем функцию-каллбэк. Если она удаляет брэйкпойнт -- проверяем, контексты всех потоков отлаживаемого процесса на предмет нахождения в месте установленной точки останова. Если кто-то стоит там, то запоминаем его -- возможно нам еще придет отладочное событие на только что удаленную точку останова(т.е. оно уже стоит в очереди), и поэтому мы не должны вызывать для него OnException.

    Дальше 2 варианта:
    1 -- мы суспендим все потоки, кроме того, в котором сработала точка останова, выставляем в нем TF восстанавливаем инструкцию и выполняем ее, после чего назад пишем брэйкпойнт. Но тут есть очень неприятный момент -- траблы с трэйсом sysenter. Одна из которых следующая: если поток, в котором мы вызываем sysenter входит в ожидание объекта, который должен быть установлен в другом потоке -- получим дедлок.

    2 -- мы выделяем чанк в памяти, в который копируем затертую брэйкпойнтом инструкцию + прыжок на следующую. Уже лучше в плане стабильности, но
    выделять память в чужом процессе не оч. гуд.(хотя может я еще какой фигни не заметил?)

    Собственно, может у кого есть соображения, как можно сделать лучше? Написана же куча отладчиков, все работают =) Поделитесь, плиз, мыслями.
     
  2. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Velheart
    Пишите брейк на начало инструкции. Когда сработает обрабатываете. Что не понятно ?
     
  3. Velheart

    Velheart New Member

    Публикаций:
    0
    Регистрация:
    2 июн 2008
    Сообщения:
    526
    Собственно вопрос в том, как лучше обрабатывать, чтобы в обработчике вызывался произвольный калбэк + после срабатывания брэйкпойнта все потоки застопленного процесса выполнялись как будто его и не было + брэйкпойнт оставался пока мы его не удалим сами + не один поток отлаживаемого процесса не мог выполнить инструкцию на которую мы ставим брэйкпойнт, так, чтобы он не сработал.
     
  4. l_inc

    l_inc New Member

    Публикаций:
    0
    Регистрация:
    29 сен 2005
    Сообщения:
    2.566
    Velheart
    Что значит стоит "там"? Если поток на пути в обработчик, то он на брэйкпоинте не стоит. ИМХО лучше в RemoveBreakpoint фактический брэйкпоинт оставлять, но в своём списке помечать его, как снятый. Когда снимать его фактически... может через пару секунд после установки флага снятости.
    1)
    Честно говоря, я не понимаю, откуда берётся дэдлок. ИМХО вариант вполне себе нормальный для случая, если ожидается реакция пользователя. Иначе тормозить все потоки означает... тормозить.
    2)
    Вот этого я вообще не понимаю. А как Вы собираетесь список брэйкпоинтов хранить, если не в выделенной памяти? Вот в структуре списка брэйкпоинтов и добавьте поле, содержащее старую инструкцию и прыжок назад. Единственное, что далеко не все инструкции можно просто так скопировать по другому адресу. Скажем, call $+5 отработает неверно.
     
  5. l_inc

    l_inc New Member

    Публикаций:
    0
    Регистрация:
    29 сен 2005
    Сообщения:
    2.566
    Velheart
    На эти два утверждения не обращайте внимания. Мой прогон. :)
     
  6. l_inc

    l_inc New Member

    Публикаций:
    0
    Регистрация:
    29 сен 2005
    Сообщения:
    2.566
    Velheart
    И насчёт дэдлока тоже догнал. Тоже disregard, please. :)
     
  7. Velheart

    Velheart New Member

    Публикаций:
    0
    Регистрация:
    2 июн 2008
    Сообщения:
    526
    l_inc
    Про это я не подумал, сенкс, это действительно лучше, чем то, что я придумал =)
     
  8. Sol_Ksacap

    Sol_Ksacap Миша

    Публикаций:
    0
    Регистрация:
    6 мар 2008
    Сообщения:
    623
    Вот здесь есть один неочевидный момент.
    Для простоты предположим, что у нас однопроцессорная машина. Также, в целевом процессе 2 потока. Единственный брейкпойнт установлен по адресу 12408. Сейчас выполняется поток T1. Выполняется, выполняется – и попадает на наш брейкпойнт. Как известно, int3 – это трап, а воспринимать его как фаулт мы можем лишь благодаря тому, что винда отматывает сохраняемый в ExceptionRecord и ContextRecord ip на один байт назад. Т.е. начальный трапфрейм при возникновении исключения #BP содержит адрес следующей за int3 инструкции – в данном случае там будет лежать число 12409. Далее, вернёмся к процессу выполнения. Поток T1 после натыкания на int3 продолжает исполняться – но уже в ядре и с отключенными прерываниями (поскольку Windows устанавливает тип дескриптора для int3 в IDT как Interrupt Gate, но не Trap Gate). После выполнения полусотни инструкций он включит прерывания. И, допустим, почти сразу после их включения сработает таймер, и винда обнаружит, что квант времени для первого потока истёк – и поставит на выполнение T2. Этот поток также достигнет нашего брейкпойнта, однако на этот раз точка рапортования отладчику будет достигнута и все потоки целевого процесса будут остановлены. Именно здесь кроется проблема – поскольку в трапфрейме T1 ip на момент переключения потоков ещё не был отмотан, то рапортуемый ip контекста для T1 будет равен 12409.
    (В этих рассуждениях были опущены некоторые шаги – вроде процесса реальной остановки потоков; Также нам неизвестно, если Windows пытается делать что-нибудь забавное для избежания описанной ситуации – в этом случае пусть кто-нибудь поправит нас).