Подтвердить что ты не робот

Любопытный случай отладчика Visual Studio 2010 (он не может попасть в точку останова)

Любопытный случай отладчика Visual Studio 2010 (он не может попасть в точку останова)

Это код, который воспроизводит проблему:

class Program {
  static void Main(string[] args) {
    bool b = false;

    if (b) {
        List<string> list = new List<string>();
        foreach (var item in list) {

        }
    } else {
        Console.WriteLine("1");
    }
    Console.WriteLine("2");//add a break point here in VS2010
  }
  //1. configuration: release
  //2. platform target: x64 or Any Cpu
  //3. debug info: pdb only or full
  //4. OS: Win7 x64
  //5. optimize code: enabled
}

Добавьте точку останова к последней инструкции кода, затем отлаживайте ее в vs2010, вы увидите, что точка останова не может быть удалена.

Чтобы воспроизвести этот любопытный случай, вам необходимо выполнить следующие условия:

  • Операционная система: окна 7 x64;
  • Конфигурация VS build: release;
  • Целевая платформа сборки VS: x64 или Any Cpu;
  • VS build debug info: только pdb или full;
  • VS построить оптимизировать код: включен;

Я не уверен, что этих условий достаточно, чтобы воспроизвести его, но это то, как моя машина была настроена, когда я нашел эту проблему.

Почему отладчик не может попасть в точку останова?

Спасибо заранее!

И если вы можете воспроизвести эту проблему, пожалуйста, подумайте о голосовании этот пост.

4b9b3361

Ответ 1

Когда предоставленный пример построен в режиме деблокирования, а затем JIT-ed в 64-разрядный машинный код, он не содержит достаточной информации для отладчика, чтобы скорректировать точку останова с какой-либо конкретной машинной инструкцией. Вот почему отладчик никогда не останавливается на этой точке останова во время выполнения машинного кода JIT-ed. Он просто не знает, где остановиться. Вероятно, это какое-то неправильное поведение или даже ошибка в 64-битном отладчике CLR, поскольку он воспроизводится только тогда, когда он JIT-ed в 64-разрядный машинный код, но не в 32-разрядный машинный код.

Когда отладчик видит точку останова в коде, он пытается найти машинную инструкцию в коде JIT-ed, соответствующем местоположению, помеченному точкой останова. Во-первых, ему нужно найти инструкцию IL, которая соответствует местоположению точки останова в вашем коде С#. Затем ему нужно найти машинную инструкцию, соответствующую команде IL. Затем он устанавливает реальную точку останова в найденной машинной инструкции и начинает выполнение метода. В вашем случае похоже, что отладчик просто игнорирует точку останова, потому что не может сопоставить ее с конкретной машинной инструкцией.

Отладчик не может найти адрес машинной инструкции, который следует за оператором if... else. Оператор if... else и код внутри него каким-то образом вызывают такое поведение. Неважно, какое утверждение следует за if... else. Вы можете заменить оператор Console.WriteLine( "2" ) на какой-то другой, и вы все равно сможете воспроизвести проблему.

Вы увидите, что компилятор С# испускает блок try... catch вокруг логики, которая читает список, если вы разобьете результирующую сборку с Reflector. Это документированная функция компилятора С#. Вы можете узнать больше об этом в Инструкция foreach

Попробуй... уловить... наконец, блок имеет довольно инвазивный эффект на код JIT-ed. Он использует механизм Windows SEH под капотом и сильно переписывает ваш код. Я не могу найти ссылку на хорошую статью прямо сейчас, но я уверен, что вы можете найти ее там, если хотите.

Это то, что происходит здесь. Попытка... наконец-то блокировать внутри if if else оператор вызывает отладчик с икотой. Вы можете воспроизвести свою проблему с помощью очень простого кода.

bool b = false;
if (b)
{
    try
    {
        b = true;
    }
    finally
    {
        b = true;
    }
}
else
{
    b = true;
}
b = true;

Этот код не вызывает каких-либо внешних функций (он устраняет эффект применения метода, предложенного одним из ответов), и он компилируется непосредственно в IL без какого-либо дополнительного кода, добавленного компилятором С#.

Он воспроизводится только в режиме деблокирования, потому что в режиме отладки компилятор выдает команду IL NOP для каждой строки вашего кода на С#. Инструкция IL NOP ничего не делает, и она напрямую компилируется инструкцией CPU NOP JITER, которая ничего не делает. Полезность этой команды заключается в том, что ее можно использовать отладчиком в качестве привязки для точек останова, даже если остальная часть кода плохо перезаписана JITER.

Я смог заставить отладчик работать правильно, поставив одну инструкцию NOP прямо перед оператором, который следует за if... else.

Подробнее о процессах NOP и процессах отладки можно прочитать здесь Отладка IL

Вы можете попытаться использовать расширение WinDbg и SOS для его изучения JIT-ed версии метода. Вы можете попробовать проверить машинный код, который генерирует JIT-er, и попытаться понять, почему он не может сопоставить этот машинный код с конкретной строкой С#.

Вот пара ссылок об использовании WinDbg для взлома управляемого кода и получения адреса памяти метода JIT-ed. Я считаю, что вы должны найти способ получить JIT-ed код для метода оттуда: Установка точки останова в WinDbg для управляемого кода, SOS Cheat Sheet (.NET 2.0/3.0/3.5).

Вы также можете попытаться сообщить о проблеме Microsoft. Вероятно, это ошибка отладчика CLR.

Спасибо за интересный вопрос.

Ответ 2

Используя VS2010 SP1, он останавливается на последней строке, если вы установите контрольную точку в режиме деблокирования. Вы действительно должны установить его, он конкретно упоминает, что он исправляет проблемы отладчика, где он иногда пропускает точки останова (хотя и не в этом конкретном случае).

Proof

Ответ 3

Измените конфигурацию сборки на "Debug", а не "Release".

Ответ 4

Компилятор JIT использует методы оптимизации, которые могут вызвать это.

Одна такая оптимизация называется методом inline, который может отвечать за это поведение.

Я не могу сказать точно сейчас, потому что я использую компьютер другого человека... но вы можете проверить это сами:

1) Создайте следующий метод:

[MethodImpl(MethodImplOptions.NoInlining)]
public static void MyMethod(string str)
{
    str += "-CONCAT-STRING";
}

2) замените вызовы на "Console.WriteLine" только с помощью "MyMethod"

3) Установите точку останова и попробуйте.

4) Теперь удалите атрибут MethodImpl из метода "MyMethod".

//[MethodImpl(MethodImplOptions.NoInlining)]
public static void MyMethod(string str)
{
    str += "-CONCAT-STRING";
}

5) Запустите снова, с точкой останова в том же месте.

6) Если он останавливается в первом запуске, но не во втором прогоне... тогда это и есть причина.

Ответ 5

Я думаю, что при отладке в режиме выпуска ваши точки останова могут не соответствовать фактическим строкам кода, потому что машинный код может быть оптимизирован. В случае вашего кода вы фактически ничего не делаете от печати "1", а затем "2", поэтому было бы безопасно предположить, что компилятор удалит код (b == false), поскольку он никогда не будет достиг.

Ответ 6

Построение выпуска не позволяет создавать отладочные символы, что само по себе очевидно.

Вы можете переопределить это, перейдя в свойства вашего проекта. выберите Release-configuration и нажмите "Дополнительно" на вкладке "Build". Установите полную версию Debug-Info. enter image description here