Формат файла .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).