学会黑科技,一招搞定 iOS 14.2 的 libffi crash

作者:字节移动技能 —— 谢潇洒

苹果晋级 14.2,全球 iOS 遭了秧。libffi 在 iOS14.2 上发生了 crash,我司的许多 App 深受困扰,有许多根底库都是产品质量法用了 libffi。
学会黑科技,一招搞定 iOS 14.2 的 libffi crash

经过定位,发现是 vmr产品运营emap 导致的 code s函数调用的一般格局ign error。咱们经过运用静态 trampoline 的方法让 libffi 不需求运用 vmremap,处理了这个问题。app装置下载这儿就介绍一下相关的结束原理。

libffi 是什么

高层言语的编译器生成遵照某些约好的代码。这些公约部分是单独汇编作业所必需的。“调用约好”本质上是编译器对函数入口求职处将在哪里找到函数参数的假定的一组假定。“调函数调用进程用约好”还指定函数的返回值在appreciate哪里找到。

一些程序在编译时或许不知道要传递给函数的参数。例如,在运行时,说明器或许会被奉告用于调用给定函数的参数的数量和类函数调用中的参数太少型。Libffi 可用于求职信范文比如此类程序,以供给从说明器程序到编译代码的桥梁。

libffi 库为各种调用ios下载约好供给了一个便携式、高档的编程接口。这容许程序员在运行时调用调用接口描绘指定的任何函数。

ffi 的运用

简略的找了一个运用 ffi 的库看一下他的调用接口

ffi_ty求职信英语作文pe *returnType = st_ffiTypeWithType(self.signaios是什么意思ture.returnType)application;
NSAssert(returnType, @"can't find a ffi_type of %@", self.signature.re求职turnType);
NSUInteger argumentCouappearnt = self->_argsCount;
_args = malloc(sizeof(ffi_type *) * argumentCount产品批号是生产日期吗) ;
for (int i = 0; i < argumentCount; i++) {
ffi_type* current_ffi_tyappstorepe = st_approachffiTypeW求职意向ithType(self.signature.argumentAPPTypes[i]);
NSAssert(current_ffi_type, @"can't find a ffi_type of %@", self.signature.argumentTypes[i]);
_args[i] = current_ffi_type;
}
// 创立 ffi 跳板用到的 closure
_closure = ffi_closure_allo求职意向c(siz求职简历eof(ffi_closure), (void **)&xxx_func_ptr);
// 创立 cif,调用函数用到的参数和返回值的类型信息, 之后在调用时会结合call convention 处理参数和返回值
if(ffi_prep_cif(&_cif, FFI_DEFAULT_ABI, (unsigned int)argumentCappointmentount, returnType, _args) == FFI_OK) {
// closure函数调用可以出现在履行语句中 写入 跳板数据页
if (ffi_prep_closure_loc(_closure, &_cif, _st_ffi_function, (__bridge voidappointment *)(self), xxx_func_ptr) != FFI_OK) {
NSAssert(NO, @"genarate IMP failed");
}
} else {
NSAssert(NO, @"");
}

看完产品运营这段代码,大约能了解 ffi 的操作。appearance

  1. 供给给外界一个指针(指向 trampoline entry)
  2. 创立一个 closure, 将调用相关的参数返回值信息放到 closure 里
  3. 将 c求职losure 写入到 trampoline 对应的 tramp函数调用可以出现在表达式中吗oline data entry 处

之后咱们调用 trampoline entry func ptr 时,

  1. 会找到 写入到 trampoline 对应的 trampoline data entry 处的 closure 数据
  2. 依据 closure 供给的求职简历模板免费调用参数和返回值信息,结合调用约好,操作寄存器和栈,写入参数 进行函数调用,获取返回值。

那 ffi 是怎样找到 trampoline 对应的 tram产品批号是生产日期吗poline data entry 处的 c函数调用losure 数据 呢?

咱们从 ffi 分配 trampoline 初步说起:

static ffi_applicationtrampoline_table *
ff求职i_remap_trampol函数调用进程ine_table_alloc (void)
{
.....approach
/* Allocate two pages -- a config page and a placeholder page */
config_page = 0x0;
kt = vm_allocate (applicationmach_taappearancesk_se求职简历lf (), &config_page, PAGE_MAX_SIZE * 2,
VM_FLAGS_ANYWHERE);
if (kt != KERN_SUCCESS)
return NULL;
/* Allocate two pages -- a config page and a placeholder page */
//bdffc_closure_trampoline_table_paappointmentge
/* Remap the trampoline tabl函数调用可以出现在履行语句中e on t产品设计专业op of the placeholder page */
trampoline_ios下载page = config_page + PAGE_MAX_SIZE;
trampo产品设计专业line_page_template = (vm_address_t)&ffi_closure_remap_trampoline_table_page;
#ifdef __aios模拟器rm__
/* bdffc_closure_trampoline_table_page can be thumb-biased on so产品战略me ARM ar产品战略chs */
trampoline_page_template &= ~1UL;
#endif
kt = vm_remap (mach_task_self (), &tappearrampoline_page, PAGE_MAX_SIZE, 0x0,
VM_FLAGS_OVERWRITE, mach_task_self (), trampoline_page_te求职毛遂自荐mpappstorelate,
FALSE, &cur_prot, &max_prot, VM_INHERIT_SHARE);
if (kt != KERN_SUCCESS)
{
vm_deallocate (mach_task_self (), config_page, PAGE_MAX_SI求职招聘网ZE * 2);
return NULL;
}
/* W求职招聘网e have valid trampoline and config page产品设计s */
table = c函数调用时的实参和形参之间传递alloc (1, sizeof (ffi_trampoline_table));
taios8备忘录ble-&gt求职意向;free_count = FFI_REMAP_TRAMPOLINE_COUNT/2;
table->求职config_page = config_pagapproache;
table->trampoline_page = trampolappointmentine_page;
......
return table;
}

首要 ffi 在创立 trampoline 时,会分配两个接连的 page求职毛遂自荐

trampoline page 会 remap 到咱们事先在代码中汇编写的 ffi_closure求职意向_remios8备忘录ap_trampoline_table_page。

其结构如图所示:

学会黑科技,一招搞定 iOS 14.2 的 libffi crash

当我app装置下载ffi_prep_closure_loc(_closure, &_cif, _st_ffi_fu产品战略nction, (__bridge void *)(self), entry1)) 写入 closur函数调用e 数据时, 会写入到 entry1 对应的 cloAPPsuer1。


ffi_status
ffi_prep_closure_loc (ffi_closure *closure,
ffi_cif* cif,
void (*fun)(ffi_cif函数调用可以出现在履行语句中*,void*,void**,void*),
void *user_data,
void *产品介绍codeloc)
{
......
if (cif->flags & AARCH64_FLAG_ARG_V)
start = ffi_closure_SYSV_V; // ffi 对 closure的处理函数调用中的参数太少函数
else
start = ffi_clos函数调用c言语ure_SYSV;
void **config = (void**)((uint8_t *)codeloc - PAGE_MAX_SIZE函数调用可以出现在表达式中吗);
config[0] = closure;
con产品司理fig[1] = start;
......
}

这是怎样求职简历对应到的呢? closure1 和 entry1 间隔其所属 Page 的 offset 是一同的,经过 offset,成功树立 trampoline entry 和 trampoline closure 的对应联络。

现在咱们知道这个函数调用可以作为一个函数的形参联络,咱们经过代码看一下app装置下载到底在程序运行的时分 是怎样找到 closure 的。

这四条appear指令是咱们 trampoline entry 的代码结束,便是 ffi 返回的 xxx_func_ptr

adr x16, -PAGE_MAX_SIZE
ldp x17, x16, [x16]
br x16
nop

经过 .rept 咱们创立 PAGE_MAX_SIZE / FFI_TRAMPOLINE_SIZE 个跳板,刚好一个页的巨细

# 动态remap的产品质量法 page
.align PAGE_MAX_产品司理SHIFT
CNAME(f函数调用的一般格局fi_closure_remap_trampoline_table_page):
.rept PAGE_MAX_SIZE / FFI_TRAMPOLINE_SIZE
# 这是咱们的 trampoline entry, 便是ffi生成的函数指针
adr x16, -PAGE函数调用中的参数太少_MAX_SIZE                         /函数调用可以作为一个函数的形参/ 将pc地址减去PAGE_MAX_SIZE, 找到 trampoi函数调用c言语ne data entry
ldp x17, x16, [x16]                             // 加载咱们写入的 closure, start 到 x17, x16
br x16ios14                                          // 跳转到 start 函数
nop        /* ea求职意向怎样写ch entry in the trampoline config page is 2*sizeof(void*) so the trampoline itse函数调用中的参数太少lf canno求职简历t be smaller that 16 bytes */
.endr

经过 pc 地址减去 PAGE_MAX_SIZE 就找到对应的 trampoline data entry 了。

静态跳板的结束

因为代码段和数据段在不同的内存区域。

咱们此时不能经过 像 vmremap 相同分配两个接连的 PAGE,在寻找 trampo求职简历模板免费line data entry 仅仅简略的-PAGE_MAX_SIZE 找到对应联络,需求略微麻烦点的处理。

主要是经过 adrp 找到_ffi_static_trampoline_data_page1_ffi_static_trampoline_page1的初步地址,用 pc-_ffi_static_trampoline_page1的初步地址核算 offset,找到 trampoline data entry。

# 静态分配的page
#ifdef __MACH__
#include <mach/machine/vm_para求职意向怎样写m.h>
.align 14
.data
.global _ffi_static_trampoline_data_page1
_ffi_static_trampoline_dat函数调用c言语a_page1:
.space PAGE_MAX_SIZEappointment*5
.align PAGE_MAX_SHIFT
.text
CNAME(_ff求职信范文比如i_static_trampoline_page1):
_ffi_local_forwarding_bridge:
adrp x17, fios14.4.1更新了什么fi_cios14.4.2值得更新吗losure_static_trampol函数调用中的参数太少ine_table_page_startios14桌面布局图片@PAGE;// text page
sub  x16, x16, x17;// offset
adrp x17, _ffi_static_trampoline_data_page1@PAGE;// data page
add x16, x16, x17;// data address
ldp x17, x16, [x16];// x17 closure x16 start
br x16
nop
nop
.align PAGE_MAX_SHIFT
CNAME(ffi_closure_static_trampoline_tabl求职意向怎样写e_page):
#这个ios14.4.2值得更新吗label 用来adrp@PAGE 核算 trampoline 到 trampoline page的offset
#留了5个用来调试。
# 我approveapproachstatic trampoline 两条指令就够了,这儿运用4个,和remap的保持一同
ffi_closu求职简历re_static_trampoline_table_page_start:
adr x16, #0
b _ffi_local_函数调用进程forwarding_bridge
nop
nop
adr x16, #0
b _ffi_local_forwarding_bri求职信范文比如dge
nop
nop
adr x16, #0
b _ffi_local_forwarding_bridge
nop
nop
adr x16, #0
b _ffi_local_forwarding_bridge
nop
nop
adr x16, #0
b _ffi_local_forwarding_bridge
nop
nop
//application 5 * 4
.APPrept (PAGE_MAX_函数调用c言语SIZE函数调用可以出现在表达式中吗*5-5*4) / FFI_TRAMPOLINE_SIZE
adr x16, #0
b _ffi_locios退款al_forwarding_bridge
nop
nop
.endr
.globl CNAME(产品介绍ffi_closure_static_trampoline_table_page)
FFI_HIDDEN(CNAME(ffi_closure_stios14桌面布局图片atic_tios8备忘录rampoline_table_pa函数调用的一般格局ge))
#ifdef _appear_ELF__
.type        CNAME(ffi_closure_static_trampoline_table_page), #function
.size        CNAME(ffi_closure_static_t求职意向rampoline_table_page), . - CNAME(ffi_closure_static_trampo求职毛遂自荐line_table_page)
#endif
#endif

关于字节移动平台团队

字节跳动移动平台团队(Client Infrastruc求职简历模板免费ture)是大前端根底技能工作领军者,负责整个字节跳动的大前端根底设施建造,前进公司全产品线的功ios8备忘录能、稳定性和工程功率,支撑的产品包括但不限于抖音、今日头条、西瓜视频、火山小视频等,在移动端、Web、Desktop 等各终端都有深入研究。

便是现在!客户端/前端/服务端/检验开发 面向社会+学校招聘,base 北上广深杭成!一同来用技能改动世界,感兴趣可以联络邮箱 chenxuwei.cxw@bytedance.com邮件主题:简历-姓名-求职意向-电话