1# ArkGuard字节码混淆常见问题 2<!--Kit: ArkTS--> 3<!--Subsystem: ArkCompiler--> 4<!--Owner: @oatuwwutao--> 5<!--Designer: @hufeng20--> 6<!--Tester: @kirl75; @zsw_zhushiwei--> 7<!--Adviser: @foryourself--> 8 9## 字节码混淆与源码混淆差异 10 11### 混淆范围差异 12 13**json文件** 14字节码混淆在开启`-enable-filename-obfuscation`混淆项后,json文件名会参与混淆。 15 16### 混淆选项差异 17 181. 字节码混淆开关,默认关闭,在[开启混淆功能](bytecode-obfuscation-guide.md#开启混淆步骤)后,需要额外在模块目录下`obfuscation-rules.txt`文件中配置`-enable-bytecode-obfuscation` 、`-enable-bytecode-obfuscation-debugging`。 192. 字节码混淆,不支持以下混淆项`-remove-comments`。 20 21### 混淆后文件结构差异 22 23**目录差异** 24 25  26 27字节码混淆后,obfuscation目录中多了obf、origin文件夹和config.json文件,具体详见[混淆效果](bytecode-obfuscation-guide.md#查看混淆效果)。 28 29**文件内容差异** 30 31nameCache.json文件: 32 33源码混淆后: 34 35```txt 36{ 37 "entry/src/main/ets/entryability/EntryAbility.ets": { 38 "IdentifierCache": { 39 "#UIAbility": "UIAbility", 40 ...... 41 "#testObject": "i", 42 "#EntryAbility": "j" 43 }, 44 "MemberMethodCache": { 45 .... 46 }, 47 "obfName": "entry/src/main/ets/entryability/EntryAbility.ets" 48 }, 49 ...... 50 }, 51 "compileSdkVersion": "5.0.0.70", 52 "entryPackageInfo": "entry|1.0.0", 53 "PropertyCache": { 54 ...... 55 }, 56 "FileNameCache": { 57 ...... 58 } 59} 60``` 61 62字节码混淆后: 63 64```json 65{ 66 "entry/src/main/ets/entryability/EntryAbility.ets": { 67 "IdentifierCache": { 68 "#EntryAbility": "a", 69 "#testObject": "c" 70 }, 71 "MemberMethodCache": { 72 "EntryAbility:0:0": "a", 73 ...... 74 }, 75 "obfName": "entry/src/main/ets/entryability/EntryAbility.ets", 76 "OriSourceFile": "entry|entry|1.0.0|src/main/ets/entryability/EntryAbility.ts", 77 "ObfSourceFile": "entry|entry|1.0.0|src/main/ets/entryability/EntryAbility.ts" 78 }, 79 ...... 80 "entryPackageInfo": "entry|1.0.0", 81 "compileSdkVersion": "5.0.0.70", 82 "PropertyCache": { 83 ...... 84 }, 85 "FileNameCache": { 86 ...... 87 } 88} 89``` 90 911. IdentifierCache中,字节码混淆时差异: 92 1. 不涉及函数参数名混淆。 93 2. 无匿名函数混淆名称映射。 942. 在开启`-enable-filename-obfuscation`混淆项时,字节码混淆比源码混淆,多了`OriSourceFile(混淆前源文件路径)`和`ObfSourceFile(混淆后源文件路径)`字段。 95 96### 切换注意点 97 98**UI混淆差异** 99 100字节码混淆不提供UI混淆的能力。 101由于字节码中UI组件存在大量字符串的形式绑定属性、方法、类、变量等,字节码混淆已通过系统白名单扫描的机制,保证功能正常。 102 103**以字符串的形式作为函数参数绑定属性** 104 105源码: 106 107```ts 108@Component 109export struct MainPage { 110 @State messageStr: string = 'Hello World'; 111 112 build() { 113 } 114} 115``` 116 117中间文件: 118 119```abc 120this.__messageStr = new ObservedPropertySimplePU('Hello World', this, "messageStr"); 121``` 122 123在中间文件转换过程中,message以字面量形式进行了绑定;此时,存在messageStr这个属性被混淆了,但是这个方法的字符串参数没有混淆,导致UI失效。 124 125**解决办法**:收集struct里所有成员,加入白名单,不参与混淆。目前由于字节码混淆不提供UI混淆能力,系统会自动识别添加到白名单,不需要开发者配置。 126 127**字节码中通过字符串绑定属性** 128 129源码: 130 131```ts 132// Sample.ets 133import { Type } from '@kit.ArkUI'; 134 135// 数据中心 136@ObservedV2 137class SampleChild { 138 @Trace p123: number = 0; 139 p2: number = 10; 140} 141 142@ObservedV2 143export class Sample { 144 // 对于复杂对象需要@Type修饰,确保序列化成功 145 @Type(SampleChild) 146 @Trace f123: SampleChild = new SampleChild(); 147} 148 149@ObservedV2 150class Info { 151 @Trace sample: Sample = new Sample(); 152} 153``` 154 155字节码文件: 156 157```abc 158tryldglobalbyname 0x136, Trace 159sta v2 160lda v0 161ldobjbyname 0x137, prototype 162sta v3 163lda.str sample 164sta v4 165lda v2 166callargs2 0x2c, v3, v4 167lda v0 168ldobjbyname 0x139, prototype 169sta v2 170lda.str sample 171sta v3 172lda v1 173callargs2 0x2e, v2, v3 174``` 175 176字节码层面存在一个global对象Trace,再通过字符串sample绑定属性。 177 178**解决办法**:字节码混淆需要收集扫描到的所有decorator节点,并且识别到Trace修饰的参数就自动加入白名单,不需要开发者配置。 179 180## 如何排查功能异常 181 182**排查功能异常步骤** 183 1841. 先在obfuscation-rules.txt配置-disable-obfuscation选项用于关闭混淆,确认问题是否由混淆引起。 1852. 若确认是开启混淆后功能出现的异常,请先阅读文档了解[混淆规则](bytecode-obfuscation.md#混淆选项)的能力以及哪些语法场景需要[配置白名单](bytecode-obfuscation.md#已有保留选项汇总)来保证应用功能正常。 1863. 参考本文已有的问题,若是相似场景可参考对应的解决方法快速解决。 1874. 若常见案例中未找到相似案例,建议依据各项配置功能正向定位(若不需要相应功能,可删除对应配置项)。 1885. 应用运行时崩溃分析方法: 189 a.打开应用运行日志或者点击DevEco Studio中出现的Crash弹窗,找到运行时崩溃栈。 190 b.应用运行时崩溃栈中的行号为编译产物的行号,方法名也可能为混淆后名称;因此排查时建议直接根据崩溃栈查看编译产物,进而分析哪些名称不能被混淆,然后将其配置进白名单中。 1916. 应用在运行时未崩溃但出现功能异常的分析方法(比如白屏): 192 a.打开应用运行日志:选择HiLog,检索与功能异常直接相关的日志,定位问题发生的上下文。 193 b.定位异常代码段:通过分析日志,找到导致功能异常的具体代码块。 194 c.增强日志输出:在疑似异常的功能代码中,对处理的数据字段增加日志记录。 195 d.分析并确定关键字段:通过对新增日志输出的分析,识别是否由于混淆导致该字段的数据异常。 196 e.配置白名单保护关键字段:将确认在混淆后对应用功能产生直接影响的关键字段添加到白名单中。 197 198## 常规配置问题处理 199 200### 开启enable-bytecode-obfuscation-debugging,没有生成pa文件如何处理 201 202首先确保Build Mode设置为release,查看根目录下的build-profile.json5中,设置 "compatibleSdkVersionStage": "beta3",再检查每个module中obfuscation-rules.txt文件里,开启字节码。 203 204### 混淆如何查看混淆效果 205 206在混淆结束后会将中间产物落盘,因此可以在编译产物build目录中找到混淆后的中间产物以查看混淆效果,同时可以找到混淆生成的名称映射表及系统API白名单文件。 207- 混淆后的文件目录:build/default/[...]/release/obfuscation/obf。 208- 混淆名称映射表及系统API白名单目录:build/default/[...]/release/obfuscation。 209 210 211 212- 名称映射表文件:nameCache.json,该文件记录了源码名称混淆的映射关系。 213- 系统API白名单文件:systemApiCache.json,该文件记录了SDK中的接口与属性名称,与其重名的源码不会被混淆。 214 215 216## 编译报错处理 217 218**案例一:报错内容为 ERROR: [Class]get different name for method.** 219 220**问题现象**:使用@CustomDialog,自定义对话框,内部再弹出另一个对话框,开启字节码混淆后,执行build失败,报错信息为: 221Error message: ArkTSCompilerError: ArkTS:ERROR Failed to execute ByteCode Obfuscate. 222Error message: [Class]get different name for method:&entry/src/main/ets/pages/XXXX&.#~@0>#setController^1. 223 224```ts 225// 代码1 226@CustomDialog 227export default struct TmsDialog { 228 controller?: CustomDialogController 229 dialogController:CustomDialogController 230 231 build() { 232 } 233} 234 235// 代码2 236@CustomDialog 237struct Index{ 238 controller?: CustomDialogController 239 dialogController?:CustomDialogController 240 241 build() { 242 } 243} 244``` 245 246**问题原因**: 247 248在这个示例中,在自定义的对话框中,再弹一个对话框;如上示例中代码1,或在一个UI中定义两个CustomDialogController对象,执行时,ets代码转ts后,会生成两个相同的setController函数,从而导致字节码混淆时报错。 249 250**解决方案**: 251 252```ts 253@CustomDialog 254export default struct TmsDialog { 255 controller?: CustomDialogController 256 dialogController:CustomDialogController|null = null; //修改此处的定义声明方式。 257 258 build() { 259 } 260} 261``` 262 263示例代码1中,在运行时,是无法正常弹出dialogController的,只需要在定义时改为解决方案中的代码,就可以正常弹出dialogController,同时字节码混淆功能正常; 264 265示例代码2中,由于我们只是使用CustomDialogController,因此不需要@CustomDialog,直接删除@CustomDialog即可,删除后功能正常,字节码混淆功能正常。 266 267从API version 18开始,上述示例代码将不能正常编译。新的版本中,一个@CustomDialog组件只能有一个未初始化的CustomDialogController。 268 269## 运行异常处理 270 271### 开启-enable-property-obfuscation选项可能出现的问题 272 273**案例一:报错内容为 Cannot read property 'xxx' of undefined** 274 275```ts 276// 示例JSON文件结构(test.json): 277/* 278{ 279 "jsonObj": { 280 "jsonProperty": "value" 281 } 282} 283*/ 284 285// 混淆前 286import jsonData from "./test.json"; 287 288let jsonProp = jsonData.jsonObj.jsonProperty; 289 290// 混淆后 291import jsonData from "./test.json"; 292 293let jsonProp = jsonData.i.j; 294``` 295 296开启属性混淆后,"jsonProperty"被混淆成随机字符"j",但json文件中为原始名称,从而导致值为undefined。 297**解决方案**:使用-keep-property-name选项将json文件里的字段配置到白名单。 298 299**案例二:使用了数据库相关的字段,开启属性混淆后,出现报错** 300 301报错内容为table Account has no column named a23 in 'INSERT INTO Account(a23)'。 302代码里使用了数据库字段,混淆时该SQL语句中字段名称被混淆,但数据库中字段为原始名称,从而导致报错。 303**解决方案**:使用-keep-property-name选项将使用到的数据库字段配置到白名单。 304 305**案例三:使用Record<string, Object>作为对象的类型时,该对象里的属性被混淆,导致功能异常** 306 307**问题现象**: 308parameters的类型为Record<string, Object>,在开启属性混淆后,parameters对象中的属性linkSource被混淆,进而导致功能异常。示例如下: 309 310```ts 311// 混淆前 312import { Want } from '@kit.AbilityKit'; 313 314let petalMapWant: Want = { 315 bundleName: 'com.example.myapplication', 316 uri: 'maps://', 317 parameters: { 318 linkSource: 'com.other.app' 319 } 320} 321``` 322```ts 323// 混淆后 324import type Want from "@ohos:app.ability.Want"; 325 326let petalMapWant: Want = { 327 bundleName: 'com.example.myapplication', 328 uri: 'maps://', 329 parameters: { 330 i: 'com.other.app' 331 } 332}; 333``` 334 335**问题原因**: 336 337在这个示例中,所创建的对象的内容需要传递给系统来加载某个页面,因此对象中的属性名称不能被混淆,否则会造成功能异常。示例中parameters的类型为Record<string, Object>,这只是一个表示以字符串为键的对象的泛型定义,并没有详细描述其内部结构和属性类型。因此,混淆工具无法识别该对象内部哪些属性不应被混淆,从而可能导致内部属性名linkSource被混淆。 338 339**解决方案**: 340 341将混淆后会出现问题的属性名配置到属性白名单中,示例如下: 342 343```txt 344-keep-property-name 345linkSource 346``` 347 348**案例四:使用@Type和@Trace组合修饰的装饰器属性,混淆后,功能不正常** 349 350**问题现象**: 351 352使用@Type和@Trace组合修饰的装饰器属性,可以正常混淆,但混淆后,功能异常。 353 354```ts 355// Sample.ets 356import { Type } from '@kit.ArkUI'; 357 358@ObservedV2 359class SampleChild { 360 @Trace p123: number = 0; 361 p2: number = 10; 362} 363 364@ObservedV2 365export class Sample { 366 // 对于复杂对象需要@Type修饰,确保序列化成功 367 @Type(SampleChild) 368 @Trace f123: SampleChild = new SampleChild(); 369} 370 371// 调用 372// a.ets 373import { PersistenceV2 } from '@kit.ArkUI'; 374import { Sample } from './Sample'; 375 376@Entry 377@ComponentV2 378struct Page { 379 prop: Sample = PersistenceV2.connect(Sample, () => new Sample())!; 380 381 build() { 382 Column() { 383 Text(`Page1 add 1 to prop.p1: ${this.prop.f123.p123}`) 384 } 385 } 386} 387``` 388 389混淆后,p123,f123都被正常替换了,但处理Trace,Type装饰器属性时,p123,f123都被识别为字符串,不参与混淆,导致调用失败。 390 391**问题原因**: 392 393装饰器修饰的属性名需要保留,使用@Type和@Trace组合修饰的装饰器属性同样需要被保留。 394 395**解决方案**: 396 397使用-keep-property-name选项,将未直接导出的类型内的属性配置到属性白名单中。示例如下: 398 399```txt 400-keep-property-name 401f123 402p123 403``` 404 405**案例五:同时开启-enable-property-obfuscation和-keep选项可能会出现的问题** 406 407**问题现象**: 408使用如下混淆配置: 409 410```txt 411-enable-property-obfuscation 412-keep 413./file1.ts 414``` 415 416并且在file2.ts中导入file1.ts的接口。此时,接口中有属性的类型为对象类型,该对象类型的属性在file1.ts中被保留,在file2.ts中被混淆,从而导致调用时引发功能异常。示例如下: 417 418```ts 419// 混淆前 420// file1.ts 421export interface MyInfo { 422 age: number; 423 address: { 424 city1: string; 425 } 426} 427 428// file2.ts 429import { MyInfo } from './file1'; 430 431const person: MyInfo = { 432 age: 20, 433 address: { 434 city1: "shanghai" 435 } 436} 437 438// 混淆后,file1.ts的代码被保留 439// file2.ts 440import { MyInfo } from './file1'; 441 442const person: MyInfo = { 443 age: 20, 444 address: { 445 i: "shanghai" 446 } 447} 448``` 449 450**问题原因**: 451 452-keep选项保留file1.ts文件时,file1.ts中代码不会被混淆。对于导出属性(如address)所属类型内的属性,不会被自动收集在属性白名单中。因此,该类型内的属性在其他文件中被使用时,会被混淆。 453 454**解决方案**: 455 456**方案一**:使用interface定义该属性的类型,并使用export进行导出,这样该属性会自动被收集到属性白名单中。示例如下: 457 458```ts 459// file1.ts 460export interface AddressType { 461 city1: string 462} 463 464export interface MyInfo { 465 age: number; 466 address: AddressType; 467} 468``` 469 470**方案二**:使用-keep-property-name选项,将未直接导出的类型内的属性配置到属性白名单中。示例如下: 471 472```ts 473-keep-property-name 474city1 475``` 476 477### 同时开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项可能出现的问题 478 479**当开启这两个选项时,主模块调用其他模块方法时涉及的方法名称混淆情况如下**: 480|主模块 |依赖模块 |导入与导出的名称混淆情况| 481|-------|--------|---------| 482|HAP/HSP| HSP |HSP和主模块是独立编译的,混淆后名称会不一致,因此都需要配置白名单| 483|HAP/HSP| 本地HAR |本地HAR与主模块一起编译,混淆后名称一致| 484|HAP/HSP| 三方库| 三方库中导出的名称及其属性会被收集到白名单,因此导入和导出时都不会被混淆| 485 486HSP需要将给其他模块用的方法配置到白名单中。因为主模块里也需要配置相同的白名单,所以推荐将HSP配置了白名单的混淆文件(假设名称为hsp-white-list.txt)添加到依赖它的模块的混淆配置项里,即下图files字段里。 487 488 489 490**案例一:动态导入某个类,类定义处被混淆,导入类名时却没有混淆,导致报错** 491 492```ts 493// 混淆前 494// utils.ts 495export function add(a: number, b: number): number { 496 return a + b; 497} 498 499// main.ts 500async function loadAndUseAdd() { 501 try { 502 const mathUtils = await import('./utils'); 503 const result = mathUtils.add(2, 3); 504 } catch (error) { 505 console.error('Failure reason:', error); 506 } 507} 508 509loadAndUseAdd(); 510``` 511```ts 512 513// 混淆后 514// utils.ts 515export function c1(d1: number, e1: number): number { 516 return d1 + e1; 517} 518 519// main.ts 520async function i() { 521 try { 522 const a1 = await import("@normalized:N&&&entry/src/main/ets/pages/utils&"); 523 const b1 = a1.add(2, 3); 524 } 525 catch (z) { 526 console.error('Failure reason:', z); 527 } 528} 529i(); 530``` 531 532函数add在定义时位于顶层作用域,但通过.add访问时被视为属性。由于未开启-enable-property-obfuscation选项,导致add被使用时未进行混淆。 533 534**解决方案**: 535 536方案一:开启-enable-property-obfuscation选项。 537 538方案二:使用-keep-global-name选项将"add"配置到白名单。 539 540**案例二:在使用namespace中的方法时,该方法定义的地方被混淆了,但使用的地方却没有被混淆,导致报错** 541 542```ts 543// export.ts 544export namespace NS { 545 export function foo() {} 546} 547 548// import.ts 549import { NS } from './export'; 550 551NS.foo(); 552``` 553```ts 554// 混淆后 555// export.ts 556export namespace i { 557 export function j() {} 558} 559 560// import.ts 561import { i } from './export'; 562 563i.foo(); 564``` 565 566namespace中的foo属于export元素,当通过NS.foo调用时被视为属性。由于未开启-enable-property-obfuscation选项,导致foo在使用时未被混淆。 567 568**解决方案**: 569 5701. 开启-enable-property-obfuscation选项。 5712. 将namespace里导出的方法使用-keep-global-name选项添加到白名单。 572 573**案例三:使用了declare global,混淆后报语法错误** 574 575```ts 576// file.ts 577 578// 混淆前 579declare global { 580 var myAge : string 581} 582 583// 混淆后 584declare a2 { 585 var b2 : string 586} 587``` 588 589报错内容为SyntaxError: Unexpected token。 590 591**解决方案**: 592 593使用-keep-global-name选项将global配置到白名单中。 594 595从API version 18 开始,global 已加入系统的白名单,不需要开发者再使用 -keep-global-name 配置 596 597**案例四:使用Reflect.defineMetadata(),混淆后,提示找不到函数,导致程序异常** 598 599**问题现象**: 600 601在开启-enable-toplevel-obfuscation属性混淆后,字节码混淆时,混淆正常,运行时报错,错误日志: 602 603```txt 604Error message:is not callable 605Stacktrace:Cannot get SourceMap info, dump raw stack: at anonymous (ads_service|@hw-ads/ohos-ads-model|1.0.1|src/main/ets/annotations/FieldType.ts:6:1。 606``` 607 608```js 609// oh-package.json5 610"dependencies": { 611 "reflect-metadata": "0.2.1" 612} 613 614// test.ts 615import 'reflect-metadata'; 616 617// 调用代码 618export const FIELD_TYPE_KEY = Symbol('fieldType'); 619export function FieldType(...types: Function[]): PropertyDecorator { 620 return (target, key) => { 621 Reflect.defineMetadata(FIELD_TYPE_KEY, types, target, key); 622 }; 623} 624 625``` 626 627**问题分析**: 628 629在开启-enable-toplevel-obfuscation属性混淆后,Reflect文件中,函数名参与混淆,exporter函数中的字符串"defineMetadata"不参与混淆,导致外部使用Reflect.defineMetadata时,找不到对应函数。 630 631**解决方案**: 632 633使用-keep-global-name选项将defineMetadata配置到白名单中。由于Reflect文件中多次使用exporter,建议直接使用-keep选项。 634 635```txt 636-keep 637../xxx/xxx/xxx/Reflect.ts // 使用文件的相对路径 638``` 639 640### 未开启-enable-string-property-obfuscation混淆选项,字符串字面量属性名却被混淆,导致字符串字面量属性名的值为undefined 641 642```ts 643// file.ts 644// 混淆前 645const person = { 646 myAge: 18 647} 648person["myAge"] = 20; 649``` 650```ts 651// file.ts 652// 混淆后 653const person = { 654 myAge: 18 655} 656person["m"] = 20; 657``` 658 659**解决方案**: 660 6611. 确认是否有依赖的HAR包开启了字符串属性名混淆,若开启了,则会影响主工程,需将其关闭。 6622. 若不能关闭-enable-string-property-obfuscation选项,将属性名配置到白名单中。 6633. 若依赖HAR包未开启字符串属性名混淆,同时SDK版本小于4.1.5.3,请更新SDK。 664 665### 开启-enable-filename-obfuscation选项后,可能会出现的问题 666 667**案例一:报错为Error Failed to get a resolved OhmUrl for 'D:code/MyApplication/f12/library1/pages/d.ets' imported by 'undefined'** 668 669工程的目录结构如下图所示,模块library1的外层还有目录"directory",开启文件名混淆后,"directory" 被混淆为f12,导致路径找不到。 670 671 672 673**解决方案**: 674 6751. 如果工程的目录结构和报错内容都相似,请将SDK更新至最低5.0.0.26版本。 6762. 使用-keep-file-name将模块外层的目录名"directory"配置到白名单中。 677 678**案例二:报错为Cannot find module 'ets/appability/AppAbility' which is application Entry Point** 679 680由于系统会在应用运行时加载ability文件,用户需要手动配置相应的白名单,防止指定文件被混淆,导致运行失败。 681**解决方案**:使用-keep-file-name选项,将src/main/module.json5文件中,'srcEntry'字段所对应的路径配置到白名单中。 682 683```txt 684-keep-file-name 685appability 686AppAbility 687``` 688 689**HAP与HSP依赖相同的本地源码HAR模块,可能会出现的问题。** 690 691* 若开启文件名混淆,会出现以下问题: 692 **问题一**:单例功能异常问题。原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块在HAP与HSP的包中可能会出现相同的文件名被混淆成不同文件名的情况。 693 **问题二**:接口调用失败问题。原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块在HAP与HSP的包中可能会出现不同的文件名被混淆成相同的文件名的情况。 694* 若开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项,在应用运行时会出现加载接口失败的问题。 695 原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块中暴露的接口在HAP与HSP中被混淆成不同的名称。 696 697**解决方案**: 698 6991. 将HAP与HSP共同依赖的本地源码HAR改造为字节码HAR,这样此HAR在被依赖时不会被二次混淆。 7002. 将HAP与HSP共同依赖的本地源码HAR以release模式构建打包,这样此HAR在被依赖时,其文件名与对外接口不会被混淆。