Android R8混淆后,如何基于mapping.txt自动修改C++代码?
解决方案:基于R8 mapping.txt自动适配C++代码的编译流程
针对你遇到的两个核心问题,以下是具体的实现方案,完全基于Gradle和自定义脚本完成,无需永久修改源码:
一、仅编译时临时修改C++代码,不改动原项目文件
核心思路是复制原始C++代码到临时目录,在临时目录中完成符号替换,再让CMake指向该临时目录编译,原始源码目录保持完全不变。
具体步骤:
在App模块的
build.gradle中定义Gradle任务,负责创建临时目录、复制源码、执行符号替换脚本:android { // ...其他配置 buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // 让CMake指向临时生成的源码目录 externalNativeBuild { cmake { path file('build/tmp/native-obfuscated/CMakeLists.txt') } } } } // 生成混淆后临时原生代码的任务 task generateObfuscatedNativeSources(type: Copy) { def originalSrcDir = file('src/main/cpp') def tempSrcDir = file('build/tmp/native-obfuscated') // 清空并复制原始C++代码到临时目录 delete tempSrcDir from originalSrcDir into tempSrcDir // 复制完成后执行符号替换脚本 doLast { def mappingFile = file("build/outputs/mapping/release/mapping.txt") def replaceScript = file("scripts/replace_native_symbols.py") exec { commandLine "python", replaceScript.absolutePath, tempSrcDir.absolutePath, mappingFile.absolutePath } } } }编写符号替换脚本(以Python为例),解析
mapping.txt并替换临时目录中C++代码的类名、字段名:import os import re def parse_mapping(mapping_path): class_map = {} field_map = {} current_class = None with open(mapping_path, 'r') as f: for line in f: line = line.strip() # 匹配类映射:com/example/MyClass -> a: class_match = re.match(r'^([\w\/]+) -> (\w+):$', line) if class_match: original_class = class_match.group(1).replace('/', '.') obfuscated_class = class_match.group(2) class_map[original_class] = obfuscated_class current_class = original_class continue # 匹配字段映射: myField: Ljava/lang/String; -> a field_match = re.match(r'^\s+([\w]+): .+ -> (\w+)$', line) if field_match and current_class: original_field = field_match.group(1) obfuscated_field = field_match.group(2) field_map[f"{current_class}.{original_field}"] = obfuscated_field return class_map, field_map def replace_symbols(file_path, class_map, field_map): with open(file_path, 'r') as f: content = f.read() # 替换JNI中的类名引用(如FindClass("com.example.MyClass")) for original, obfuscated in class_map.items(): content = content.replace(f'"{original}"', f'"{obfuscated}"') # 替换JNI中的字段名引用(如GetFieldID(clazz, "myField", "...")) for original_full, obfuscated in field_map.items(): _, original_field = original_full.split('.', 1) content = re.sub(r'GetFieldID\([^,]+, "' + re.escape(original_field) + r'",', f'GetFieldID(\\1, "{obfuscated}",', content) with open(file_path, 'w') as f: f.write(content) def main(src_dir, mapping_path): class_map, field_map = parse_mapping(mapping_path) for root, _, files in os.walk(src_dir): for file in files: if file.endswith(('.cpp', '.h')): replace_symbols(os.path.join(root, file), class_map, field_map) if __name__ == '__main__': import sys if len(sys.argv) != 3: print("Usage: python replace_native_symbols.py <临时源码目录> <mapping.txt路径>") sys.exit(1) main(sys.argv[1], sys.argv[2])
二、调整编译顺序,确保mapping.txt生成后再编译C++代码
默认情况下,原生代码编译任务早于R8混淆任务,需要通过Gradle任务依赖强制调整顺序:
在App模块的build.gradle中添加任务依赖配置:
android { // ...其他配置 afterEvaluate { // 获取关键任务实例 def minifyTask = tasks.findByName('minifyReleaseWithR8') def generateNativeTask = tasks.findByName('generateObfuscatedNativeSources') def nativeBuildTask = tasks.findByName('externalNativeBuildRelease') if (minifyTask && generateNativeTask && nativeBuildTask) { // 强制顺序:R8混淆生成mapping → 生成临时C++代码 → 编译原生代码 generateNativeTask.dependsOn minifyTask nativeBuildTask.dependsOn generateNativeTask // 打破原生编译默认先于Kotlin编译的顺序 minifyTask.mustRunAfter tasks.findByName('compileReleaseKotlin') } } }
关键说明:
minifyReleaseWithR8任务完成后,mapping.txt才会生成在build/outputs/mapping/release/目录下- 通过
dependsOn和mustRunAfter强制任务执行顺序,确保脚本能拿到最新的混淆映射关系 - Debug模式若不需要混淆,可单独配置CMake指向原始源码目录,避免额外开销
内容的提问来源于stack exchange,提问作者Paul-Etienne
相关产品推荐
相关产品推荐

