Формат файла .NET – внутреннее устройство сигнатур (часть 1) - Начало работы
ОГЛАВЛЕНИЕ
3. Начало работы
В данном разделе вы изучите несколько вещей, нужных для понимания остальной части статьи, поэтому не недооценивайте ее: содержащаяся здесь информация будет широко использоваться в следующих разделах. Термины, связанные с сигнатурами, но не рассмотренные здесь, будут попутно объяснены позже.
3.1 Проводник CFF
Для просмотра метаданных .NET и кода сигнатур будет использоваться проводник CFF, написанный Дэниэлом Пистелли. Проводник CFF – бесплатный инструмент, умеющий просматривать и редактировать заголовки PE, ресурсы и некоторые поля и флаги метаданных .NET. Вы можете загрузить его с данного сайта. На рисунке ниже показан проводник CFF, работающий с загруженной тестовой сборкой. Обычно сигнатуры индексируются с помощью столбца Сигнатура, красным кругом выделено расположение сигнатуры MethodDefSig, индексируемой Method.Signature в куче #Blob, которую можно просмотреть, нажав на элемент, обведенный зеленым кругом.
Рисунок 1.Работающий проводник CFF
3.2 Порядок следования байтов
Лучшее описание дано в Википедии:
"В вычислениях, порядок следования байтов – это упорядочивание байтов (иногда битов), используемое для представления некоторого типа данных. Типичные случаи – порядок, в котором целые значения сохраняются в виде байтов в памяти компьютера (по отношению к данной схеме адресации памяти) и порядок передачи по сети или другой среде. Если говорить именно о байтах, порядок следования байтов также называют порядком байтов".
В данном случае порядок следования байтов рассматривается как порядок байтов данных (обычно целочисленных), хранящихся в файле. Существует два метода (порядка) представления данных в файле, с порядком следования байтов, начиная со старшего, и с порядком следования байтов, начиная с младшего, файл PE/.NET использует оба метода, поэтому каждый из них рассматривается ниже.
Порядок следования байтов, начиная со старшего
В данном методе упорядочивания самый старший байт сохраняется в позиции файла с наименьшим смещением, значение следующего байта сохраняется в следующем смещении файла, и т.д. В примере ниже значение 0x1B5680DA сохраняется в смещении 100, при этом память выглядит так:
100 |
101 |
102 |
103 |
||
... |
1B |
56 |
80 |
DA |
... |
Порядок следования байтов, начиная с младшего
По сравнению с порядком следования байтов, начиная со старшего, порядок следования байтов, начиная с младшего, сохраняет данные в обратном порядке, т.е. самый младший байт сохраняется в наименьшем смещении. В данном случае значение 0x1B5680DA, сохраненное в формате с порядком следования байтов, начиная с младшего, будет выглядеть так:
100 |
101 |
102 |
103 |
||
... |
DA |
80 |
56 |
1B |
... |
3.3 Сжатое целое
Сигнатуры сжимаются перед сохранением в куче #Blob путем сжатия целых чисел, встроенных в сигнатуру. В отличие от нормальных целых чисел, имеющих фиксированный размер, сжатые целые используют ровно столько места, сколько нужно, почти все сигнатуры используют сжатие целых вместо нормальных целых чисел фиксированного размера. Так как подавляющее большинство чисел в сигнатурах лежит ниже 128, экономия места существенная. Ниже приведен алгоритм кодирования, скопированный из спецификации:
Если значение лежит между 0 (0x00) и 127 (0x7F) включительно, кодировать в виде однобайтового целого (бит 7 пустой, значение хранится в битах от 6 до 0).
Если значение лежит между 28 (0x80) и 214 - 1 (0x3FFF) включительно, кодировать в виде 2-байтового целого с установленным битом 15, пустым битом 14 (значение хранится в битах от 13 до 0).
В ином случае кодировать в виде 4-байтового целого, с установленным битом 31, установленным битом 30, пустым битом 29 (значение хранится в битах от 28 до 0).
Пустая строка должна представляться с помощью зарезервированного одиночного байта 0xFF, с отсутствием последующих данных
Пример 1
Значение меньше 0x80, значит это первый случай, удаляем три ненужных байта.
Исходное значение (32-бит) |
Сжатое значение |
Сэкономленные байты |
|
Шестнадцатеричный |
00 00 00 03 |
0x03 |
3 |
Двоичный |
00000000 0000000 00000000 00000011 |
00000011 |
- |
Пример 2
Такой же, как пример 1.
Исходное значение (32-бит) |
Сжатое значение |
Сэкономленные байты |
|
Шестнадцатеричный |
00 00 00 7F |
7F |
3 |
Двоичный |
00000000 0000000 00000000 01111111 |
01111111 |
- |
Пример 3
В этом примере исходное значение равняется 0x80, хотя одного байта достаточно для сохранения 0x80, использование сжатого целого требует освобождения последнего бита, поэтому для сохранения значения 0x80 в виде сжатого целого нужно иметь дополнительный байт.
Исходное значение (32-бит) |
Сжатое значение |
Сэкономленные байты |
|
Шестнадцатеричный |
00 00 00 80 |
80 80 |
2 |
Двоичный |
00000000 0000000 00000000 10000000 |
10000000 10000000 |
- |
Пример 4
Удаляем два ненужных байта.
Исходное значение (32-бит) |
Сжатое значение |
Сэкономленные байты |
|
Шестнадцатеричный |
00 00 2E 57 |
AE 57 |
2 |
Двоичный |
00000000 0000000 00101110 01010111 |
10101110 01010111 |
- |
Разумеется, сжатие обходится некоторой ценой, несколько битов должно быть зарезервировано, чтобы они указывали, сколько байтов занимает сжатое целое. Таким образом, максимальное закодированное целое имеет длину 29 битов со значением 0x1FFFFFFF. Сжатые целые физически кодируются с использованием порядка следования байтов, начиная со старшего.
3.4 Константы
Следующий список перечисляет распространенные константы, часто используемые почти во всех сигнатурах. В следующих частях статьи мы будем ссылаться на них очень часто по сокращениям, используя только последний член имени, например, ELEMENT_TYPE_I8 как I8, ELEMENT_TYPE_STRING как STRING, и т.д.
Имя |
Значение |
Примечания |
ELEMENT_TYPE_END |
0x00 |
Отмечает конец списка |
ELEMENT_TYPE_VOID |
0x01 |
System.Void |
ELEMENT_TYPE_BOOLEAN |
0x02 |
System.Boolean |
ELEMENT_TYPE_CHAR |
0x03 |
System.Char |
ELEMENT_TYPE_I1 |
0x04 |
System.SByte |
ELEMENT_TYPE_U1 |
0x05 |
System.Byte |
ELEMENT_TYPE_I2 |
0x06 |
System.Int16 |
ELEMENT_TYPE_U2 |
0x07 |
System.UInt16 |
ELEMENT_TYPE_I4 |
0x08 |
System.Int32 |
ELEMENT_TYPE_U4 |
0x09 |
System.UInt32 |
ELEMENT_TYPE_I8 |
0x0A |
System.Int64 |
ELEMENT_TYPE_U8 |
0x0B |
System.UInt64 |
ELEMENT_TYPE_R4 |
0x0C |
System.Single |
ELEMENT_TYPE_R8 |
0x0D |
System.Double |
ELEMENT_TYPE_STRING |
0x0E |
System.String |
ELEMENT_TYPE_PTR |
0x0F |
Неуправляемый указатель, сопровождаемый элементом Type(тип). |
ELEMENT_TYPE_BYREF |
0x10 |
Управляемый указатель, сопровождаемый элементом Type. |
ELEMENT_TYPE_VALUETYPE |
0x11 |
Модификатор типа значения, сопровождаемый меткой TypeDef или TypeRef |
ELEMENT_TYPE_CLASS |
0x12 |
Модификатор типа класса, сопровождаемый меткой TypeDef или TypeRef |
ELEMENT_TYPE_VAR |
0x13 |
Обобщенный параметр в определении обобщенного типа, представленный в виде числа |
ELEMENT_TYPE_ARRAY |
0x14 |
Модификатор типа многомерного массива. |
ELEMENT_TYPE_GENERICINST |
0x15 |
Реализация обобщенного типа. Сопровождается тип тип-арг-число тип-1 ... тип-n |
ELEMENT_TYPE_TYPEDBYREF |
0x16 |
Типовая ссылка. |
ELEMENT_TYPE_I |
0x18 |
System.IntPtr |
ELEMENT_TYPE_U |
0x19 |
System.UIntPtr |
ELEMENT_TYPE_FNPTR |
0x1B |
Указатель на функцию, сопровождаемый полной сигнатурой метода |
ELEMENT_TYPE_OBJECT |
0x1C |
System.Object |
ELEMENT_TYPE_SZARRAY |
0x1D |
Модификатор типа одномерного массива с нулевой нижней границей. |
ELEMENT_TYPE_MVAR |
0x1E |
Обобщенный параметр в определении обобщенного метода, представленный в виде числа |
ELEMENT_TYPE_CMOD_REQD |
0x1F |
Обязательный модификатор, сопровождаемый меткой TypeDef или TypeRef |
ELEMENT_TYPE_CMOD_OPT |
0x20 |
Необязательный модификатор, сопровождаемый меткой TypeDef или TypeRef |
ELEMENT_TYPE_INTERNAL |
0x21 |
Реализован внутри CLI |
ELEMENT_TYPE_MODIFIER |
0x40 |
ORed со следующими типами элементов |
ELEMENT_TYPE_SENTINEL |
0x41 |
Сигнальная ме(а?)тка для сигнатуры метода vararg |
ELEMENT_TYPE_PINNED |
0x45 |
Обозначает локальную переменную, указывающую на закрепленный объект |
0x50 |
Показывает аргумент типа System.Type. |
|
0x51 |
Используется в специальных атрибутах, чтобы задавать упакованный объект (§23.3 в спецификации ECMA-355). |
|
0x52 |
Зарезервирован |
|
0x53 |
Используется в специальных атрибутах для обозначения поля (§22.10, §23.3 в спецификации ECMA-355). |
|
0x54 |
Используется в специальных атрибутах для обозначения свойства (§22.10, §23.3 в спецификации ECMA-355). |
|
0x55 |
Используется в специальных атрибутах для задания перечисления (§23.3 в спецификации ECMA-355). |