A custom attribute is an instance of an attribute class stored with an item of the metadata: the [Obsolete] and [Serializable] of C#, CompilationRelaxations and every other attribute a compiler emits. In IL assembly it is the .custom directive: the constructor to call, and the values for it as a blob of bytes (ECMA-335 II.21 and II.23.3).
The .custom directive
The attribute goes with the item declared just before it (a type, method, field, property, event, parameter or generic parameter, or the assembly or module when the directive stands inside the braces of .assembly or at the top level after .module); .param picks a parameter (.param [1], with .param [0] for the return value) and .param type a generic parameter. A .custom straight after an instruction attaches to the member or type that instruction named. Four forms exist in the grammar of ilasm (customDescr):
| Form | Meaning |
|---|---|
.custom ctor = ( hex bytes ) | the blob as bytes: the usual form, and the one the standard describes |
.custom ctor = { values } | the blob written as typed values; ilasm builds the bytes (the verbal form; ildasm writes it with /CAVERBAL) |
.custom ctor = "text" | the bytes of the string are the blob |
.custom ctor | no blob: the constructor takes no arguments |
The ctor is a method reference to the constructor of the attribute class: instance void [mscorlib]System.ObsoleteAttribute::.ctor(string). To attach the attribute to an item other than the preceding one, write the owner in parentheses: .custom (class Foo) ctor = … or .custom (method void Foo::M()) ctor = …; the owner is a type specification or a member reference.
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.method public static void Old() cil managed
{
.custom instance void [mscorlib]System.ObsoleteAttribute::.ctor() = ( 01 00 00 00 )
ret
}
int32 argument and one with none (the second is the example of ECMA-335 II.21).The blob in bytes
A blob (II.23.3) has this shape: the prolog 01 00; the fixed arguments, one value for each parameter of the constructor, in the encoding of its type (a number in little-endian bytes, a string as a compressed length and then UTF-8 text, a type name as a string, FF for a null string); then a two-byte count of named arguments, and for each one the byte 53 (a field) or 54 (a property), its type, its name as a string and its value. So 01 00 00 00 is a blob with no arguments, and 01 00 01 00 00 is one bool set to true followed by an empty named-argument list. The blobs are written in the byte order of the file, with no separators other than blanks.
The verbal form
Between braces the values are written with their types, in the order of the constructor’s parameters, and the named arguments come after them.
| Value | Written as |
|---|---|
| numbers and booleans | int8(n), int16(n), int32(n), int64(n), the unsigned forms uint8(n) to uint64(n) (or unsigned int8(n) and so on), float32(x), float64(x), char(n), bool(true), bool(false) |
| strings | string('text'), string(nullref) |
| types | type(class 'Namespace.Name'), type(Name), type(nullref) |
| any object | object(value): the value with its type recorded |
| arrays | the element type, the length in brackets, and the elements in parentheses: int32[3](1 2 3), string[2]('a' 'b'), bool[2](true false), type[1](class 'X'), object[2](int32(1) string('a')) |
| a named argument | field type 'Name' = value or property type 'Name' = value, where the type is a simple type, type, object, enum class 'E' or enum E, with [] after it for an array |
.custom instance void Sample.InfoAttribute::.ctor(int32, string) = {
int32(42)
string('hello')
property string 'Note' = string('extra')
field int32[] 'Codes' = int32[2](7 9)
}
Pseudo custom attributes
The standard reserves nine attribute names whose values are not stored as attributes: they set bits and rows of the metadata (II.21.2.1). A compiler writes them as source attributes; in IL assembly the same information has its own syntax, which is why a listing shows no .custom for them.
| Attribute | What it sets | Syntax in IL assembly |
|---|---|---|
DllImportAttribute | the platform-invoke bit and a row of the import table | pinvokeimpl(…) (Marshalling and platform invoke) |
MarshalAsAttribute | the marshalling descriptor of a field or parameter | marshal(…) |
MethodImplAttribute | the implementation flags of a method | the words after the parameter list (cil managed noinlining) |
StructLayoutAttribute | layout, string format, packing and size of a type | sequential, explicit, ansi…, .pack, .size |
FieldOffsetAttribute | the offset of a field | .field [offset] … |
InAttribute, OutAttribute | the in and out bits of a parameter | [in], [out] |
AssemblyAlgorithmIDAttribute, AssemblyFlagsAttribute | the hash algorithm and the flags of the assembly (reserved only) | .hash algorithm and the words after .assembly |
Declarative security
Declarative security attaches permission settings to a type, method or assembly. The two directives are in secDecl of the grammar; the action says when the settings apply.
| Form | Meaning |
|---|---|
.permissionset action = ( hex bytes ) | the encoded permission set as bytes (bytearray ( … ) works in place of = ( … )) |
.permissionset action "xml" | the permission set as an XML string, stored as UTF-16 |
.permissionset action = { class = { named arguments }, … } | the verbal form: each permission is class 'Name' or a type reference, then its named arguments in the form of a custom attribute |
.permission action type ( "Name" = value, … ) | one permission with its settings; a value is true, false, an integer, a string, or an enumeration value such as MyEnum(int32: 3) (int8, int16 and int32 are accepted as the size) |
.permission action type = { named arguments }, .permission action type | the verbal form, and a permission with no settings |
| Action | Meaning (ECMA-335 II.20) |
|---|---|
assert | assert the permission, so that callers do not need it |
demand | demand the permission of all callers |
deny | deny the permission, so checks fail |
inheritcheck | demand the permission of a derived class |
linkcheck | demand the permission of the caller |
permitonly | reduce the permissions, so that other checks fail |
reqopt | request optional extra permissions |
reqrefuse | refuse to be granted these permissions |
request, reqmin, prejitgrant, prejitdeny, noncasdemand, noncaslinkdemand, noncasinheritance | seven more actions that ilasm accepts and the standard does not list (their meaning is not described here) |
The standard writes the settings of .permission as 'name' = 'value' pairs of single-quoted strings; the grammar of ilasm takes double-quoted names and typed values instead.
Not described
Known, not yet covered
- Reference: the complete binary format of a blob (II.23.3: the encoding of enumerations, arrays, boxed values and type names) and of a permission set (II.22.11) is not repeated; only the shape is given. The meaning of each security action for the runtime (II.20 describes them in one line each) is as short as the standard’s own.
Unknown
- Reference: how current .NET versions treat declarative security (the directives are still read and written by ilasm and ildasm) was not checked in any documentation; the examples of
.permissionare from the grammar, not from a real assembly.