最近調試了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, type, id) \
struct struct_align_##name { \
char c; \
type x; \
}; \
ffi_type ffi_type_##name = { \
sizeof(type), \
offsetof(struct struct_align_##name, x), \
id, NULL \
}
定義了struct_align_uint32結構體,這一系列結構體的第一個元素都是char,第二個是具體的uint32,sint32,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, ffi_abi abi, unsigned int nargs,
ffi_type *rtype, ffi_type **atypes)
{
return ffi_prep_cif_core(cif, abi, 0, nargs, 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, int c, char d), 輸入:r0 = a, 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, unsigned *);
extern void ffi_call_VFP (void (*fn)(void), extended_cif *, unsigned, unsigned, 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, cif->bytes, cif->flags, 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, fp, lr}
UNWIND .save {r0-r3, fp, 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, fp, 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, fp, 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, [sp, #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!, {r0-r3,fp, lr}
bx lr
#else
ldmia sp!, {r0-r3,fp, pc}
#endif
.ffi_call_SYSV_end:
UNWIND .fnend
#ifdef __ELF__
.size CNAME(ffi_call_SYSV),.ffi_call_SYSV_end-CNAME(ffi_call_SYSV)