[hellogcc] Re: gcc中的reloc实现简介

  • From: Yao Qi <qiyaoltc@xxxxxxxxx>
  • To: hellogcc@xxxxxxxxxxxxx
  • Date: Fri, 21 Oct 2011 09:50:39 +0800

On Thu, Oct 20, 2011 at 05:09:19PM +0800, Liu wrote:
> 我做的时候没找到资料,现在尝试造一个资料。

> #include <stdio.h>
> 
> int add(int a, int b)
> {
>   int c;
>   c = a + b;
> }
> 
> int main(void)
> {
>   int c;
> 
>   c = add(1, 2);
>   printf("c = %d\n", c);
> 
>   return 0;
> }


很好的文章。

> 
> \section{GOT和PLT是干神马吃滴}
> 上一节说过,调用的printf是libc.so里面的函数,也就是一个reloc的函数,而这个reloc有涉及到了PLT,追查PLT发现了一个叫GOT我玩意儿。
> 
> 我只能简单说一下,具体概念自己去补充,
> 
> PLT是把位置无关的调用定位到实际地址。GOT则是把位置无关的代码地位到实际地址。
>

PLT/GOT的作用比你说的要多一些,还是自己看linker&loader吧。
 
> 很乱,对吧?结合实际例子看看。
> \begin{shaded}
> \begin{verbatim}
>   400567:     e8 c4 fe ff ff          callq  400430 <printf@plt>
> \end{verbatim}
> \end{shaded}
> PLT把这个位置无关的call,即call printf定位到400430,400430是什么?
> \begin{shaded}
> \begin{verbatim}
> 0000000000400430 <printf@plt>:
>   400430:     ff 25 d2 1b 00 00       jmpq   *0x1bd2(%rip)        # 402008 
> <_GLOBAL_OFFSET_TABLE_+0x20>
> \end{verbatim}
> \end{shaded}
> 就是这个,printf函数的地址。
> 
> 但是400430地址的代码是一个jmp,马上jmp到了一个地址,而这个地址恰恰就是\_GLOBAL\_OFFSET\_TABLE\_+0x20,\_GLOBAL\_OFFSET\_TABLE\_是一个基地址,通过相对基地址的偏移来寻址的。
> 
> \section{gcc里面是怎么处理GOT和PLT的}
> 现在应该明确了一件事情,那就是gcc对reloc的处理其实只要关心call就好。

这句不对吧。其实你这里讲的都是PIC 代码。reloc 和 PIC没有什么直接关系。
PIC和非PIC都需要 reloc,数据段也需要reloc。

严格说,gcc对pic的处理关心call,但是不是*只*关心call。只是说在这个例子,
关心call。
> 
> machine.h里面需要有一行代码定义GOT使用哪个寄存器存放基地址。
> \begin{shaded}
> \begin{verbatim}
> #define PIC_OFFSET_TABLE_REGNUM num
> \end{verbatim}
> \end{shaded}
> 
> 然后machine.md里面对call要有相应的照顾。
> \begin{shaded}
> \begin{verbatim}
> (define_expand "call"
>   [(parallel [(call (match_operand:SI 0 "sym_ref_mem_operand" "")
>                   (match_operand 1 "" "i"))
>             (clobber (reg:SI 9))])]

能把上边这个解释一下不? operand 0 和operand 1分别是什么?
为啥clobber reg:SI 9?这个是什么?

问题可能有点跑题 :)

>   ""
>   "
> {
>   if (flag_pic)
>     {
>       rtx reg = gen_reg_rtx (SImode);
>       emit_insn (gen_set_got (pic_offset_table_rtx));
>       emit_call_insn (gen_call_internal_plt (operands[0], reg));
>     }
>   else
>     emit_call_insn (gen_call_internal (operands[0], operands[1]));
>   DONE;
> }")
> \end{verbatim}
> \end{shaded}
> 首先,call的操作数就是sym\_ref\_mem\_operand了,sym是一个标号,位置未确定的那种。
> 
> 然后,通过emit\_insn (gen\_set\_got (pic\_offset\_table\_rtx))来找到相对GOT的偏移。
> 
> 最后才是emit\_call\_insn (gen\_call\_internal\_plt (operands[0], 
> reg))输出PIC的call代码。
> 
> 跟最开始,我们通过printf这个sym找到PLT,再通过PLT找到GOT的线索刚好是一个逆过程。
> 
> 做一个port要做完整,PIC这种基本的东西,该有的都应该做了的。
> \end{document}


-- 
Yao Qi <qiyaoltc AT gmail DOT com>

If you learn one useless thing every day, in a single year you'll learn
365 useless things.

Other related posts: