转自:http://blog.chinaunix.net/uid-26537702-id-3045121.html
GAS是GNUBinutils系列二进制工具链当中的一种,主要处理的目标是以AT&T语法为主的汇编语言,并且将其汇编指令或者汇编伪指令编译生成对应合适的二进制目标文件(Object File),并交由随后的GLD进行链接和加载等处理,从而完成程序编译的最后运行。
新版本的Binutils2.20与之前的Binutils的最大不同之处在于,所有的Binutils二进制工具都基于Binutils的BFD库,而BFD库的主要功能,则是提供给Binutils的其他二进制工具使用操作不同类型文件的公共接口。因此,在最新版本的GAS中,设计者并没有亲自处理各种不同的文件,包括汇编源文件和生成目标文件,而是利用BFD库的强大功能,完成对此的操作,故而GAS更注重其内部对文件信息的处理,而目标文件的生成,则变成了对相应的BFD库中对应文件结构内容的填充,使得GAS的设计和移植工作更加容易和便捷。
汇编的整体工作流程体现在一个as.c文件的main函数里,对main分析可以了解汇编器的工作过程。在main函数中首先进行汇编器的初始化,然后读入源文件进行解析,最后把解析结果写到目标文件,具体流程如图:
读取源文件read_a_source_file函数的流程图。该函数的功能是读取源文件并且对源文件进行解析,具体如图
汇编函数perform_an_assembly_pass从命令行参数指定的源代码文件中读取源文件,解析到汇编器建立的fixup和frag结果中,对源文件只执行一遍扫描,具体流程如图
由于GAS以BFD库作为操作底层源文件和二进制文件,所以GAS对于目标文件信息的生成和填写均是通过BFD结构变量(名为stdoutput的全局变量),并且以此结构作为组织GAS输出的基础结构。GAS具体数据逻辑结构如下图所示:
在BFD结构中,目标文件的段信息保存在BFD的section链表中,而在GAS分析源文件的过程中,会用subsegment作为临时逻辑结构存放各子段信息,而具体的源代码则以frag作为逻辑结构进行保存同源代码相关的各种信息(包括relax信息,重定位信息,最终的二进制输出信息)。
从这个逻辑结构当中,可以比较明了的看到,GAS的主要工作,就是通过单遍扫描(或者两遍)输入源文件,将目标文件需要的信息,填充到BFD各个结构当中,然后交由BFD库的底层实现将文件信息按照目标格式输出。换句话说,GAS的主要工作,就是根据BFD的结构进行填空。
目标文件输出流程如下图所示:
在输出阶段,BFD结构中的各项信息均已完整填充,唯一遗漏的信息包括第一遍扫描中没有得到解析的地址信息和在扫描过程中的fix链表信息。这个过程主要是由write_object_file进行的,除了相应的初始化过程,分为三个主要步骤,首先第一步为relax,主要功能是处理跳转指令的伪指令操作,并对对应的跳转指令进行调整,第二步为fix,即对第一遍处理时没有解析的地址信息进行解析,而没有解析的地址符号则会输出到对应的重定位信息中。然后就是利用BFD库中相关函数,对文件进行输出。
GAS重要数据结构
Symbol是GAS中的重要数据结构之一,主要字段为BFD的asymbol,用于输出,frag类型的指针,用于指明符号所在frag,还有为expression的符号值。其余的字段用于指示符号所处的环境和属性,一般用于符号的处理和输出等操作。
/* BFD中的asymbol,用以输出 */ asymbol *bsym; /* 符号的值,后文介绍 */ expressionS sy_value; /* 前后引用,构成链表结构 */ struct symbol *sy_next; struct symbol *sy_previous; /*符号所在的frag */ struct frag *sy_frag; /* 该符号是否已经输出 */ unsigned int written : 1; // 后两个是表示状态量 /*该符号是否已经完全解析 */ unsigned int sy_resolved : 1; /*该符号是否正在解析 */ unsigned int sy_resolving : 1; /*该符号是否会使用在重定位当中(是的话,会强制输出) */ unsigned int sy_used_in_reloc : 1; /* 符号是否作为操作数或者表达式(符号还可以做标号等) */ unsigned int sy_used : 1; /* 符号是否可以重定义 */ unsigned int sy_volatile : 1; /* 符号是否是前向引用 */ unsigned int sy_forward_ref : 1; /* 符号是否定义与MRI段中 */ unsigned int sy_mri_common : 1; /* 符号是否定义在.weakref符号后 */ unsigned int sy_weakrefr : 1; /* 符号是否在.weakref之后,并且符号没有在符号表中定义 */ unsigned int sy_weakrefd : 1; |
除了Symbol之外,还有一种Symbol类型,用于表示未确定的也就是未解析的符号类型,是local_symbol。
/* 占位符,恒为NULL,表示此符号为local_symbol */ asymbol *lsy_marker; /* 占位符,如果为reg_section的话,则表示符号解析完毕 */ segT lsy_section; /* 符号的字符串名称 */ const char *lsy_name; /* 当符号尚未解析,则为frag;如果解析成功,则为实际符号 */ union { fragS *lsy_frag; symbolS *lsy_sym; } u; /* 符号值,为地址值,即当时定义符号所在的地址 */ valueT lsy_value; |
Local_symbol表示未解析的符号类型,当解析成功之后,local_symbol中会包含实际符号值,而并不会删除对应的local_symbol。可以看到,local_symbol内部除了实际符号字段外,大多为标志占位符,表示解析是否成功。
二者关系图如下:
表达式Expression的数据结构:
/*主符号 */ symbolS *X_add_symbol; /*第二个操作的符号 */ symbolS *X_op_symbol; /*可有可无的立即数(可以代表地址) */ offsetT X_add_number; /* 如何解释该表达式 */ operatorT X_op : 8; /* 表明X_add_number是否为无符号 */ unsigned int X_unsigned : 1; |
Expression类型不止是如其字面意思所示,除了表达表达式或复杂表达式(复杂表达式会使用在伪段expr_section定义的符号表达),还可以表达符号的值,而其中X_op(为枚举值)则指示了如何将X_add_symbol,X_op_symbol和X_add_number组合起来形成表达式值。如下则是X_op所代表的表达式组合:
/* 非法表达式 */ O_illegal, /* 不存在的表达式 */ O_absent, /* X_add_number (a constant expression). */ O_constant, /* X_add_symbol + X_add_number. */ O_symbol, /* X_add_symbol + X_add_number - the base address of the image. */ O_symbol_rva, /* A register (X_add_number is register number). */ O_register, /* 如果X_add_number小于等于0,则表示浮点数,否则,表示整数 */ O_big, /* (- X_add_symbol) + X_add_number. */ O_uminus, /* (~ X_add_symbol) + X_add_number. */ O_bit_not, /* (! X_add_symbol) + X_add_number. */ O_logical_not, /* (X_add_symbol * X_op_symbol) + X_add_number. */ O_multiply, /* (X_add_symbol / X_op_symbol) + X_add_number. */ O_divide, /* (X_add_symbol % X_op_symbol) + X_add_number. */ O_modulus, /* (X_add_symbol << X_op_symbol) + X_add_number. */ O_left_shift, /* (X_add_symbol >> X_op_symbol) + X_add_number. */ O_right_shift, /* (X_add_symbol | X_op_symbol) + X_add_number. */ O_bit_inclusive_or, /* (X_add_symbol |~ X_op_symbol) + X_add_number. */ O_bit_or_not, /* (X_add_symbol ^ X_op_symbol) + X_add_number. */ O_bit_exclusive_or, /* (X_add_symbol & X_op_symbol) + X_add_number. */ O_bit_and, /* (X_add_symbol + X_op_symbol) + X_add_number. */ O_add, /* (X_add_symbol – X_op_symbol) + X_add_number. */ O_subtract, /* (X_add_symbol == X_op_symbol) + X_add_number. */ O_eq, /* (X_add_symbol != X_op_symbol) + X_add_number. */ O_ne, /* (X_add_symbol < X_op_symbol) + X_add_number. */ O_lt, /* (X_add_symbol <= X_op_symbol) + X_add_number. */ O_le, /* (X_add_symbol >= X_op_symbol) + X_add_number. */ O_ge, /* (X_add_symbol > X_op_symbol) + X_add_number. */ O_gt, /* (X_add_symbol && X_op_symbol) + X_add_number. */ O_logical_and, /* (X_add_symbol || X_op_symbol) + X_add_number. */ O_logical_or, /* X_op_symbol [ X_add_symbol ] */ O_index, /* 机器相关表达式类型 */ O_md1, O_md2, O_md3, O_md4, O_md5, O_md6, O_md7, O_md8, O_md9, O_md10, O_md11, O_md12, O_md13, O_md14, O_md15, O_md16, O_md17, O_md18, O_md19, O_md20, O_md21, O_md22, O_md23, O_md24, O_md25, O_md26, O_md27, O_md28, O_md29, O_md30, O_md31, O_md32, /* 表达式最大值 */ O_max |
通过上述的X_op所代表的表达式组合方式,可以看到,Expression可以表达诸如LHS op RHS + offset的表达式,并且通过这样的方式,生成相应的值。
Relax_type在输出目标文件时进行relax时所需要的信息,包括跳转的地址范围和目前地址长度,比如,6位偏移,然后左移两位,那么这个地址偏移可以表示前后128字节的地址,并且地址字节长度为1。这个类型主要使用在机器相关的处理,做移植工作的人员需要提供目标机器所对应的跳转类型,即relax表,便于relax的进行。
/* 该跳转向前可以跳转最大偏移 */ offsetT rlx_forward; /*该跳转向前可以跳转最大偏移*/ offsetT rlx_backward; /* 地址的字节长度(即表示该跳转地址的字节长度) */ unsigned char rlx_length; /* 是否还有需要relax,0为结束 */ relax_substateT rlx_more; |
union { //重定位之前 struct { symbolS *offset_sym; //偏移量 reloc_howto_type *howto; //重定位类型 symbolS *sym; //需要定位符号 bfd_vma addend; //基址 } a; //重定位之后 struct { asection *sec; //重定位从属的段 asymbol *s; //重定位符号 arelent r; //重定位信息入口 } b; } u; |
Reloc_list包含了重定位信息,包括重定位之前的信息收集(包括基址,偏移和重定位类型),和重定位之后信息(主要是arelent,即BFD中特殊的重定位入口类型)
/*是否是pc相对重定位 */ unsigned fx_pcrel : 1; /*是否为立即数偏移量 */ unsigned fx_im_disp : 2; /* 机器相关位,自己备用 */ unsigned fx_tcbit : 1; unsigned fx_tcbit2 : 1; /*是否经过重定位 */ unsigned fx_done : 1; /*是否取消溢出 */ unsigned fx_no_overflow : 1; /* 是否检查溢出 */ unsigned fx_signed : 1; /* pc相对偏移量 */ char fx_pcrel_adjust; /* fix需要多少字节数*/ unsigned char fx_size; /* 进行fix所对应的frag */ fragS *fx_frag; /* 要进行fix的起始地址 */ long fx_where; /*要增加的符号或符号链表 */ symbolS *fx_addsy; /*要去除的符号或符号链表 */ symbolS *fx_subsy; /*要增加的绝对偏移 */ valueT fx_offset; /*当前的dot地址(dot值是特殊的值,类似当前地址,用于计算实际fix后偏移) */ addressT fx_dot_value; /* 链表结构 */ struct fix *fx_next; /* 对bit调整,即少于1个字节的fix */ bit_fixS *fx_bit_fixP; //重定位类型(如何定位相对或者绝对,定位长度64 32等) bfd_reloc_code_real_type fx_r_type; |
Fix结构主要用于在输出目标文件期间,对未解析地址信息进行fix,而fix的信息,是有md_assemble中对于特定指令所生成的fix信息,链接到一个fix链表中,这个fix链表分别保存在frchain(frag的一个控制链表)和segment_info(段信息)中(后文介绍)。
/*位于目标文件的原始地址(字节偏移量)*/ addressT fr_address; /* 最后一遍relax后的地址 */ addressT last_fr_address; /* fix之后frag中的固定字节大小 */ offsetT fr_fix; /* 固定字节之后的字节大小 */ offsetT fr_var; /* 可变尾部的大小 */ offsetT fr_offset; /* 尾部可变符号(链表) */ symbolS *fr_symbol; /* opcode地址 */ char *fr_opcode; /* 构成链表结构 */ struct frag *fr_next; //下一条代码段 /* 是否跳过relax */ unsigned int relax_marker:1; /* 是否为空frag */ unsigned int has_code:1; /* 指令地址 */ unsigned int insn_addr:6; /* relax状态 */ relax_stateT fr_type; /* 这个也是relax状态,可以用于在机器相关处理的分类 */ relax_substateT fr_subtype; /* 代码片段所包含的二进制信息 */ char fr_literal[1]; |
Frag是GAS中基本的数据结构,它是基于汇编代码的最小结构,是最后构成目标输出文件的基本。可以看到,Frag包含了目前代码片段的起始地址和这些代码的二进制信息,而其余的信息,则是帮助该Frag进行relax和重定位。
struct frag *frch_root; /* frag头 */
struct frag *frch_last; /* frag尾 */
struct frchain *frch_next; /* 下一个frchain,构成链表 */
subsegT frch_subseg; /* 对应的subsegment */
fixS *fix_root; /* fix头 */
fixS *fix_tail; /* fix尾 */
struct obstack frch_obstack; /* 分配内存区,后文讲述 */
fragS *frch_frag_now; /* 当前的frag */
Frchain是frag的控制结构,可以看到,不仅包含所有对应的frag信息,还有当前操作的frag,同时,还有对应于这系列frag的fix信息。这些fix信息,分别保存在各自的frchain中,会在其后整理保存到相应的segment_info中。
上述几者关系如下图:
序号 | 名称 | 功能 |
1 | int md_parse_option(optc,optarg) | 机器相关对命令行选项处理(default处理) |
2 | void md_show_usage(stdout) | 输出机器相关选项帮助文件 |
3 | void md_pop_insert() | 在po_hash中插入伪指令符号 |
4 | void md_begin() | 开始第一pass前的准备工作 |
5 | void md_assemble(s) | 对s(行指令)进行汇编处理 |
6 | int md_estimate_size_before_relax(fragP,segment) | 返回frag需要增长的长度值 |
7 | void md_convert_frag(stdoutput,sec,fragP) | 调整rs_machine_dependent类型fragP的大小,满足sec的要求 |
8 | valueT md_section_align(seg,size) | 调整seg的大小,使之对齐边间 |
9 | void md_apply_fix (fixP, &add_number, this_segment); | 将add_number添加到对应的fixP中 |
10 | arelent* tc_gen_reloc (sec, fixp) | 生成重定位信息 |
11 | void md_number_to_chars(ptr,use,nbytes) | 根据大端还是小端,将数字转化为字符串 |
12 | void md_operand(expression) | 处理操作数,判断表达式 |
13 | void md_atof(type,litP,sizeP) | 字符串转换为浮点 |
14 | symbolS * md_undefined_symbol (char *name) | 处理未定义的符号 |
序号 | 名称 | 功能 |
1 | LOCAL_LABELS_FB | 是否使用局部标号(0~9) |
2 | tc_comment_chars | 是否定义了注释符号 |
3 | TARGET_FORMAT | 确定目标文件格式 |
4 | TARGET_ARCH | 确定机器体系结构 |
5 | md_end() | pass过源文件后的处理 |
6 | TARGET_BYTES_BIG_ENDIAN | 机器是否为大端 |
7 | tc_fix_adjustable(fixP) | 判断这个fix是否可以被gas独立完成 |
8 | TC_FORCE_RELOCATION(fix,seg) | 判断是否需要重定位 |
9 | MD_PCREL_FROM_SECTION(fix,sec) | 返回fix在sec中所要fix的偏移量 |
10 | LISTING_HEADER | 列表文件输出的开头 |
11 | md_operand | 处理操作数 |
12 | md_undefined_symbol | 处理未定义符号 |
GAS的工作过程的主要使用的变量:
序号 | 名称 | 类型 | 作用 |
1 | md_shortopts | const char * | 命令行选项简写 |
2 | md_longopts | struct option | 命令行选项全称 |
3 | md_longopts_size | size_t | 命令行选项大小 |
4 | md_pseudo_table | pseudo_typeS [] | 描述机器相关伪指令集 |
5 | line_separator | char | 行分隔符 |
6 | comment_chars | const char [] | 注释符号 |
7 | line_comment_chars | const char [] | 行注释符 |
8 | md_relax_table | relax_typeS [] | relax表 |
9 | md_short_jump_size | int | short jump的最大长度 |
10 | md_long_jump_size | int | long jump的最大长度 |
11 | EXP_CHARS | const char [] | 幂指数符号 |
12 | FLT_CHARS | const char [] | 浮点数符号 |