Я не настолько знаком с тем, как это делается с CLR, но, вероятно, это очень похоже на то, как это делается с нативным кодом. Когда компилятор генерирует машинные инструкции, он добавляет записи в pdb, которые в основном говорят: «Инструкция по текущему адресу X пришла из строки 25 в foo.cpp».
Отладчик знает, какой адрес программы выполняется в данный момент. Поэтому он ищет какой-то адрес X в pdb и видит, что он пришел из строки 25 в foo.cpp. Используя это, он может «пошагово» пройти по вашему исходному коду.
Этот процесс одинаков независимо от режима отладки или выпуска (при условии, что pdb вообще генерируется в режиме выпуска). Вы правы, однако, что часто в режиме выпуска из-за оптимизаций отладчик не будет проходить «линейно» через код. Это может неожиданно перескочить на разные строки. Это происходит из-за того, что оптимизатор меняет порядок команд, но не меняет сопоставление адреса с исходной строкой, поэтому отладчик по-прежнему может следовать ему.