xxx.so has text relocations. This is wasting memory and is a security risk. Please fix

 最近使用jni编译so文件,发现在android M中将之前一直视为warning的text relocation升级为了error ,直接导致system.load so文件的时候失败。翻了网上各种资料,关于text relocation的解决办法总结了一下:


这个错误的主要原因是加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于 这个.so文件代码段和数据段内存映射的位置.



1、遇到该问题,首先检查ndk版本,如果版本为r8c一下,请首先升级ndk版本到最新

2、仍然不能解决,You need to make the code in your library position independent...add -fpic or -fPIC to your LOCALC_FLAGS in your Android.mk and you also need to ensure that you're not linking against any static or shared libraries that contain text relocations themselves. If they do and you can re-compile them, use one of the flags mentioned above.

-fpic为gcc使用方法,主要是生成与地址无关的可执行文件。具体解释见链接:

http://www.akkadia.org/drepper/textrelocs.html

为防止呗墙,这里保留一份:

Text Relocations

When code is executed by a processor it must usually access various parts of the address space to access objects like variables or functions. There are two ways in which objects can be access:

  • with their absolute address in the address space
  • with relative addresses, i.e., the difference between two addresses

The former seem to be the most obvious choice. At runtime each object has a fixed address which has to be known and can be used. There are two key words in this sentence: at runtime. The final layout of the address space is often not fixed until runtime. In the early days this was no problem. Programs consisted of one big block of code loaded at a fixed address. But today we have shared libraries and position independent executables (PIEs). For those kinds of position independent binaries the actual address used at runtime is not known until the binary is loaded. Referencing any object in an position independent binary cannot simply happen with an absolute address. There are two solutions of different efficiency for two different situations:

  1. The references to an object from a function in the same position independent binary can take advantage of the fact that regardless of where the binary is loaded, the relative distance between a specific point in the binary and the object is constant. Using addressing relative to a fixed point in the binary means that once the address of the fixed point in the object is determine all that is needed is to add the offset to the value to get the final address. The generated code does not need any change as long as the base value is treated as a variable.Some processors allow even simpler handling of this: the address of the instruction can be implicitly used as the base value. This requires no base value kept around.

  2. For references from a function in one binary to an object in another binary there is no fixed offset which can be determined at link-time. With ELF the final object might not even be found in the same binary for every start of the program. Therefore it is necessary to dynamically compute the address of the object the first time it is used in each program run.

How to generate programs to create the best possible references and many more things about position independent binaries is explained in my DSO How-To. What is explained in the following sections is how to correct the mistake of not using the correct way to generate position independent binaries. The result of such mistakes are text relocations. Some of the more modern architectures architecture (or more correctly, their ABIs) actively forbid text relocations. But there are others and unfortunately x86 is among them.

What do they look like?

A text relocation is the result of a reference to an object with a variable address at runtime using an absolute addressing mode. The instruction encoding itself contains the address and therefore the executable text of the binary must be changed to contain the correct address when taking the actual load addresses at runtime into account.

The result of a text relocation is that the binary text is written to. This means this page of the binary cannot be physically shared with other processes on the system (this is the goal of DSOs, aka shared libraries). It also means that the binary must have permission to change the access permissions for the memory page to include writing and then back to executing. This is a privileged operation when SELinux is enabled. I have a write-up of the ways this privilege mechanism makes itself known and how it can be controlled, if necessary.

Spotting binaries with text relocations is simple. The linker is supposed to mark them. It sets the DF_TEXTREL bit in the DT_FLAGS entry in the dynamic section. In older binaries a DT_TEXTREL entry is present. In any case, a binary with text relocations can be spotted with this:

eu-readelf -d  | fgrep -q TEXTREL

If this command pipeline exits with a zero status a text relocation is present.

This is the easy part of recognizing text relocations. It is more difficult to spot the exact relocation record(s) responsible for the TEXTREL flag. The relocation records of a binary can be viewed with

eu-readelf -r 

The first column is the address. Every address that is in the range of a segment which is loaded without write permission indicates a text relocation. The addresses of the segments can be seen with

eu-readelf -l  | fgrep LOAD

This alone can be pretty time consuming but it gets worse.

What is responsible?

To be able to remove the text relocation it is necessary to find out what piece of code corresponds to the address with the text relocation. The minimum information needed is a function name. Better yet is it to get the source file name and line number information as well. To get all this one can look through the symbol tables and debug information of the binary. eu-readelf provides all this information as well.but it is tedious to get to it. There is the eu-addr2lineprogram which can make this much simpler. Given a binary name, the program can provide source file and line information for all the address given as input.

This is still quite complicated. It means three steps to arrive at the result. Too much for most developers who are not accustomed with binary formats or cannot find the time to dig into the details.

There is Help

Because text relocations are a huge problem and because it is obvious that programmers are not willing to put in the time to figure out the details I have written a tool which automates the process. It is, just like the other programs mentioned here, part of the elfutils package. Before we see how to use the program look at this code:

int a;
int
main (void)
{
  return a;
}

If this code is compiled on x86 with

gcc -o u -g -pie u.c

one can verify with the above mentioned commands that the resulting binary contains text relocations. The explained three steps can be used to find the reason for the text relocation. Or: one can run a single command:

eu-findtextrel u

In the case of this binaries the result of this program run will be the following output:

/home/drepper/local/elfutils-build/20060530/u.c not compiled with -fpic/-fPIC

That's as precise as one gets it. The solution is indeed to pass additional parameters to the compiler. -fpic and -fPIC are mentioned here (see the DSO How-To for the difference). The tool could also have mentioned -fpie and -fPIE which might be more optimal in this case. In any case, the solution for most C files is as simple as this.

The tool is not able to distinguish compiler-generated code from assembler code written by the programmer. In both cases the tool will print the message above since it is what is correct in 99% of all cases. If the problem is indeed the result of hand-written assembler code the solution is not as simple as adding a compiler/assembler flag. The code needs to be rewritten. This is architecture specific and can vary widely between every single instance. We are not going into those details here. Find a person with sufficient assembly programming skills if this problem appears.

The program above was compiled with debugging enabled. If this is not the case or the debug information is not available the tool cannot give information about the file which must be recompiled. In this case the output looks like this:

the file containing the function 'main' is not compiled with -fpic/-fPIC

It is left to the person who has to fix the problem to map the function name to a source file. In some situation it is not clear which function is responsible and then the tool gives the user the names of two functions, both of which are candidates. This is still a good situation. Sometimes no information at all is available and then the tool can only provide an address value and it is up to the user to figure out from what sources the code at that address has been generated. The tool does all it can to give as good information as possible. But there are limits. To not be exposed to the limits it is best to have debug information available.

Conclusion

Using the eu-findtextrel program it is in most situations relatively painless to determine the culprit(s) for the text relocations easily. There is usually no reason to not fix the problems. While a programs with text relocations can be made to run by relaxing the SELinux security this is a bad idea. The kind of permissions which have to be granted to the program create a gaping hole in the security policy. Attackers will be able to modify the memory as well. If this is not the case a program can enforce a strict W^X policy. I.e., no memory page is writable and executable at the same time. And more: SElinux can also enforce that no writable page can be marked as read/exec-only. With these provisions an attacker has no room where to place his/her exploit code. This is a huge win. So, always fix all text relocations. We've made it as easy as possible.


3、如果在so中,动态链接了其他可执行文件,比如.o文件,同样必须要保证.o文件使用-fpic编译,不能有text relocation问题


-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
  则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
  位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

gcc -shared -fPIC -o 1.so 1.c
这里有一个-fPIC参数
PIC就是position independent code
PIC使.so文件的代码段变为真正意义上的共享
如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于 这个.so文件代码段和数据段内存映射的位置.


不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目.

因此,不用fPIC编译so并不总是不好.
如果你满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享

如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1:gcc默认开启-fPIC选项
2:loader使你的代码位置无关

从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下


`-shared'
     Produce a shared object which can then be linked with other
     objects to form an executable.  Not all systems support this
     option.  For predictable results, you must also specify the same
     set of options that were used to generate code (`-fpic', `-fPIC',
     or model suboptions) when you specify this option.(1)



-fPIC 的使用,会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则,无法实现动态链接。

non-PIC 与 PIC 代码的区别主要在于 access global data, jump label 的不同。
比如一条 access global data 的指令,
non-PIC 的形势是:ld r3, var1
PIC 的形式则是:ld r3,   var1-offset@GOT ,意思是从 GOT 表的 index 为 var1-offset 的地方处
指示的地址处装载一个值,即 var1-offset@GOT 处的4个 byte 其实就是 var1 的地址。这个地址只有在运行的时候才知道,是由 dynamic-loader(ld-linux.so) 填进去的。

再比如 jump label 指令
non-PIC 的形势是:jump printf ,意思是调用 printf。
PIC 的形式则是:jump   printf-offset@GOT ,
意思是跳到 GOT 表的 index 为 printf-offset 的地方处
指示的地址去执行,
这个地址处的代码摆放在 .plt section

每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为 printf-offset 的地方,

同时执行这个函数。这样,第2次呼叫 printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。

GOT 是 data section, 是一个 table, 除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;
PLT 是 text section, 是一段一段的 code,执行中不需要修改。
每个 target 实现 PIC 的机制不同,但大同小异。比如 MIPS 没有 .plt, 而是叫 .stub,功能和 .plt 一样。

可见,动态链接执行很复杂,比静态链接执行时间长;但是,极大的节省了 size,PIC 和动态链接技术是计算机发展史上非常重要的一个 里程碑。

gcc manul上面有说
-fpic        If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)

-fPIC       If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.

关键在于GOT全局偏移量表里面的跳转项大小。
intel处理器应该是统一4字节,没有问题。
powerpc上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。

-fpic为了节约内存,在GOT里面预留了“短”长度。
而-fPIC则采用了更大的跳转项。


你可能感兴趣的:(android)