首页
社区
课程
招聘
[原创]编译歪理
发表于: 2009-6-27 03:25 12914

[原创]编译歪理

2009-6-27 03:25
12914

废话:
像我这种人玩程序才能称为绝对意义上的”山寨”.没有老师教,没有同事一起学习,像我(非计算机从业人员),忙了一天下班回来写几行代码,有时候草草写了两行实在累了倒下就睡,连脚都不洗.我们这个群体最喜欢易懂的教程,有的教程实在好啊,看了一遍明显感觉到自己真的懂了,有些教程就不一样了,要递归着看,你要翻书你要拖滚动条,最后要看很多遍大脑中才有一个脉络,才能算得上看懂了.

人的智力不尽相同,导致了理解问题有快有慢,但有的能理解有的绝对不能理解的问题应该极少,相对论比较难理解,但我这种庸人穷一生去看也未必不能理解.只是我们想节省点时间,对我而言计算机知识到老也学不完,我只想多学一点,如果教程都成了天书那我还真耗的辛苦.写书的人就不能把问题说的更易接受一些了吗?已经能理解相对论的人就不能把相对论说的更简单一点了吗?我相信只要他有心就一定能!

罗云缤的32位汇编教程和windows程序设计第5版哪个更易理解?我看是罗的书更易理解.汇编作为描述语言应该比用C作为描述语言难懂,但是罗云缤是个有心人,他真的把32位汇编写的比windows程序设计更易懂.

有的高人很热心,看到那些在论坛提问的同学也热心的写出教程来,无奈理解的层次相差比较大,他写的东西别人不一定看的懂,很多他认为显然的问题,别人看来未必显然.而有的牛人也实在太忙,他们着力于把问题说清楚就行了,没有考虑过读者的接受力.其实谁都没有错,教程简单化不是义务.那前面说的不成了废话了吗?是,也不全是.起码把教程简单化是件好事.做义工也不是义务,却有很多人在做.愿意做义工的人请跟我来,听我讲讲我的计划...

看雪论坛是一块学习的净土,在看雪提问不用担心有人教训你,国内真的是独此一家.csdn都有不少人说话教训人的口气很重,看雪没有,这也是我这种菜鸟也敢在这里胡扯的原因.也只有在这里我才敢大声的喊出自己的想法:教程文学  文学可谓是花样繁多,多数为表达情感,叙述故事.这些文章都各有规范,这些规范也确实使的这些文章有血有肉.但是技术文章到目前为止都是自定义文体,都是作者自己发挥的.有的发挥的好,有的发挥的不是很好,我觉得这样不是太好,因为技术太重要了,好的教程让一个从未接触过某项技术的人看了就可以投入工作了,这才是文学的真正作用.我认一句死理,吟诗能促进生产力发展吗?我看只能抒发下感情,陶冶下情操.这种东西都能棱角分明,有鼻有眼,教程这么重要的东西到现在连个基本原则都没有,个人认为方向不对.科学技术是第一生产力,教程是传播技术的,教程的地位应该高,很高.

说到这里,发觉我似乎在提倡什么,我又开始惶恐了,我拿什么来作为提倡的资本呢?我什么也没有,不过我可以提出来,规则自然有人会定出来.说了这么多还没说教程文学是个什么样的文学.其实我也说不好.但是我心里似乎有个样子,我决定举个例子来描述我心目中的教程文学.讲述一个山寨中的山寨编译器,请留意一下帖子的标题:编译歪理


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (30)
雪    币: 14
活跃值: (28)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
第一章:
源代码是字符串,编译就是把字符串搞成机器码.那就先看一段字符串:
int add(int x,int y);
怎么把这个字符串理解为一个函数申明?我有个提议,把它先分解了, 用链表存放,分解成这样:
“int” ”add” “(” “int” “x” “,” “int” “y” “)” “;”
把这些分解得到的东西叫”记号”

介绍一种分解的方法:
#define SCAN_STATE_START        1        //开始扫描
#define SCAN_STATE_STRING        2        //int 、add、x、y之类的

BOOL Scan(const char *pSource,_TOKEN *pToken)
{
        int  iLen=1; // 记号长度
        int  iScanstate=SCAN_STATE_START; // 扫描状态
        char *pSt=NULL; // 记号的开始,记号搜集完整后根据pSt和iLen把完整的记号添加到链表
        for(char *p=pSource;p!=0;p++)
{
                char c=*p;
                switch(c)
                {
                        case SCAN_STATE_START: // 新的记号开始了
                                if(isalpha(c) || c=='_') // 种种迹象表明,这个记号是个字符串
{
                                        iScanstate=SCAN_STATE_CHAR;
                                        pSt=p; // 保存记号的开始位置
                                        iLen=1;// 记号的长度
                                }
                                .
                                .
                                .
break;

case SCAN_STATE_CHAR: // 记号是字符串而且还没搜集完
if(isalnum(c) || c=='_') iLen++; //记号长度加1
else // 记号搜集完整了
{
                                        // 把收集到的记号添加到链表
                                        // 这里可以把记号细分一下
        p--; // 退回到记号的最后一个字符
iScanstate=SCAN_STATE_START; // 迎接下一个记号
}break;
.
.
.
}
}
        return TURE;
}

注:这个过程在编译原理书上叫词法分析,我写的时候不想提前说,因为没必要,如果说了,读者还有可能去思考词法分析这个词语用在这里合不合适,有没有更好的词语来代替这个词法分析...尽量讲实质.废话尽量少讲.前面能说的废话还很多,我只说介绍一种分解方法,读者听起来好像这个方法就是我原创的一样,不是,方法是书上的,我之所以没说是不想浪费读者的阅读时间.自谦的话,呵呵 哈哈 之类的都是在浪费读者的阅读时间.而记号这个名词是必须的,因为后面老是要用到,这个名字必须要起.
2009-6-27 03:25
0
雪    币: 14
活跃值: (28)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
有点小问题,文本格式贴上来有点乱了,把word文档发上来吧
上传的附件:
2009-6-27 03:29
0
雪    币: 158
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
谢谢 支持了!
2009-6-27 04:02
0
雪    币: 158
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
我是菜鸟不多废话 收藏先!
2009-6-27 04:06
0
雪    币: 2242
活跃值: (488)
能力值: ( LV9,RANK:200 )
在线值:
发帖
回帖
粉丝
6
mark一下
2009-6-27 05:08
0
雪    币: 142
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
没有老师教,没有同事一起学习,像我(非计算机从业人员),忙了一天下班回来写几行代码,有时候草草写了两行实在累了倒下就睡,连脚都不洗

我们怎么那么像???
2009-6-27 08:31
0
雪    币: 370
活跃值: (52)
能力值: ( LV13,RANK:350 )
在线值:
发帖
回帖
粉丝
8
很好 支持加学习 还有谢谢 关注更新
2009-6-27 13:13
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
关注。。。。。。。
2009-6-27 18:47
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
10
看了我才明白为什么很少交流技术...之所以发明这么多名词....主要是方便交流...
2009-6-28 03:01
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
支持!
收下了。谢谢楼主的编译歪理
2009-6-28 08:25
0
雪    币: 227
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
就是自动机咯,
做这个东西还是要看编译原理的。。
2009-6-28 09:16
0
雪    币: 14
活跃值: (28)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
收集到每个记号的同时应该区分一下,尽可能的多积累一点信息,比如”while”跟”add”肯定是有区别的,在定义链表节点的时候最好能反映出这种区别:
struct _TOKEN
{
int                 iTType;  // 记号具体类型
int                 iTTypex; // 记号模糊类型
int                iLine;   // 记号所在行
char        *pStr;         // 记号字符串
_TOKEN        *pNext;         // 下一个记号
};
数字总是比字符串容易处理,iTType就是要把”for”与数字200对应起来,把”+”与数字43对应起来,我们把他们定义成宏,选几个典型的列出来:
#define TT_ADD                        43      // +
#define TT_FOR                        200     // for
#define TT_INT                        250     // int
#define TT_NAME                        300     // 变量名或函数名,因为现在还无法区别
#define TT_CONST                301     // 常数

“for”与”+”是有区别的,iTTypex就是要反映这种区别,也选典型的列出来:
#define TTEX_OP                        1       // 运算符(+,-,>>,...)
#define TTEX_OPP                2       // 运算参数(变量,函数,常数)
#define TTEX_KW                        3       // 保留字(if,while,...)
#define TTEX_DT                        4       // 数据类型(int,void,...)

收集到一个记号的时候,就顺便把记号的iTType和iTTypex求出来,做到这点应该是很容易的.也许你还想支持”typedef”,”define”,”include”.建议第一次写不要去支持,编译器写完后可以再考虑这些问题.

注:读者自己应该知道记号链表怎么定义,我还是把我的思路写出来了,我想:能让读者用眼睛就能看明白最好,何必让他用脑子去想呢?
写程序的人应该都很注重代码的效率,如今cpu运行速度已经够快了,代码效率问题已经淡化了,为什么大家还这么重视效率呢?程序员在这点上都很明理,都知道cpu是为用户工作的,而不是给程序员去浪费的.那么写书的作者呢?作者该做的事情是把书尽量写好,尽量多为读者做一点,反正写都写了.
写教程的人不同,可以随意发挥.这里说明一下,以免误会.
2009-6-28 09:22
0
雪    币: 14
活跃值: (28)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
14
下面有一段代码,我们的目标是把这段代码翻译成机器码,首先把它分解成记号:
int ig;
void Fun(int ix,int iy)
{
        if(ix>iy)
        {
                if(10==iy)
                {
                        iy=ix+ig;
                }
                ix=iy;
        }
}
void Fun2()
{

}
分解成的记号如下:



接下来我们要分析这些记号了.
看上面的源码,我用一块灰布把函数体挡住了,也就是说看不到函数体了,那就假装它不存在.发现只有变量定义和函数头,如果想支持更多的话可能还有”define”,”include”,结构体定义...但是决不可能有表达式,if之类的.接下来就开始分析可见部分:
BOOL Parse()
{
        _TOKEN        *pEnd=NULL;
        for(_TOKEN *p=”记号”表头指针;p!=NULL;p=p->pNext){
                if(p->iTTypex==TTEX_DT){// 数据类型
                        if(VarDefine(p,&pEnd)==TRUE){// 是变量定义吗?
                                p=pEnd; continue;
}
if(FunDeclare(p,&pEnd)==TRUE){// 是函数申明吗?
        p=pEnd; continue;
}
if(FunDefine(p,&pEnd)==TRUE){// 是函数定义吗?
        p=pEnd; continue;
}
...
return FALSE;
}
else if(...){...}
else return FALSE;
}
return TRUE;
}
VarDefine,FunDeclare,FunDefine三个函数都没具体实现,但是它们的意图是很明显的,就拿VarDefine来说,从p指向的记号节点开始分析,如果确实是变量定义就将变量定义的结束记号也就是”;”的指针填写在pEnd中,并且返回TRUE.如果不是变量定义返回FALSE,把机会让给FunDeclare函数...

注:
1.我总结了某些书看的累的原因:战线拉的太长.读者遇到一个问题可能要看十几页甚至几十页才能找到答案,是跳过去找答案还是继续往下看?如果跳过去,找到了答案,但是往往又有新的问题或者其他原因导致你找答案也不应看懂,只好来来回回只到把该熟悉的都熟悉,该记住的都记住了才能看懂.在没看懂的时候还有可能种种原因不能继续看下去,第2天再看的时候发现前面有东西忘记了,这种书你必须长期看下去了.看到熟透了,基本上才有看懂的希望.
在实现VarDefine等函数之前要明确一下目标:把”记号”翻译成中间代码,取名为”记号码”,用链表存放.下面是”记号码”表的节点定义:
struct _TCODE
{
        int                iTCType;        // 记号码类型
        union                                // 可选成员
{
                char *pFunName; // 函数名(翻译成机器码的时候需要)
                _EXP *pExp;                // 表达式链表头指针(先不管它)
}Option;
_TCODE  *pNext;                // 下一个记号码
};
“记号码”的种类也是有限的,所以也用宏来表示它的类型.
#define                TCT_FUN_ENTRY                        1  // 函数入口
#define                TCT_FUN_EXIT                        2  // 函数出口
#define                TCT_IF_CONDITION                3  // if条件
#define                TCT_ELSE_IF_CONDITION        4  // else if条件
#define                TCT_ELSE                                5  // else
#define                TCT_IF_END                                6  // if结束
#define                TCT_ELSE_IF_END                        7  // else if结束
#define                TCT_ELSE_END                        8  // else 结束
#define                TCT_LOOP_ENTRY                        9  // 循环入口
#define                TCT_LOOP_CONDITION                10 // 循环条件
#define                TCT_LOOP_EXTI                        11 // 循环出口
#define                TCT_BREAK                                12 // break
#define                TCT_CONTINUE                        13 // continue
#define                TCT_JMP_LOOP_ENTRY                14 // 跳转到循环入口
#define                TCT_JMP_LOOP_EXIT                15 // 跳转到循环出口
#define                TCT_JMP_ELSE_END                16 // 跳转到else结束

现在就来实现VarDefine等函数,挑选FunDefine函数来实现:
BOOL FunDefine(_TOKEN *pSt,_TOKEN **pEnd)
{
_TOKEN *pEnd=NULL;
_TCODE *pTcode=NULL;
        int iRetype=pSt->iTType; // 函数返回值类型
        int iRetgrade=0; // 函数返回值指针层数,比如这种函数:int ******Fun()...
        int iCall=TT_STDCALL; // 函数调用约定,让他默认为_stdcall
        char *pName=NULL;
        for(_TOKEN *p=pSt;p->iTType==TT_PS;p=p->pNext) iRetgrade++;//TT_PS:”*”
        if(p->iTTypex==TTEX_CALL){iCall=p->iTType; p=p->pNext;}//如果有调用约定,取之
        if(p->iTType==TT_NAME){
                pName=new char[strlen(p->pName)+1];
                strcpy(pName,p->pName);
                p=p->pNext;
        }else return FLASE;
        if(p->iTType==TT_LPAR) p=p->pNext; else {delete []pName;return FALSE;}
        pTcode=AddTcode(&记号码头指针,TCT_FUN_ENTRY,pName); // 添加记号码
if(GetParam(p,pTcode,&pEnd)==FALSE){
delete[]pName;returnFALSE;}else p=p->pNext;
        if(Match(p,pName,&pEnd)==FALSE){delete []pName; return FALSE;}
        AddTcode(&记号码头指针,TCT_FUN_EXIT,pName); // 添加记号码
        delete []pName;
return TRUE;
}

注:
1.        这种分析方法很山寨,不过容易理解,先通过这种分析法来了解总体思路,正则表达式方法后面再说
2.        代码写的太挤可读性差,只是是希望能在一页之内写完,遇到问题查找也快
3.        看上对错误检查不够仔细,某些错误是可以留到后面的,最主要的是指针是否为NULL没有检查,没关系,山寨版将会用异常处理中报错然后修改指令指针
上传的附件:
2009-6-28 15:52
0
雪    币: 14
活跃值: (28)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15
这里有个山寨"编译器",一开始是照着编译原理书和随书源码模仿着写的,写着写着发现按照它那么专业的方法写下去不知道要写到何年马月,然后尽量能山寨就山寨了,还有编译原理书有的地方理解的也不好有的东西也模仿不好,总之就是山寨不是我所愿意的,只是不山寨我完成不了.
看了开头的人都应该了解,我不是计算机从业人员,写代码从来不讲究什么规范,烂是必然的.
这东西怎么用呢?
它不能生成exe文件,但是它能生成机器码,生成机器码的同时它会把机器码复制到进程中的某一块地方.比如写了这么几行代码:
int iTest;
void OnMessage()
{
      iTest=18;
}

生成的机器码的汇编形式应该是这样:
push ebp
mov  ebp,esp
push esi
push edi
mov [00x40xxxx],0x12
pop  edi
pop  esi
mov esp.ebp
pop ebp
retn 0
这些函数的地址都是保存起来的,可以随时查找并调用.目标进程中的资源也是可以被"编译器"利用的,比如MessageBox函数,你可以把它注册为编译器的工具函数,注册之后就可以调用了.如果已经把wsprintf函数注册成工具函数的话:
int iTest;
void OnMessage()
{
      iTest=18;
      char szTest[24];
      wsprintf(szTest,"%d",iTest);
      MessageBox(0,"",0,0);
}
然后就可以在进程中的任何地方查找OnMessage函数的地址,然后调用.这个到底有什么用呢?
1.软件的用户接口.
你写了一个抓网络封包的工具,数据包加密部分是不确定的,你可以在截获数据包的时候,看看用户有没有自己写解密函数,如果有,调用之后再显示解密结果.如果用户没写解密函数,那就显示未解密的数据.
2.注入到目标进程.
这个"编译器"是个dll形式的,把这个dll注入到目标进程,然后想办法做其他动作,做什么动作我不知道,不过应该有点作用的.
感觉类似这样的东西应该有点小用,可惜我写不好,如果有人有实力做个专业点的话不妨做一个.

工程目录下有个Use文件夹,里面是使用方法代码.
上传的附件:
2009-6-29 07:37
0
雪    币: 204
活跃值: (33)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
楼主道出了我们的心声,痛快
2009-6-29 10:45
0
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
17
支持+学习~
2009-7-1 12:33
0
雪    币: 442
活跃值: (107)
能力值: ( LV9,RANK:350 )
在线值:
发帖
回帖
粉丝
18
我发现罗云彬名字写错的大有人在,楼主你也没有逃脱魔爪哦...
2009-7-1 12:45
0
雪    币: 206
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
同是非专(业)编程人,相逢何必曾相识!
2009-7-1 13:36
0
雪    币: 53
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
支持楼主-_-
2009-7-1 13:52
0
雪    币: 392
活跃值: (89)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
21
呵呵,我是专科出身的,但是我觉得,凡是热爱技术的大家都一样,没有高低之分。摆正心态,努力去学习奋斗。我最喜欢C/ASM,擅长C++,但是到了公司,发现真正重要的是解决问题的能力,是学习新知识的能力,以前的基础和经验都只是铺垫。
书籍,能让读者容易明白才是好书,要言之有物。我喜欢文档,不管是工具类还是思想类,当时学windbg就是把文档翻了个遍。文档最大的特点就是:逻辑清晰,简约。
很同意LZ的想法,用简单的语言来描述事物,并且表达清楚,那样的作者才是受欢迎的。
2009-7-2 18:20
0
雪    币: 234
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
楼主辛苦,等等下次更新
2009-7-2 19:04
0
雪    币: 177
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tbc
23
。。。。。。。。。
2009-7-8 08:03
0
雪    币: 269
活跃值: (25)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
24
LZ第一贴的话挺棒的,深有感受啊。
2009-8-13 07:50
0
雪    币: 209
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
Mark,谢谢
2009-8-14 23:52
0
游客
登录 | 注册 方可回帖
返回
//