Правильное выполнение вашего языка .NET в отладчике

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

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

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

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

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

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

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

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

Выпущенный код преобразуется в С# (через 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));
}

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

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

Здесь вывод 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 не применяются.

  • Вызовите baz с помощью null arg, он работает правильно. (null? x), за которым следует x.
  • Вызов baz with Cons arg, он работает правильно. (null? x), то (пара? x), то (автомобиль x).
  • Вызовите baz с другим arg, он терпит неудачу. (null? x), то (пара? x), то (автомобиль x) тогда (утверждение-нарушение...).

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

  • Вызовите baz с помощью null arg, он работает правильно. (null? x), за которым следует x.
  • Вызов baz with Cons arg, он не работает. (null? x), то (пара? x).
  • Вызовите baz с другим arg, он терпит неудачу. (null? x), то (пара? x), то (автомобиль x) тогда (утверждение-нарушение...).

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

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

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

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

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

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

Заключение

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

Литература:

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

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

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

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

Мои выводы:

  • После любой инструкции brXXX вам понадобится точка последовательности (если не действительна aka '#line hidden', испустите nop).
  • Перед любой инструкцией brXXX испустите '#line hidden' и nop.

Применяя это, не исправляйте проблемы (только?).

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

  • После ret испустите '#line hidden' и 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, и все намного хуже. Возможно, это может быть неправильный код для правильной отладки. В режиме "release" все выходные данные все еще проверяются.

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

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

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

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

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

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

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

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

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

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

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

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

У меня зарегистрирована ошибка на сайте MS Connect относительно проблемы с точкой останова.

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

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

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

Ответ 1

Я инженер команды Visual Studio Debugger.

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

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

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

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

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

Update:

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

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

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

Спасибо,

Лука

Ответ 2

Я инженер команды SharpDevelop Debugger: -)

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

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

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

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

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

Дэвид