从程序员角度看ELF(5)
时间:2007-04-09 来源:Echo CHEN
★6 位置无关代码(PIC)的汇编语言编程
当用gcc指定-fPIC的时候,gcc将从C源代码中产生PIC的汇编语言代码。但有时候,我们需要用汇编语言来产生PIC代码。
在ELF下,PIC的实现是使用基寄存器(base register)。在PIC下,所有的标号引用都是通过基寄存器实现的,为此,要用汇编写PIC的话,必须保存基寄存器(base register)。由于位置无关代码,控制传送指令的目的地址必须被替换或者是在PIC情况下计算的。对X86机器来说,该基寄存器
(base register)就是ebx.这里我们将介绍在X86上安全的PIC汇编代码的两种方法。这些技术在Linux C库中也被使用到。
★6.1 在C中内嵌汇编
gcc支持内嵌汇编的声明,可让程序员在C语言中使用汇编语言。当写LINUX系统调用接口的时候这是很有用的,而无须使用机器相关指令。
在linux 下系统调用是通过int $0x80的。一般的,系统调用会有三个参数:
#include <sys/syscall.h>
extern int errno;
int read( int fd,void *buf ,size count)
{
long ret;
__asm__ __volatile__ ("int $0x80"
:"=a"(ret)
:"O"(SYS_read),"b"((long)fd),
"c"((long)buf),"d"((long)count):"bx");
if (ret>=0)
{
return (int) ret:
}
errno=-ret;
retrun -1;
}
以上汇编代码把系统调用号SYS_read放到了eax中,fd到ebx中,buf到ecx中,count到edx中,从int $0x80中返回值ret放在eax中。在不用-fPIC的情况下,这样定义运行良好。带-fPIC的gcc应该要检查ebx是否被被改变,并且应该在汇编代码里保存和恢复ebx。但是不幸的是,事实上不是这样的。我们为了支持PIC必须自己写汇编代码。
#include <sys/syscall.h>
extern int errno;
int read( int fd,void *buf ,size count)
{
long ret;
__asm__ __volatile__ ("pushl %%ebx\n\t"
"movl %%esi,%%ebx\n\t"
"int $0x80\n\t"
"popl %%ebx"
:"=a"(ret)
:"O"(SYS_read),"S"((long)fd),
"c"((long)buf),"d"((long)count):"bx");
if (ret>=0)
{
return (int) ret:
}
errno=-ret;
return -1;
}
这里首先把fd放到esi中,然后保存ebx,把esi移到ebx,在int $0x80后恢复ebx。这样保证ebx不被改变(除了在int $0x80中断调用中)。同样的原则也适用于其他内嵌的汇编。
在任何时候,当ebx可能要被改变时,千万要记得保存和恢复ebx.
★6.2 用汇编语言编程
假如我们在系统调用时需要传5个参数时候,内嵌的汇编代码即使是PIC的,也不能工作,因为x86没有足够的寄存器。我们需要直接用汇编语言
编写。
syscall(int syscall_number,...)的一般汇编代码如下:
.file "syscall.S"
.text
.global syscall
.global errno
.align 16
syscall:
pushl 5ebp
movl %esp,%ebp
pushl %edi
pushl %esi
pushl %ebx
movl 8(%ebp),%eax
movl 12(%ebp),%ebx
movl 16(%ebp),%ecx
movl 20(%ebp),%edx
movl 24(%ebp),%esi
movl 28(%ebp),%edi
int $0x80
test %eax,%eax
jpe .LLexit
negl %eax
movl %eax,errno
movl $-1, %eax
.LLexit:
popl %ebx
popl %esi
popl %edi
movl %ebp,%esp
popl %ebp
ret
.type syscall,@function
.L_syscall_end:
.size syscall,.L_syscall_end -syscall
在PIC下,我们必须通过GOT(global offset table)来访问任何全局变量(除了保存在基寄存器ebx中的)。修改的代码如下:
.file "syscall.S"
.text
.global syscall
.global errno
.align 16
syscall:
pushl %ebp
movl %esp,%ebp
pushl %edi
pushl %esi
pushl %ebx
call .LL4
.LL4:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.- .LL4],%ebx
pushl %ebx
movl 8(%ebp),%eax
movl 12(%ebp),%ebx
movl 16(%ebp),%ecx
movl 20(%ebp),%edx
movl 24(%ebp),%esi
movl 28(%ebp),%edi
int $0x80
popl %ebx
movl %eax,%edx
test %edx,%edx
jge .LLexit
negl %edx
movl errno@GOT(%ebx),%eax
movl %edx,(%eax)
movl $-1,%eax
.LLexit:
popl %ebx
popl %esi
popl %edi
movl %ebp,%esp
popl %ebp
ret
.type syscall,@function
.L_syscall_end:
.size syscall,.L_syscall_end-syscall
假如要得到PIC的汇编代码,但是又不知道如何写,你可以写一个C的,然后如下编译:
#gcc -O -fPIC -S foo.c
它将告诉gcc产生汇编代码foo.s输出,根据需要,可以修改它。
★7 结束语
根据以上讨论的,我们可以得出这样的结论:ELF是非常灵活的二进制格式。它提供了非常有用的功能。这种规范没有给程序和程序员太多限制。它使创建共享库容易,使动态装载和共享库的结合更加容易。在ELF下,在C++中,全局的构造函数和析构函数在共享库和静态库中用同样方法处理。
[译注:
到此,文章是翻译好了,但里面的一些东西看起来可能有点问题,比如说_libc_subinit section没有他说的那个功能,-dynamic-linker选项在默认的redhat 6.2系统上不能用,_dlinfo动态连接接口库函数好象在linux没有实现 等等一系列问题,欢迎讨论指正 mailto: [email protected] [email protected] ]
参考:
1. Operating System API Reference:UNIX SVR4.2,UNIX Press,1992
2. SunOs 5.3 Linker and Libraries Manual,SunSoft ,1993.
3. Richard M.Stallman,Using and porting GNU CC for version 2.6, Free Software Foundation,September 1994.
4. Steve Chamberlain and Roland Pesch,Using ld:The GNU linker,ld version 2,Cygnus Support,January 1994.
当用gcc指定-fPIC的时候,gcc将从C源代码中产生PIC的汇编语言代码。但有时候,我们需要用汇编语言来产生PIC代码。
在ELF下,PIC的实现是使用基寄存器(base register)。在PIC下,所有的标号引用都是通过基寄存器实现的,为此,要用汇编写PIC的话,必须保存基寄存器(base register)。由于位置无关代码,控制传送指令的目的地址必须被替换或者是在PIC情况下计算的。对X86机器来说,该基寄存器
(base register)就是ebx.这里我们将介绍在X86上安全的PIC汇编代码的两种方法。这些技术在Linux C库中也被使用到。
★6.1 在C中内嵌汇编
gcc支持内嵌汇编的声明,可让程序员在C语言中使用汇编语言。当写LINUX系统调用接口的时候这是很有用的,而无须使用机器相关指令。
在linux 下系统调用是通过int $0x80的。一般的,系统调用会有三个参数:
#include <sys/syscall.h>
extern int errno;
int read( int fd,void *buf ,size count)
{
long ret;
__asm__ __volatile__ ("int $0x80"
:"=a"(ret)
:"O"(SYS_read),"b"((long)fd),
"c"((long)buf),"d"((long)count):"bx");
if (ret>=0)
{
return (int) ret:
}
errno=-ret;
retrun -1;
}
以上汇编代码把系统调用号SYS_read放到了eax中,fd到ebx中,buf到ecx中,count到edx中,从int $0x80中返回值ret放在eax中。在不用-fPIC的情况下,这样定义运行良好。带-fPIC的gcc应该要检查ebx是否被被改变,并且应该在汇编代码里保存和恢复ebx。但是不幸的是,事实上不是这样的。我们为了支持PIC必须自己写汇编代码。
#include <sys/syscall.h>
extern int errno;
int read( int fd,void *buf ,size count)
{
long ret;
__asm__ __volatile__ ("pushl %%ebx\n\t"
"movl %%esi,%%ebx\n\t"
"int $0x80\n\t"
"popl %%ebx"
:"=a"(ret)
:"O"(SYS_read),"S"((long)fd),
"c"((long)buf),"d"((long)count):"bx");
if (ret>=0)
{
return (int) ret:
}
errno=-ret;
return -1;
}
这里首先把fd放到esi中,然后保存ebx,把esi移到ebx,在int $0x80后恢复ebx。这样保证ebx不被改变(除了在int $0x80中断调用中)。同样的原则也适用于其他内嵌的汇编。
在任何时候,当ebx可能要被改变时,千万要记得保存和恢复ebx.
★6.2 用汇编语言编程
假如我们在系统调用时需要传5个参数时候,内嵌的汇编代码即使是PIC的,也不能工作,因为x86没有足够的寄存器。我们需要直接用汇编语言
编写。
syscall(int syscall_number,...)的一般汇编代码如下:
.file "syscall.S"
.text
.global syscall
.global errno
.align 16
syscall:
pushl 5ebp
movl %esp,%ebp
pushl %edi
pushl %esi
pushl %ebx
movl 8(%ebp),%eax
movl 12(%ebp),%ebx
movl 16(%ebp),%ecx
movl 20(%ebp),%edx
movl 24(%ebp),%esi
movl 28(%ebp),%edi
int $0x80
test %eax,%eax
jpe .LLexit
negl %eax
movl %eax,errno
movl $-1, %eax
.LLexit:
popl %ebx
popl %esi
popl %edi
movl %ebp,%esp
popl %ebp
ret
.type syscall,@function
.L_syscall_end:
.size syscall,.L_syscall_end -syscall
在PIC下,我们必须通过GOT(global offset table)来访问任何全局变量(除了保存在基寄存器ebx中的)。修改的代码如下:
.file "syscall.S"
.text
.global syscall
.global errno
.align 16
syscall:
pushl %ebp
movl %esp,%ebp
pushl %edi
pushl %esi
pushl %ebx
call .LL4
.LL4:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.- .LL4],%ebx
pushl %ebx
movl 8(%ebp),%eax
movl 12(%ebp),%ebx
movl 16(%ebp),%ecx
movl 20(%ebp),%edx
movl 24(%ebp),%esi
movl 28(%ebp),%edi
int $0x80
popl %ebx
movl %eax,%edx
test %edx,%edx
jge .LLexit
negl %edx
movl errno@GOT(%ebx),%eax
movl %edx,(%eax)
movl $-1,%eax
.LLexit:
popl %ebx
popl %esi
popl %edi
movl %ebp,%esp
popl %ebp
ret
.type syscall,@function
.L_syscall_end:
.size syscall,.L_syscall_end-syscall
假如要得到PIC的汇编代码,但是又不知道如何写,你可以写一个C的,然后如下编译:
#gcc -O -fPIC -S foo.c
它将告诉gcc产生汇编代码foo.s输出,根据需要,可以修改它。
★7 结束语
根据以上讨论的,我们可以得出这样的结论:ELF是非常灵活的二进制格式。它提供了非常有用的功能。这种规范没有给程序和程序员太多限制。它使创建共享库容易,使动态装载和共享库的结合更加容易。在ELF下,在C++中,全局的构造函数和析构函数在共享库和静态库中用同样方法处理。
[译注:
到此,文章是翻译好了,但里面的一些东西看起来可能有点问题,比如说_libc_subinit section没有他说的那个功能,-dynamic-linker选项在默认的redhat 6.2系统上不能用,_dlinfo动态连接接口库函数好象在linux没有实现 等等一系列问题,欢迎讨论指正 mailto: [email protected] [email protected] ]
参考:
1. Operating System API Reference:UNIX SVR4.2,UNIX Press,1992
2. SunOs 5.3 Linker and Libraries Manual,SunSoft ,1993.
3. Richard M.Stallman,Using and porting GNU CC for version 2.6, Free Software Foundation,September 1994.
4. Steve Chamberlain and Roland Pesch,Using ld:The GNU linker,ld version 2,Cygnus Support,January 1994.
相关阅读 更多 +