最近调试了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_FIELDSFFI_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_VARIANTffi_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*));
#endiffor (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 thischeck after the initialization.  */FFI_ASSERT_VALID_TYPE(*ptr);#if !defined X86_ANY && !defined S390 && !defined PA
#ifdef SPARCif (((*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 TILEif (bytes < 10 * FFI_SIZEOF_ARG &&bytes + STACK_ARG_SIZE((*ptr)->size) > 10 * FFI_SIZEOF_ARG){/* An argument is never split between the 10 parameterregisters and the stack.  */bytes = 10 * FFI_SIZEOF_ARG;}
#endif
#ifdef XTENSAif (bytes <= 6*4 && bytes + STACK_ARG_SIZE((*ptr)->size) > 6*4)bytes = 6*4;
#endifbytes += STACK_ARG_SIZE((*ptr)->size);}
#endif}cif->bytes = bytes;/* Perform machine dependent cif processing */
#ifdef FFI_TARGET_SPECIFIC_VARIADICif (isvariadic)return ffi_prep_cif_machdep_var(cif, nfixedargs, ntotalargs);
#endifreturn 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);}elseecif.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;
#endifdefault: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 registersstmfd   sp!, {r0-r3, fp, lr}UNWIND .save    {r0-r3, fp, lr}@ 备份sp指针mov fp, spUNWIND .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 positionmov 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 registersldmia   sp, {r0-r3}@ 按照上面说的放参数的规则,调整好sp的位置@ and adjust stacksub lr, fp, sp  @ cif->bytes == fp - spldr ip, [fp]    @ load fn() in advancecmp lr, #16movhs   lr, #16add sp, sp, lr@ r0~r3存放前4个参数,sp指向第5个参数,调用fn@ call (fn) (...)call_reg(ip)@ 恢复sp@ Remove the space we pushed for the argsmov sp, fp@ r2用来保存fn的返回值@ Load r2 with the pointer to storage for the return valueldr 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, #0beq LSYM(Lepilogue)@ return INTcmp r3, #FFI_TYPE_INT
#if defined(__SOFTFP__) || defined(__ARM_EABI__)cmpne   r3, #FFI_TYPE_FLOAT
#endifstreq   r0, [r2]beq LSYM(Lepilogue)
......
LSYM(Lepilogue):
#if defined (__INTERWORKING__)ldmia   sp!, {r0-r3,fp, lr}bx  lr
#elseldmia   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)

linux libffi 简介 高级语言互调库相关推荐

  1. 01 | Linux详细简介

    抱最大的希望,尽最大的努力,做最坏的打算. 目录 一.Linux详细简介 二.发音 三.历史 1.UNIX渊源 2.创立 3.命名 4.发展现状 四.系统架构 五.Linux发行版 六.Linux的应 ...

  2. Linux 交叉编译简介

    Linux 交叉编译简介 主机,目标,交叉编译器 主机与目标 编译器是将源代码转换为可执行代码的程序.像所有程序一样,编译器运行在特定类型的计算机上,输出的新程序也运行在特定类型的计算机上. 运行编译 ...

  3. linux内核体系学习路径_Linux内核分析(一)linux体系简介|内核源码简介|内核配置编译安装...

    从本篇博文开始我将对linux内核进行学习和分析,整个过程必将十分艰辛,但我会坚持到底,同时在博文中如果那些地方有问题还请各位大神为我讲解. 今天我们会分析到以下内容: 1. Linux体系结构简介 ...

  4. linux进程简介,及PID

    在 Linux 底下执行一个指令时,系统会给予这个动作一个 ID, 我们称为 PID,而根据启用这个指令的使用者与相关的指令功能,而给予这个特定 PID 一组权限, 该指令可以进行的行为则与这个 PI ...

  5. Linux系统简介-虚拟机安装教程(保姆级)-Linux常用命令

    Linux系统简介-虚拟机安装教程-Linux常用命令 1.Linux系统简介 1.1Linux系统的历史 1.2Linux系统的特点和优势 1.3Linux发行版的分类 2.虚拟机的安装教程 2.1 ...

  6. RHEL 5基础篇—linux的简介

    RHEL 5基础篇-linux的简介 Linux是一种自由和开放源码的类Unix操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核.Linux可安装在各种计算机硬件设备中,比如手机 ...

  7. linux编程是否含7,第7章Linux编程简介.ppt

    第7章Linux编程简介 ARM 应用系统设计第7章 Linux编程简介 佘黎煌 东北大学信息科学与工程学院 电子信息工程研究所 嵌入式Linux概况 现在 Linux 广泛用于各类计算应用,不仅包括 ...

  8. linux文件系统简介_Linux文件系统简介

    linux文件系统简介 本文旨在对Linux文件系统概念进行非常高级的讨论. 它无意于对诸如EXT4之类的特定文件系统类型如何工作进行低级描述,也无意于成为文件系统命令的教程. 每台通用计算机都需要将 ...

  9. GTK+编程入门(1)—简介与glib库

    GTK+编程入门(1)-简介与glib库(2015-7-23) 分类:GTK+ 一:GTK+简介   GTK+是一个软件开发工具包,其设计目的是支持在 X Window系统下开发图形界面的应用程序.G ...

最新文章

  1. qt下编写linux消息队列,C++11消息队列 + Qt线程池 + QRunnable执行任务简单模型
  2. 科技管理的作业选题 很重要
  3. Xposed学习一:初探
  4. Java:多线程,CyclicBarrier同步器
  5. HTTP系列之:HTTP缓存
  6. HTML5移动端触摸事件
  7. java range类_Java即时类| range()方法与示例
  8. 苏宁智慧家庭助跑智慧零售
  9. Scoped CSS规范草案
  10. setInterval 和 setTimeout
  11. win10 查看系统开机历史记录
  12. php yof框架特点_PHP编程语言的特点
  13. 如何提升代码的安全性 —— 代码防御性编程的十条技巧
  14. Mac OS 使用asio库
  15. 访问服务器共享文件夹慢,win8系统访问共享时复制文件速度慢的解决方法
  16. Recheck Cond filter IO\CPU放大 原理与优化CASE - 含 超级大表 不包含(反选) SQL优化
  17. 网络安全-靶机dvwa之sql注入Low到High详解(含代码分析)
  18. json的格式是什么?json的作用是什么?json是如何传递数据的?
  19. 【毕业设计】单片机机器视觉人体识别小车 - 深度学习 yolo目标检测 人体识别 树莓派
  20. c++ string最大长度_关于C++ std::string类内存布局的探究

热门文章

  1. QCOW2 — ROW/COW 快照技术原理解析
  2. Linux_KVM虚拟机
  3. linux驱动常用函数
  4. 51单片机中变量的存储 xdata bdata idata pdata区别
  5. [LAMP]Apache和PHP的结合
  6. Codeforces 768E:Game of Stones
  7. 解决eclipse编译的几种方法
  8. HDU1203_I NEED A OFFER!【01背包】
  9. MOSS 权限管理总结
  10. [BZOJ]4650 优秀的拆分(Noi2016)(哈希+二分)