NASM源代码分析--预处理数据结构

来源:互联网 发布:淘宝天机平台论坛 编辑:程序博客网 时间:2024/06/01 09:13

NASM源代码分析--预处理数据结构

       我是在读研究生,09年毕业,以前看过部分Linux源代码和BSDTCP/IP协议,因为没有太全的资料,所以只看了比较重要的部分,没有看全。虽然看源代码是学编程最快捷的方法,但不得不承认看别人的代码有时候是一种煎药,特别是在没有详细的说明和注释的情况下。

由于之前用过一段时间的NASM用来编写一个小病毒,所以才有了看NASM源代码的想法。不过刚开始看NASM源代码的时候,确实有过一段低潮,因为除了源代码,就没有其他中文资料了,还好它的注释写得还是比较详细的。看完之后想留下点什么,所以就有了下面这些学习笔记,希望能给那些看NASM源代码的朋友一点帮助。

 

我是各学理科的,所以文笔不是很好,请大家谅解。当然这些记录肯定也有不对的地方,欢迎大家和我交流探讨,我的E-MAIL:huguozhu@gmail.comhuguozhu@sohu.com,谢谢大家。

 

 

       本文是对前几篇文章的合计,没有新内容,预处理所有重要的数据结构都在这里了。

 

NASM源代码分析之预处理(1

       NASM源代码的整体框架,互联网上已经有资料介绍了,具体的大家可以参见《NASM源码阅读笔记》一文。

       NASM编译源代码的时候,分为三种情况,分别是:

       1):只产生文件依赖关系(operating_mode = op_depend);

       2):只进行预处理(operating_mode = op_preprocess);

       3):预处理并生成目标代码(operating_mode = op_normal)。

 

       第一种情况比较简单,代码量也比较少,很容易就看明白。第二种情况比第一种要难很多,而且涉及的数据结构也很多,刚开始看的时候很容易就看晕了。所以本人根据近段时间以来看代码的体会,总结了下面的内容,希望能给那些看NASM代码的朋友一些帮助。这也是我第一次用这种方式写下我的总结,肯定有不全,甚至时错误的地方,欢迎大家批评指正。我的QQ79943558

      

       preprocess.c是处理预处理的最主要的文件,里面包括了几乎全部关于预处理的内容。其中最基本的结构是Token结构,代码如下:

struct Token                      

{

    Token *next;                

    char *text;                    // 代表的Token内容,如"if"""

       SMacro *mac;                  // 当指向的类型为TOK_SMAC_END有效

       int type;                        // Token类型

};

 

Token在《编译原理》(龙书中文版P18-8行)中的定义是:在一个产生式中,像if和括号这样的词法元素称为记号(token)。在NASM中,token指代的就是源程序中最小的元素。

其中,type的类型包括有:

enum

{

       TOK_WHITESPACE = 1,     // 空格

       TOK_COMMENT,              // 注释

       TOK_ID,                            // 变量名,如 语句:count dd 0x13, count即为TOK_ID

       TOK_PREPROC_ID,           // 预处理名,以形如 %1% 1%{...},

       TOK_STRING,                    // 以单引号('')或双引号("")包括的字符(串)

       TOK_NUMBER,                  // 数字

       TOK_SMAC_END,              // 单行宏结束

       TOK_OTHER,                    //  >>, <<, >=, %%, &&, !=,<>或逗号,句号等

       TOK_SMAC_PARAM,          // 宏参数

    TOK_INTERNAL_STRING  // TOK_STRING类似,只是没有引号

};

 

NASM对源文件进行预处理时,先将之Token化,Token化函数为preprocess.c/tokenise(char *line)

 

例如语句:inc      al;

经过tokenise处理之后分为四个token元素,分别为 1 inc, 2 空格,3 al 4; 。如图:

 

nasm源代码分析之预处理(2

       NASM预处理中,每次从源代码中读取一行,并保存于数据结构Line中。Line指代已经被Token化后的一行源代码。

struct Line                                 // 指代一行代码(经过函数tokenise(char *line)处理后的,其中参数插入char *line为源程序中代码)。

{

    Line *next;                          // Line链表

    MMacro *finishes;

    Token *first;                        // 指向代表"处理后的一行代码"Token链的第一个元素

};

first指向代表该行代码的Token链表,结合《nasm源代码分析之预处理(1)》中的图,如果当前行代码是:“inc al;”,first就指向Token的链表头。

finishes项只有当Line结构指代多行宏的时候才有效,这个以后会有详述。

 

 

对于宏定义语句形如:

#define TRUE 1

#define f(x)  (2*x+3)

NASM中称为单行宏,以区别于用用 %macro...%endmarco 定义的多行宏。单行宏由数据结构struct SMacro表示:

struct SMacro              

{

    SMacro *next;

    char *name;                  // 单行宏名

    int casesense;                // 大小写敏感?

    int nparam;                   // 参数个数

    int in_progress;

    Token *expansion;         // 对应的Token链表

};

name很容易理解,例如宏定义:#define foo(x)  (2*x+3) ,name=foo”。

casesense代表该宏是否大小写敏感,如果casesense==0,则不敏感,即foo(x)FOo(x)FOO(x)fOO(x)指的是同一个宏。casesense!=0,则大小写敏感,即代表的是不同的宏。

nparam指宏的参数个数,上例中参数个数,即nparam=1,如果宏定义为:

#define MYSTRING “Hello World”,nparam = 0。可参见函数:preproc.c/mstrcmp()

in_progress指当前是否有在读取该宏定义。

从本质上来说,struct SMacro代表的也是一行代码,和struct Line差不多,只是Line泛指一般的代码行。所以在SMacro中,Token *expansion的含义和Line中的Token *first项是一样的。

 

 

 

NASM源代码分析之预处理(3

struct Include                     

{   Include *next;                     

    FILE *fp;                                  

    Cond *conds;       

    Line *expansion;

    char *fname;

    int lineno, lineinc;

    MMacro *mstk;                          /* stack of active macros/reps */

};

 

结构Include指代一个被包含的文件,比如在程序开头加入“ %include stdio.h”,则该Include结构即指向stdio.h这个文件,以下解释以这行代码为实例。

结构元素定义:

Include *next : Include 链表指针。

FILE  *fp  : 容易理解,指向该文件的指针。

Cond *conds: 见下面的Cond结构

Line *expansion: 在包含文件中添加额外行,例如在编译命令中通过-D预定义宏,则系统将该定义加入到包含文件中,具体代码大家可参见preproc.c中的Readline函数,开头的处理标准宏和预定义宏就用到了这个属性。

char *fname:   文件名,在此例中,*fname = “stdio.h”

int lineno:    在预定义输出文件中,该包含文件出现的开头行数。

int lineinc:          一般设为1,如果出现代码为:“%line nnn[+mmm] [filename]”时,则lineinc=mmm.

MMacro *mstk:  用于macros/reps,将到MMacro时再细谈。

 

 

 

struct Cond

{   Cond *next;          

    int state;         // 条件状态,内容如下

};

Cond表示一个判断条件,可用于判断文件包含中的判断问题,例如:

       %include “stdio.h”

包含在模块:

       %ifdef ABC

              %include “stdio.h”

              .......

       %endif

如果,ABC未被定义,则代表%include “stdio.h”Include结构的cons项就等于

COND_IF_FALSE

       int state的值包括:

       enum { 

    COND_IF_TRUE, COND_IF_FALSE, 

    COND_ELSE_TRUE, COND_ELSE_FALSE, 

    COND_NEVER

};

大家可能光看就可以明白它的意思了。

在代码中:

%if

...                  (1)

%else

       ...                  (2)

%endif

COND_IF_TRUECOND_IF_FALSE用于在(1)中,

COND_ELSE_TRUECOND_ELSE_FALSE用于(2)中。

 

 

 

NASM源代码分析之预处理(4

上下文栈结构:

struct Context

{    Context *next;

    SMacro *localmac;

    char *name;

    unsigned long number;

};

    NASM中用上下文栈(context-stack)实现在多个宏调用之间共享label(以下摘自《NASM中文手册》,抱歉不知道中文翻译是哪位,忠心感谢该作者),比如一个 ‘repeat' ... 'until'循环,'repeat'宏的展开可能需要能够去引用'until'中定义的宏。而且在 使用这样的宏时,你可能还会嵌套多层循环(具体参见NASM手册4.7 The Context Stack一节)。

如以下代码用到这个特性:

      %macro repeat 0     
          %push   repeat
          %$begin:     
      %endmacro
     
      %macro until 1     
          j%-1    %$begin
          %pop     
      %endmacro
然后象下面这样使用它:
      mov     cx,string
      repeat
      add     cx,3
      scasb
      until   e

注:%$ :会定义一个对于当前栈顶的上下文来讲是本地的lable

   %%:会定义一个对于它所在的那个宏来讲是本地的label一样

在这里使用context-stack是为了让宏repeatuntil中的标号(label%$begin指向一样,因为在两个宏中使用%$begin,当前栈顶都是repeat。当然这只是上下文栈的其中一个用途。

 

结构中项的含义:

Context *next    : 栈链表

SMacro *localmac: %push时,localmac=NULL;其余用于%$标号时有效。

char *name      : 在代码 %push repeat name=repeat”

unsigned long number: 当前栈中context的个数。