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

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

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

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

经过定位,发现是vmremap导致的code sign error。咱们经过运用静态trampoline的办法让libffi不需求运用vmremap,处理了这个问程序员薪酬一般多少题。这儿就介绍一下相关的完结原理。

libffi 是什么

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

一些程序在编译时或许不知道要传递给函数的参数。例如,在运转时,说冥具或许会被奉告用于调用给程序员装逼代码定函数的参数的数量和类型。L产品质量法ibffi可用于此类程序,以供应从说冥具程序到application编译代码的桥梁。

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

ffi的产品司理运用

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

ffi_type *returnType = st_ffiTypeWithT产品司理ype(self.signature.r程序员训练班要多少钱eturnType);
NSAssert(returnType, @"can't find a ffi_type of %@", self.signat程序员怎样学ure.return求职信300字Type);
NSUInteger argumentCount =求职信范文比如 self->_argsCount;
_args = malloc(sizeof(ffi_type *) * argumentC前端结构ount) ;
for (int i =程序员是学什么专业 0; i < argumentCount; i++) {
ffi_type* current_appointmentffi_type = st_ffiTyp程序员是做什么的eWithType求职意向怎样写(self.signaapplicationture.argumentTypes[i]);
NSAssert(current_ffi_type, @"can't find a ffi_type of %@", self.signature.argumentT前端训练机构ypes[产品运营i]);
_args[i] = current_ffi_type;
}
// 创立 ffi 跳板用到的 closu程序员需求什么学历re 
_closure = ffi_closure_alloc(sizeof(ffi_closure), (void **)&xxx_func产品生命周期_ptr);
// 创立 cif,调用函数用到的参数和回来值的类型信息, 之后在调用时会结合call convention 处appearance理参数和回来值
if(ffi_prep_cif(&_cif, FF程序员训练班要多少钱I_DEFAULT_ABI, (unsigned int)argumentCount, returnType, _args) == FFI_OK) {
// closure 写入 跳板数据页
if (ffi_程序员是做什么的prep_closure_程序员是做什么的loc(_closure, &_cif, _st_ffi_function, (__bridge void *)(selAPPf), xxx_func_ptr) != FFI_OK) {
NSAssert(NO, @"genarate IMP failed");
}
} else {
NSAssert(NO, @"FUCK");
}

看完这段代码,大约能了解 ffi 的操作。

  1. 供应给外界一个指针(指向trampoline产品 entry)
  2. 创立一个closure, 将调用相关的参数回来值信application息放到closure里
  3. 将closure写入到trampoline 对应的trappointmentampoline程序员训练班要多少钱 data entry 处

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

  1. 会找到 写入到 trampoline 对应的trampoline data entry 处的 closure 数据
  2. 根据closure 供应的调用参数和appstore回来值信息,结appear合调用约好,操作寄存器和栈,写求职入参数 进行函数调程序员客栈用,获取回来值。

那ffi 是怎样找到 trampoline 对应的trampoline data entry 处的 closure 数据 呢?

咱们从 ffi 分配 trampoline 开端说起:

st程序员是做什么的atic ffi_trampolin产品批号是生产日期吗e_table *
ffi_rem前端ap_trampoline_table_alloc (void)
{
.....
/* Allocate two pages -- a config page and a placeholder pa产品ge */
config_page = 0x0;
kt = vm_allocate (mach_task_self (), &config_page, PAGE_MAX求职_SIZE * 2,
VM_FLAGS_ANYWHERE);
if (kt != KERN产品设计专业_SUCCESS)
ret求职信范文比如urn NULL;
/* Allocate two pages -- a confi程序员训练班要多少钱g page and a placeholder page */
//bdffc_closure_trampoline_table_page
/* Remap the trampoline table on top of the placeholder page */
trampoline_page = config_page + PAGE_MAX_SIZE;
t程序员训练班要多少钱rampoline_page_template = (vm_address_t)&ffi_closure_remap_trampoline_table_page;
#ifdef __程序员训练班要多少钱arm__
/* bdffc_closure_trampoline_table_page can b程序员怎样学e thumb-biased o程序员怎样学n some ARM archs */
trampoline_page_template &= ~1UL;
#endif
kt = vm_remap (mach_task_self (), &trampoline_page, PAGE_MAX_SIZE, 0x0,
VM_approveFLAGS_OVERWRITE, mach_task_self (求职毛遂自荐), trampoline_page_template,
FALSE, &cur_prot, &max_prot, VM_INHERIT_SHARE);
if (kt != KERN_SUCCESS)
{
vm_deallocate (mach_task_self (), c求职简历onfig_page, PAGE_MAX_SIZE * 2);
return NULL;
}
/* We have valid trampoline and config pages */
table = callo程序员训练班要多少钱c (1, sizeof (ffi_trampolin产品设计e_t程序员训练班要多少钱able));
table->fr产品e程序员那么心爱e_count = FFI_REMAP_TRAMPOLINE_COappearanceUNT/2;
table->c前端面试题on程序员那么心爱fig前端工程师_page = config_page;
table->trampoline_page = trampoline_page;
......产品质量法
return table;
}

首要 ffi 在创立trampoline 时,
会分配两个连apple续的 page

trampoline page 会 remap 到咱们事先在代码中汇编写的 ffi_closure_remap_trampoline_table_page。

其结构如图所示:

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

当咱们 ffi_prep_closure_loc(_clos求职简历ure, &_cif, _st_ffi_function, (__bridge void *)(self), entry1))程序员薪酬一般多少 写入closure数据时, 会写入到 entry1 对应的 closu程序员是学什么专业er1。


ffi_status
ffi_prep_cappreciatelosure_loc (ffi_closure *closure,
ffi_cif* cif,
void (*fun)(ffi_cif*,void*,void**,void*),
void *user_data,
void *c前端开发需求学什么odeloc求职信300字)
{
......
if (cif->flags & AARCH64_FLAG_AR求职意向怎样写G_V)
start = ffi_closure_SYSV_V; // ffi 对 clo产品质量法sure的处理函数
else
start = ffi_closure_SYSV;
void **config = (void**)((uappreciateint8_t *)codeloc - PAGE_MAX_SIZE);
config[0] =前端和后端哪个薪酬高 closure;
config程序员是学什么专业[1] = start;
......
}

这是怎样对应到的呢? closure1 和 entry1 距离appointment其所属前端Page的offset是共同的,经过offset,成功建立 trampoline entry 和 trampoline closure 的对应联络。

现在咱们知道这个联络,咱们经过代码看一下前端工程师到底在程序运转的时分 是怎样找前端到 closure 的。
这四条指令是咱们trampoline entry的代码完结,就是 ffi 回来的 xxx_func_ptr

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

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

# 动态remap的 p求职信300字age
.align PAGE_MA程序员X求职信300字_SHIFT
CNAME(ffi_closu求职简历模板免费re_remap_trampoline_table_page):
.rept PAGE_MAX_S前端和后端的差异IZE / FFI_TRAMPOLINE_SIZE
# 这是咱们的 trampoline entry, 就是ffi生成的函appear数指针
adr x16, -PAGE_MAX_SIZE                         // 将pc地址减去PAGE_MAX_SIZE, 找到 tr程序员训练班要多少钱ampoine data entry
ldp x17, x16, [x16]                             // 加载咱们写入的 closure, start 到 x17, x16
br x16                                          // 跳转到 start求职信英语作文 函数
nop        /* each entry in the trampoline config page is 2*sizeof(void*) so the求职简历模板免费 trampoline itself cannot be smaller that 16 bytes */
.endr

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

静态跳板的完结

因为代码段和数据段在不同的内存区域。
咱们此刻不能经过 像vmremap相同分配两个连续的PAGE,在寻找trampoline dat程序员怎样学a entry只是简略的-PAGE_MAX_SIZE找到对应联络,需求稍微费事点产品设计专业的处理。

首要app装置下载是经过adrp找到_前端开发ffi_static_tramp程序员客栈olappearine_data求职毛遂自荐_page1_ffi_static_trampoline_page1的开始地址,用pc-_ffi_static_trampoline_page1的开程序员那么心爱始地址核算off前端开发需求学什么set,找到trampoline data entry。

# 静态分配的page
#ifdef __MACH__
#include <m求职简历ach/machine/vm_param.h>
.align 14
.程序员装逼代码data
.global _ffi_static_前端面试题trampo前端学什么line_data_产品质量法page1
_ffi_static_trampoline_data_page1:
.space PAGE_MAX_SIZE*5
.align PAGE_MAX_SHIFT
.text
CNAME(_ffi_sta程序员那么心爱tic_程序员是学什么专业trampoline_page1):
_ffi_local_forwarding_bridge:
adrp x17, ffi_closure_static_tram求职pol产品司理ine_table_page_start@PAGE;// text page
sub  x16, x16, x1求职简历7;// 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_table_page):程序员薪酬一般多少
#这个label 用来adrp@PAGE 核算 trampoline 到 trampoline产品战略 page的offset
#留了5个用来调试approach
# 咱们static trampoline 两条指令就够了,这儿运用4个,和remap的保持共同
ffi_closure_static_t产品设计专业rampoline_table_page_start:
adr x16, #0
b _ffi_local_forwarding_bridge
nop
nop
adr x16, #0
b _ffi_l前端和后端哪个薪酬高ocal_forwarding_bridge
nop
nop
adr x16, #0
b _ffi_local_forwardiappearanceng_bridge
nop
nop
adr x16, #0
b _ffi_local_forwarding_br程序员是学什么专业idge
nop
nop
adr x16, #0
b _ffi_local_fo前端和后端哪个薪酬高rwarding_bridapprovege
nop
nop
// 5 * 4
.rept (PAGE_MAX_SIZE*appointment5-5*4) / FFI_TRAMPOLINE_SIZE
adr x16, #0
b _ffi_local_fo产品rwarding_bridge
nop
nop
.endr
.globl CNAME(ffi_closure_static_trampoline_table_page)
FFI_HIDDEN(CNAME(ffi_closure_static_trampoline_table_page))
#ifdef __ELF__
.type        CNAME(ffi_closure_sta产品设计tic_trampoline_table_page), #func求职招聘网ti程序员薪酬一般多少on
.size        CNAME(ffi_closure_static_trampolin程序员装逼代码e_table_page), . - CNAME(ffi_closure_static_tra前端结构mpoline_table_page)
#endif
#endif

关于字节移动途径团队

字节跳动移动途径团队(Client Infrastructure)是大前端根底技能工作领军者,担任整个字节跳动的大前端根底设施建造,提高公司全产品线的性能、稳定性和工程功率,支撑的产品包含但不限于抖音、今天头条、西瓜视频、火山小视频等,在移动端、Web、Desktop等各终端都有深入研究。

就是现在!客户端/前端/服务端产品介绍/检验开发 面向社会+校园招聘,base北上广深杭成!一同来用技能改变国apple,感兴趣能够联络邮箱 chenxuwei.cxw@bytedance.com,邮件主题 简历-姓名-求职意向-电话