Сделайте ваш шаг .NET правильно в отладчике

135

Во-первых, я прошу прощения за длину этого вопроса.

Я автор IronScheme . В последнее время я усердно работал над выдачей приличной отладочной информации, чтобы я мог использовать «нативный» отладчик .NET.

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

Первая проблема связана со степпингом.

Поскольку Scheme является языком выражений, все имеет тенденцию заключаться в круглые скобки, в отличие от основных языков .NET, которые, кажется, основаны на операторах (или строках).

Исходный код (схема) выглядит так:

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))

Я специально выложил каждое выражение на новую строку.

Излучаемый код преобразуется в C # (через ILSpy) выглядит следующим образом:

public static object ::baz(object x)
{
  if (x == null)
  {
    return x;
  }
  if (x is Cons)
  {
    return Builtins.Car(x);
  }
  return #.ironscheme.exceptions::assertion-violation+(
     RuntimeHelpers.False, "nooo", Builtins.List(x));
}

Как видите, довольно просто.

Примечание: если код был преобразован в условное выражение (? :) в C #, все это было бы просто одним шагом отладки, имейте это в виду.

Вот вывод IL с номерами источника и строки:

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       56 (0x38)
    .maxstack  6
    .line 15,15 : 1,2 ''
//000014: 
//000015: (define (baz x)
    IL_0000:  nop
    .line 17,17 : 6,15 ''
//000016:   (cond
//000017:     [(null? x) 
    IL_0001:  ldarg.0
    IL_0002:  brtrue     IL_0009

    .line 18,18 : 7,8 ''
//000018:       x]
    IL_0007:  ldarg.0
    IL_0008:  ret

    .line 19,19 : 6,15 ''
//000019:     [(pair? x) 
    .line 19,19 : 6,15 ''
    IL_0009:  ldarg.0
    IL_000a:  isinst [IronScheme]IronScheme.Runtime.Cons
    IL_000f:  ldnull
    IL_0010:  cgt.un
    IL_0012:  brfalse    IL_0020

    IL_0017:  ldarg.0
    .line 20,20 : 7,14 ''
//000020:       (car x)]
    IL_0018:  tail.
    IL_001a:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_001f:  ret

    IL_0020:  ldsfld object 
         [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_0025:  ldstr      "nooo"
    IL_002a:  ldarg.0
    IL_002b:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
//000021:     [else
//000022:       (assertion-violation #f "nooo" x)]))
    IL_0030:  tail.
    IL_0032:  call object [ironscheme.boot]#::
       'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_0037:  ret
  } // end of method 'eval-core(033)'::'::baz'

Примечание. Чтобы не дать отладчику просто выделить весь метод, я делаю точку входа метода шириной всего в 1 столбец.

Как видите, каждое выражение корректно отображается на строку.

Теперь проблема со степпингом (протестирована на VS2010, но такая же / похожая проблема на VS2008):

Эти с IgnoreSymbolStoreSequencePointsне применяются.

  1. Вызовите базу с нулевым аргументом, он работает правильно. (null? x), за которым следует x.
  2. Позвоните в базу с Cons аргумент, он работает правильно. (null? x), затем (pair? x), затем (car x).
  3. Позвонить в базу с другим аргументом, это не удастся. (null? x) затем (pair? x) затем (car x) затем (нарушение-утверждение ...).

При применении IgnoreSymbolStoreSequencePoints(как рекомендуется):

  1. Вызовите базу с нулевым аргументом, он работает правильно. (null? x), за которым следует x.
  2. Позвонить в базу с Cons аргумент, это не удается. (null? x) затем (pair? x).
  3. Позвонить в базу с другим аргументом, это не удастся. (null? x) затем (pair? x) затем (car x) затем (нарушение-утверждение ...).

Я также нахожу в этом режиме, что некоторые строки (не показаны здесь) неправильно выделены, они выключены на 1.

Вот некоторые идеи, которые могут быть причинами:

  • Tailcalls смущает отладчик
  • Перекрывающиеся местоположения (здесь не показаны) сбивают с толку отладчик (это очень хорошо работает при установке точки останова)
  • ????

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

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

Ситуация становится немного лучше, когда IgnoreSymbolStoreSequencePointsне применяется.

Вывод

Возможно, отладчик VS просто глючит :(

Ссылки:

  1. Создание отладки языка CLR / .NET

Обновление 1:

Mdbg не работает для 64-битных сборок. Так что это вне. У меня больше нет 32-битных машин для тестирования. Обновление: я уверен, что это не большая проблема, у кого-нибудь есть исправление? Изменить: Да, глупый я, просто запустите mdbg в командной строке x64 :)

Обновление 2:

Я создал приложение на C # и попытался проанализировать информацию о линии.

Мои выводы:

  • После любой brXXXинструкции вам нужно указать точку последовательности (если она недействительна, то есть «#line hidden», выведите a nop).
  • Перед любой brXXXинструкцией выведите «#line hidden» и a nop.

Применение этого, однако, не решает проблемы (в одиночку?).

Но добавление следующего дает желаемый результат :)

  • После retэтого выделите «#line hidden» и a nop.

Это использует режим, где IgnoreSymbolStoreSequencePointsне применяется. При применении некоторые шаги все еще пропускаются :(

Вот выход IL, когда выше был применен:

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       63 (0x3f)
    .maxstack  6
    .line 15,15 : 1,2 ''
    IL_0000:  nop
    .line 17,17 : 6,15 ''
    IL_0001:  ldarg.0
    .line 16707566,16707566 : 0,0 ''
    IL_0002:  nop
    IL_0003:  brtrue     IL_000c

    .line 16707566,16707566 : 0,0 ''
    IL_0008:  nop
    .line 18,18 : 7,8 ''
    IL_0009:  ldarg.0
    IL_000a:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_000b:  nop
    .line 19,19 : 6,15 ''
    .line 19,19 : 6,15 ''
    IL_000c:  ldarg.0
    IL_000d:  isinst     [IronScheme]IronScheme.Runtime.Cons
    IL_0012:  ldnull
    IL_0013:  cgt.un
    .line 16707566,16707566 : 0,0 ''
    IL_0015:  nop
    IL_0016:  brfalse    IL_0026

    .line 16707566,16707566 : 0,0 ''
    IL_001b:  nop
    IL_001c:  ldarg.0
    .line 20,20 : 7,14 ''
    IL_001d:  tail.
    IL_001f:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_0024:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_0025:  nop
    IL_0026:  ldsfld object 
      [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_002b:  ldstr      "nooo"
    IL_0030:  ldarg.0
    IL_0031:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
    IL_0036:  tail.
    IL_0038:  call object [ironscheme.boot]#::
      'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_003d:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_003e:  nop
  } // end of method 'eval-core(033)'::'::baz'

Обновление 3:

Проблема с вышеприведенным «полувлажом». Peverify сообщает об ошибках на всех методах из-за nopпосле ret. Я не понимаю проблемы на самом деле. Как можно nopперерыв проверки после ret. Это как мертвый код (за исключением того, что это даже не код) ... О, хорошо, эксперименты продолжаются.

Обновление 4:

Вернувшись домой, удалил «непроверяемый» код, работающий на VS2008, и все стало намного хуже. Возможно, запуск непроверяемого кода для правильной отладки может быть ответом. В режиме «релиз» все выходные данные будут проверяемыми.

Обновление 5:

Теперь я решил, что моя вышеупомянутая идея - единственный жизнеспособный вариант на данный момент. Хотя сгенерированный код не поддается проверке, я еще не нашел ни одного VerificationException. Я не знаю, как этот сценарий повлияет на конечного пользователя.

В качестве бонуса, моя вторая проблема также была решена. :)

Вот небольшая заставка о том, чем я закончил. Он попадает в точки останова, делает правильный шаг (вход / выход / овер) и т. Д. В общем, желаемый эффект.

Я, однако, все еще не принимаю это как способ сделать это. Мне это кажется слишком хакерским. Было бы неплохо получить подтверждение о реальной проблеме.

Обновление 6:

Только что было внесено изменение для тестирования кода на VS2010, кажется, есть некоторые проблемы:

  1. Первый звонок теперь не выполняется правильно. (нарушение-утверждение ...) ударил. В других случаях работает нормально. Какой-то старый код выдавал ненужные позиции. Убрал код, работает как положено. :)
  2. А если серьезно, то при втором вызове программы точки останова завершаются неудачно (при компиляции в памяти выгрузка сборки в файл, кажется, снова делает точки останова счастливыми).

Оба эти случая работают правильно под VS2008. Основное отличие состоит в том, что под VS2010 все приложение компилируется для .NET 4, а под VS2008 компилируется в .NET 2. Оба работают под управлением 64-разрядных систем.

Обновление 7:

Как уже упоминалось, я получил mdbg под 64-битной. К сожалению, у него также есть проблема с точкой останова, из-за которой он не может выйти из строя, если я перезапущу программу (это означает, что она перекомпилируется, поэтому не использует ту же сборку, но все еще использует тот же исходный код).

Обновление 8:

Я подал ошибку на сайте MS Connect в отношении проблемы с точкой останова.

Обновление: исправлено

Обновление 9:

После долгих раздумий кажется, что единственный способ порадовать отладчик - это использовать SSA, поэтому каждый шаг может быть изолированным и последовательным. Мне еще предстоит доказать это понятие, хотя. Но это кажется логичным. Очевидно, что очистка временного кода от SSA нарушит отладку, но это легко переключить, и оставить их без особых накладных расходов.

leppie
источник
Любые идеи приветствуются, я опробую их и добавлю результаты к вопросу. Первые 2 идеи, попробуйте mdbg и напишите тот же код на C # и сравните.
Леппи
6
Что за вопрос?
Джордж Стокер
9
Я думаю, что его вопрос звучит так: «Почему мой язык не работает правильно?»
МакКей,
1
Если вы не пропустите peverify, вы не сможете работать в ситуациях частичного доверия, например, с подключенного диска или во многих установках хостинга плагинов. Как вы генерируете свой IL, рефлексию. Emit или через текст + ilasm? В любом случае вы всегда можете сами указать .line 16707566,16707566: 0,0 '', так что вам не нужно беспокоиться о том, что они ставят nop после возврата.
Hippiehunter
@Hippiehunter: Спасибо. К сожалению, без nops, степпинг не удался (я проверю это снова, чтобы быть уверенным). Это жертва, я думаю, мне придется пойти на это. Это не то, что VS может даже работать без прав администратора :) Кстати, используя Reflection.Emit через DLR (очень взломанный ранний разветвленный).
Леппи

Ответы:

26

Я инженер в команде отладчика Visual Studio.

Поправьте меня, если я ошибаюсь, но похоже, что единственная оставшаяся проблема заключается в том, что при переключении с PDB на формат символов динамической компиляции .NET 4 пропускаются некоторые точки останова.

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

  1. VS (2008+) может работать без прав администратора
  2. Какие-нибудь символы загружаются вообще второй раз? Вы можете проверить, взломав (через исключение или вызов System.Diagnostic.Debugger.Break ())
  3. Предполагая, что символы загружаются, есть ли репродукция, которую вы можете отправить нам?
  4. Скорее всего, разница в том, что формат символов для динамически скомпилированного кода на 100% отличается между .NET 2 (поток PDB) и .NET 4 (IL DB, я думаю, они это называли?)
  5. Звук нопа о праве. Смотрите правила для генерации неявных точек последовательности ниже.
  6. Вам на самом деле не нужно излучать вещи по-разному. По умолчанию VS шагает по «операторам символов», где, как автор компилятора, вы определяете, что означает «оператор символов». Так что, если вы хотите, чтобы каждое выражение было отдельной вещью в файле символов, это будет прекрасно работать.

JIT создает неявную точку последовательности, основанную на следующих правилах: 1. Инструкции IL nop 2. Пустые точки стека IL 3. Инструкция IL сразу после инструкции вызова

Если выясняется, что для решения вашей проблемы нам нужно репро, вы можете сообщить об ошибке подключения и безопасно загрузить файлы через этот носитель.

Обновить:

Мы призываем других пользователей, столкнувшихся с этой проблемой, попробовать предварительную версию Dev11 для разработчиков по адресу http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543 и оставить свои комментарии. (Должна быть цель 4.5)

Обновление 2:

Леппи проверил исправление, чтобы работать для него на бета-версии Dev11, доступной по адресу http://www.microsoft.com/visualstudio/11/en-us/downloads, как отмечено в ошибке подключения https://connect.microsoft. ru / VisualStudio / обратная связь / подробности / 684089 / .

Спасибо,

Люк

Люк Ким
источник
Большое спасибо. Я постараюсь сделать repro, если нужно.
Леппи
Что касается подтверждения, да, вы правы, но я все же предпочел бы решить проблемы с пошаговыми инструкциями, не нарушая проверяемость. Но, как я уже сказал, я готов пожертвовать тем, что, если бы я мог доказать, что он никогда не бросит время выполнения VerificationException.
Леппи
Что касается пункта 6, я просто сделал это «на основе строки» для этого вопроса. Отладчик фактически правильно вводит / выводит / выдает выражения, как я намереваюсь, чтобы это работало :) Единственная проблема с подходом - установка точек останова для «внутренних» выражений, если «внешнее» выражение охватывает их; отладчик в VS стремится создать самое внешнее выражение в качестве точки останова.
Леппи
Я думаю, что я выделил проблему с точкой останова. Во время работы под CLR2 точки останова переоцениваются при повторном запуске кода, хотя и нового кода. На CLR4 точки останова только «придерживаются» исходного кода. Я сделал небольшой скринкаст о поведении CLR4 @ screencast.com/t/eiSilNzL5Nr . Обратите внимание, что имя типа изменяется между вызовами.
Леппи
Я подал ошибку при подключении. Воспроизведение довольно простое :) connect.microsoft.com/VisualStudio/feedback/details/684089
Леппи
3

Я инженер в команде отладчика SharpDevelop :-)

Вы решили проблему?

Вы пытались отладить его в SharpDevelop? Если есть ошибка в .NET, мне интересно, нужно ли нам реализовать какой-то обходной путь. Я не знаю об этой проблеме.

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

Быстрые заметки об оригинальном коде IL:

  • .line 19,19: 6,15 '' происходит дважды?
  • .line 20,20: 7,14 '' не начинается с неявной точки последовательности (стек не пуст). я волнуюсь
  • .line 20,20: 7,14 '' включает в себя код "car x" (хорошо), а также "#f nooo x" (плохо?)
  • относительно NOP после рет. Что насчет stloc, ldloc, ret? Я думаю, что C # использует этот трюк, чтобы сделать ret отличной точкой последовательности.

Дэвид

dsrbecky
источник
+1 Спасибо за отзыв, как первое замечание, неудачный побочный эффект от генератора кода, но, похоже, безобидный. Второй момент, это именно то, что мне нужно, но я вижу вашу точку зрения, рассмотрим это. Не уверен, что вы подразумеваете под последним пунктом.
Леппи
Третий момент заключался в том, что .line 22,22: 7,40 '' должно быть до IL_0020. Или должно быть что-то до IL_0020, в противном случае код по-прежнему считается .line 20,20: 7,14 ''. Четвертый пункт «ret, nop» может быть заменен на «sloc, .line, ldloc, ret». Я видел шаблон раньше, возможно, он был излишним, но, возможно, у него была причина.
dsrbecky
Я не могу сделать stloc, ldloc до ret, так как я потеряю хвостовой вызов. Ах, я понимаю, вы имели в виду исходный вывод?
Леппи