Program30.csc.txt
public string GetElementType
( int index , byte [] blobarray , out int howmanybytes)
{
howmanybytes = 0;
string returnstring =
"";
byte type =
blobarray[index];
if ( type == 0x0f)
returnstring =
GetPointerToken(index, blobarray , out howmanybytes);
return returnstring;
}
public string
GetPointerToken(int index , byte [] blobarray , out int howmanybytes)
{
string returnstring =
"";
int howmanybytes2;
returnstring =
GetElementType(index+1 , blobarray , out howmanybytes2) + "*";
howmanybytes = howmanybytes2
+ 1;
return returnstring;
}
e.il
.class zzz
{
.method int32 *** a1()
{
}
.method class zzz ****** a2()
{
}
.method int32
[12][3,5] * a3()
{
}
}
Output
.method /*06000001*/
int32***
.method /*06000002*/
class zzz/* 02000002 */******
.method /*06000003*/
int32[12][3,5]*
In the above il file we have
added a unmanaged pointer * like we added a managed pointer & in the
earlier example. There is not much change in from the earlier program.
The only difference is that
the unmanaged pointer has a type of 0x0f and in the il file we have shown how
recursion really works by adding as many stars as we could and yet demonstrate
that the program works. Thus the short explanation.
Program31.csc
public string DecodeToken
(int token , int type)
{
byte tabletype =
(byte)(token & 0x03);
int tableindex = token
>> 2;
string returnstring =
"";
if ( tabletype == 0)
returnstring =
typedefnames[tableindex];
if ( tabletype == 1 )
returnstring =
typerefnames[tableindex] ;
return returnstring;
}
public string
GetTableRefNameForFillArray( int tablerow , int typerow , string tablename)
{
string nameandnamespace =
"";
if ( tablename ==
"AssemblyRef")
nameandnamespace =
NameReserved(GetString(AssemblyRefStruct[tablerow].name)) ;
if ( tablename == "ModuleRef")
nameandnamespace =
".module " + NameReserved(GetString(ModuleRefStruct[tablerow].name))
;
string stringnamespace =
NameReserved(GetString(TypeRefStruct[typerow].nspace)) ;
string stringnested = "";
if ( stringnamespace !=
"")
stringnamespace = stringnamespace + ".";
if ( tablename ==
"AssemblyRef")
stringnested = "[" + nameandnamespace + "/* 23" +
tablerow.ToString("X6") + " */]" + stringnamespace +
NameReserved(GetString(TypeRefStruct[typerow].name));
if ( tablename ==
"ModuleRef")
stringnested = "[" + nameandnamespace +
"/* 1A" + tablerow.ToString("X6") + " */]" +
stringnamespace + NameReserved(GetString(TypeRefStruct[typerow].name));
if ( tablename ==
"Module")
stringnested = stringnested + stringnamespace + NameReserved(GetString(TypeRefStruct[typerow].name));
stringnested = stringnested + "/* 01" + typerow.ToString("X6") + "
*//" ;
return stringnested;
}
public string
GetTypeRefForFillarray(int k)
{
string stringnamespace =
NameReserved(GetString(TypeRefStruct[k].nspace)) ;
if ( stringnamespace !=
"")
stringnamespace=
stringnamespace + ".";
string stringnested = stringnamespace +
NameReserved(GetString(TypeRefStruct[k].name)) ;
stringnested = stringnested + "/* 01" + k.ToString("X6") + "
*/" ;
return stringnested;
}
string [] typerefnames;
string [] typedefnames;
public void FillArray ()
{
int old = tableoffset;
bool tablehasrows = tablepresent(1);
int offs = tableoffset;
tableoffset = old;
if ( tablehasrows )
{
typerefnames = new
string[rows[1]+1];
for ( int k = 1 ; k <=
rows[1] ; k++)
{
short resolutionscope =
BitConverter.ToInt16 (metadata , offs);
offs = offs + 2;
int name =
ReadStringIndex(metadata , offs);
offs = offs + offsetstring;
int nspace =
ReadStringIndex(metadata , offs);
offs = offs + offsetstring;
string stringname = NameReserved(GetString(name));
string
stringtypefminusnested = GetString(nspace) ;
if ( stringtypefminusnested
.Length != 0)
stringtypefminusnested =
stringtypefminusnested + ".";
stringtypefminusnested =
stringtypefminusnested + stringname;
string stringnested =
"";
string resolutioncodedtable
= GetResolutionScopeTable(resolutionscope );
int resolutionrow =
GetResolutionScopeValue(resolutionscope );
if ( resolutioncodedtable ==
"Module")
{
stringtypefminusnested =
stringtypefminusnested + "/* 01" + k.ToString("X6") +
" */";
}
if ( resolutioncodedtable ==
"AssemblyRef")
{
stringnested = "[" +
NameReserved(GetString(AssemblyRefStruct[resolutionrow].name)) + "/*
23" + resolutionrow.ToString("X6") + " */]";
stringtypefminusnested =
stringtypefminusnested + "/* 01" + k.ToString("X6") +
" */";
}
if ( resolutioncodedtable ==
"ModuleRef")
{
stringnested = "[.module " +
NameReserved(GetString(ModuleRefStruct[resolutionrow].name)) + "/*
1A" + resolutionrow.ToString("x6") + " */]";
stringtypefminusnested =
stringtypefminusnested + "/* 01" + k.ToString("X6") +
" */";
}
if ( resolutioncodedtable ==
"TypeRef" )
{
string resolutioncodedtable1
= GetResolutionScopeTable(TypeRefStruct[resolutionrow].resolutionscope );
int resolutionrow1 =
GetResolutionScopeValue(TypeRefStruct[resolutionrow].resolutionscope );
if ( resolutioncodedtable1
== "AssemblyRef" || resolutioncodedtable1 == "ModuleRef" ||
resolutioncodedtable1 == "Module")
{
stringnested =
GetTableRefNameForFillArray(resolutionrow1 , resolutionrow ,
resolutioncodedtable1);
stringnested = stringnested
+ GetTypeRefForFillarray(k);
stringtypefminusnested =
"";
}
else
{
string tablename =
GetResolutionScopeTable(TypeRefStruct[k].resolutionscope );
int row =
GetResolutionScopeValue(TypeRefStruct[k].resolutionscope );
int cnt = 0;
while ( tablename ==
"TypeRef")
{
row =
GetResolutionScopeValue(TypeRefStruct[row].resolutionscope );
tablename =
GetResolutionScopeTable(TypeRefStruct[row].resolutionscope );
cnt++;
}
int i = 0;
int [] typerows = new
int[cnt];
string tablename1 = GetResolutionScopeTable(TypeRefStruct[k].resolutionscope
);
int row1 =
GetResolutionScopeValue(TypeRefStruct[k].resolutionscope );
while ( i < cnt )
{
row1 =
GetResolutionScopeValue(TypeRefStruct[row1].resolutionscope );
tablename1 =
GetResolutionScopeTable(TypeRefStruct[row1].resolutionscope );
typerows[i] = row1;
i++;
}
int assemblyrow =
GetResolutionScopeValue(TypeRefStruct[row].resolutionscope );
int typerow =
typerows[typerows.Length-1];
stringnested =
GetTableRefNameForFillArray( assemblyrow , typerow , tablename);
i = 0;
string dummy = "";
while ( i < cnt-1 )
{
dummy =
GetTypeRefForFillarray(typerows[i]) + "/" + dummy;
i++;
}
stringnested = stringnested
+ dummy;
stringnested = stringnested
+ GetTypeRefForFillarray(resolutionrow);
stringnested = stringnested
+ "/" + GetTypeRefForFillarray(k);
stringtypefminusnested =
"";
}
}
typerefnames[k] =
stringnested + stringtypefminusnested ;
//Console.WriteLine(".......{0}
{1} {2}" , resolutioncodedtable , typerefnames[k] , k);
}
}
old = tableoffset;
tablehasrows = tablepresent(2);
offs = tableoffset;
tableoffset = old;
if ( tablehasrows )
{
typedefnames = new
string[rows[2]+1];
for ( int k = 1 ; k <=
rows[2] ; k++)
{
int name =
TypeDefStruct[k].name;
offs += offsetstring;
int nspace =
TypeDefStruct[k].nspace;
offs += offsetstring;
string nestedtypestring = "";
nestedtypestring = GetNestedTypeAsString(k);
string namestring = GetString(name);
string namespacestring =
NameReserved(GetString(nspace));
if ( namespacestring.Length
!= 0)
namespacestring = namespacestring
+ ".";
namestring = NameReserved(namestring );
typedefnames[k] =
nestedtypestring + namespacestring + namestring + "/* 02" + k.ToString("X6") + "
*/";
}
}
}
e.il
.assembly extern vijay
{
}
.module extern bbb
.module ccc
.class zzz
{
.method class [vijay]z1 a1()
{
}
.method class [.module bbb]z2 a2()
{
}
.method class [.module ccc]z3 a3()
{
}
.method class [vijay]z4/z5 a41()
{
}
.method class [vijay]z4/z5/z6 a5()
{
}
.method class [vijay]z4/z5/z6/z7 a6()
{
}
.method class [vijay]z4/z5/z6/z7/z8/z9/z10/z11 a71()
{
}
.method class [.module bbb]z4/z5/z6/z7/z8/z9/z10/z11
a72()
{
}
.method class [.module ccc]z4/z5/z6/z7/z8/z9/z10/z11
a73()
{
}
}
Output
.method /*06000001*/
class [vijay/* 23000001 */]z1/* 01000002 */
.method /*06000002*/
class [.module bbb/* 1A000001 */]z2/* 01000003 */
.method /*06000003*/
class z3/* 01000004 */
.method /*06000004*/
class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 */
.method /*06000005*/
class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 *//z6/*
01000007 */
.method /*06000006*/
class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 *//z6/*
01000007 *//z7/* 01000008 */
.method /*06000007*/
class [vijay/* 23000001 */]z4/* 01000005 *//z5/* 01000006 *//z6/*
01000007 *//z7/* 01000008 *//z8/* 01000009 *//z9/* 0100000A *//z10/* 0100000B
*//z11/* 0100000C */
.method /*06000008*/
class [.module bbb/* 1A000001 */]z4/* 0100000D *//z5/* 0100000E *//z6/*
0100000F *//z7/* 01000010 *//z8/* 01000011 *//z9/* 01000012 *//z10/* 01000013
*//z11/* 01000014 */
.method /*06000009*/
class z4/* 01000015 *//z5/* 01000016 *//z6/* 01000017 *//z7/* 01000018
*//z8/* 01000019 *//z9/* 0100001A *//z10/* 0100001B *//z11/* 0100001C */
So far we have looked at
data types that have been created within our program and now we need to look at
those defined somewhere else. Thus the method a1 has a return type of class z1.
The only problem is that this class z1 is defined in vijay which is placed in
square brackets.
This name vijay has to been a
assembly ref as we specified earlier. The only problem is there is no check to
see whether this assembly vijay actually exits and within it there exists a
type called z1. Every type we reference in our program is stored in the type
ref table.
The name and namespace field
store for us the name and namespace name respectively. The field resolution we
will deal with in a short while. In the
type signature, the class type remains at 12 and the token for the method a1 is
09. As the first bit is 1, this token refers to the type ref table and not the
typedef table.
Thus in the DecodeToken
table the if statement checks if the variable tabletype is 1, it picks up the
row from the typeref and not typedef table. Thus in the FillArray method we now
populate the typerefnames array. The variable stringtypefminusnested simply
gives us the name and namespace combo of the type used with or without the dot.
Then we move on to the core
of what this program is all about. We want to know where this type is really
stored. The field resolutionscope is just what the doctor ordered. This method
used earlier gives us one of the four tables, AssemblyRef, Module, ModuleRef
and TypeRef. These values tell us where to find the type.
For the method a1, the type
is to be found in a Assembly and hence the coded index is a AssembleyRef. The
coded index row variable now points to the AssemblyRef table.
Thus we start with a open [,
then we pick up the name of the assembly from the AssemblyRef table using the
resolutionrow variable, followed by the table number of the AssemblyRef table
0x23 and the row number in this table using the resolutionrow variable. Finally
the for loop index variable k tells us the row number in the type ref table
that has a number 1.
This is at the end as a comment
for all the typeref rows. We add the two strings stringtypefminusnested and
string stringnested at the end and place this value into the typerefnames
array. In this case we need not have two separate variables as one would have
done the job. Read on.
For method a2, the return
type is z2 and this is now found in a module bbb. There is a module extern
directive that specifies the existence of a module with this name. Thus the
coded index table is now ModuleRef as the type is in the same assembly but a
different module.
For the module ref we add
the .module directive and now the variable resolution row is an offset into the
ModuleRef table whose number is 0x1a. The rest of the code remains like that of
the AssemblyRef.
The third method a3 has its
return data type z3 stored in a module called ccc and this is also the name of
the current module using the module directive. Thus the code index table will
return Module and in the output we do not display the words module and the name
of the module, but only the type name.
The problems starts with the
method a4 that has a type in the AssemblyRef vijay but its type is nested. The
main type is z4 and within this type we have a nested type z5. The Nested Types
table does not get an entry added as that table deals with nested types created
in this assembly and not somewhere else.
Thus two records will be
added to the TypeRef table, one for the type z4 and the second for the type z5.
To make matters simpler for us to understand we will comment the entire il file
and only focus on one method at a time from now on. Our current focus is method
a4.
The TypeRef table has three
records, the first for the type Object as all classes are derived from class
Object at the end of the day. Thus we will not show you the first record of the
type ref table that refers to the class Object.
The second row refers to the
class z4 and the resolution scope refers to the assembly ref table record 1
that is vijay. This is obvious as the class or type must reside someplace. The
third record refers to the class z5. This class is nested within the class z4.
This relationship is mapped
by specifying the resolution scope of TypeRef and the row number is the row
number of the class within which this class is nested. Thus whenever we see a
TypeRef resolution scope it means that the current class is nested within the class specified by the
resolution index.
This class if it has a
resolution scope of TypeRef means one more level of nesting. This relationship
ends with a type whose resolution scope is Module, ModuleRef or AssemblyRef as
we shall soon see.
Row #2
ResolutionScope : AssemblyRef[1]
Name : "z4"
Row #3
ResolutionScope : TypeRef[2]
Name : "z5"
The if statement for the
value of the resolution scope index being TypeRef is true for the nested type
z5 and not type z4. The value of the variable resolutionrow is now 2 which
simply means that this TypeRef row may be the parent type.
We use this value to move to
row 2, we are at row three now and find out the new values of this coded index.
If it is any of the terminating table types, we call the function
GetTableRefNameForFillArray that gives us the name of the Assembly or Module or
ModuleRef that the type is in.
The last parameter is the
type of container that this type resides in. The first parameter is the
tablerow in the table type specified in the last parameter and the second
parameter is the type row number that has a coded index other than TypeRef. Lets look at this function
GetTableRefNameForFillArray first.
We first ask which table the
tablerow parameter is an offset to. Depending upon one of two and not three
possible tables, we use the relevant array to gives us the name of the assembly
or module. If the type resides in the same module, the module directive is
removed.
For the AssemblyRef table,
it is simply the table name and for the ModuleRef table it is the module
directive plus the extern module name. We now need to add the type name and
namespace name which is stored in the TypeRef table. This type is the parent
type z4 and not nested type z5 in this case.
We then add the relevant
name of the type and the row number in comments. As this is a nested type we
end with a single slash specifying a nested type. Now that we have the parent
type, we need to add the last nested type z5 which is the value of the loop
index k.
Thus we have a method
GetTypeRefForFillarray that simply displays a type with a certain index. This
function is very elementary and all that we do is add the name and namespace
and then the row number of the type ref table in comments. This is how we take
care of a single level of a nested type.
This does not work with a
type that has more than one level of nesting. We need more complex code. This
if statement was unnecessary but as an aid to understanding. The else takes
place whenever we have more than one level of nesting. Lets take a certain case
of method a6 which is complex enough.
Its data type of the return
value is z4/z5/z6/z7 where we have four levels of nesting. How do we handle
complex cases like this. Simple we start at the inner else statement.
Row #2
ResolutionScope : AssemblyRef[1]
Name : "z4"
Row #3
ResolutionScope : TypeRef[2]
Name : "z5"
Row #4
ResolutionScope : TypeRef[3]
Name : "z6"
Row #5
ResolutionScope : TypeRef[4]
Name : "z7"
Lets start with the rows of
the TypeRef table. In the DecodeToken method, the value of the tableindex will
b3 5, the record number for the z7 type. If we look at the rows, the resolution
coded table is typeref that tells us that the parent type of z7 is z6.
But this row also has a
coded index of TypeRef and thus class z5 is the parent of class z6. However
class z5 or row 3 has also a TypeRef coded index and thus row 2 or class z4 is
the parent for class z5. The good news is that class z4 has a coded index of
AssemblyRef. Lets take a hands on look at our code.
The string tablename is
TypeRef and the type row is 5 or class z7. The row variable is 4 specifying
that this row will be the parent of class z7. We would now need to find out how
many levels of nesting are there. Thus we need to keep looping until we find a
type row whose coded index table is not TypeRef.
The problem is that we
cannot assume that each nested types parent is stored one about it. So we in a
while loop, keep looping until we reach a table name that is not TypeRef. Now
that we have entered the while loop, we are on a nested type and hence we
calculate the row number of the parent type.
This is stored in row. We
then use this row number to give us the coded index table of the parent type.
If this is ever not TypeRef we exit the while loop. Thus in our specific case
the cnt variable will have a value of 2 as we have entered the while loop twice
and the row number that had a coded index other than TypeRef is the second row.
Thus the value of cnt is
actually one less than the number of types. It gives us types z5 and z6. Z7 is
the type denoted by the k index and z4 by the AssembleyRef. We would now like
to create an array of 2 int’s t store the row numbers of types z5 and z6.
We once again enter the
while loop twice and simply recalculate the coded index row variable and store
it in the typerows array. The values in this array are rows 3 and 2 as we are moving up the type row table.
The tablename1 and row1 variables are set to a value of the type z7 or row 5 in
the type ref table.
We are now calling the
method GetTableRefNameForFillArray with the first parameter assemblyrow as 1 as
this is the record number of the AssemblyStruct table and name vijay. The
typerow variable is the 2 as this is the first parent type z4. This method will
return a string as [vijay/* 23000001 */]z4/* 01000002 *//.
We now enter the while loop
only once as the value of cnt is only two. Here we display the next series of
nested types. The typerow array has a value of 3 and the string dummy contains
z5/* 01000003 *//. Now we need to display the second last type that is denoted
by the resolutionrow variable or row 4 or type z6.
Finally the current type
denoted by k or row 5 or type z7 is displayed at the end without the nested
slash. We could not have placed the second last GetTypeRefForFillarray within
the while as the type z4 would come twice. If you are not clear please reread
the entire explanation a couple of times with some aspirin.
Program32.csc
public string GetElementType
( int index , byte [] blobarray , out int howmanybytes)
{
howmanybytes = 0;
string returnstring =
"";
string modoptstring =
"";
int bytestaken;
modoptstring =
GetModOptOrModReq( index , blobarray , out bytestaken);
index = index + bytestaken;
byte type =
blobarray[index];
if ( type >= 0x01
&& type <= 0x0e )
{
returnstring =
GetType(type);
howmanybytes = 1;
}
if ( type == 0x45 )
{
int howmanybytes2 ;
returnstring =
GetElementType( index + 1 , blobarray , out howmanybytes2) + "
pinned";
howmanybytes = howmanybytes2
+ 1;
}
howmanybytes = howmanybytes
+ bytestaken;
if ( modoptstring !=
"")
returnstring = returnstring
+ " " + modoptstring;
return returnstring ;
}
public string GetTokenType (
byte [] blobarray , int index , out int howmanybytes)
{
string returnstring =
"";
int uncompressedbyte;
int howmanybytes1 = 0;
howmanybytes1 =
howmanybytes1 +
CorSigUncompressData(blobarray , index + 1 , out uncompressedbyte);
string dummy1 = DecodeToken(uncompressedbyte ,
blobarray[index]);
if ( blobarray[index] ==
0x12)
returnstring = "class
" + dummy1;
else if ( blobarray[index]
== 0x11)
returnstring =
"valuetype " + dummy1;
else
returnstring = dummy1;
howmanybytes =
howmanybytes1;
return returnstring;
}
public string
GetModOptOrModReq ( int index , byte [] blobarray , out int bytestaken)
{
string returnstring =
"";
bytestaken = 0;
int whileindex = index;
if ( blobarray[index] == 0x20 || blobarray[index] == 0x1f)
{
while ( true )
{
int noofbytes;
string tokens = GetTokenType
(blobarray , whileindex , out noofbytes) ;
if (
blobarray[whileindex] == 0x20)
tokens = "modopt("
+ tokens + ")";
if (
blobarray[whileindex] == 0x1f)
tokens = "modreq("
+ tokens + ")";
bytestaken = bytestaken +
noofbytes + 1;
returnstring = tokens +
" " + returnstring ;
whileindex = whileindex +
noofbytes + 1;
if (
!(blobarray[whileindex] == 0x20 ||
blobarray[whileindex] == 0x1f) )
break;
}
}
if ( returnstring !=
"")
returnstring =
returnstring.Remove(returnstring.Length-1,1);
return returnstring;
}
e.il
{
}
.class zzz
{
.method void modopt([vijay]aa ) a1()
{
}
.method void modreq([vijay]aa ) a2()
{
}
.method void * modopt([vijay]aa ) modreq([vijay]bb )
modreq ([vijay]cc ) modreq([vijay]dd
) a3()
{
}
}
Output
.method /*06000001*/
void modopt([vijay/* 23000001 */]aa/* 01000002 */)
.method /*06000002*/
void modreq([vijay/* 23000001 */]aa/* 01000002 */)
.method /*06000003*/
void* modopt([vijay/* 23000001 */]aa/* 01000002 */) modreq([vijay/*
23000001 */]bb/* 01000003 */) modreq([vijay/* 23000001 */]cc/* 01000004 */)
modreq([vijay/* 23000001 */]dd/* 01000005 */)
We are now going to delve
into two modifiers modreq and modopt that we have no parallel when it comes to
the C# programming language. If you were a C++ programmer, thee custom
modifiers may have made some sense. These modifiers are similar to custom
attributes but they are part of the method signature unlike attributes that are
not.
The modreq modifer means
that the caller must understand the modifer whereas modopt means that the
modifer may be ignored. These modifiers can be attached to a parameter or a
return value, in other words they associate only with a data type. This allows
us to associate a type reference with a item.
The CLI however does not
care about these modifiers other than changing the signature of the method.
These modifiers are thus used by tools that interact with the metadata. These
tools are compilers, program analysers etc. These modifiers tell us that a
modopt may be ignored but a modreq has some special meaning.
If this was a C++ book, we
would have explained how the concept of a const can be modelled using custom
modifiers. Lets look at the first method a1 which has a optional modifer with a
type reference to type aa in assembly vijay. The modifiers need a any type
reference. This could be a type created somewhere else or in the same file.
A int32 type will not
suffice. For consistency we have used a type aa for all. The signature for this
method reads as 20 00 20 09 01. You will realize that the third byte is not a 1
which is the data type of a void but 0x20. This is the type number for a modopt
modifier.
These modifiers are followed
by a token type that pinpoints the type reference. This is the reason in built
types like int32 are not permitted. After the type token follows the data type
of the return value. Lets look at method a2 that uses a modreq instead.
Its signature is 20 00 1F 09
01 which tells us that a modreq has a type of 0x1f. Finally method a3 is more
complex, it starts with one modopt and then we have three modreq modifiers. Its
signature is 20 00 1F 15 1F 11 1F 0D 20 09 0F 01. The last two bytes are for
the pointer to void.
Even though we start with a
modopt, then the modreqs, the order is the signature is reversed. This we see
the three modreqs first and then the modopt. When we display these custom
modifiers we will have to reverse them. Lets start with the method GetElementType
first.
At the very beginning we
would like to find out if there are any custom modifiers. This is most
important as custom modifiers come first and then the data type. This is true
for a return value as well as for parameters.
We pass the function GetModOptOrModReq
three parameters, the index of the type in the blob array, the blobarray and we
expect a string that has all the modopt and modreq modifiers. Also we expect
the out parameter to give us the number of bytes taken up by the custom
modifiers.
We then increase the index
variable by the number of bytes taken up by the custom modifiers so we now
point to the actual data type. At the end of the GetElementType method we add
the howmanybytes variable by the bytestaken or the number of bytes taken up by
the custom modifiers.
This variable is the out
parameter for the GetElementType method. Finally we add the modoptstring to the
end of the returnstring and not at the beginning with a space. Lets move on the
GetModOptOrModReq that actually does all the work. Most of the time, custom
modifiers are rarely used and this method will do nothing.
Thus we initialize the out
parameter bytestaken to b zero and the returnstring to a empty string. If and
only if the byte denoted by the index parameter is a modreq 0x1f or a
modopt 0x20, do we need to do
something. As we may have scores of custom modifiers we enter a infinite while
loop.
The fact that we are here
now means that we have at least one custom modifer. We then call the method GetTokenType to figure out the token
that follows a custom modifer. This method remains the same like before but
with a small else added.
Earlier we called this
function for a class or valuetype token and within if statements added a class
or valuetype to the value returned by the DecodeToken method. Now as we are
calling it from a third method, we need to add no such embellishments to the
token string and thus the final else simply initializes the return value.
Thus variable tokens now
contains the type reference token as a string but we need to place this token
in a modopt or modreq keyword. Thus we check whether the byte type is that of
modopt 0x20 or modreq 0x1f. We use the variable whileindex for this offset.
We now set bytestaken to
increase by the number of bytes the token has taken that is stored in noofbytes
and again by 1 as one byte gets taken by the custom modifier. We then set the
return string returnstring to this value of the token but as the world is
reverse, we add the tokens at the beginning and not at the end.
We increase the whileindex
by the same amount as bytestaken and now check if the next byte after the
custom modifier is also another custom modifer. If it is we go back to the
loop, if not we end the loop. Finally these is an extra space left in this
string returnstring which we remove using the Remove method.
This is how we handle custom
modifiers.
Program33.csc
public void
CreateMethodDefSignature (byte [] blobarray , int row)
{
string returntypestring =
"";
returntypestring =
GetElementType(index , blobarray , out
howmanybytes ,row , "Method") + " ";
methoddefreturnarray[row] =
returntypestring;
}
public void
DisplayAllMethods (int typerow)
{
methodstring =
methodstring + ".method ";
methodstring = methodstring
+ "/*06" + methodindex.ToString("X6") + "*/ " ;
string s = methodstring +
" " +
methoddefreturnarray[methodindex] + " " +
GetString(MethodStruct[methodindex].name);
Console.WriteLine(s);
}
public string GetElementType
( int index , byte [] blobarray , out int howmanybytes , int row , string name)
{
howmanybytes = 0;
string returnstring =
"";
string modoptstring =
"";
int bytestaken;
modoptstring =
GetModOptOrModReq ( index , blobarray , out bytestaken);
index = index + bytestaken;
byte type =
blobarray[index];
else if ( type == 0x1b)
{
int howmanybytes2 ;
returnstring =
GetPointerToFunctionSignature ( index , blobarray , out howmanybytes2 , ref
modoptstring, row , name) ;
modoptstring = "";
howmanybytes = howmanybytes2
;
}
howmanybytes = howmanybytes
+ bytestaken;
if ( modoptstring != "")
returnstring = returnstring
+ " " + modoptstring;
return returnstring ;
}
public string
GetPointerToFunctionSignature ( int index , byte [] blobarray , out int
howmanybytes , ref string modoptstring, int row , string name)
{
int uncompressedbyte;
index = index + 1;
int nobytes ;
int totalbytes = 1;
string returnstring =
"";
nobytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
if ( row != 0 &&
name == "Method")
{
methoddeftypearray [row] =
DecodeFirstByteofMethodSignature (uncompressedbyte , row);
methoddeftypearray[row] =
"method " + methoddeftypearray [row];
}
else
returnstring = "method " +
DecodeFirstByteofMethodSignature(uncompressedbyte , row);
index = index + nobytes;
totalbytes = totalbytes +
nobytes;
nobytes = CorSigUncompressData(blobarray
, index , out uncompressedbyte);
index = index + nobytes;
totalbytes = totalbytes +
nobytes;
int nooftypes =
uncompressedbyte;
int bytestaken1;
string modoptstring1 =
GetModOptOrModReq( index , blobarray , out bytestaken1);
index = index + bytestaken1;
totalbytes = totalbytes +
bytestaken1;
returnstring = returnstring
+ GetElementType(index , blobarray ,
out nobytes , 0 , "") ;
index = index + nobytes;
totalbytes = totalbytes +
nobytes;
if (modoptstring1!=
"")
returnstring = returnstring +
" " + modoptstring1 ;
returnstring = returnstring
+ " *(";
if ( nooftypes == 0)
returnstring = returnstring
+ ")";
else
for ( int i = 1; i <=
nooftypes ; i++)
{
returnstring = returnstring
+ GetElementType(index , blobarray ,
out nobytes ,0 , "");
if ( i != nooftypes)
returnstring = returnstring
+ ",";
else
returnstring = returnstring
+ ")";
index = index + nobytes;
totalbytes = totalbytes +
nobytes;
}
if ( modoptstring !=
"")
returnstring = returnstring
+ " " + modoptstring;
howmanybytes = totalbytes;
return returnstring;
}
e.il
.assembly extern vijay
{
}
.class zzz
{
.method public static method
unmanaged stdcall int32*(int8,int16,string ) a1(unsigned int8 a)
{
}
.method public static method
unmanaged fastcall int32 modopt(zzz)
modopt ([vijay] yyy) modopt ([vijay] xxx)
*(int8,int16,string ) modreq([vijay]a1) modreq([vijay]a2) a2(int8 a, int16 b)
{
}
.method public static method
unmanaged thiscall int32
*(int8,int16,string ) modreq([vijay]a1) modreq([vijay]a2) a3(int8 a, int16 b)
{
}
.method public static method
unmanaged cdecl int32 modopt(zzz)
modopt ([vijay] yyy) modopt ([vijay] xxx)
*(int8,int16,string ) a4(int8 a,
int16 b)
{
}
}
Output
.method /*06000001*/
int32 *(int8,int16,string) a1
.method /*06000002*/
int32 modopt(zzz/* 02000002 */) modopt([vijay/* 23000001 */]yyy/*
01000002 */) modopt([vijay/* 23000001 */]xxx/* 01000003 */)
*(int8,int16,string) modreq([vijay/* 23000001 */]a1/* 01000004 */)
modreq([vijay/* 23000001 */]a2/* 01000005 */)
a2
.method /*06000003*/
int32 *(int8,int16,string) modreq([vijay/* 23000001 */]a1/* 01000004 */)
modreq([vijay/* 23000001 */]a2/* 01000005 */)
a3
.method /*06000004*/
int32 modopt(zzz/* 02000002 */) modopt([vijay/* 23000001 */]yyy/*
01000002 */) modopt([vijay/* 23000001 */]xxx/* 01000003 */)
*(int8,int16,string) a4
In the above example the
DisplayAllMethods function has only one change and that is the name of the
method stored in the field name is also displayed. The major changes takes
place in the GetElementType method. We have added two more parameters to this
method. This is how code gets written in real life.
As we did no prior planning
earlier, we did not account for the fact that the existing parameters for the
GetElementType method we not enough. Thus each and every place we have called
the GetElementType method in the past, we now have to add these two extra
parameters.
As these earlier calls did
not use these parameters, we use 0 and the empty string “”. The only place we
use it is in the CreateMethodDefSignature method where we pass the row number
in the method table for the fourth
parameter and the fifth is where we have called the GetElementType
method from, the Method table obviously.
There is one more addition
to the GetElementType method and that is the type 0x1b that stands for a full
method signature. Lets look at the first method a1 in the il file. The first
change is the addition of the word method to the calling convention. This is
followed by the data type int32 and then a star.
This complicates life as we
now have a pointer to a function. The open and close brackets now encompass the
parameters of this function. Whenever we have a pointer to a function, we shall
see a 0x1b in our signature byte. These bytes now look as 00 01 1B 02 03 08 04
06 0E 05.
The first is the calling
convention byte that is zero, followed by the number of parameters of the
method a1 01. Then we get a 0x1b which is the type for a full method signature
following. This byte now has the calling convention byte first and 2 is
stdcall. As this is a pointer to a function, we need to add the word method to
the calling convention.
The next byte is the stdcall
calling convention followed by not the count bytes of number of parameters to
function a1 but to the function that the return value points to 3. Each and
every function call returns a value and in our case our function returns a
int32 and its type byte 08 is next. This is followed by the three parameter
types to the method a int8 or 04, then a int16 a 06 and a string 0e.
This completes the pointer
to method and then we have the data type of the first parameter. Method a2
complicates life a little bit for us. The modopt and modreq appear after any
data type. We have two data types in a pointer to function situation, the first
is the int32 or return type of the pointer method.
In function a2 we have a
series of modopt which if you forgotten is 0x20 and then we have a series of
modreqs or 0x1f after the pointer function parameters and before the name of
the function. Thus the signature bytes read as 00 02 1F 15 1F 11 1B 04 03 20 0D
20 09 20 08 08 04 06 0E 04 06.
This make look complex but
if you look at them closely, you will see that byte 3 onwards are the modreqs
0x1f. Thus what we write at the end come first in the signature. This is then
followed by the signature of the pointer to function 0x1b and the next two
bytes.
Then come the modopt’s and
return value of the pointer, the three function pointer data types and the data
types of the three parameters. Methods a3 and a4 have the required modifer at
the beginning or at the end. First lets look at the code of the GetElementType
method All that we do is what we have always done.
We call a method
GetPointerToFunctionSignature which takes the same five parameters passed to
the GetElementType method. The fourth is a valid row number of the method in
the method table and the last is the string Method. In the same vein the
howmanybytes variable is set to the third parameter of the
GetPointerToFunctionSignature method.
The major deviation is that
we set the variable modoptstring to null which means that we undo the effect of
the first GetModOptOrModReq method called. Also we pass this string
modoptstring as the fifth parameter.
Coming to the GetPointerToFunctionSignature method we start with using the
index variable as an offset into the signature.
We are now at the 0x1b and
need to pick up the calling convention byte. We increase the index variable by
1 and also use the totalbytes to tell us how many bytes the pointer to function
signature consumes. As the 0x1b takes up one byte, we start variable totalbytes
with 1 and not zero.
Now we need to decode the
calling convention byte using the method DecodeFirstByteofMethodSignature that
we have done before. We also need to add the words method before it. The only
problem is that for a method we have stored the calling convention in the array
methoddeftypearray.
For others like a local var
signature, method ref signature etc we have created no such array. Thus we
check if the row is non zero and the name parameter is Method. If yes we simply
place the calling convention string in the array methoddeftypearray.
If another signature, we add
the calling convention to the returnstring variable that will contain the
entire signature to be returned. The index and totalbytes variable will move in
lock step always. We then take care of the number of parameters to the function
by storing the result in nooftypes that we use later to simply print out the
data types to the method.
Now we may or may not have
custom modifiers modopt or modreq. Here the modifiers are the one before and
the first set of modifiers are stored in the string modoptstring. We then call
the GetElementType method to gives the data type of the pointer to function
which is a int32 in our case.
We once increase the index
and totalbytes variables. We now check whether the modoptstring1is blank or not
as we need to add the modopt string with a space. These custom modifiers are
the custom modifiers that we have just got. We then place the * and the
brackets and if the function has no data types, i.e. nooftypes is zero we add
the closing brackets.
We then in a for loop use
GetElementType to give us the data type of each parameter. We also know that of
the last parameter we do not put a comma but a close bracket. Finally we add
the last modoptstring that actually comes first in the signature byte and we
captured at the beginning of the GetElementType method.
We also initialize the out
parameter howmanybytes to totalbytes. This completes what we believe is the
most difficult method signature.
Finally we have used the
same pointer to function as a parameter. .method public static int32 a2(method
unmanaged stdcall int8 modopt([vijay]aa) modopt([vijay]bb) * (int16,int32)
modreq([vijay]cc) a )
{
}
Its signatures bytes are 00
01 08 1F 11 1B 02 02 20 0D 20 09 04 06 08. Thus life remains the same whether
we deal with parameters or return values.
We finally take a real
complex method signature. First lets take a look at the il file.
e.il
.assembly extern vijay
{
}
.class zzz
{
.method public static method
unmanaged stdcall bool modopt([vijay]bb) * ( method unmanaged thiscall int8 modreq([vijay]cc) modreq([vijay]dd)
*(int16,method unmanaged fastcall int32 modreq([vijay]z1) modopt([vijay]z2) * (
bool , unsigned int32) ) , int64 , unsigned int64) modreq([vijay]ee) a2()
{
}
}
Looks very intimidating but
lets take it one byte at a time by looking at the signature generated. This
looks like 00 00 1F 1D 1B 02 03 20 09 02 1B 03 02 1F 11 1F 0D 04 06 1B 04 02 20
19 1F 15 08 02 09 0A 0B.Lets as always start from the beginning taking.
The first 00 is the calling
convention and the second 00 the number of parameters the function a1 takes
that are none. Then we come across the modreq modifer 1f that is the last
modreq modifer modreq([vijay]ee). Remember these modifiers are stored
backwards, the last is seen first.
Then we have a 0x1b or the
pointer to function signature. The 02 is the standard calling type stdcall of
the main pointer to function that is followed by the number of parameters 03
that this pointer has. The last two are a int64 and unsigned int64. This is
followed by the two modopt’ s modopt([vijay]aa) modopt([vijay]bb) that we have before the *.
The 02 is the data type for
a bool which is the return value data type for the pointer to function. Then we
have the data type of the first parameter out of three which unfortunately
starts with a 0x1b or a pointer to function. The 03 is the data type fir a
thiscall and the 02 is the number of parameters of this pointer to function.
The two 0x1f’s are the
modreq’s modreq([vijay]cc) modreq([vijay]dd) and the 04 is the data type int8
the return type of this pointer to function. As we have two parameters to this
function a int16 whose data type is 06. The second parameter is a another
pointer to function and hence we see a 0x1b.
Its calling convention is 04
or fastcall. The next 02 tells us that we have two parameters. We have a modopt
and a modreq following modreq([vijay]z1) modopt([vijay]z2). The int32 data is the return type of the function and this
accounts for the 08. The 02 and 09 and the bool and unsigned int32 data types
of the innermost pointer to function.
Finally the in64 and
unsigned int64 have a data type 0f 0x0a and 0x0b that take up the last two
bytes of the signature.
The following il file hangs
ilasm.
.method public static method
unmanaged stdcall bool modopt([vijay]bb) * ( method unmanaged thiscall int8 modreq([vijay]cc) modreq([vijay]dd)
*(int16,method unmanaged fastcall int32 modreq([vijay]z1) modopt([vijay]z2) * (
bool , unsigned int32) ) ) modreq([vijay]ee) a2(int8 aa, int16 bb)
{
}
Program34.csc.txt
public void
DisplayAllMethods (int typerow)
{
if ( TypeDefStruct == null)
return;
if ( MethodStruct == null)
return;
int start , startofnext=0;
start = TypeDefStruct[typerow].mindex ;
if ( typerow ==
(TypeDefStruct.Length -1) )
{
startofnext=
MethodStruct.Length;
}
else
startofnext =
TypeDefStruct[typerow+1].mindex ;
for ( int methodindex =
start ; methodindex < startofnext ; methodindex++)
{
string methodstring =
CreateSpaces(spacesforrest);
if ( IsTypeNested(typerow))
methodstring = methodstring
+ CreateSpaces(spacesfornested);
methodstring =
methodstring + ".method ";
methodstring = methodstring
+ "/*06" + methodindex.ToString("X6") + "*/ " ;
string s = methodstring +
methoddefreturnarray[methodindex] + GetString(MethodStruct[methodindex].name) +
"(" + methoddefparamarray [methodindex] + ")" ;
Console.WriteLine(s);
}
}
string [] paramnames;
public void
CreateMethodDefSignature (byte [] blobarray , int row)
{
//Console.WriteLine("CreateMethodDefSignature
Array Length={0} method row={1} name={2}" , blobarray.Length , row ,
GetString(MethodStruct[row].name));
int
howmanybytes,uncompressedbyte , count , index;
index = 0;
howmanybytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
methoddeftypearray[row] =
DecodeFirstByteofMethodSignature(uncompressedbyte , row);
index = index +
howmanybytes;
howmanybytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
count = uncompressedbyte;
methoddefparamcount[row] =
count;
index = index +
howmanybytes;
string returntypestring =
"";
returntypestring =
GetElementType(index , blobarray , out
howmanybytes ,row , "Method") + " ";
index = index +
howmanybytes;
methoddefreturnarray[row] =
returntypestring;
string returnstring1 =
"" ;
string returnstring2 =
"";
string typestring =
"";
for ( int l = 1 ; l <=
count ; l++)
{
typestring = GetElementType
( index , blobarray , out howmanybytes , 0 , "") ;
index = index +
howmanybytes;
returnstring2= returnstring2
+ typestring ;
if ( l != count)
returnstring2 =
returnstring2 + ",";
int ind = GetParamRowNumber
(row, l );
Now every parameter like a
return value can have attributes like in, out, opt as well as marshalling information.
The GetParamAttrforParamCalling function takes the param row number and returns
the attributes.
string paramcalling =
GetParamAttrforParamCalling (ind);
string parammarshal =
GetParamAttrforParamMarshal(ind , 1 ) ;
returnstring1 =
returnstring1 + paramcalling ;
returnstring1 =
returnstring1 + typestring + " " ;
returnstring1 =
returnstring1 + parammarshal ;
returnstring1 =
returnstring1 + GetParamNameForMethod( row , count , l);
if ( l != count)
returnstring1 =
returnstring1 + "," ;
methoddefparamarray[row] =
returnstring1;
methoddefparamarray1[row] =
returnstring2;
}
}
public string
GetParamNameForMethod (int row , int count , int l)
{
string returnstring =
"";
string mattributes =
GetMethodAttribute(MethodStruct[row].flags , row);
if ( ParamStruct == null)
{
//isnan.exe
if (
mattributes.IndexOf("static") != -1)
l--;
return "A_" + l.ToString();
}
int start , startofnext=0 ,
paramcount;
start = MethodStruct[row].param ;
if ( row == (MethodStruct.Length -1) )
startofnext=
ParamStruct.Length;
else
startofnext =
MethodStruct[row+1].param;
paramcount = startofnext - start;
int ind;
bool paramfound = false;
for ( ind = start ; ind <
startofnext ; ind++)
{
if (
ParamStruct[ind].sequence == l)
{
paramfound = true;
break;
}
}
if ( paramfound &&
paramnames[ind] != "" )
returnstring =
NameReserved(paramnames[ind]);
else
{
//Interop.orders.dll
if (
mattributes.IndexOf("static") != -1)
l--;
returnstring =
"A_" + l.ToString();
}
return returnstring;
}
int GetParamRowNumber( int
row , int l )
{
if ( MethodStruct == null
|| ParamStruct == null)
return 0;
int start = MethodStruct[row].param ;
int end = 0;
if ( row == (MethodStruct.Length -1) )
end = ParamStruct.Length;
else
end =
MethodStruct[row+1].param;
end--;
int i;
for ( i = start ; i <=
end ; i++)
{
if ( ParamStruct[i].sequence
== l)
return i;
}
return 0;
}
public string
GetParamAttrforParamMarshal (int paramindex , int tabletype)
{
string returnstring =
"";
if (ParamStruct == null )
return "";
if (paramindex >=
ParamStruct.Length )
return "";
int pattr =
ParamStruct[paramindex].pattr;
returnstring =
DecodeParamAttributes( pattr , tabletype , paramindex , 0x2000);
return returnstring;
}
public string
GetParamAttrforParamCalling (int paramindex)
{
string returnstring =
"";
if (ParamStruct == null )
return "";
if (paramindex >=
ParamStruct.Length )
return "";
if
(ParamStruct[paramindex].sequence == 0)
return returnstring;
int pattr =
ParamStruct[paramindex].pattr;
if ( (pattr & 0x01) ==
0x01)
returnstring = returnstring
+ "[in]" ;
if ( (pattr & 0x02) == 0x02)
returnstring = returnstring
+ "[out]" ;
if ( (pattr & 0x10) ==
0x10)
returnstring = returnstring
+ "[opt]" ;
if ( returnstring !=
"")
returnstring = returnstring
+ " ";
return returnstring ;
}
public void FillArray ()
{
int old = tableoffset;
bool tablehasrows =
tablepresent(8);
int offs = tableoffset;
tableoffset = old;
if ( tablehasrows )
{
paramnames = new
string[rows[8] + 1];
for ( int k = 1 ; k <=
rows[8] ; k++)
{
short pattr =
BitConverter.ToInt16 (metadata, offs);
offs += 2;
int sequence = BitConverter.ToInt16
(metadata, offs);
offs += 2;
int name =
ReadStringIndex(metadata, offs);
offs += offsetstring;
string dummy =
GetString(name);
paramnames [k] =
NameReserved(dummy);
}
}
}
e.il
.assembly extern vijay
{
}
.class zzz
{
.method void a1(int8 i1 ,
int16 i2)
{
}
.method void a2(int8 , int16 i3)
{
}
.method static void
a21(int8 , int16 i3)
{
}
.method static void
a3(int8 , int16 )
{
}
.method [in] void a4( int8
i4 , int16 i5)
{
}
.method void marshal (int32) a5 ([out] int8 i4 ,
int16 i5)
{
}
.method void modopt([vijay]aa) modreq([vijay]bb) a6([out]
int8 i4 , int16 i5)
{
}
.method public static method
unmanaged stdcall int32*(int8,int16 aa ,string bb) a7(unsigned int8 a)
{
}
//.method public static
method unmanaged stdcall bool *( method unmanaged thiscall int8 *(int16,method unmanaged fastcall int32
* ( bool pp, unsigned int32 kk) ) , int64 gg, unsigned int64 dd) a82(){}
}
Method name=a1 ParmNo=1
Method name=a2 ParmNo=3
Method name=a21 ParmNo=5
Method name=a3 ParmNo=7
Method name=a4 ParmNo=9
Method name=a5 ParmNo=12
Method name=a6 ParmNo=15
Method name=a7 ParmNo=17
Param 1 name=i1 seq=1 attr=0
Param 2 name=i2 seq=2 attr=0
Param 3 name=A_0 seq=1
attr=0
Param 4 name=i3 seq=2 attr=0
Param 5 name=A_0 seq=1
attr=0
Param 6 name=i3 seq=2 attr=0
Param 7 name=A_0 seq=1
attr=0
Param 8 name=A_1 seq=2
attr=0
Param 9 name= seq=0 attr=1
Param 10 name=i4 seq=1
attr=0
Param 11 name=i5 seq=2
attr=0
Param 12 name= seq=0
attr=8192
Param 13 name=i4 seq=1
attr=2
Param 14 name=i5 seq=2
attr=0
Param 15 name=i4 seq=1
attr=2
Param 16 name=i5 seq=2
attr=0
Param 17 name=a seq=1 attr=0
Output
.method /*06000001*/ void a1(int8 i1,int16 i2)
.method /*06000002*/ void a2(int8 A_0,int16 i3)
.method /*06000003*/ void a21(int8 A_0,int16 i3)
.method /*06000004*/ void a3(int8 A_0,int16 A_1)
.method /*06000005*/ void a4(int8 i4,int16 i5)
.method /*06000006*/ void a5([out] int8 i4,int16 i5)
.method /*06000007*/ void modopt([vijay/* 23000001 */]aa/*
01000002 */) modreq([vijay/* 23000001 */]bb/* 01000003 */) a6([out] int8
i4,int16 i5)
.method /*06000008*/ int32 *(int8,int16,string) a7(unsigned int8
a)
In the DisplayAllMethods
method we now print out the contents of the array methoddefparamarray. In this
example we will display the method along with all the parameter data types and
names. Before we move onto the code, lets look at the il file first and at the
same time we have displayed the rows from the method table as well as the Param
table.
Lets look at method a1 in
the il file, it has two parameters i1 and i2. The method table has a field
called Param that behaves like the method field in the TypeDef table. The
current row tells us the first row number in the Param table and the next
record tells us the first row of the next method in the Param table.
Thus for row 1 in the method
table the param field has a value of 1 and for the next row the same field has
a value of 3 as the first method has two params. Moving on to the Params table,
the name field is the name of the param that is i1 and i2. In the second method
a2, the first param has no name and the second param a name i1.
In il we are allowed to
specify a parameter but give it no name. In spite of this, ilasm adds two
records to the param table and lets the second param have its name i3, but
gives the first param a name A_0. In method a21, the keyword static has been
added and we yet have the unknown name parameter as A_0.
Thus adding static does not
change the name that ilasm automatically chooses. In method a3, both the
parameters have no names and the system decides the names A_0 and A_1
respectively. This creates one more parameter that has no name and a sequence
number of zero.
In method a5 we have added
the marshal keyword which also add a new parameter with no name but a sequence
number of zero. In method a6 we have the modifiers modopt and modreq but this
does not effect the number of rows in the param table unlike the earlier two
cases.
Method a7 has a return value
of a pointer to function but this does not matter to ilasm as the function called
may have parameters but we do not specify any names. If we do, they get
ignored. Now lets take a look at the CreateMethodDefSignature method that fills
up the array methoddefparamarray with all the parameter data types and names.
The count variable tells us
the number of parameters that we have and we iterate all the parameters using a
for loop. We store in typestring the parameter data type that we get using the
GetElementType method. We do not specify the method row number and a method
name as we do not want to add the words method to the calling convention.
The returnstring2 variable
is set to the value of typestring and then we decide to add a comma or not. The
comma should be added to separate all parameters but the last. We now that we
are on the last parameter when variable count is equal to the variable l.
Hey where are the parameter
names. We need two types of signatures, one with parameter names when we
display a declaration of a function. When we call a function we also need the
signature but without the parameter names and only the data types. Thus we have
two separate strings returnstring2 and returnstring1.
The function
GetParamRowNumber is passed two parameters, the method row number and the
parameter number l. Given these two values, it must return the corresponding
row number owned by this parameter. If for some reason, a parameter does not
own any row, this function returns zero.
Lets figure out how this
function works. We first store in the start and end variable the first and last
parameter row owned by this method. We reduce the end variable by 1 as we do
not, it will give us the starting row of the next method. Then we iterate in a
for loop starting from start and ending with end.
This means that if our
method owns 5 rows in the param table, this loop will iterate five times. All
that we check is for a record with the same sequence number that the parameter
has. If we meet a match we return the param row number stored in the loop
variable i.
If we get out of the loop,
it means that this param have no row in the param table and hence we return 0.
Earlier we showed you that in spite of having parameters with no names and
attributes, the system yet allocated a row in the param table and gave the
parameter a name. This is what the current verison of ilasm does.
Thus all that we need to do
is figure out the starting param row number and then add l to it. The only
precaution we need to take is ask whether we have a param number with a
sequence of zero. The point is that today every parameter owns one record in
the param table.
Earlier versions of ilasm
made our lives more difficult and for reasons of efficiency did not allocate
one row to the param table. Thus in method a2, the first param that has no name
would not be allocated a row in the param table. Thus method a2 would have only
one row in the param table with a sequence number of 2. Method a3 would be
worse.
It would be allocated no row
in the param table as both params have no names or attributes. Thus we cannot
assume that every param has a row in the param table. But if it has one, the
sequence number field will have a value corresponding to the variable l. We had
a method called GetParamAttrforMethodCalling which returned the attributes.
There we read from the MethodStruct table and here we are reading from the
ParamStruct table. As the code for the attributes is not very large we decided
to simply copy, cut paste from the earlier method. A better way would be to
create a separate method for the common code which both methods could then
call.
Hey but we do not want to
win a prize for writing politically correct code. We did not make the same
mistake for the method GetParamAttrforParamMarshal where we now call the
DecodeParamAttributes with the attribute byte that we read from the Param
table.
The tabletype is 1 and the
flags is 0x2000 to specify a param attribute and not a return value attribute.
Now lets move on to the most important method GetParamNameForMethod that does
the real work. We pass it the Method row number, the count of parameters as
well as the parameter number.
The first problem is that we
may have a situation where we have 10 methods, all of them with parameters but
none of them with a name. This would give us as per the earlier compiler a
empty param or no param table. Thus we first check whether our param table is
empty. If yes, then we need to name the parameter ourselves.
The naming convention is
that the parameter names start with 1 for instance methods and one less for
static methods. Thus if the method has a static attribute, we reduce the
parameter number stored in l by 1 and then create a name A_ along with the
parameter number.
We then figure out as before
the stating and end param table row number but as we do not reduce the startofnext
by one, in the for loop we remove the equal to sign. Also the paramcount
variable tells us the number of parameters as per the Method table.
The count variable is as per
the method signature. Today both are the same, Yesterday this need not be true.
As before we loop in the for statement and leave when we find a sequence number
that matches our parameter count number stored in variable l. Along the way we
set the paramfound parameter to true.
We first move to our method
FillArray where we have filled up our paramnames array with the names of the
parameters. This is really not necessary as all that we do is fill up the
paramnames array with the name of the param stored in the name field. We then
ask if the row has a param, then the name would be stored in the paramnames
array.
At times however, the
parameter has a row in the param array but the name is a null. For these cases
the else must be called. The else must also be called when the param has no row
un the param table. Once again the earlier rules apply, reduce by 1 if it is
static method.
The following gives us the
wrong answer for the parameter names. The sequence numbers go haywire.
.method public static method
unmanaged stdcall bool modopt([vijay]bb) * ( method unmanaged thiscall int8 modreq([vijay]cc) modreq([vijay]dd)
*(int16,method unmanaged fastcall int32 modreq([vijay]z1) modopt([vijay]z2) * (
bool , unsigned int32) ) , int64 , unsigned int64) modreq([vijay]ee) a2(int8
aa, int16 bb)
{
}
Method a2 1
Param seq=4 name=aa attr=0
Param seq=2 name=bb attr=0
We have not finished methods
yet and we have miles to go.
Program35.csc.txt
public void
DisplayAllMethods (int typeindex)
{
if ( TypeDefStruct == null)
return;
if ( MethodStruct == null)
return;
int start , startofnext=0;
start = TypeDefStruct[typeindex].mindex ;
if ( typeindex ==
(TypeDefStruct.Length -1) )
{
startofnext=
MethodStruct.Length;
}
else
startofnext =
TypeDefStruct[typeindex+1].mindex ;
bool placedrvazero ;
for ( int methodindex =
start ; methodindex < startofnext ; methodindex++)
{
placedrvazero = false;
string methodstring =
CreateSpaces(spacesforrest);
if (
IsTypeNested(typeindex))
methodstring = methodstring
+ CreateSpaces(spacesfornested);
methodstring =
methodstring + ".method ";
methodstring = methodstring
+ "/*06" + methodindex.ToString("X6") + "*/ " ;
string methodattribute =
GetMethodAttribute(MethodStruct[methodindex].flags , methodindex);
int pos1 =
methodattribute.IndexOf("instance");
if ( pos1 != -1)
methodattribute =
methodattribute.Remove(pos1, 9);
string enterformethodattrbute
= "";
string enterforreturnvalue =
"";
string enterformarshal =
"";
string paramattrstring =
"";
string parammarshalstring =
"";
paramattrstring =
GetParamAttrforMethodCalling(methodindex);
parammarshalstring =
GetParamAttrforMethodMarshal(methodindex , 0 );
//Console.WriteLine("..methodattribute={0}
return={1} param={2} marshal={3} type={4}" , methodattribute ,
methoddefreturnarray[methodindex] , parammarshalstring , parammarshalstring1 ,
methoddeftypearray[methodindex]);
//int typeindex =
GetTypeForMethod(methodindex);
string nspace= "";
nspace=
NameReserved(GetString(TypeDefStruct[typeindex].nspace));
int parenttype =
GetParentForNestedType(typeindex);
if ( parenttype != 0)
nspace =
NameReserved(GetString(TypeDefStruct[parenttype].nspace));
bool cat2return = false;
bool cat3returnvaluemarshal
= false;
bool cat4 = false;
int howmany =
HowManyMethodAttributes(methodattribute);
//cat2return
if ( parammarshalstring ==
"" )
{
if ( methodattribute ==
"public static " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"private static " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"family static " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"assembly static " && !IsTypeNested(typeindex) &&
nspace == "" )
cat2return = true;
if ( methodattribute ==
"public virtual " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"private virtual " && !IsTypeNested(typeindex) &&
nspace == "")
cat2return = true;
if ( methodattribute ==
"family virtual " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"public newslot " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"private newslot " && !IsTypeNested(typeindex) &&
nspace == "")
cat2return = true;
if ( methodattribute ==
"family newslot " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"public final " )
cat2return = true;
if ( methodattribute ==
"private final " && !(IsTypeNested(typeindex) &&
nspace != "") )
cat2return = true;
if ( methodattribute ==
"family final " )
cat2return = true;
if ( methodattribute ==
"public abstract " && !IsTypeNested(typeindex) &&
nspace == "")
cat2return = true;
if ( methodattribute ==
"private abstract " && !IsTypeNested(typeindex) &&
nspace == "")
cat2return = true;
if ( methodattribute ==
"family abstract " && !IsTypeNested(typeindex) &&
nspace == "")
cat2return = true;
if ( methodattribute ==
"public hidebysig " && !IsTypeNested(typeindex) &&
nspace == "" )
cat2return = true;
if ( methodattribute ==
"family hidebysig " && !IsTypeNested(typeindex) &&
nspace == "" )
cat2return = true;
if (
methodattribute.IndexOf("pinvokeimpl") == -1)
{
if ( howmany == 0 &&
methoddeftypearray[methodindex].IndexOf("instance ") != -1 )
cat2return = true;
}
}
int aa = 0x00;
//cat3returnvaluemarshal
if ( parammarshalstring !=
"" )
{
if ( methodattribute ==
"public hidebysig " && nspace == "" &&
!IsTypeNested(typeindex))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"family hidebysig " && nspace == "" &&
!IsTypeNested(typeindex))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"public virtual " && nspace != "" &&
!IsTypeNested(typeindex))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"public virtual " && nspace == "" &&
!IsTypeNested(typeindex))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"public virtual " && nspace == "" &&
IsTypeNested(typeindex))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"public static " && ! (IsTypeNested(typeindex) &&
nspace != ""))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"private static " && ! (IsTypeNested(typeindex) &&
nspace != ""))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"family static " && ! (IsTypeNested(typeindex) &&
nspace != ""))
cat3returnvaluemarshal =
true;
if ( methodattribute ==
"assembly static " && !IsTypeNested(typeindex) &&
nspace == "")
cat3returnvaluemarshal =
true;
if ( howmany == 0 &&
methoddeftypearray[methodindex] == "instance " )
cat3returnvaluemarshal =
true;
}
//cat4
if (parammarshalstring !=
"" && methodattribute.IndexOf("pinvokeimpl") == -1)
{
if ( methodattribute ==
"public virtual " && nspace != "" &&
IsTypeNested(typeindex))
cat4 = true;
if ( howmany == 0 &&
IsTypeNested(typeindex) && nspace != "" &&
methoddeftypearray[methodindex] == "" )
{
bool problem1 = true;
if ( methodindex == aa)
Console.WriteLine("....{0}
{1}.." , methoddefreturnarray[methodindex].Length ,
methoddefreturnarray[methodindex]);
if ( methodattribute ==
"assembly static " || methodattribute == "public static "
|| methodattribute == "private static " || methodattribute == "family static " )
{
if ( paramattrstring ==
"" )
problem1 = false;
}
if ( problem1)
cat4 = true;
}
if (
methodattribute.IndexOf("hidebysig") != -1)
{
if (
methodattribute.IndexOf("public") != -1 ||
methodattribute.IndexOf("family") != -1)
{
if ( IsTypeNested(typeindex)
|| nspace != "" )
cat4 = true;
}
else
cat4 = true;
}
if ( howmany >= 2 )
cat4 = true;
}
if ( cat2return )
{
enterformethodattrbute =
"";
enterforreturnvalue =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
enterformarshal =
"";
}
else if (
cat3returnvaluemarshal)
{
enterformethodattrbute =
"";
enterforreturnvalue =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
enterformarshal =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
}
else if ( cat4)
{
enterformethodattrbute =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
enterforreturnvalue =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
enterformarshal =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
}
else
{
enterformethodattrbute =
"\r\n" + CreateSpaces(spacesforrest + 8 + spacesfornested);
enterforreturnvalue = "
";
if (parammarshalstring !=
"")
enterformarshal = "
";
}
methodstring = methodstring
+ methodattribute + enterformethodattrbute;
methodstring = methodstring
+ methoddeftypearray[methodindex] + paramattrstring ;
methodstring = methodstring
+ methoddefreturnarray[methodindex] + enterforreturnvalue;
methodstring = methodstring
+ parammarshalstring + enterformarshal
;
int no = 0;
if ( cat2return )
no++;
if ( cat3returnvaluemarshal)
no++;
if ( cat4)
no++;
if ( methodindex == aa || no
>= 2)
{
Console.WriteLine("...cat2return={0}
cat3returnvaluemarshal={1} cat4={2}
howmany={3} methoddeftypearray={4} paramattrstring={5}" ,
cat2return,cat3returnvaluemarshal,cat4 ,howmany ,methoddeftypearray[methodindex],paramattrstring
);
Console.WriteLine("...IsTypeNested={0}
nspace={1} parammarshalstring={2}",IsTypeNested(typeindex),
nspace,parammarshalstring);
}
if ( methodstring.Length -
methodstring.LastIndexOf("\r\n") >= 44 )
{
if ( methodindex == aa || no
>= 2)
{
Console.WriteLine("............");
}
methodstring =
methodstring.Remove(methodstring.Length-1,1);
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest + 8+ spacesfornested);
}
methodstring = methodstring
+ NameReserved(GetString(MethodStruct[methodindex].name)) ;
if (
methodattribute.IndexOf("privatescope") != -1)
methodstring = methodstring
+ "$PST06" + methodindex.ToString("X6");
methodstring = methodstring
+ "(";
Console.WriteLine(methodstring
);
}
public int HowManyMethodAttributes(string
methodattribute)
{
int count = 0;
if (
methodattribute.IndexOf("hidebysig") != -1)
count++;
if (
methodattribute.IndexOf("newslot") != -1)
count++;
if (
methodattribute.IndexOf("specialname") != -1)
count++;
if ( methodattribute.IndexOf("rtspecialname")
!= -1)
count++;
if (
methodattribute.IndexOf("final") != -1)
count++;
if (
methodattribute.IndexOf("virtual") != -1)
count++;
if (
methodattribute.IndexOf("abstract") != -1)
count++;
return count;
}
e.il
.class t1
{
.method public static
void a1() {}
}
.namespace n1
{
.class t1
{
.method public static
void a2() {}
}
}
.class t2
{
.class t1
{
.method public static
void a3() {}
}
}
.namespace n1
{
.class t2
{
.class t1
{
.method public static [in]
int8* a4() {}
}
}
}
Output
.method /*06000001*/ public
static void
a1(
.method /*06000002*/ public
static void
a2(
.method /*06000003*/ public
static void
a3(
.method /*06000004*/ public
static
[in] int8*
a4(
This program took us a long
time to figure out. You may wonder why as all that it does is simply
display the method declaration out for
us. A method declaration consists of the words method, its row number in the
method table, followed by the method attributes that the GetMethodAttribute
function gives us and we store in the variable methodattribute.
This is followed by the
return value attributes like in/out for which we have a method
GetParamAttrforMethodCalling. We store this string in the paramattrstring
variable. Then comes the return type that is stored in the
methoddefreturnarray.
This is followed by the
marshal keyword that is got by the method GetParamAttrforMethodMarshal and
stored in the string parammarshalstring. Lastly comes the name of the method.
Great, we have fetched all these values before and all that we need to do now
is string them together.
Right, no wrong. The problem
is not of spaces but of the lowly enter. Ildasm can use two or three or four
lines to display the method definition. Thus we now need to figure out where
the enters are to be placed. This is what is the cause of our troubles today
and yours while you read this book.
We searched in heaven and
hell and could not find the rules of where an enter must be placed. By trial
and error we hope that we have succeeded. We first remove the words instance
from the variable methodattribute. We do this by first using the method IndexOf
which returns either –1 or the position of our string in the string we are
looking for.
If the string is found, we
remove the words instance using the Remove method. The word static however is
left as is. This is because the words instance follow the return value, unlike
static where they remain separate.
We can have a enter at four
different places. First at the end of the method attributes, then after the
return value. An enter can also come after the marshal keyword and finally
before the name of the function if it is larger than a certain length.
Thus we initialize three
string variables that will store a space or an enter depending upon the
circumstance. To figure out where the enter needs be placed we first need to
know the type row number that the method belongs to. The parameter typeindex is
what contains the type row number for us.
We also need to know the
namespace the method is in if any. We first assume that the type is in no
namespace and hence we can simply store in nspace the name of the namespace
given the type. We have with us a method GetParentForNestedType that given the
type tells us the parent type which was covered earlier.
This function return either
the parent type of a zero. Thus if variable parenttype is non zero, we have a nested type and hence
the nspace variable will contain the
namespace of the parent type.
We also figured out the hard
way that as mentioned earlier where an enter came come so we have three Boolean
variables to tells where the enters should happen. The variable cat2return if
true tells us that this method should be on two lines with an enter after the
return value.
This means that the method
attributes and the return value is on the same line. The second Boolean
cat3returnvaluemarshal is for those cases where the method moves on to three
lines and an enter is needed after the return value as well as the marshal keyword.
The last cat4 is when the method signature is over 4 lines.
After the method attribute,
return value as well as the marshal keyword. Before we actually start, there is
a useful function called HowManyMethodAttributes. We found out that not only are
the enters decided by whether the type is nested or not, whether it is within a
namespace or not but also by how many method attributes there are.
Thus we need a count of the
number of attributes like hidebysig , newslot, specialname, rtspecialname, final,
virtual and abstract. We have not counted attributes like the access ones like
public or static or pinvokeimpl. We now move on to finding out which category
out of our four a method belongs to.
The first is when we have
the method on two lines and the enter must come after the return value placing
the method name on a new line or cat2retrun. To stress test our hypothesis we
have used a separate il file for each test. Lets start with public static as
the only attributes for our method.
In our il file we try it
with four possibilities. In a class with no namespace and no nesting, in a
class that is placed within a namespace , in a class that is nested, and
finally a nested class as well as in a namespace. What we leaned that as long
as the class does not have both a namespace and is nested, the cat2return will
be true.
Thus the if statement is
true for all cases but those where both nspace is not null as well as the type
is not nested. This case only applies if the marshal keyword is absent or the
variable parammarshalstring is null. The private static and public static also
follow the same rules and hence are not mentioned.
The deviation is assembly
static where cat2return is true only is both the type is not nested and the
namespace is empty. Thus we have realized that there was no consistency in
where the enters are to be placed.
The next three if statements
check whether the method attribute is public virtual, private virtual as well
as family virtual and that both type is not nested as well as the namespace is
not null. Fortunately the public newslot, private newslot and family newslot
behave in the same way.
However for public final
family final, the namespace and nesting is not relevant but for private final
both namespace and nesting cannot be true. When it comes to public abstract,
private abstract and family abstract, there must be no nesting and the
namespace must be null.
The same rules also apply to
public hidebysig and family hidebysig also. The last rule is pretty different.
First the pinvokeimpl attribute must be absent. Then this must apply to an
instance method and not a static one. Finally there must be no attribute like
newslot, final etc be present.
This can happen only if the
howmany variable is zero. Thus lets look at a specific case.
.class t1
{
.method public void a1() {}
.method void a2() {}
}
.method /*06000001*/ public instance void
a1(
.method /*06000002*/ privatescope instance void
a2$PST06000002(
Thus if we have no
attributes other than the access modifiers and privatescope is the default for
no access modifer we meet the last rule.
Lets move on to the second
condition where we place the message definition on three lines with a enter
after the return values as well as after the keyword marshal. This obviously
assumes that the keyword marshal is exists. Lets take the case of the
attributes public static but with the marshal keyword present unlike earlier.
.class t1
{
.method public static
void marshal(int32 )a1() {}
}
.namespace n1
{
.class t1
{
.method public static
void marshal(int32 ) a2() {}
}
}
.class t2
{
.class t1
{
.method public static
void marshal(int32 ) a3() {}
}
}
.namespace n1
{
.class t2
{
.class t1
{
.method public static [in]
int8* marshal(int32 ) a4() {}
}
}
}
.method /*06000001*/ public
static void
marshal( int32)
a1(
.method /*06000002*/ public
static void
marshal( int32)
a2(
.method /*06000003*/ public
static void
marshal( int32)
a3(
.method /*06000004*/ public
static
[in] int8*
marshal( int32)
a4(
The same il file as before
now with the marshal keyword added. The first three methods come on three lines
with a enter after the return value and marshal keyword. When the class is
nested as well as the namespace is present, the method falls over four lines.
The same rules apply for
private static and family static. The odd man out is always assembly static
where the type must not be nested as well as the type cannot be present within
a namespace. The attributes public hidebysig and family hidebysig are like
assembly static. Some things do not change.
The attributes public
virtual are like public static but we have written them as three separate conditions
instead of one. You figure out which one is better for you to understand. The
C# compiler does not seem to care.
The last rule is like the
earlier case where we had a single access attribute and nothing else and a
instance function. Some similarity with the cat2return case but not enough to
generalize them in code.
Lets take the case where
everything falls over four lines. This will set the cat4 variable to true. The
two general conditions are that the marshal keyword must be present and the pinvokeimpl
attribute absent. The simple case of public virtual shows the method definition
over four lines only for the most complex case, nesting and namespace.
.namespace n1
{
.class t2
{
.class t1
{
.method public virtual [in]
int8* marshal(int32 ) a4() {}
}
}
}
.method /*06000001*/ public
virtual
instance [in] int8*
marshal( int32)
a4(
Thus the method definition
is placed over four lines.
The second case is more
complex. It starts with us having a single access modifier as the howmany
variable is zero. Then the type must be nested and the namespace not empty and
the method being static and not instance.
We set the variable problem1
to true and this sets off the cat4 variable being true. This is demonstrated by
methods a1 and a2. The only problem is that it works for attributes like
privatescope and famandassem and not for assembly static, public static,
private static and family static.
Here we have two further
conditions that will set the problem1 variable to false and negate the cat4
variable being set to true. The first case is when the param attribute string
is null. Thus methods a3 and a4 have no param attributes like in and out and
thus problem1 is false and the method do not fall over four lines.
There are however cases
where the length of the marshal type matters, but we have ignored them.
.namespace n1
{
.class t2
{
.class t1
{
.method static [in] int8*
marshal(int32 ) a1() {}
.method famandassem static
[in] int8* marshal(int32 ) a2() {}
.method public static
void marshal(int8 ) a3() {}
.method public static int8
** marshal(int32 ) a4() {}
}
}
}
.method /*06000001*/
privatescope static
[in] int8*
marshal( int32)
a1$PST06000001(
.method /*06000002*/
famandassem static
[in] int8*
marshal( int32)
a2(
.method /*06000003*/ public
static
void marshal(
int8) a3(
.method /*06000004*/ public
static
int8**
marshal( int32) a4(
The complexities have just
started. If the attributes contain a hidebysig and within this either public or
family. In the first set of the il file, the type is nested and both private
and public are seen over two lines. Private is the normal case and public
because it is either in a nested class or in a namespace. In the second case, the class is not nested or in a
namespace and the private is on four lines but the public only on three.
.class t2
{
.class t1
{
.method private hidebysig
[in] int8* marshal(int32 ) a1() {}
.method public hidebysig
[in] int8* marshal(int32 ) a2() {}
}
}
.class t1
{
.method private hidebysig
[in] int8* marshal(int32 ) a1() {}
.method public hidebysig
[in] int8* marshal(int32 ) a2() {}
}
.method /*06000001*/ private
hidebysig
instance [in] int8*
marshal( int32)
a1(
.method /*06000002*/ public
hidebysig
instance [in] int8*
marshal( int32)
a2(
.method /*06000003*/ private
hidebysig
instance [in] int8*
marshal( int32)
a1(
.method /*06000004*/ public
hidebysig instance [in] int8*
marshal( int32)
a2(
The final case is when we
have two or more than two attributes like newslot, virtual etc and we have a marshal
keyword, then everything falls over three lines. Now we have to implement what
we have doe so far.
Thus we have a series of if
statements that set the three string variables enterformethodattrbute,
enterforreturnvalue and enterformarshal to their values. If cat2return is true,
then there should be nothing after the method attribute and we set the
enterformethodattrbute variable to null.
This also means that some
later we will concatenate the methodattribute variable with the enterformethodattrbute variable and then
concatenate the return value stored in the methoddefreturnarray with the
enterforreturnvalue variable.
As we want an enter after
the return value we set the enterforreturnvalue to an enter followed by spaces.
These depend upon the nesting level as well as the words .method. Then we come
to the second case cat3returnvaluemarshal where we make the string
enterformethodattrbute null as no enter after the method attributes but we need
a enter after the return value and the enterformarshal.
For the cat4 we need an
enter placed for all the three variables. Finally we let you on a secret. The
most common occurrence is when the everything is placed on two lines with a
enter after the method attribute. This places the return value on a new line
along with the method name.
As this occurs most often we
put it in the final statement. This is why our code is s small as the else
takes care of everything else. The il file has a small sample.
.class zzz
{
.method public newslot
virtual final static [in] int8 **a3()
{
}
}
.method /*06000001*/ public
newslot static final virtual
[in] int8** a3(
The above occurs is the
spoilt child and occurs with the maximum frequency. Now we concatenate the
strings as explained before to the string methodstring. Finally we have to add
the last enter if the length of the string is larger than 44.
For some reason ildasm does
not like the last line before the method name to exceed 44. We check the length
of the string to date and minus the last occurrence of an enter. If this value
is greater than 44, we now need to add a enter. But before we do this, we have
a space at the end of the line that we need to remove and hence the Remove
method removes the last space.
Then we add a new enter to
the method string. All is not over after adding the name as if this methods
attribute is privatescope, we have to add the words $PST with the row number of
the method table. We finally add the ( bracket.
program36.csc.txt
methodstring = methodstring
+ "(";
int len = 0;
if (
methoddefparamarray[methodindex] != null)
{
int indexofenter =
methodstring.LastIndexOf("\r\n");
int lengthofstring =
methodstring.Length ;
len = lengthofstring -
indexofenter - 2;
methodstring = methodstring
+ ParamOnMultipleLines (methoddefparamarray[methodindex] , len );
}
else
methodstring = methodstring
+ methoddefparamarray[methodindex];
methodstring =
methodstring + ")";
string implflags =
GetMethodAttribute1 (MethodStruct[methodindex].impflags);
methodstring = methodstring
+ " " + implflags ;
string methodsignature =
GetMethodSignatureBytes (methodindex);
methodstring = methodstring
+ methodsignature;
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest+ spacesfornested) +
"{" ;
Console.WriteLine(methodstring
);
}
}
public string
ParamOnMultipleLines (string paramstring , int spaces)
{
if ( paramstring ==
"")
return "";
if ( paramstring == null)
return "";
string returnstring =
"";
if (
paramstring.IndexOf(",") == -1
)
{
paramstring =
paramstring.Replace("^" , ",");
return paramstring;
}
char [] chararray = {','};
string [] stringarray =
paramstring.Split(chararray);
returnstring =
stringarray[0] + ",";
for ( int ii = 1 ; ii <
stringarray.Length ; ii++)
{
returnstring = returnstring
+ "\r\n" + CreateSpaces(spaces) + stringarray[ii] ;
if ( ii !=
stringarray.Length -1 )
returnstring =
returnstring + ",";
}
returnstring =
returnstring.Replace("^" , ",");
return returnstring;
}
public string
GetMethodAttribute1 (int methodflags)
{
string returnstring =
"";
if ( (methodflags &
0x0003) == 0x0003)
returnstring = returnstring
+ "runtime ";
else
if ( (methodflags &
0x0001) == 0x0001)
returnstring = returnstring
+ "native ";
else
returnstring = returnstring
+ "cil ";
if ( (methodflags &
0x0004) == 0x0004)
returnstring = returnstring
+ "unmanaged";
else
returnstring = returnstring
+ "managed";
if ( (methodflags &
0x0080) == 0x0080)
returnstring = returnstring
+ " preservesig";
if ( (methodflags &
0x0010) == 0x0010)
returnstring = returnstring
+ " forwardref";
if ( (methodflags & 0x01000)
== 0x1000)
returnstring = returnstring
+ " internalcall";
if ( (methodflags &
0x0020) == 0x0020)
returnstring = returnstring
+ " synchronized";
if ( (methodflags &
0x0008) == 0x0008)
returnstring = returnstring
+ " noinlining";
return returnstring;
}
public string
GetMethodSignatureBytes (int methodrow)
{
string returnstring = "\r\n" +
CreateSpaces(spacesforrest+ spacesfornested) + "// SIG: ";
int uncompressedbyte;
int signatureindex =
MethodStruct[methodrow].signature;
int howmanybytes;
howmanybytes =
CorSigUncompressData(blob , signatureindex, out uncompressedbyte);
int count =
uncompressedbyte;
if ( howmanybytes == 2)
count++;
for (int j = howmanybytes ;
j <= count ; j++)
{
returnstring = returnstring
+ blob[signatureindex + j ].ToString("X2") ;
if ( j != count )
returnstring = returnstring
+ " ";
}
return returnstring;
}
e.il
.class zzz
{
.method public newslot
virtual final static [in] int8 **aa(int8 I , int16 j, int32 k)
{
}
}
.method /*06000001*/ public
newslot static final virtual
[in] int8**
aa(int8 I,
int16 j,
int32 k) cil managed
// SIG: 00 03 0F 0F 04 04 06 08
{
This program is much simpler
than the previous one as we are using it as a breather. The next one obviously
will be more problematic to understand. We first place the ( bracket and then
we have to place the parameters to the method. If the method has no parameter
or a single one very good.
If it has more than one, the
second one onwards comes on a new line with some spaces so that it is indented
one below the other. This we only do if we have parameters and thus we check
the value of the methoddefparamarray being non null. We first need to find out
where we placed the last enter.
This as explained earlier
could be at a number of places. We then figure out the length of the string so
far and the number of spaces we need to place is the difference between the two
and we further need to subtract 2 from this. This is because the data types
have to line up one against the other in a lock step fashion.
Taking the example of method
aa, the last enter has been placed before the name of function and the
difference between them will give us in this case the column number where the
type name of the first parameter is to be placed. Looking at it from another viewpoint, we place the type name
after the name of the method and the open bracket.
After putting the last
enter, we added some spaces, we then placed the name of the method and a open
bracket. Then we wrote the type name. We want to know how many characters we
placed after the last enter bearing in mind that the enter is two characters.
We now pass the method
ParamOnMultipleLines the array methoddefparamarray that contains the parameters
with commas and also the number of spaces. All that we want this method to do
is place a enter and len number of spaces after each comma and return such a
string that we concatenate with methodstring.
The function is pretty
simple as we first check for a null or empty string. Remember this function
gets called from lots of places. We then check whether we have a comma or not
in our paramstring. If we do not then it could only mean that we have only a
single parameter and thus we do nothing.
At two places in this
function we replace a ^ with a , and we will later explain why. Now’s not the
time and place. The String class lets us split a string on a series of
characters. Whenever someone wants more than one we can give it in an array.
Thus the split method
requires an array of chars and it will split the string into a series or arrays
of strings whenever that character is met. Thus in method aa as we have three
parameters and two commas, we will get an array of three strings minus the
commas. This happens as we have placed a comma in the chararray array.
Had we also placed a
semicolon, the Split method would break the string whenever it found a comma or
a semicolon. Now we start concatenating the string in the variable returnstring
by simply initializing it to the first member of the array stringarray and
placing a comma.
Then we go in a loop and keep adding to returnstring an
enter and the number of spaces we had calculated earlier. As always we add a
comma only if it is not the last member.
Now we add the close the
bracket and then there are some more attributes that come at the end of the
method name. We have a method GetMethodAttribute1 that gets us these attributes
and there is a separate field for these attributes in the Method table.
If we do not specify an
attribute the default is cil managed. We will delve on these attributes in a
short while. We now add these final attributes and now we need to display the
method signature within comments on a fresh line.
We have a method aptly
called GetMethodSignatureBytes that does the work for us. This signature we
will not explain as we began the create method def method with displaying these
bytes off the blob array.
We only place a space if it
is not the last byte, the same we do for a comma. The reason we increment the
count variable by 1 if the howmanybytes variable is 2 as we have taken two
bytes for the length of the signature. We also add a enter and a series of
spaces as well as the open the brace.
e.il
.class zzz
{
.method int8 aa(int8
[3...7,,,0...,45] i)
{
}
}
.method /*06000001*/
privatescope instance int8
aa$PST06000001(int8[3...7,0,0,0,45] i) cil managed
// SIG: 20 01 04 14 04 05 05 05 00 00 00 2D 05 06 00 00 00 00
When we wrote the function
ParamOnMultipleLines we did not consider that an array data type can also have
commas and in this case we cannot break at the commas. Thus we needed to hide
the commas in the array data type. So we came up with an ingenious solution.
In the GetArrayType function
at three different places we add a comma to the return string. We simply
replace these commas with a ! sign. As an example we replace the line
returnstring = returnstring + ","; with returnstring = returnstring +
"^";.
Thus in the param method if
we have a single parameter we have replace the ! with a comma and at the end.
Then unfortunately a return value can also have arrays and hence the
methoddefreturnarray must also have all !’s replaced by commas. Thus in the
CreateMethodDefSignature method we must add the line returntypestring =
returntypestring.Replace("^",",");
This is the quick and dirty
way of writing code. Do not think about all the options at the beginning and
then keep making changes and watch the program go berserk at the wrong time.
Think first and then think again and then write code. Free advise that no one
heeds. Now you know what sloppy programming is all about.
Program37.csc
string methodsignature =
GetMethodSignatureBytes(methodindex);
methodstring = methodstring
+ methodsignature;
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest+ spacesfornested) +
"{" ;
int tablerow =
entrypointtoken & 0x00ffffff;
if ( tablerow == methodindex
)
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest + 2 + spacesfornested) +
".entrypoint";
Console.WriteLine(methodstring);
//DisplayCustomAttribute("MethodDef"
, methodindex , 2 + spacesforrest + spacesfornested);
//Console.Write(DisplayParamsForMethod(methodindex));
//DisplayMoreParamsForMethod(methodindex);
DisplayAllSecurity( 1 ,
methodindex );
//DisplayOverrideMethod(methodindex);
// interfaces.exe
if
(implflags.IndexOf("cil managed") == -1 ||
MethodStruct[methodindex].rva == 0)
{
bool problem1 = true;
if (
methodattribute.IndexOf("pinvokeimpl") != -1 )
{
if (
methodattribute.IndexOf("pinvokeimpl(/* No map */)") != -1)
{
//problem is that we cannot
create one in ilasm because of the No Map and if no map is not there then else
gets called
Console.WriteLine(" // Embedded native code");
Console.WriteLine(" //
Disassembly of native methods is not supported.");
Console.WriteLine(" //
Managed TargetRVA = 0x{0}" ,
MethodStruct[methodindex].rva.ToString("x4"));
Console.WriteLine(CreateSpaces(spacesforrest+
spacesfornested) + "} // end of global method " +
NameReserved(GetString(MethodStruct[methodindex].name)));
}
else
{
methodstring =
CreateSpaces(spacesforrest+ spacesfornested) + "}" ;
Console.Write(methodstring);
}
methodstring = "";
problem1 = false;
}
else if
(implflags.IndexOf("runtime managed") != -1)
{
methodstring = "";
}
else if
(implflags.IndexOf("runtime unmanaged") != -1)
methodstring = " //
Method provided by Runtime\r\n";
else if
(implflags.IndexOf("native unmanaged") != -1)
methodstring = " //
Unmanaged TargetRVA = 0x" +
MethodStruct[methodindex].rva.ToString("x4") + "\r\n";
else if ( implflags.IndexOf("native")
!= -1)
{
methodstring = " //
Disassembly of native methods is not supported.";
methodstring = methodstring
+ "\r\n // Managed TargetRVA = 0x" +
MethodStruct[methodindex].rva.ToString("x4") + "\r\n";
}
else if (
MethodStruct[methodindex].rva == 0 &&
implflags.IndexOf("unmanaged") == -1 )
{
methodstring =
CreateSpaces(spacesforrest+ spacesfornested + 2) + "// Method begins at
RVA 0x0\r\n";
}
else
{
methodstring = "";
}
if (problem1)
methodstring = methodstring
+ CreateSpaces(spacesforrest+ spacesfornested) + "} // end of method
" + NameReserved(GetString(TypeDefStruct[typeindex].name)) +
"::" + NameReserved(GetString(MethodStruct[methodindex].name)) +
"\r\n";
Console.WriteLine(methodstring);
continue;
}
methodstring = "";
methodstring = methodstring
+ CreateSpaces(spacesforrest + 2 + spacesfornested) + "// Method begins at RVA 0x" +
MethodStruct[methodindex].rva.ToString("x4");
Console.Write(methodstring);
}
}
}
e.il
.class abstract zzz
{
.method public static
pinvokeimpl("user32.dll" ansi stdcall lasterr ) int8 a1() native
unmanaged
{
}
.method static public int8
a0() cil managed
{
.entrypoint
}
.method public int8 a4()
runtime managed
{
}
.method public int8 a5()
runtime unmanaged
{
}
.method public int8 a6()
native unmanaged
{
}
.method public int8 a7()
native managed
{
}
.method abstract void a9() cil managed
{
}
.method public int8 a8() cil
unmanaged
{
}
.method void a10() cil managed preservesig forwardref
internalcall synchronized noinlining
{
}
}
//.method public static pinvokeimpl("user32.dll"
ansi stdcall lasterr ) int8 a2()
//{
//}
//.method public static
pinvokeimpl(/* No map */) unsigned int32
_mainCRTStartup() native unmanaged preservesig{}
Output1
//Global methods
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.method /*06000001*/ public
static pinvokeimpl("user32.dll" ansi lasterr)
int8 a2() cil
managed
// SIG: 00 00 04
{
}
Output2
.class /*02000002*/ private
abstract auto ansi zzz
extends [mscorlib/* 23000001 */]System.Object/* 01000001 */
{
.method /*06000001*/ public static
pinvokeimpl("user32.dll" ansi lasterr)
int8 a1() native
unmanaged
// SIG: 00 00 04
{
}
.method /*06000002*/ public static int8
a0() cil managed
// SIG: 00 00 04
{
.entrypoint
// Method begins at RVA 0x2051
.method /*06000003*/ public instance int8
a4() runtime managed
// SIG: 20 00 04
{
} // end of method zzz::a4
.method /*06000004*/ public instance int8
a5() runtime unmanaged
// SIG: 20 00 04
{
// Method provided by
Runtime
} // end of method zzz::a5
.method /*06000005*/ public instance int8
a6() native unmanaged
// SIG: 20 00 04
{
// Unmanaged TargetRVA
= 0x2054
} // end of method zzz::a6
.method /*06000006*/ public instance int8
a7() native managed
// SIG: 20 00 04
{
// Disassembly of
native methods is not supported.
// Managed TargetRVA =
0x2055
} // end of method zzz::a7
.method /*06000007*/ privatescope abstract
instance void
a9$PST06000007() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x0
} // end of method zzz::a9
.method /*06000008*/ public instance int8
a8() cil unmanaged
// SIG: 20 00 04
{
} // end of method zzz::a8
.method /*06000009*/ privatescope instance void
a10$PST06000009() cil managed preservesig forwardref
internalcall synchronized noinlining
// SIG: 20 00 01
{
// Method begins at RVA 0x0
} // end of method zzz::a10
} // end of class zzz
In this example we once
again display some more details about a method. We first want to know whether
the method is the first method to be called or does it have the entrypoint
directive attached to it. Some years ago we explained the instance variable
entrypoint that was stored in the first metadata structure contained the row
index of the entrypoint method.
We thus knock off the last
or high eight bits and store the method row number in the tablerow variable. We
then check this value with the variable methodindex and if we find a match we
write out the entrypoint directive. Thus method a0 has the entrypoint token and
this is why the method is static.
We then come across four
methods that are commented out and we will deal with them in separate programs
following this one. Too many cooks spoil the broth and too many concepts in one
program gives Jack a splitting headache. A method at the end also has
attributes that are divided into three categories.
The first is the nature of
code and this can be four of which we do three now. These are il, native and
runtime. These are mutually exclusive. The attribute il is the normal one which
says the obvious, the code we write is in the IL programming language which is
the default.
The problem is that using
the pinvokeimpl keyword we can specify that the code is somewhere else and
hence in a another language and hence the attribute native. For delegates, the
code is supplied by the system and hence we have the attribute runtime to
describe it. Then we have another complication, managed or unmanaged.
Managed code is code written
in il that the system guarantees will break no rules and hence is safe to run.
Unmanaged code is normally code written with pointers in C# and as no rules can
be enforced on it, the system does not guarantee that the code will break no
rules and hence unmanaged. Thus we have six combinations, all not valid.
Thus native managed makes no
sense as the code is somewhere else and the system however cannot take any
responsibility for its good behavior. The problem as always is that nobody
check or nonsensical combinations. Then finally there are attributes like
noinlining that fall into their own separate category.
The only attribute that
carries code is the one with cil managed. Thus we need to trap those where the
attribute is not cl managed and also the rva field that tells us where in
memory the code is stored is stored for this function is not zero. If this
field is zero, then it only means that there is no code for this method.
We first check for the
attributes pinvokeimpl. Here we again make a distinction on whether the method
is global or not. Functions a1 and a2 have the pinvokeimpl attribute and a2 is
global which means that it is outside a type. In C# because of Java, functions
outside a class or global are not permitted whereas in il they are.
These are artificial rules
that programming languages apply for no valid rhyme or reason. Now if we have
along with the pinvokeimpl attribute a No map value, then we are specifying
that there is no dll that has the code for this method. If we have such a
method in our code, ilasm gives us an error.
But for some reason there
are scores of programs written in other languages that for some reason compile
to an executable. One of them is a file called isymwrapper.dll. This .net
executable when run through ildasm and then the out through ilasm gives us an
error at the pinvokeimpl attribute. The round tripping does not work at all.
Thus in the il file we have
a example commented out for you to see the error that happens. If the
pinvokeimpl attribute takes a No Map attribute, we need to the display three
lines along with the rva field. If the no map is absent all that we do is
simply display the closing brace and nothing else.
We also the methodstring
attribute to empty as we have already used the WriteLine function to display
its value along with the entrypoint directive outside the if statement. We also
set the problem1 bool variable to false as it value by default is true. Now
there are some individual cases to handle.
If the implflags are runtime
managed as in method a4 we need no display anything and hence the methodstring
variable is set to false. If the attributes are runtime unmanaged in function
a5, or native unmanaged method a6, we need to display another value. Else if it
any variation of native like method a7 we display another string.
Then if the rva is zero and
not unmanaged like method a9 which is abstract and managed we have one more if
statement to handle it. Finally for cases that do not fit in any of the above
like the likes of method a8 that does
not have the abstract attribute we have the final else that takes of every case
which requires an empty string.
Method a10 has all the extra
attributes tagged along. For all except the pinvokeimpl attribute we have not
written the words end of method. Thus only if variable problem1 is not set to
false do we set the methodstring to the words end of method.
We then also write out the
name of the namespace and the name and in all these cases as there is no code,
we need to loop back to the start of the for and the next method. If we do not
enter this if statement, it only means that we have lots of code and thus we
write out the rva of the method and then proceed to display the code that we
will display years from now. Lots of other more important work to be done
first.
We are also displaying one
such pinvokeimpl attribute from the file isymwrapper.dll where it is a global
method.
else if ( implflags.IndexOf("optil")
!= -1 )
{
methodstring =
CreateSpaces(spacesforrest+ spacesfornested + 2) + "// Method begins at
RVA 0x" +
MethodStruct[methodindex].rva.ToString("x4") +
"\r\n";
methodstring = methodstring
+ CreateSpaces(spacesforrest+ spacesfornested + 2) + "// Code size 0 (0x0)\r\n";
}
else
{
methodstring = "";
}
e.il
.class zzz
{
.method void a1() optil
managed
{
}
}
Output
.method /*06000001*/ privatescope instance void
a1$PST06000001() optil managed
// SIG: 20 00 01
{
// Method begins at RVA
0x2050
// Code size 0
(0x0)
} // end of method zzz::a1
GetMethodAttribute1
if ( (methodflags &
0x0002) == 0x0002)
returnstring = returnstring
+ "optil ";
else
returnstring = returnstring
+ "cil ";
There is one last reserved
attribute optil that cannot carry any code. Thus in the GetMethodAttribute1 we
simply check whether the value is 2 which is the code for the attribute optil.
We next add a if statement to check this value of optil to add two lines of
code to our method.
Program38.csc
public void
DisplayOverrideMethod (int methodrow)
{
if ( MethodImpStruct ==
null )
return;
for ( int ii = 1 ; ii <
MethodImpStruct.Length ; ii++)
{
string codeddeftablename =
GetMethodDefTable (MethodImpStruct[ii].codeddef );
int codeddefindex =
GetMethodDefValue (MethodImpStruct[ii].codeddef );
string codedbodytablename =
GetMethodDefTable(MethodImpStruct[ii].codedbody);
int codedbodyindex =
GetMethodDefValue(MethodImpStruct[ii].codedbody );
//Console.WriteLine("...codeddeftablename={0}
codedbodytablename={1}",codeddeftablename,codedbodytablename);
int typeindex =
GetTypeForMethod(codedbodyindex);
if ( typeindex == MethodImpStruct[ii].classindex &&
codedbodytablename ==
"MethodDef" && codedbodyindex == methodrow)
{
if (codeddeftablename ==
"MethodRef" )
{
Console.Write(CreateSpaces(spacesforrest+2
+ spacesfornested));
Console.Write(".override
");
int typerefindex =
GetTypeRefFromMethodRef (codeddefindex);
Console.WriteLine("{0}::{1}
/*01{2}::0A{3}*/ " , typerefnames [typerefindex],
NameReserved(GetString(MemberRefStruct[codeddefindex].name)) , typerefindex
.ToString("X6") , codeddefindex.ToString("X6"));
}
if (codeddeftablename == "MethodDef" )
{
Console.Write(CreateSpaces(spacesforrest+2
+ spacesfornested));
int typedefindex = GetTypeForMethod(codeddefindex);
string nestedtypestring =
"";
nestedtypestring =
GetNestedTypeAsString (typedefindex);
string nspace =
NameReserved(GetString(TypeDefStruct[typedefindex].nspace));
if ( nspace != "")
nspace = nspace +
".";
nspace = nestedtypestring +
nspace + NameReserved(GetString(TypeDefStruct[typedefindex].name));
Console.Write(".override
{0}/* 02{1} */::{2} " , nspace,
typedefindex.ToString("X6"),NameReserved(GetString(MethodStruct[codeddefindex].name))
);
Console.WriteLine("/*02{0}::06{1}*/
" , typedefindex.ToString("X6") ,
codeddefindex.ToString("X6"));
}
}
}
}
public int
GetMethodDefValue(int implcoded)
{
return implcoded >> 1;
}
public string
GetMethodDefTable(int implcoded)
{
string returnstring =
"";
short tag =
(short)(implcoded & (short)0x01);
if ( tag == 0)
returnstring = returnstring
+ "MethodDef";
if ( tag == 1)
returnstring = returnstring
+ "MethodRef";
return returnstring;
}
public int GetTypeForMethod
(int methodrow)
{
int typedefindex = 0;
for ( typedefindex = 1 ;
typedefindex < TypeDefStruct.Length - 1
; typedefindex++)
{
int start =
TypeDefStruct[typedefindex].mindex;
int end =
TypeDefStruct[typedefindex+1].mindex -1 ;
if ( methodrow >= start
&& methodrow <= end)
return typedefindex;
}
return typedefindex;
}
public int GetTypeRefFromMethodRef(int
methodrefrow)
{
int memberrefcodedindex = 0;
memberrefcodedindex =
MemberRefStruct[methodrefrow].clas;
string tablename =
GetMemberRefTable(memberrefcodedindex);
int typerefindex =
GetMemberRefValue(memberrefcodedindex);
return typerefindex;
}
public string
GetMemberRefTable(int memberrefcodedindex)
{
string returnstring =
"";
short tag =
(short)(memberrefcodedindex & (short)0x07);
if ( tag == 0)
returnstring = returnstring
+ "NotUsed";
if ( tag == 1)
returnstring = returnstring
+ "TypeRef";
if ( tag == 2)
returnstring = returnstring
+ "ModuleRef";
if ( tag == 3)
returnstring = returnstring
+ "MethodDef";
if ( tag == 4)
returnstring = returnstring
+ "TypeSpec";
return returnstring;
}
public int
GetMemberRefValue(int memberrefcodedindex)
{
return memberrefcodedindex
>> 3;
}
}
e.il
.assembly extern vijay
{
}
.class zzz
{
.method void a1() native
unmanaged
{
.override zzz::a2
.override [vijay]yyy::a3
}
.method void a2() runtime
managed
{
}
}
Output
.class /*02000002*/ private auto
ansi zzz
extends [mscorlib/* 23000002 */]System.Object/* 01000001 */
{
.method /*06000001*/ privatescope instance void
a1$PST06000001() native unmanaged
// SIG: 20 00 01
{
.override zzz/* 02000002 */::a2 /*02000002::06000002*/
.override [vijay/* 23000001 */]yyy/* 01000002 */::a3
/*01000002::0A000001*/
// Unmanaged TargetRVA
= 0x2050
} // end of method zzz::a1
.method /*06000002*/ privatescope instance void
a2$PST06000002() runtime managed
// SIG: 20 00 01
{
} // end of method zzz::a2
} // end of class zzz
This program basically looks
at the override keyword. This keyword can occur within a class or a method. The
simpler version is found in the method and not in the class. All that the
keyword override does is specify a method that the method it is in will
override. As we are in method a1 we want this method to override method a2 in
the same class.
In the second override we
want this method a1 to override method a3 in class yyy assembly vijay. In the
programming world we have the concept of a virtual function which specifies
that there is someone somewhere who will override me. The job of the override
keyword is to supply the actual function.
The concept of Polymorphisms
is implemented using the override keyword. The longer of the override does the
same thing but as it is in a type, we have to supply both the overridden
function and the function that will override it.
We will first have to
uncomment the method DisplayOverrideMethod that is passed the method row number
as a parameter. Each time we use a override keyword a row gets added to the
MethodImpStruct table. The first field if this table is the codeddef field that
is a coded index that can store one of two tables, MethodDef or MethodRef.
We use the dual methods
GetMethodDefTable and GetMethodDefValue to decipher the coded index. This field
simply tells us the name of the method that we want to override. The next field
codedbody is a similar coded index but it tells us the method name that will
override and in our case it has to a MethodDef as we are writing the override
keyword in a actual method.
Thus deciphering the
codedbody is of really use to us as it will give us our method row number. We now need to know the type that contains
the override keyword as it is normally stored in a type. We forget to pass the
type this method belongs to even though we have it in the DisplayAllMethods
function.
We use the GetTypeForMethod
to return the type a method belongs to given the method row number. All that we do in this method is scan all
the types that we have present excepting the last. We then figure out the first
and the last method row number that this type owns and hence we subtract by
one. We then check if our method falls within this range and then return the type if successful.
Finally if no match is
found, we return the last type number. We complicated your life by bringing
this function as we always have the method type available. We first check three
things before we do anything. First the type of the method is in should match
the classindex field that stores the type.
Then the codedbodytablename
must be MethodDef and finally the methodrow parameter must match the
codedbodyindex index field. If these three conditions match we are not yet home
free as we could have either a MethodDef or a MethodDef method name after the
override keyword.
Lets take the first override
where we specify the name of a method created by us or where the
codeddeftablename variable has a value of MethodDef the second if statement. We
first write out the spaces and once again get the type for this method. This is
unnecessary but the program works.
We then need the name of the
type and as it can be nested, we take our good old method GetNestedTypeAsString
to figure the out all the nested types if any. Then we figure the namespace by
looking at the nspace field of the TypeDef array and joining then if the
namespace is not null.
Must have written this code
at least a zillion times by now. Then we finally add the name of the type minus
the nested types. We then write out the override keyword with the type number
in comments and finally the method row number in comments. The interesting part
is when we are overriding a method defined somewhere else.
The second override
activates the first if statement where the value of variable codeddeftablename
is MethodRef as we are overriding a method in the assembly vijay that has a
class yyy and the method name is a3.We as before write out the initial spaces and
the override keyword.
We now use a method
GetTypeRefFromMethodRef that gives us a type index from the TypeRef table as
the variable codeddefindex gives us a row number in the MethodRef table and not
the MethodDef table as before.
Once we get this TypeRef row
number, all that we do is simply display the corresponding member of the array
typerefnames that store the full form from the TypeRef table. I hope you now
appreciate all the trouble we took in populating this typerefnames array.
Remember within the square brackets you can also have a module name etc.
Once we have the TypeRef
name, we also display the name of the method which is present in the MemberRef
array and also the row number from the TypeRef and MemberRef tables. Thus all
that we need to understand is how the method GetTypeRefFromMethodRef does its
job. This method is pretty simple as all that we do is pick up the class field
from the MemberRef array.
This field is a coded index
as the class could reside in different places. We had spend a lot of time of
this earlier. The table name is really not necessary and just there for
debugging purposes and to tell you what are the possible places a type can be.
We right shift by 3 to get the TypeRef index that we simply return.
We will come back to the
more complex form of override a little later and then deal with all the
different table types.
Program39.csc
public string
DisplayParamsForMethod (int methodrow)
{
if ( ParamStruct == null)
return "";
string returnstring =
"";
if (ConstantsStruct == null)
return "";
int start=0, end=0;
start =
MethodStruct[methodrow].param;
if ( methodrow ==
(MethodStruct.Length - 1))
end =
ParamStruct.Length ;
else
end =
MethodStruct[methodrow+1].param ;
for ( int ii = 1 ; ii <
ConstantsStruct.Length ; ii++)
{
string consttablename =
GetHasConstTable(ConstantsStruct[ii].parent);
int constindex =
GetHasConstValue(ConstantsStruct[ii].parent);
if ( consttablename ==
"ParamDef" )
{
if ( constindex >= start
&& constindex < end && start != end)
{
int seq = ParamStruct[constindex].sequence;
returnstring = returnstring
+ CreateSpaces(spacesforrest+ spacesfornested+2) + ".param [" +
seq.ToString() + "]";
returnstring = returnstring
+ "/*08" + constindex.ToString("X6") + "*/ = " ;
int val1 =
BitConverter.ToInt32(blob , ConstantsStruct[ii].value+1);
if (
ConstantsStruct[ii].dtype == 2 )
{
bool val =
BitConverter.ToBoolean(blob , ConstantsStruct[ii].value+1);
if ( val )
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(true)" + "\r\n";
else
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(false)"+ "\r\n";
}
if (
ConstantsStruct[ii].dtype == 0x03 )
{
int val =
blob[ConstantsStruct[ii].value+1];
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0x" + val.ToString("X4") + ")" +
"\r\n";
}
if (
ConstantsStruct[ii].dtype == 0x04 ||
ConstantsStruct[ii].dtype == 0x05)
{
int val =
blob[ConstantsStruct[ii].value+1];
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0x" + val.ToString("X2") + ")" +
"\r\n";
}
if (
ConstantsStruct[ii].dtype == 0x06 || ConstantsStruct[ii].dtype == 0x07 )
{
int val =
BitConverter.ToInt16(blob , ConstantsStruct[ii].value+1);
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0x" + val.ToString("X4") + ")" + "\r\n";
}
if (
ConstantsStruct[ii].dtype == 0x08 || ConstantsStruct[ii].dtype == 0x09 )
{
int val =
BitConverter.ToInt32(blob , ConstantsStruct[ii].value+1);
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0x" + val.ToString("X8") + ")" +
"\r\n";
}
if (
ConstantsStruct[ii].dtype == 0x0a || ConstantsStruct[ii].dtype == 0x0b )
{
long val =
BitConverter.ToInt64(blob , ConstantsStruct[ii].value+1);
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0x" + val.ToString("X") + ")" +
"\r\n";
}
if (
ConstantsStruct[ii].dtype == 0xc ) //float32
{
double val =
BitConverter.ToDouble(blob , ConstantsStruct[ii].value+1);
if ( val ==
0.10000000000000001)
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0.10000000000000001)" + "\r\n";
else
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(" + val.ToString() + "."
+ ")" + "\r\n";
}
if (
ConstantsStruct[ii].dtype == 0xd )
{
double val =
BitConverter.ToDouble(blob , ConstantsStruct[ii].value+1);
if ( val ==
0.10000000000000001)
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(0.10000000000000001)" + "\r\n";
else
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(" + val.ToString() + "."
+ ")" + "\r\n";
}
if ( ConstantsStruct[ii].dtype
== 0xe )
{
int len =
blob[ConstantsStruct[ii].value] ;
if ( len >= 1)
{
returnstring = returnstring
+ "\"";
for ( int jj = 1 ; jj <
len ; jj++)
{
if (
blob[ConstantsStruct[ii].value+jj] != 0)
returnstring = returnstring
+ (char)blob[ConstantsStruct[ii].value+jj];
}
returnstring = returnstring
+ "\"\r\n" ;
}
else
returnstring = returnstring
+ "\"\"" +
"\r\n";
}
if (
ConstantsStruct[ii].dtype == 0x12 )
{
returnstring = returnstring
+ "nullref" +
"\r\n";
}
}
}
}
return returnstring;
}
public int
GetHasConstValue(int constcodedindex)
{
return constcodedindex
>> 2;
}
public string
GetHasConstTable(int constcodedindex)
{
string returnstring =
"";
int tag = constcodedindex
& 0x03;
if ( tag == 0)
returnstring = returnstring
+ "FieldDef";
if ( tag == 1)
returnstring = returnstring
+ "ParamDef";
if ( tag == 2)
returnstring = returnstring
+ "Property";
return returnstring;
}
}
e.il
.class zzz
{
.method void a1(int32 i )
native unmanaged
{
.param[1] = bool(true)
.param[0] = int32(1)
.param[2] = int32(13)
}
.method void a2() native
unmanaged
{
.param[0] = int32(11)
.param[1] = int32(12)
}
.method void a3(int32 ,
int32 , int32, int32 , int32 , int32, int32, int32, int32) native unmanaged
{
.param[1] = char(1)
.param[2] = int8(2)
.param[3] = int16(4)
.param[4] = int32(5)
.param[5] = int64(6)
//.param[6] = float32(6.2)
//.param[7] = float64(7.2)
.param[8] = "ABC"
.param[9] = nullref
}
}
Output
.class /*02000002*/ private
auto ansi zzz
extends [mscorlib/* 23000001 */]System.Object/* 01000001 */
{
.method /*06000001*/ privatescope instance void
a1$PST06000001(int32 i) native unmanaged
// SIG: 20 01 01 08
{
.param [1]/*08000001*/
= bool(true)
// Unmanaged TargetRVA
= 0x2050
} // end of method zzz::a1
.method /*06000002*/ privatescope instance void
a2$PST06000002() native unmanaged
// SIG: 20 00 01
{
// Unmanaged TargetRVA
= 0x2051
} // end of method zzz::a2
.method /*06000003*/ privatescope instance void
a3$PST06000003(int32 A_0,
int32 A_1,
int32 A_2,
int32 A_3,
int32 A_4,
int32 A_5,
int32 A_6,
int32 A_7,
int32 A_8) native unmanaged
// SIG: 20 09 01 08 08 08 08 08 08 08 08 08
{
.param [1]/*08000002*/
= char(0x0001)
.param [2]/*08000003*/
= int8(0x02)
.param [3]/*08000004*/
= int16(0x0004)
.param [4]/*08000005*/
= int32(0x00000005)
.param [5]/*08000006*/
= int64(0x6)
.param [8]/*08000009*/
= "ABC"
.param [9]/*0800000A*/
= nullref
// Unmanaged TargetRVA
= 0x2052
} // end of method zzz::a3
} // end of class zzz
In this example lets take a
close look at the param directive. This directive takes a number in square
brackets that represents the parameter position. For the first time we count
from 1 and not zero. In the method a1 the param directive that has a value 0
and 2 does not show up as we have only one parameter.
Thus if we Write any
erroneous param directives, ilasm discards them. Ditto for method a2, both get
discarded. Wonder why such error checks are not done always, would make our
life much easier. The param directive is used to give a param a value and hence
the equal to sign and a data type along
with a value compatible with this data type.
All the params constant
values are stored in the Constants table and our job know is to display all the
param directives, they can be more per function as method a3 demonstrates. A
value of zero stands for or represents the return value.
The CLI makes sure that
every parameter gets a value but in
languages like C++ if a parameter does not have a value, it can get a default
one. This is where we have the param directive. Thus if the parameter does not
have a value, the tool will use the param directive for a value as the user did
not supply one.
Thus the CLI has a hands off
approach to the param directive and the compilers can do whatever they like
with the param directive. We now uncomment the method DisplayParamsForMethod
and pass it the method row number. This method is slightly different from the
others as it returns a string with a enter at the end.
Thus we use the Write and
not the WriteLine method to display it. The MethodStruct array tells us the
first and last parameter in the Params table that this method owns. As always
we store these values in the start and end variables respectively. Now the
Constants table stores constants that appear anywhere in our code.
Thus the parent field of the
Constants table tells us using a coded index which table owns this constant. We
have up to three possible owners, a field, property or a param. As we are
interested in only the param directive the first if statement checks for the
parent name to be ParamDef.
The variable constindex will
now stand for a row number in the Param table and if it lies between start and
end, it is a param directive for this method. If start and end are the same,
then the method owns no parameters.
Fortunately for us the
sequence number is also the parameter number and hence we write out the param
directive as well as the sequence number in brackets and the Param table row
number in comments. Then we place the equal to sign to be followed by the data
type. The field dtype gives us the type of the data type.
The Constants table also has
a field value that is an index into the blob heap which always begins by a
length field. We have always assumed that no one in his right mind will create
a constant larger than 127 bytes. Thus if the type is 2, then the data type
follows that of method GetType i.e. a bool.
We pick up the Boolean value
stored one more from the blob array due to the length and display the words
true or false. This is shown in method a1. Then moving to method a3, we start
with the char data type whose dtype is 3. A value of dtype 3 is that of a byte
and hence we pick up the value directly.
We also have to chose the
format type x2 or x4 accordingly. The value 4 and 5 are that of int8 which we
also read one byte of the blob array. 6 and 7 are for int16 and 8 and 9 are for
in32. The unsigned data type gives an error ad hence the clubbing. Finally 0xa
and 0xb are for int64.
In each case we use the
relevant BitConverter method to read off the blob array. The problem with numbers with decimal places
is how do we display them. The original ildasm has been written in C and hence
we will work with decimal numbers later. All that we say is that dtype values
0xc and 0xd are for such numbers.
A value of 0xe stands for a
string and hence we look at the first byte the length byte. If it is larger
than 1 we have a string that is not null or empty and we now display the string
in ascii ignoring the bytes that are zeroes. We also add a double inverted
comma at the beginning and at the end and use no data type, just the string.
If the string is a null
string, we simply display two double inverted commas with nothing in between.
Finally a value of 0x12 means the words nullref. There are some special cases
like 0, 1 , 0xf , 0x11 whose values are illegal and hence we have not accounted
for them.
Also if the string does not
contain displayable characters, we use the bytearray data type instead. This
would look like
.param[1] = bytearray (0C 00
00 00 00 00 00 00 )
This is how the param
directive is implemented and we have not seen the last of this directive.
Prgram40.csc
Public void
DisplayAllMethods()
{
Console.WriteLine(methodstring);
continue;
}
string vtentrystring =
GetVtentryString(methodindex);
methodstring =
vtentrystring;
methodstring = methodstring
+ CreateSpaces(spacesforrest + 2 + spacesfornested) + "// Method begins at RVA 0x" +
MethodStruct[methodindex].rva.ToString("x4") + "\r\n";
methodstring = methodstring
+ CreateSpaces(spacesforrest + 2 + spacesfornested) + "// Code size 0
(0x0)" + "\r\n";
methodstring = methodstring
+ CreateSpaces(spacesforrest+ spacesfornested) + "} // end of method
" + NameReserved(GetString(TypeDefStruct[typeindex].name)) + "::"
+ NameReserved(GetString(MethodStruct[methodindex].name)) + "\r\n";
Console.WriteLine(methodstring);
}
}
int [] methodvtentryarray ;
public void
ReadandDisplayVTableFixup()
{
//Console.WriteLine("........{0}"
, MethodStruct.Length );
if ( MethodStruct == null)
return;
methodvtentryarray = new int[MethodStruct.Length + 1];
for ( int jj = 1 ; jj <=
MethodStruct.Length ; jj++)
methodvtentryarray[jj] = 0;
if ( vtablerva != 0)
{
long save ;
long position =
ConvertRVA(vtablerva) ;
if ( position == -1)
return;
mfilestream.Position =
position;
Console.WriteLine("//
VTableFixup Directory:");
int count1 = vtablesize/8;
vtfixuparray = new
string[count1];
for ( int ii = 0 ; ii <
count1 ; ii++)
{
vtfixuparray[ii] =
".vtfixup ";
int fixuprva =
mbinaryreader.ReadInt32();
Console.WriteLine("// IMAGE_COR_VTABLEFIXUP[{0}]:" , ii);
Console.WriteLine("// RVA: {0}",fixuprva.ToString("x8"));
short count =
mbinaryreader.ReadInt16();
Console.WriteLine("// Count: {0}", count.ToString("x4"));
short type =
mbinaryreader.ReadInt16();
Console.WriteLine("// Type: {0}", type.ToString("x4"));
save = mfilestream.Position;
mfilestream.Position =
ConvertRVA(fixuprva) ;
int i1 ;
long [] val = new
long[count] ;
for ( i1 = 0 ; i1 < count
; i1++)
{
if ( (type&0x01) == 0x01)
val[i1] =
mbinaryreader.ReadInt32();
if ( (type&0x02) == 0x02)
val[i1] =
mbinaryreader.ReadInt64();
if ( (type&0x01) == 0x01 )
Console.WriteLine("// [{0}] ({1})",i1.ToString("x4") , val[i1].ToString("X8"));
if ( (type&0x02) == 0x02)
Console.WriteLine("// [{0}] (
{1})",i1.ToString("x4") ,
(val[i1]&0xffffffff).ToString("X"));
}
mfilestream.Position = save;
vtfixuparray[ii] =
vtfixuparray[ii] + "[" + (i1).ToString("X") + "]
";
if ( (type&0x01) == 0x01)
vtfixuparray[ii] =
vtfixuparray[ii] + "int32 ";
if ( (type&0x02) == 0x02)
vtfixuparray[ii] =
vtfixuparray[ii] + "int64 ";
if ( (type&0x04) == 0x04)
vtfixuparray[ii] =
vtfixuparray[ii] + "fromunmanaged ";
vtfixuparray[ii] =
vtfixuparray[ii] + "at D_" + fixuprva.ToString("X8");
vtfixuparray[ii] =
vtfixuparray[ii] + " //";
for ( i1 = 0 ; i1 < count
; i1++)
{
if ( (type&0x01) == 0x01)
vtfixuparray[ii] =
vtfixuparray[ii] + " " +
val[i1].ToString("X8");
if ( (type&0x02) == 0x02)
vtfixuparray[ii] =
vtfixuparray[ii] + " " + val[i1].ToString("X16");
}
long index = 0;
if ( val != null)
index = val[0] &
0xffffff;
methodvtentryarray[ii+1] =
(int)index;
}
Console.WriteLine();
}
}
public string
GetVtentryString (int methodrow)
{
string returnstring
="";
int kk =
GetVtentryInteger(methodrow);
if ( kk >= 1)
returnstring =
CreateSpaces(spacesforrest+2) + ".vtentry " + kk.ToString() + "
: 1" + "\r\n" ;
return returnstring;
}
public int GetVtentryInteger
(int methodrow)
{
int ii ;
for ( ii = 1 ; ii <
methodvtentryarray.Length-1 ; ii++)
{
if ( methodvtentryarray[ii]
== methodrow)
break;
}
if ( methodvtentryarray[ii]
== 0)
return 0;
else
return ii;
}
}
e.il
.vtfixup [1] int32
fromunmanaged at D_0000000
.vtfixup [1] int32 fromunmanaged
at D_0000040
.vtfixup [1] int32
fromunmanaged at D_0000010
.vtfixup [1] int32
fromunmanaged at D_0000020
.data D_0000010 = bytearray
( 03 00 00 06)
.data D_0000020 = bytearray
( 01 00 00 06)
.data D_0000000 = bytearray
( 02 00 00 06)
.data D_0000040 = bytearray
( 04 00 00 06)
.class zzz
{
.method void a1()
{
}
.method void a2()
{
}
.method void a3()
{
}
.method void a4()
{
}
.method void a5()
{
}
}
Output
.method /*06000001*/ privatescope instance void
a1$PST06000001() cil managed
// SIG: 20 00 01
{
.vtentry 4 : 1
} // end of method zzz::a1
.method /*06000002*/ privatescope instance void
a2$PST06000002() cil managed
// SIG: 20 00 01
{
.vtentry 1 : 1
} // end of method zzz::a2
.method /*06000003*/ privatescope instance void
a3$PST06000003() cil managed
// SIG: 20 00 01
{
.vtentry 3 : 1
} // end of method zzz::a3
.method /*06000004*/ privatescope instance void
a4$PST06000004() cil managed
// SIG: 20 00 01
{
.vtentry 2 : 1
} // end of method zzz::a4
.method /*06000005*/ privatescope instance void
a5$PST06000005() cil managed
// SIG: 20 00 01
{
} // end of method zzz::a5
For one last time we come
back to the function ReadandDisplayVTableFixup. We have displayed the entire
function again as we have added some more lines to it. Whenever we have the
directive vtfixup, we also have in a method the directive vtentry.
This program does two
things, it adds the vtentry directive and also ends a cil managed method. As of
now we will have no code in our methods and a little later we will only focus
on il code. Lets gets everything else out of the way.
In our il code we have four
of each, four vtfixup directives, four data directives and four methods. In the
output we have 4 vtentry directives. In the il file as the first data directive
is at D_0000000, it refers to method 2 and the second data directive at
D_0000040 to method 4. There is no semblance of order either in the data
directives or the vtfixup’s.
Thus in the output, method
a1 gets a vtentry directive with 4:1 , the second method a2 vtentry 1:1 and the
third 3:1. The first change we have made is add a instance array of ints called
methodvtentryarray and like all arrays in our program is one larger. Then we
set every member to zero in this array.
Then at the very end we
simply add the method row number to the array. If you see the e.il file again,
the lot of vtfixup’s are all not ordered, the data directives are also not
ordered. The system figures them as the order of the data directives are not
important as is the row number stored in them. The array thus contains row
numbers 2,4,3 and 1.
This is as the first vtfixup
directive has the data directive as D_00 which has row number 2. the second
vtfixup is D_40 which is row 4 and the third is D_10 that is 3. Thus the
vtfixup’s decide the method row numbers.
This simply means that the method 2 will have a vtentry of 1 as this is
the first member of the array, the method 4 a vtentry of 2 as it is the second
and the method 3 will have a slot value of 3 and finally method 1 slot 4.
We now call the
GetVtentryString method that returns the vtentry directive as a string. Here we
call another method GetVtentryInteger that simply returns a number and all that
we do is add the directive vtentry with this number and a colon with a 1. This
we do only of we are returned a non zero. For method a5 we will be returned a
zero as in the methodvtentryarray array there is no row number 5 as one of its
values.
Moving to the
GetVtentryInteger method, all that we do here is scan the methodvtentryarray
till the Length of the array minus 1 and if we ever meet a match where the
methodrow variable has the value of any of our methodvtentryarray array members we simply break. Now we ask
why did the break happen.
If it happened because the
loop got over or because we met a match. We have two ways of quitting the
loop. If the value of the
methodvtentryarray array was zero then
we return zero as no match was found. We could have also checked whether the
value of ii is equal to the length of the array minus 1.
Else we return the ii or the
index member of the methodvtentryarray array that has the matching row number.
This is the slot number we use in the vtentry directive.
Pogram41.csc
public void DisplayOneType
(int typedefindex)
{
DisplayOneTypeDefStart(typedefindex);
DisplaySizeAndPack(typedefindex);
DisplayNestedTypes(typedefindex);
DisplayOverride(typedefindex);
DisplayAllMethods(typedefindex);
DisplayOneTypeDefEnd(typedefindex
);
}
public void DisplayOverride
(int typedefindex)
{
if ( MethodImpStruct ==
null )
return;
for ( int ii = 1 ; ii <
MethodImpStruct.Length ; ii++)
{
if ( typedefindex ==
MethodImpStruct[ii].classindex )
{
string codedeftablename = GetMethodDefTable(MethodImpStruct[ii].codeddef
);
int codedefindex =
GetMethodDefValue(MethodImpStruct[ii].codeddef );
string codebodytablename =
GetMethodDefTable(MethodImpStruct[ii].codedbody);
int codebodyindex =
GetMethodDefValue(MethodImpStruct[ii].codedbody );
//Console.WriteLine("...codedeftablename={0}
codebodytablename={1} codedefindex={2} codebodyindex={3}
",codedeftablename ,codebodytablename,codedefindex,codebodyindex );
if ( codebodytablename==
"MethodDef")
continue;
string finals =
"";
if ( codedeftablename ==
"MethodDef" )
{
finals =
CreateSpaces(spacesforrest + spacesfornested);
finals = finals +
".override " ;
int typedefmethodindex =
GetTypeForMethod(codedefindex);
finals = finals +
NameReserved(GetString(TypeDefStruct[typedefmethodindex ].name)) ;
finals = finals + "/*
02" + typedefmethodindex.ToString("X6") + " */::";
finals = finals +
NameReserved(GetString(MethodStruct[codedefindex].name)) + " with";
}
else
{
finals =
CreateSpaces(spacesforrest + spacesfornested);
finals = finals +
".override " ;
int typerefindex =
GetTypeRefFromMethodRef(codedefindex);
finals = finals +
typerefnames[typerefindex] + "::";
finals = finals +
NameReserved(GetString(MemberRefStruct[codedefindex].name)) + "
with";
}
int typerefindex1 =
GetTypeRefFromMethodRef(codebodyindex);
finals = finals + "
" + methodreftypearray[codebodyindex];
finals = finals +
methodrefreturnarray[codebodyindex] + " " ;
finals = finals +
DisplayTypeRefExtends(typerefindex1) + "::";
finals = finals +
NameReserved(GetString(MemberRefStruct[codebodyindex].name)) + "(" ;
int len = finals.Length;
string paramstring =
ParamOnMultipleLines(methodrefparamarray1[codebodyindex], len) ;
finals = finals +
paramstring + ") /* 0A" + codebodyindex.ToString("X6") +
" */";
Console.WriteLine(finals);
}
}
}
string []
methodrefparamarray1 ;
string []
methodrefreturnarray ;
string []
methodreftypearray;
public void CreateSignatures
()
{
if (MethodStruct != null)
{
methoddefparamarray = new
string[MethodStruct.Length];
methoddefparamarray1 = new
string[MethodStruct.Length];
methoddefreturnarray = new
string[MethodStruct.Length];
methoddeftypearray = new string[MethodStruct.Length];
methoddefparamcount = new
int[MethodStruct.Length];
for ( int l = 1 ; l <
MethodStruct.Length ; l++)
{
CreateSignatureForEachType(1
, MethodStruct[l].signature, l);
}
}
//MethodRefTable
if ( MemberRefStruct != null
)
{
methodreftypearray = new
string[MemberRefStruct.Length];
methodrefreturnarray = new
string[MemberRefStruct.Length];
methodrefparamarray1 = new string[MemberRefStruct.Length];
for ( int l = 1 ; l <
MemberRefStruct.Length ; l++)
{
CreateSignatureForEachType(2
, MemberRefStruct[l].sig, l);
}
}
}
public void
CreateSignatureForEachType (byte type , int index , int row)
{
//Console.WriteLine(".......type={0}
row={1} index={2} blob.Length={3} {4}" , type ,
row.ToString("X") , (ushort)index , blob.Length , (uint)index);
int uncompressedbyte , count
, howmanybytes;
howmanybytes =
CorSigUncompressData(blob , index , out uncompressedbyte);
count = uncompressedbyte;
byte [] blob1 = new
byte[count];
Array.Copy(blob , index +
howmanybytes , blob1 , 0 , count);
if ( type == 1)
CreateMethodDefSignature(blob1
, row);
if ( type == 2)
CreateMethodRefSignature(blob1
, row);
}
public void
CreateMethodRefSignature (byte [] blobarray , int row)
{
//Console.WriteLine("CreateMethodRefSignature
Name={0} row={1} b.Length={2}" , GetString(MemberRefStruct[row].name),
row, blobarray.Length);
int aa = -1;
if ( row == aa)
{
Console.WriteLine("row
number {0} {1}" , row , GetString(MemberRefStruct[row].name));
for ( int l = 0 ; l <
blobarray.Length ; l++)
Console.Write("{0}
" , blobarray[l].ToString("X"));
Console.WriteLine();
}
int
howmanybytes,uncompressedbyte , index;
index = 0;
howmanybytes=
CorSigUncompressData(blobarray , index , out uncompressedbyte);
if ( uncompressedbyte ==
0x06 )
{
index = index +
howmanybytes;
methodrefreturnarray[row] =
GetElementType( index , blobarray , out howmanybytes , 0 , "");
return;
}
methodreftypearray[row] =
DecodeFirstByteofMethodSignature(uncompressedbyte , row);;
index = index +
howmanybytes;
howmanybytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
count = uncompressedbyte;
index = index +
howmanybytes;
string typestring =
GetElementType( index , blobarray , out howmanybytes, 0 , "");
methodrefreturnarray[row] =
typestring;
index = index +
howmanybytes;
string returnstring =
"" ;
methodrefparamarray1[row] =
"";
for ( int l = 1 ; l <=
count ; l++)
{
string sentinel =
"";
if ( blobarray[index] ==
0x41)
{
sentinel = "..." +
"," ;
index = index + 1;
}
typestring = sentinel + GetElementType( index ,
blobarray , out howmanybytes , 0 , "") ;
index = index +
howmanybytes;
returnstring = returnstring
+ typestring;
if ( l != count)
returnstring =
returnstring + "," ;
}
if ( index <
blobarray.Length && blobarray[index] == 0x41)
returnstring = returnstring
+ ",..." ;
methodrefparamarray1[row] =
returnstring;
}
e.il
.assembly extern vijay
{
}
.class interface iii
{
.method public void aa() cil
managed
{
}
}
.namespace mmm
{
.class ccc
{
.method public void abc()
{
}
.method public void pqr()
{
}
.override iii::aa with
instance void ccc::abc()
.override zzz::pqr with
instance void ccc::abc()
.override [vijay]aaa::pqr
with instance void ccc::abc()
.override [vijay]aaa::pqr
with instance void [vijay]aaa::iii(int32,...,int32)
}
}
Output
.namespace mmm
{
.class /*02000003*/ private auto ansi ccc
extends [mscorlib/* 23000002 */]System.Object/* 01000001
*/
{
.override iii/* 02000002 */::aa with instance void ccc/*
01000002 */::abc() /* 0A000001 */
.override zzz/* 01000003 */::pqr with instance void ccc/*
01000002 */::abc() /* 0A000001 */
.override [vijay/* 23000001 */]aaa/* 01000004 */::pqr with
instance void ccc/* 01000002 */::abc() /* 0A000001 */
.override [vijay/* 23000001 */]aaa/* 01000004 */::pqr with
instance void [vijay/* 23000001 */]aaa/* 01000004 */::iii(int32,
...,
int32)
/* 0A000004 */
Earlier we worked with the
override directive that we placed in a method and now we place them in a type.
The first thing we need to do is add a line in the DisplayOneType method called
DisplayOverride that will display all the overrides for one type.
The placement is important
as all the override directives for a type are placed before displaying all the
methods. The same basic rules that we mentioned earlier apply here also, which
is that a record gets added to the MethodImpl table.
We as before check whether
the classindex field carries the type that we are currently displaying. We need
to understand once again that when we use the override directive, we first
specify the method we want to override and then use the with clause to specify
the method we want to override it with. As we are referring to a method in the
with clause, this will always be a reference to the MethodRef table.
In the earlier case there
was no with clause that we sued with the override. Thus the codedbody field or
the variable codebodytablename will always represent the MemberRef table as
earlier it was always Method table.
In this case therefore the
variable codedefindex taken from the coded index codebodyindex will always be a
row number in the MemberRef table. Thus we first check if the codebodytablename
table has the value MethodDef and if so we loop back as the
DisplayOverrideMethod method will handle it as it is a override directive
without the with clause and hence part of a method and not type.
Now the first method name
that is represented by the codeddef field may be either a MethodDef or a
MemberRef. Lets move to the il file first to understand which table value this
field has. The first override directive specifies the name of a method aa that
is an interface iii.
Whenever this case happens
we have a table value of MethodDef. In the second case, even though we specify
a method pqr in the same class zzz this is a MethodRef and not a MethodDef as
we would like to understand. The last two are MemberRef’s as they are
represents methods in a Assembly.
Thus a MethodDef only comes
in when we are dealing with methods in a interface and not methods that we have
created ourselves. This is important as if the variable codedeftablename is
MethodDef, we need to pick up the type name from the Typedef table and the
method name from the Method table.
The codedefindex is the row
number in the Method table and as we need the type it belongs to we use the
GetTypeForMethod method to give us the type.
Once we have the type we
then simply display the type name without the namespace for some reason and
then the type row number in comments and the name of the method from the Method
table and finally the clause with. If the else gets called then we know that we
have a MemberRef and hence we use the method GetTypeRefFromMethodRef that gives
us the row number of this method in the TypeRef table.
We have done this method
earlier in Program38. We use the typerefnames array to display the Type name
and then the codedefindex variable to display the name of the method from the
MemberRef table. Now all that is left is the method name and type after the
with clause and this is a reference to the MemberRef table.
We as before get the TypeRef
table index using the codebodyindex variable. We then use the
methodreftypearray that gives us the type i.e. instance or static for the
MemberRef method. This is the same concept we used for the Method table where
we had three arrays store the type, the return value and the parameters.
The only difference is the
words def are replaced with ref. We will show you in a short while how similar
are the MemberRef and Method tables. We then display the return value stored in
the methodrefreturnarray array and then the type name from the TypeRef table
using a method we used before DisplayTypeRefExtends.
We then add a colon and
finally add the name of the method from the MemberRef table. Finally the array
methodrefparamarray1 gives us the parameters to the method and we use the
ParamOnMultipleLines method to display then on different lines. Most of the
code used here fortunately has been done before.
Lets now look at how we
populate the three ref arrays. As always we define three arrays of strings as
instance variables. Then we in the CreateSignatures method we initialize these
three arrays to the length of the MemberRef table.
Then we call the
CreateSignatureForEachType function once for each row of the MemberRef table
but with the first parameter as 2 and not 1 as for the method table. We also
pass the sig offset in the blob heap. In the CreateSignatureForEachType we do
nothing different that what we did earlier and call the
CreateMethodRefSignature as the value of the type variable is 2 now.
In this method
CreateMethodRefSignature we first check for the value of the first byte after
the length to be 06 or not. If it is then we do not have a full method
signature with parameters following but only a data type that we set the
methodrefreturnarray to. This is not a reference to a method with parameters
but only a return type.
The number 6 is actually the
type for fields that we will come to later. Like always we have the type byte
that we store in the methodreftypearray array and then the count of parameters
followed by the return type that we store in the methodrefreturnarray array.
Then we loop in a for
statement to pick up the parameters and as we are in a MemberRef we have no
parameter names. The only addition is the use of three dots or a sentinel ….
This as explained before is used for parameters with a variable number of
arguments. The type for a sentinel is 0x41 and if we meet one we simply add
three dots and a comma.
We finally store the
parameter list in the array methodrefparamarray1. No major changes in what we
have done earlier. The last override uses a sentinel. In case you did not
notice both the array typerefnames and DisplayTypeRefExtends return the same
result and could have been used in place of each other.
There is the lat if
statement that we have not explained in the CreateMethodRefSignature. The
problem is that when we encounter a sentinel, this is not counted as a valid
parameter. The count member does not acknowledge its presence. This is why when
we meet a sentinel, we simply increase the index variable by 1.
The problem is that if the
last parameter is the sentinel, it does not get added to the return string
variable at all. Thus we have to check when we exit the for, are we on the
sentinel as well as we are not crossing the bounds of the array. A simple
MemberRef with the signature bytes will clarify matters for us.
20 2 1 8 41 8
.override [vijay]aaa::pqr
with instance void [vijay]aaa::iii(int32,...,int32)
20 1 1 8 41
.override [vijay]aaa::abc
with instance void [vijay]aaa::iii(int32,...)
In the case of the first
override, the iii method has two parameters as the second byte of the signature
tells us. The sentinel byte 0x41 in the middle of the two int32’s does get
counted. In the last override, the sentinel byte is at the very end and the
count byte is only 1.
Program42.csc
public string
DisplayFromConstantsTable(int start , int end ,string tablename )
{
string returnstring =
"";
for ( int ii = 1 ; ii <
ConstantsStruct.Length ; ii++)
{
string consttablename =
GetHasConstTable(ConstantsStruct[ii].parent);
int constindex =
GetHasConstValue(ConstantsStruct[ii].parent);
if ( consttablename ==
tablename )
{
if ( (constindex >= start
&& constindex < end && start != end && tablename ==
"ParamDef" ) || (tablename == "Property" && start
== constindex ) )
{
int seq =
ParamStruct[constindex].sequence;
if ( tablename ==
"ParamDef" )
{
returnstring = returnstring
+ CreateSpaces(spacesforrest+ spacesfornested+2) + ".param [" +
seq.ToString() + "]";
returnstring = returnstring
+ "/*08" + constindex.ToString("X6") + "*/ = " ;
}
else
returnstring = " =
";
int val1 =
BitConverter.ToInt32(blob , ConstantsStruct[ii].value+1);
if (
ConstantsStruct[ii].dtype == 2 )
{
bool val =
BitConverter.ToBoolean(blob , ConstantsStruct[ii].value+1);
if ( val )
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(true)" + "\r\n";
else
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(false)"+ "\r\n";
}
}
}
}
return returnstring;
}
public string
DisplayParamsForMethod (int methodrow)
{
if ( ParamStruct == null)
return "";
string returnstring =
"";
if (ConstantsStruct == null)
return "";
int start=0, end=0;
start =
MethodStruct[methodrow].param;
if ( methodrow ==
(MethodStruct.Length - 1))
end =
ParamStruct.Length ;
else
end =
MethodStruct[methodrow+1].param;
returnstring =
DisplayFromConstantsTable(start , end ,"ParamDef");
return returnstring;
}
public void DisplayOneType
(int typedefindex)
{
DisplayOneTypeDefStart(typedefindex);
DisplaySizeAndPack(typedefindex);
DisplayAllSecurity( 0 ,
typedefindex);
DisplayNestedTypes(typedefindex);
DisplayOverride(typedefindex);
DisplayAllMethods(typedefindex);
DisplayAllProperties
(typedefindex);
DisplayOneTypeDefEnd(typedefindex
);
}
string [] propertytypearray;
string []
propertyreturnarray ;
string [] propertyparmarray;
public void CreateSignatures
()
{
//MethodTable
if (MethodStruct != null)
{
methoddefreturnarray = new
string[MethodStruct.Length];
methoddefparamarray = new string[MethodStruct.Length];
methoddefparamarray1 = new string[MethodStruct.Length];
methoddeftypearray = new string[MethodStruct.Length];
methoddefparamcount = new
int[MethodStruct.Length];
for ( int l = 1 ; l <
MethodStruct.Length ; l++)
{
CreateSignatureForEachType(1
, MethodStruct[l].signature, l);
}
}
//MethodRefTable
if ( MemberRefStruct != null
)
{
methodreftypearray = new
string[MemberRefStruct.Length];
methodrefreturnarray = new
string[MemberRefStruct.Length];
methodrefparamarray1 = new string[MemberRefStruct.Length];
for ( int l = 1 ; l <
MemberRefStruct.Length ; l++)
{
CreateSignatureForEachType(2
, MemberRefStruct[l].sig, l);
}
}
//propertytable
if ( PropertyStruct != null
)
{
propertyparmarray = new
string[PropertyStruct.Length];
propertyreturnarray = new
string[PropertyStruct.Length];
propertytypearray = new string[PropertyStruct.Length];
for ( int l = 1 ; l <
PropertyStruct.Length ; l++)
{
CreateSignatureForEachType
(7, PropertyStruct[l].type, l);
}
}
}
public void
CreateSignatureForEachType (byte type , int index , int row)
{
int uncompressedbyte , count
, howmanybytes;
howmanybytes =
CorSigUncompressData(blob , index , out uncompressedbyte);
count = uncompressedbyte;
byte [] blob1 = new
byte[count];
Array.Copy(blob , index +
howmanybytes , blob1 , 0 , count);
if ( type == 7)
CreatePropertySignature
(blob1 , row);
if ( type == 1)
CreateMethodDefSignature(blob1
, row);
if ( type == 2)
CreateMethodRefSignature(blob1
, row);
}
public void
CreatePropertySignature (byte [] blobarray , int row)
{
int aa = -1;
if ( row == aa)
{
for ( int l = 0 ; l <
blobarray.Length ; l++)
Console.Write("{0}
" , blobarray[l].ToString("X"));
Console.WriteLine();
}
int howmanybytes ,
uncompressedbyte;
int index = 0;
howmanybytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
index = index +
howmanybytes;
if (
(uncompressedbyte&0x20) == 0x20)
propertytypearray [row] =
"instance ";
howmanybytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
index = index +
howmanybytes;
int count = uncompressedbyte;
string typestring =
"";
string returnstring =
"";
returnstring =
GetElementType( index , blobarray , out howmanybytes , 0 , "") ;
propertyreturnarray [row] =
returnstring;
index = index +
howmanybytes;
returnstring = "";
for ( int l = 1 ; l <= count
; l++)
{
typestring = GetElementType(
index , blobarray , out howmanybytes , 0 , "") ;
returnstring = returnstring
+ typestring;
index = index +
howmanybytes;
if ( l != count)
returnstring = returnstring
+ ",";
}
propertyparmarray [row] =
returnstring;
}
public void
DisplayAllProperties (int typeindex)
{
int ii;
if ( PropertyMapStruct ==
null || PropertyStruct == null )
return;
for ( ii = 1 ; ii <
PropertyMapStruct.Length ; ii++)
{
if ( typeindex ==
(PropertyMapStruct[ii].parent) )
break;
}
if ( ii ==
PropertyMapStruct.Length)
return;
int start =
PropertyMapStruct[ii].propertylist;
int end;
if ( ii+1 ==
PropertyMapStruct.Length )
end = PropertyStruct.Length
- 1;
else
end = PropertyMapStruct[ii + 1].propertylist -1;
for ( int propertyrow =
start ; propertyrow <= end ; propertyrow++)
{
string returnstring =
CreateSpaces(spacesforrest + spacesfornested);
returnstring = returnstring
+ ".property /*17" + propertyrow.ToString("X6") + "*/
";
string name =
NameReserved(GetString(PropertyStruct[propertyrow].name)) ;
int flags =
PropertyStruct[propertyrow].flags;
string propertyattribute =
GetPropertyAttribute (flags );
returnstring = returnstring
+ propertyattribute + propertytypearray[propertyrow] +
propertyreturnarray[propertyrow] ;
if ( returnstring.Length
>= 41 )
returnstring = returnstring
+ "\r\n" + CreateSpaces(spacesforrest + spacesfornested)+
CreateSpaces(7);
returnstring = returnstring
+ " " + name + "(" ;
int len =
returnstring.Length;
int len1 =
returnstring.LastIndexOf("\r\n");
if ( len1 == -1)
len1 = -2;
string dummy =
ParamOnMultipleLines (propertyparmarray[propertyrow], len-len1-2);
returnstring = returnstring
+ dummy + ")";
returnstring = returnstring
+ GetPropertyAttributeDefault (flags, propertyrow);
Console.WriteLine(returnstring);
Console.WriteLine(CreateSpaces(spacesforrest
+ spacesfornested) + "{");
for ( int kk = 1 ; kk <
MethodSemanticsStruct.Length ; kk++)
{
string attributes =
GetMethodSemanticsAttributes
(MethodSemanticsStruct[kk].methodsemanticsattributes);
string table = GetHasSemanticsTable
(MethodSemanticsStruct[kk].association);
int propertyindex =
GetHasSemanticsValue (MethodSemanticsStruct[kk].association);
int methodindex =
MethodSemanticsStruct[kk].methodindex;
string methodattribute =
GetMethodAttribute(MethodStruct[methodindex].flags , methodindex);
string paramstring =
methoddefparamarray1[methodindex];
if ( propertyindex ==
propertyrow && table == "Property" )
{
string dummy1 =
CreateSpaces(spacesforrest + 2 + spacesfornested);
dummy1 = dummy1 + attributes
+ " /*06" + methodindex.ToString("X6") + "*/ " +
methoddeftypearray [methodindex] + methoddefreturnarray[methodindex];
string nestedtypestring = GetNestedTypeAsString (typeindex);
string name1 =
NameReserved(GetString(MethodStruct[methodindex].name)) + "(";
string nspace =
NameReserved(GetString(TypeDefStruct[typeindex].nspace));
if ( nspace != "")
nspace = nspace +
".";
string dummy2 = dummy1 + nestedtypestring + nspace +
NameReserved(GetString(TypeDefStruct[typeindex].name)) + "/* 02" +
typeindex.ToString("X6") +
" */::" + name1;
int dummyint =
dummy2.IndexOf("(");
string dummy3 =
ParamOnMultipleLines(paramstring , dummyint + 1) ;
dummy3 = dummy3 +
")" + " /* 06" + (methodindex).ToString("X6") +
" */";
Console.WriteLine(dummy2 +
dummy3);
}
}
Console.Write(CreateSpaces(spacesforrest
+ spacesfornested));
Console.Write("}
");
Console.WriteLine("//
end of property {0}::{1}" ,
NameReserved(GetString(TypeDefStruct[typeindex].name)) , name);
}
}
public string
GetHasSemanticsTable(int asscoiationbyte)
{
string returnstring =
"";
int tag = asscoiationbyte
& 0x01;
if ( tag == 0 )
returnstring = returnstring
+ "Event";
if ( tag == 1 )
returnstring = returnstring
+ "Property";
return returnstring;
}
public int
GetHasSemanticsValue(int asscoiationbyte)
{
return asscoiationbyte >>
1;
}
public string
GetMethodSemanticsAttributes(short asscoiationbyte)
{
string returnstring =
"";
if ( (asscoiationbyte &
0x01) == 0x01)
returnstring = returnstring
+ ".set";
if ( (asscoiationbyte &
0x02) == 0x02)
returnstring = returnstring
+ ".get";
if ( (asscoiationbyte &
0x04) == 0x04)
returnstring = returnstring
+ ".other";
if ( (asscoiationbyte &
0x08) == 0x08)
returnstring = returnstring
+ "Event Addon";
if ( (asscoiationbyte &
0x10) == 0x10)
returnstring = returnstring
+ "Event Remove";
if ( (asscoiationbyte &
0x20) == 0x20)
returnstring = returnstring
+ "Event Fire";
return returnstring;
}
public string
GetPropertyAttribute(int flags )
{
string returnstring =
"";
if ( (flags&0x200) ==
0x200 )
returnstring = returnstring
+ "specialname ";
if ( (flags&0x400) ==
0x400 )
returnstring = returnstring
+ "rtspecialname ";
return returnstring ;
}
public string
GetPropertyAttributeDefault(int flags, int row)
{
string returnstring =
"";
if ( (flags&0x1000) ==
0x1000)
{
returnstring =
DisplayFromConstantsTable ( row , 0 ,"Property");
returnstring =
returnstring.Remove(returnstring.Length-2,2);
}
return returnstring;
}
}
e.il
.class zzz
{
.method public static int32 a1()
{
}
.method public instance void a2(int32 'value')
{
}
.method public instance void a3()
{
}
.method public static int32 a4()
{
}
.property rtspecialname
specialname instance class zzz aa(int32 , int64 , int8) = int32(10)
{
.get int32 zzz::a1()
.set instance void zzz::a2(int32)
.other instance void zzz::a3()
}
.property int32 bb()
{
.get int32 zzz::a4()
}
}
Output
.property /*17000001*/ specialname instance class zzz/* 02000002
*/
aa(int32,
int64,
int8) = int32(0x0000000A)
{
.get /*06000001*/ int32 zzz/* 02000002 */::a1() /* 06000001 */
.set /*06000002*/ instance void zzz/* 02000002 */::a2(int32) /*
06000002 */
.other /*06000003*/ instance void zzz/* 02000002 */::a3() /*
06000003 */
} // end of property zzz::aa
.property /*17000002*/ int32 bb()
{
.get /*06000004*/ int32 zzz/* 02000002 */::a4() /* 06000004 */
} // end of property zzz::bb
This example will display
all the properties that we have in our type. There is really no concept of a
property in il. In C#, a property looks like, walks like, talks like a variable.
If we have no access to source code, there is no way of knowing whether a
entity is a property or a variable.
The concept of properties is
touted as one of the major features of the C# programming language. When C#
code gets converted to il, the property aa gets converted to two methods get_aa
and set_aa. Whenever we set the value of a property , the set_aa method
replaces the setting of the property in our IL code.
In the same vein when we
access a property value, this gets replaced by a call to the get_aa method that
returns the value of the property. The set_aa method gets called with a
parameter value that contains the new
value of our property. Thus properties in il are nothing but two method calls.
However in IL we have a
property directive which we will now discuss. But before that a small change.
Some programs above we displayed the param keyword that had a value taken from
the Constants table. Thus in the function DisplayParamsForMethod we first
figured out the param numbers that our method owned and then displayed the
value from the constants table.
At that time we forget that
the constants table is also used by two other directives, one the properties
directive and the other the fields directive. Thus we did not want to repeat
the same code to retrieve the constants value from the constants table and thus
broke up the DisplayParamsForMethod into two methods.
In this method we first
figure out the start and end param numbers and then call the
DisplayFromConstantsTable method with the last string parameter being ParamDef
and the first and second parameter being the start and end param numbers owned
by this method.
Then in the
DisplayFromConstantsTable we simply display the value from the constants table.
It is this method that we will call once again a little later. We then move our
focus to the method DisplayOneType where we have added a method
DisplayAllProperties that will display for us all the properties associated
with a type.
But before we come to this
method we need to focus on the CreateSignatures method where we populate the
string arrays associated with properties. As always we initialize the three
string arrays depending upon the length of the Property table. This table has
one row for every property directive present in the il code.
The variable
propertyparmarray will contain the parameters that a property can carry, the
propertyreturnarray the return value, methodreftypearray the type of method
instance or a null if static. We then call the CreateSignatureForEachType with
a value of 7 that stands for a property.
In this method we simply
call the CreatePropertySignature method that populates the above three string
arrays. In this case we call the field that holds the signature type and not
sig just for a change of scene. The property signature is much simpler to
understand than the two that we did earlier.
The first byte will contain
the value 0x8 to signify a property. This byte will have 0x20 set if it is a
instance method and static is not permitted. Thus a value of 0x28 signifies the
presence of instance .We first check the value of the first byte for the
presence of instance or 0x20 and if yes we set the corresponding row of the
propertytypearray to instance.
The next byte is the count
of parameters. This is followed by the return type that we store in the
propertyreturnarray array. Then a for loop we read all the property parameters
in the propertyparmarray array. It is in the DisplayAllProperties method that
we actually display the properties directive.
But first lets look at all
the tables that will be populated by our property keyword. As we have two
properties aa and bb the Property table will have two records only. This table
has three fields, type that we saw earlier, then the name field and finally the
flags field that store one of three flags that we will soon decipher.
The property is associated
with a type and a type can have more than one property. Thus properties behave
like methods and logically should have been stored in the TypeDef table like
methods are. Unfortunately properties are not as common as methods and thus a
new table PropertyMap got created which stores which properties are associated
with which type.
This table has two fields,
the TypeDef that owns the property and the starting property number. Thus we
have to read this table to figure out the first and last property that is owned
by the type. This is why in the DisplayAllProperties method we get the
variables start and end by reading the PropertyMap table.
Now that we have the starting
and end property row numbers we in a for loop use the propertyrow variable to
iterate though all of them. If we start by placing the spaces, the words
property , the property row number in comments and then the name of the
property.
Now we use the method
GetPropertyAttribute to figure out which of the flags the property has. A value
of 0x200 means that we have used the flag specialname and the value 0x400
rtspecialname. The value 0x1000 is for having a default value that we display
after the properties parameters are displayed.
This default will be handled
by a different method. Thus we thought that the GetPropertyAttribute will be
easy to understand. There is one problem. The rtspecialname attribute if placed
by us, is ignored by ilasm and thus for property aa, the flags value is not
0x600
which means both attributes
but only 0x200 that means specialname.
However if we go into the
dll and change the value to 0x600, then ildasm displays both specialname and
rtspecialname. By now you understand the depth we have gone to make our book as
complete as possible but we can guarantee you major goof ups. We then add this
attribute and type of the property along with the return value and then stop.
This is because if the
length of all this is beyond 41 characters, then we need to put an enter to
place everything else like the name and parameters on a new line. Now we add
the name of the property and the open brackets and then need the parameters
which if more than one have to be placed on a new line.
We use the
ParamOnMultipleLines to do this for us but it requires the number of spaces to
be placed before each parameter. We first calculate the length of the property
string up to the open bracket and then also find out the last enter placed.
The number of spaces is like
before, the length so far minus the last enter minus 2 for the enter. However
in most cases unlike methods there will be no enter placed and here we simply
subtract 2 for the alignment. We then write call the method
GetPropertyAttributeDefault to write out the constant value that the property
has been initialized to.
We pass it two parameters,
the flags and the property row number. We first check if the default flag of
0x1000 is turned on or not. If it is we next call the DisplayFromConstantsTable
with the first parameter as the property row number or start, the second
parameter as zero as the end has no meaning here and the third being Property
as this constant row is owned by the property table.
Coming to the
DisplayFromConstantsTable method we now have an or in the if statement. The
first gets activated for the ParamDef value, the second for if the constindex
variable has a row number equal to the property row number passed and if the
tablename parameter is Property. Also we have one more if statement that check
again for the table name.
If it is ParamDef then we
need the words param and id it is Property we only need the equal to sign. When
we leave this function, we have added an enter and thus we need to remove this
enter using the Remove method.
Normally the default value
for a property is rarely seen or used. Normally a property can have as many as
three directives within it, a .get, a .set or a .other. These attributes are
stored in the table MethodSemantics. This table is also used by events which
the next program will demonstrate.
Thus as we have a total of
four directives within our two properties, this table will have four records.
The field attributes tells us which of the directives we have used. The method
GetMethodSemanticsAttributes tells us that we can have as many as six
attributes, three for properties as mentioned earlier and three for events.
The field association tells
is whether this row is owned by a event or a property and the
GetHasSemanticsTable returns one of these value and the GetHasSemanticsValue
method in our case the property row number.
We need the property row
number as the MethodSemantics table most important role is to give us the
method that will handle the get, set or the other. Thus we do not have to
search the method table for the matching method name as there is no rule that
says that a set directive for a property aa must have the method name as
set_aa.
Thus the methodindex
variable gives us the method name that we will display after the directive.
Thus we scan the entire MethodSemantics table and check if the table variable
is Property and the propertyindex is our property number.
We first write out the
spaces and then the directive stored in the attributes variable and then the
method row number in comments. Then we use the methoddeftypearray to write out
the words instance for the method, and then the return value.
We next use the
GetNestedTypeAsString to get the name of the type and then the name and
namespace. We then join these together and write out the TypeDef table row
number. We finally write out the params of this method and then the end of the
braces and the words end of property. A property cannot refer to an method
defined in another assembly.
There are also a million
rules that specify what the parameters to the get, set and the property should
have. We have broken all the rules to stress test our program and ilasm does
not seem to implement these rules. Over to events.
Program43.csc
public void DisplayOneType
(int typedefindex)
{
DisplayOneTypeDefStart(typedefindex);
DisplaySizeAndPack(typedefindex);
DisplayAllSecurity( 0 ,
typedefindex);
DisplayNestedTypes(typedefindex);
DisplayOverride(typedefindex);
DisplayAllMethods(typedefindex);
DisplayAllEvents
(typedefindex);
DisplayAllProperties(typedefindex);
DisplayOneTypeDefEnd(typedefindex
);
}
public void DisplayAllEvents
(int typeindex)
{
int ii;
if ( EventMapStruct ==
null )
return;
for ( ii = 1 ; ii <
EventMapStruct.Length ; ii++)
{
if ( typeindex ==
(EventMapStruct[ii].index ) )
break;
}
if ( ii ==
EventMapStruct.Length)
return;
int start =
EventMapStruct[ii].eindex;
int end;
if ( ii ==
EventMapStruct.Length -1 )
end = EventStruct.Length -
1;
else
end = EventMapStruct[ii + 1].eindex -1;
for ( int eventrow = start ;
eventrow <= end ; eventrow++)
{
Console.Write(CreateSpaces(spacesforrest
+ spacesfornested));
Console.Write(".event
/*14{0}*/ {1}" , (eventrow).ToString("X6") ,
GetPropertyAttribute( EventStruct[eventrow].attr));
string name =
NameReserved(GetString(EventStruct[eventrow].name)) ;
string codedtablename =
GetTypeDefOrRefTable(EventStruct[eventrow].coded) ;
int codedindex =
GetTypeDefOrRefValue(EventStruct[eventrow].coded);
string returnstring =
"";
if ( codedtablename ==
"TypeRef")
returnstring =
typerefnames[codedindex] + " /*01" +
codedindex.ToString("X6") + "*/";
if ( codedtablename ==
"TypeDef")
returnstring =
typedefnames[codedindex] + " /*02" +
codedindex.ToString("X6") + "*/";
Console.Write(returnstring);
Console.Write(" "
+ name) ;
Console.WriteLine();
Console.Write(CreateSpaces(spacesforrest+
spacesfornested));
Console.WriteLine("{");
for ( int kk = 1 ; kk <
MethodSemanticsStruct.Length ; kk++)
{
string attributes =
GetMethodSemanticsAttributes(MethodSemanticsStruct[kk].methodsemanticsattributes);
string table =
GetHasSemanticsTable(MethodSemanticsStruct[kk].association);
int eventindex =
GetHasSemanticsValue(MethodSemanticsStruct[kk].association);
int methodindex =
MethodSemanticsStruct[kk].methodindex;
if ( eventindex == eventrow
&& table == "Event" )
{
Console.Write(CreateSpaces(spacesforrest+2+
spacesfornested));
Console.Write("{0}
{1}{2}", attributes ,
methoddeftypearray[methodindex],
methoddefreturnarray[methodindex] );
string name1 =
NameReserved(GetString(MethodStruct[methodindex].name));
string nspace =
NameReserved(GetString(TypeDefStruct[typeindex].nspace));
if ( nspace != "")
nspace = nspace +
".";
nspace = nspace +
NameReserved(GetString(TypeDefStruct[typeindex].name));
string dummy = "";
dummy =
GetNestedTypeAsString(typeindex);
nspace = dummy + nspace;
Console.Write("{0}/*
02{1} */::{2}({3})" , nspace , typeindex.ToString("X6") , name1
, methoddefparamarray1[methodindex]);
Console.WriteLine(" /*
06{0} */" , (methodindex).ToString("X6"));
}
}
Console.Write(CreateSpaces(spacesforrest+
spacesfornested));
Console.Write("}
");
Console.WriteLine("//
end of event {0}::{1}" ,
NameReserved(GetString(TypeDefStruct[typeindex].name)) , name);
}
}
public string
GetMethodSemanticsAttributes(short asscoiationbyte)
{
string returnstring =
"";
if ( (asscoiationbyte &
0x01) == 0x01)
returnstring = returnstring
+ ".set";
if ( (asscoiationbyte &
0x02) == 0x02)
returnstring = returnstring
+ ".get";
if ( (asscoiationbyte &
0x04) == 0x04)
returnstring = returnstring
+ ".other";
if ( (asscoiationbyte &
0x08) == 0x08)
returnstring = returnstring
+ ".addon";
if ( (asscoiationbyte &
0x10) == 0x10)
returnstring = returnstring
+ ".removeon";
if ( (asscoiationbyte &
0x20) == 0x20)
returnstring = returnstring
+ ".fire";
return returnstring;
}
}
e.il
.assembly extern vijay
{
}
.class zzz
{
.method public void a1(class
[vijay]System.EventHandler)
{
}
.method public instance void a2(class [vijay]System.EventHandler)
{
}
.method public instance void a3()
{
}
.method public static int32 a4()
{
}
.method public instance int32 a5()
{
}
.method public instance void a6()
{
}
.method public instance void a7()
{
}
.event specialname
rtspecialname [vijay]System.EventHandler a
{
.addon instance void
zzz::a1(class [vijay]System.EventHandler)
.addon instance void zzz::a2(class
[vijay]System.EventHandler)
.removeon instance void
zzz::a2(class [vijay]System.EventHandler)
.removeon instance void
zzz::a1(class [vijay]System.EventHandler)
.other instance void
zzz::a3()
.other int32 zzz::a4()
.fire instance int32
zzz::a5()
.fire instance void
zzz::a6()
}
.event specialname
rtspecialname zzz b
{
.addon instance void
zzz::a7()
}
}
.event /*14000001*/ specialname [vijay/* 23000001
*/]System.EventHandler/* 01000002 */ /*01000002*/ a
{
.addon instance void zzz/* 02000002 */::a2(class [vijay/*
23000001 */]System.EventHandler/* 01000002 */) /* 06000002 */
.removeon instance void zzz/* 02000002 */::a1(class [vijay/*
23000001 */]System.EventHandler/* 01000002 */) /* 06000001 */
.fire instance void zzz/* 02000002 */::a6() /* 06000006 */
.other instance void zzz/* 02000002 */::a3() /* 06000003 */
.other int32 zzz/* 02000002 */::a4() /* 06000004 */
} // end of event zzz::a
.event /*14000002*/ specialname zzz/* 02000002 */ /*02000002*/ b
{
.addon instance void zzz/* 02000002 */::a7() /* 06000007 */
} // end of event zzz::b
In this program we deal with
events which are very similar to properties. We add just one function call
DisplayAllEvents to the DisplayOneType method that displays all the events for
us. The point to be noted is that first the overrides are displayed, followed
by methods, followed by events and then properties.
Lets now move our focus to
the DisplayAllEvents method. The EventMap table stores all the types that have
events in a similar way to the PropertyMap table. In the same way we get the
variables start and end to store the first and last event owned by this type.
In the first for loop we
display the initial spaces followed by the event directive and the event row
number in comments. We then reuse the method GetPropertyAttribute to display
the specialname attribute. Events cannot have default values. We then get the
name of the event and unlike properties that have parameters, events have a
data type.
Thus we do not need a index
into the blob heap for the event signature but simply a coded index field coded
that tells us whether the type is a TypeRef or a TypeDef. For event a it is a
TypeRef and for event b a TypeDef.
Depending upon the type
reference we simply use the typerefnames or the typedefnames array to give us
the full type name. We write out the name and then scan the entire
MethodSemantics table for a match of our event. The attributes variable in this
case will have three values, addon, removeon and fire.
It shares the other
directive with properties. Thus we trap those rows that have the table name as
Event and the event row matches our event number. We have on purpose for event
a have multiple declarations of the same type just to tell you that only the
last one is used. However for the other directive all are used.
We write out the initial
spaces as always, then one of the four attributes, followed by the type of the
method from the methoddeftypearray and the return value of the method. The same
methodindex variable is used as the offset like we did for properties.
We then get the name and
namespace of the method and the type that is belongs to using the
GetNestedTypeAsString and simply put them together. These methods have
parameters that we finally write out in brackets.
Thus events are no different
other than signature and having one more directive than properties. Thus the
short and sweet explanations.
Program44.csc
string [] fieldflagsarray ;
string [] fieldparamarray ;
public void CreateSignatures
()
{
//MethodTable
if (MethodStruct != null)
{
methoddefreturnarray = new
string[MethodStruct.Length];
methoddefparamarray = new string[MethodStruct.Length];
methoddefparamarray1 = new string[MethodStruct.Length];
methoddeftypearray = new string[MethodStruct.Length];
methoddefparamcount = new
int[MethodStruct.Length];
for ( int l = 1 ; l <
MethodStruct.Length ; l++)
{
CreateSignatureForEachType(1
, MethodStruct[l].signature, l);
}
}
//MethodRefTable
if ( MemberRefStruct != null
)
{
methodreftypearray = new
string[MemberRefStruct.Length];
methodrefreturnarray = new
string[MemberRefStruct.Length];
methodrefparamarray1 = new string[MemberRefStruct.Length];
for ( int l = 1 ; l <
MemberRefStruct.Length ; l++)
{
CreateSignatureForEachType(2
, MemberRefStruct[l].sig, l);
}
}
//fieldtable
if ( FieldStruct != null )
{
fieldflagsarray = new
string[FieldStruct.Length];
fieldparamarray = new
string[FieldStruct.Length];
for ( int l = 1 ; l <
FieldStruct.Length ; l++)
{
CreateSignatureForEachType
(6, FieldStruct[l].sig, l);
}
}
//propertytable
if ( PropertyStruct != null
)
{
propertyparmarray = new
string[PropertyStruct.Length];
propertyreturnarray = new
string[PropertyStruct.Length];
propertytypearray = new string[PropertyStruct.Length];
for ( int l = 1 ; l < PropertyStruct.Length
; l++)
{
CreateSignatureForEachType(7,
PropertyStruct[l].type, l);
}
}
}
public void
CreateSignatureForEachType (byte type , int index , int row)
{
int uncompressedbyte , count
, howmanybytes;
howmanybytes =
CorSigUncompressData(blob , index , out uncompressedbyte);
count = uncompressedbyte;
byte [] blob1 = new
byte[count];
Array.Copy(blob , index +
howmanybytes , blob1 , 0 , count);
if ( type == 7)
CreatePropertySignature(blob1
, row);
if ( type == 6)
CreateFieldSignature(blob1 ,
row);
if ( type == 1)
CreateMethodDefSignature(blob1
, row);
if ( type == 2)
CreateMethodRefSignature(blob1
, row);
}
public void
CreateFieldSignature (byte [] blobarray , int row)
{
int howmanybytes ,
uncompressedbyte;
int index = 0;
howmanybytes = CorSigUncompressData(blobarray
, index , out uncompressedbyte);
index = index +
howmanybytes;
string returnstring =
GetElementType(index , blobarray , out
howmanybytes , 0 , "");
returnstring =
returnstring.Replace("^",",");
fieldparamarray [row] =
returnstring;
fieldflagsarray [row] =
GetFieldAttributes (row);
}
public void DisplayOneType
(int typedefindex)
{
DisplayOneTypeDefStart(typedefindex);
DisplaySizeAndPack(typedefindex);
DisplayAllSecurity( 0 ,
typedefindex);
DisplayNestedTypes(typedefindex);
DisplayOverride(typedefindex);
DisplayAllFields(typedefindex);
DisplayAllMethods(typedefindex);
DisplayAllEvents(typedefindex);
DisplayAllProperties(typedefindex);
DisplayOneTypeDefEnd(typedefindex
);
}
public void DisplayAllFields
(int typeindex)
{
if ( FieldStruct == null )
return;
int start , startofnext=0;
start = TypeDefStruct[typeindex].findex ;
if ( typeindex ==
(TypeDefStruct.Length - 1) )
startofnext=
FieldStruct.Length;
else
startofnext =
TypeDefStruct[typeindex+1].findex ;
string returnstring =
"";
for ( int fieldindex = start
; fieldindex < startofnext ; fieldindex++)
{
if ( typeindex == 1)
returnstring = "";
else
returnstring =
CreateSpaces(spacesfornested + spacesforrest) ;
returnstring = returnstring
+ ".field /*04" + fieldindex.ToString("X6") +
"*/" ;
returnstring = returnstring
+ " " + fieldflagsarray
[fieldindex] + fieldparamarray [fieldindex] + " " +
NameReserved(GetString(FieldStruct[fieldindex].name)) ;
if (
fieldflagsarray[fieldindex].IndexOf("privatescope") != -1 )
returnstring = returnstring
+ "$PST04" + fieldindex.ToString("X6");
string fieldrva =
GetFieldRVA(fieldindex);
if ( fieldrva !=
"")
returnstring = returnstring
+ " at" + fieldrva;
int len =
returnstring.Length;
string str =
DisplayFromConstantsTable (fieldindex ,0 ,
"FieldDef");
if ( str != "")
str =
str.Remove(str.Length-2,2);
returnstring = returnstring
+ str;
Console.WriteLine("{0}"
, returnstring);
}
}
public string
DisplayFromConstantsTable(int start , int end ,string tablename )
{
string returnstring =
"";
for ( int ii = 1 ; ii <
ConstantsStruct.Length ; ii++)
{
string consttablename =
GetHasConstTable(ConstantsStruct[ii].parent);
int constindex =
GetHasConstValue(ConstantsStruct[ii].parent);
if ( consttablename ==
tablename )
{
if ( (constindex >= start
&& constindex < end && start != end && tablename ==
"ParamDef" ) || ((tablename == "Property" || tablename ==
"FieldDef") && start == constindex ) )
{
}
}
}
return returnstring;
}
public string
GetFieldAttributes (int fieldrow)
{
int flags =
FieldStruct[fieldrow].flags;
string returnstring =
"";
string arrayoffset =
"";
int ii;
if ( FieldLayoutStruct !=
null)
{
for ( ii = 1 ; ii <
FieldLayoutStruct.Length ; ii++)
{
int jj =
FieldLayoutStruct[ii].fieldindex ;
if ( jj == fieldrow )
break;
}
if ( ii !=
FieldLayoutStruct.Length )
{
int offset =
FieldLayoutStruct[ii].offset ;
arrayoffset = "["
+ offset.ToString("") + "] ";
}
}
if ( (flags & 0x06) ==
0x06)
returnstring = returnstring
+ "public ";
else
if ( (flags & 0x05) ==
0x05)
returnstring = returnstring
+ "famorassem ";
else
if ( (flags & 0x03) ==
0x03)
returnstring = returnstring
+ "assembly ";
else
if ( (flags & 0x01) ==
0x01)
returnstring = returnstring
+ "private ";
else
if ( (flags & 0x02) ==
0x02)
returnstring = returnstring
+ "famandassem ";
else
if ( (flags & 0x04) ==
0x04)
returnstring = returnstring
+ "family ";
else
returnstring = returnstring
+ "privatescope ";
if ( (flags & 0x10) ==
0x10)
{
int firstfourbits = flags
& 0xF;
if ( (firstfourbits == 0x06)
|| (firstfourbits == 0x01))
{
returnstring = returnstring
+ "static " ;
}
else
returnstring = "static
" + returnstring;
}
returnstring = arrayoffset +
returnstring;
if ( (flags & 0x20) ==
0x20)
returnstring = returnstring
+ "initonly ";
if ( (flags & 0x40) ==
0x40)
returnstring = returnstring
+ "literal ";
if ( (flags & 0x80) ==
0x80)
returnstring = returnstring
+ "notserialized ";
if ( (flags & 0x200) ==
0x200)
returnstring = returnstring
+ "specialname ";
if ( (flags & 0x400) ==
0x400)
returnstring = returnstring
+ "rtspecialname ";
if ( (flags & 0x400) ==
0x400)
returnstring = returnstring
+ "";
returnstring = returnstring
+ DecodeParamAttributes ( flags , 0 ,
fieldrow , 0x1000);
return returnstring;
}
public string GetFieldRVA
(int fieldrow)
{
string returnstring =
"";
int ii;
if ( FieldRVAStruct == null
)
return "";
for ( ii = 1 ; ii <
FieldRVAStruct.Length ; ii++)
{
int jj =
FieldRVAStruct[ii].fieldi;
if ( jj == fieldrow )
break;
}
if ( ii !=
FieldRVAStruct.Length )
{
int rva=
FieldRVAStruct[ii].rva ;
returnstring = "
D_" + rva.ToString("X8") ;
}
return returnstring;
}
e.il
.class zzz
{
.field int32 i
.field [10] public int32 j
.field static public int32 k
.field static family int32 l
.field public static int32 n
at vijay
.field public marshal(int32)
int32 o
.field public int32 m1 =
bool(true)
.field public int32 m2 =
int8(2)
.field public int32 m3 =
int16(3)
.field public int32 m4 =
int32(5)
.field public int32 m5 =
int64(6)
.field public string p = "hi"
.field public rtspecialname string q
}
.data vijay = int32(11)
Output
.class /*02000002*/ private
auto ansi zzz
extends [mscorlib/* 23000001 */]System.Object/* 01000001 */
{
.pack 1
.size 0
.field /*04000001*/ privatescope int32 i$PST04000001
.field /*04000002*/ [10] public int32 j
.field /*04000003*/ public static int32 k
.field /*04000004*/ static family int32 l
.field /*04000005*/ public static int32 n at D_00004000
.field /*04000006*/ public
marshal( int32) int32 o
.field /*04000007*/ public int32 m1 = bool(true)
.field /*04000008*/ public int32 m2 = int8(0x02)
.field /*04000009*/ public int32 m3 = int16(0x0003)
.field /*0400000A*/ public int32 m4 = int32(0x00000005)
.field /*0400000B*/ public int32 m5 = int64(0x6)
.field /*0400000C*/ public string p = "hi"
.field /*0400000D*/ public string q
} // end of class zzz
What we call instance
variables in a programming language, il calls the same thing fields. Fields are
what we use our programs data in memory. Thus fields at the end of the day are
nothing but memory locations that have a type. As with everything we have
static and instance fields. A static field is one no matter how many instances
of the type we create.
A instance field can only be
used once we instantiate the field and thus each time we do so, a new instance
of that field gets created. Thus an instance field is not shared by all the
instances like a static field is. Unlike C#, il supports global fields and
methods but with the condition that global fields must be static.
A field is declared using
the directive .field and this is first followed by a number specifying the
offset in memory where this field must be stored as for field j which is at an
offset of 10. Obviously the layout of the class must be instance and this
offset is invalid for global or static fields.
This is followed by field
attributes like public, private that we did earlier plus some unique for
fields. Then comes the obvious the type of the field and then the name. After
this comes two optional entities, associating the field with a memory location
like in field n or giving it a default value like filed m onwards.
All global fields must have
such a data label as it specifies where in the PE file the fields data is to be
found. For static fields this requirement is optional but normally followed.
As always we start with the
CreateSignatures method where we initialize the two string arrays,
fieldflagsarray which stores the fields attributes and data type of the field
wrongly called fieldparamarray as the field has no parameters. We now call the
method CreateSignatureForEachType with a value of 6 as this is code given for a
field.
In this method we simply
call the method CreateFieldSignature which is the simplest method we have ever
written. All that we do here is to first read the first byte which will always
be 6 and this is then followed by the type of field that we store in the array
fieldparamarray.
But before this we replace
the ^ sign with a comma due to the array problem we had earlier. The
fieldflagsarray is really not needed as the field attributes are not stored
with the signature in the blob array but along with each field in the Field
table. Lets move to the fields attributes which get decoded by the method
GetFieldAttributes.
It is the flags member of
the field table that stores the attributes for the fields. The offset per field
is stored in the table FieldLayout which has only two fields, one the field
table row number and the other the offset. Thus we scan the table FieldLayout
and quit out of the loop if we meet our field number.
If a match is found, we
display the offset in square brackets in decimals and not in hex. Consistency
thy name is not ildasm. Then as usual we have the seven access modifiers for
the fields. There is only one small problem with the static keyword which has a
bit mask of 0x10.
The only problem is whether
static comes before or after the access modifiers. The problem as all questions
asked about life are depends. If we have a public or private these come before
the static, the rest of the access modifiers come after like we have with
fields k and l respectively.
We have the attributes
initonly, literal, notserialized, specialname and finally rtspecialname that do
not show up like for field q. We now add the method DisplayAllFields in the
method DisplayOneType and bear in mind the order.
For displaying all fields we
do what we did for methods set the start and startofnext to the first and last
but one field owned by each type. As we can have global fields we check if the
typeindex value is one as this is for global fields and thus set the number of
spaces to zero else to be decided by the namespace and nesting levels.
We then write out the field
directive, the field row number in comments and then the attributes from the
fieldflagsarray array and then the data type from the fieldparamarray. A space
and then the name and if this has the attribute privatescope, then the name as
always get the added string $PST04 and row number.
Lets take field n which has
the words at followed by the name vijay that is defined using the data
directive. The data directive uses the constants seen before to give a memory
location in this vijay a value. We may use names like vijay but if you look at
the output, it gets replaced by a label D_00004000.
If you look at the output
generated by ildasm, this data directive has a value of 10 that we will display
later. We have a method GetFieldRVA to figure out the at clause for us. All the
at clauses are stored in another simple table called the FieldRVA table which
as the earlier table has two columns.
The first is the rva of the
field and the second the field row number. Like before if we meet a match we
display the field rva which is a number show above and not a name like vijay.
Finally we have to display the default value for the field and use the good old
DisplayFromConstantsTable table to do so for us.
The second parameter is 0
like for a property. The only change we have made is that the if statement
second condition now has checks that the constindex must be equal to start and
the table name could be either FieldDef or Property as before. There is no
difference in who uses a default value, they all get stored in the constants
table.
As mentioned before we will
revisit this method for handling floating point numbers and now also strings.
This ends the discourse on fields.
Program45.csc
public void DisplayOneType
(int typedefindex)
{
DisplayOneTypeDefStart(typedefindex);
DisplaySizeAndPack(typedefindex);
DisplayCustomAttribute("TypeDef"
, typedefindex , 2 + spacefornamespace + spacesfornested);
DisplayAllSecurity( 0 ,
typedefindex);
DisplayNestedTypes(typedefindex);
DisplayOverride(typedefindex);
DisplayAllFields(typedefindex);
DisplayAllMethods(typedefindex);
DisplayAllEvents(typedefindex);
DisplayAllProperties(typedefindex);
DisplayOneTypeDefEnd(typedefindex
);
}
public void
DisplayCustomAttribute (string tname , int tabindex , int noofspaces)
{
if (CustomAttributeStruct ==
null)
return;
string initialspaces;
initialspaces =
CreateSpaces(noofspaces);
for ( int ii = 1 ; ii <
CustomAttributeStruct.Length ; ii++)
{
string parentcodedtablename
= GetHasCustomAttributeTable (CustomAttributeStruct[ii].parent) ;
int parentcodedindex =
GetHasCustomAttributeValue(CustomAttributeStruct[ii].parent);
string typecodetable =
GetCustomAttributeTypeTable (CustomAttributeStruct[ii].type) ;
int typecodedindex =
GetCustomAttributeTypevalue(CustomAttributeStruct[ii].type);
int tableindexcode = 0;
int tablenumber = 0;
string returnstring =
"";
bool custombug = false;
int typeindex ;
string typename =
"";
string returnvaluestring =
"";
string paramstring = "";
string dummy = "";
string methodname =
"";
string
onespace="";
if ( typecodetable ==
"MethodRef" )
{
tableindexcode = 0x0A;
typeindex=
MemberRefStruct[typecodedindex].clas >> 3;
typename =
typerefnames[typeindex] ;
returnvaluestring = methodrefreturnarray [typecodedindex];
paramstring =
methodrefparamarray1 [typecodedindex];
tablenumber = 1;
methodname =
NameReserved(GetString(MemberRefStruct[typecodedindex].name));
onespace = " ";
}
else
{
tableindexcode = 0x06;
typeindex = GetTypeForMethod
(typecodedindex);
typename =
typedefnames[typeindex] ;
returnvaluestring =
methoddefreturnarray[typecodedindex];
paramstring = methoddefparamarray1[typecodedindex] ;
tablenumber = 02;
methodname =
NameReserved(GetString(MethodStruct[typecodedindex].name));
}
if ( (tname ==
parentcodedtablename && tabindex == parentcodedindex) ||
(parentcodedtablename == "TypeRef" && tabindex == 0) )
{
if
(typename.IndexOf("System.Diagnostics.DebuggableAttribute") != -1)
custombug = true;
if ( custombug )
{
Console.Write(CreateSpaces(noofspaces));
Console.WriteLine("//
--- The following custom attribute is added automatically, do not uncomment
-------");
returnstring = returnstring
+ CreateSpaces(noofspaces) + "//";
if ( initialspaces ==
"")
returnstring = returnstring
+ " ";
}
if ( parentcodedtablename ==
"TypeRef")
{
returnstring = returnstring
+ initialspaces + ".custom /*0C" + ii.ToString("X6")
+"*/ (" + typerefnames[parentcodedindex];
returnstring = returnstring
+ "/*" + tablenumber.ToString("X2") + parentcodedindex.ToString("X6")
+ "*/ ) ";
}
else
returnstring = returnstring
+ initialspaces + ".custom /*0C" + ii.ToString("X6") +
":" + tableindexcode.ToString("X2") +
typecodedindex.ToString("X6") + "*/ ";
returnstring = returnstring
+ "instance " + returnvaluestring + onespace + typename ;
returnstring = returnstring
+ "::" + methodname;
returnstring = returnstring
+ "(";
if ( noofspaces == 0)
dummy = ParamOnMultipleLines (paramstring ,
returnstring.Length + 2);
else if ( paramstring != null)
dummy = ParamOnMultipleLines(paramstring ,
returnstring.Length);
if ( custombug )
{
int ind1 =
dummy.IndexOf("\r\n");
dummy= dummy.Insert(ind1+2,
CreateSpaces(noofspaces) + "//");
dummy= dummy.Remove(ind1+10,
4);
}
returnstring = returnstring
+ dummy ;
returnstring = returnstring
+ ")";
returnstring = returnstring
+ " /* " + tableindexcode.ToString("X2") +
typecodedindex.ToString("X6") + " */";
Console.Write(returnstring);
int index =
CustomAttributeStruct[ii].value;
int
howmanybytes,uncompressedbyte ;
howmanybytes =
CorSigUncompressData(blob , index , out uncompressedbyte);
if ( uncompressedbyte == 0)
{
Console.WriteLine();
continue;
}
index = index +
howmanybytes;
byte [] blobarray = new
byte[uncompressedbyte];
Array.Copy(blob , index ,
blobarray , 0 , uncompressedbyte);
bool displayoneline = true;
string displaystring =
"";
for ( int jj = 0 ; jj < uncompressedbyte ; jj++)
{
if ( blobarray[jj] <
0x20)
displayoneline = false;
if ( blobarray[jj] >=
0x7f)
displayoneline = false;
}
if ( displayoneline)
{
displaystring = " =
\"";
for ( int jj = 0 ; jj < uncompressedbyte ; jj++)
{
displaystring =
displaystring + (char)blobarray[jj];
}
displaystring =
displaystring + "\"";
if ( tname ==
"MemberRef")
Console.Write(displaystring);
else
Console.WriteLine(displaystring);
}
else
{
int
startoffunctionclosebracket = returnstring.LastIndexOf(")");
int lastenter =
returnstring.LastIndexOf("\r\n");
if ( lastenter == -1)
lastenter = -2;
int diff2 =
startoffunctionclosebracket - lastenter + 19;
Console.Write(" = (
");
DisplayFormattedColumns
(CustomAttributeStruct[ii].value,diff2 , false);
}
}
}
}
public int
GetCustomAttributeTypevalue(int attributecodedtype)
{
return attributecodedtype
>> 3;
}
public string
GetCustomAttributeTypeTable( int attributecodedtype)
{
string returnstring = "";
int tag = attributecodedtype
& 0x07;
if ( tag == 0)
returnstring = returnstring
+ "NotUsed";
if ( tag == 1)
returnstring = returnstring
+ "NotUsed";
if ( tag == 2)
returnstring = returnstring
+ "MethodDef";
if ( tag == 3)
returnstring = returnstring
+ "MethodRef";
if ( tag == 4)
returnstring = returnstring
+ "NotUsed";
return returnstring;
}
public int
GetHasCustomAttributeValue( int attributecodedparent)
{
return attributecodedparent
>> 5;
}
public string
GetHasCustomAttributeTable ( int attributecodedparent)
{
string returnstring =
"";
int tag =
attributecodedparent & 0x1F;
if ( tag == 0)
returnstring = returnstring
+ "MethodDef";
if ( tag == 1)
returnstring = returnstring
+ "FieldDef";
if ( tag == 2)
returnstring = returnstring
+ "TypeRef";
if ( tag == 3)
returnstring = returnstring
+ "TypeDef";
if ( tag == 4)
returnstring = returnstring
+ "ParamDef";
if ( tag == 5)
returnstring = returnstring
+ "InterfaceImpl";
if ( tag == 6)
returnstring = returnstring
+ "MemberRef";
if ( tag == 7)
returnstring = returnstring
+ "Module";
if ( tag == 8)
returnstring = returnstring
+ "DeclSecurity";
if ( tag == 9)
returnstring = returnstring
+ "Property";
if ( tag == 10)
returnstring = returnstring
+ "Event";
if ( tag == 11)
returnstring = returnstring
+ "Signature";
if ( tag == 12)
returnstring = returnstring
+ "ModuleRef";
if ( tag == 13)
returnstring = returnstring
+ "TypeSpec";
if ( tag == 14)
returnstring = returnstring
+ "Assembly";
if ( tag == 15)
returnstring = returnstring
+ "AssemblyRef";
if ( tag == 16)
returnstring = returnstring
+ "File";
if ( tag == 17)
returnstring = returnstring
+ "ExportedType";
if ( tag == 18)
returnstring = returnstring
+ "ManifestResource";
return returnstring;
}
}
e.il
.assembly extern vijay
{
}
.class zzz
{
.custom instance void [vijay]a1::.ctor(bool,
string ) = ( 41 41 42 )
.custom instance void
[vijay]a2::.ctor(bool) = ( 7f 41 41 42 )
}
//.custom ([vijay]a3)
instance void [vijay]a4::.ctor()= ( 65 )
Output
.class /*02000002*/ private
auto ansi zzz
extends [mscorlib/* 23000002 */]System.Object/* 01000001 */
{
.custom /*0C000001:0A000001*/ instance void [vijay/* 23000001
*/]a1/* 01000002 */::.ctor(bool,
string) /* 0A000001 */ = "AAB"
.custom
/*0C000002:0A000002*/ instance void [vijay/* 23000001 */]a2/* 01000003
*/::.ctor(bool) /* 0A000002 */ = ( 7F 41 41 42 ) // .AAB
} // end of class zzz
This example deals with one
major feature of the .net world called custom attributes. Using this we are
allowed to extend the .net world in directions the original designers did not
plan. Thus in the DisplayOneType method we add the DisplayCustomAttribute
method with three parameters.
The first is the entity contains
the custom attribute. You will be surprised to note that there are over 19
entities that this attribute can be placed in. In our program now we will
wonder about attributes placed in the TypeDef table only.
The second parameter is the
row number of the previous parameter as each type can have as many attributes
as it likes. Thus the above table-index combination will be present in the
CustomAttribute table as a coded index. The last is the indentation to be given
to the custom directive.
The custom attributes add
annotations to any item of the metadata and are used to store anything we like
at compile time and access it at runtime. We start with the directive custom
followed by the name of a method that has to be ctor.
This is followed by the parameters
if any and then some bytes in brackets that will contain the values that the
parameters need to be initialized with. In our case we have two custom
attributes, both refer to the .ctor method or the constructor but the first is
a MemberRef as it refers to a .ctor defined in the assembly vijay and the
second to a MethodDef as the .ctor lies in the same type.
The problem with the
initialization bytes is that if they are all ascii, they are displayed one
after the other, if there is a single non ascii byte, then they are displayed
with brackets and with the ascii values as comments like before. There is one
last exception.
If the type is
System.Diagnostics.DebuggableAttribute only then there are some comments added
before the attribute. Lets start with the actual code in the
DisplayCustomAttribute method where we scan the CustomAttribute table as each
custom attribute is stored here. The first field parent is a coded index that
tells us where this custom directive must be written.
The method GetHasCustomAttributeTable
has up to 19 different tables and two programs down the line we have tried to
show you each of the above 19 cases. The parentcodedtablename variable does
store the table name and the parentcodedindex the actual row number in that
table that owns the custom directive.
This is the only coded index
that takes up five bits. Then we have one more coded index that tells us
whether the method .ctor is a MethodDef or a MethodRef. We have a function
GetCustomAttributeTypeTable that decodes the type field and even though it
takes up three bits, one bit would have sufficed.
We then create a huge list
of variables that we set depending upon whether we have a Method definition or
a Method reference. We will explain the values we set the variables to when we
actually use them. We do this so that we can have one set of statements that
handle both cases.
We now have a if statement
that decides if we have a match. There are two cases when a match is found. One
when both tname and tabindex parameters match the parent field coded index. Two
is when we have a value of TypeRef for the table and the tabindex parameter is
0. This special case we will handle a little later.
The first thing we do when
we meet a match is check whether we want the extra comments if the type is
System.Diagnostics.DebuggableAttribute. The typename variable can either be a
type in TypeDef or the TypeRef table.
As we have the array
typerefnames or typedefnames that store the entire name we use the variable
typeindex as an offset to this array. We get the type the method is in by
either using the GetTypeForMethod method for a MethodDef and the simple method
for a MemberRef by reading the clas field of the MemberRef table.
The typecodedindex variable
is the index into either of the two tables. We keep using the noofspaces to
write out the spaces. There will be one case only where the tablename is
TypeRef and the spaces are zero. In comments we have shown you a custom
attribute that has a open and close bracket and the name of the type ref within
it.
We first handle this
specific this first. We first display the dumber of initialspaces and then the
custom directive and the row number denoted by the loop variable ii. Then we
have the open bracket and use the parentcodedindex variable as the index into
the typerefnames array that contains all the type refs.
We then display the
tablenumber variable that is either 2 for a typedef or a MethodDef. For a
method ref this will be 1 the code for a TypeRef table. In the dame vein the
tableindexcode variable is 6 the code for a MethodDef in the else statement or
the code for a MethodRef which is 0a.
We end by writing out the
row number of the parent table stored in parentcodedindex variable. If the
parentcodedtablename table is not TypeRef but any of the other 18, then life
gets simpler. We display the initial spaces and then the custom directive and
the row number in the custom table within comments.
This is followed by the
MethodRef or MethodDef table number stored in the tableindexcode variable and
then the row number in one of these tables. As the constructor is not the
static one .cctor, we have to write out the words instance followed by the
return value of the constructor.
The variable
returnvaluestring is set to the methoddefreturnarray array for the case of a
MethodDef and the corresponding methodrefreturnarray for a MemberRef. The
variable typecodedindex is used as an offset like before. Then comes a problem.
In the case of the MemberRef
we have not left a space at the end of the each member of the
methodrefreturnarray array and thus we set the onespace variable to a space. We
do not do this in the case of a MethodDef.
We then write out the
typename variable that we explained earlier and then the name of the method
that can either come from the name field of the MemberRef or MethodDef table.
We then open a bracket and then again check for number of spaces. If zero the
special case of the TypeRef , we call the ParamOnMultipleLines with two spaces
more than the length of the string, if not we just use the length as it would
be obvious.
The paramstring string that
contains all the parameters is once again either the methodrefparamarray1 or
the methoddefparamarray1. One small problem if we have added the extra comments
our work is not done yet. We first find out the last enter or the last line and
then add some comments two chars later with some spaces and comments.
These should come at the
beginning of the last line. We then need to remove the 4 chars in this case as
noofspaces is 2 we have just added to
position the parameters one below the
other. Normally we will remove the number of chars we have added in the Insert
function.
We then close the bracket
and write out the table code number for MethodDef or MethodRef and the table
row number. All is not over yet as we have to display either the bytes in
brackets or a string. We use the value field as an offset into the blob array.
We as before use the first
bytes to figure out the length of the bytes owned by the custom attribute. We
write out a empty enter if there are no bytes at all. We then sue the Copy
method to copy these bytes into a fresh and clean array called blobarray. We
then set the displayoneline variable to true initially and then scan the entire blobarray.
If we meet a single number
that is not a ascii char, we set the displayoneline variable to false. For us a
non ascii char is a byte that is less than 32 or larger than 128. If this
variable is true we start with a double inverted comma and the write the entire
string into the displaystring variable.
We then close the double and
either place a enter or not depending upon whether the table name is MemberRef
or not. A little later we will explain that a call of a method can also have a
custom attribute and thus we will have a problem of a extra enter if the custom
directive is on a MemberRef.
Finally we have to display
the bytes in brackets and thus we first out where we placed the last close
function bracket and the last enter. If there was no enter placed then we set
the lastenter variable to minus 2 as a enter takes up two bytes. We then get
the difference as the subtraction of the two minus 19 bytes.
We use 19 bytes as we
painstakingly calculated the number of bytes we have after the last close
bracket. These include spaces, the comments and the row number and the equal to
sign. We call our trusty DisplayFormattedColumns to display the bytes for us.
Program46.csc
Public void
DisplayAllMethods (int typeindex)
{
int tablerow =
entrypointtoken & 0x00ffffff;
if ( tablerow == methodindex
)
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest + 2 + spacesfornested) +
".entrypoint";
Console.WriteLine(methodstring);
DisplayCustomAttribute
("MethodDef" , methodindex , 2 + spacesforrest + spacesfornested);
DisplayParamsForMethod
(methodindex);
DisplayAllSecurity( 1 ,
methodindex );
}
public string
DisplayParamsForMethod (int methodrow)
{
if ( ParamStruct == null)
return "";
string returnstring =
"";
int start=0, end=0;
start =
MethodStruct[methodrow].param;
if ( methodrow ==
(MethodStruct.Length - 1))
end =
ParamStruct.Length ;
else
end =
MethodStruct[methodrow+1].param;
for ( int index = start ;
index < end ; index++)
{
int seq =
ParamStruct[index].sequence;
returnstring =
CreateSpaces(spacesforrest+ spacesfornested+2) + ".param [" +
seq.ToString() + "]" + "/*08" +
index.ToString("X6") + "*/";
string str =
DisplayFromConstantsTable (index ,0 ,"ParamDef");
if ( str != "")
Console.Write(returnstring +
str );
bool b = HasCustomAttribute
("ParamDef" , index);
if ( b && str ==
"")
Console.WriteLine(returnstring
+ " ");
DisplayCustomAttribute
("ParamDef" , index , spacesforrest+ spacesfornested+2);
}
return "";
}
public string
DisplayFromConstantsTable(int start , int end ,string tablename )
{
string returnstring =
"";
if ( ConstantsStruct ==
null)
return "";
for ( int ii = 1 ; ii <
ConstantsStruct.Length ; ii++)
{
string consttablename =
GetHasConstTable(ConstantsStruct[ii].parent);
int constindex =
GetHasConstValue(ConstantsStruct[ii].parent);
if ( consttablename ==
tablename )
{
if ( start == constindex )
{
if ( consttablename ==
"Property")
returnstring = " =
";
else
returnstring = " = ";
if (
ConstantsStruct[ii].dtype == 2 )
{
bool val =
BitConverter.ToBoolean(blob , ConstantsStruct[ii].value+1);
if ( val )
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(true)" + "\r\n";
else
returnstring = returnstring
+ GetType(ConstantsStruct[ii].dtype) +
"(false)"+ "\r\n";
}
public bool
HasCustomAttribute( string tablename , int index)
{
if ( CustomAttributeStruct
== null)
return false;
for ( int ii = 1 ; ii <
CustomAttributeStruct.Length ; ii++)
{
string parentcodedtablename=
GetHasCustomAttributeTable(CustomAttributeStruct[ii].parent) ;
int parentcodedindex=
GetHasCustomAttributeValue(CustomAttributeStruct[ii].parent);
if ( parentcodedtablename ==
tablename && parentcodedindex
== index)
return true;
}
return false;
}
e.il
.assembly extern vijay
{
}
.class zzz
{
.method public instance void
.ctor(bool i , bool j , int32 k , int64 l , int16 m , int8 n)
{
.custom instance void
[vijay]a3::.ctor(bool) = ( 41 41 42 )
.custom instance void
[vijay]a31::.ctor(bool)
.param[1]
.param[3]
.custom instance void
[vijay]a2::.ctor(bool) = ( 41 41 42 )
.param[2] = int8(1)
.custom instance void
[vijay]a1::.ctor(bool) = ( 41 41 42 )
.param[4]
.custom instance void
[vijay]a4::.ctor(bool) = ( 41 41 42 )
.param[5] = int16(2)
.param[6]
.custom instance void
[vijay]a6::.ctor(bool)
.custom instance void
[vijay]a6::.ctor(bool)
.param[7]
}
}
Output
.method /*06000001*/ public specialname rtspecialname
instance void
.ctor(bool i,
bool j,
int32 k,
int64 l,
int16 m,
int8 n) cil managed
// SIG: 20 06 01 02 02 08 0A 06 04
{
.custom /*0C000001:0A000001*/ instance void [vijay/* 23000001
*/]a3/* 01000002 */::.ctor(bool) /* 0A000001 */ = "AAB"
.custom /*0C000002:0A000002*/ instance void [vijay/* 23000001
*/]a31/* 01000003 */::.ctor(bool) /* 0A000002 */
.param [2]/*08000002*/
= int8(0x01)
.custom /*0C000003:0A000004*/ instance void [vijay/* 23000001
*/]a1/* 01000005 */::.ctor(bool) /* 0A000004 */ = "AAB"
.param [3]/*08000003*/
.custom /*0C000004:0A000003*/ instance void [vijay/* 23000001
*/]a2/* 01000004 */::.ctor(bool) /* 0A000003 */ = "AAB"
.param [4]/*08000004*/
.custom /*0C000005:0A000005*/ instance void [vijay/* 23000001
*/]a4/* 01000006 */::.ctor(bool) /* 0A000005 */ = "AAB"
.param [5]/*08000005*/
= int16(0x0002)
.param [6]/*08000006*/
.custom /*0C000006:0A000006*/ instance void [vijay/* 23000001
*/]a6/* 01000007 */::.ctor(bool) /* 0A000006 */
.custom /*0C000007:0A000006*/ instance void [vijay/* 23000001
*/]a6/* 01000007 */::.ctor(bool) /* 0A000006 */
// Method begins at RVA 0x2050
// Code size 0
(0x0)
} // end of method zzz::.ctor
We now need to display the
attributes that come along with the param directive. If we look at the method
DisplayAllMethods we will first see the call of the method
DisplayCustomAttribute with the first parameter MethodDef as we want to first
display all custom attributes associated with a Method.
This will be the first
custom attribute in our il file. Then we have rewritten the method
DisplayParamsForMethod as this is the method that will earlier displayed all
the param directives for us. Lets take a fresh look at the param directives in our il file.
The param directive with 1
will never show up in the output as it requires one of two conditions to be
fulfilled. Either it must have a constant value or a custom attribute
associated with it. Thus param 3 has a custom attribute following it and thus
it qualifies. The param 2 has both a
custom attribute as well as a constant value.
Param 3 and 2 are not in
sorted order but the system sorts them out before placing them in tables. Param
4 has an attribute, param 5 only a constant value and param 6 has two custom
attributes and param 7 is not found in the output as there are only six
parameters.
Lets put the above in code
where we first write out all the custom attributes for a method and then the
params and the attributes if any. We start once again by rewriting code and
this is the bane of our life. Some programs above we had such a workable
DisplayParamsForMethod method and all that gone done the tube. What a life.
We first tell ourselves that
we have six parameters to the method and thus can have at least 6 rows in the
param table. We would you to confirm this and look at the name of the params
and the sequence numbers. Thus at most we will write out six .params
directives.
We prepare ourselves by
first picking out the sequence number as they are ordered sequentially in the
seq variable with index being the succeeding row numbers in the Param table. We
then create the param directive string with the seq variable as the number in
brackets and the index variable being used for the row number in the param
table as a comment.
This string variables value
may never be used. We then use the slightly rewritten method
DisplayFromConstantsTable to give us any constant value that the param
attribute may have. The DisplayFromConstantsTable now uses the same logic as
for Fields and Properties, only the first parameter is used, the second end is
ignored.
For the sake of not changing
the other method calls for the method DisplayFromConstantsTable we chose to
maintain three parameters when we are using only two. The if statement I also
simple, we check for a table name match as well as a row match. If this happens
we first write out the initial equal to sign and then the constant value which
code is unaltered.
The problem is that the
property table requires one less space than fields and params. Thus we check
the string return value from the method DisplayFromConstantsTable. If it I not
null, this only means that we have a constant value and thus the param
directive must be displayed.
Thus we have a if statement
that displays the param directive but we must remember because of our initial
design this string has a enter at the end and therefore we use the Write and
not WriteLine method. We also now need to figure out whether this param has a
custom attribute.
Thus we first have a simple
method HasCustomAttribute that takes a table name and a row number and tells us
whether this entity owns a row in the CustomAttribute table. In this method we
scan all the rows of the CustomAttribute table and if the coded index parent
gives us a row and table name match we return true and quit the loop.
If we move out of the loop,
it means no match and we return false. Now that the variable tells us whether
we have a custom attribute associated
with our param directive or not, we use a if statement that checks that
variable b is true as well as the str variable is null.
This is important as if the
str variable is non null, the earlier if statement displayed the param
directive. We also add a space after the param directive as if it has no
default value, it has to have a extra space. Now we use the
DisplayCustomAttribute method to display the custom attributes if any
associated with this param row number.