`
haoningabc
  • 浏览: 1476591 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

NASM源码阅读笔记

阅读更多
参考 http://tieba.baidu.com/p/587614377

NASM源码阅读笔记
  NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码
提供了很大的方便。按作者的说法,这是一个模块化的,可重用的x86汇编器,
而且能够被嵌入进其它的程序中,比如做为一个高级语言编译器的后端程序。下面
的文字希望能对大家有所帮助。错误之处,多多指正。:>

一、各模块简介:
    NASM按功能将汇编器的各个部分独立为模块,各个模块之间并不进行直接的联系。
    这种编程结构使得阅读源代码变得轻松起来,可以将各个模块独立出来分别阅读。
    在源码中的doc/internal.doc中有一个模块间的结构图:

                  +--- preproc.c ----+
                  |                  |
                  +---- parser.c ----+
                  |        |         |
                  |     float.c      |
                  |                  |
                  +--- assemble.c ---+
                  |        |         |
        nasm.c ---+     insnsa.c     +--- nasmlib.c
                  |                  |
                  +--- listing.c ----+
                  |                  |
                  +---- labels.c ----+
                  |                  |

0
回复
1楼2009-06-03 06:01举报 |
 
DJ南
                  +--- outform.c ----+
                  |                  |
                  +----- *out.c -----+
  图中没有出现的其它模块有eval.c和float.c。eval.c是表达式求值模块,用于
  preproc.c,parser.c和*out.c;float.c是浮点数常量存储转换模块,用于parser.c。
  各模块将需要导出的函数和数据结构的定义放入对应的头文件中,以便被其它模块调用。
  /*需要注意的是许多函数指针或包含函数指针的结构以参数的形式在各模块间传递,这种
   *间接性使得模块之间的结构更加清晰。
   */

 1、强大的预处理器preproc.c:

    预处理部分是NASM中最复杂,也是代码最多的模块,有四千多行代码。
    它使用自己的源码扫描程序ppscan,支持名字相同但参数不同的宏,支持
    各个宏通过上下文相关堆栈来交换信息,支持宏内进行预处理循环等。
     (可参阅NASM使用文档来了解预处理的功能和使用方法)

        preproc.c对外仅导出一个结构:
           typedef struct {
               /*
                * Called at the start of a pass; given a file name, the number
                * of the pass, an error reporting function, an evaluator
                * function, and a listing generator to talk to.
                */
               void (*reset) (char *, int, efunc, evalfunc, ListGen *);

               /*
                * Called to fetch a line of preprocessed source. The line
                * returned has been malloc‘ed, and so should be freed after
                * use.

回复 2楼2009-06-03 06:01举报 |
 
DJ南
                */
               char *(*getline) (void);

               /*
                * Called at the end of a pass.
                */
               void (*cleanup) (int);
           } Preproc;

           其中reset用于初始化,cleanup用于预处理后的清理工作。
           而函数名getline则是相对于主程序来说的。它对编译器内嵌的宏和汇编源程序
           从上至下进行处理,记录遇到的宏信息,用实际的代码和数据替换调用的宏,并
           返回一行可以被下一个模块(这里是parser.c)使用的“规范的”汇编代码。
           /*预处理部分相对于主程序后面调用的模块来说是透明的。*/

   2、功能单一的解析模块parser.c:
            按作者的说法,解析模块的作用就是解析由`标号‘,`指令‘,`操作数‘和`注释‘组成的
        “规范的”汇编代码行,填充并返回这行代码的insn结构:
           typedef struct {                    /* an instruction itself */
               char *label;                    /* the label defined, or NULL */
               int prefixes[MAXPREFIX];       /* instruction prefixes, if any */
               int nprefix;                    /* number of entries in above */
               int opcode;                     /* the opcode - not just the string */

回复 3楼2009-06-03 06:01举报 |
 
DJ南
               int condition;                  /* the condition code, if Jcc/SETcc */
               int operands;                   /* how many operands? 0-3 
                                               * (more if db et al) */
               operand oprs[3];        /* the operands, defined as above */
               extop *eops;                    /* extended operands */
               int eops_float;                /* true if DD and floating */
               long times;                     /* repeat count (TIMES prefix) */
               int forw_ref;                   /* is there a forward reference? */
           } insn;

           该模块导出三个函数:
           void parser_global_info (struct ofmt *output, loc_t *locp);
           insn *parse_line (int pass, char *buffer, insn *result,
                          efunc error, evalfunc evaluate, ldfunc ldef);
           void cleanup_insn (insn *instruction);

回复 4楼2009-06-03 06:01举报 |
 
DJ南

           其中parser_global_info用于解析前的初始化工作(这里是初始化输出的文件格式和
           当前行所在的位置,在定义标号时需要用到)。cleanup_insn用于解析后的清理工作。

           而parse_line的功能就是按照上面提到的`标号‘,`指令‘,`操作数‘和`注释‘的
       顺序对buffer里的代码进行解析,并对相应部分的合法性做一些检查,比如检查指令的前缀是
       不是超过了所能允许的最大值,操作数是否为内存地址,代码中是否含有一个向前的引用等。
       它还提供了对伪指令DB,DD,RESB,RESD,INCBIN等的支持。

   3、代码生成器assemble.c:

         当parser.c填充好insn结构后,assemble.c就按照Intel机器码规则生成实际的
      机器码,并传给out*.c来生成具体格式的目标文件。

           它导出两个函数:
           long insn_size (long segment, long offset, int bits, unsigned long cpu,
                        insn *instruction, efunc error);
           long assemble (long segment, long offset, int bits, unsigned long cpu,
                       insn *instruction, struct ofmt *output, efunc error,
                       ListGen *listgen);
           其中insn_size一般用在第一遍分析源码时,用于确定某一行代码在实际目标文件中所需的
       空间,而assemble则一般用在第二遍分析源码时,它转化insn结构中的指令为实际的机器码,
       并输出到out*.c中以生成具体格式的目标文件。

   4、简单的表达式求值模块eval.c:
       eval.c用于计算并返回代码中表达式的值,其中运算符||,^^,&&等用于条件汇编%if类指令
     的表达式中,返回真或假,|,^,&,<<,+,*,/等用于对常量值进行运算,SEG,WRT等则用于得到
       程序中段(或节)实际的基址和偏移。
           eval.c的功能比较简单,不能像MASM那样处理类似(eax != 0)这样的表达式。(后记1)。
       eval.c利用标准的scan程序扫描表达式缓冲区,找出存在的运算符进行运算,并将

回复 5楼2009-06-03 06:01举报 |
 
DJ南
     处理后的值存入expr结构中:
            /*
             * Expression-evaluator datatype. Expressions, within the
             * evaluator, are stored as an array of these beasts, terminated by
             * a record with type==0. Mostly, it‘s a vector type: each type
             * denotes some kind of a component, and the value denotes the
             * multiple of that component present in the expression. The
             * exception is the WRT type, whose `value‘ field denotes the
             * segment to which the expression is relative. These segments will
             * be segment-base types, i.e. either odd segment values or SEG_ABS
             * types. So it is still valid to assume that anything with a
             * `value‘ field of zero is insignificant.
             */
            typedef struct {
                long type;                             /* a register, or EXPR_xxx */
                long value;                            /* must be >= 32 bits */
            } expr;
            值得注意的是eval.c只在第一遍分析源码时处理运算符||,^^,&&等,因为它们只在

回复 6楼2009-06-03 06:01举报 |
 
DJ南
        预处理表达式中才可能出现。

    5. 标号处理器label.c:
            NASM的标号分局部标号和全局标号两种(外部标号可看作是在另一个源程序中的全局标号)。
            局部标号名最终将由两部分组成:"上一个全局标号名+局部标号名"。
            全局标号名最终将由三部分组成:"lprefix+全局标号名+lpostfix"。其中lprefix和lpostfix
        都是可选的,它们分别用于在所有的全局标号名的前面和后面添加字符串。比如在编译时指定选项
        --prefix_ 将会在所在的全局标号名前加下划线,这就会和在C中生成标号的情况差不多。
            最终lable.c会将标号的相关信息传给对应的out*.c来生成目标文件中的符号。

            这个模块导出以下数据和函数:
                extern char lprefix[PREFIX_MAX];
                extern char lpostfix[PREFIX_MAX];

                int lookup_label (char *label, long *segment, long *offset);
                int is_extern (char *label);
                void define_label (char *label, long segment, long offset, char *special,
                                   int is_norm, int isextrn, struct ofmt *ofmt, efunc error);
                void redefine_label (char *label, long segment, long offset, char *special,
                                   int is_norm, int isextrn, struct ofmt *ofmt, efunc error);
                void define_common (char *label, long segment, long size, char *special,
                                    struct ofmt *ofmt, efunc error);

回复 7楼2009-06-03 06:01举报 |
 
DJ南
                void declare_as_global (char *label, char *special, efunc error);
                int init_labels (void);
                void cleanup_labels (void);

          函数init_labels和cleanup_labels分别用于内部数据的初始化和清理工作。
          函数lookup_label用于查找存在的标号并返回对应的段和偏移的值。
          函数define_label用于定义标号并将信息传递给相应的out*.c来生成目标文件中的符号。
            函数redefine_label用于检查标号是否存在定位错误并对其进行修正(按作者的说法,虽然大多
            数的汇编器都存在这个功能,但在这里好像并不能起到什么作用)。

    6. 列表文件生成模块listing.c:
            list.c用于生成列表文件。列表文件的格式如下:
            列表文件的行号 代码在目标文件中的偏移 <列表嵌套层数> 源代码行或宏展开后的代码

            其中的列表嵌套层数用于INCBIN,TIMES,INCLUDE和MACRO等伪指令,用于表示这些指令展开后在
        代码中嵌套的层数。在实际生成的列表文件中不对TIMES指令和指定了.nolist的宏进行展开操作。

          list.c对外导出如下结构:
                ListGen nasmlist = {
                    list_init,
                    list_cleanup,
                    list_output,
                    list_line,
                    list_uplevel,
                    list_downlevel
                };
          其中list_init和list_cleanup分别用于模块内部数据的初始化和清理操作。
          list_output用于生成列表文件格式中的的前两部分,list_line用于生成列表文件格式中的

回复 8楼2009-06-03 06:01举报 |
 
DJ南
        后两部分。list_upleve和list_downlevel被其它模块调用,以更新list.c中的列表嵌套层数。
        值得注意的是源码中的数据结构MacroInhibit目前在模块中并没有什么实际的作用,而且list.c
        也没有对传入的参数进行充分的处理。按作者的说法,这些函数实现并没有经过很好的考虑,
        它们只是因为"差不多能够工作",所以才被保留至今。

    7. 浮点数转换模块float.c:
            float.c模块导出一个函数float_const:
                int float_const (char *number, long sign, unsigned char *result, int bytes,
                 efunc error);
            这个函数负责将指令DD, DQ和DT后的浮点数常量转换为Intel机器内部的数据表达形式。
            float.c能识别的浮点数格式如下:
                dd    1.2                     ; an easy one
                dq    1.e10                   ; 10,000,000,000
                dq    1.e+10                  ; synonymous with 1.e10
                dq    1.e-10                  ; 0.000 000 000 1
                dt    3.141592653589793238462 ; pi
            需要注意的是NASM不提供浮点数的表达式求值运算,这是因为NASM认为不能保证所有
        存在ANSI C编译器的的系统都提供浮点数运算的库函数,而自己实现又有点得不偿失。

    8.指令模板集模块insnsa.c:
            NASM的CVS库中有三个与该模块有关的文件:insns.h(指令模板结构定义和其它常量定义),
        insns.dat(NASM能识别的Intel指令集信息表),insns.pl(用于从insns.dat中生成insnsa.c,

回复 9楼2009-06-03 06:01举报 |
 
DJ南
        insnsd.c, insnsi.h, insnsn.c等文件的Perl脚本)。
           在insns.pl生成的四个文件中,insnsa.c和insnd.c是C格式的指令集信息表,分别用于
        assemble.c和ndisasm.c。insnsi.h是指令集名字的枚举表,insnsn.c是指令集名字的字符串表,
        这两个文件用在扫描(nasmlib.c)和反汇编(ndisasm.c)中。

    9. 为其它模块提供支持的nasmlib.c:
            nasmlib.c提供如下函数:
              ·具有报错和记录功能的内存分配和释放函数;
              ·字符串和数字间的转换和赋值函数;
              ·对动态分配的随机存储数组(RAA)和顺序存储数组(SAA)进行管理的函数;
              ·一个标准的源码扫描函数;
              ·表达式处理的库函数;
              ·为目标文件自动添加扩展名的函数;
              ·对源码和目标文件提供支持的其它函数。

    10. 目标格式文件输出模块output\*out.c和对其进行管理的模块outform.c:
                output目录下的*out.c是NASM所能输出的目标格式文件的源代码实现。每一个输出模块
            一般只导出一个ofmt形式的数据结构(ofmt数据结构的定义可参见nasm.h文件)。
                out*.c还提供了对特定格式的附加指令支持。比如如果在生成OBJ格式时想导入其它DLL的
            函数,可以使用import指令,如下将导入wsock32.dll中的函数WSAStartup:
                import WSAStartup wsock32.dll

                outform.c提供了对上述模块进行查找,列举和注册的功能。

二、流程简介:

    主程序nasm.c的流程如下:

        (1)初始化需要的数据结构,注册编译器支持的目标文件格式。
        (2)使用内部函数parse_cmdline解析命令行参数,设置对应的参数值或输出相应的帮助信息后退出。
        (3)如果生成的目标格式中含有附加的标准宏,调用预处理中的函数对其进行注册。
        (4)调用函数parser_global_info和eval_global_info分别初始化parser.c和eval.c。

回复 10楼2009-06-03 06:01举报 |
 
DJ南

        (5)根据变量operating_mode的值判断操作模式,默认为op_normal。
            1.如果operating_mode为op_depend(编译时指定了参数-M),则对外输出makefile格式的文件依赖关系;
            2.如果operating_mode为op_preprocess(编译时指定了参数-e),则只对源代码进行预处理操作,并添加相应的
              行号信息,然后输出到目标文件或标准输出(stdout);
            3.如果operating_mode为op_normal,则先调用函数init_labels和ofmt->init分别初始化label.c和out*.c,
              然后调用函数assemble_file对源文件进行编译处理。

        (6)释放掉程序动态分配的内存(RAA和SAA),调用函数eval_cleanup和nasmlib_cleanup进行相应模块的清理工作。
           然后根据变量terminate_after_phase的值设置程序返回值并结束运行。

    其中的函数assemble_file负责了最主要的工作,它接收源程序文件名,并调用各个模块对源程序进行预处理,
    解析,编译,生成指定目标格式文件等操作。
        assemble_file函数流程如下:

        (1)初始化目标文件格式中的段(节)和偏移值,初始化预处理模块,设置当前扫描次数。
        (2)调用预处理模块中的函数preproc->getline()返回一行可以被解析使用的“标准”汇编代码,并增加当前行数。

        (3)调用函数getkw(line,&value)来判断当前行格式是否为[directive value]并返回directive和value相应的值。
           这里的directive是指NASM自己的一些伪指令,例如SECTION,EXTERN,GLOBAL等。若上述格式getkw无法识别,
           则调用ofmt->directive (line+1, value, 1)来判断是否为目标文件格式的附加指令。
        (4)若当前行不是上述格式,则调用标准的解析函数parse_line解析当前行。
        (5)记录或处理当前行指令中的向前引用。
        (6)处理EQU指令,并只在第二次解析时才处理标号前缀为".."的特殊EQU指令。

        (7)调用编译模块进行处理。第一次扫描源码时调用insn_size,并对伪指令RESB,DB等调用
           函数ofmt->current_dfmt->debug_typevalue(typeinfo)来设置调试信息;第二次扫描时则直接调用assemble来
           生成目标代码。
        (8)调用函数preproc->cleanup()和nasmlist.cleanup()进行扫描后的清理工作。

回复 11楼2009-06-03 06:01举报 |
 
DJ南
                            %rep %0 - 1
                                    %rotate -1
                                    %assign i   i-1

                                    ;查看前一个数是否为‘ADDR‘
                                    %rotate -1
                                    %ifidni %1,"ADDR"
                                            ;将参数列表移到当前并处理
                                            %rotate 1
                                            lea eax,    %1
                                            push        eax
                                            ;跳过ADDR参数
                                            %rotate -1

回复 13楼2009-06-03 06:01举报 |
 
DJ南
                                            %assign i   i-1
                                    %else
                                            %rotate 1
                                            xpush {%1}
                                    %endif


                                    %if i <=1
                                            %exitrep
                                    %endif
                            %endrep
                            %rotate -1
                    %endif
                    extern      %1
                    call        [%1]
            %endmacro

    /*$Id: nasm_1.html,v 1.6 2003/12/09 10:39:09 jingtao Exp $*/
分享到:
评论

相关推荐

    nasm 源代码分析笔记

    **NASM汇编器源代码分析笔记** NASM(Netwide Assembler)是一款流行的、开源的x86汇编器,支持多种目标格式,如Intel 8086到最新的x86-64架构。它以其简洁的语法和广泛的平台兼容性而闻名。在深入分析nasm-0.98.39...

    《一个操作系统的实现》读书笔记(一)

    这个文本文件可能包含了操作系统实现的完整源代码或者项目文档,记录了系统的各个组件、函数和数据结构。通过阅读这个文件,读者可以了解操作系统的设计思路,以及各部分是如何协同工作的。 6. **操作系统构建流程...

    同济大学计算机课程实验-汇编语言程序设计-内含源码和说明书.zip

    这些源代码涵盖了基础的指令系统,如数据处理、流程控制、输入输出等。通过分析和运行这些代码,学生可以了解到如何用汇编语言进行数值计算、条件判断、循环操作以及与硬件交互。同时,源码还可能涉及了函数调用、...

    AT91RM9200开发笔记(3):U-boot-1.3.0移植

    4. **修改源码**:针对AT91RM9200的硬件特性,可能需要修改U-boot的源代码,如初始化脚本、中断处理、设备驱动等。例如,针对板级支持包(Board Support Package, BSP),可能需要添加或修改针对特定外设的初始化...

    atom-script:在Atom中运行(脚本|选择|源代码)

    组装(NASM) 是的 是的 ( 1C(BSL) 是的 是的 ansible-playbook 是的 是的 AutoHotKey.exe 是的 是的 osascript JS 是的 是的 node 重击 是的 是的 如果您的SHELL或#!线是重bash 。 (打击自动化测试系统) 是...

    openssl编译好的dll、lib、头文件

    - 获取源代码:从OpenSSL官方网站下载最新版本的源代码。 - 配置环境:设置Visual Studio或MinGW等编译环境,以及相关依赖项,如Zlib和NASM。 - 配置选项:使用`Configure`脚本选择目标平台、编译选项(例如静态/...

    汇编语言的基本学习笔记

    例如,可能会介绍如何使用`masm`(Microsoft Macro Assembler)或其他汇编器将源代码转换成目标文件,然后使用链接器如`link.exe`将其链接成可执行文件。 #### 可见寄存器组(#4) 这部分可能详细介绍了80x86架构...

    《x86汇编语言:从实模式到保护模式》配套工具

    3. **交叉编译器/链接器**:如NASM或MASM,用于将汇编语言源代码转换为可执行文件。学习汇编语言时,理解编译和链接过程是非常关键的。 4. **文档和笔记**:可能包含作者的注解、补充材料,甚至是书中未提及的进阶...

    最强大的王爽汇编语言学习环境.rar

    1. **源代码示例**:王爽的书中包含了许多精心设计的汇编语言程序实例,这些实例涵盖了从简单的数据处理到复杂的控制流程。通过阅读和分析这些代码,学习者可以更好地理解和掌握汇编语言的语法和逻辑。 2. **实验...

    AsmStudio R5.rar

    1. **源代码编辑器**:AsmStudio R5提供了一个强大的文本编辑器,支持语法高亮、自动完成和错误检测,这有助于提高程序员的编写效率并减少错误。 2. **项目管理**:用户可以创建、管理和组织多个汇编语言项目,每个...

    tetris.rar_DOS__DOS_

    5. **软件工程实践**:理解开源项目的结构,如何从源代码构建可执行文件,以及如何阅读和理解他人的代码。 这个项目对于想要深入了解DOS系统、汇编语言编程或者复古游戏开发的人来说是一个宝贵的资源。通过研究和...

    Windows(64位)下的汇编调试工具包

    4. **汇编工具**:工具包可能包括汇编器(Assembler),将汇编语言源代码转换成机器码;链接器(Linker),将汇编器生成的多个目标模块组合成可执行文件;还有可能包含反汇编器(Disassembler),用于查看和理解二...

Global site tag (gtag.js) - Google Analytics