Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf
ГЛАВА 6 Улучшенная отладка приложений .NET в среде Visual Studio .NET |
231 |
|
|
комментариев над соответствующими инструкциями MSIL. Получить сведения обо всех ключах можно, указав в командной строке ключ /?. Для запуска ILDASM со всеми нужными мне ключами я использую командный файл:
ildasm /adv /source %1
Чтобы увидеть код MSIL, соответствующий конкретному элементу, нужно дважды щелкнуть его значок. В появившемся окне в зависимости от выбранного элемен та вы увидите его дизассемблированный код, информацию об объявлениях или общую информацию. Если окно выглядит так, как на рис. 6 10, мы можем присту пить к изучению MSIL. Одно из достоинств ILDASM в том, что он поддерживает функциональность буксировки, благодаря чему вы сможете легко переключаться между модулями.
Наконец я хочу рассказать вам, что сделать, чтобы при работе с ILDASM вы видели код MSIL и свой код C#, J#, Managed Extensions for C++ или Visual Basic .NET одновременно. Если при отладке управляемого приложения вы наблюдали в от ладчике за окном Disassembly, вы, вероятно, видели код на одном из языков .NET и только ассемблерные команды Intel. Причина в том, что MSIL компилируется по требованию (just in time, JIT), так что на самом деле выполняется только ма шинный язык ассемблера, а не MSIL. Что делает ILDASM действительно интерес ным, так это то, что по крайней мере в одном аспекте он достиг совершенства: это по настоящему «обратимый» (round trip) дизассемблер!
Рис. 6 10. MSIL для метода
Обратимый дизассемблер позволяет дизассемблировать двоичный файл и тут же ассемблировать его, собрав приложение заново. Так как в состав .NET входит ILASM, ассемблер промежуточного языка Microsoft (Microsoft Intermediate Language Assembler), то у вас есть все, чтобы видеть MSIL и код своих программ C#/J#/Managed Extensions for C++/Visual Basic .NET одновременно. Это позволяет лучше узнать
232 ЧАСТЬ II Производительная отладка
тонкости работы программы. Дизассемблируйте файл при помощи ILDASM, задав ключи командной строки /SOURCE и /OUT= и указав имя файла вывода с расшире нием .IL. Скомпилируйте файл при помощи ILASM с ключом /DEBUG. Теперь вы сможете просматривать MSIL в отладчике Visual Studio .NET и видеть соответству ющий код C#/J#/Managed Extensions for C++/Visual Basic .NET как комментарии. Открыв окно Disassembly, вы увидите, как высокоуровневый язык компилируется в MSIL и как инструкции MSIL компилируются по требованию в язык ассемблера процессоров Intel. Листинг 6 2 представляет собой дизассемблированный метод программы ShowBPs (которую можно найти на CD, прилагаемом к книге) вместе с соответствующим исходным кодом, указанным в комментариях.
Листинг 6-2. Объединенный вывод исходного кода и кода MSIL
.method private instance void btnConditionalBreaks_Click(object sender,
class [mscorlib]System.EventArgs e) cil managed
{
// Code size |
139 (0x8b) |
.maxstack 4 |
|
.locals init |
([0] int32 i, |
[1] |
int32 j, |
[2] |
string[] _Vb_t_array_0, |
[3] |
class [System.Windows.Forms] |
|
System.Windows.Forms.TextBox _Vb_t_ref_0) |
//000120:
//000121: Private Sub btnConditionalBreaks_Click _
// |
|
( ByVal sender As System.Object, _ |
// |
|
ByVal e As System.EventArgs) _ |
// |
|
Handles btnConditionalBreaks.Click |
IL_0000: |
nop |
|
//000122: |
Dim i As Integer = 0 |
|
IL_0001: |
ldc.i4.0 |
|
IL_0002: |
stloc.0 |
|
//000123: |
Dim j As Integer = 0 |
|
IL_0003: |
ldc.i4.0 |
|
IL_0004: |
stloc.1 |
|
//000124: |
|
|
//000125: ' Очистка поля вывода. |
||
//000126: edtOutput.Clear() |
||
IL_0005: |
ldarg.0 |
|
IL_0006: |
callvirt |
instance class |
|
[System.Windows.Forms]System.Windows.Forms.TextBox |
|
|
|
ShowBPs.ShowBPsForm::get_edtOutput() |
IL_000b: |
callvirt |
instance void |
|
[System.Windows.Forms]System.Windows.Forms.TextBoxBase::Clear() |
|
IL_0010: |
nop |
|
//000127: |
|
|
//000128: ' Я записал оба цикла в одной строке, чтобы показать, |
||
// |
' как устанавливать точки прерываний для части строки. |
|
ГЛАВА 6 |
Улучшенная отладка приложений .NET в среде Visual Studio .NET |
233 |
|
|
|
|
|
|
|
|
|
//000129: For i = 1 To |
5 : For j = 1 To 5 |
|
|
IL_0011: |
ldc.i4.1 |
|
|
IL_0012: |
stloc.0 |
|
|
IL_0013: |
ldc.i4.1 |
|
|
IL_0014: |
stloc.1 |
|
|
//000130: ' Вывод текста |
|
||
//000131: edtOutput.Text += "i = " + i.ToString() + " j = " + _ |
|
||
// |
j.ToString() + vbCrLf |
|
|
IL_0015: |
ldarg.0 |
|
|
IL_0016: |
callvirt |
instance class |
|
|
[System.Windows.Forms]System.Windows.Forms.TextBox |
|
|
|
|
ShowBPs.ShowBPsForm::get_edtOutput() |
|
IL_001b: |
stloc.3 |
|
|
IL_001c: |
ldloc.3 |
|
|
IL_001d: |
ldc.i4.6 |
|
|
IL_001e: |
newarr |
[mscorlib]System.String |
|
IL_0023: |
stloc.2 |
|
|
IL_0024: |
ldloc.2 |
|
|
IL_0025: |
ldc.i4.0 |
|
|
IL_0026: |
ldloc.3 |
|
|
IL_0027: |
callvirt |
instance string |
|
|
[System.Windows.Forms]System.Windows.Forms.TextBox::get_Text() |
|
|
IL_002c: |
stelem.ref |
|
|
IL_002d: |
nop |
|
|
IL_002e: |
ldloc.2 |
|
|
IL_002f: |
ldc.i4.1 |
|
|
IL_0030: |
ldstr |
"i = " |
|
IL_0035: |
stelem.ref |
|
|
IL_0036: |
nop |
|
|
IL_0037: |
ldloc.2 |
|
|
IL_0038: |
ldc.i4.2 |
|
|
IL_0039: |
ldloca.s |
i |
|
IL_003b: |
call |
instance string [mscorlib]System.Int32::ToString() |
|
IL_0040: |
stelem.ref |
|
|
IL_0041: |
nop |
|
|
IL_0042: |
ldloc.2 |
|
|
IL_0043: |
ldc.i4.3 |
|
|
IL_0044: |
ldstr |
" j = " |
|
IL_0049: |
stelem.ref |
|
|
IL_004a: |
nop |
|
|
IL_004b: |
ldloc.2 |
|
|
IL_004c: |
ldc.i4.4 |
|
|
IL_004d: |
ldloca.s |
j |
|
IL_004f: |
call |
instance string [mscorlib]System.Int32::ToString() |
|
IL_0054: |
stelem.ref |
|
|
IL_0055: |
nop |
|
|
IL_0056: |
ldloc.2 |
|
|
IL_0057: |
ldc.i4.5 |
|
|
IL_0058: |
ldstr |
"\r\n" |
|
IL_005d: |
stelem.ref |
|
|
|
|
|
|
см. след. стр.
234 |
ЧАСТЬ II Производительная отладка |
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IL_005e: |
nop |
|
|
|
|
IL_005f: |
ldloc.2 |
|
|
|
|
IL_0060: |
call |
string [mscorlib]System.String::Concat(string[]) |
|
|
|
IL_0065: |
callvirt |
instance void |
|
|
|
[System.Windows.Forms]System.Windows.Forms.TextBox::set_Text(string) |
|
||
|
|
IL_006a: |
nop |
|
|
|
//000132: ' |
Обновление текста в поле вывода. |
|
||
|
//000133: edtOutput.Update() |
|
|||
|
|
IL_006b: |
ldarg.0 |
|
|
|
|
IL_006c: |
callvirt |
instance class |
|
|
|
|
[System.Windows.Forms]System.Windows.Forms.TextBox |
|
|
|
|
|
|
ShowBPs.ShowBPsForm::get_edtOutput() |
|
|
|
IL_0071: |
callvirt |
instance void |
|
|
|
|
[System.Windows.Forms]System.Windows.Forms.Control::Update() |
|
|
|
|
IL_0076: |
nop |
|
|
|
//000134: Next j |
|
|
||
|
|
IL_0077: |
nop |
|
|
|
|
IL_0078: |
ldloc.1 |
|
|
|
|
IL_0079: |
ldc.i4.1 |
|
|
|
|
IL_007a: |
add.ovf |
|
|
|
|
IL_007b: |
stloc.1 |
|
|
|
|
IL_007c: |
ldloc.1 |
|
|
|
|
IL_007d: |
ldc.i4.5 |
|
|
|
|
IL_007e: |
ble.s |
IL_0015 |
|
|
//000135: Next i |
|
|
||
|
|
IL_0080: |
nop |
|
|
|
|
IL_0081: |
ldloc.0 |
|
|
|
|
IL_0082: |
ldc.i4.1 |
|
|
|
|
IL_0083: |
add.ovf |
|
|
|
|
IL_0084: |
stloc.0 |
|
|
|
|
IL_0085: |
ldloc.0 |
|
|
|
|
IL_0086: |
ldc.i4.5 |
|
|
|
|
IL_0087: |
ble.s |
IL_0013 |
|
|
//000136: |
End Sub |
|
|
|
|
|
IL_0089: |
nop |
|
|
|
|
IL_008a: |
ret |
|
|
|
|
|
|
||
|
} // end of |
method ShowBPsForm::btnConditionalBreaks_Click |
|
||
|
|
|
|
|
|
Основы CLR
Прежде чем вы начнете продираться сквозь лес команд MSIL, я должен вкратце пояснить работу CLR. CLR по сути является процессором для команд MSIL. В то время как традиционные процессоры для выполнения всех команд используют регистры и стеки, CLR использует только стек. Это значит, что при сложении чисел CLR должна загрузить оба числа в стек и вызвать команду их сложения. Команда сложения удаляет два числа из стека и помещает результат в его вершину. Если вы по складу характера похожи на меня, возможно, вам захочется изучить дей
ГЛАВА 6 Улучшенная отладка приложений .NET в среде Visual Studio .NET |
235 |
|
|
ствительную реализацию CLR. Для знакомства с системой, похожей на CLR и в то же время достаточно небольшой, чтобы ее можно было понять, обратитесь к книге Брайана Кернигана и Роба Пайка (Brian Kernighan and Rob Pike) «The Unix Program ming Environment» (Prentice Hall, 1984). В этой книге они разрабатывают кальку лятор высшего порядка (higher order calculator, hoc) — нетривиальный, написан ный на C пример системы, основанной на стеке. Если вы хотите увидеть реаль ную реализацию CLR, загрузите из Интернета общеязыковую инфраструктуру с открытым кодом (Shared Source Common Language Infrastructure) — также извес тную как «Rotor», — кросс платформенную CLR, основанную на стандартах Евро пейской ассоциации производителей компьютеров (European Computer Manufac turers’ Association). Она включает огромный объем кода, но вы сможете узнать, как все работает. Загрузить Shared Source CLI можно по адресу: http://msdn.micro soft.com/netframework.
В ячейках стека CLR может храниться любой тип значения. Копирование зна чений из памяти в стек называется загрузкой (loading), а копирование значений стека в память — сохранением (storing). В отличие от стека процессоров Intel стек CLR не хранит локальных переменных — они хранятся в памяти. Стеки локальны по отношению к работающим методам, и CLR сохраняет их между вызовами ме тодов. Наконец, в стек также помещаются возвращаемые методами значения. Те перь, когда я описал вкратце работу CLR, перейдем прямо к командам.
MSIL, локальные переменные и параметры
Думаю, что прежде чем перейти к более сложным вопросам, нам стоит рассмот реть простейшую программу, написанную на MSIL, т. е. «Hello World!». Так вы смо жете увидеть программу MSIL в действии, и я смогу начать объяснение элемен тов, которые будут встречаться в информации, выводимой ILDASM. Полная про грамма «Hello World!» приведена в листинге 6 3; вы найдете ее на CD, прилагае мом к книге, под именем HelloWorld.IL. Даже если вы впервые видите MSIL, вы легко поймете, что происходит. Все слова, начинающиеся с точки, являются ди рективами ассемблеру ILASM.EXE, а для комментариев служат стандартные двой ные слэши C#.
Листинг 6-3. HelloWorld.IL
//Для запуска программы нужна директива .assembly.
.assembly hello {}
//Объявление main в стиле "C".
.method static public void main() il managed
{
//Эта директива указывает ядру исполняющей подсистемы, откуда
//начинается выполнение программы. Нужна одна на программу.
//Кроме того, она может использоваться и в случае методов.
.entrypoint
//ILASM этого не требует, но ILDASM всегда показывает
//эту директиву, поэтому я включил ее в программу.
см. след. стр.
236 ЧАСТЬ II Производительная отладка
.maxstack 1
//Помещаем строку в стек. ldstr "Hello World from IL!"
//Вызываем метод Writeline класса System.Console.
call |
void [mscorlib]System.Console::WriteLine(class System.String) |
//Возвращаемся в вызывающую программу. Если про это забыть, файл
//скомпилируется, но ядро исполняющей подсистемы сгенерирует исключение.
ret
}
Важными элементами HelloWorld.IL являются три последние строки. Команда ldstr помещает строку в стек. Помещение элементов в стек называется загрузкой (loading), поэтому, когда вам будут встречаться команды, начинающиеся с букв «ld», вы сможете сразу догадаться, что они берут значения из памяти и помещают их в стек. Извлечение элементов из стека и размещение их в памяти в программе «Hello World!» не использовалось, однако я все равно скажу, что этот процесс называет ся сохранением (storing) и все команды такого типа начинаются с букв «st». Воо ружившись знанием этих двух фактов и помощью, предоставляемой ILDASM пу тем помещения жестко закодированных строк в дизассемблированный листинг, вы можете всерьез заняться восстановлением алгоритма.
Теперь, когда я вкратце рассказал об ассемблере MSIL, самое время узнать, ка кая же информация, предоставлямая ILDASM, поможет вам разобраться в тонко стях работы программы.
Узнать параметры и возвращаемые типы при помощи ILDASM проще просто го: дизассемблер указывает их, когда вы дважды щелкаете имя метода. На самом деле все еще лучше, потому что дизассемблер показывает действительные имена параметров. Значения классов изображаются в формате [module]namespace.class. Базовые типы, такие как int, char и т. д., изображаются в виде типа специфическо го класса. Например, int имеет вид Int32.
Локальные переменные также очень легко определяются. Если у вас есть сим волы отладки, то будут указаны действительные имена переменных. Однако ди зассемблированные системные классы будут выглядеть примерно так:
.locals (class [mscorlib]Microsoft.Win32.RegistryKey V_0,
class System.Object V_1,
int32 V_2,
int32 V_3)
После директивы .locals в круглых скобках указывается полный список пара метров, разделенных запятыми. Вслед за типом располагается поле вида V_#, где
# — номер каждого параметра. Как вы увидите, номер используется в довольно многих командах. В предыдущем фрагменте [mscorlib] указывает на конкретную DLL, к которой относится данный класс.
ГЛАВА 6 Улучшенная отладка приложений .NET в среде Visual Studio .NET |
237 |
|
|
Важные команды
Вместо того чтобы приводить огромную таблицу команд, я расскажу про наибо лее важные и приведу примеры их применения. Я начну с команд загрузки и объяс ню все их варианты. Рассматривая другие типы команд, я не буду касаться тех их аспектов, которые не отличаются от команд загрузки, и просто продемонстрирую их использование. Назначение команд, которые я не описываю, просто понять по их названиям.
ldc |
Загрузка численной константы |
Помещает в стек жестко закодированное число. Она имеет формат ldc.size[.num], где size — размер значения в байтах, а num — специальный короткий код для 4 байтных целых чисел, находящихся в пределах от –128 до 127 (если size имеет значение i4). Поле size может иметь значения i4 (4 байтовое целое число), i8 (8 байтовое целое число), r4 (4 байтовое число с плавающей точкой) или r8 (8 бай товое число с плавающей точкой). Эта команда имеет много форм, что призвано уменьшить число кодов операций.
ldc.i4.0 |
// Загрузка в стек |
0 с использованием |
|
// специальной формы. |
|
ldc.r8 2.1000000000000001 |
// Загрузка в стек |
2.1000000000000001. |
ldc.i4.m1 |
// Загрузка в стек |
–1. Это специальная |
|
// форма команды. |
|
ldc.i4.s 9 |
// Загрузка в стек |
9 с использованием |
|
// короткой формы. |
|
ldarg Загрузка аргумента ldarga Загрузка адреса аргумента
Номера аргументов начинаются с 0. Для экземплярных методов аргументом 0 является указатель this, а первый аргумент имеет номер 1, а не 0.
ldarg.2 |
|
// Загрузка в стек аргумента 2. При использовании |
|
|
// этой формы максимальным номером является 3. |
ldarg.s 6 |
|
// Загрузка в стек аргумента 6. Эта форма применяется |
|
|
// для всех номеров аргументов больше 4 (включительно). |
ldarga.s newSample |
// Загрузка адреса newSample |
|
ldloc |
Загрузка локальной переменной |
|
ldloca |
Загрузка адреса локальной переменной |
|
Эти команды загружают в стек указанную локальную переменную. Все локальные переменные определяются порядком, в котором они указаны в объявлении locals. Команда ldloca загружает адрес локальной переменной.
ldloc.0 |
|
// Загрузка |
в |
стек |
локальной переменной |
с индексом 0. |
При |
|
|
|
// |
использовании этой формы максимальным индексом является 3. |
|||||
ldloc.s |
V_6 |
// |
Загрузка |
в |
стек |
локальной переменной |
с индексом 6. |
|
//Эта форма используется для всех переменных
//с индексом больше или равным 4.
ldloca.s V_5 // Загрузка в стек адреса локальной переменной с индексом 5.
238 ЧАСТЬ II Производительная отладка
ldfld Загрузка поля объекта класса ldsfld Загрузка статического поля класса
Эти команды загружают в стек нормальное или статическое поле объекта. На языке MSIL дизассемблированный объект представляется очень просто, так как в нем указывается полное значение поля. Команда ldflda загружает адрес поля.
//Загрузка поля _Originator класса System.Reflection.AssemblyName.
//Заметьте: в команде указывается и тип поля.
ldfld |
unsigned int8[] System.Reflection.AssemblyName::_Originator |
|
// Загрузка пустой строки из |
класса System.String. |
|
ldsfld |
class System.String |
[mscorlib]System.String::Empty |
ldelem Загрузка элемента массива
Загружает в стек указанный элемент одномерного массива, начинающегося с ну левого индекса. Если две предыдущих команды поместили в стек массив и его индекс (в указанном порядке), ldelem удаляет массив и индекс из стека и помеща ет в вершину стека указанный элемент. За ldelem следует поле типа. Наиболее ча стым полем типа в откомпилированной библиотеке базовых классов является ldelem.ref, которое получает элемент как объект. Другими распространенными типами этой команды являются ldelem.i4 для получения элемента как знакового целого 4 байтового числа и ldelem.i8 для получения 8 байтового целого числа.
.locals (System.String[] V_0, // Символы [] указывают на объявление массива.
int32 V_1 ) |
// Индекс. |
|
|
... |
// Здесь нужно заполнить параметр V_0. |
||
ldloc.0 |
// Загрузка массива. |
|
|
ldc.i4.0 |
// |
Загрузка нулевого |
индекса. |
ldelem.ref |
// |
Получение объекта |
с нулевым индексом. |
ldlen Загрузка длины (lLength) массива
Эта команда удаляет из стека одномерный массив, начинающийся с нулевого ин декса, и помещает в стек длину массива.
// Загрузка поля атрибута, которое является массивом. ldfld class System.ComponentModel.MemberAttribute[]
System.ComponentModel.MemberDescriptor::attributes
stloc.1
ldloc.1 ldlen
starg Сохранение значения в слоте аргумента
Берет значение из вершины стека и помещает его в указанный аргумент.
starg.s categoryHelp |
// Сохранение |
значения из вершины стека |
|
|
// |
в свойстве |
categoryHelp. Во всех командах |
|
// |
starg используется форма .s. |
|
|
ГЛАВА 6 Улучшенная отладка приложений .NET в среде Visual Studio .NET |
239 |
|
|
|
stelem |
Сохранение элемента массива |
|
Если три предыдущих команды помещают в стек одномерный массив, начинаю щийся с нулевого индекса, индекс и значение (в указанном порядке), stelem при водит размерный тип к типу соответствующего массива и помещает значение в массив. Команда stelem удаляет все три элемента из стека. Как и при команде ldelem, преобразование определяется полем типа. Чаще всего используется stelem.ref, вы полняющая преобразование размерного типа в объект.
.method public hidebysig specialname
instance void |
set_MachineName(class System.String 'value') il managed |
|
{ |
|
|
.maxstack |
|
4 |
.locals (class System.String[] V_0) |
||
|
|
|
ldloc.0 |
|
// Загрузка массива в стек. |
ldc.i4.1 |
|
// Загрузка индекса: константы 1. |
ldarg.1 |
|
// Загрузка аргумента: строки. |
stelem.ref |
// Сохранение элемента. |
|
|
|
|
stfld |
Сохранение значения в поле объекта |
|
Берет значение из вершины стека и помещает его в поле объекта. Как и при за грузке поля, указывается полная ссылка.
stfld |
int32[] System.Diagnostics.CategoryEntry::HelpIndexes |
ceq |
Сравнение на равенство |
Сравнивает два верхних значения, находящихся в стеке. Оба элемента удаляются из стека, и если их значения равны, в стек заносится 1; в противном случае в стек заносится 0.
ldloc.1 |
// Загрузка |
локальной |
переменной с индексом 1. |
|
ldc.i4.0 |
// |
Загрузка |
константы |
0. |
ceq |
// |
Сравнение элементов на равенство. |
||
cgt |
Сравнение по условию «больше чем» |
|
||
Также сравнивает два верхних значения стека. Оба элемента удаляются из стека, и, если значение, которое было занесено в стек первым, больше второго, в стек заносится 1, в противном случае — 0. Команда cgt может также иметь модифика тор .un, указывающий, что сравнение является беззнаковым или неупорядочен ным.
// Получение числа элементов в |
наборе. |
|
call instance int32 System.Diagnostics. |
||
CounterCreationDataCollection::get_Count() |
||
ldc.i4.0 |
// |
Загрузка константы 0. |
cgt |
// |
Число элементов (count) больше 0? |
240 |
ЧАСТЬ II Производительная отладка |
|
|
clt |
Сравнение по условию «меньше чем» |
Идентична cgt за исключением того, что 1 заносится в стек, если первое значе ние меньше второго.
// Получение уровня трассировки.
call instance value class System.Diagnostics.TraceLevel
|
System.Diagnostics.TraceSwitch::get_Level() |
|
ldc.i4.1 |
|
// Загрузка константы 1. |
clt |
|
// Уровень трассировки меньше 1? |
br |
Безусловный переход |
|
Эта команда MSIL аналогична оператору goto. |
||
br.s IL_008d |
// Переход к смещению в методе. |
|
brfalse Переход, если ложь brtrue Переход, если истина
Обе команды проверяют значение, находящееся в вершине стека, и выполняют переход, если выполняется соответствующее условие. Команда brtrue совершает переход, только когда это значение равняется 1, а brfalse — только когда оно равно 0. Обе удаляют значение из вершины стека.
ldloc.1 |
|
// Загрузить локальную переменную с индексом 1. |
brfalse.s |
IL_006a |
// Переход, если 0. |
ldloc.2 |
|
// Загрузка локальной переменной с индексом 2. |
brtrue.s |
IL_006c |
// Переход, если 1. |
beq |
Переход, если равно |
|
bgt |
Переход, если больше или равно |
|
ble |
Переход, если меньше или равно |
|
blt |
Переход, если меньше |
|
bne |
Переход, если не равно |
|
Все эти команды берут два значения, находящиеся в вершине стека, и сравнива ют самое верхнее значение со вторым по счету. Во всех случаях сначала произ водится сравнение, после чего следует булев переход. Так, команда bgt эквивалентна последовательности команд cgt и brtrue.
conv Преобразование типа данных
Преобразует значение, находящееся в вершине стека, к новому типу и помещает в вершину преобразованное значение. Тип, к которому преобразуется значение, указывается после команды conv. Например, conv.u4 выполняет преобразование значения к 4 байтовому беззнаковому целому. Если после команды conv указан только тип, она не генерирует исключений при всех видах переполнения. Если между conv и типом указано поле .ovf (например, conv.ovf.u8), переполнение при водит к генерированию исключения.
ldloc.0 |
// |
Загрузка локальной переменной с индексом 0 (массив). |
ldlen |
// |
Получение длины массива. |
