道理越辨越明。我不是来吵架的,而是特地来澄清一些概念。赵劼的看法实在有些偏激,但凡与之意见向左的,一概穷追猛打至死。有感于他的毁人不倦大多穿凿附会之说,于是,暂时搁下手上的工作,发此文以正视听。
道理越辨越明。我不是来吵架的,而是来澄清一些概念。赵劼的看法实在有些偏激,但凡与之意见向左的,一概穷追猛打至死。赵同学在社区的影响力在我之上,但这也正是其可怕之处——一旦有所偏差,必然会误导更多的朋友。有感于他的毁人不倦大多穿凿附会之说,于是,暂时搁下手上的工作,发此文以正视听。
综观赵劼《老赵谈IL:IL是什么,它又不是什么?那么汇编呢?》一文,对我的指责主要集中在2个地方:
其一:我曾经指出,UltraEdit32也可以观察IL中的一些数据,但是没有给出具体操作办法。于是赵劼就单方面理解为我是信口开河,并多次在公开场合抓住这个话柄对我进行诋毁。我其实不是很想提及这个办法,所以一直没有说破,因为这个法子太过BT。介绍如下:
首先,我们信手写一个简单的a.cs文件:
public class Bao
{
public static void Main()
{
System.Console.WriteLine("hello, Jianqiang!");
}
}
编译命令如下:
csc b.cs
于是生成b.exe文件。
我们用UltraEdit32打开它,
以上是第1到第9行,怎么看这个图呢?
开始的MZ是一个魔数,用来纪念设计DOS内存管理系统的人,所有exe文件都是以这两个字母作为开始的,也就是4D和5A,你可以去查ASCII表,但是有了UltraEdit32,我们可以只看右边对应的字母。接下来是一句话:This is program cannot be run in DOS mode。这句话是一句友好的错误信息,会在运行环境不对导致推出时向用户显示。有人会问,中间那些句点和乱码是什么?不要管它,有的时候右边的信息会给我们误导,因为有时我们要一次读2个字节,我们只按照微软文档的offset和size找出那些位于固定位置的信息就可以了。比如说,接下来是PE头的全部所有信息,比如说PE 头的魔数00(第80h行开始4个字节 50 45 00 00, 即PE00),只要按照偏移量和大小来找,都可以找到。
继续找下去,还能发现更多数据:比如说第80h行的4C 01,表示Machine 0x014C,而之后的03 00则表示有3个Section,等等。
看到这里,你也许会觉得很累,没错,我也很累,但是为了证明用UltraEdit32也能看IL中的数据,我只好半夜不睡觉眯着眼睛找这些东西,让某些人心服口服外带佩服。对于PE头,这法子百试百灵。这时候,某些人又会吹毛求屁了,说你有本事找个元数据给我看看啊?注意,我没有说过从UltraEdit32可以看到IL的所有数据,尤其是那些放在#~流中的压缩数据。但是,我另有办法,这法子是一位印度哥哥教我的,就是用C#中的IO操作来读取exe文件,只要按照微软文档定义的规格来做就可以:
还是刚才那个a.exe文件,编写如下代码:
Code
using System;
using System.IO;
using System.Reflection;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.abc();
}
int tableoffset;
int[] rows;
int[] offset;
int[] ssize;
byte[] metadata;
byte[] strings;
long valid;
byte[][] names;
public void abc()
{
long startofmetadata;
FileStream s = new FileStream(@"C:\a.exe", FileMode.Open);
BinaryReader r = new BinaryReader(s);
s.Seek(360, SeekOrigin.Begin);
int rva, size;
rva = r.ReadInt32();
size = r.ReadInt32();
int where = rva % 0x2000 + 512;
s.Seek(where + 4 + 4, SeekOrigin.Begin);
rva = r.ReadInt32();
where = rva % 0x2000 + 512;
s.Seek(where, SeekOrigin.Begin);
startofmetadata = s.Position;
s.Seek(4 + 2 + 2 + 4 + 4 + 12 + 2, SeekOrigin.Current);
int streams = r.ReadInt16();
offset = new int[5];
ssize = new int[5];
names = new byte[5][];
names[0] = new byte[10];
names[1] = new byte[10];
names[2] = new byte[10];
names[3] = new byte[10];
names[4] = new byte[10];
int i = 0;
int j;
for (i = 0; i < streams; i++)
{
offset[i] = r.ReadInt32();
ssize[i] = r.ReadInt32();
j = 0;
byte bb;
while (true)
{
bb = r.ReadByte();
if (bb == 0)
break;
names[i][j] = bb;
j++;
}
names[i][j] = bb;
while (true)
{
if (s.Position % 4 == 0)
break;
byte b = r.ReadByte();
if (b != 0)
{
s.Seek(-1, SeekOrigin.Current);
break;
}
}
}
for (i = 0; i < streams; i++)
{
if (names[i][1] == '~')
{
metadata = new byte[ssize[i]];
s.Seek(startofmetadata + offset[i], SeekOrigin.Begin);
for (int k = 0; k < ssize[i]; k++)
metadata[k] = r.ReadByte();
}
if (names[i][1] == 'S')
{
strings = new byte[ssize[i]];
s.Seek(startofmetadata + offset[i], SeekOrigin.Begin);
for (int k = 0; k < ssize[i]; k++)
strings[k] = r.ReadByte();
}
}
valid = BitConverter.ToInt64(metadata, 8);
tableoffset = 24;
rows = new int[64];
Array.Clear(rows, 0, rows.Length);
for (int k = 0; k <= 63; k++)
{
int tablepresent = (int)(valid >> k) & 1;
if (tablepresent == 1)
{
rows[k] = BitConverter.ToInt32(metadata, tableoffset);
tableoffset += 4;
}
}
xyz();
}
//这个函数用来判断第i个元数据表是否存在,此外还得到tableoffset,也就是第i个元数据表的偏移量
public bool tablepresent(byte i)
{
int p = (int)(valid >> i) & 1;
byte[] sizes = {
10, 6, 14, 2, 6, 2, 14, 2, 6, 4, 6, 6, 6, 4,
6, 8, 6, 2, 4, 2, 6, 4, 2, 6, 6, 6, 2, 2, 8,
6, 8, 4, 22, 4, 12, 20, 6, 14, 8, 14, 12, 4
};
for (int j = 0; j < i; j++)
{
int o = sizes[j] * rows[j];
tableoffset = tableoffset + o;
}
if (p == 1)
return true;
else
return false;
}
public void xyz()
{
//从0开始算起,第3个是TypeDef元数据表
bool b = tablepresent(2);
int offs = tableoffset;
//肯定存在,所以这里返回true
if (b)
{
for (int k = 1; k <= rows[2]; k++)
{
TypeAttributes flags = (TypeAttributes)BitConverter.ToInt32(metadata, offs);
offs += 4;
int name = BitConverter.ToInt16(metadata, offs);
offs += 2;
int nspace = BitConverter.ToInt16(metadata, offs);
offs += 2;
int cindex = BitConverter.ToInt16(metadata, offs);
offs += 2;
int findex = BitConverter.ToInt16(metadata, offs);
offs += 2;
int mindex = BitConverter.ToInt16(metadata, offs);
offs += 2;
Console.WriteLine("Row:{0}", k);
Console.WriteLine("Flags : {0}", flags);
Console.WriteLine("Name : {0}", GetString(name));
int u = cindex & 3;
}
}
}
public string GetString(int starting)
{
int i = starting;
while (strings[i] != 0)
{
i++;
}
System.Text.Encoding e = System.Text.Encoding.UTF8;
string s = e.GetString(strings, starting, i - starting);
return s;
}
}
(以上代码部分参考自Vijay Mukhi的Metadata Tables一书,稍有修改并加以中文注释)
这里我只显示类的名称Bao,以及相应的Flags,如下图所示:
再往下写就能写出一个包版的ILDASM了。
老实说,如果不是今天和赵同学吐口水,我吃饱了没事干也不会用UltraEdit32和C#干这个事情。花了大半夜在写这些东西,还是值得的。只是想教育某些人,尤其是牛人,不要总自以为是,认为凡是与你观点不同的就一定是错误的。
其二,赵劼认为我分不清IL和汇编,于是语重心长的摘抄了大段大段的定义来指导我什么是IL,什么又是汇编。选择只有两种,要么不接受,那就是冒天下之大不韪,公然和“教科书”叫板;要么接受,这些定义确实是对的,但就变成了你赵劼对我的教诲和指责也是对的,我也要接受。
我只好耸耸肩以示无奈——用不着你来教我,这些概念我也知道。
IL Assembler究竟是什么?我这里也有一则定义,来自微软MS IL Team,相信要比你用来引经论典的《Essential .NET》和《CLR via C#》更加权威吧:
Unlike high-level languages, and like other assembly languages, ILAsm is platform-driven rather than concept-driven. An assembly language usually is an exact linguistic mapping of the underlying platform, which in this case is the common language runtime.
就是说,“ILAsm不同于高级语言而类似于其他汇编语言,它是平台驱动而非概念驱动的。一门汇编语言通常会与底层平台之间存在精确的语言映射,在目前的情况下,这个底层平台就是CLR”。
我很奇怪,微软既然用的是IL Assembler而不是IL Complier,就已经表明要把它当作一门汇编语言了,但是,为什么又有人跳出来加上“面向对象的”、“高级的”这样的定语以示区别呢?猪就是猪,哪怕它能跳起来像你我一样写代码,也还是猪。更何况连设计者都认同了,可使用者还坚持所谓的“白马非马”之说,这就滑天下之大稽了。
话说,我最不该做的,就是对赵劼认为“不该学习IL”的观点第一个产生异议。老虎的屁股摸不得,于是新仇旧账一起算,列数了我以上若干罪名。不过呢,我也有和赵同学一样的嗜好,就是抓住一点坚决不放手,直到你认罪伏法。
是否要学习一点IL呢,这取决于程序员的修为以及领域。我早在《Expert .NET 2.0 IL Assembler 译者序》中明确指出,对于2-3年编程经验的程序员,学这门技术无异于玩火自焚;5年左右经验也未必,因为你可能习惯了企业级编程,对这些底层的东西难以接受。但是,要看懂赵劼的文章《从汇编入手,探究泛型的性能问题》,没有IL这方面的修为是绝对不行的。但赵同学实在不该画蛇添足,在文章末尾散布“不该学习IL”的言论。这对你的追随者而言无异于画饼充饥。一面在炫耀这东西多好多好,一方面又在告诉大家不要去买,因为你买不起,只能看着我买我享受。
既然赵劼再次提到了我翻译的《Expert .NET 2.0 IL Assembler》一书,我也只好多说几句。其实我一直很感激赵同学,因为他从我翻译伊始就泼冷水,说我会毁了这本书。其实,这种人心肠不坏,只是有一张臭嘴;不过,这反而到激发了我对这本书的精益求精——我还就阿Q了。谢谢,谢谢!
还有就是,没事别总拿MVP当噱头,赵劼同学你在挖苦我的同时,也请注意一下自身形象,想想这样做对己对人的不良影响。