最近调试了weston的一个coredump,对libffi有了一些了解,在此记录下,使用的是arm处理器,32位,soft float,libffi3.1,使用的abi是SYSV。
libffi简介和使用示例:http://www.atmark-techno.com/~yashi/libffi.html,建议先看完,有所了解再继续看本文。大体意思就是libffi用于高级语言之间的相互调用。由于函数指针,参数类型,参数个数,参数的值都可以在运行时指定,所以在脚本语言调用c里面用的比较多,比如python 的ctypes;也可以调用不同abi(应用程序二进制接口)编译的程序,这个了解的不多。
数据类型
libffi定义了ffi_type结构体,用于描述对应的c语言中的uint32,sint32,floate,void *,struct等类型:
typedef struct _ffi_type
{
size_t size;
unsigned short alignment;
unsigned short type;
struct _ffi_type **elements;
} ffi_type;
比如变量ffi_type_uint32用于描述c语言的uint32类型,它所占大小是4;对齐大小是4;在libffi中用于标记类型的数字是FFI_TYPE_UINT32,也就是9;elements在c语言基本类型中没有用到,固定为NULL,elements在结构体中才会用到,为结构体中的元素。
ffi_type_uint32变量是通过FFI_TYPEDEF(uint32,UINT32,FFI_TYPE_UINT32)得到的
#define FFI_TYPEDEF(name,type,id) \
struct struct_align_##name { \
char c; \
type x; \
}; \
const ffi_type ffi_type_##name = { \
sizeof(type),\
offsetof(struct struct_align_##name,x),\
id,NULL \
}
#define FFI_NONCONST_TYPEDEF(name,id) \
struct struct_align_##name { \
char c; \
type x; \
}; \
ffi_type ffi_type_##name = { \
sizeof(type),NULL \
}
定义了struct_align_uint32结构体,这一系列结构体的第一个元素都是char,第二个是具体的uint32,void *等,用于之后求取对齐字节数。
ffi_type_uint32为ffi_type类型的const变量,sizeof(uint32)得到uint32的大小;offsetof类似于内核里面著名的container_of函数中求取结构体中元素偏移字节数的代码,可以得到uint32在struct_align_uint32中的偏移为4,表示uint32是4字节对齐的;id是FFI_TYPE_UINT32,值为9;elements为NULL。
一、初始化ffi_cif结构体
ffi_cif结构体定义为:
typedef struct {
ffi_abi abi;
unsigned nargs;
ffi_type **arg_types;
ffi_type *rtype;
unsigned bytes;
unsigned flags;
#ifdef FFI_EXTRA_CIF_FIELDS
FFI_EXTRA_CIF_FIELDS;
#endif
} ffi_cif;
表示了函数调用中的一些信息,比如abi;输入参数个数;输入参数类型(ffi_type_uint32之类的);返回值类型;输入参数占用空间的大小(aapcs要求进入arm函数时堆栈是8字节对齐的。由于这个缓冲区是在sysv.S的ffi_call_SYSV函数中通过sub sp,fp,r2申请的,申请完就调用ffi_prep_args_SYSV,所以这个大小必须是8的倍数);flags(如果返回类型是c语言基本类型,那么flags就是返回类型,如果返回类型是结构体,需要有所处理,见ffi_prep_cif_machdep函数)。
使用如下函数初始化ffi_cif结构体:
ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif,ffi_abi abi,unsigned int isvariadic,unsigned int nfixedargs,unsigned int ntotalargs,ffi_type *rtype,ffi_type **atypes)
{
unsigned bytes = 0;
unsigned int i;
ffi_type **ptr;
FFI_ASSERT(cif != NULL);
FFI_ASSERT((!isvariadic) || (nfixedargs >= 1));
FFI_ASSERT(nfixedargs <= ntotalargs);
if (! (abi > FFI_FirsT_ABI && abi < FFI_LAST_ABI))
return FFI_BAD_ABI;
cif->abi = abi;
cif->arg_types = atypes;
cif->nargs = ntotalargs;
cif->rtype = rtype;
cif->flags = 0;
#if HAVE_LONG_DOUBLE_VARIANT
ffi_prep_types (abi);
#endif
/* Initialize the return type if necessary */
if ((cif->rtype->size == 0) && (initialize_aggregate(cif->rtype) != FFI_OK))
return FFI_BAD_TYPEDEF;
/* Perform a sanity check on the return type */
FFI_ASSERT_VALID_TYPE(cif->rtype);
/* x86,x86-64 and s390 stack space allocation is handled in prep_machdep. */
#if !defined M68K && !defined X86_ANY && !defined S390 && !defined PA
/* Make space for the return structure pointer */
if (cif->rtype->type == FFI_TYPE_STRUCT
#ifdef SPARC
&& (cif->abi != FFI_V9 || cif->rtype->size > 32)
#endif
#ifdef TILE
&& (cif->rtype->size > 10 * FFI_SIZEOF_ARG)
#endif
#ifdef XTENSA
&& (cif->rtype->size > 16)
#endif
#ifdef NIOS2
&& (cif->rtype->size > 8)
#endif
)
bytes = STACK_ARG_SIZE(sizeof(void*));
#endif
for (ptr = cif->arg_types,i = cif->nargs; i > 0; i--,ptr++)
{
/* Initialize any uninitialized aggregate type deFinitions */
if (((*ptr)->size == 0) && (initialize_aggregate((*ptr)) != FFI_OK))
return FFI_BAD_TYPEDEF;
/* Perform a sanity check on the argument type,do this
check after the initialization. */
FFI_ASSERT_VALID_TYPE(*ptr);
#if !defined X86_ANY && !defined S390 && !defined PA
#ifdef SPARC
if (((*ptr)->type == FFI_TYPE_STRUCT
&& ((*ptr)->size > 16 || cif->abi != FFI_V9))
|| ((*ptr)->type == FFI_TYPE_LONGDOUBLE
&& cif->abi != FFI_V9))
bytes += sizeof(void*);
else
#endif
{
/* Add any padding if necessary */
if (((*ptr)->alignment - 1) & bytes)
bytes = (unsigned)ALIGN(bytes,(*ptr)->alignment);
#ifdef TILE
if (bytes < 10 * FFI_SIZEOF_ARG &&
bytes + STACK_ARG_SIZE((*ptr)->size) > 10 * FFI_SIZEOF_ARG)
{
/* An argument is never split between the 10 parameter
registers and the stack. */
bytes = 10 * FFI_SIZEOF_ARG;
}
#endif
#ifdef XTENSA
if (bytes <= 6*4 && bytes + STACK_ARG_SIZE((*ptr)->size) > 6*4)
bytes = 6*4;
#endif
bytes += STACK_ARG_SIZE((*ptr)->size);
}
#endif
}
cif->bytes = bytes;
/* Perform machine dependent cif processing */
#ifdef FFI_TARGET_SPECIFIC_VARIADIC
if (isvariadic)
return ffi_prep_cif_machdep_var(cif,nfixedargs,ntotalargs);
#endif
return ffi_prep_cif_machdep(cif);
}
#endif /* not __CRIS__ */
ffi_status ffi_prep_cif(ffi_cif *cif,unsigned int nargs,ffi_type **atypes)
{
return ffi_prep_cif_core(cif,abi,nargs,rtype,atypes);
}
需要详细说明下sysv的传参方式:
1、输入参数通过r0-r3传递,多余的放入堆栈中;返回值放入r0,不够的话放入{r0,r1}或者{r0,r1,r2,r3},比如:
int foo(int a,int b,int c,int d),输入:r0 = a,r1 = b,r2 = c,r3 = d,返回:r0 = 类型为int的retvalue
int *foo(char a,double b,char d),r1用于对齐(double 要求8字节对齐),b = {r2,r3},c放在堆栈的sp[0]位置,d放在堆栈的sp[4]位置,这里的sp是指进入函数时的sp;返回:r0 = 类型为int *的retvalue
2、注意如果返回值是结构体,情况有些特殊:
struct client foo(int a,char b,float c),输入:r0 = 一个strcut client *变量,由调用者给出,r1 = a,r2 = b,r3 = c;返回:strcut client *变量,和调用者给的一样
bytes大小的计算:
1、如果返回值是结构体,一个结构体指针需要传递给函数,因此bytes+=4 (sizeof(struct xxx *) = 4)
2、如果bytes的大小不满足参数的对齐要求,比如bytes=5时,下一个需要处理的输入参数是double(size=8,align=8),那么bytes向上取align=8的倍数,所以bytes=8
3、将参数放入缓冲区(bytes就是缓冲区的大小,缓冲区在ffi_call_SYSV中申请的)时,如果参数size<sizeof(int),那么就按照int的大小来存放(注意有无符号),因为即使是传递一个char,也得使用一个独立的寄存器,一个寄存器不能传递两个char参数
具体将参数放入缓冲区的,由ffi_prep_args_SYSV函数处理:
int ffi_prep_args_SYSV(char *stack,extended_cif *ecif,float *vfp_space)
{
register unsigned int i;
register void **p_argv;
register char *argp;
register ffi_type **p_arg;
argp = stack;
if ( ecif->cif->flags == FFI_TYPE_STRUCT ) {
*(void **) argp = ecif->rvalue;
argp += 4;
}
p_argv = ecif->avalue;
for (i = ecif->cif->nargs,p_arg = ecif->cif->arg_types;
(i != 0);
i--,p_arg++,p_argv++)
{
argp = ffi_align(p_arg,argp);
argp += ffi_put_arg(p_arg,p_argv,argp);
}
return 0;
}
二、调用函数指针fn
将准备ffi_cif和调用fn分开的原因是,函数可能需要使用不同的参数值调用多次,但是参数类型是不变的。
/* Prototypes for assembly functions,in sysv.S */
extern void ffi_call_SYSV (void (*fn)(void),extended_cif *,unsigned,unsigned *);
extern void ffi_call_VFP (void (*fn)(void),unsigned *);
void ffi_call(ffi_cif *cif,void (*fn)(void),void *rvalue,void **avalue)
{
extended_cif ecif;
int small_struct = (cif->flags == FFI_TYPE_INT
&& cif->rtype->type == FFI_TYPE_STRUCT);
int vfp_struct = (cif->flags == FFI_TYPE_STRUCT_VFP_FLOAT
|| cif->flags == FFI_TYPE_STRUCT_VFP_DOUBLE);
unsigned int temp;
ecif.cif = cif;
ecif.avalue = avalue;
/* If the return value is a struct and we don't have a return */
/* value address then we need to make one */
if ((rvalue == NULL) &&
(cif->flags == FFI_TYPE_STRUCT))
{
ecif.rvalue = alloca(cif->rtype->size);
}
else if (small_struct)
ecif.rvalue = &temp;
else if (vfp_struct)
{
/* Largest case is double x 4. */
ecif.rvalue = alloca(32);
}
else
ecif.rvalue = rvalue;
switch (cif->abi)
{
case FFI_SYSV:
ffi_call_SYSV (fn,&ecif,cif->bytes,cif->flags,ecif.rvalue);
break;
case FFI_VFP:
#ifdef __ARM_EABI__
ffi_call_VFP (fn,ecif.rvalue);
break;
#endif
default:
FFI_ASSERT(0);
break;
}
if (small_struct)
{
FFI_ASSERT(rvalue != NULL);
memcpy (rvalue,&temp,cif->rtype->size);
}
else if (vfp_struct)
{
FFI_ASSERT(rvalue != NULL);
memcpy (rvalue,ecif.rvalue,cif->rtype->size);
}
}
cif是刚才准备好的那个;fn是将要调用的函数指针;rvalue用于存放fn的返回值,与rtype对应;avalue用于存放fn的输入参数的值,与arg_types对应。
ffi_call的核心是ffi_call_SYSV函数,这个一个汇编函数,主要意思是:
ARM_FUNC_START(ffi_call_SYSV)
@ 函数开头保存了几个寄存器,lr是调用者的pc指针
@ Save registers
stmfd sp!,{r0-r3,lr}
UNWIND .save {r0-r3,lr}
@ 备份sp指针
mov fp,sp
UNWIND .setfp fp,sp
@ 通过减sp,申请内存,大小为bytes
@ 因为申请内存后,按照aapcs的要求,调用ffi_prep_args_SYSV时sp需要是8的倍数,所以bytes也必须是8的倍数
@ Make room for all of the new args.
sub sp,r2
@ ffi_prep_args_SYSV是根据arg_types和avalue,将bytes大小的数据放入堆栈里,r0和r1是它的输入参数
@ r0是缓存的起始地址,r1是ecif,ecif包含了cif,rvalue,avalue
@ Place all of the ffi_prep_args in position
mov r0,sp
@ r1 already set
@ Call ffi_prep_args(stack,&ecif)
bl CNAME(ffi_prep_args_SYSV)
@ 经过ffi_prep_args_SYSV的处理,fn所需要的参数已经都放在堆栈里了
@ 前16字节的参数放到r0~r3寄存器里,如果是4个int,那么r0~r3分别存放fn从左到右第1个到第4个参数
@ 如果是char,double这样的,由于对齐的要求,{r2,r3}存放double,char在r0的低字节中,r1无用
@ r0~r3如果没有保存完fn所有的参数,那么其他参数放在堆栈中
@ 比如有6个int参数,那么第5个int就在fn函数一开始的sp[0]位置,第6个在sp[4]
@ move first 4 parameters in registers
ldmia sp,{r0-r3}
@ 按照上面说的放参数的规则,调整好sp的位置
@ and adjust stack
sub lr,sp @ cif->bytes == fp - sp
ldr ip,[fp] @ load fn() in advance
cmp lr,#16
movhs lr,#16
add sp,sp,lr
@ r0~r3存放前4个参数,sp指向第5个参数,调用fn
@ call (fn) (...)
call_reg(ip)
@ 恢复sp
@ Remove the space we pushed for the args
mov sp,fp
@ r2用来保存fn的返回值
@ Load r2 with the pointer to storage for the return value
ldr r2,[sp,#24]
@ r3 = flags,flags根据rtype返回类型设置的
@ Load r3 with the return type code
ldr r3,#12]
@ 如果rvalue == NULL,不保存返回值,退出函数
@ 如果不为NULL,那么根据rtype的不同,按照不同的方式保存返回值
@ If the return value pointer is NULL,assume no return value.
cmp r2,#0
beq LSYM(Lepilogue)
@ return INT
cmp r3,#FFI_TYPE_INT
#if defined(__SOFTFP__) || defined(__ARM_EABI__)
cmpne r3,#FFI_TYPE_FLOAT
#endif
streq r0,[r2]
beq LSYM(Lepilogue)
......
LSYM(Lepilogue):
#if defined (__INTERWORKING__)
ldmia sp!,lr}
bx lr
#else
ldmia sp!,pc}
#endif
.ffi_call_SYSV_end:
UNWIND .fnend
#ifdef __ELF__
.size CNAME(ffi_call_SYSV),.ffi_call_SYSV_end-CNAME(ffi_call_SYSV)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。