小赵的IL无用论是否站得住脚?

2026-05-25 09:223阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2751个文字,预计阅读时间需要12分钟。

小赵的IL无用论是否站得住脚?

我明白您的意思。以下是对原文的简化:

理解越深。我不是来打架的,而是来澄清一些概念的。赵丽的观点其实有些偏激,但与意见相左的,一股蛮横追杀到底。赵同学在社区的影响力在我之上,但这正是其可怖之处。

道理越辨越明。我不是来吵架的,而是来澄清一些概念。赵劼的看法实在有些偏激,但凡与之意见向左的,一概穷追猛打至死。赵同学在社区的影响力在我之上,但这也正是其可怕之处——一旦有所偏差,必然会误导更多的朋友。有感于他的毁人不倦大多穿凿附会之说,于是,暂时搁下手上的工作,发此文以正视听。

综观赵劼《老赵谈IL:IL是什么,它又不是什么?那么汇编呢?》一文,对我的指责主要集中在2个地方:

其一:我曾经指出,UltraEdit32也可以观察IL中的一些数据,但是没有给出具体操作办法。于是赵劼就单方面理解为我是信口开河,并多次在公开场合抓住这个话柄对我进行诋毁。我其实不是很想提及这个办法,所以一直没有说破,因为这个法子太过BT。介绍如下:

首先,我们信手写一个简单的a.cs文件:

publicclassBao
{
publicstaticvoidMain()
{
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
usingSystem;
usingSystem.IO;
usingSystem.Reflection;

publicclasszzz
{
publicstaticvoidMain()
{
zzza=newzzz();
a.abc();
}

inttableoffset;
int[]rows;
int[]offset;
int[]ssize;
byte[]metadata;
byte[]strings;
longvalid;
byte[][]names;

publicvoidabc()
{
longstartofmetadata;

FileStreams=newFileStream(@"C:\a.exe",FileMode.Open);
BinaryReaderr=newBinaryReader(s);

s.Seek(360,SeekOrigin.Begin);

intrva,size;
rva=r.ReadInt32();
size=r.ReadInt32();

intwhere=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);

intstreams=r.ReadInt16();
offset=newint[5];
ssize=newint[5];
names=newbyte[5][];
names[0]=newbyte[10];
names[1]=newbyte[10];
names[2]=newbyte[10];
names[3]=newbyte[10];
names[4]=newbyte[10];

inti=0;
intj;

for(i=0;i<streams;i++)
{
offset[i]=r.ReadInt32();
ssize[i]=r.ReadInt32();

j=0;
bytebb;

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;

byteb=r.ReadByte();
if(b!=0)
{
s.Seek(-1,SeekOrigin.Current);
break;
}
}
}

for(i=0;i<streams;i++)
{
if(names[i][1]=='~')
{
metadata=newbyte[ssize[i]];
s.Seek(startofmetadata+offset[i],SeekOrigin.Begin);

for(intk=0;k<ssize[i];k++)
metadata[k]=r.ReadByte();
}

if(names[i][1]=='S')
{
strings=newbyte[ssize[i]];

s.Seek(startofmetadata+offset[i],SeekOrigin.Begin);

for(intk=0;k<ssize[i];k++)
strings[k]=r.ReadByte();
}
}

valid=BitConverter.ToInt64(metadata,8);
tableoffset=24;
rows=newint[64];

Array.Clear(rows,0,rows.Length);

for(intk=0;k<=63;k++)
{
inttablepresent=(int)(valid>>k)&1;
if(tablepresent==1)
{
rows[k]=BitConverter.ToInt32(metadata,tableoffset);
tableoffset+=4;
}
}

xyz();
}

//这个函数用来判断第i个元数据表是否存在,此外还得到tableoffset,也就是第i个元数据表的偏移量
publicbooltablepresent(bytei)
{
intp=(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(intj=0;j<i;j++)
{
into=sizes[j]*rows[j];
tableoffset=tableoffset+o;
}

if(p==1)
returntrue;
else
returnfalse;
}

publicvoidxyz()
{
//从0开始算起,第3个是TypeDef元数据表
boolb=tablepresent(2);
intoffs=tableoffset;

//肯定存在,所以这里返回true
if(b)
{
for(intk=1;k<=rows[2];k++)
{
TypeAttributesflags=(TypeAttributes)BitConverter.ToInt32(metadata,offs);

offs+=4;
intname=BitConverter.ToInt16(metadata,offs);

offs+=2;
intnspace=BitConverter.ToInt16(metadata,offs);

offs+=2;
intcindex=BitConverter.ToInt16(metadata,offs);

offs+=2;
intfindex=BitConverter.ToInt16(metadata,offs);

offs+=2;
intmindex=BitConverter.ToInt16(metadata,offs);

offs+=2;

Console.WriteLine("Row:{0}",k);
Console.WriteLine("Flags:{0}",flags);
Console.WriteLine("Name:{0}",GetString(name));
intu=cindex&3;
}
}
}

publicstringGetString(intstarting)
{
inti=starting;
while(strings[i]!=0)
{
i++;
}

System.Text.Encodinge=System.Text.Encoding.UTF8;
strings=e.GetString(strings,starting,i-starting);

returns;
}
}

(以上代码部分参考自Vijay Mukhi的Metadata Tables一书,稍有修改并加以中文注释)

这里我只显示类的名称Bao,以及相应的Flags,如下图所示:

再往下写就能写出一个包版的ILDASM了。

老实说,如果不是今天和赵同学吐口水,我吃饱了没事干也不会用UltraEdit32和C#干这个事情。花了大半夜在写这些东西,还是值得的。只是想教育某些人,尤其是牛人,不要总自以为是,认为凡是与你观点不同的就一定是错误的。

其二,赵劼认为我分不清IL和汇编,于是语重心长的摘抄了大段大段的定义来指导我什么是IL,什么又是汇编。选择只有两种,要么不接受,那就是冒天下之大不韪,公然和“教科书”叫板;要么接受,这些定义确实是对的,但就变成了你赵劼对我的教诲和指责也是对的,我也要接受。

我只好耸耸肩以示无奈——用不着你来教我,这些概念我也知道。

IL Assembler究竟是什么?我这里也有一则定义,来自微软MS IL Team,相信要比你用来引经论典的《Essential .NET》和《CLR via C#》更加权威吧:

小赵的IL无用论是否站得住脚?

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当噱头,赵劼同学你在挖苦我的同时,也请注意一下自身形象,想想这样做对己对人的不良影响。

本文共计2751个文字,预计阅读时间需要12分钟。

小赵的IL无用论是否站得住脚?

我明白您的意思。以下是对原文的简化:

理解越深。我不是来打架的,而是来澄清一些概念的。赵丽的观点其实有些偏激,但与意见相左的,一股蛮横追杀到底。赵同学在社区的影响力在我之上,但这正是其可怖之处。

道理越辨越明。我不是来吵架的,而是来澄清一些概念。赵劼的看法实在有些偏激,但凡与之意见向左的,一概穷追猛打至死。赵同学在社区的影响力在我之上,但这也正是其可怕之处——一旦有所偏差,必然会误导更多的朋友。有感于他的毁人不倦大多穿凿附会之说,于是,暂时搁下手上的工作,发此文以正视听。

综观赵劼《老赵谈IL:IL是什么,它又不是什么?那么汇编呢?》一文,对我的指责主要集中在2个地方:

其一:我曾经指出,UltraEdit32也可以观察IL中的一些数据,但是没有给出具体操作办法。于是赵劼就单方面理解为我是信口开河,并多次在公开场合抓住这个话柄对我进行诋毁。我其实不是很想提及这个办法,所以一直没有说破,因为这个法子太过BT。介绍如下:

首先,我们信手写一个简单的a.cs文件:

publicclassBao
{
publicstaticvoidMain()
{
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
usingSystem;
usingSystem.IO;
usingSystem.Reflection;

publicclasszzz
{
publicstaticvoidMain()
{
zzza=newzzz();
a.abc();
}

inttableoffset;
int[]rows;
int[]offset;
int[]ssize;
byte[]metadata;
byte[]strings;
longvalid;
byte[][]names;

publicvoidabc()
{
longstartofmetadata;

FileStreams=newFileStream(@"C:\a.exe",FileMode.Open);
BinaryReaderr=newBinaryReader(s);

s.Seek(360,SeekOrigin.Begin);

intrva,size;
rva=r.ReadInt32();
size=r.ReadInt32();

intwhere=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);

intstreams=r.ReadInt16();
offset=newint[5];
ssize=newint[5];
names=newbyte[5][];
names[0]=newbyte[10];
names[1]=newbyte[10];
names[2]=newbyte[10];
names[3]=newbyte[10];
names[4]=newbyte[10];

inti=0;
intj;

for(i=0;i<streams;i++)
{
offset[i]=r.ReadInt32();
ssize[i]=r.ReadInt32();

j=0;
bytebb;

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;

byteb=r.ReadByte();
if(b!=0)
{
s.Seek(-1,SeekOrigin.Current);
break;
}
}
}

for(i=0;i<streams;i++)
{
if(names[i][1]=='~')
{
metadata=newbyte[ssize[i]];
s.Seek(startofmetadata+offset[i],SeekOrigin.Begin);

for(intk=0;k<ssize[i];k++)
metadata[k]=r.ReadByte();
}

if(names[i][1]=='S')
{
strings=newbyte[ssize[i]];

s.Seek(startofmetadata+offset[i],SeekOrigin.Begin);

for(intk=0;k<ssize[i];k++)
strings[k]=r.ReadByte();
}
}

valid=BitConverter.ToInt64(metadata,8);
tableoffset=24;
rows=newint[64];

Array.Clear(rows,0,rows.Length);

for(intk=0;k<=63;k++)
{
inttablepresent=(int)(valid>>k)&1;
if(tablepresent==1)
{
rows[k]=BitConverter.ToInt32(metadata,tableoffset);
tableoffset+=4;
}
}

xyz();
}

//这个函数用来判断第i个元数据表是否存在,此外还得到tableoffset,也就是第i个元数据表的偏移量
publicbooltablepresent(bytei)
{
intp=(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(intj=0;j<i;j++)
{
into=sizes[j]*rows[j];
tableoffset=tableoffset+o;
}

if(p==1)
returntrue;
else
returnfalse;
}

publicvoidxyz()
{
//从0开始算起,第3个是TypeDef元数据表
boolb=tablepresent(2);
intoffs=tableoffset;

//肯定存在,所以这里返回true
if(b)
{
for(intk=1;k<=rows[2];k++)
{
TypeAttributesflags=(TypeAttributes)BitConverter.ToInt32(metadata,offs);

offs+=4;
intname=BitConverter.ToInt16(metadata,offs);

offs+=2;
intnspace=BitConverter.ToInt16(metadata,offs);

offs+=2;
intcindex=BitConverter.ToInt16(metadata,offs);

offs+=2;
intfindex=BitConverter.ToInt16(metadata,offs);

offs+=2;
intmindex=BitConverter.ToInt16(metadata,offs);

offs+=2;

Console.WriteLine("Row:{0}",k);
Console.WriteLine("Flags:{0}",flags);
Console.WriteLine("Name:{0}",GetString(name));
intu=cindex&3;
}
}
}

publicstringGetString(intstarting)
{
inti=starting;
while(strings[i]!=0)
{
i++;
}

System.Text.Encodinge=System.Text.Encoding.UTF8;
strings=e.GetString(strings,starting,i-starting);

returns;
}
}

(以上代码部分参考自Vijay Mukhi的Metadata Tables一书,稍有修改并加以中文注释)

这里我只显示类的名称Bao,以及相应的Flags,如下图所示:

再往下写就能写出一个包版的ILDASM了。

老实说,如果不是今天和赵同学吐口水,我吃饱了没事干也不会用UltraEdit32和C#干这个事情。花了大半夜在写这些东西,还是值得的。只是想教育某些人,尤其是牛人,不要总自以为是,认为凡是与你观点不同的就一定是错误的。

其二,赵劼认为我分不清IL和汇编,于是语重心长的摘抄了大段大段的定义来指导我什么是IL,什么又是汇编。选择只有两种,要么不接受,那就是冒天下之大不韪,公然和“教科书”叫板;要么接受,这些定义确实是对的,但就变成了你赵劼对我的教诲和指责也是对的,我也要接受。

我只好耸耸肩以示无奈——用不着你来教我,这些概念我也知道。

IL Assembler究竟是什么?我这里也有一则定义,来自微软MS IL Team,相信要比你用来引经论典的《Essential .NET》和《CLR via C#》更加权威吧:

小赵的IL无用论是否站得住脚?

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当噱头,赵劼同学你在挖苦我的同时,也请注意一下自身形象,想想这样做对己对人的不良影响。