注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

gcc中386内联汇编实现基础及命令行选项配置处理  

2012-04-17 21:53:18|  分类: Gcc源代码分析 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、内联汇编及命令行选项
其实对于gcc比较核心的寄存器分配及优化来说,我是不懂的,关于这一点我相信大家没什么异议。所以这些都是最为基础最为直观的一些代码,这些代码对于gcc精华的理解可能没有什么作用,只是说一方面是个人比较感兴趣,这里只是把自己看的一些东西原原本本的记录下来;另一方面也是一个渐进的过程,先把一些相对基础和外围的东西看了之后再看比较复杂的东西。
gcc可能拥有令人眼花缭乱的命令行选项,有些人觉得是多才多艺,有些人则可能觉得是鱼龙混杂,不管如何,它们都在哪里,并且数量庞大。有些时候,我们可能知道或者希望知道一个命令行选项是如何影响程序的行为,那么比较简单的方法就是找到这个选项影响的变量,然后查看代码中对这个变量引用的地方,从这些地方展开,这样也是一个渐进的过程,所以理解gcc的命令行选项对gcc的理解也有一定意义。
作为对比,我们知道内核同样有很多的功能选项,通过这些选项可以在内核编译的时候选择内核的一些功能,这就是一个裁剪的概念。作为对比,gcc的命令行选项则是动态的选择gcc的行为特征,所以选项对于一个比较友好和通用的程序来说是有意义的。补充一下,内核其实也可以接收运行时选项,内核中散布于各个文件中的__setup宏定义,例如linux-2.6.21\init\main.c文件设置用户态始祖文件的init选项
__setup("init=", init_setup);
用来实现运行时功能选择。例如我运行的虚拟机的内核命令行选项为
[tsecer@Harry root]$ cat /proc/cmdline
ro root=/dev/mapper/vg_harry-lv_root  LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYBOARDTYPE=pc KEYTABLE=us rhgb quiet
二、内联汇编
1、测试程序
这个程序主体来自glibc中关于鲁棒锁的一个386实现代码glibc-2.7\nptl\sysdeps\unix\sysv\linux\i386\lowlevellock.h,我为了说明稍微修改了一些代码,添加了字符名使用
[tsecer@Harry stage2-gcc]$ cat lllock.c
#define LOCK_INSTR   
#define LLL_STUB_UNWIND_INFO_4
#define lll_robust_lock(futex, id, private) \
  ({ int result, ignore1, ignore2;                          \
     __asm __volatile (LOCK_INSTR "cmpxchgl %[ignore1], %2\n\t"                  \
               "jnz _L_robust_lock_%=\n\t"                  \
               ".subsection 1\n\t"                      \
               ".type _L_robust_lock_%=,@function\n"              \
               "_L_robust_lock_%=:\n"                      \
               "1:\tleal %2, %%edx\n"                      \
               "0:\tmovl %7, %%ecx\n"                      \
               "2:\tcall __lll_robust_lock_wait\n"              \
               "3:\tjmp 18f\n"                          \
               "4:\t.size _L_robust_lock_%=, 4b-1b\n\t"              \
               ".previous\n"                          \
               LLL_STUB_UNWIND_INFO_4                      \
               "18:"                              \
               : "=a" (result), [ignore1]"=c" (ignore1), "=m" (futex),          \
             "=&d" (ignore2)                      \
               : "0" (0), "1" (id), "m" (futex), "g" (private)          \
               : "memory");                          \
     result; })
int foo()
{
int futex ,id ,private;
lll_robust_lock(futex,id,private);
}
2、中括号表达式解析及处理
对应上例中的
: "=a" (result), [ignore1]"=c" (ignore1), "=m" (futex),          \
其关键处理流程为
#0  c_parser_asm_operands (parser=0xb7cf5120, convert_p=0 '\000')
    at ../../gcc-4.2.0/gcc/c-parser.c:4231
#1  0x080acc19 in c_parser_asm_statement (parser=0xb7cf5120)
    at ../../gcc-4.2.0/gcc/c-parser.c:4170
#2  0x080abc71 in c_parser_statement_after_labels (parser=0xb7cf5120)
    at ../../gcc-4.2.0/gcc/c-parser.c:3759
#3  0x080ab623 in c_parser_compound_statement_nostart (parser=0xb7cf5120)
    at ../../gcc-4.2.0/gcc/c-parser.c:3504
……
这个调用链比较长,我们就只保留最后的几级,其中的输入部分和输出部分都是使用这个函数来完成解析。可以看到,在这个函数的最开始,考虑到了对于中括号引导的字符类型标识符的解析(相对于完全的数字形式标识符),其中各个部分的解析为:
  if (c_parser_next_token_is (parser, CPP_OPEN_SQUARE)) 对于中括号引导的标识符型操作数说明。
……
      str = c_parser_asm_string_literal (parser);对于变量中字符串内容的解析,这个操作非常简单,只是读入了字符串,没有对其中内容做任何解析。
……
 expr = c_parser_expression (parser);表达式解析。
对于这种类型字符,gcc将会在读入之后,直接统一代劳替换为大家喜闻乐见的数值表示形式,对应代码调用链为
#0  resolve_operand_name_1 (
    p=0x8778b9a "[ignore1], %2\n\tjnz _L_robust_lock_%=\n\t.subsection 1\n\t.type _L_robust_lock_%=,@function\n_L_robust_lock_%=:\n1:\tleal %2, %%edx\n0:\tmovl %7, %%ecx\n2:\tcall __lll_robust_lock_wait\n3:\tjmp 18f\n4:\t.size _L_robu"..., outputs=0xb7d87000, inputs=0xb7d87168) at ../../gcc-4.2.0/gcc/stmt.c:1299
#1  0x08461834 in resolve_asm_operand_names (string=0xb7cf9318,
    outputs=0xb7d87000, inputs=0xb7d87168) at ../../gcc-4.2.0/gcc/stmt.c:1282
#2  0x080722e3 in build_asm_expr (string=0xb7cf9318, outputs=0xb7d87000,
    inputs=0xb7d87168, clobbers=0xb7d87288, simple=0 '\000')
    at ../../gcc-4.2.0/gcc/c-typeck.c:6833
#3  0x080ace1d in c_parser_asm_statement (parser=0xb7cf5120)
    at ../../gcc-4.2.0/gcc/c-parser.c:4211
在resolve_operand_name_1函数中,gcc会对其中变量进行替换,其中的%d就是字符变量对应的数字编号
  sprintf (p, "%d", op);
  p = strchr (p, '\0');
所以后端完成替换的时候只处理数字即可。
3、重名变量处理
对应上例内容为
: "0" (0),
这里可以看到的是,它的限制说明是和第一个描述相同,这样就节省了一个描述,而且更为重要的是保证了两者是真真正正的完全一致,这个一致性体现为它们在gcc中表示为相同的指针。
对于这些限制位于parse_output_constraint/parse_input_constraint函数中,其中对于数值类型描述说明的处理:
  case '0':  case '1':  case '2':  case '3':  case '4':
      case '5':  case '6':  case '7':  case '8':  case '9':
    {
      char *end;
      unsigned long match;

      saw_match = true;

      match = strtoul (constraint + j, &end, 10);
      if (match >= (unsigned long) noutputs)
        {
          error ("matching constraint references invalid operand number");
          return false;
        }

      /* Try and find the real constraint for this dup.  Only do this
         if the matching constraint is the only alternative.  */
      if (*end == '\0'
          && (j == 0 || (j == 1 && constraint[0] == '%')))
        {
          constraint = constraints[match];这里的match就是"0"中的0,这里直接使用了相同的限制,在C语言中表现为两个指针将会指向一个相同实体
          *constraint_p = constraint;
          c_len = strlen (constraint);
          j = 0;
          /* ??? At the end of the loop, we will skip the first part of
         the matched constraint.  This assumes not only that the
         other constraint is an output constraint, but also that
         the '=' or '+' come first.  */
          break;
        }
4、clobber变量检测
在expand_asm_operands函数中,对于clobber链表中的处理比较特殊,它的特殊之处在于它最早接触了后端,进行了寄存器合法性检测。该表达式的特点就是它不能使用寄存器缩写,而必须是寄存器名称或者是memory cc字符串。expand_asm_operands--->>>decode_reg_name
 for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
    if (reg_names[i][0]
        && ! strcmp (asmspec, strip_reg_name (reg_names[i])))该数组位于gcc-4.2.0\gcc\config\i386\i386.h中HI_REGISTER_NAMES
      return i;

#ifdef ADDITIONAL_REGISTER_NAMES
      {
    static const struct { const char *const name; const int number; } table[]
      = ADDITIONAL_REGISTER_NAMES;该数组位于gcc-4.2.0\gcc\config\i386\i386.h中ADDITIONAL_REGISTER_NAMES数组

    for (i = 0; i < (int) ARRAY_SIZE (table); i++)
      if (table[i].name[0]
          && ! strcmp (asmspec, table[i].name))
        return table[i].number;
      }
#endif /* ADDITIONAL_REGISTER_NAMES */

      if (!strcmp (asmspec, "memory"))
    return -4;

      if (!strcmp (asmspec, "cc"))
    return -3;
5、后端对指令模板的统一处理
gcc-4.2.0\gcc\final.c文件中output_asm_insn对模板字符串进行最终格式化。该函数中对于大的框架做了处理,从用户提供的汇编指令模板中逐个处理,对自己感兴趣的字符尝试进行特殊处理,其中最为重要的就是对于‘%’字符串的处理,当然还有一些特殊体系结构的方言(DIALECT)的处理等,对于gcc不感兴趣的内容,和printf一样,该单词被按照字面内容输出。
 case '%':
    /* %% outputs a single %.  */
    if (*p == '%')
      {
        p++;
        putc (c, asm_out_file);
      }
    /* %= outputs a number which is unique to each insn in the entire
       compilation.  This is useful for making local labels that are
       referred to more than once in a given insn.  */
    else if (*p == '=')这个地方出现了我们例子中所用的"%="形式,其中的数值为insn_counter,也就是迄今为止gcc输出的指令数,由于整个内联汇编是作为一个指令,所以在整个编译单元中,“%=”唯一不同,而在每个汇编指令内部,该值相同,有兴趣的同学可以数一下生成的汇编文件中指令数
      {
        p++;
        fprintf (asm_out_file, "%d", insn_counter);
      }
    /* % followed by a letter and some digits
       outputs an operand in a special way depending on the letter.
       Letters `acln' are implemented directly.
       Other letters are passed to `output_operand' so that
       the PRINT_OPERAND macro can define them.  */
    else if (ISALPHA (*p))
      {
  ……
}else if (ISDIGIT (*p))
      {
        unsigned long opnum;
        char *endptr;

        opnum = strtoul (p, &endptr, 10);
        if (this_is_asm_operands && opnum >= insn_noperands)
          output_operand_lossage ("operand number out of range");
        else
          output_operand (operands[opnum], 0);

        if (!opoutput[opnum])
          oporder[ops++] = opnum;
        opoutput[opnum] = 1;

        p = endptr;
        c = *p;
      }
6、386后端处理
gcc-4.2.0\gcc\config\i386\i386.c
print_operand (FILE *file, rtx x, int code),而其中对于寄存器的打印通过print_reg函数完成,而对于内存变量打印则通过print_operand_address (FILE *file, rtx addr)函数完成,然后是一些常量
  if (GET_CODE (x) == REG)
    print_reg (x, code, file);

  else if (GET_CODE (x) == MEM)
……
    output_address (x);
    }
……
    if (code != 'P')
    {
      if (GET_CODE (x) == CONST_INT || GET_CODE (x) == CONST_DOUBLE)
        {
          if (ASSEMBLER_DIALECT == ASM_ATT)
        putc ('$', file);
        }
      else if (GET_CODE (x) == CONST || GET_CODE (x) == SYMBOL_REF
           || GET_CODE (x) == LABEL_REF)
        {
          if (ASSEMBLER_DIALECT == ASM_ATT)
        putc ('$', file);
          else
        fputs ("OFFSET FLAT:", file);
        }
    }
      if (GET_CODE (x) == CONST_INT)
    fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (x));
这就是转换的大致脉络,这里比较重要的一点就是:在汇编指令的模板格式化中,不会区分操作数源地址和目的地址,只是进行机械替换,此时gcc不再也无法尝试理解汇编指令语义,这是一个为了解耦的协议格式化
还有一点,在print_reg中使用
code = GET_MODE_SIZE (GET_MODE (x));
判断了寄存器寄存器对应的C语言变量(即 "r"(expression)中expression表达式结果)的大小,从而选择寄存器。但是对于内存替换print_operand_address函数来说,它并没有判断地址的大小,事实上对于AT&T格式的汇编,它操作内存的时候,具体操作内存多少地址,这个大小是由操作指令来决定的,例如操作一个byte,那么需要使用
movb al,-8(esp)
而对于word操作则为
movw ax,-8(esp)
形式,当然386是通过另一种指明地址类型的方法来说明。
三、gcc命令行选项处理
gcc的命令行选项在源代码中是通过配置文件描述的,然后在编译过程中动态生成C语言的.h和.c文件,所以一般在源代码中无法看到这些定义。对于gcc的配置,是通过gcc-4.2.0\gcc文件夹下的opt-*.awk文件来动态生成的,而配置文件则位于各个文件夹下的*.opt文件,例如gcc-4.2.0\gcc\common.opt文件,其中各行解释主要位于gcc-4.2.0\gcc\opt-functions.awk文件,其中
# Return a bitmask of CL_* values for option flags FLAGS.
function switch_flags (flags)
{
    result = "0"
    for (j = 0; j < n_langs; j++) {
        regex = langs[j]
        gsub ( "\\+", "\\+", regex )
        result = result test_flag(regex, flags, " | " macros[j])
    }
    result = result \
      test_flag("Common", flags, " | CL_COMMON") \
      test_flag("Target", flags, " | CL_TARGET") \
      test_flag("Joined", flags, " | CL_JOINED") \
      test_flag("JoinedOrMissing", flags, " | CL_JOINED | CL_MISSING_OK") \
      test_flag("Separate", flags, " | CL_SEPARATE") \
      test_flag("RejectNegative", flags, " | CL_REJECT_NEGATIVE") \
      test_flag("UInteger", flags, " | CL_UINTEGER") \
      test_flag("Undocumented", flags,  " | CL_UNDOCUMENTED") \
      test_flag("Report", flags, " | CL_REPORT")
    sub( "^0 \\| ", "", result )
    return result
}
gcc-4.2.0\gcc\common.opt
Wstack-protector
Common Var(warn_stack_protect)
Warn when not issuing stack smashing protection for some reason
对应动态生成内容
    290 /* Set by -Wstack-protector.
    291    Warn when not issuing stack smashing protection for some reason  */
    292 int warn_stack_protect;
……
1838   { "-Wstack-protector",
   1839     "Warn when not issuing stack smashing protection for some reason",
   1840     N_OPTS, 16, 123,
   1841     CL_COMMON,
   1842     &warn_stack_protect, CLVC_BOOLEAN, 0 },

动态生成的options.c中动态定义cl_options,这个变量在gcc的find_opt中使用
   1219 const unsigned int cl_options_count = N_OPTS;
   1220
   1221 const struct cl_option cl_options[] =
   1222 {
   1223   { "--help",
   1224     "Display this information",
   1225     N_OPTS, 5, -1,
   1226     CL_COMMON,
   1227     0, CLVC_BOOLEAN, 0 },
   1228   { "--output-pch=",
   1229     0,
   1230     N_OPTS, 12, -1,
   1231     CL_C | CL_CXX | CL_ObjC | CL_ObjCXX | CL_JOINED | CL_SEPARATE,
   1232     0, CLVC_STRING, 0 },
在对应的头文件options.h中,其中定义的内容为
    385 enum opt_code
    386 {
    387   OPT__help,                                 /* --help */
    388   OPT__output_pch_,                          /* --output-pch= */
    389   OPT__param,                                /* --param */
    390   OPT__target_help,                          /* --target-help */
    391   OPT__version,                              /* --version */
    392   OPT_A,                                     /* -A */

这个OPT_XXX的顺序和cl_optisons中该选项出现顺序一致,也就是说OPT_XXX枚举对应的是该选项在cl_optsions数组中的下标
大致来说,一个opt文件中三行为一个配置项,其中
第一行为该命令行配置形式字符串
第二行为该命令行的一些属性说明
第三行为该命令选项的一行注释行说明
funroll-loops
Common Report Var(flag_unroll_loops)
Perform loop unrolling when iteration count is known
其中第二行最为复杂,其中通常通过Var来说明它对应的结果标志赋值给哪个变量,也就是选项和变量建立对应关系,而Common和Report之类的关键字说明可以参考opts.h中注释。
#define CL_DISABLED        (1 << 21) /* Disabled in this configuration.  */
#define CL_TARGET        (1 << 22) /* Target-specific option.  */
#define CL_REPORT        (1 << 23) /* Report argument with -fverbose-asm  */
#define CL_JOINED        (1 << 24) /* If takes joined argument.  */例如-O2
#define CL_SEPARATE        (1 << 25) /* If takes a separate argument.  */
#define CL_REJECT_NEGATIVE    (1 << 26) /* Reject no- form.  */
#define CL_MISSING_OK        (1 << 27) /* Missing argument OK (joined).  */
#define CL_UINTEGER        (1 << 28) /* Argument is an integer >=0.  */
#define CL_COMMON        (1 << 29) /* Language-independent.  */
#define CL_UNDOCUMENTED        (1 << 30) /* Do not output with --help.  */

这些说明将会帮助不同选项处理,在gcc-4.2.0\gcc\opts.c文件中handle_option,会根据配置和参数类型来解析接下来可能存在的参数,默认为CLVC_BOOLEAN:
 if (option->flag_var)
    switch (option->var_type)
      {
      case CLVC_BOOLEAN:
    *(int *) option->flag_var = value;
    break;

      case CLVC_EQUAL:
    *(int *) option->flag_var = (value
                     ? option->var_value
                     : !option->var_value);
    break;

      case CLVC_BIT_CLEAR:
      case CLVC_BIT_SET:
    if ((value != 0) == (option->var_type == CLVC_BIT_SET))
      *(int *) option->flag_var |= option->var_value;
    else
      *(int *) option->flag_var &= ~option->var_value;
    if (option->flag_var == &target_flags)
      target_flags_explicit |= option->var_value;
    break;

      case CLVC_STRING:
    *(const char **) option->flag_var = arg;
    break;
      }
 
  if (option->flags & lang_mask)
    if (lang_hooks.handle_option (opt_index, arg, value) == 0)这个对于C语言来说为c_common_handle_option函数
      result = 0;
  评论这张
 
阅读(1961)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017