从asm_out_file看gcc汇编代码的生成

来源:互联网 发布:北京和隆优化待遇 编辑:程序博客网 时间:2024/05/19 22:47
看了两天的gcc_internals,不得其门而入,于是决定从代码入手,先看看gcc汇编代码生成这一块。也希望由此看看中间语言的表示。
先写一最简单的main.c文件:
int main(int argc, char** argv)
{
       return 0;
}
输出的汇编代码为:
.file "d:/tmp/main.c";
.text;
       .align 4
.global _main;
.type _main, STT_FUNC;
_main:
       LINK 0;
       [FP+8] = R0;
       [FP+12] = R1;
       R0 = 0 (X);
       UNLINK;
       rts;
       .size _main, .-_main
       .ident      "GCC: (GNU) 4.1 2008-01-24 13:00:00 debug gcc "
开始调试。
1.   .file输出
由于FILE* asm_out_file是gcc输出汇编文件的指针(在toplev.c中定义),在源文件中一搜,发现有13个文件376行使用了此变量,再仔细观察这些行的数据,在
gcc/config/bfin/bfin.c
中发现了一个函数:
static void
output_file_start (void)
{
 FILE *file = asm_out_file;
 int i;
 
 fprintf (file, ".file /"%s/";/n", input_filename);
 
 for (i = 0; arg_regs[i] >= 0; i++)
    ;
 max_arg_registers = i;    /* how many arg reg used */
}
猜测这个大概就是汇编输出第一行的位置了,在这里断下来,看看函数调用堆栈:
>     gcc4.exe!output_file_start() 行108    C
      gcc4.exe!init_asm_output(const char * name=0x003d38a5) 行1253 + 0x8 字节 C
      gcc4.exe!lang_dependent_init(const char * name=0x003d38a5) 行1847 + 0x9 字节    C
      gcc4.exe!do_compile() 行1948 + 0xb 字节     C
      gcc4.exe!toplev_main(unsigned int argc=0x00000002, const char * * argv=0x003d3870) 行1983      C
      gcc4.exe!main(int argc=0x00000002, char * * argv=0x003d3870) 行35 + 0xd 字节   C
      gcc4.exe!__tmainCRTStartup() 行318 + 0x19 字节       C
      gcc4.exe!mainCRTStartup() 行187   C
      kernel32.dll!7c816fd7()       
      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]    
 
原来还在初始化阶段,看来用处不大。
2   .text输出
再看看.text的输出。
很容易在bfin.h中发现了一个定义:
#define TEXT_SECTION_ASM_OP ".text;"
再查找TEXT_SECTION_ASM_OP这个宏出现的位置,在gcc/varasm.c中发现了一个函数:
/* Tell assembler to switch to text section. */
 
void
text_section (void)
{
 if (in_section != in_text)
    {
      in_section = in_text;
      last_text_section = in_text;
      fprintf (asm_out_file, "%s/n", TEXT_SECTION_ASM_OP);
    }
}
在这里断下来,再看看函数调用的堆栈:
>     gcc4.exe!text_section() 行214   C
      gcc4.exe!function_section(tree_node * decl=0x00edcb00) 行581   C
      gcc4.exe!assemble_start_function(tree_node * decl=0x00edcb00, const char * fnname=0x00ebc9f1) 行1318 + 0x9 字节       C
      gcc4.exe!rest_of_handle_final() 行3951 + 0x10 字节     C
      gcc4.exe!execute_one_pass(tree_opt_pass * pass=0x00d863f4) 行827 + 0xa 字节     C
      gcc4.exe!execute_pass_list(tree_opt_pass * pass=0x00d863f4) 行859 + 0x9 字节      C
      gcc4.exe!execute_pass_list(tree_opt_pass * pass=0x00d87074) 行860 + 0xc 字节     C
      gcc4.exe!execute_pass_list(tree_opt_pass * pass=0x00d87040) 行860 + 0xc 字节     C
      gcc4.exe!tree_rest_of_compilation(tree_node * fndecl=0x00edcb00) 行419 + 0xb 字节     C
      gcc4.exe!c_expand_body(tree_node * fndecl=0x00edcb00) 行6668 + 0x9 字节 C
      gcc4.exe!cgraph_expand_function(cgraph_node * node=0x00edcb80) 行1055 + 0xc 字节 C
      gcc4.exe!cgraph_assemble_pending_functions() 行352 + 0x9 字节      C
      gcc4.exe!cgraph_finalize_function(tree_node * decl=0x00edcb00, unsigned char nested=0x00) 行455 + 0x5 字节   C
      gcc4.exe!finish_function() 行6637 + 0xb 字节       C
      gcc4.exe!c_parser_declaration_or_fndef(c_parser * parser=0x00ede078, unsigned char fndef_ok='', unsigned char empty_ok='', unsigned char nested=0x00, unsigned char start_attr_ok='') 行1309 C
      gcc4.exe!c_parser_external_declaration(c_parser * parser=0x00ede078) 行1071 + 0x11 字节    C
      gcc4.exe!c_parser_translation_unit(c_parser * parser=0x00ede078) 行977 + 0x9 字节      C
      gcc4.exe!c_parse_file() 行6258 + 0xb 字节    C
      gcc4.exe!c_common_parse_file(int set_yydebug=0x00000000) 行1144C
      gcc4.exe!compile_file() 行991 + 0xe 字节      C
      gcc4.exe!do_compile() 行1951 C
      gcc4.exe!toplev_main(unsigned int argc=0x00000002, const char * * argv=0x00383870) 行1983      C
      gcc4.exe!main(int argc=0x00000002, char * * argv=0x00383870) 行35 + 0xd 字节   C
      gcc4.exe!__tmainCRTStartup() 行318 + 0x19 字节       C
      gcc4.exe!mainCRTStartup() 行187   C
      kernel32.dll!7c816fd7()       
      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]    
 
沿着调用堆栈一路向上跟踪,在gcc/final.c中发现一函数:
/* Turn the RTL into assembly. */
static void
rest_of_handle_final (void)
{
 rtx x;
 const char *fnname;
 
 /* Get the function's name, as described by its RTL. This may be
     different from the DECL_NAME name used in the source file. */
 
 x = DECL_RTL (current_function_decl);
 gcc_assert (MEM_P (x));
 x = XEXP (x, 0);
 gcc_assert (GET_CODE (x) == SYMBOL_REF);
 fnname = XSTR (x, 0);
 
 assemble_start_function (current_function_decl, fnname);
 final_start_function (get_insns (), asm_out_file, optimize);
 final (get_insns (), asm_out_file, optimize);
 final_end_function ();
 
 
}
估计在这里应该就可以看到rtl的面目了。看看final.c文件头的注释:
/* This is the final pass of the compiler.
   It looks at the rtl code for a function and outputs assembler code.
 
   Call `final_start_function' to output the assembler code for function entry,
   `final' to output assembler code for some RTL code,
   `final_end_function' to output assembler code for function exit.
   If a function is compiled in several pieces, each piece is
   output separately with `final'.
 
   Some optimizations are also done at this level.
   Move instructions that were made unnecessary by good register allocation
   are detected and omitted from the output. (Though most of these
   are removed by the last jump pass.)
 
   Instructions to set the condition codes are omitted when it can be
   seen that the condition codes already had the desired values.
 
   In some cases it is sufficient if the inherited condition codes
   have related values, but this may require the following insn
   (the one that tests the condition codes) to be modified.
 
   The code for the function prologue and epilogue are generated
   directly in assembler by the target functions function_prologue and
   function_epilogue. Those instructions never exist as rtl. */
也验证了这一看法。
3   rtx初探
RTL 有五种对象:表达式、整数、宽整数、字符串和向量。其中,最重要的是表达式。RTL 表达式(RTX)类似于 C 的结构,通常用指针来引用它。这种引用它的指针的类型定义名为rtx,在实现上就是一个称为rtx_def的结构体。
表达式根据“表达式代码”(又称 RTX 代码)分类。RTX 代码是文件rtl.h和 rtl.def中定义的一个名字,其实现为C 语言中的一个枚举常量。表达式的代码和意义与机器无关,我们可用 GET_CODE (x) 宏从 RTX 中抽取 RTX 代码,用 PUT_CODE (x, newcode) 来改变 RTX 代码。
RTX 代码决定了一个表达式所含的操作数个数及操作数的对象类型。在RTL 中不能通过操作数本身而得知操作数是何种对象,而必须从操作数的上下文,即 RTX 代码决定其对象类型。例如:在代码为subreg 的表达式中,它的第一个操作数是一个表达式,第二个操作数是一个整数。在代码为plus的表达式内则有两个均为表达式的操作数。在symbol_ref 表达式内,只有一个被视作为字符串的操作数。
表达式的书写形式为:
      (RTX代码 [机器方式] 操作数1 … 操作数 n)
其中,RTX 代码即RTL 表达式代码的名字,机器方式是可选的。各操作数之间用空格分开。
在上述的rest_of_handle_final函数中断下来,看看一个实际的rtx的值:
l         code:
code的定义为:
/* The kind of expression this is. */
ENUM_BITFIELD(rtx_code) code: 16;
取值为0x2a,转换为rtx_code(在rtl.h及rtl.def中定义)后为SYMBOL_REF。
l         mode
mode的定义为:
 /* The kind of value the expression has. */
 ENUM_BITFIELD(machine_mode) mode : 8;
取值为6,转换为machine_mode后为SImode,这个值来自于machmode.def,其说明为:
/* Basic integer modes. We go up to TI in generic code (128 bits).
   The name OI is reserved for a 256-bit type (needed by some back ends).
   FIXME TI shouldn't be generically available either. */
INT_MODE (QI, 1);
INT_MODE (HI, 2);
INT_MODE (SI, 4);
INT_MODE (DI, 8);
INT_MODE (TI, 16);
l         标志位
在此结构体中的其它各标志位均为0。
l         第一个操作数名称
使用 fnname = XSTR (x, 0);可得到第一个操作数的名称为“main”。
 
4   跟踪:汇编代码生成
其实在final.c的文件头的注释中已经清晰写明了生成的步骤,以下是实际的代码:
 assemble_start_function (current_function_decl, fnname);
 final_start_function (get_insns (), asm_out_file, optimize);
 final (get_insns (), asm_out_file, optimize);
 final_end_function ();
 assemble_end_function (current_function_decl, fnname);
 
l         assemble_start_function
此函数位于varasm.c,其注释为:
/* Output assembler code for the constant pool of a function and associated
   with defining the name of the function. DECL describes the function.
   NAME is the function's name. For the constant pool, we use the current
   constant pool data. */
其实就是输出
.text;
       .align 4
.global _main;
.type _main, STT_FUNC;
_main:
这几行汇编代码。
l         final_start_function
这个函数位于final.c,其注释是:
/* Output assembler code for the start of a function,
   and initialize some of the variables in this file
   for the new function. The label for the function and associated
   assembler pseudo-ops have already been output in `assemble_start_function'.
 
   FIRST is the first insn of the rtl for the function being compiled.
   FILE is the file to write assembler code to.
   OPTIMIZE is nonzero if we should eliminate redundant
     test and compare insns. */
对于输出汇编代码来讲,这个函数啥也没干。
l         final
这个函数位于final.c,用于生成函数内的汇编代码,其注释为:
/* Output assembler code for some insns: all or part of a function.
   For description of args, see `final_start_function', above. */
这个函数内使用了一个循环输出当前要生成代码的函数下的所有表达式。
 /* Output the insns. */
 for (insn = NEXT_INSN (first); insn;)
    {
    
      insn = final_scan_insn (insn, file, optimize, 0, &seen, 0);
}
经过这个函数的处理,就生成了main函数中的汇编代码:
       LINK 0;
       [FP+8] = R0;
       [FP+12] = R1;
       R0 = 0 (X);
       UNLINK;
       rts;
 
l         final_scan_insn
终于到代码生成的部分了,好家伙,800多行,够看的!这个函数位于final.c,其注释为:
/* The final scan for one insn, INSN.
   Args are same as in `final', except that INSN
   is the insn being scanned.
   Value returned is the next insn to be scanned.
 
   NOPEEPHOLES is the flag to disallow peephole processing (currently
   used for within delayed branch sequence output).
 
   SEEN is used to track the end of the prologue, for emitting
   debug information. We force the emission of a line note after
   both NOTE_INSN_PROLOGUE_END and NOTE_INSN_FUNCTION_BEG, or
   at the beginning of the second basic block, whichever comes
   first. */
仔细看这个函数,看似很长,其实也没什么,其主体就是一个switch语句:
 switch (GET_CODE (insn))
要知道insn的code有124种之多,平均一下每种code的处理代码这一点也不觉得长了,比如在main.c函数的处理中碰到的第一个rxt的code为NOTE,几行代码就跳出来了!
对于实际的代码生成,这个函数使用了template,如第一行代码:
LINK 0;
这个函数实际是使用一个模板字符串“LINK %Z0;”,并以此模板字符串为参数调用output_asm_insn函数来生成实际的代码。本部分有待后面再研究。
l         final_end_function
这个函数的工作就轻松了。它位于final.c,其注释为:
/* Output assembler code for the end of a function.
   For clarity, args are same as those of `final_start_function'
   even though not all of them are needed. */
对于汇编代码生成来讲它什么也没做!
l         assemble_end_function
这个函数也简单,位于final.c,其注释为:
/* Output assembler code associated with defining the size of the
   function. DECL describes the function. NAME is the function's name. */
它实际输出了:
       .size _main, .-_main
5   .ident输出
在源代码中查找,很容易发现在config/elfos.h中有一个定义
#define IDENT_ASM_OP "/t.ident/t"
再查找宏出现的位置,在toplev.c中发现了一个引用:
 if (!flag_no_ident)
    fprintf (asm_out_file, "%s/"GCC: (GNU) %s/"/n",
          IDENT_ASM_OP, version_string);
断下来,查看调用堆栈:
>     gcc4.exe!compile_file() 行1047C
      gcc4.exe!do_compile() 行1951 C
      gcc4.exe!toplev_main(unsigned int argc=0x00000002, const char * * argv=0x00383870) 行1983      C
      gcc4.exe!main(int argc=0x00000002, char * * argv=0x00383870) 行35 + 0xd 字节   C
      gcc4.exe!__tmainCRTStartup() 行318 + 0x19 字节       C
      gcc4.exe!mainCRTStartup() 行187   C
      kernel32.dll!7c816fd7()       
      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]    
至此,gcc就完成了整个汇编文件的输出。
 
 
欲知后事如何,且看下回分解!呵呵!
原帖网址:http://blog.csdn.net/lights_joy/archive/2008/01/30/2073727.aspx