Program47.csc
Public void
DisplayAllMethods(int typeindex)
{
methodstring = methodstring
+ CreateSpaces(spacesforrest + 2 + spacesfornested) + "// Code size 0
(0x0)" + "\r\n";
if (
IsGlobalMethod(methodindex))
methodstring = methodstring
+ CreateSpaces(spacesforrest+ spacesfornested) + "} // end of global
method " + NameReserved(GetString(MethodStruct[methodindex].name)) +
"\r\n";
else
methodstring = methodstring
+ CreateSpaces(spacesforrest+ spacesfornested) + "} // end of method
" + NameReserved(GetString(TypeDefStruct[typeindex].name)) +
"::" + NameReserved(GetString(MethodStruct[methodindex].name)) +
"\r\n";
Console.WriteLine(methodstring);
}
public void
DisplayTypeDefsAndMethods ()
{
notprototype = true;
if ( TypeDefStruct.Length !=
2)
{
Console.WriteLine();
Console.WriteLine("//
=============================================================");
Console.WriteLine();
}
Console.WriteLine();
Console.WriteLine("//
=============== GLOBAL FIELDS AND METHODS ===================");
Console.WriteLine();
DisplayGlobalFields();
DisplayGlobalMethods();
if ( TypeDefStruct.Length !=
2)
{
Console.WriteLine();
Console.WriteLine("//
=============================================================");
}
public void
DisplayGlobalFields ()
{
int start , startofnext =0;
if ( TypeDefStruct == null
|| FieldStruct == null)
return;
start = TypeDefStruct[1].findex ;
if ( TypeDefStruct.Length ==
2 )
startofnext =
FieldStruct.Length;
else
startofnext =
TypeDefStruct[2].findex ;
if ( start != startofnext )
{
Console.WriteLine("//Global
fields");
Console.WriteLine("//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
DisplayAllFields (1);
}
}
public void
DisplayGlobalMethods ()
{
int start , startofnext=0;
start = TypeDefStruct[1].mindex ;
if ( TypeDefStruct == null
|| MethodStruct == null)
return;
if ( TypeDefStruct.Length ==
2 )
startofnext=
MethodStruct.Length;
else
startofnext =
TypeDefStruct[2].mindex ;
if ( start != startofnext )
{
Console.WriteLine("//Global
methods");
Console.WriteLine("//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
spacesforrest = 0;
DisplayAllMethods(1);
spacesforrest = 2;
}
}
public bool IsGlobalMethod
(int methodrow)
{
int start , startofnext=0;
methodrow ,
TypeDefStruct.Length);
if ( TypeDefStruct.Length ==
2)
return true;
start = TypeDefStruct[1].mindex ;
if ( TypeDefStruct.Length ==
1 )
{
startofnext=
MethodStruct.Length;
}
else
startofnext =
TypeDefStruct[2].mindex;
if ( methodrow >= start
&& methodrow < startofnext )
return true;
else
return false;
}
e.il
.namespace kkk
{
.method static private void
a1()
{
}
.field static private int32
i
}
.class zzz
{
}
Output
//
=============================================================
// =============== GLOBAL
FIELDS AND METHODS ===================
//Global fields
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.field /*04000001*/ private
static int32 i
//Global methods
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.method /*06000001*/ private
static void
a1() cil managed
// SIG: 00 00 01
{
// Method begins at RVA 0x2050
// Code size 0 (0x0)
} // end of global method a1
//
=============================================================
In this small program we
display all the global method and fields that we have in the il file. Most
newer languages do not support the concept of global fields and variables but
we are fortunate that il does. We told you a long time ago that we always have
a extra type that will handle global entities.
We have in the
DisplayAllMethods a if statement at the very end that checks whether we have a
global function or not. We have written a function IsGlobalMethod that tells us
whether the current method is global or not by passing it the method number. In
this method we first check if there are only global methods in the il file.
This happens if the length
of the TypeDef array is 2, remember it is always larger by one. We also told
you that there is always a global type available. We then find out as before
the first and last method owned by this global type. If the method row falls
within this range we return true or else we return false.
The only change is that as
always we no not check for the last type but for the first type. If this method
returns true, we do display the type name that is Module but add the words
global instead. In the method DisplayTypeDefsAndMethods we have add two lines
in the beginning that call the methods DisplayGlobalFields and
DisplayGlobalMethods.
The method
DisplayGlobalFields first checks whether they are global fields available. It
does this by setting start to the findex of the first TypeDef entity that is
module. Then it sets the startofnext to the second Typedef.
At times there is only a
single type and hence we then set startofnext to the length of the number of
rows in the Field table. If start and startofnext are the same, we have global
fields and therefore display some text and call the DisplayAllFields method
with the type row number as 1.
We do the same for the
DisplayGlobalMethods but now also set the spacesforrest at 2 for the rest of
the method that will follow the global methods. The setting of the variable has
no effect on the global methods as there is no global namespace for global
fields or methods and if we even add one, it gets removed.
Program48.csc
e.il
.assembly extern mscorlib
{
}
.mresource aa
{
.custom instance void
[mscorlib]a18::.ctor() = (65)
}
.assembly extern vijay
{
.custom instance void
[mscorlib]a15::.ctor() = (65)
}
.assembly e.dll
{
.custom instance void
[vijay]a14::.ctor() = (65)
}
.module extern kk.dll
.custom instance void
[vijay]a12::.ctor() = (65)
.module aa.dll
.custom instance void
[vijay]a7::.ctor() = (65)
.file aa
.custom instance void
[mscorlib]a177::.ctor() = (65)
.class extern xxx
{
.custom instance void
[mscorlib]a17::.ctor() = (65)
.file aa
}
.class uuu
{
.permissionset assert = (12
)
.custom instance void
[vijay]a8::.ctor() = (65)
}
.class zzz
{
.custom instance void
[vijay]a4::.ctor() = (65)
.field public int32 pqr
.custom instance void
[vijay]a2::.ctor() = (65)
.method public void
abc(int32 i , int32 j)
{
.param [1] = int8(10)
.custom instance void
[vijay]a5::.ctor() = (65)
}
.method public void abc1()
{
.custom instance void
[vijay]a1::.ctor() = (65)
}
/*
.method void xyz()
{
call void [vijay]yyy::pqr()
.custom instance void
[vijay]a6::.ctor() = (65)
}
*/
.property instance int32
aa()
{
.custom instance void
[vijay]a9::.ctor() = (65)
.get instance void
zzz::abc1()
}
.event [vijay]re a
{
.custom instance void
[vijay]a10::.ctor() = (65)
.addon instance void
zzz::abc1()
}
}
.class interface iii
{
.custom instance void
[vijay]a5::.ctor() = (65)
}
.custom ([vijay]a3) instance
void [vijay]a33::.ctor() = (65)
//.custom ([vijay]a6::abc())
instance void [vijay]a66::.ctor() = (65)
We have not made any major
additions to this program but simply added a series of DisplayCustomAttribute
methods calls at many places. Thus we will start with the il file and show you
code fragments of the functions where we have added the call of the method
DisplayCustomAttribute. We have also tried to show you the il output and the
method together. The first is the function abc where we move the method
CreateSignatures above the method DisplayModuleRefs as we would like all the
arrays to be filled up earlier.
public void abc(string []
args)
{
CreateSignatures();
DisplayModuleRefs ();
DisplayAssembleyRefs();
}
public void
DisplayAssembleyRefs ()
{
if (AssemblyRefStruct ==
null)
return;
for ( int i = 1 ; i <
AssemblyRefStruct.Length ; i++)
{
Console.WriteLine(".assembly
extern /*23{0}*/ {1}", i.ToString("X6") ,
NameReserved(GetString(AssemblyRefStruct[i].name)));
Console.WriteLine("{");
DisplayCustomAttribute
("AssemblyRef" , i , 2 );
.assembly extern
/*23000002*/ vijay
{
.custom /*0C00000A:0A000002*/ instance void [mscorlib/* 23000001
*/]a15/* 01000002 */::.ctor() /* 0A000002 */ = "e"
.ver 0:0:0:0
}
We have a custom attribute in the
AssemblyRef table and thus we add the method DisplayCustomAttribute just after
the { brace.
public void DisplayAssembley
()
{
if (AssemblyStruct.Length ==
1)
return;
Console.WriteLine(".assembly
/*20000001*/ {0}" , NameReserved(GetString(AssemblyStruct[1].name)));
Console.WriteLine("{");
DisplayCustomAttribute("Assembly"
, 1 , 2 + spacefornamespace);
.assembly /*20000001*/ e.dll
{
.custom /*0C000006:0A000003*/ instance void [vijay/* 23000002
*/]a14/* 01000003 */::.ctor() /* 0A000003 */ = "e"
.ver 0:0:0:0
}
The second place is the Assembly
table as this table has only one row we have used the row number as 1.
public void
DisplayClassExtern ()
{
Console.Write(".class
extern /*27{0}*/ {1}" , ii.ToString("X6") , ss1 );
Console.WriteLine(ss);
Console.WriteLine("{");
DisplayCustomAttribute("ExportedType"
, ii , 2);
}
.class extern /*27000001*/
xxx
{
.custom /*0C000007:0A000006*/ instance void [mscorlib/* 23000001
*/]a17/* 01000006 */::.ctor() /* 0A000006 */ = "e"
.file aa/*26000001*/
}
The third place is the Class
Extern directive or what we call the Exported Type.
public void DisplayModuleAndMore()
{
Console.WriteLine(".module
{0}" , NameReserved(GetString(ModuleStruct[1].Name)));
Console.Write("// MVID:
");
DisplayGuid(ModuleStruct[1].Mvid);
Console.WriteLine();
DisplayCustomAttribute("Module" , 1 , 0);
.module aa.dll
// MVID:
{AF4812DF-31BB-48D0-87D5-964909ECF751}
.custom
/*0C000003:0A000005*/ instance void [vijay/* 23000002 */]a7/* 01000005
*/::.ctor() /* 0A000005 */ = "e"
The fourth place is the Module
table and once again the row number is 1.
public void DisplayAllFields
(int typeindex)
{
Console.WriteLine("{0}"
, returnstring);
DisplayCustomAttribute("FieldDef"
, fieldindex , spacesfornested + spacesforrest);
}
}
.field /*04000001*/ public int32 pqr
.custom /*0C000001:0A000009*/ instance void [vijay/* 23000002
*/]a2/* 0100000A */::.ctor() /* 0A000009 */ = "e"
The fifth one is the attribute
with fields and we place it right at the end of the function.
Console.WriteLine("{");
DisplayCustomAttribute("Event"
, eventrow , spacesfornested + spacesforrest+2);
for ( int kk = 1 ; kk <
MethodSemanticsStruct.Length ; kk++)
{
.event /*14000001*/ [vijay/* 23000002 */]re/* 0100000E */
/*0100000E*/ a
{
.custom /*0C000005:0A00000D*/ instance void [vijay/* 23000002
*/]a10/* 0100000F */::.ctor() /* 0A00000D */ = "e"
.addon instance void zzz/* 02000003 */::abc1() /* 06000002 */
} // end of event zzz::a
With an event the attribute is
placed at the beginning like that of a
property.
public void
DisplayAllProperties (int typeindex)
{
DisplayCustomAttribute("Property"
, propertyrow , 2 + spacesforrest + spacesfornested);
for ( int kk = 1 ; kk <
MethodSemanticsStruct.Length ; kk++)
{
}
.property /*17000001*/ instance int32 aa()
{
.custom /*0C000004:0A00000C*/ instance void [vijay/* 23000002
*/]a9/* 0100000D */::.ctor() /* 0A00000C */ = "e"
.get /*06000002*/ instance void zzz/* 02000003 */::abc1() /*
06000002 */
} // end of property zzz::aa
public void DisplayEnd()
{
string nspace =
NameReserved(GetString(TypeDefStruct[TypeDefStruct.Length-1].nspace));
if ( ! placedend)
{
Console.WriteLine();
Console.WriteLine("//
=============================================================");
Console.WriteLine();
placedend = true;
}
if ( nspace == "")
DisplayCustomAttribute("TypeRef"
, 0 , 0);
.custom /*0C00000D*/
([vijay/* 23000002 */]a3/* 01000010 *//*01000010*/ ) instance void [vijay/*
23000002 */]a33/* 01000011 */::.ctor() /* 0A00000E */ = "e"
//*********** DISASSEMBLY
COMPLETE ***********************
We then need to add the TypeRef
custom attribute at the very end of the output and thus use the DisplayEnd if the namespace is null.
Also we need the TypeRef for the case where the namespace is not null.
public void
DisplayOneTypeDefEnd (int typeindex )
{
if ( nspace1 != nspace2 )
{
if ( lasttypedisplayed ==
typeindex && notprototype )
{
Console.WriteLine();
Console.WriteLine("//
=============================================================");
Console.WriteLine();
placedend = true;
DisplayCustomAttribute("TypeRef"
, 0 , 2);
}
}
public void DisplayFileTable
()
{
DisplayCustomAttribute
("File" , ii , 0);
}
}
.file /*26000001*/ aa
.custom
/*0C000007:0A000006*/ instance void [mscorlib/* 23000001 */]a177/* 01000006
*/::.ctor() /* 0A000006 */ = "e"
We have added the method
DisplayCustomAttribute at the end of the DisplayFileTable method.
Customs that did not happen
e.il
.assembly extern vijay
{
}
.mresource aa
{
.custom instance void
[vijay]a18::.ctor() = (65)
}
.class zzz
{
}
.mresource /*28000001*/ aa
{
// WARNING: managed resource file aa created
}
We have a custom attribute in the
mresource directive and ilasm just does not create an entry in the
CustomAttribute table for it. In the next series of examples we will manually
add a custom attribute and figure out how ildasm handles it.
e.il
.assembly extern vijay
{
}
.mresource aa
{
}
.class zzz
{
.custom instance void
[vijay]a18::.ctor() = (65)
}
We create a simple il file
with a custom attribute in a type and a single mresource directive. We print
out the initial bytes of the custom attribute table and search for these bytes
in a hex editor. We used Ultra Edit as explained earlier. In our case the first
six bytes of the CustomAttribute table are 43 0 B 0 5 0. The first two bytes are the parent
coded index. The first 5 bits of the first byte 43 are the table number. This
is 3 the table number of a TypeDef and the last 13 bits have a value of 2. This
is as the first TypeDef we create is number as the global type def is number 1.
We change the first byte to 32 as the first 5 bits are 18, the code for a
manifest resource and the last three bits taken by themselves are 1, to stand
for row number 1. When we run ildasm on e.dll, we get the following output.
.mresource /*28000001*/ aa
{
.custom /*0C000001:0A000001*/ instance void [vijay/* 23000001
*/]a18/* 01000002 */::.ctor() /* 0A000001 */ = "e"
// WARNING: managed resource file aa created
}
Thus ildasm recognizes a
custom attribute and ilasm does not. We have not added code to our mresource
directive to display a custom attribute.
.assembly extern vijay
{
}
.module extern aa.dll
.custom instance void
[vijay]a18::.ctor() = (65)
.class zzz
{
}
We have added a custom
attribute to the module extern directive and ilasm ignores it. We once again
get back to the earlier program and now change the first byte from 43 to 2C as
we want the first 5 bits to be 12 the code for a module ref table and the last
three bits to be 1.
.module extern aa.dll
/*1A000001*/
.custom
/*0C000001:0A000001*/ instance void [vijay/* 23000001 */]a18/* 01000002
*/::.ctor() /* 0A000001 */ = "e"
When we run ildasm on e.dll,
we see the custom attribute below the module extern directive.
.assembly extern vijay
{
}
.class zzz
{
.permissionset assert = ( 41
42)
.custom instance void
[vijay]a18::.ctor() = (65)
}
We have added the
.permissionset directive and a custom attribute below it. Ilasm instead of
associating the custom directive with the Decl security associates it with the
type. We once again change the first byte to 28 as 8 is the number for
security. When we run ildasm we get the following output.
.permissionset assert = (41 42 ) // AB
.custom /*0C000001:0A000001*/ instance void [vijay/* 23000001
*/]a18/* 01000002 */::.ctor() /* 0A000001 */ = "e"
Once again ildasm passes
where ilasm fails.
.assembly extern vijay
{
}
.class zzz implements iii
{
}
.class interface iii
{
}
.class yyy
{
.custom instance void
[vijay]a18::.ctor() = (65)
}
In this case the first byte is 83 as the TypeDef row is now 3
and not 2. We change it to 25 as we want row 1 and 5 is the code for the
interface implementation. In this case the custom attribute comes at the bottom
as.
.custom /*0C000001*/
(UNKNOWN_OWNER/*09000001*/ ) instance void [vijay/* 23000001 */]a18/* 01000003
*/::.ctor() /* 0A000001 */ = "e"
The InterfaceImpl table has
a number of 9. We have three more MemberRef, TypeSpec and StandAloneSig that we
will do later.
Program49.csc
Public void
DisplayAllMethods(int typeindex)
{
string vtentrystring =
GetVtentryString(methodindex);
methodstring =
vtentrystring;
methodstring = methodstring
+ CreateSpaces(spacesforrest + 2 + spacesfornested) + "// Method begins at RVA 0x" +
MethodStruct[methodindex].rva.ToString("x4");
Console.Write(methodstring);
long fileoffset =
ConvertRVA(MethodStruct[methodindex].rva);
mfilestream.Seek(fileoffset
, SeekOrigin.Begin);
DisplayFatFormat
(NameReserved(GetString(TypeDefStruct[typeindex].name)) ,
NameReserved(GetString(MethodStruct[methodindex].name)) , methodindex );
}
}
public void
DisplayFatFormat(string classname , string methodname , int methodindex)
{
DisplayInitialMethodHeader(methodindex);
DisplayMethodILCode(classname
, methodname , methodindex);
}
public void
DisplayInitialMethodHeader (int methodindex)
{
int tiny =
mbinaryreader.ReadByte();
if ( (tiny & 0x03) ==
0x03)
tinyformat = false;
else
{
Console.WriteLine(".....{0}"
, tiny);
tinyformat = true;
}
mfilestream.Seek(-1,SeekOrigin.Current);
string methodstring =
"";
if ( !tinyformat )
{
long where =
mfilestream.Position;
short first =
mbinaryreader.ReadInt16();
first12 = (short)(first
& 0x0fff);
short stacksize =
mbinaryreader.ReadInt16();
codesize =
mbinaryreader.ReadInt32();
methodstring = "\r\n" +
CreateSpaces(spacesforrest + 2 + spacesfornested) + "// Code size " + codesize.ToString() + "
(0x" + codesize.ToString("x") + ")\r\n";
methodstring = methodstring
+ CreateSpaces(spacesforrest + 2+ spacesfornested) + ".maxstack " + stacksize.ToString();
int standalonesig =
mbinaryreader.ReadInt32();
int rowstandalonesig =
standalonesig & 0x00ffffff;
Console.WriteLine(".....{0}"
, standalonesig.ToString("X8"));
if (StandAloneSigStruct !=
null && standalonesigarray == null)
standalonesigarray = new
string[StandAloneSigStruct.Length];
if ( rowstandalonesig != 0 )
{
CreateSignatureForEachType
(5 , StandAloneSigStruct[rowstandalonesig ].index , rowstandalonesig);
if (standalonesigarray
[rowstandalonesig ] != "")
{
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest + 2+ spacesfornested) + ".locals /*11" +
rowstandalonesig.ToString("X6") + "*/ ";
if ((first12&0x10) == 0x10
)
methodstring = methodstring
+ "init (" ;
else
methodstring = methodstring
+ "(" ;
methodstring = methodstring
+ standalonesigarray[rowstandalonesig ];
methodstring = methodstring
+ ")";
}
}
}
else
{
mbinaryreader.ReadByte();
codesize = tiny >> 2;
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest + 2 + spacesfornested ) + "// Code size " + codesize.ToString() + " (0x" + codesize.ToString("x")
+ ")";
if (codesize != 0)
methodstring = methodstring
+ "\r\n" + CreateSpaces(spacesforrest + 2+ spacesfornested) + ".maxstack 8" ;
}
Console.WriteLine(methodstring);
}
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 == 5)
CreateLocalVarSignature(blob1
, row);
if ( type == 1)
CreateMethodDefSignature(blob1
, row);
if ( type == 2)
CreateMethodRefSignature(blob1
, row);
}
public void
CreateLocalVarSignature (byte [] blobarray , int row)
{
int index = 0;
standalonesigarray[row] =
"";
if ( blobarray[index] !=
0x07)
return;
index++;
int
howmanybytes,uncompressedbyte ;
howmanybytes =
CorSigUncompressData(blobarray , index , out uncompressedbyte);
index = index +
howmanybytes;
string returnstring =
"";
for ( int l = 1 ; l <= uncompressedbyte
; l++)
{
string typestring =
GetElementType(index , blobarray , out
howmanybytes , 0 , "");
typestring =
typestring.Replace("^",",");
int variableVindex = l - 1 ;
returnstring = returnstring
+ typestring + " V_" +
variableVindex.ToString() ;
if ( l != uncompressedbyte)
returnstring = returnstring
+ ",\r\n" + CreateSpaces(spacesforrest + 2 + spacesfornested) +
CreateSpaces(9);
index = index +
howmanybytes;
}
standalonesigarray [row] =
returnstring;
}
public void
DisplayMethodILCode ( string classname , string methodname ,int methodindex)
{
Console.Write(CreateSpaces(spacesforrest+
spacesfornested));
Console.Write("}");
if (GetTypeForMethod
(methodindex)== 1)
Console.WriteLine(" //
end of global method {0}\r\n" , methodname);
else
Console.WriteLine(" //
end of method {0}::{1}\r\n" , classname , methodname);
}
string []
standalonesigarray;
int codesize ;
bool tinyformat;
int first12;
}
e.il
.class zzz
{
.method void xyz()
{
}
.method void xyz()
{
.locals ( int32 V_1 , int8
j)
.maxstack 2
ret
}
.method void xyz()
{
.locals init ( int8 j)
ret
}
}
.method /*06000001*/ privatescope instance void
xyz$PST06000001() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2050.....2
// Code size 0
(0x0)
} // end of method zzz::xyz
.method /*06000002*/ privatescope instance void
xyz$PST06000002() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2054.....11000001
// Code size 1
(0x1)
.maxstack 2
.locals /*11000001*/ (int32 V_0,
int8 V_1)
} // end of method zzz::xyz
.method /*06000003*/ privatescope instance void
xyz$PST06000003() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2064.....11000002
// Code size 1
(0x1)
.maxstack 8
.locals /*11000002*/ init (int8 V_0)
} // end of method zzz::xyz
Now is when the fun starts
as we are actually moving to display or disassemble all the il code in a
method. We first start off with the DisplayAllMethods methods where for the
last time we make any changes. We replace the last lines with the above lines.
The Method array has a field rva that tells us where the code for this method
is to be found in memory.
We use the ConvertRVA method
to tell us where this location is on disk. We then use the Seek function to
move the file pointer to this position and call a method named DisplayFatFormat
to write out all the il code for us. We pass this method the typename, the
method name and the method index.
When we arrive at this
function, we find that we are calling two more functions, so lets look at the
first DisplayInitialMethodHeader which is simply passed the methodindex
variable. The first thing we do is read the first byte of the method structure.
This is the byte that tells us what is the type of method structure, tiny or
large.
Actually we only need to
look at the first bits of the first byte. If its value is 2, then method header
is tiny or else for the large type is 3. As there is no third type, we get away
with a simple if and else. This distinction is made mainly depending upon the
size of code.
We have a instance variable
tinyformat that is set to true if the first three bits are not 3. Now that we
have read the first byte, we need to move the file pointer back one and then
reread the bytes again depending upon whether it is a tiny or large format.
Thus the method
DisplayFatFormat should have be renamed DisplayFatAndTinyFormat as it reads
both. Lets start with the more complex format the fat one. This is normally the
default format and applies to methods where the code size is larger than 5 bits
or larger than 32 bytes.
Also methods that have
exceptions, local variables, extra data sections and the operand stack needs to
handle more than 8 entities all need the
fat format. As we said before the tint format is foe the methods that really do
nothing. We store in the variable where the current position of the file
pointer that we will use later.
We also re-read the first
short whose three bytes told us the type of format. We need only the first 12
bits and thus we mask off the last four bits. The top most four bits gives us a
size of the header that we will see later. Remember these 12 bits are called
flags even though we have only 4 flags of which the tiny and fat have already
been done.
Thus we have two flags only
to decipher. This flags is followed by a short that tells us the size of the
stack. Then we have a int that is the size of the code and we immediately first
add the Code Size to the methodstring variable. This is followed by the size of
the stack. The codesize is not a directive but maxstack is.
Then a method may have
variables that are local. These are enclosed within the .locals directive and
have the same format as parameters, but they cannot have names like parameters
but computer generated names beginning with V_ and the local variable ordinal
number. The last int gives us a token which tells us where the local variables
can be found.
For those of you with a
short memory, the top byte of a token is the table number and the remaining
three bytes the row number. As we have displayed the signature token, the first
byte will be 11 the stand alone signature table number. If this row number is
zero, then we know that the method has no local variables.
Earlier we made a simple
mistake. Before our program started displaying stuff we calculated all the
method signatures in the CreateSignatures method. Now that way was written in
stone and hence he have used another way. We first check whether the array
StandAloneSigStruct is not null and our standalonesigarray string array is also
not null.
The first check is to make
sure that we have some method that has local variables and the second to make
sure that we create our string array standalonesigarray only once.
Then we check whether this
method has local variables and if yes, we call the method
CreateSignatureForEachType with a number 5, the type for stand alone signatures
and then followed by the blob array offset and the StandAloneSig table row
number rowstandalonesig to set the standalonesigarray array member to.
This simply sets one member
of the array to a signature. We will come to the CreateSignatureForEachType
method a little later. Now if the corresponding standalonesigarray array member
is not null we write out the directive locals along with the signature.
The locals directive has one
more keyword init that will call the default constructors for all the local
variables. This flags has a value of 0x10 and we then write out the open and
close brackets of the locals directive. This completes the fat format the tiny
format is really very simple.
We read the first byte and
then right shift it by two to remove the first two bits that were the flags.
This now give us the size of the code that we write out and if the code size is
not zero, the size of the stack is 8. This completes the initial reading of the
method header.
In the
CreateSignatureForEachType method, we simply call the method
CreateLocalVarSignature. This is once again a signature that is easy for us as
the first byte is always 7. The next byte is the count of the number of local
variables that make up the signature. This is followed by the individual local
variable data types that we pick up using the GetElementType method.
We also replace the ^ with
the comma for arrays. We then need to write out the name of the variable and
hence start with V_ and the loop variable l minus 1 as the count starts with 0.
We then need to place a comma, followed with a enter as each of these variables
needs to be on a new line with the right amount of spaces.
Thus this code cannot be
called for the earlier signatures as then we do not have the value of the
variables spacesfornested and spacesforrest. We finally set the
standalonesigarray array with the returnstring variable.
We have the method
DisplayMethodILCode that only displays some spaces and then depending upon
whether it is a global method or not displays the end of the method. We could
have used the method IsGlobalMethod instead of GetTypeForMethod at all times.
Now coming to the IL code
generated by our program, we have the first function show us that it has the
tiny format as the else is true and the firstbyte has a value of 2 which also
gives the code a size of zero.
For the second two methods,
the local var token starts with 0x11 and then we have the row numbers in the
StandAloneSig table and not the method table. If we specify a maxstack, then
ours gets used or else a default value of 8 is taken. Also for the tint format
the maxstack has no place where it can be stored.
If we make the mistake of
giving a local variable a name, ilasm removes the name and we then have to
generate a standard name for it.
Program50.csc
public void
DisplayMethodILCode ( string classname , string methodname ,int methodindex)
{
byte [] codearray = new
byte[codesize];
for ( int i = 1 ; i <=
codesize ; i++)
{
codearray[i-1] =
mbinaryreader.ReadByte();
Console.Write("
{0}" , codearray[i-1].ToString("X") );
}
Console.WriteLine();
Console.Write(CreateSpaces(spacesforrest+
spacesfornested));
Console.Write("}");
}
e.il
.class zzz
{
.method void xyz()
{
.locals ( int32 V_1 , int8
j)
.maxstack 2
ret
nop
ret
nop
ret
}
}
.method /*06000001*/
privatescope instance void
xyz$PST06000001() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2050
// Code size 5
(0x5)
.maxstack 2
.locals /*11000001*/ (int32 V_0,
int8 V_1)
2A 0 2A 0 2A
} // end of method zzz::xyz
Now is when we start out
book. It is in the method DisplayMethodILCode that we get our hands on the il
code. This il code is available just after the initial method structure. We need to store this code in an array
codearray that we initialize to the size of the code that we have stored in the
instance variable codesize.
We read each byte into the
array and then simply display the members of the array. What we see is the ret instruction that must
be present as the last instruction of every method has a value of 0x2a. The nop
instruction that behaves like most of the world has a value of zero as it is
supposed to do nothing.
Thus after the method
structure we have a series of bytes that we will have to decipher into il code.
The next program does just that.
Program51.csc
using System.Reflection;
using
System.Reflection.Emit;
public void abc(string []
args)
{
ReadPEStructures(args);
DisplayPEStructures();
ReadandDisplayImportAdressTable();
ReadandDisplayCLRHeader();
ReadStreamsData();
FillTableSizes();
ReadTablesIntoStructures();
DisplayTablesForDebugging();
ReadandDisplayVTableFixup();
ReadandDisplayExportAddressTableJumps();
FillArray();
FillOpCodeArray ();
CreateSignatures();
}
OpCode [] OpCodesArray;
OpCode []
OpCodesArray1;
public void
FillOpCodeArray()
{
OpCodesArray = new
OpCode[66000];
OpCodesArray1 = new
OpCode[32];
FieldInfo[] fields =
typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (FieldInfo f in
fields)
{
OpCode o = (OpCode) f.GetValue(null);
if ( o.Value <= 255
&& o.Value >= 0)
OpCodesArray[(short)o.Value]
= o;
}
}
public void
DisplayMethodILCode ( string classname , string methodname ,int methodindex)
{
byte []codearray = new
byte[codesize];
for ( int i = 1 ; i <=
codesize ; i++)
codearray[i-1] =
mbinaryreader.ReadByte();
for ( int arrayoffset = 0 ;
arrayoffset < codesize ; )
{
int instructionbyte = codearray[arrayoffset];
OpCode opcode;
opcode = OpCodesArray
[instructionbyte];
string strings =
CreateSpaces(spacesforrest+2+ spacesfornested);
strings = strings +
"IL_" + arrayoffset .ToString("x4") + ": /* " +
opcode.Value.ToString("X2") + " | ";
int
sizeofinstructionanddata =
DecodeILInstrcution2( opcode , arrayoffset
, codearray , methodindex , strings);
Console.WriteLine();
arrayoffset = arrayoffset + sizeofinstructionanddata;
}
Console.Write(CreateSpaces(spacesforrest+
spacesfornested));
Console.Write("}");
if (
GetTypeForMethod(methodindex)== 1)
Console.WriteLine(" //
end of global method {0}\r\n" , methodname);
else
Console.WriteLine(" //
end of method {0}::{1}\r\n" , classname , methodname);
}
public int
DecodeILInstrcution2 (OpCode opcode , int codeindex , byte [] codearray , int
methodindex , string strings)
{
int sizeofinstructiondata =
0;
if ( opcode.OperandType ==
OperandType.InlineNone)
{
Console.Write(strings);
Console.Write("{0}*/
{1}",CreateSpaces(17) ,
opcode.Name);
if ( opcode.Name ==
"ret" && codeindex != (codearray.Length -1) )
Console.WriteLine();
if ( opcode.Name ==
"throw" && codeindex != (codearray.Length -1) )
Console.WriteLine();
sizeofinstructiondata =1;
}
return
sizeofinstructiondata;
}
}
e.il
.class zzz
{
.method void xyz()
{
.locals ( int32 V_1 , int8
j)
.maxstack 2
sub
ret
add
mul
nop
ret
}
}
.method /*06000001*/ privatescope instance void
xyz$PST06000001() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2050
// Code size 6
(0x6)
.maxstack 2
.locals /*11000001*/ (int32 V_0,
int8 V_1)
IL_0000: /* 59 | */ sub
IL_0001: /* 2A
| */ ret
IL_0002: /* 58 | */ add
IL_0003: /* 5A | */ mul
IL_0004: /* 00 | */ nop
IL_0005: /* 2A | */ ret
} // end of method zzz::xyz
Finally we have some il code
disassembled. When you look at the il file, we have a series of il instructions
popularly called opcodes. We have used
ones like ret and nop earlier and now add, mul etc. These opcodes have one
thing in common, they all take no parameters. The il world has divided the il
instructions into a family depending upon the parameters or operands that they
take.
All il instructions that
have the same operand type behave in a similar manner. The il world has also
given us a enum called OperandType that tells us how many different types of
operands a il instruction takes. What we now need to do is handle each operand
type.
What we cannot do is tell
ourselves that we have a series of if statements that takes each opcode and
figures out what the name of the opcode is and what is its operand type. We
need a more generic way of handling the above problem. Lets take a look at
method abc first.
We have introduced a new
method FillOpCodeArray just before we create the method signatures. We have
also defined two instance arrays OpCodesArray and OpCodesArray1 of type OpCode.
These classes are from the System.Reflection namespace. The OpCode structure
represents a Il instruction.
It has properties like Name
that tells us the name of the Il instruction, OperandType that tell us the
Operand types that this instruction takes and Value that tell us the byte value
or number of the il instruction. For example the above program showed us that
0x2a is the value of the ret instruction, and 0 for nop.
Thus we now need fill up the
OpCodesArray with a valid OpCode object that represents each opcode. As 0x2A
represents a ret instruction, the 2a member of the OpCodesArray array should
stand for the ret instruction. In the method FillOpCodeArray, this is what we
do.
We first initialize the
OpCodesArray array to a very large value of members 66000 and the second array
to 32 members only. This second array will be used later. The foreach loop lets
us iterate through an object. The FieldInfo class simply provides access to the
metadata for field attributes.
The GetFields function which
is part of the Type class gets us all the fields from the Type that calls it.
In this case we require all the fields from the class OpCodes. Every valid
opcode in the IL instruction set is a field in the OpCode class. Thus if we
have 500 il instructions we will have 500 fields in the OpCode class.
These fields are static and
read only. To be specific, the OpCode class has 221 fields in all. This way we
can figure out all the fields that a certain class has. The parameter to the
GetFields function allows us to get at those fields that meet certain
conditions like they must be static or public etc.
Now that we have all the IL
instructions with us in the FieldInfo array we simply use the GetValue method
to give us the actual field. The array on the left is type OpCode and GetValue
returns an object and hence the cast. Thus variable o now is the OpCode object
where o.Name is the name of the OpCode say ret and o.Value is its byte value
0x2a.
Now all that we have to do
is initialize the 2a member of the array with o which is what we do in the next
line. We only take those opcodes that lie between 0 and 255 only. The other
cases we will handle differently a little later. Thus we now have an array with
all the opcode values, name and types of parameters.
Lets now move on to the
method DisplayMethodILCode which first reads all the byte codes into the
codearray array. Then we use another for loop to iterate through this array. We
first pick up the first or zeroth byte from the codearray using the loop
variable arrayoffset.
We know that this byte is an
opcode value and not data and thus pick up the relevant OpCode structure that
represents it from the OpCodesArray array. Thus if variable instructionbyte had
a value 0x2a, then opcode would stand for the OpCode for the ret instruction.
We write out the initial
spaces as always and then the words IL_ followed by the instruction offset
within the code bytes. This is represented by the loop variable arrayoffset. We
then have to add a colon some spaces and then in comments the actual opcode
value, 2a for a ret.
Then some more spaces and a
or sign | . Some instructions write some data after the or sign. Thus what we
have done so far is what is the same for each instruction. We now call the
method DecodeILInstrcution2 to write out the actual instruction for us.
We pass it the OpCode
representing the instruction, the array offset from the beginning of the first
byte of code arrayoffset, the entire codearray of all the bytes, the
methodindex of the method and finally the initial string that we have just
created of the instruction stored in the variables strings.
This function after writing
out the entire single instruction, will then return a answer that will give us
how many bytes this instruction has taken. This is important as the il
instructions take up a variable number of bytes.
We add this return value to
arrayoffset so that the
arrayoffset variable points to the next
il instruction at the start of the loop. Thus all our focus from now on will be
on the DecodeILInstrcution2 method. Lets move there now.
In this method we have a
series of if statements that handle each operand type. The first one we check
for is InLineNone where we have no operands at all. Thus we use the parameter
opcode and check whether its field OperandType equals the value of the enum
OperandType InLineLine.
If we find a match we first
write out the initial bytes that we are passed and then the name of the opcode.
The name does not have to ret as there are a hundred opcodes that have a type
InLineNone. This is why we simply use the Name property and we do have to worry
about the name of the OpCode.
This way we do have a
hundred if statements that check each opcode value and write out a name. The
only problem is that only if the opcode name is ret or throw and we are not the
last instruction, we should write out a enter. Thus if a ret or throw is the
last instruction, a extra enter will now be written out, else it will be.
Also the variable
sizeofinstructiondata will return the number of bytes taken up by this
instruction type and for no operands the value is obviously one. This way we
will handle each Operand Type. Lets sum up what we have done so far. As there
are far too many IL instructions for our liking, we create an array of OpCode
structures that will handle each IL instruction. What we need to know is the
name and operand types that this IL instruction will take. We read each byte
from the codesize array and then read the corresponding OpCode from the
OpCodesArray array. Then we figure out its operand type and handle each operand
type differently. Lets see it work with the next example.
e.il
.class zzz
{
.method void xyz()
{
arglist
}
}
.method /*06000001*/ privatescope instance void
xyz$PST06000001() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2050
// Code size 2
(0x2)
.maxstack 8
IL_0000: /* FE | */ prefix1
IL_0001: /* 00 | */ nop
} // end of method zzz::xyz
Things just do not cut it.
We have used a simple IL instruction arglist and for some reason we get two
instructions. Why. The guys who designed the IL instruction set toke some inspiration
from the Java guys who had the concept of a byte code which said that every
instruction should not be larger than one byte or have a value ranging from 0
to 255.
Unfortunately there are too
many IL instructions and hence they
designed a different way of
giving them values. The first 225 or 0 to 224 IL opcodes have a direct value.
From now on things change. The arglist has a value FE 00 as FE stands for
Prefix 1 and there are seven such prefixes.
Thus we first need to check
the first byte and if it is larger than F8 we have a prefix byte. FF is the
last single byte instruction and is called the prefixref. Thus for two byte
instructions we have to ignore the first byte and simply read the second. Thus
we have to make modifications in our program to handle two byte instructions.
Program52.csc
public void
FillOpCodeArray()
{
OpCodesArray = new
OpCode[256];
OpCodesArray1 = new
OpCode[32];
FieldInfo[] fields =
typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (FieldInfo f in
fields)
{
OpCode o = (OpCode)
f.GetValue(null);
if ( o.Value <= 255
&& o.Value >= 0)
OpCodesArray[(short)o.Value]
= o;
OpCodesArray1[(ushort) 0x00]
= OpCodes.Arglist;
OpCodesArray1[(ushort) 0x01]
= OpCodes.Ceq;
OpCodesArray1[(ushort) 0x02]
= OpCodes.Cgt;
OpCodesArray1[(ushort) 0x03]
= OpCodes.Cgt_Un;
OpCodesArray1[(ushort) 0x04]
= OpCodes.Clt;
OpCodesArray1[(ushort) 0x05]
= OpCodes.Clt_Un;
OpCodesArray1[(ushort) 0x06]
= OpCodes.Ldftn;
OpCodesArray1[(ushort) 0x07]
= OpCodes.Ldvirtftn;
OpCodesArray1[(ushort) 0x09]
= OpCodes.Ldarg;
OpCodesArray1[(ushort) 0x0A]
= OpCodes.Ldarga;
OpCodesArray1[(ushort) 0x0B]
= OpCodes.Starg;
OpCodesArray1[(ushort) 0x0C]
= OpCodes.Ldloc;
OpCodesArray1[(ushort) 0x0D]
= OpCodes.Ldloca;
OpCodesArray1[(ushort) 0x0E]
= OpCodes.Stloc;
OpCodesArray1[(ushort) 0x0F]
= OpCodes.Localloc;
OpCodesArray1[(ushort) 0x11]
= OpCodes.Endfilter;
OpCodesArray1[(ushort) 0x12]
= OpCodes.Unaligned;
OpCodesArray1[(ushort) 0x13]
= OpCodes.Volatile;
OpCodesArray1[(ushort) 0x14]
= OpCodes.Tailcall;
OpCodesArray1[(ushort) 0x15]
= OpCodes.Initobj;
OpCodesArray1[(ushort) 0x17]
= OpCodes.Cpblk;
OpCodesArray1[(ushort) 0x18]
= OpCodes.Initblk;
OpCodesArray1[(ushort) 0x1A]
= OpCodes.Rethrow;
OpCodesArray1[(ushort) 0x1C]
= OpCodes.Sizeof;
OpCodesArray1[(ushort) 0x1D]
= OpCodes.Refanytype;
}
}
public void
DisplayMethodILCode ( string classname , string methodname ,int methodindex)
{
byte [] codearray = new
byte[codesize];
for ( int i = 1 ; i <=
codesize ; i++)
codearray[i-1] =
mbinaryreader.ReadByte();
for ( int arrayoffset = 0 ;
arrayoffset < codesize ; )
{
int instructionbyte = codearray[arrayoffset ];
OpCode opcode;
string strings =
"";
if ( instructionbyte ==
0xFE)
{
instructionbyte = codearray[arrayoffset
+1];
opcode = OpCodesArray1
[instructionbyte] ;
strings =
CreateSpaces(spacesforrest+2+ spacesfornested);
strings = strings +
"IL_" + arrayoffset.ToString("x4") + ": /* " +
opcode.Value.ToString("X") + " | ";
arrayoffset = arrayoffset + 1;
}
else
{
opcode =
OpCodesArray[instructionbyte];
strings =
CreateSpaces(spacesforrest+2+ spacesfornested);
strings = strings +
"IL_" + arrayoffset .ToString("x4") + ": /* " +
opcode.Value.ToString("X2") + " | ";
}
int
sizeofinstructionanddata =
DecodeILInstrcution2 ( opcode , arrayoffset
, codearray , methodindex , strings);
Console.WriteLine();
arrayoffset = arrayoffset + sizeofinstructionanddata;
}
Console.Write(CreateSpaces(spacesforrest+
spacesfornested));
Console.Write("}");
if ( GetTypeForMethod(methodindex)==
1)
Console.WriteLine(" //
end of global method {0}\r\n" , methodname);
else
Console.WriteLine(" //
end of method {0}::{1}\r\n" , classname , methodname);
}
e.il
.class zzz
{
.method void xyz()
{
ret
arglist
}
}
.method /*06000001*/ privatescope instance void
xyz$PST06000001() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2050
// Code size 3
(0x3)
.maxstack 8
IL_0000: /* 2A | */ ret
IL_0001: /* FE00 | */ arglist
} // end of method zzz::xyz
Lets start with the method
FillOpCodeArray. Here we have reduced the size of the first array to a
reasonable 256 as the one byte opcodes will not exceed 255. Thus the array
OpCodesArray will store for us all the single byte opcodes.
Then we have a second array
OpCodesArray1 that will store the two byte opcodes. These unfortunately have to
be keyed in manually and they are 30 of them. Thus we will now have to search
in two arrays for our opcode. This is what we do in the method
DisplayMethodILCode.
We read the first byte from
the codearray and then check if it is the prefix1 opcode. If it is, we then
pick up the second or the next byte and use this as an offset into the
OpCodesArray1 array to give us the right opcode.
The key point is that we are
reading the second byte and then picking the opcode from the second and not the
first opcode array. We then set the strings variable to the initial number of
spaces as well as the initial parts of the Il instruction which is the IL_ and
the line number stored in the arrayoffset variable.
We stop at the or sign as
before. As we are dealing with a 2 byte instruction, the arrayoffset variable
is increased by 1. If it is a one byte
instruction, we do what we did earlier.
Thus when we call the method
DecodeILInstrcution2, it does not care whether it is a one byte or two byte
instruction set. The strings variable has a extra space for a one byte
instruction set.
Finally we will list out all
the InLineNone operand types or those that take up no parameters. We are
decoding instructions depending upon the types of parameters they take. They
are 146 instructions that take no operand. These are nop , break , ldarg.0 ,
ldarg.1 , ldarg.2 , ldarg.3 , ldloc.0 , ldloc.1 , ldloc.2 , ldloc.3 , stloc.0 ,
stloc.1 , stloc.2 , stloc.3 , ldnull , ldc.i4.m1 , ldc.i4.0 , ldc.i4.1 ,
ldc.i4.2 , ldc.i4.3 , ldc.i4.4 , ldc.i4.5 , ldc.i4.6 , ldc.i4.7 , ldc.i4.8 ,
dup , pop , ret , ldind.i1 , ldind.u1 , ldind.i2 , ldind.u2 , ldind.i4 , ldind.u4
, ldind.i8 , ldind.i , ldind.r4 , ldind.r8 , ldind.ref , stind.ref , stind.i1 ,
stind.i2 , stind.i4 , stind.i8 , stind.r4 , stind.r8 , add , sub , mul , div ,
div.un , rem , rem.un , and , or , xor , shl , shr , shr.un , neg , not ,
conv.i1 , conv.i2 , conv.i4 , conv.i8 , conv.r4 , conv.r8 , conv.u4 , conv.u8 ,
conv.r.un , throw , conv.ovf.i1.un , conv.ovf.i2.un , conv.ovf.i4.un ,
conv.ovf.i8.un , conv.ovf.u1.un , conv.ovf.u2.un , conv.ovf.u4.un ,
conv.ovf.u8.un , conv.ovf.i.un , conv.ovf.u.un , ldlen , ldelem.i1 , ldelem.u1
, ldelem.i2 , ldelem.u2 , ldelem.i4 , ldelem.u4 , ldelem.i8 , ldelem.i ,
ldelem.r4 , ldelem.r8 , ldelem.ref , stelem.i , stelem.i1 , stelem.i2 ,
stelem.i4 , stelem.i8 , stelem.r4 , stelem.r8 , stelem.ref , conv.ovf.i1 ,
conv.ovf.u1 , conv.ovf.i2 , conv.ovf.u2 , conv.ovf.i4 , conv.ovf.u4 ,
conv.ovf.i8 , conv.ovf.u8 , ckfinite , conv.u2 , conv.u1 , conv.i , conv.ovf.i
, conv.ovf.u , add.ovf , add.ovf.un , mul.ovf , mul.ovf.un , sub.ovf ,
sub.ovf.un , endfinally , stind.i , conv.u , prefix7 , prefix6 , prefix5 ,
prefix4 , prefix3 , prefix2 , prefix1 , prefixref , arglist , ceq , cgt ,
cgt.un , clt , clt.un , localloc , endfilter , volatile. , tail. , cpblk ,
initblk , rethrow , refanytype. This is just for completeness.
Program53.csc
public int
DecodeILInstrcution2 (OpCode opcode , int codeindex , byte [] codearray , int
methodindex , string strings)
{
int sizeofinstructiondata =
0;
if ( opcode.OperandType ==
OperandType.InlineI)
{
Console.Write(strings);
int token = BitConverter.ToInt32(
codearray , codeindex + 1 );
byte b1 =
codearray[codeindex+1];
byte b2 =
codearray[codeindex+2];
byte b3 =
codearray[codeindex+3];
byte b4 =
codearray[codeindex+4];
Console.Write("{0}{1}{2}{3}",b1.ToString("X2"),b2.ToString("X2"),b3.ToString("X2"),b4.ToString("X2"));
Console.Write(CreateSpaces(8))