Pull to refresh

Про брейкпойнты

Reading time4 min
Views18K
Думаю не ошибусь, если напишу, что каждый программист когда-либо пользовался отладчиком, отлаживал программу пошагово, устанавливал брейкпойнты и т.п. При этом некоторые программисты не любят отлаживать. Другие — обожают. А большинство просто использует отладчик не задумываясь о любви и ненависти, ведь это просто еще один удобный инструмент для работы.
Для многих программистов отладчики — это черный ящик. Они умеют с ним обращаться, но не знают, как он работает. Я не говорю, что это плохо — в подавляющем большинстве случаев можно легко отлаживать программу без знания устройства отладчика.
А для тех, кто хочет заглянуть внутрь черного ящика, я написал эту небольшую статью.
Тут я расскажу про одну из самых загадочных (по крайней мере для меня) возможностей отладчиков — про работу с брейкпойнтами. Я постараюсь рассказать это максимально просто и без лишних деталей. Однако я пишу эту статью для тех читателей, который уже знают что такое брейкпойнты и умеют их использовать.

Вы когда-нибудь задумывались, что происходит, когда вы нажимаете «Вставить брейкпойнт»? Или о том, как отладчик может добавлять брейкпойнты в код на лету и удалять их? О том, как работают брейкпойнты с условиями? О том, тормозят ли множественные установленные брейкпойнты выполнение программы?

Сначала немного теории:

Наверняка уже многие из вас сталкивались или слышали про ассемблерную инструкцию int 3 (_asm int 3 в C++). Именно эта инструкция — это основа всей системы брейкпойнтов.
Когда процессор встречает эту инструкцию, то он инициирует отладочное исключение и передает его в обработчик прерываний операционной системы. А операционная система конвертирует его в софтварное исключение и инициирует его в том месте исполняемого файла, в котором было встречено это int 3. Тип этого исключения общеизвестен — это STATUS_BREAKPOINT (0x80000003).
Если к процессу, инициировавшему это исключение, не присоединен отладчик, то этот процесс упадет. И вы увидите сообщения наподобии этих:


Это возможно, если вы запускаете отладочную сборку или же сами оставили в своем коде int 3 в релизной сборке.

Если же процесс работал под отладчиком (а обычно исключения STATUS_BREAKPOINT используются именно отладчиком), то это исключение передается в отладчик и дальше уже отладчик его обрабатывает.
Также на уровне процессора поддерживаются брейкпойнты, срабатывающие на обращение к области памяти (может отлавливаться чтение, запись или исполнение кода). Таких брейкпойнтов может быть не более четырех. Они работают по другой схеме и глобальны для всей системы в целом, а не только для отлаживаемого приложения. Я на их работе подробно останавливаться не буду, а желающие могут погуглить про регистры DR0-DR7.

Теперь практика:

Когда вы ставите брейкпойнт в отладчике, то происходит следующее:
Отладчик переводит область памяти, куда вы поставили брейкпойнт, в режим read-write (по умолчанию область памяти с исполняемым кодом read only), чтобы можно было менять там код на лету. Затем отладчик заменяет ассемблерную инструкцию в месте, куда вы поставили брейкпойнт, на int 3, а саму эту замененную инструкцию запоминает у себя в памяти.
При этом всегда, когда вы просматриваете ассемблерный код в отладчике, он заменяет эти int 3 на реальные инструкции, но на самом деле там int 3, скрытые от ваших глаз.
Также отладчик всегда заменяет все int 3 обратно на реальные инструкции, когда процесс останавливается на брейкпойнте и управление передается пользователю.

Когда срабатывает брейкпойнт под отладчиком, то:
Отладчик подменяет все int 3 (не только тот, который сработал, а все) на правильные инструкции, которые он запомнил при вставке этих int 3, и показывает точку брейкпойнта пользователю уже с нормальным кодом. Потом, когда пользователь запускает приложение выполняться дальше, то отладчик опять вставляет int 3 во все нужные места.
Если брейкпойнт с условием, то при его срабатывании отладчик сначала проверяет условие и только при выполнении условия делает всю работу, как при обычном брейкпойнте. Если же условие не сработало, то отладчик выполняет запомненную команду, замененную на int 3, а потом продолжает выполнение с инструкции после int 3.

Когда вы удаляете брейкпойнт, то:
Отладчик просто заменяет вставленный int 3 обратно на нужную команду и забывает про этот брейкпойнт.

Выводы, которые можно сделать из всего вышесказанного:

Тормозят ли брейкпойнты, если их много?
Нет, если они без условий. Простой брейкпойнт не требует дополнительных ресурсов отладчика или отлаживаемого процесса — это просто инструкция int 3. Даже если их миллион, но они не срабатывают, то это ни на милисекунду не замедлит выполнение программы.
Но если установить брейкпойнт с условием, то даже один может замедлить программу в разы, если он стоит в часто вызываемом месте, так как в каждом таком брейкпойнте будет вызываться прерывание, потом вызываться исключение, передаваться в отладчик, который будет проверять условие, подменять команды и возвращать выполнение обратно в место брейкпойнта.
Брейкпойнты на память также не тормозят, т.к. они поддерживаются на уровне процессора.

Также надо учесть, что когда вы запускаете приложение под отладчиком, то в момент запуска он будет заменять на int 3 все инструкции в местах брейкпойнтов. И, если их очень много, это может занять долгое время (Например, отладчик MS Visual Studio 2005 при установке брейкпойнта в большом проекте (C++) в частоиспользуемую шаблонную функцию, например std::vector::operator[], иногда подвисает на минуту).

P.S.:
Все, что я тут написал, не касается брейкпойнтов в интерпретируемых языках программирования — я не знаю точно, как они там работают. Там за все отвечает интерпретатор и схема работы брейкпойнтов в нем может быть любой теоретически. Это же касается кода, исполняющегося под виртуальными машинами.
P.S.2:
Я специально писал все термины (брейкпойнт, отладчик, упадёт и т.п.) по-русски, чтобы не перемешивать английский и русский текст и чтобы было проще читать. Также я специально назвал точку останова брейкпойнтом, т.к. мне так кажется правильнее.

Test: 193784457
Tags:
Hubs:
Total votes 110: ↑101 and ↓9+92
Comments69

Articles