Home > Article > Operation and Maintenance > How to deeply understand GOT table and PLT table
Operating systems usually use dynamic linking to improve the efficiency of program running. In the case of dynamic linking, all functions in the link library are not loaded together when the program is loaded. Instead, they are loaded on demand when the program is executed. If a function is not called, then it will not be loaded in the program. Being loaded into life. This design can improve the smoothness of program operation and reduce memory space. Moreover, modern operating systems do not allow modification of code segments, only data segments, so the GOT table and PLT table came into being.
Let’s take a brief look at an example
Let’s follow up with scanf@plt
You will find that there are three lines of code
jmp 一个地址 push 一个值到栈里面 jmp 一个地址
Looking at the name of the function, you can know that this is the plt table of the scanf function. Don't worry about understanding what plt is. What is it used for? Let's continue to look down. Let's first look at what the first jmp is and where it jumps.
In fact, this is the got table of the function corresponding to the plt table, and we will find that the value of 0x201020 is the address of the push command, and other places are 0. At this time, I want to ask :
1. What is the significance of got table and plt table? Why do we jump around?
2. What is the connection between the got table and the plt table? Is there any corresponding relationship?
So if you have questions, first look at the answer, and then confirm it. We must understand that the operating system usually uses dynamic linking to improve the efficiency of program operation, and it cannot be written back to the code segment.
In the above example, we can see that call scanf —> scanf’s plt table —> scanf’s got table. As for the value of the got table, we don’t care about it for now. We can form such a thinking at this moment. The real scanf function can be found from the got table for the program to load and run.
After we think so, then this becomes an indirect addressing process
We get the data segment to store the function address The small piece of code is called PLT (Procedure Linkage Table). The data segment where the function address is stored is called GOT (Global Offset Table). After we form such a thought, we can carefully understand the details inside.
After understanding such a general process, let’s take a look at how it is called step by step. There are several doubts above. To solve:
1. How does the got table know the real address of the scanf function?
2. What is the structure of got table and plt table? Let's take a look at the plt table first. We just found that the third line of code in the scanf@plt table is a jmp address. Follow up to see what it is.
In fact, this is a program PLT table. Starting (plt[0]), what it does is:
push got[1] jmp **got[2]
Followed by the plt table of each function. At this time, let’s take a look at this mysterious GOT table again
Except for these two (the address of push 0xn of the printf and scanf functions, which is the second address of the corresponding plt table code address), other got[1], got[2] are 0, so what does the plt table point to the got table with 0? Because we have fallen behind a condition, modern operating systems do not allow modification of code segments, only data segments, which is write-back. A more professional name should be runtime relocation. When we run the program, our previous address and saved content have changed. Before that, let’s save the content during the link and make a comparison
② 寻找printf的plt表 ③ jmp到plt[0] ④ jmp got[2] -> 0x00000 ⑤⑥ printf和scanf的got[3] got[4] -> plt[1] plt[2]的第二条代码的地址 ⑦⑧ 证实上面一点
Run the program and set a breakpoint at scanf
. You can find that the scanf@plt table has changed at this time. Check the content in got[4]
Still continue debugging at the address of push 0x1, until here, the got[4] address is modified
I want to ask at this time, this is where?
##Then call<_dl_fixup> in got[2] to modify the address in got[3];那么问题就来了,刚才got[2]处不是0吗,怎么现在又是这个(_dl_runtime_resolve)?这就是运行时重定位。
其实got表的前三项是:
got[0]:address of .dynamic section 也就是本ELF动态段(.dynamic段)的装载地址 got[1]:address of link_map object( 编译时填充0)也就是本ELF的link_map数据结构描述符地址,作用:link_map结构,结合.rel.plt段的偏移量,才能真正找到该elf的.rel.plt got[2]:address of _dl_runtime_resolve function (编译时填充为0) 也就是_dl_runtime_resolve函数的地址,来得到真正的函数地址,回写到对应的got表位置中。
那么此刻,got表怎么知道scanf函数的真实地址?
这个问题已经解决了。我们可以看一下其中的装载过程:
说到这个,可以看到在_dl_runtimw_resolve之前和之后,会将真正的函数地址,也就是glibc运行库中的函数的地址,回写到代码段,就是got[n](n>=3)中。也就是说在函数第一次调用时,才通过连接器动态解析并加载到.got.plt中,而这个过程称之为延时加载或者惰性加载。
到这里,也要接近尾声了,当第二次调用同一个函数的时候,就不会与第一次一样那么麻烦了,因为got[n]中已经有了真实地址,直接jmp该地址即可。
The above is the detailed content of How to deeply understand GOT table and PLT table. For more information, please follow other related articles on the PHP Chinese website!