The
above example is to illustrate a simple point. The assembler does not do
extensive error checks. We have two files, p2.il and p3.il that contain the
same class xxx and function xyz. In the file p1.il, we have the same .class
extern twice and we get no error. The second class extern refers to a file
p3.dll that does not have a file directive at the top level.
No errors at all at assemble
time which means that the assembler ignores our indiscretions. Also at runtime
the xyz method gets called from p2.dll. Change the order of the class externs
and watch the assembler complain. Remember the old adage buy at your own risk
applies here also. No helping hand from the assembler. No hand holding also.
A assembly and a module lets
us group constructs together and each play a different role in the .Net world.
A set or a group of files is what we call an assembly. It is the abstract
entity that we call a manifest that keeps track of all the files present.
The assembly table only
store for us the Version, name, culture and security requirements that we have
not touched upon yet. The assembly manifest must keep track of not only the
files but also the cryptographic hash of each file. The manifest is computed
from the metadata as mentioned earlier.
The runtime also needs to
know which types are defined by other files and can be exported out of this
assembly. This is achieved using the class extern directive.
Obviously if the type is
defined in the same file that has the assembly directive, its attributes like
public, private etc decide whether it
can be exported from this assembly or not. We could use digital signatures and
a public key to compute it with the Manifest.
The problem with digital
signatures is that for some reason it did not set the Thames on fire. The major
difference between assemblies and modules is that assemblies comprise modules.
A module consists of a single file that adheres to the rules of the .net world.
It can either be a dll or a exe file, it must contain executable code.
There is one file in the
list of files that carries the assembly directive and it is this file that
gives us a list of other modules that make up the assembly. The file that
carries the assembly directive is also a module. If the assembly is a dll, then
we do not need a function that has the entrypoint directive.
However if it is a exe file,
then the file that contains the entrypoint directive should also contain the
assembly directive or manifest. The concept of namespaces may exist in
programming language, the CLI does not understand what we are talking about.
Type names are always
specified using the full name which is relative to the assembly that we have
created them in. If the file is say a text file or a bmp file or a video file
and does not contain metadata, will not have the module directive. It is
normally not a common practice that many assemblies can refer to the same
module but nothing stops us from doing so.
The advantage is that once a
dll is loaded into memory, the next assembly that references the dll will not
result in the dll being loaded again in memory.
Program13.csc
public void abc(string []
args)
{
ReadPEStructures(args);
DisplayPEStructures();
ReadandDisplayImportAdressTable();
ReadandDisplayCLRHeader();
ReadStreamsData();
FillTableSizes();
ReadTablesIntoStructures();
DisplayTablesForDebugging();
ReadandDisplayVTableFixup();
ReadandDisplayExportAddressTableJumps();
DisplayModuleRefs();
DisplayAssembleyRefs();
DisplayAssembley();
DisplayFileTable();
DisplayClassExtern();
}
public void DisplayClassExtern
()
{
if (ExportedTypeStruct ==
null)
return;
for ( int ii = 1 ; ii <
ExportedTypeStruct.Length ; ii++)
{
string ss1 =
GetTypeAttributeFlagsForClassExtern(ExportedTypeStruct[ii].flags );
string ss =
GetString(ExportedTypeStruct[ii].nspace) ;
if ( ss.Length != 0)
ss = ss + ".";
ss = ss +
NameReserved(GetString(ExportedTypeStruct[ii].name));
int table =
ExportedTypeStruct[ii].coded & 0x03;
int index =
ExportedTypeStruct[ii].coded >> 2;
if ( index != 0)
{
Console.Write(".class
extern /*27{0}*/ {1}" , ii.ToString("X6") , ss1 );
Console.WriteLine(ss);
Console.WriteLine("{");
if ( table == 0)
{
Console.WriteLine(" .file {0}/*26{1}*/ " ,
NameReserved(GetString(FileStruct[index].name)) ,
index.ToString("X6"));
}
if ( table == 2)
{
Console.WriteLine(" .comtype '{0}' /*27{1}*/ " ,
NameReserved(GetString(ExportedTypeStruct[index].name)) ,
index.ToString("X6"));
}
if (
ExportedTypeStruct[ii].typedefindex != 0)
Console.WriteLine(" .class 0x{0}",
ExportedTypeStruct[ii].typedefindex.ToString("X8"));
Console.WriteLine("}");
}
}
}
public string
GetTypeAttributeFlagsForClassExtern(int typeattributeflags )
{
typeattributeflags =
typeattributeflags & 0x07;
string
visibiltymaskstring="";
if ( typeattributeflags ==
1)
visibiltymaskstring =
"public ";
if ( typeattributeflags ==
2)
visibiltymaskstring =
"nested public ";
return visibiltymaskstring;
}
e.il
.file mscorlib.dll
.file jj.dll
.assembly e
{
}
.file aa.dll
.file bb
.class extern o1
{
.file jj.dll
}
.file jj.dll
.class extern public jjj
{
.file aa.dll
}
.class extern nested public kkk
{
.file aa.dll
}
.class extern public lll
{
.class extern kkk
}
.class extern public ooo
{
.class extern nnn
}
.class extern public ppp
{
.class extern jjj
}
.class extern public mmm
{
.class extern System.Object
}
.class extern public System.Object
{
.file mscorlib.dll
}
.class extern public nnn
{
.file jj.dll
}
.class extern a1
{
.file jj.dll
.class 56
}
.class zzz
{
.method static void abc()
{
.entrypoint
ret
}
}
Output
.assembly
extern /*23000001*/ mscorlib
{
.ver 0:0:0:0
}
.assembly
/*20000001*/ e
{
.ver 0:0:0:0
}
.file
/*26000001*/ mscorlib.dll
.file
/*26000002*/ jj.dll
.file
/*26000003*/ aa.dll
.file
/*26000004*/ bb
.class
extern /*27000001*/ o1
{
.file jj.dll/*26000002*/
}
.class
extern /*27000002*/ public jjj
{
.file aa.dll/*26000003*/
}
.class
extern /*27000003*/ nested public kkk
{
.file aa.dll/*26000003*/
}
.class
extern /*27000004*/ public lll
{
.comtype 'kkk' /*27000003*/
}
.class
extern /*27000006*/ public ppp
{
.comtype 'jjj' /*27000002*/
}
.class
extern /*27000008*/ public System.Object
{
.file mscorlib.dll/*26000001*/
}
.class
extern /*27000009*/ public nnn
{
.file jj.dll/*26000002*/
}
.class
extern /*2700000A*/ a1
{
.file jj.dll/*26000002*/
.class 0x00000038
}
In this program we simply
display the directive class extern. First we as always add a function
DisplayClassExtern to the abc function. We then in a loop iterate through all
the rows in the ExportedType table. The first thing we need to figure out is
the export attributes that can have one of two values, public or nested public.
These specify the visibility
or who all can see and thus use this type. We have a function
GetTypeAttributeFlagsForClassExtern to do the job for us and return the string
for us. The visibility attributes are stored in the first three bits. Thus we
bit wise and the parameter passed by 7 as the other bits specify other
attributes.
We then check whether the
value is 1 or 2 which tells us whether the public or nested public attributes
are on. We then need the name or more precisely the dotted name of the extern
type. This is stored in two fields name and nspace.
We first get the name of the
namespace and then if the namespace is present we add a dot and then add the
name of the class name. The next field coded is most important as it tells us
what directives are used in the braces. We could if you remember use .file and
.class extern.
The coded field tells us the
name of the table and the row in that table. The row in the table must be non
zero if the directive class extern has to be displayed. Why this is so will be
explained in a short while by using a practical example.
Thus the entire directive
will not be displayed if the variable index is zero even though the dotted name
has a non null value. Now that the index is non zero we then check the table
that the index refers to. If its value is zero, it refers to the file table, 2
means that it refers to the Exported Type table.
A value of 1 means the
Assembly Ref table and for some reason we have found no way to simulate this
value. Now we take each individual table that make up the coded index. The
first is the file table with a value of 0. Here we simply use the index
variable as a index into the FileStruct table and display the name of the file
stored there.
We also need to display the
row number of the file table that is available to us in the index variable. A
table with number 2 means that we have used the class extern directive and lets
understand it with a specific reference to the class lll in the file e.il. Here
we had written the class extern directive
with the name of the class kkk.
The class kkk happens to be
another class extern with a file directive. This gets converted to a directive
comtype whose explanation is no where to be found. Also ilasm does not
understand such a directive. All that we know is that we have used a class name
that is a class defined somewhere else as we have used the class extern
directive.
This class name kkk is
stored in the ExportedType table and use the index variable to fetch its name.
We are not allowed to use a type name like zzz in the class extern as it is a
type we are creating in the current file. The if statement which checks the
value of the typedefindex as its always zero unless we add the .class
directive. All that this directive does is fills up the typedefindex field with
the row number of the type table which is present in the other module. As we
will explain much later this is only a hint and nobody checks the value we
write after the class directive.
Looking at the file e.il,
the first three class extern classes o1,jjj and kkk simply use file directive.
The difference is the visibility attributes. The class ooo is not present in
the output at all even though it is there in the e.il file.
Lets investigate. For this
class we have used the class extern directive with the class name nnn. This
class in turn is stated to be in the file aa.dll. Thus as class ooo does
nothing but refers to class nnn, there is no use for its independent existence
and hence ilasm being a smart cookie ignores it and does not place it in the
Exported Type table.
Makes sense as the class is
simply an alias for another class. This
class ppp is similar in concept to the
class ppp that uses the undocumented comtype directive. The class mmm is also
not found as its an alias for the class System.Object. no point cluttering up
this table with classes that do nothing.
Whenever we use the class
extern directive we should use the assembly directive also but we will not get
a error if we do not. Obviously there is internally one manifest module only
and all types exported should be present in this manifest.
The rational for this table
is that after some tool reads this table, he/she can figure out all the types
that others can use from this assembly. This manifest module will therefore
contain all the types exported from all the modules that make up the assembly.
Unfortunately this manifest module is also called the assembly.
Thus each time we create a
type in any module of an assembly, this table of ours gets one row added. In
other words, every type created in another module has a entry in the TypeDef
table and this row number will be placed in the Exported Type table. The
TypeDefID or typedefindex field in our case is always zero as mentioned earlier
unless of course we use the class directive.
The point we are making is
that this is the first time we are referring to a row in another module and
these are called foreign tokens. The fact of the matter is that the assembler
is too lazy to go the other modules and figure out what the type def row
indexes are.
The type name is stored in
the name and namespace fields and there was a plan to use the typedefid field
if the above search failed. The specs at one place say that the implementation
coded index can contain only the File and Exported Type table and at a another
place adds the Assembly Ref table also.
For the file table the
visibility mask has to be public and not nested public but we do not get an
error if we do make such a mistake. The specs also say that if the table is the
Exported type then the visibility mask has to be nested public. No error again
if we break the rule. Why have rules when no one checks for them.
Program14.csc
public void abc(string [] args)
{
ReadPEStructures(args);
DisplayPEStructures();
ReadandDisplayImportAdressTable();
ReadandDisplayCLRHeader();
ReadStreamsData();
FillTableSizes();
ReadTablesIntoStructures();
DisplayTablesForDebugging();
ReadandDisplayVTableFixup();
ReadandDisplayExportAddressTableJumps();
DisplayModuleRefs();
DisplayAssembleyRefs();
DisplayAssembley();
DisplayFileTable();
DisplayClassExtern();
DisplayResources();
}
public void
DisplayResources()
{
if ( ManifestResourceStruct
== null)
return;
for ( int ii = 1 ; ii < ManifestResourceStruct.Length
; ii++)
{
string flags =
GetManifestResourceAttributes(ManifestResourceStruct[ii].flags);
Console.WriteLine(".mresource
/*28{0}*/ {1}{2}" ,
ii.ToString("X6") , flags,
NameReserved(GetString(ManifestResourceStruct[ii].name)) );
Console.WriteLine("{");
string table =
GetManifestResourceTable(ManifestResourceStruct[ii].coded);
int index =
GetManifestResourceValue(ManifestResourceStruct[ii].coded);
if ( table ==
"AssemblyRef")
Console.WriteLine(" .assembly extern {0} /*23{1}*/ " ,
NameReserved(GetString(AssemblyRefStruct[index].name)) ,
index.ToString("X6"));
else if ( table ==
"File" && index > 0)
Console.WriteLine(" .file {0}/*26{1}*/ at 0x{2}" , NameReserved(GetString(FileStruct[index].name))
, index.ToString("X6") ,ManifestResourceStruct[ii].offset.ToString("X8")
);
else
Console.WriteLine(" // WARNING: managed resource file {0}
created",NameReserved(GetString(ManifestResourceStruct[ii].name) ) );
Console.WriteLine("}");
}
}
public int
GetManifestResourceValue(int manifiestvalue)
{
return
manifiestvalue>> 2;
}
public string
GetManifestResourceTable(int manifiestvalue)
{
string returnstring =
"";
short tag =
(short)(manifiestvalue & (short)0x03);
if ( tag == 0)
returnstring = returnstring
+ "File";
if ( tag == 1)
returnstring = returnstring
+ "AssemblyRef";
return returnstring;
}
public string
GetManifestResourceAttributes(int manifiestvalue)
{
string
returnstring="";
if ( (manifiestvalue &
0x001) == 0x001)
returnstring = returnstring
+ "public ";
if ( (manifiestvalue &
0x002) == 0x002)
returnstring = returnstring
+ "private ";
return returnstring;
}
e.il
.assembly e
{
}
.assembly extern a1
{
}
.file aa.dll
.mresource r1
{
}
.mresource public r1
{
}
.mresource private r2
{
.assembly extern a1
}
.mresource public r3
{
.file aa.dll at 12
}
.mresource public r4
{
.file aa.dll at 12
.assembly extern a1
}
.class zzz
{
.method static void abc()
{
.entrypoint
ret
}
}
Output
.assembly extern
/*23000001*/ a1
{
.ver 0:0:0:0
}
.assembly extern
/*23000002*/ mscorlib
{
.ver 0:0:0:0
}
.assembly /*20000001*/ e
{
.ver 0:0:0:0
}
.file /*26000001*/ aa.dll
.mresource /*28000001*/ r1
{
// WARNING: managed resource file r1 created
}
.mresource /*28000002*/
public r1
{
// WARNING: managed resource file r1 created
}
.mresource /*28000003*/ private
r2
{
.assembly extern a1 /*23000001*/
}
.mresource /*28000004*/
public r3
{
.file aa.dll/*26000001*/
at 0x0000000C
}
.mresource /*28000005*/
public r4
{
.assembly extern a1 /*23000001*/
}
The above program displays
all the resources that we have. We have added a function DisplayResources that displays all the rows
from the Manifest Resource table. This table gets filled up by the mresource
directive. The first field is the flags field which gives us the visibility
mask that is similar to the class extern directive.
The only difference is that
the two valid values are public and private. We use the function
GetManifestResourceAttributes to return one of these values. None of these
visibility attributes is mandatory. We display the visibility attributes along
with the mresource directive and the row number in the Manifest Resource table.
Nearly every table has a
coded index and the Resource table does not lag behind. In this case the coded
index either points to the File table if the value is 0 or assembly ref table
whose value is 1. The specs call this the Implementation coded index which also
included the Exported type table but for resources it is only the above two.
We use the
GetManifestResourceTable function to return the table name and the function
GetManifestResourceValue to return the row number after right shifting the
coded index by two bits. In the mresource directive we can either write the
Assembley extern directive or the file directive or both or none.
If we use the assembly
extern directive, then the index variable is the row number of the Assembly Ref
table and we display the name of the assembly. If we had had used the file
directive and this indexes a row number larger than or equal to 1, then we
display the file name along with the position of the file where the resource
starts.
The index variable is the
row number in the File table and the offset within the file is stored in the
offset field of the Resource table. Finally if we have used the file directive
and the index variable is zero, then within comments we display a warning
stating that a file with the same name as the mresource directive has been
created.
If we specify no directive,
we get no error and the above warning is displayed. If we use both, the assembly
directive is used and not the file directive. As always no error checks for the
file directive or the offset. An assembly can have lots of different data items
associated with it.
If we ever want to name a
item of data, we use the manifest resource to do so. If we do not have a
assembly directive, it is perfectly legal to use the mresource directive, but
we will not be able to execute the assembly.
The reason we specify public
or private for a manifest resource is so that the assembly knows whether this
item can be exported or seen outside this assembly or should remain visible
only within the assembly if it is flagged private.
If the resource is stored in
the file and that file is not a module, it can be a text file for example, then
we would need a separate file directive, declaring that file. In this case the
byte offset will be zero.
If the resource is defined
in another assembly, we would need to have a assembly extern directive at the
top level before we can use the assembly extern directive within the mresource
directive. The offset field is normally a valid offset which is relative from
the resource data directory entry in the COR header.
But as said earlier, this
error check is not done at present. If the index is null, it means that the
resource is stored in the current file and hence the warning.
Program15.csc
public void abc(string []
args)
{
ReadPEStructures(args);
DisplayPEStructures();
ReadandDisplayImportAdressTable();
ReadandDisplayCLRHeader();
ReadStreamsData();
FillTableSizes();
ReadTablesIntoStructures();
DisplayTablesForDebugging();
ReadandDisplayVTableFixup();
ReadandDisplayExportAddressTableJumps();
DisplayModuleRefs();
DisplayAssembleyRefs();
DisplayAssembley();
DisplayFileTable();
DisplayClassExtern();
DisplayResources();
DisplayModuleAndMore();
}
public void
DisplayModuleAndMore()
{
Console.WriteLine(".module
{0}" , NameReserved(GetString(ModuleStruct[1].Name)));
Console.Write("// MVID:
");
DisplayGuid(ModuleStruct[1].Mvid);
Console.WriteLine();
Console.WriteLine(".imagebase
0x{0}" , ImageBase.ToString("x8"));
Console.WriteLine(".subsystem
0x{0}" , subsystem.ToString("X8"));
Console.WriteLine(".file
alignment {0}" , filea);
Console.WriteLine(".corflags
0x{0}" , corflags.ToString("x8"));
Console.WriteLine("//
Image base: 0x03000000");
public void DisplayGuid (int
guidindex)
{
Console.Write("{");
Console.Write("{0}{1}{2}{3}",
guid[guidindex+2].ToString("X2") ,
guid[guidindex+1].ToString("X2") ,
guid[guidindex].ToString("X2") ,
guid[guidindex-1].ToString("X2"));
Console.Write("-{0}{1}-",guid[guidindex+4].ToString("X2")
, guid[guidindex+3].ToString("X2"));
Console.Write("{0}{1}-",guid[guidindex+6].ToString("X2")
, guid[guidindex+5].ToString("X2"));
Console.Write("{0}{1}-",guid[guidindex+7].ToString("X2")
, guid[guidindex+8].ToString("X2"));
Console.Write("{0}{1}{2}{3}{4}{5}",guid[guidindex+9].ToString("X2"),guid[guidindex+10].ToString("X2"),guid[guidindex+11].ToString("X2"),guid[guidindex+12].ToString("X2"),guid[guidindex+13].ToString("X2"),guid[guidindex+14].ToString("X2"));
Console.Write("}");
}
e.il
.assembly e
{
}
.module aaaa
.class zzz
{
.method static void abc()
{
.entrypoint
ret
}
}
output
.module aaaa
// MVID:
{EDBE9E84-F6DE-468C-B8CB-0CB099FD1EA4}
.imagebase
0x00400000
.subsystem
0x00000003
.file
alignment 512
.corflags
0x00000001
// Image
base: 0x03000000
This one is a small program
and all that it does is adds one more function call DisplayModuleAndMore to the
abc function. The .module directive is optional and this adds one record to the
Module table. As mentioned earlier we can have only one module directive and
not two and if we do not, one gets added for us automatically.
We simply display the
directive module and follow this with the words MVID. We then call a function
DisplayGuid that displays a guid for us. Every application needs to be uniquely
identified and the assembler gives it a unique 128 bit number stored in the
field Mvid. Each time we regenerate our assembly, this number changes.
The reason it is a 128 bit
number is because such a number is unique across time and space. The problem
with the guid is that it is displayed in a
certain manner and the function DisplayGuid simply displays the bytes
from the offset of the guid stream passed as a parameter.
For example we display the
third byte first followed by the second etc. This guid is calculated using the
ISO/IEC standard 11578:1996. The full form of a GUID is a Globally Unique
IDentifier. It is a concept used by CORBA and OLE in the past.
The VES (Virtual Execution
System or Runtime) does not make any use of the Guid but debuggers should use
this number to uniquely identify the module. The name of the file is not the
physical file name but the logical name stored in the metadata.
The module table is the
first one the designers of the metadata thought of as they gave it a number of
0. The generation field is reserved and has a value of zero. EncId and
EncBaseId are also reserved but they are indexes into th Guid heap and also
have a value of 0. Both Mvid and the name field have to have a non null value.
If you remember earlier we
had figured some instance variables like file alignment and subsystem. We are
simply displaying these values here. The Imagebase, subsystem and file
alignment variables we have already displayed earlier. The ImageBase is once
again displayed within comments with a constant value.
Some points that we missed
about these values we will explain now. The corflags directive was not written
by us and the CLI expects the value to be 1. For backwards compatibility the least
3 significant bits are reserved. Thus the values from 8 to 65535 will be used
by future versions.
The guys who create
experimental and or non standard versions have the blessings of the .Net
standard to use values larger than 65535. The subsystem directive is used only
when we execute the assembly and thus dll’s have no use for it. This may be a
32 bit number but it can have only two possible values.
A value of 2 means that the
program should be run using whatever conventions are fit for a GUI applications.
A value of 3 is for a console application. At this point in time there is no
third environment for a application to execute. The file alignment can only be
values that are a multiple of 512 bytes.
Program16.csc
string [] vtfixuparray;
public void abc(string []
args)
{
ReadPEStructures(args);
DisplayPEStructures();
ReadandDisplayImportAdressTable();
ReadandDisplayCLRHeader();
ReadStreamsData();
FillTableSizes();
ReadTablesIntoStructures();
DisplayTablesForDebugging();
ReadandDisplayVTableFixup();
ReadandDisplayExportAddressTableJumps();
DisplayModuleRefs();
DisplayAssembleyRefs();
DisplayAssembley();
DisplayFileTable();
DisplayClassExtern();
DisplayResources();
DisplayModuleAndMore();
DispalyVtFixup();
}
public void
ReadandDisplayVTableFixup()
{
if ( MethodStruct == null)
return;
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");
}
}
Console.WriteLine();
}
}
public void DispalyVtFixup()
{
if (vtfixuparray == null)
return;
for ( int ii = 0 ; ii <
vtfixuparray.Length ; ii++)
Console.WriteLine(vtfixuparray[ii]);
}
e.il
.class public a11111
{
.method public static
void adf() cil managed
{
.entrypoint
}
.method public int64 a1() cil managed
{
}
.method public int64 a2() cil managed
{
}
.method public int64 a3() cil managed
{
}
.method public int64 a4() cil managed
{
}
.method public int64 a5() cil managed
{
}
.method public int64 a6() cil managed
{
}
.method public int64 a7() cil managed
{
}
}
.vtfixup [1] int32 at
D_00008010
.vtfixup [1] int32
fromunmanaged at D_00008020
.vtfixup [1] int64 at
D_00008030
.vtfixup [1] int64
fromunmanaged at D_00008040
.vtfixup [0] int64 at
D_00008050
.vtfixup [2] int64 int64 at
D_00008060
.data D_00008010 = bytearray
( 01 00 00 06)
.data D_00008020 = bytearray
( 02 00 00 06)
.data D_00008030 = bytearray
( 03 00 00 06)
.data D_00008040 = bytearray
( 04 00 00 06)
.data D_00008050 = bytearray
( 05 00 00 06)
.data D_00008060 = bytearray
( 06 00 00 06 00 00 00 00 07 00 00 06 00 00 00 00)
Output
.vtfixup [1]
int32 at D_00004000 // 06000001
.vtfixup [1]
int32 fromunmanaged at D_00004004 // 06000002
.vtfixup [1]
int64 at D_00004008 // 0600000406000003
.vtfixup [1]
int64 fromunmanaged at D_0000400C // 0600000506000004
.vtfixup [0]
int64 at D_00004010 //
.vtfixup [2]
int64 at D_00004014 // 0000000006000006 0000000006000007
If you look at program7.csc
carefully, we displayed the directive vtfixup. If you looked at the output of
that program very carefully, you would have realized that it was all in
comments. We did not display the actual directive vtfixup at that time.
We have added a instance
array vtfixuparray and we also call a function DispalyVtFixup in the function
abc to display the directive. We have also added some more code in the
ReadandDisplayVTableFixup function that will populate the array vtfixuparray
and then display it later at the end.
As explained before the
count1 variable is a count of the items and we use this to give us an array of
the desired size. Creating an array of size is not an error and the array just
does not get created. We use the same variable count1 to also create the array
val to store the individual values.
Depending upon the type
being a int32 or int64 we read either 4 or 8 bytes into the corresponding val
array. We then create the entire string into the vtfixuparray and use the type
variable to determine the width of the table. The fixuprva gives us the data
address which we concatenate with a D_.
Then we place the comments
and after this we need the values that we wrote in the bytearray. These depend
upon the count value we specified in the square brackets. Thus we use the same
loop again and concatenate the bytearray values, reading either 4 or 8 bytes
depending as we said on the width of the table.
When we look at the
DispalyVtFixup function, it simply displays the members of the vtfixuparray.
Program17.csc
public void abc(string []
args)
{
ReadPEStructures(args);
DisplayPEStructures();
ReadandDisplayImportAdressTable();
ReadandDisplayCLRHeader();
ReadStreamsData();
FillTableSizes();
ReadTablesIntoStructures();
DisplayTablesForDebugging();
ReadandDisplayVTableFixup();
ReadandDisplayExportAddressTableJumps();
DisplayModuleRefs();
DisplayAssembleyRefs();
DisplayAssembley();
DisplayFileTable();
DisplayClassExtern();
DisplayResources();
DisplayModuleAndMore();
DispalyVtFixup();
DisplayTypeDefs();
}
public void DisplayTypeDefs
()
{
if ( TypeDefStruct.Length !=
2)
{
Console.WriteLine("//");
Console.WriteLine("//
============== CLASS STRUCTURE DECLARATION ==================");
Console.WriteLine("//");
writenamespace = true;
for ( int i = 2 ; i <
TypeDefStruct.Length ; i++)
{
if (
GetString(TypeDefStruct[i].name) == "_Deleted" && streamnames[0] == "#-")
{
continue;
}
if ( ! IsTypeNested(i) )
{
DisplayOneTypePrototype(i);
}
}
}
}
public bool IsTypeNested
(int typeindex)
{
if (NestedClassStruct ==
null)
return false;
for ( int ii = 1 ; ii< NestedClassStruct.Length ; ii++)
{
if ( NestedClassStruct[ii].nestedclass
== typeindex)
return true;
}
return false;
}
public void
DisplayOneTypePrototype (int typedefindex)
{
DisplayOneTypeDefStart(typedefindex);
DisplayNestedTypesPrototypes(typedefindex);
DisplayOneTypeDefEnd(typedefindex);
}
public void DisplayOneTypeDefStart
(int typerow)
{
string namespacename =
NameReserved(GetString(TypeDefStruct[typerow].nspace));
if ( namespacename != "")
{
if ( writenamespace )
{
Console.WriteLine(".namespace
{0}" , namespacename );
Console.WriteLine("{" );
spacefornamespace = 2;
spacesforrest = 4;
}
}
string typestring =
"";
if ( IsTypeNested(typerow))
typestring = typestring +
CreateSpaces(spacesfornested);
typestring = typestring +
CreateSpaces(spacefornamespace);
typestring = typestring +
".class /*02" + typerow.ToString("X6") + "*/ ";
string attributeflags =
GetTypeAttributeFlags(TypeDefStruct[typerow].flags , typerow);
Console.WriteLine("{0}{1}{2}"
, typestring , attributeflags ,
NameReserved(GetString(TypeDefStruct[typerow].name)));
string tablename = GetTypeDefOrRefTable(TypeDefStruct[typerow].cindex);
int index =
GetTypeDefOrRefValue(TypeDefStruct[typerow].cindex);
string typeextends =
"";
if ( tablename ==
"TypeRef" )
{
typeextends =
DisplayTypeRefExtends(index);
}
if ( tablename ==
"TypeDef" )
{
typeextends = GetNestedTypeAsString(index) +
DisplayTypeDefExtends(index);
}
if ( typeextends.Length !=
0)
{
typestring = "";
if ( IsTypeNested(typerow))
typestring = typestring +
CreateSpaces(spacesfornested);
typestring = typestring +
CreateSpaces(spacefornamespace);
typestring = typestring +
" extends " +
typeextends;
Console.WriteLine(typestring);
}
string interfacestring =
DisplayAllInterfaces(typerow);
if ( interfacestring.Length
!= 0)
{
typestring = "";
if ( IsTypeNested(typerow))
typestring = typestring +
CreateSpaces(spacesfornested);
typestring = typestring +
CreateSpaces(spacefornamespace);
typestring = typestring +
" implements " +
interfacestring;
Console.Write(typestring);
}
typestring = "";
if ( IsTypeNested(typerow))
typestring = typestring +
CreateSpaces(spacesfornested);
typestring = typestring +
CreateSpaces(spacefornamespace);
typestring = typestring +
"{";
Console.WriteLine(typestring);
}
public string
GetTypeAttributeFlags (int typeattributeflags , int typeindex)
{
string returnstring =
"";
int visibiltymask =
typeattributeflags & 0x07;
string
visibiltymaskstring="";
if ( visibiltymask == 0)
visibiltymaskstring =
"private ";
if ( visibiltymask == 1)
visibiltymaskstring =
"public ";
if ( visibiltymask == 2)
visibiltymaskstring =
"nested public ";
if ( visibiltymask == 3)
visibiltymaskstring =
"nested private ";
if ( visibiltymask == 4)
visibiltymaskstring =
"nested family ";
if ( visibiltymask == 5)
visibiltymaskstring =
"nested assembly ";
if ( visibiltymask == 6)
visibiltymaskstring =
"nested famandassem ";
if ( visibiltymask == 7)
visibiltymaskstring =
"nested famorassem ";
int classlayoutmask =
typeattributeflags & 0x18;
string classlayoutstring =
"";
if ( classlayoutmask == 0)
classlayoutstring =
"auto ";
if ( classlayoutmask ==
0x08)
classlayoutstring =
"sequential ";
if ( classlayoutmask ==
0x10)
classlayoutstring =
"explicit ";
string interfacestring =
"";
if ( (typeattributeflags
& 0x20) == 0x20)
interfacestring = "interface ";
string abstractstring =
"";
if ( (typeattributeflags
& 0x80) == 0x80)
abstractstring = "abstract ";
string sealedstring =
"";
if ( (typeattributeflags
& 0x100) == 0x100)
sealedstring = "sealed ";
string specialnamestring =
"";
if ( (typeattributeflags
& 0x400) == 0x400)
specialnamestring = "specialname ";
string importstring =
"";
if ( (typeattributeflags
& 0x1000) == 0x1000)
importstring = "import ";
string serializablestring =
"";
if ( (typeattributeflags
& 0x2000) == 0x2000)
serializablestring =
"serializable ";
int stringformatmask =
typeattributeflags & 0x30000;
string stringformastring =
"";
if ( stringformatmask == 0)
stringformastring =
"ansi ";
if ( stringformatmask ==
0x10000)
stringformastring =
"unicode ";
if ( stringformatmask ==
0x20000)
stringformastring =
"autochar ";
string beforefieldinitstring
= "";
if ( (typeattributeflags
& 0x00100000) == 0x00100000)
beforefieldinitstring =
"beforefieldinit ";
//string rtspecialnamestring
= "";
//if ( (typeattributeflags
& 0x800) == 0x800)
//rtspecialnamestring =
"rtspecialname ";
if ( IsTypeNested(typeindex)
)
returnstring =
interfacestring + abstractstring + classlayoutstring + stringformastring + serializablestring + sealedstring +
importstring + visibiltymaskstring +
beforefieldinitstring;
else
returnstring =
interfacestring + visibiltymaskstring + abstractstring + classlayoutstring +
stringformastring + importstring + serializablestring + sealedstring +
specialnamestring + beforefieldinitstring ;
return returnstring;
}
public string
DisplayTypeDefExtends (int typedefindex)
{
if ( typedefindex == 0)
return "";
string name =
NameReserved(GetString(TypeDefStruct[typedefindex].name));
string returnstring =
NameReserved(GetString(TypeDefStruct[typedefindex].nspace));
if ( returnstring.Length !=
0)
returnstring = returnstring
+ ".";
returnstring = returnstring
+ name + "/* 02" +
typedefindex.ToString("X6") + " */";
return returnstring;
}
public string
GetNestedTypeAsString(int rowindex)
{
string netsedtypestring =
"";
string
namespaceandnameparent2 = "";
string
namespaceandnameparent3= "";
if ( IsTypeNested(rowindex)
)
{
int rowindexparent =
GetParentForNestedType(rowindex);
if (
IsTypeNested(rowindexparent) )
{
int rowindexparentparent =
GetParentForNestedType(rowindexparent);
if (
IsTypeNested(rowindexparentparent) )
{
int rowindexp3 =
GetParentForNestedType(rowindexparentparent);
string nameparent3 =
NameReserved(GetString(TypeDefStruct[rowindexp3].name));
namespaceandnameparent3=
NameReserved(GetString(TypeDefStruct[rowindexp3].nspace));
if (
namespaceandnameparent3.Length != 0)
namespaceandnameparent3 =
namespaceandnameparent3 + ".";
namespaceandnameparent3=
namespaceandnameparent3 + nameparent3 + "/* 02" +
rowindexp3.ToString("X6") + " *//";
}
string nameparent2 =
NameReserved(GetString(TypeDefStruct[rowindexparentparent].name));
namespaceandnameparent2 =
NameReserved(GetString(TypeDefStruct[rowindexparentparent].nspace));
if (
namespaceandnameparent2.Length != 0)
namespaceandnameparent2 =
namespaceandnameparent2 + ".";
namespaceandnameparent2 =
namespaceandnameparent3 + namespaceandnameparent2 + nameparent2 + "/*
02" + rowindexparentparent.ToString("X6") + " *//";
}
string nameparent1 =
NameReserved(GetString(TypeDefStruct[rowindexparent].name));
netsedtypestring =
NameReserved(GetString(TypeDefStruct[rowindexparent].nspace));
if ( netsedtypestring.Length
!= 0)
netsedtypestring =
netsedtypestring + ".";
netsedtypestring =
namespaceandnameparent2 + netsedtypestring + nameparent1 + "/* 02" +
rowindexparent.ToString("X6") + " *//";
}
return netsedtypestring;
}
public int
GetParentForNestedType (int typeindex)
{
int ii = 0;
if ( NestedClassStruct ==
null)
return 0;
for ( ii = 0 ; ii <
NestedClassStruct.Length - 1 ; ii++)
{
if ( typeindex ==
NestedClassStruct[ii].nestedclass )
break;
}
return
NestedClassStruct[ii].enclosingclass;
}
public string
DisplayTypeRefExtends (int typerefindex)
{
string returnstring =
"";
int resolutionscope =
TypeRefStruct[typerefindex].resolutionscope;
string resolutionscopetable
= GetResolutionScopeTable(resolutionscope);
int resolutionscopeindex =
GetResolutionScopeValue(resolutionscope);
string dummy = "";
if ( resolutionscopetable ==
"Module")
{
}
if ( resolutionscopetable ==
"AssemblyRef")
{
returnstring = "["
+ NameReserved(GetString(AssemblyRefStruct[resolutionscopeindex].name)) ;
returnstring = returnstring
+ "/* 23" + resolutionscopeindex.ToString("X6") + "
*/]";
}
if ( resolutionscopetable ==
"ModuleRef")
{
returnstring =
"[.module " +
NameReserved(GetString(ModuleRefStruct[resolutionscopeindex].name)) ;
returnstring = returnstring
+ "/* 1A" + resolutionscopeindex.ToString("X6") + "
*/]";
}
if ( resolutionscopetable ==
"TypeRef")
{
int resolutionscopeindex1 =
GetResolutionScopeValue(TypeRefStruct[resolutionscopeindex].resolutionscope );
string resolutionscopetable1
= GetResolutionScopeTable(TypeRefStruct[resolutionscopeindex].resolutionscope
);
if ( resolutionscopetable1
== "AssemblyRef")
{
dummy = "[" +
NameReserved(GetString(AssemblyRefStruct[resolutionscopeindex1].name)) +
"/* 23" + resolutionscopeindex1.ToString("X6") + "
*/]";
string nspace1 =
NameReserved(GetString(TypeRefStruct[resolutionscopeindex].nspace));
if ( nspace1 !=
"")
nspace1 = nspace1 +
".";
dummy = dummy + nspace1 +
NameReserved(GetString(TypeRefStruct[resolutionscopeindex].name)) + "/*
01" + resolutionscopeindex.ToString("X6") + " *//";
}
}
int namespaceindex =
TypeRefStruct[typerefindex].nspace;
string nspace =
NameReserved(GetString(namespaceindex));
returnstring = returnstring
+ nspace ;
if ( nspace.Length != 0)
returnstring = returnstring
+ ".";
int nameindex =
TypeRefStruct[typerefindex].name;
returnstring = dummy +
returnstring + NameReserved(GetString(nameindex)) + "/* 01" +
typerefindex.ToString("X6") + " */";
return returnstring;
}
public string
DisplayAllInterfaces (int typeindex)
{
string returnstring =
"";
if ( InterfaceImplStruct ==
null || InterfaceImplStruct.Length == 1)
return "";
for ( int i = 1 ; i <
InterfaceImplStruct.Length ; i++)
{
if ( typeindex ==
InterfaceImplStruct[i].classindex )
{
string codedtablename =
GetTypeDefOrRefTable(InterfaceImplStruct[i].interfaceindex);
int interfaceindex =
GetTypeDefOrRefValue(InterfaceImplStruct[i].interfaceindex);
string interfacename =
"";
if ( codedtablename ==
"TypeRef" )
interfacename = DisplayTypeRefExtends(interfaceindex);
if ( codedtablename ==
"TypeDef" )
interfacename = GetNestedTypeAsString(interfaceindex) +
DisplayTypeDefExtends(interfaceindex);
returnstring = returnstring
+ interfacename;
bool nextclassindex ;
if ( i ==
(InterfaceImplStruct.Length - 1))
nextclassindex = false;
else if ( typeindex !=
InterfaceImplStruct[i+1].classindex )
nextclassindex = false;
else
nextclassindex = true;
if ( nextclassindex )
returnstring = returnstring
+ ",\r\n " +
CreateSpaces(spacefornamespace+spacesfornested);
else
returnstring = returnstring
+ "\r\n";
}
}
return returnstring;
}
public string
GetTypeDefOrRefTable (int codedvalue)
{
string returnstring =
"";
short tag =
(short)(codedvalue & (short)0x03);
if ( tag == 0)
returnstring = returnstring
+ "TypeDef";
if ( tag == 1)
returnstring = returnstring
+ "TypeRef";
if ( tag == 2)
returnstring = returnstring
+ "TypeSpec";
return returnstring;
}
public int
GetTypeDefOrRefValue(int codedvalue)
{
return codedvalue >>
2;
}
public void
DisplayNestedTypesPrototypes (int typedefindex)
{
if (NestedClassStruct ==
null)
return ;
for ( int ii = 1 ; ii <
NestedClassStruct.Length ; ii++)
{
if
(NestedClassStruct[ii].enclosingclass == typedefindex)
{
spacesfornested += 2;
DisplayOneTypePrototype(NestedClassStruct[ii].nestedclass );
spacesfornested -= 2;
}
}
}
public void
DisplayOneTypeDefEnd (int typeindex )
{
string dummy = "";
if ( IsTypeNested(typeindex)
)
dummy = dummy +
CreateSpaces(spacesfornested);
dummy = dummy +
CreateSpaces(spacefornamespace);
dummy = dummy + "} //
end of class ";
string classname =
NameReserved(GetString(TypeDefStruct[typeindex].name));
dummy = dummy + classname ;
Console.WriteLine(dummy);
string namespacename =
NameReserved(GetString(TypeDefStruct[typeindex].nspace));
Console.WriteLine();
if ( namespacename != "")
{
string nspace1 =
NameReserved(GetString(TypeDefStruct[typeindex].nspace));
int ii;
for ( ii = typeindex + 1 ;
ii < TypeDefStruct.Length - 1 ; ii++)
{
if ( IsTypeNested(ii) )
continue;
break;
}
string nspace2 =
"";
if ( ii !=
TypeDefStruct.Length )
nspace2 =
NameReserved(GetString(TypeDefStruct[ii].nspace));
if ( nspace1 != nspace2 )
{
if ( lasttypedisplayed ==
typeindex && notprototype )
{
Console.WriteLine();
Console.WriteLine("//
=============================================================");
Console.WriteLine();
placedend = true;
}
Console.Write("}");
Console.WriteLine(" //
end of namespace {0}", namespacename);
spacefornamespace = 0;
spacesforrest = 2;
writenamespace = true;
Console.WriteLine();
}
else
writenamespace = false;
}
}
e.il
.class public abstract
beforefieldinit a1
{
.method public static void
Main()
{
.entrypoint
}
}
.class private sequential
unicode explicit sealed specialname rtspecialname interface _Deleted
{
}
.namespace n1
{
.namespace n2
{
.class public autochar a2
{
.class public a3
{
.class a33
{
.class a333
{
.class a3333
{
}
}
}
}
.class a4
{
}
}
}
}
.class a5
{
}
.class a6 extends a5
{
}
.class a7 extends
n1.n2.a2/a3/a33/a333
{
}
.class a71 extends
n1.n2.a2/a3
{
}
.class a72 extends
n1.n2.a2/a3/a33
{
}
.class a8 extends
[mscorlib]aaa
{
}
.class a9 extends
[mscorlib]ppp.aaa/aa
{
}
.module extern bb
.module e.exe
.class a10 extends [.module
bb]a11
{
}
.class a12 extends [.module
e.exe]a13
{
}
.assembly ee
{
}
.class a14 extends [ee]a13
{
}
.class interface a15
{
}
.class interface a16
{
}
.class a17 implements
a15,a16,[mscorlib]aa
{
}
Output
.module extern bb
/*1A000001*/
.assembly extern
/*23000001*/ mscorlib
{
.ver 0:0:0:0
}
.assembly extern
/*23000002*/ ee
{
.ver 0:0:0:0
}
.assembly /*20000001*/ ee
{
.ver 0:0:0:0
}
.module e.exe
// MVID:
{DB25BB73-A933-4B68-9124-8F5135AC4D55}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03000000
//
// ============== CLASS
STRUCTURE DECLARATION ==================
//
.class /*02000002*/ public
abstract auto ansi beforefieldinit a1
extends [mscorlib/* 23000001 */]System.Object/* 01000001 */
{
} // end of class a1
.class /*02000003*/
interface private abstract explicit unicode sealed specialname _Deleted
{
} // end of class _Deleted