iOS 寄存器编程引起的编译问题
一、寄存器编程代码
1.实例
在某些组件运用到了寄存器相关编程,目的是为了调出当前运行堆栈,部分应用代码如下:
uintptr_t xol_mach_linkRegister(mcontext_t const machineContext){
#if defined(__i386__) || defined(__x86_64__)
return 0;
#endif
#if defined(__arm64__) || defined(__arm__)
return machineContext->__ss.__lr;
#endif
return 0;
}
直接拿到 __ss
对应结构体看到 (以手机真机运行环境举例
)
_STRUCT_MCONTEXT64
{
_STRUCT_ARM_EXCEPTION_STATE64 __es;
_STRUCT_ARM_THREAD_STATE64 __ss;
_STRUCT_ARM_NEON_STATE64 __ns;
};
打开 _STRUCT_ARM_THREAD_STATE64
看到
#define _STRUCT_ARM_THREAD_STATE64 struct __darwin_arm_thread_state64
_STRUCT_ARM_THREAD_STATE64
{
__uint64_t __x[29]; /* General purpose registers x0-x28 */
__uint64_t __fp; /* Frame pointer x29 */
__uint64_t __lr; /* Link register x30 */
__uint64_t __sp; /* Stack pointer x31 */
__uint64_t __pc; /* Program counter */
__uint32_t __cpsr; /* Current program status register */
__uint32_t __pad; /* Same size for 32-bit or 64-bit clients */
};
项目里用到的属性有
__uint64_t __fp; /* Frame pointer x29 */
__uint64_t __lr; /* Link register x30 */
__uint64_t __sp; /* Stack pointer x31 */
__uint64_t __pc; /* Program counter */
2. 寄存器对应设备:
解释一下几个宏:
i386 & x86
: 电脑模拟器
__arm64__
: 现有的终端设备 (iPhone 12等)
__arm__
:包含armv7,armv7s,(iphone4,ipad2等)
本组件不支持armv7与模拟器环境,但是由于是平台化app,各团队能修改的权限有限,如果主工程支持armv7,那么就要支持对应的打包环境。
二、打包之路
1.发布组件
pod lib lint
不通过
不同编译环境下的不同头文件路径:
由于对不同编译环境的支持会引用到不同的宏,如:
在arm
下
//文件路径 iOS 14.4 -> user/include/i386/_mcontext.h
_STRUCT_MCONTEXT64
{
_STRUCT_ARM_EXCEPTION_STATE64 __es;
_STRUCT_ARM_THREAD_STATE64 __ss;
_STRUCT_ARM_NEON_STATE64 __ns;
};
i386
下
//文件路径 Simulator iOS 14.4 -> user/include/i386/_mcontext.h
_STRUCT_MCONTEXT32
{
_STRUCT_X86_EXCEPTION_STATE32 __es;
_STRUCT_X86_THREAD_STATE32 __ss;
_STRUCT_X86_FLOAT_STATE32 __fs;
};
对应的 __ss
能指向的寄存器名称也不一样
//文件路径 iOS 14.4 -> user/include/mach/arm/_structs.h
// arm64
_STRUCT_ARM_THREAD_STATE64
{
__uint64_t __x[29]; /* General purpose registers x0-x28 */
__uint64_t __fp; /* Frame pointer x29 */
__uint64_t __lr; /* Link register x30 */
__uint64_t __sp; /* Stack pointer x31 */
__uint64_t __pc; /* Program counter */
__uint32_t __cpsr; /* Current program status register */
__uint32_t __pad; /* Same size for 32-bit or 64-bit clients */
};
// i386
//文件路径 Simulator iOS 14.4 -> user/include/mach/i386/_structs.h
_STRUCT_X86_THREAD_STATE32
{
unsigned int __eax;
unsigned int __ebx;
unsigned int __ecx;
unsigned int __edx;
unsigned int __edi;
unsigned int __esi;
unsigned int __ebp;
unsigned int __esp;
unsigned int __ss;
unsigned int __eflags;
unsigned int __eip;
unsigned int __cs;
unsigned int __ds;
unsigned int __es;
unsigned int __fs;
unsigned int __gs;
};
所以项目里代码写法:
#if defined(__i386__) || defined(__x86_64__)
return 0;
#endif
#if defined(__arm64__) || defined(__arm__)
return machineContext->__ss.__pc;
#endif
报错:pod lib lint
pod lib lint
时模拟器无法找到__arm64__
环境
解决方案:在podspec文件加上
s.libraries = 'stdc++'
2.打包framework
pod 成功后,由于是平台化开发,所以主模块需要打包framework,主模块又引入了当前组件,当前组件引起报错
报错: 平台framework
打包报错
error: no member named '__pc' in 'struct __darwin_arm_thread_state64' error: no member named '__lr' in 'struct __darwin_arm_thread_state64' error: no member named '__fp' in 'struct __darwin_arm_thread_state64' error: no member named '__sp' in 'struct __darwin_arm_thread_state64'
原因:虽然开发过程中编译和运行没有问题,但是,jenkins
打包环境下__darwin_arm_thread_state64
环境下找不到__ss.__pc
这种指定
,因为打包服务器集成了armv7
,armv7s
等不同指令集架构
解决方案:
通过查看源码发现了这些宏
//文件路径 iOS 14.4 -> user/include/mach/arm/_structs.h
#define __darwin_arm_thread_state64_get_pc(ts) \
((ts).__pc)
#define __darwin_arm_thread_state64_get_lr(ts) \
((ts).__lr)
#define __darwin_arm_thread_state64_get_sp(ts) \
((ts).__sp)
#define __darwin_arm_thread_state64_get_fp(ts) \
((ts).__fp)
将return machineContext->__ss.__lr;
引用改为
return __darwin_arm_thread_state64_get_sp(machineContext->__ss);
发布版本,framework打包通过
3.打包测试包
主工程包不需要打模块framework,此时又引起主工程打包服务器编译问题
报错:jenkins
主工程包报错
Undefined symbols for architecture armv7:
"___darwin_arm_thread_state64_get_pc", referenced from:
+[XXX xxx:] in libXXX.a(xxxxxx.o)
"___darwin_arm_thread_state64_get_lr", referenced from:
+[XXX xxx:] in libXXX.a(xxxxxx.o)
"___darwin_arm_thread_state64_get_fp", referenced from:
+[XXX xxx:] in libXXX.a(xxxxxx.o)
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
意思是主工程支持了armv7
,打包服务器会编译所有的处理器环境,在armv7环境下,找不到对应的宏
解决方案:
去掉armv7环境下对相关宏的引用,代码改为:
#if defined(__arm64__)
// return machineContext->__ss.__sp;
return __darwin_arm_thread_state64_get_sp(machineContext->__ss);
#endif
return 0;
这种改法实际上是懒人改法,因为该组件不需要对armv7环境设备负责,所以在arm64环境下,正常引用对应宏,在其他环境下,直接返回0;
总结
- 在没有查看源码时,不知道有
__darwin_arm_thread_state64_get_sp
这种用法,导致打包失败情况下,查无结果,网上也大多是关于RN相关引用不当的问题,但是RN本身在最新版本修复了这个问题 - 对于
__darwin_arm_thread_state64_get_sp
也不是万能的,第三个报错就是在armv7环境下并没有声明这个宏,所以需要进一步对环境进行判断 - 这部分代码其实并不是组件的业务逻辑内容,但是报错也会造成一定的阻塞,我们都往往只看了XNU对于arm64部分的源码,其实往往忽略了关于 armv7,armv7s,i386和x86相关,在多平台编译问题上,需要更多的了解源码,找到解决问题的答案。