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

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

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

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

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

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

  • 标 题:答复
  • 作 者:xiaoming
  • 时 间:2009-06-27 03:25:51

第一章:
源代码是字符串,编译就是把字符串搞成机器码.那就先看一段字符串:
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;
}

注:这个过程在编译原理书上叫词法分析,我写的时候不想提前说,因为没必要,如果说了,读者还有可能去思考词法分析这个词语用在这里合不合适,有没有更好的词语来代替这个词法分析...尽量讲实质.废话尽量少讲.前面能说的废话还很多,我只说介绍一种分解方法,读者听起来好像这个方法就是我原创的一样,不是,方法是书上的,我之所以没说是不想浪费读者的阅读时间.自谦的话,呵呵 哈哈 之类的都是在浪费读者的阅读时间.而记号这个名词是必须的,因为后面老是要用到,这个名字必须要起.

  • 标 题:歪理更新
  • 作 者:xiaoming
  • 时 间:2009-06-28 09:22:07

收集到每个记号的同时应该区分一下,尽可能的多积累一点信息,比如”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是为用户工作的,而不是给程序员去浪费的.那么写书的作者呢?作者该做的事情是把书尽量写好,尽量多为读者做一点,反正写都写了.
写教程的人不同,可以随意发挥.这里说明一下,以免误会.

  • 标 题:更新6-28
  • 作 者:xiaoming
  • 时 间:2009-06-28 15:52:21

下面有一段代码,我们的目标是把这段代码翻译成机器码,首先把它分解成记号:
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没有检查,没关系,山寨版将会用异常处理中报错然后修改指令指针

  • 标 题:先把东西帖上来吧
  • 作 者:xiaoming
  • 时 间:2009-06-29 07:37:00

这里有个山寨"编译器",一开始是照着编译原理书和随书源码模仿着写的,写着写着发现按照它那么专业的方法写下去不知道要写到何年马月,然后尽量能山寨就山寨了,还有编译原理书有的地方理解的也不好有的东西也模仿不好,总之就是山寨不是我所愿意的,只是不山寨我完成不了.
看了开头的人都应该了解,我不是计算机从业人员,写代码从来不讲究什么规范,烂是必然的.
这东西怎么用呢?
它不能生成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文件夹,里面是使用方法代码.

上传的附件 ITCB.rar