从程序员角度看ELF (3)
时间:2007-04-09 来源:Echo CHEN
★5.3 扩展的GCC特性
GCC有许多扩展的特性。有些对ELF特别的有用。其中一个就是__attribute__。 使用__attribute__可以使一个函数放到__CTOR_LIST__或者__DTOR_LIST__里。
例如:
[alert7@redhat62 dl]# cat miss.c
#include <stdio.h>
#include <stdlib.h>
static void foo(void) __attribute__ ((constructor));
static void bar(void) __attribute__ ((destructor));
int main(int argc, char *argv[])
{
printf("foo == %p\n", foo);
printf("bar == %p\n", bar);
exit(EXIT_SUCCESS);
}
void foo(void)
{
printf("hi dear njlily!\n");
}
void bar(void)
{
printf("missing u! goodbye!\n");
}
[alert7@redhat62 dl]# gcc -o miss miss.c
[alert7@redhat62 dl]# ./miss
hi dear njlily!
foo == 0x8048434
bar == 0x8048448
missing u! goodbye!
我们来看看是否加到了.ctors和.dtors中。
[alert7@redhat62 dl]# objdump -s -j .ctors miss
miss: file format elf32-i386
Contents of section .ctors:
8049504 ffffffff 34840408 00000000 ....4.......
[alert7@redhat62 dl]# objdump -s -j .dtors miss
miss: file format elf32-i386
Contents of section .dtors:
8049510 ffffffff 48840408 00000000 ....H.......
已经把foo和bar地址分别放到了.ctors和.dors,显示34840408只是因为
x86上是LSB编码的,小端序。
__attribute__ ((constructor))促使函数foo在进入main之前会被自动调用。__attribute__ ((destructor))促使函数bar在main返回或者exit调用之后会被自动调用。foo和bar必须是不能带参数的而且必须是static void类型的函数。在ELF下,这个特性在一般的可执行文件和共享库中都能很好的工作。
我们也可以创建自己的section,在这里我创建了一个alert7 section.
[alert7@redhat62 dl]# cat test.c
#include <stdio.h>
#include <stdlib.h>
static void foo(void) __attribute__ ((section ("alert7")));
static void bar(void) __attribute__ ((section ("alert7")));
int main(int argc, char *argv[])
{
foo();
printf("foo == %p\n", foo);
printf("bar == %p\n", bar);
bar();
exit(EXIT_SUCCESS);
}
void foo(void)
{
printf("hi dear njlily!\n");
}
void bar(void)
{
printf("missing u! goodbye!\n");
}
[alert7@redhat62 dl]# gcc -o test test.c
[alert7@redhat62 dl]# ./test
hi dear njlily!
foo == 0x804847c
bar == 0x8048490
missing u! goodbye!
[alert7@redhat62 dl]# objdump -x test
....
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 00000013 080480f4 080480f4 000000f4 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
12 alert7 00000026 0804847c 0804847c 0000047c 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
...
[alert7@redhat62 dl]# objdump -D test
Disassembly of section alert7:
0804847c <foo>:
804847c: 55 push %ebp
804847d: 89 e5 mov %esp,%ebp
804847f: 68 de 84 04 08 push $0x80484de
8048484: e8 a3 fe ff ff call 804832c <_init+0x70>
8048489: 83 c4 04 add $0x4,%esp
804848c: c9 leave
804848d: c3 ret
804848e: 89 f6 mov %esi,%esi
08048490 <bar>:
8048490: 55 push %ebp
8048491: 89 e5 mov %esp,%ebp
8048493: 68 ef 84 04 08 push $0x80484ef
8048498: e8 8f fe ff ff call 804832c <_init+0x70>
804849d: 83 c4 04 add $0x4,%esp
80484a0: c9 leave
80484a1: c3 ret
在这里,我创建了一个自己的alert7 section,并把foo,bar两个函数放到了这个section中。一般定义的函数都会放在.text section中。
★5.3.1 在C库中的初始化函数
另外一个GCC的特性是__attribute__( section ("sectionname") ).使用这个,能把一个函数或者是数据结构放到任何的section中。
static void
foo (int argc,char **argc,char **envp)
__attribute__ ((section ("_libc_foo")));
static void
foo (int argc,char **argv,char **envp)
{
}
static void
bar (int argc,char **argv,char **envp)
{
}
static void * __libc_subinit_bar__
__attribute__ (( section ("_libc_subinit")))=&(bar);
这里,我们把foo放到了_libc_foo section,把__libc_subinit_bar__放到了_libc_subinit section中。在Linux C库中,_libc_subinit 是一个特别的section,它包含了一个函数指针(有如下原型)的数组。
void (*) (int argc,char **argv,char **envp);
这里的argc,argv,envp跟在main中的有同样的意义。该section中的函数在进入main函数之前就会被调用。这是很有用的,可用来在Linux C库中初始化一些全局变量。
[译注:_libc_subinit section真有这个特别的功能吗?我是没有试成功,如果有人试成功或者认为我理解有误的地方,千万记得mail给我:)
测试程序如下:
#include <stdio.h>
#include <stdlib.h>
static void foo(int argc,char **argv,char **envp)
{
printf("hi dear njlily!\n");
}
int main(int argc, char *argv[])
{
printf("foo == %p\n", foo);
exit(EXIT_SUCCESS);
}
static void * __libc_subinit_bar__
__attribute__ (( section ("_libc_subinit")))=&(foo);
[alert7@redhat62 dl]# gcc -o test1 test1.c
[alert7@redhat62 dl]# ./test1
foo == 0x8048400
:( 用objdump,显示已经创建了一个_libc_subinit section,并且
该section前四个字节就是foo地址0x8048400
]
★5.4 利用GCC和GNU ld
这一些命令行的选项对GCC和GNU ld创建ELF时特别有用。-shared告诉gcc产生一个共享库,该共享库能在连接时和其他的共享文件一起形成可执行文件,该共享库也能在运行时装载进可执行文件的地址空间。使用-shared是创建一个共享ELF库的首选方法。
另外一个有用的命令行选项是-Wl,ldoption,传递参数ldoption作为连接器的选项。假如ldoption包含多个逗号,将分离成多个选项。
-static选项将产生一个和static库一道连接的可执行文件。当没有开启-static选项时,连接器首先试着用共享库,假如共享版本不可用,然后再试着用静态(static)库。
这里还有些特别的命令行选项对ELF来说特别的或者说是有用的。
-dynamic-linker file
设置动态连接器(dynamic linker)的名字。默认的动态连接器 或者是/usr/lib/libc.so.1或者是/usr/lib/libd1.so.1
-export-dynamic
告诉连接器使在可执行文件中的所有标号对动态连接器可用。当一个动态装载进的共享库参考可执行文件中的标号,该标号一般在动态连接时是不可用时,这时候就特别有用。
-lfile
加文件到需要连接的列表中。该选项可用在许多时候。ld将搜索它的path-list查找文件libfile.so(也就是说假如库为libbar.so,那么使用的时候就这样使用,-lbar),或者libfile.a(static版本的)。
一些情况下,共享库名libfile.so会被存储在resulting executable 或者是共享库中。当resulting executable或者是共享库被装载进内存,动态连接器也将把使用记录过的共享库装载到进程的地址空间去。在以后的事情情况下,把必要的函数和数据被拷贝到可执行文件,减少代码长度。
-m emulation
仿效emulation连接器r.-V参数可列出所有可用的选项.
-M | -Map mapfile
把连接map输出到标准输出或者一个mapfile文件里,该连接map含有关于标号被ld映象到了哪里的一些诊断信息,还有全局共同的存储分配信息。
-rpath directory
加一个目录到运行时library的搜索路径。所有的-rpath参数被连接在一起然后传给动态连接器。它们被用来在运行时定位共享库。
-soname name
当创建一个共享库时,指定的名字被放在共享库中。当和这个共享库连接的可执行文件被运行,动态连接器将试着map记录着的指定名字的共享库而不是传给连接器的文件名。
-static
告诉连接器不要和任何共享库连接。
-verbose
告诉连接器打印它每个要打开的文件名。
linux下gcc beta版本使用-dynamic-linker file选项设置动态连接器为/lib/ld-linker.so.1。该选项可以使ELF和a.out共享库很好的共存。
有件事情是另人感兴趣的。
[alert7@redhat62 dl]# gcc -shared -o libbar.so libbar.o -lfoo
假如libfoo.so被用来创建共享库时,有趣的时候就会发生了。当libbar.so被映象到进程的地址空间的时候,动态连接器也把libfoo.so映象到内存。假如libbar.so需要libfoo.so的时候,这个特性非常有用。实际上使用libbars.o库的程序编译时是不需要-lfoo的。假如archive版本的libfoo.a被使用,当在libbar.a中的标号被libbar.o引用时,它将会被搜索到。假使在libbar.so中包含libfoo.a甚至它们根本不被libbar.o使用,在这样的情况下必须逐步把.o文件加到libbar.o中:
# rm -rf /tmp/foo
# mkdir /tmp/foo
# (cd /tmp/foo/;ar -x ....../libfoo.a)
# gcc -shared -o libbar.so libbar.o /tmp/foo/*.o
# rm -rf /tmp/foo
在libfoo.a中的.o文件必须用-fPIC编译或者至少和PIC(位置无关)是兼容的。
当使用
static void * __libc_subinit_bar__
__attribute__ ((section ("_libc_subinit")))=&(bar);
来把一个标号放到一个没有被连接器定义的section中(在这里是_libc_subinit).连接器将所有在_libc_subinit section中的标号共同创建两个标号,一个是__start__libc_subinit和__stop__libc_subinit,它们作为C的标志符被使用。
警告:
下面是完全可能的:连接器可能不能搜索到包含_libc_subinit section的文件(该section中没有程序执行需要的标号)。这就使程序要确定使_libc_subinit section能被连接器搜索得到。
一种解决的办法是:把一个dummy标号放到_libc_subinit section中,在文件中定义它,使它参考引用_libc_subinit section.
GCC有许多扩展的特性。有些对ELF特别的有用。其中一个就是__attribute__。 使用__attribute__可以使一个函数放到__CTOR_LIST__或者__DTOR_LIST__里。
例如:
[alert7@redhat62 dl]# cat miss.c
#include <stdio.h>
#include <stdlib.h>
static void foo(void) __attribute__ ((constructor));
static void bar(void) __attribute__ ((destructor));
int main(int argc, char *argv[])
{
printf("foo == %p\n", foo);
printf("bar == %p\n", bar);
exit(EXIT_SUCCESS);
}
void foo(void)
{
printf("hi dear njlily!\n");
}
void bar(void)
{
printf("missing u! goodbye!\n");
}
[alert7@redhat62 dl]# gcc -o miss miss.c
[alert7@redhat62 dl]# ./miss
hi dear njlily!
foo == 0x8048434
bar == 0x8048448
missing u! goodbye!
我们来看看是否加到了.ctors和.dtors中。
[alert7@redhat62 dl]# objdump -s -j .ctors miss
miss: file format elf32-i386
Contents of section .ctors:
8049504 ffffffff 34840408 00000000 ....4.......
[alert7@redhat62 dl]# objdump -s -j .dtors miss
miss: file format elf32-i386
Contents of section .dtors:
8049510 ffffffff 48840408 00000000 ....H.......
已经把foo和bar地址分别放到了.ctors和.dors,显示34840408只是因为
x86上是LSB编码的,小端序。
__attribute__ ((constructor))促使函数foo在进入main之前会被自动调用。__attribute__ ((destructor))促使函数bar在main返回或者exit调用之后会被自动调用。foo和bar必须是不能带参数的而且必须是static void类型的函数。在ELF下,这个特性在一般的可执行文件和共享库中都能很好的工作。
我们也可以创建自己的section,在这里我创建了一个alert7 section.
[alert7@redhat62 dl]# cat test.c
#include <stdio.h>
#include <stdlib.h>
static void foo(void) __attribute__ ((section ("alert7")));
static void bar(void) __attribute__ ((section ("alert7")));
int main(int argc, char *argv[])
{
foo();
printf("foo == %p\n", foo);
printf("bar == %p\n", bar);
bar();
exit(EXIT_SUCCESS);
}
void foo(void)
{
printf("hi dear njlily!\n");
}
void bar(void)
{
printf("missing u! goodbye!\n");
}
[alert7@redhat62 dl]# gcc -o test test.c
[alert7@redhat62 dl]# ./test
hi dear njlily!
foo == 0x804847c
bar == 0x8048490
missing u! goodbye!
[alert7@redhat62 dl]# objdump -x test
....
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 00000013 080480f4 080480f4 000000f4 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
12 alert7 00000026 0804847c 0804847c 0000047c 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
...
[alert7@redhat62 dl]# objdump -D test
Disassembly of section alert7:
0804847c <foo>:
804847c: 55 push %ebp
804847d: 89 e5 mov %esp,%ebp
804847f: 68 de 84 04 08 push $0x80484de
8048484: e8 a3 fe ff ff call 804832c <_init+0x70>
8048489: 83 c4 04 add $0x4,%esp
804848c: c9 leave
804848d: c3 ret
804848e: 89 f6 mov %esi,%esi
08048490 <bar>:
8048490: 55 push %ebp
8048491: 89 e5 mov %esp,%ebp
8048493: 68 ef 84 04 08 push $0x80484ef
8048498: e8 8f fe ff ff call 804832c <_init+0x70>
804849d: 83 c4 04 add $0x4,%esp
80484a0: c9 leave
80484a1: c3 ret
在这里,我创建了一个自己的alert7 section,并把foo,bar两个函数放到了这个section中。一般定义的函数都会放在.text section中。
★5.3.1 在C库中的初始化函数
另外一个GCC的特性是__attribute__( section ("sectionname") ).使用这个,能把一个函数或者是数据结构放到任何的section中。
static void
foo (int argc,char **argc,char **envp)
__attribute__ ((section ("_libc_foo")));
static void
foo (int argc,char **argv,char **envp)
{
}
static void
bar (int argc,char **argv,char **envp)
{
}
static void * __libc_subinit_bar__
__attribute__ (( section ("_libc_subinit")))=&(bar);
这里,我们把foo放到了_libc_foo section,把__libc_subinit_bar__放到了_libc_subinit section中。在Linux C库中,_libc_subinit 是一个特别的section,它包含了一个函数指针(有如下原型)的数组。
void (*) (int argc,char **argv,char **envp);
这里的argc,argv,envp跟在main中的有同样的意义。该section中的函数在进入main函数之前就会被调用。这是很有用的,可用来在Linux C库中初始化一些全局变量。
[译注:_libc_subinit section真有这个特别的功能吗?我是没有试成功,如果有人试成功或者认为我理解有误的地方,千万记得mail给我:)
测试程序如下:
#include <stdio.h>
#include <stdlib.h>
static void foo(int argc,char **argv,char **envp)
{
printf("hi dear njlily!\n");
}
int main(int argc, char *argv[])
{
printf("foo == %p\n", foo);
exit(EXIT_SUCCESS);
}
static void * __libc_subinit_bar__
__attribute__ (( section ("_libc_subinit")))=&(foo);
[alert7@redhat62 dl]# gcc -o test1 test1.c
[alert7@redhat62 dl]# ./test1
foo == 0x8048400
:( 用objdump,显示已经创建了一个_libc_subinit section,并且
该section前四个字节就是foo地址0x8048400
]
★5.4 利用GCC和GNU ld
这一些命令行的选项对GCC和GNU ld创建ELF时特别有用。-shared告诉gcc产生一个共享库,该共享库能在连接时和其他的共享文件一起形成可执行文件,该共享库也能在运行时装载进可执行文件的地址空间。使用-shared是创建一个共享ELF库的首选方法。
另外一个有用的命令行选项是-Wl,ldoption,传递参数ldoption作为连接器的选项。假如ldoption包含多个逗号,将分离成多个选项。
-static选项将产生一个和static库一道连接的可执行文件。当没有开启-static选项时,连接器首先试着用共享库,假如共享版本不可用,然后再试着用静态(static)库。
这里还有些特别的命令行选项对ELF来说特别的或者说是有用的。
-dynamic-linker file
设置动态连接器(dynamic linker)的名字。默认的动态连接器 或者是/usr/lib/libc.so.1或者是/usr/lib/libd1.so.1
-export-dynamic
告诉连接器使在可执行文件中的所有标号对动态连接器可用。当一个动态装载进的共享库参考可执行文件中的标号,该标号一般在动态连接时是不可用时,这时候就特别有用。
-lfile
加文件到需要连接的列表中。该选项可用在许多时候。ld将搜索它的path-list查找文件libfile.so(也就是说假如库为libbar.so,那么使用的时候就这样使用,-lbar),或者libfile.a(static版本的)。
一些情况下,共享库名libfile.so会被存储在resulting executable 或者是共享库中。当resulting executable或者是共享库被装载进内存,动态连接器也将把使用记录过的共享库装载到进程的地址空间去。在以后的事情情况下,把必要的函数和数据被拷贝到可执行文件,减少代码长度。
-m emulation
仿效emulation连接器r.-V参数可列出所有可用的选项.
-M | -Map mapfile
把连接map输出到标准输出或者一个mapfile文件里,该连接map含有关于标号被ld映象到了哪里的一些诊断信息,还有全局共同的存储分配信息。
-rpath directory
加一个目录到运行时library的搜索路径。所有的-rpath参数被连接在一起然后传给动态连接器。它们被用来在运行时定位共享库。
-soname name
当创建一个共享库时,指定的名字被放在共享库中。当和这个共享库连接的可执行文件被运行,动态连接器将试着map记录着的指定名字的共享库而不是传给连接器的文件名。
-static
告诉连接器不要和任何共享库连接。
-verbose
告诉连接器打印它每个要打开的文件名。
linux下gcc beta版本使用-dynamic-linker file选项设置动态连接器为/lib/ld-linker.so.1。该选项可以使ELF和a.out共享库很好的共存。
有件事情是另人感兴趣的。
[alert7@redhat62 dl]# gcc -shared -o libbar.so libbar.o -lfoo
假如libfoo.so被用来创建共享库时,有趣的时候就会发生了。当libbar.so被映象到进程的地址空间的时候,动态连接器也把libfoo.so映象到内存。假如libbar.so需要libfoo.so的时候,这个特性非常有用。实际上使用libbars.o库的程序编译时是不需要-lfoo的。假如archive版本的libfoo.a被使用,当在libbar.a中的标号被libbar.o引用时,它将会被搜索到。假使在libbar.so中包含libfoo.a甚至它们根本不被libbar.o使用,在这样的情况下必须逐步把.o文件加到libbar.o中:
# rm -rf /tmp/foo
# mkdir /tmp/foo
# (cd /tmp/foo/;ar -x ....../libfoo.a)
# gcc -shared -o libbar.so libbar.o /tmp/foo/*.o
# rm -rf /tmp/foo
在libfoo.a中的.o文件必须用-fPIC编译或者至少和PIC(位置无关)是兼容的。
当使用
static void * __libc_subinit_bar__
__attribute__ ((section ("_libc_subinit")))=&(bar);
来把一个标号放到一个没有被连接器定义的section中(在这里是_libc_subinit).连接器将所有在_libc_subinit section中的标号共同创建两个标号,一个是__start__libc_subinit和__stop__libc_subinit,它们作为C的标志符被使用。
警告:
下面是完全可能的:连接器可能不能搜索到包含_libc_subinit section的文件(该section中没有程序执行需要的标号)。这就使程序要确定使_libc_subinit section能被连接器搜索得到。
一种解决的办法是:把一个dummy标号放到_libc_subinit section中,在文件中定义它,使它参考引用_libc_subinit section.
相关阅读 更多 +