Проверка параметров функций на корректность

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

Если вы читали «Should I check the parameters to my function?» в блоге Larry Osterman, и являетесь сторонником второго подхода – можете дальше не читать. :-)

Когда то давно, функции IsBadReadPtr и IsBadWritePtr использовались для проверки валидности переданных указателей. Правда, со временем выяснилось, что от этих функций больше вреда, чем пользы. Во-первых, вместо обнаружения некорректных указателей (что по идее было целью их создателей), эти функции скорее скрывали ошибки. Во-вторых, даже успешное тестирование указателя с помощью одной из функций не гарантировало успешность следующей операции с этой памятью в многозадачной среде. В-третьих, IsBadWritePtr портит память по фактически случайному адресу – гарантия того, что приложение всё-таки упадёт позднее, но только с большими неприятностями. В-четвёртых, даже IsBadReadPtr может быть виновником в сбое приложения, если проверяемый адрес приходится на зашитную страницу (guard page) стека. В результате система потеряет возможность увеличивать стек, по мере надобности, и приложение с грохотом упадёт, если попытается это сделать.

Как же правильно проверять переданные указатели? Правило простое – любое значение указателя не равное 0 считается корректным. Если указатель на самом деле указывает «не туда», то приложение завершиться ошибкой доступа, что, в общем-то, плохо, но помогает найти и исправить ошибки быстрее.

Это правило не работает для специальных случаев, вроде точки входа в ядре операционной системы или RPC. Хотя в случае RPC каждая из сторон сама контролирует выделение буферов, так что функциональность IsBadXxxPtr все равно не требуется.

В случае ядерного вызова указатели должны проверяются на корректность, однако и в этом случае IsBadXxxPtr оказывается не у дел. NT ядро использует функции ProbeForRead и ProbeForWrite, которые во много похожи на IsBadXxxPtr. Не смотря на похожесть, ProbeForXxx главным образом проверяют, что переданный буфер целиком находится в пользовательском адресном пространстве, а не то, что буфер размещён в валидной памяти. Дальнейшие обращения к переданному буферу в любом случае окружаются блоком __try - __except. Иными словами такая проверка гарантирует, что пользовательский код не сможет заставить ядерный код обратится к какому-либо адресу в адресном пространстве ядра. Тем самым ситуация с порчей защитной страницы ядерного стека, как результат запроса из пользовательского кода, не возможна. Само собой, что защитная страница пользовательского стека по-прежнему может пострадать. Но тут уж само приложение виновато.

comments powered by Disqus