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

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

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

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

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

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

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


person Cui Pengfei 崔鹏飞    schedule 21.04.2011    source источник
comment
У меня есть эта конфигурация, и я не могу воспроизвести ваше заявленное поведение. Будет ли иметь значение, что я использую Win7 64 Bit SP 1? В остальном настройка такая же, но я каждый раз попадаю в точку останова.   -  person Bob G    schedule 21.04.2011
comment
у меня отлично работает - в любом случае, почему вы отлаживаете в режиме выпуска?   -  person BrokenGlass    schedule 21.04.2011
comment
Я полагаю, что строка была оптимизирована компилятором, если вы пошагово ее выполните: он запускает console.writeline (1) дважды, но предоставляет разные аргументы?   -  person forsvarir    schedule 21.04.2011
comment
Предполагая, что в выпуске вы не можете попасть ни в одну точку останова, поскольку отладочная информация не генерируется.   -  person TygerKrash    schedule 21.04.2011
comment
@TygerKrash: Нет, я пробовал. Он достигает точки останова, если это не последний оператор.   -  person Cui Pengfei 崔鹏飞    schedule 21.04.2011
comment
@DarkBobG: Нет, я тоже использую Win7 SP1. На самом деле я не уверен, какие еще конфигурации могут повлиять на результат.   -  person Cui Pengfei 崔鹏飞    schedule 21.04.2011
comment
@BrokenGlass: Нет причин, просто играюсь :)   -  person Cui Pengfei 崔鹏飞    schedule 21.04.2011
comment
@forsvarir: Нет, я декомпилировал и увидел IL-код. Это маловероятно.   -  person Cui Pengfei 崔鹏飞    schedule 21.04.2011
comment
@CuiPengFei: Вы правы, это странно ... если вы поставите точку останова на фактический вызов WriteLine (в представлении разборки), то он действительно попадет, но вам придется снова включать его для каждого запуска.   -  person forsvarir    schedule 21.04.2011
comment
@CuiPengFei: Я думаю, что еще более странно, если вы продублируете последнюю Console.WriteLine (скопируйте ее в строку ниже), вы можете установить точку останова на последней, и она будет поражена, но с вашей текущей точкой останова на нем все равно не будет ...   -  person forsvarir    schedule 21.04.2011
comment
@forsvarir: Да, я тоже это заметил. Он пропускает только первый оператор после if-else.   -  person Cui Pengfei 崔鹏飞    schedule 21.04.2011
comment
Похоже, это задание для connect.microsoft.com.   -  person Frédéric Hamidi    schedule 23.04.2011
comment
@ Фредерик Хамиди: спасибо за совет. Я отправлю этот вопрос туда.   -  person Cui Pengfei 崔鹏飞    schedule 23.04.2011


Ответы (6)


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

Когда отладчик видит точку останова в вашем коде, он пытается найти машинную инструкцию в коде JIT-ed, которая соответствует местоположению, отмеченному точкой останова. Во-первых, ему нужно найти инструкцию 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 и процессе сопоставления отладчика здесь Отладка Иллинойс

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

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

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

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

person Dennis    schedule 24.04.2011
comment
Есть более простой способ увидеть JIT-код. Описание см. Здесь blogs.msdn.com/b/ vancem / archive / 2006/02/20 / 535807.aspx - person Dennis; 24.04.2011

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

Доказательство

person Blindy    schedule 23.04.2011
comment
спасибо, я попробую это в понедельник, когда вернусь к работе (мой компьютер дома - Windows7 x86). - person Cui Pengfei 崔鹏飞; 23.04.2011
comment
@Blindy: Я использую VS2010 SP1 (Win 7, 64-разрядная версия), и он пропускает эту точку останова. - person Mike Bailey; 24.04.2011
comment
У меня VS2010 SP1, и я все еще могу его воспроизвести. - person Dennis; 24.04.2011
comment
64-разрядная версия Win7 VS SP1: x86 | Release build работает, x64 | Release - нет. ОП заявил x64 или любой процессор. - person Rick Sladkey; 24.04.2011
comment
Привет, ребята: если вы можете воспроизвести эту проблему, проголосуйте за это сообщение: connect.microsoft.com/VisualStudio/feedback/details/664400/ - person Cui Pengfei 崔鹏飞; 24.04.2011
comment
@Blindy: У меня установлен SP1, и я все еще могу воспроизвести эту проблему. - person Cui Pengfei 崔鹏飞; 25.04.2011
comment
@cuipengfei, твой единственный шанс - это билет на подключение, тогда - person Blindy; 25.04.2011

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

person DWRoelands    schedule 21.04.2011
comment
Это не совсем ответ на вопрос, почему не попадает в цель, а как заставить его вести себя так, как ожидалось ... - person forsvarir; 21.04.2011
comment
Да, я знаю, что это сделает точку останова достижимой. Но я просто хочу знать, почему он не работает в режиме выпуска. - person Cui Pengfei 崔鹏飞; 21.04.2011
comment
Похоже, что ваша конфигурация Release Mode не включает отладочную информацию. Проверьте расширенные параметры компиляции в настройках вашего проекта. - person DWRoelands; 21.04.2011
comment
@forsvarir Это совершенно правильный ответ. В режиме выпуска компилятор намного более агрессивен в оптимизации, которую он делает, в результате ваш взгляд на исходный код мало похож на скомпилированный оптимизированный результат. - person MattDavey; 21.04.2011
comment
@MattDavey: На самом деле это не ответ. Фактически, ваш комментарий - это скорее ответ на вопрос OP. Они уже указали необходимую конфигурацию для воспроизведения проблемы. Они никогда не спрашивали, как это исправить. Они спросили ПОЧЕМУ в него не попали. Этот ответ дает однострочную инструкцию, но не указывает на причину проблемы. - person forsvarir; 21.04.2011
comment
@MattDavey: исходный код мало похож на скомпилированный оптимизированный результат. хотя другие точки останова достижимы, только точка в последней строке не работает. - person Cui Pengfei 崔鹏飞; 22.04.2011
comment
@CuiPengFei да, даже если другие точки останова достижимы (эти строки кода, возможно, не были оптимизированы) - person MattDavey; 27.04.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) Если он останавливается при первом запуске, а во втором - нет ... то причина в этом.

person Miguel Angelo    schedule 24.04.2011
comment
Спасибо, это похоже на хорошее предложение. Попробую в понедельник, так как у меня дома компьютер Win7 x86. - person Cui Pengfei 崔鹏飞; 24.04.2011
comment
Я только что это проверил. С атрибутом или без него он не может попасть в точку останова. - person Cui Pengfei 崔鹏飞; 25.04.2011

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

person AndersK    schedule 23.04.2011
comment
Я декомпилировал его и увидел код IL, я не очень хорошо знаком с кодом MSIL, но я думаю, что предложение if все еще существует. - person Cui Pengfei 崔鹏飞; 23.04.2011
comment
@CuiPengFei: оператор if (false), вероятно, будет оптимизирован JIT-компилятором. Не все оптимизации нужно делать в коде IL. Компилятор может просто позволить JIT-компилятору выполнить известную JIT-оптимизацию. - person Miguel Angelo; 24.04.2011
comment
Когда вы запускаете сеанс отладки из VS, он подавляет оптимизацию JIT. Я могу воспроизвести проблему, даже если для моего VS включены настройки Подавить JIT-оптимизацию при загрузке модуля (только управляемый). Это означает, что JIT-оптимизация не имеет ничего общего с этим поведением. - person Dennis; 24.04.2011

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

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

person Andrei    schedule 23.04.2011
comment
Я пробовал как полную, так и только pdb, и я упомянул об этом в вопросе - person Cui Pengfei 崔鹏飞; 23.04.2011