• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![bytecode-compilation-code-build](figures/bytecode-compilation-code-build.png) ![bytecode-compilation-build](figures/bytecode-compilation-build.png)
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/obf208-  混淆名称映射表及系统API白名单目录:build/default/[...]/release/obfuscation209
210![bytecode-build-product](figures/bytecode-build-product.png)
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![bytecode-buildoptionset](figures/bytecode-buildoptionset.png)
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![bytecode-directory](figures/bytecode-directory.png)
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在被依赖时,其文件名与对外接口不会被混淆。