Попробую описать что есть и что надо: У меня есть некоторый компонент-отладчик. Он должен предоставить интерфейс для отладки другим компонентам. В этом интерфейсе, в частности, есть следующие функции: SetBreakpoint, RemoveBreakpoint. После вызова SetBreakpoint при срабатывании брэйкпойнта, у клиента отладчика(а в моей реализации это просто класс-наследник) будет вызываться функция OnBreakpoint, и я хочу иметь возможность из нее вызывать RemoveBreakpoint. При любом другом исключении, в частности, при возникновении STATS_BREAKPOINT не из-за моей точки останова должна вызываться другая функция -- OnException. Собственно вопрос, как лучше реализовать функционал с установкой брэйкпойнтов? Я вижу 2 варианта, но не один из них мне не кажется очень хорошим: Общее поведение: Храним список "наших" адресов. При возникновении STATUS_BREAKPOINT находим, что адрес исключения наш. Вызываем функцию-каллбэк. Если она удаляет брэйкпойнт -- проверяем, контексты всех потоков отлаживаемого процесса на предмет нахождения в месте установленной точки останова. Если кто-то стоит там, то запоминаем его -- возможно нам еще придет отладочное событие на только что удаленную точку останова(т.е. оно уже стоит в очереди), и поэтому мы не должны вызывать для него OnException. Дальше 2 варианта: 1 -- мы суспендим все потоки, кроме того, в котором сработала точка останова, выставляем в нем TF восстанавливаем инструкцию и выполняем ее, после чего назад пишем брэйкпойнт. Но тут есть очень неприятный момент -- траблы с трэйсом sysenter. Одна из которых следующая: если поток, в котором мы вызываем sysenter входит в ожидание объекта, который должен быть установлен в другом потоке -- получим дедлок. 2 -- мы выделяем чанк в памяти, в который копируем затертую брэйкпойнтом инструкцию + прыжок на следующую. Уже лучше в плане стабильности, но выделять память в чужом процессе не оч. гуд.(хотя может я еще какой фигни не заметил?) Собственно, может у кого есть соображения, как можно сделать лучше? Написана же куча отладчиков, все работают =) Поделитесь, плиз, мыслями.
Собственно вопрос в том, как лучше обрабатывать, чтобы в обработчике вызывался произвольный калбэк + после срабатывания брэйкпойнта все потоки застопленного процесса выполнялись как будто его и не было + брэйкпойнт оставался пока мы его не удалим сами + не один поток отлаживаемого процесса не мог выполнить инструкцию на которую мы ставим брэйкпойнт, так, чтобы он не сработал.
Velheart Что значит стоит "там"? Если поток на пути в обработчик, то он на брэйкпоинте не стоит. ИМХО лучше в RemoveBreakpoint фактический брэйкпоинт оставлять, но в своём списке помечать его, как снятый. Когда снимать его фактически... может через пару секунд после установки флага снятости. 1) Честно говоря, я не понимаю, откуда берётся дэдлок. ИМХО вариант вполне себе нормальный для случая, если ожидается реакция пользователя. Иначе тормозить все потоки означает... тормозить. 2) Вот этого я вообще не понимаю. А как Вы собираетесь список брэйкпоинтов хранить, если не в выделенной памяти? Вот в структуре списка брэйкпоинтов и добавьте поле, содержащее старую инструкцию и прыжок назад. Единственное, что далеко не все инструкции можно просто так скопировать по другому адресу. Скажем, call $+5 отработает неверно.
Вот здесь есть один неочевидный момент. Для простоты предположим, что у нас однопроцессорная машина. Также, в целевом процессе 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 пытается делать что-нибудь забавное для избежания описанной ситуации – в этом случае пусть кто-нибудь поправит нас).