Неверный путь к файлу и номер строки в трассировках стека исключений из динамического кода

Мы используем System.Reflection.Emit для генерации кода во время выполнения из исходного кода (да, как в компиляторе). Мы предоставляем правильную информацию о символах в ILGenerator с помощью MarkSequencePoint и т. д. и включаем все флаги отладки в AssemblyBuilder. Сборка хранится в памяти в том же процессе, который ее компилировал, и выполняется напрямую.

При использовании отладчика Visual Studio для пошагового просмотра исходного кода для динамически сгенерированного кода он на самом деле работает отлично, и Visual Studio, кажется, полностью понимает, откуда берется код с точки зрения файлов и номеров строк.

ОДНАКО. Когда исключения создаются сгенерированным кодом, объекты System.Exception содержат полностью неверные трассировки стека. Они указывают на другие (действительные, но неправильные) файлы и номера строк. Он получает правильное имя класса и метода, но указанный файл и номер строки не имеют ничего общего с путем кода, из которого на самом деле возникло исключение.

Указанные файлы настолько не связаны, что кажется, что они не могут быть связаны с встраиванием или оптимизацией. Единственная закономерность, которую я могу заметить, заключается в том, что она кажется смещенной некоторыми файлами (в воображаемом отсортированном по алфавиту списке исходных файлов, из которых была создана сборка). Однако эта закономерность не соответствует 100%, и кажется иррациональным, что это связано с источником проблемы.

Если я создаю объект System.Diagnostics.Debug из исключения, он содержит ту же ошибочную информацию.

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

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


person Duckers    schedule 13.01.2014    source источник
comment
Есть ли шанс, что вы могли бы опубликовать небольшую и полную программу, которую мы могли бы протестировать? Например, вы можете жестко закодировать язык из двух строк или что-то в этом роде, вам не нужно будет публиковать полный компилятор.   -  person Lasse V. Karlsen    schedule 13.01.2014
comment
Вы компилируете с флагом optimize?   -  person poy    schedule 13.01.2014
comment
Эндрю: На компиляторе или на сборщике? Я пытался установить как можно больше флагов да, я хочу как можно больше отладочной информации, но я мог не знать о них всех.   -  person Duckers    schedule 13.01.2014
comment
Звучит довольно интересно. Просто очень длинный выстрел здесь. Как компилятор узнает, какая строка выдает ошибку? Я имею в виду расположение файлов. Несколько раз я замечал неправильные номера строк, которые, по моему мнению, были кодировкой и расположением файлов. Я не буду ставить на это свои деньги, хотя.   -  person danish    schedule 13.01.2014
comment
@ LasseV.Karlsen Вам просто нужен его исходный код, а затем разбогатеть, продавая свой собственный компилятор. (шутить)   -  person danish    schedule 13.01.2014
comment
@danish: Это известно, потому что при создании инструкций CIL вы можете связать документ (файл) и номер строки с блоком инструкций (обычно с каждым оператором или корнем выражения). Эти метаданные хранятся отдельно и используются отладчиками. Я также предполагаю, что он используется для создания трассировки стека для исключений, но, поскольку в моем случае это не работает, это часть того, что здесь обсуждается.   -  person Duckers    schedule 13.01.2014
comment
@Duckers Не могли бы вы взглянуть на этот проект — dropbox.com/sh/jvjdyroivzl9tgt/7csRtroedq - а скажите, у него такая же проблема, как у вас? И, очевидно, скажите мне, если я сделал что-то ужасно неправильное, это первый раз, когда я испортил запись символов с помощью динамических типов.   -  person Lasse V. Karlsen    schedule 13.01.2014
comment
@LasseV.Karlsen: Большое спасибо за усилия по созданию тестового примера. На самом деле я параллельно работал над очень похожим тестовым примером. Я не могу воспроизвести проблему с этими гораздо более простыми случаями, включая ваш случай. В нашем реальном случае есть много сотен исходных файлов и тысячи сгенерированных методов. Сейчас пробую разные вещи, чтобы посмотреть, смогу ли я воспроизвести это, но я боюсь, что ошибка вызвана сложностью/количеством данных или, возможно, другими косвенными проблемами в реальном случае использования.   -  person Duckers    schedule 13.01.2014
comment
К вашему сведению, в моем тестовом примере я использую несколько документов/исходных файлов, и это также не вызывает ошибку.   -  person Duckers    schedule 13.01.2014
comment
На самом деле, в примере проекта, когда выбрасывалось исключение в инструкции Div в строке 4, отладчик останавливался на строке 1, так что я надеялся, что будет то же самое.   -  person Lasse V. Karlsen    schedule 13.01.2014
comment
что произойдет, если вы удалите символы новой строки из динамического исходного кода? дают ли новые номера строк в стеке вызовов новые подсказки? Я написал интерпретатор ECMAScript, и вначале мой лексер не всегда правильно устанавливал строки и столбцы. Я не говорю, что С# делает это, но я смог увидеть, что существует шаблон смещений. Это было не просто случайно. Таким образом, создание динамического источника высотой в 1 строку может помочь вам увидеть что-то новое.   -  person drankin2112    schedule 14.01.2014
comment
@LasseV.Karlsen: Когда я проследил это, исключение было выдано в строке 3, как и должно быть. То же самое было указано в трассировке стека объекта Exception. Чтобы было ясно: в моем реальном случае отладчик также сообщает об исключении в правой строке, это просто трассировка стека, сгенерированная в объекте исключения, которая содержит ошибочную информацию:/   -  person Duckers    schedule 14.01.2014
comment
ХОРОШО. Тогда я удалю проект из своего дропбокса.   -  person Lasse V. Karlsen    schedule 14.01.2014
comment
Я не знаю, делаете ли вы это, но одна вещь, которая испортит трассировку стека, - это прямое повторное создание пойманного исключения в блоке catch, то есть try { // Do something } catch (Exception ex) { throw ex; }. Если да, используйте throw new Exception("My message.", ex); для переноса или просто используйте вместо этого throw; для операторов throw.   -  person JamieSee    schedule 03.03.2014


Ответы (1)


Хорошо, я НЕ смог понять, что вызвало проблему, и как заставить .NET вести себя правильно, но, по крайней мере, я смог найти обходной путь, который мог бы также работать для других, столкнувшихся с той же проблемой.

  1. Когда я генерирую байт-код CIL, я создаю отдельную базу данных, которая сопоставляет имена методов (полный путь) и смещение IL с исходными именами файлов и номерами строк.

  2. При обнаружении исключений я просматриваю трассировку стека и использую только информацию GetMethod() и GetILOffset() из объектов фрейма стека. Эта информация из среды CLR оказывается правильной, даже если GetFileName() и GetFileLineNumber() неверны.

  3. Затем я для каждого кадра стека использую имя метода и смещение IL, полученное из исключения, для поиска в моей сгенерированной базе данных, чтобы определить фактическое имя файла и номер строки для каждого кадра стека, о котором у меня есть информация.

  4. Фреймы, информацию о которых я не нашел в базе данных, обычно представляют собой кадры стека в предварительно скомпилированных модулях .NET, для которых информация, полученная из CLR, на самом деле верна. Для этих кадров я использую GetFileName() и GetFileLineNumber() непосредственно в кадре стека.

person Duckers    schedule 14.01.2014