货拉拉是如何实现symbolic demangle?

1. 前言

货拉拉移动端拥有一套完善的稳定性监控系统,该系统在帮助我们打造行业一流的产品、问题告警和问题归因等方面起到了非常好的作用。今天,我们将该系统中symbolic demange方向的实践分享出来,同时也将demangle工具直接开放到Github,希望能够帮助到同样有诉求的同学们。

2. 什么是symbolic demangle

在编程领域,demangle主要指的是将被编译器"mangle"的名称还原为原始的、可读性强的符号名称。需要理解demangle,首先需要知道什么是mangle。

什么是mangle?

在一些编程语言(特别是C++)中,编译器在生成目标代码时,会对函数、变量等符号的名称进行编码,这个过程称为"名称修饰"(name mangling)。这是因为C++支持函数重载和命名空间等高级语言特性,当这些特性引入到汇编级或机器级的目标代码时,需要一种机制来唯一地标识每一个符号。名称修饰会依照某种规则将原始名称转换为一个唯一的、包含类型信息的字符串。

例如,一个C++函数void foo(int)可能会被编译器修改成某种形式如 _Z3fooi(具体形式取决于编译器)。

那么,相反,demangle是将被名称修饰过的符号还原为原始符号的过程。这个通常在调试、堆栈追踪或诊断程序崩溃的时候非常有用,因为编译器生成的修饰名称往往难以理解。

在调试、堆栈追踪、诊断工具等场景下,demangle都显得尤为重要,能够帮助开发者更迅速地定位问题;

总结来说,demangle是一个在调试和诊断过程中非常重要的操作,它将编译器生成的复杂符号名称转换为更易懂的原始符号,帮助开发者更快地理解和定位问题。C++、Rust、Swift等语言和相应的工具链都提供了支持这个操作的工具。

下图是项目中未demangle的堆栈:

从图中能够看出,堆栈可读性较差,不利于快速锁定重要信息。

3. 为什么选择symbolic方案

在货拉拉移动端技术栈中,需要demangle的主要是C++和Swift语言。前者有c++filt,后者有Xcode工具链自带的swift-demangle,但这种独立的工具需要我们完成对语言的识别、环境的独立搭建以及效率的权衡等工作。字节的一篇文章《iOS符号解析重构之路》中曾提到:

为了解决部分C++与Rust符号demangle失效以及各种语言demangle工具不一致的问题。将原本llvm自带的demangle工具替换成了一个Rust实现,支持全语言的demangle工具symbolic-demangle(链接见参考资料),极大的降低了运维成本。

我们从这里得到了思路,在调研开源工具symbolic-demangle后,确认它是当下成本最低、效果最好的方案。很快,我们将它应用到了多个业务。

方案对比 支持多语言 支持linux 维护成本
swift-demangle X X
c++ filt X
llvm demangle ✓(不支持swift)
symbolic-demangle 一般

4. symbolic在货拉拉的实践落地

symbolic目前最高支持到Swift 5.5.1,更高的版本已经快3年没有迭代支持。然后Swift是创新业务和国际化业务的主流编程语言,于是支持更高版本的Swift demangle将是一件绕不开的事情。我们将重点阐述如何适配高版本Swift以及在Linux服务器上构建python库过程中遇到的问题。python库以开源的方式提供给大家,详见hll-symbolic-demangle

4.1 如何适配高版本swift

如何基于symbolic适配更高版本的Swift demangle呢?实践中主要分两大步:官方Swift demangle源码同步升级和symbolic项目编译;

首先,我们来介绍下官方Swift demangle源码同步升级.

4.1.1 Check out the latest release of Swift:

go 复制代码
1.  Create a directory that will house swift and its dependencies:

```
$ mkdir swift-source && cd swift-source
```

2.  Clone the swift repository into a subdirectory:

```
$ git clone git@github.com:swiftlang/swift.git
```

3.  Check out dependencies:

```
$ ./swift/utils/update-checkout --clone
```

  如果执行过程中遇到部分项目clone失败的情况,比如icu 和llvm-project 总是clone失败,那么可以尝试下自行git clone到目标位置,而不是走上面的命令,完成后继续重试上面的命令。

4.  Check out the release branch of the latest release:

```
$ cd swift
$ git checkout swift-5.10-RELEASE
```

5.  Build the complete Swift project:

```
$ ./utils/build-script --skip-build
```

这一步执行一般需要一段时间,be patient~

4.1.2 Copy updated sources and headers from the checkout to this library:

go 复制代码
  直接运行当前目前下的update脚本:

```
$ ./update.py swift-source
```

经过上述流程后,就基本完成了源码更新的工作。

4.1.3 symbolic项目编译

完成源码更新的工作后,接下来,是symbolic项目编译的步骤。

symbolic是一个Rust项目,使用C++开发。因此我们可以执行cargo build来执行编译。编译过程中会遇到很多报错。以下是我们遇到的一些问题,希望能够帮助到大家。

【问题1】当出现llvm文件缺少时,去官网找到llvm-project中的相关文件拷贝进去。

目标路径:/Users/XXX/Workspace/learn/swift-source/llvm-project/llvm/include/llvm/ADT

【问题2】vendor/swift/include/llvm/ADT/Optional.h找不到template name

去官网swift/stdlib/include/llvm/Support/type_traits.h找到完整代码更新(依赖更新的脚本有点问题)

【问题3】诸如std之类的编译错误

使用C++17编译吧

【问题4】CPP源码修改后build仍然报错

最好删除项目根目录target中的构建文件,比如debug, release。

其他问题就不一一记录了。

4.1.4 测试

编译通过后,可通过本地验证的方式测试结果。

arduino 复制代码
python3 setup.py install // 成功后pip list可见

安装前请使用pip list查看是否有其他版本的symbolic,如有,请先卸载

pip uninstall symbolic

install完成直接在/py/symbolic目录下执行如下:

java 复制代码
python3
from symbolic.demangle import demangle_name
demangle_name('$s10Speediness17NetworkQualityCLIO3run10sequentialAC6ResultVSb_tYaKFZTf4nd_nTQ0_')

除了上面的测试方法,也可以在测试用例中追加一些高版本的用例,然后执行test-swift

进入到symbolic-demangle目录下执行脚本

bash 复制代码
cargo test test_swift

4. 2 如何解决Linux上python库的构建

在Linux服务器上安装python分发包,理论上就能够通过Java跨进程调用Python的方式实现demangle。

Egg 和 Wheel 本质上都是一个 zip 格式包,Egg 文件使用 .egg 扩展名,Wheel 使用 .whl 扩展名。

Wheel 的出现是为了替代 Egg,其现在被认为是 Python 的二进制包的标准格式。

在终端执行以下命令,可生成 whl 分发包。

arduino 复制代码
python3 setup.py bdist_wheel

命令执行执行完成后,在py/dist文件夹中,可以看到 symbolic-{版本号}*.whl的文件。

使用pip3 install 命令安装 wheel 文件

bash 复制代码
pip3 install dist/symbolic-10.2.1-py2.py3-none-macosx_14_0_universal2.whl

安装成功,自此之后只需要将whl文件在服务器中进行安装即可。

但在部署服务器的过程中,遇到如下的报错。看来Mac上生成的whl分发包不适用于Linux,需要在Linux上打包whl分发包。

相比MacOS,Linux机器上,构建whl分发包变得困难起来。这也是为什么会单独抽出一节来说明如何解决Linux上构建python库的原因。

复杂之处主要体现在编译问题、库版本不匹配、环境搭建等环节。不同的环境遇到的问题或者错误也不尽相同,在此,我们仅记录下在实践过程中的一些问题;

4.2.1 环境搭建

Rust环境搭建

物理机操作系统:MacOS 14.5

虚拟机软件:VituralBox

虚拟机操作系统:Debian 12.6.0

虚拟机Python版本:3.9

4.2.2 编译失败

C++编译失败

为什么使用C++在Linux上编译时,会出现错误?

考虑到相同的代码,在不同的环境编译时出现的问题,首先考虑到应该是使用的编译器不同,但如何确认使用的是哪种编译器?

我们从执行打包的Python命令入手,找到并打开 py/setup.py

在setup()方法,出现了一个配置项:milksnake_tasks

Milksnake is an extension for setuptools that allows you to distribute dynamic linked libraries in Python wheels in the most portable way imaginable.

It gives you a hook to invoke your own build process and to then take the resulting dynamic linked library.
CFFI(C Foreign Function Interface)是 Python 中的一个库,它提供了一种在 Python 代码中调用 C 代码的方法。通过 cffi,你可以编写 Python 代码来直接使用 C 函数、调用共享库(.so 或 .dll 文件),或者甚至嵌入 C 代码。这使得在 Python 中执行低级系统调用或利用现有的 C 代码库变得更加容易和高效。

首先在MacOS中,执行打包命令。

由编译日志中,可以看来,这里使用的是GCC编译器。

再次,在Linux中,执行打包命令。

由编译日志中,可以看来,这里使用的是c++命令进行编译的,这个在之前声明的主流编译器中并没有出现。

那么gcc/g++/c++是什么关系呢?

版本号竟然一样,通过diff命令,确认两个命令完全一样。

通过名字猜想,GCC是编译c语言的,g++是编译C++语言的。

其实并不是,也可以通过gcc -xc++ -lstdc++ -shared-libgcc也编译c++源代码。

GCC和g++其实只是个外壳,在编译的时候,最终决定是调用后台的cc1还是cc1plus。

编译的语言是可以通过编译选项-x来指定。

那么gcc和g++主要的区别是:编译时自动连接的库的不同。

g++会自动连接std c++的库,而gcc不会。所以,使用gcc编译c++的代码,如果c++的代码中使用了std的类,例如:vector,会出现连接出错。

g++还有一个不同就是编译cpp文件时,会有一些预定义的宏:

arduino 复制代码
#define GXX_WEAK 1
#define __cplusplus 1
#define __DEPRECATED 1
#define GNUG 4
#define __EXCEPTIONS 1
#define private_extern extern

这就定位到了,在MacOS与Linux中,分别使用了 GCC 和 C++(也就是G++)进行编译。

接着再看编译日志,发现一个标识:-fpermissive,初步判断是一个编译命令控制参数。

最终在其官网上,找到如下线索。

允许使用旧的语法进行编译,将错误降级为警告。因此,我们加入了新的编译参数如下:

css 复制代码
fn main() {
    #[cfg(feature = "swift")]
    {
        cc::Build::new()
            .cpp(true)
            .files(&[
                "src/swiftdemangle.cpp",
                "vendor/swift/lib/Demangling/Demangler.cpp",
                "vendor/swift/lib/Demangling/Context.cpp",
                "vendor/swift/lib/Demangling/ManglingUtils.cpp",
                "vendor/swift/lib/Demangling/NodeDumper.cpp",
                "vendor/swift/lib/Demangling/NodePrinter.cpp",
                "vendor/swift/lib/Demangling/OldDemangler.cpp",
                // "vendor/swift/lib/Demangling/OldRemangler.cpp",
                "vendor/swift/lib/Demangling/Punycode.cpp",
                "vendor/swift/lib/Demangling/Remangler.cpp",
            ])
            .flag_if_supported("-std=c++17")
            .flag("-DLLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING=1")
             .flag(  "-fpermissive"  ) 
            .warnings(false)
            .include("vendor/swift/include")
            .compile("swiftdemangle");
    }
}

重新执行构建打包命令,编译成功。

GLIBC版本未找到

虽然编译打包并部署成功后,再验证符号demangle的时候又出错了,提示GLIBC_2.29版本未找到。

GLIBC,全称GNU C Library,是Linux系统中非常重要的一个组成部分。它为开发者提供了丰富的API接口,使得开发者能够更方便地进行系统编程。随着Linux系统的不断发展,glibc也在不断升级,以满足新的需求和解决潜在的问题。

当前虚拟机操作系统是 Debian 12.6.0,GLIBC版本为 2.36。

方案一:是否可以指定使用低版本的GLIBC进行编译呢?

从搜索结果中,实现成本较复杂,不作展开;

方案二:选择一个低版本的Linux OS系统,从搜索结果中来看,CentOS 7中默认的GLIBC版本为 2.17,这正是所需要的最低版本。

于是安装新的虚拟机。

虚拟机软件:VituralBox

虚拟机操作系统:CentOS 7

配置网络,安装基础开发环境。

由于CentOS 7.0官方已不再维护,已不能从官方repo下载开发环境,又是一翻配置,替换为国内repo。

GCC版本不匹配

在新的虚拟机中,进行依赖安装和编译,出现了错误,提示GCC版本不匹配。

CentOS 7中,最高支持的GCC版本是4.8.5;不支持 C++17,因此需要升级GCC;

升级如下:

sql 复制代码
yum -y install centos-release-scl
yum clean all
yum makecache

由于CentOS 7官方已不再维护,无法拉取到repo,此处需要手动替换下repo中的baseurl域名

4.2.4 安装库

在升级GCC后,编译再次出错,提示缺少libffi、python相关报错

什么是FFI?

FFI(Foreign Function Interface)允许以一种语言编写的代码调用另一种语言的代码,而libffi库提供了最底层的、与架构相关的、完整的FFI。libffi的作用就相当于编译器,它为多种调用规则提供了一系列高级语言编程接口,然后通过相应接口完成函数调用,底层会根据对应的规则,完成数据准备,生成相应的汇编指令代码。

安装libffi

yum install -y libffi-devel libffi

python-devel

yum install -y python3-devel

安装python

pip3 install wheel

在经历了环境搭建、解决编译失败、各种依赖版本冲突和依赖缺失后,终于在Linux中生成了whl分发包。

5. 最终效果

5.1 线上效果

5.2 线下对比Xcode工具

线下将升级后的symbolic和Xcode demangle工具做了对比,几乎一样;

升级后的symbolic demangle本地运行结果如下:

Xcode swift-demangle工具解析如下:

6. 货拉拉移动技术团队

货拉拉移动技术团队是一支由年轻人组成的有活力的团队,我们热爱技术,乐于分享和贡献。如果读者的你也有同样的热爱,欢迎加入我们~

7. 参考资料

name mangling维基百科

iOS符号解析重构之路

github.com/getsentry/s...

相关推荐
crasowas4 小时前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
彭亚川Allen4 小时前
优化了2年的性能,没想到最后被数据库连接池坑了一把
数据库·后端·性能优化
MClink4 小时前
Go怎么做性能优化工具篇之pprof
开发语言·性能优化·golang
ii_best6 小时前
ios按键精灵脚本开发:ios悬浮窗命令
ios
京东零售技术6 小时前
Taro小程序开发性能优化实践
性能优化·taro
理想不理想v8 小时前
wepack如何进行性能优化
性能优化
Code&Ocean11 小时前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/11 小时前
Laya ios接入goole广告,开始接入 2
ios
fantasy_arch1 天前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
码农老起1 天前
企业如何通过TDSQL实现高效数据库迁移与性能优化
数据库·性能优化