Любопытный случай отладчика Visual Studio 2010 (он не может достичь точки останова) - PullRequest
16 голосов
/ 21 апреля 2011

Интересный случай отладчика 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, вы увидите, что точка останова не может быть достигнута.

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

  1. Операционная система: Windows 7 x64;
  2. Конфигурация сборки VS: выпуск;
  3. Цель платформы сборки VS: x64 или Any CPU;
  4. Отладка сборки VSинформация: только pdb или полная;
  5. Код оптимизации сборки VS: включен;

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

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

Заранее спасибо!

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

Ответы [ 6 ]

11 голосов
/ 24 апреля 2011

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

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

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

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

Блок try… catch… finally имеет довольно инвазивный эффект для кода JIT. Он использует механизм Windows SEH и плохо переписывает ваш код. Я не могу найти ссылку на хорошую статью прямо сейчас, но я уверен, что вы можете найти ее там, если вам интересно.

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

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

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

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

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

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

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

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

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

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

5 голосов
/ 23 апреля 2011

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

2 голосов
/ 21 апреля 2011

Измените конфигурацию сборки на «Отладка» вместо «Выпуск».

1 голос
/ 24 апреля 2011

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

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

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

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) Если он останавливается при первом запуске, но не при втором запуске ... тогда это причина.

0 голосов
/ 23 апреля 2011

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

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

0 голосов
/ 23 апреля 2011

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

...