Skip to content

Commit 7c0d8d9

Browse files
authored
Support dynamic registration for native methods using --dynamic-register (amimo#63)
* Add methods dynamic register supported
1 parent 3198bdc commit 7c0d8d9

File tree

6 files changed

+141
-18
lines changed

6 files changed

+141
-18
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# VSCode
2+
.vscode
3+
4+
# Test data
5+
data
6+
7+
# Cache
8+
__pycache__

dcc.py

+87-9
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,17 @@ def archive_compiled_code(project_dir):
339339
outfile = shutil.make_archive(outfile, 'zip', project_dir)
340340
return outfile
341341

342-
343-
def compile_dex(apkfile, filtercfg):
342+
def compile_dex(apkfile, filtercfg, dynamic_register):
344343
show_logging(level=logging.INFO)
345344

346345
d = auto_vm(apkfile)
347346
dx = analysis.Analysis(d)
348347

349348
method_filter = MethodFilter(filtercfg, d)
350349

351-
compiler = Dex2C(d, dx)
350+
compiler = Dex2C(d, dx, dynamic_register)
352351

352+
native_method_prototype = {}
353353
compiled_method_code = {}
354354
errors = []
355355

@@ -372,20 +372,85 @@ def compile_dex(apkfile, filtercfg):
372372
errors.append('%s:%s' % (full_name, str(e)))
373373
continue
374374

375-
if code:
376-
compiled_method_code[method_triple] = code
375+
if code[0]:
376+
compiled_method_code[method_triple] = code[0]
377+
native_method_prototype[jni_longname] = code[1]
377378

378-
return compiled_method_code, errors
379+
return compiled_method_code, native_method_prototype, errors
379380

380381
def is_apk(name):
381382
return name.endswith('.apk')
382383

383-
def dcc_main(apkfile, filtercfg, outapk, do_compile=True, project_dir=None, source_archive='project-source.zip'):
384+
def write_dummy_dynamic_register(project_dir):
385+
source_dir = os.path.join(project_dir, 'jni', 'nc')
386+
if not os.path.exists(source_dir):
387+
os.makedirs(source_dir)
388+
389+
filepath = os.path.join(source_dir, 'DynamicRegister.cpp')
390+
with open(filepath, 'w') as fp:
391+
fp.write('#include "DynamicRegister.h"\n\nconst char *dynamic_register_compile_methods(JNIEnv *env) { return nullptr; }')
392+
393+
def write_dynamic_register(project_dir, compiled_methods, method_prototypes):
394+
source_dir = os.path.join(project_dir, 'jni', 'nc')
395+
if not os.path.exists(source_dir):
396+
os.makedirs(source_dir)
397+
398+
export_list = {}
399+
400+
# Make export list
401+
for method_triple in sorted(compiled_methods.keys()):
402+
full_name = JniLongName(*method_triple)
403+
if not full_name in method_prototypes:
404+
raise Exception('Method %s prototype info could not be found' % full_name)
405+
406+
class_path = method_triple[0][1:-1].replace('.', '/')
407+
method_name = method_triple[1]
408+
method_signature = method_triple[2]
409+
method_native_name = full_name
410+
method_native_prototype = method_prototypes[full_name]
411+
412+
if not class_path in export_list:
413+
export_list[class_path] = [] # methods
414+
415+
export_list[class_path].append((method_name, method_signature, method_native_name, method_native_prototype))
416+
417+
if len(export_list) == 0:
418+
logger.info('No export methods')
419+
return
420+
421+
# Generate extern block and export block
422+
extern_block = []
423+
export_block = ['\njclass clazz;\n']
424+
export_block_template = 'clazz = env->FindClass("%s");\nif (clazz == nullptr)\n return "Class not found: %s";\n'
425+
export_block_template += 'const JNINativeMethod export_method_%d[] = {\n%s\n};\n'
426+
export_block_template += 'env->RegisterNatives(clazz, export_method_%d, %d);\n'
427+
export_block_template += 'env->DeleteLocalRef(clazz);\n'
428+
429+
for index, class_path in enumerate(sorted(export_list.keys())):
430+
methods = export_list[class_path]
431+
432+
extern_block.append('\n'.join(['extern %s;' % method[3] for method in methods]))
433+
434+
export_methods = ',\n'.join(['{"%s", "%s", (void *)%s}' % (method[0], method[1], method[2]) for method in methods])
435+
export_block.append(export_block_template % (class_path, class_path, index, export_methods, index, len(methods)))
436+
437+
export_block.append('return nullptr;\n')
438+
439+
# Write DynamicRegister.cpp
440+
filepath = os.path.join(source_dir, 'DynamicRegister.cpp')
441+
with open(filepath, 'w') as fp:
442+
fp.write('#include "DynamicRegister.h"\n\n')
443+
fp.write('\n'.join(extern_block))
444+
fp.write('\n\nconst char *dynamic_register_compile_methods(JNIEnv *env) {')
445+
fp.write('\n'.join(export_block))
446+
fp.write('}')
447+
448+
def dcc_main(apkfile, filtercfg, outapk, do_compile=True, project_dir=None, source_archive='project-source.zip', dynamic_register=False):
384449
if not os.path.exists(apkfile):
385450
logger.error("file %s is not exists", apkfile)
386451
return
387452

388-
compiled_methods, errors = compile_dex(apkfile, filtercfg)
453+
compiled_methods, method_prototypes, errors = compile_dex(apkfile, filtercfg, dynamic_register)
389454

390455
if errors:
391456
logger.warning('================================')
@@ -400,11 +465,22 @@ def dcc_main(apkfile, filtercfg, outapk, do_compile=True, project_dir=None, sour
400465
if not os.path.exists(project_dir):
401466
shutil.copytree('project', project_dir)
402467
write_compiled_methods(project_dir, compiled_methods)
468+
469+
if dynamic_register:
470+
write_dynamic_register(project_dir, compiled_methods, method_prototypes)
471+
else:
472+
write_dummy_dynamic_register(project_dir)
403473
else:
404474
project_dir = make_temp_dir('dcc-project-')
405475
shutil.rmtree(project_dir)
406476
shutil.copytree('project', project_dir)
407477
write_compiled_methods(project_dir, compiled_methods)
478+
479+
if dynamic_register:
480+
write_dynamic_register(project_dir, compiled_methods, method_prototypes)
481+
else:
482+
write_dummy_dynamic_register(project_dir)
483+
408484
src_zip = archive_compiled_code(project_dir)
409485
shutil.move(src_zip, source_archive)
410486

@@ -428,6 +504,7 @@ def dcc_main(apkfile, filtercfg, outapk, do_compile=True, project_dir=None, sour
428504
parser.add_argument('-o', '--out', nargs='?', help='Output APK file name')
429505
parser.add_argument('--sign', action='store_true', default=False, help='Sign apk')
430506
parser.add_argument('--filter', default='filter.txt', help='Method filter configure file')
507+
parser.add_argument('--dynamic-register', action='store_true', default=False, help='Export native methods using RegisterNatives')
431508
parser.add_argument('--no-build', action='store_true', default=False, help='Do not build the compiled code')
432509
parser.add_argument('--source-dir', help='The compiled cpp code output directory.')
433510
parser.add_argument('--project-archive', default='project-source.zip', help='Archive the project directory')
@@ -439,6 +516,7 @@ def dcc_main(apkfile, filtercfg, outapk, do_compile=True, project_dir=None, sour
439516
filtercfg = args['filter']
440517
do_compile = not args['no_build']
441518
source_archive = args['project_archive']
519+
dynamic_register = args['dynamic_register']
442520

443521
if args['source_dir']:
444522
project_dir = args['source_dir']
@@ -460,7 +538,7 @@ def dcc_main(apkfile, filtercfg, outapk, do_compile=True, project_dir=None, sour
460538
APKTOOL = dcc_cfg['apktool']
461539

462540
try:
463-
dcc_main(infile, filtercfg, outapk, do_compile, project_dir, source_archive)
541+
dcc_main(infile, filtercfg, outapk, do_compile, project_dir, source_archive, dynamic_register)
464542
except Exception as e:
465543
logger.error("Compile %s failed!" % infile, exc_info=True)
466544
finally:

dex2c/compiler.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,18 @@ def get_source(self):
101101
if self.writer:
102102
return str(self.writer)
103103
return ''
104+
105+
def get_prototype(self):
106+
if self.writer:
107+
return self.writer.get_prototype()
108+
return ''
104109

105110
def __repr__(self):
106111
return 'class IrMethod(object): %s' % self.name
107112

108113

109114
class IrBuilder(object):
110-
def __init__(self, methanalysis):
115+
def __init__(self, methanalysis, dynamic_register):
111116
method = methanalysis.get_method()
112117
self.method = method
113118
self.irmethod = None
@@ -118,6 +123,7 @@ def __init__(self, methanalysis):
118123
self.var_to_name = defaultdict()
119124
self.offset_to_node = {}
120125
self.graph = None
126+
self.dynamic_register = dynamic_register
121127

122128
self.access = util.get_access_method(method.get_access_flags())
123129

@@ -176,7 +182,7 @@ def process(self):
176182
irmethod.params = self.lparams
177183
irmethod.params_type = self.params_type
178184

179-
writer = Writer(irmethod)
185+
writer = Writer(irmethod, self.dynamic_register)
180186
writer.write_method()
181187
irmethod.writer = writer
182188
return irmethod
@@ -547,18 +553,19 @@ def process_and_show(self):
547553

548554

549555
class Dex2C:
550-
def __init__(self, vm, vmx):
556+
def __init__(self, vm, vmx, dynamic_register):
551557
self.vm = vm
552558
self.vmx = vmx
559+
self.dynamic_register = dynamic_register
553560

554561
def get_source_method(self, m):
555562
mx = self.vmx.get_method(m)
556-
z = IrBuilder(mx)
563+
z = IrBuilder(mx, self.dynamic_register)
557564
irmethod = z.process()
558565
if irmethod:
559-
return irmethod.get_source()
566+
return (irmethod.get_source(), irmethod.get_prototype())
560567
else:
561-
return None
568+
return (None, None)
562569

563570
def get_source_class(self, _class):
564571
c = DvClass(_class, self.vmx)

dex2c/writer.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ def get_name(self, item):
5252

5353

5454
class Writer(object):
55-
def __init__(self, irmethod):
55+
def __init__(self, irmethod, dynamic_register):
5656
self.graph = irmethod.graph
5757
self.method = irmethod.method
5858
self.irmethod = irmethod
5959
self.visited_nodes = set()
6060
self.buffer = []
61+
self.dynamic_register = dynamic_register
62+
self.prototype = []
6163

6264
entry = irmethod.entry
6365
self.ra = irmethod.ra
@@ -67,6 +69,9 @@ def __init__(self, irmethod):
6769

6870
def __str__(self):
6971
return ''.join(self.buffer)
72+
73+
def get_prototype(self):
74+
return ''.join(self.prototype)
7075

7176
def write_trace(self, ins):
7277
s = ins.dump()
@@ -98,7 +103,13 @@ def write_method(self):
98103

99104
self.write("\n/* %s->%s%s */\n" % (class_name, name, proto))
100105

101-
self.write('extern "C" JNIEXPORT %s JNICALL\n' % get_native_type(self.irmethod.rtype))
106+
if self.dynamic_register:
107+
self.write(get_native_type(self.irmethod.rtype) + ' ')
108+
self.prototype.append(get_native_type(self.irmethod.rtype) + ' ')
109+
self.prototype.append(jni_name)
110+
else:
111+
self.write('extern "C" JNIEXPORT %s JNICALL\n' % get_native_type(self.irmethod.rtype))
112+
102113
self.write(jni_name)
103114
params = self.irmethod.params
104115
if 'static' not in access:
@@ -110,8 +121,12 @@ def write_method(self):
110121
zip(self.irmethod.params_type, params)])
111122
if proto:
112123
self.write('(JNIEnv *env, jobject thiz, %s)' % proto)
124+
if self.dynamic_register:
125+
self.prototype.append('(JNIEnv *env, jobject thiz, %s)' % proto)
113126
else:
114127
self.write('(JNIEnv *env, jobject thiz)')
128+
if self.dynamic_register:
129+
self.prototype.append('(JNIEnv *env, jobject thiz)')
115130
self.write('{\n')
116131
nodes = self.irmethod.irblocks
117132
for node in nodes:
@@ -145,7 +160,7 @@ def visit_node(self, node):
145160
var_declared.add(r)
146161
self.write('%s v%s' % (get_cdecl_type(var_type), r))
147162
if util.is_ref(var_type):
148-
self.write(' = NULL');
163+
self.write(' = NULL')
149164
self.write(";\n")
150165

151166
if node.var_to_declare and self.irmethod.landing_pads:

project/jni/nc/Dex2C.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "ScopedLocalRef.h"
77
#include "ScopedPthreadMutexLock.h"
88
#include "well_known_classes.h"
9+
#include "DynamicRegister.h"
910

1011
struct MemberTriple {
1112
MemberTriple(const char *cls_name, const char *name, const char *sig):class_name_(cls_name), member_name_(name), signautre_(sig) {}
@@ -257,5 +258,11 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
257258
return JNI_ERR;
258259
}
259260
cache_well_known_classes(env);
261+
const char *result = dynamic_register_compile_methods(env);
262+
if (result != nullptr)
263+
{
264+
LOGD("d2c_throw_exception %s", result);
265+
return JNI_ERR;
266+
}
260267
return JNI_VERSION_1_6;
261268
}

project/jni/nc/DynamicRegister.h

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef _DYNAMIC_REGISTER_H_ // !_DYNAMIC_REGISTER_H_
2+
#define _DYNAMIC_REGISTER_H_
3+
4+
#include <jni.h>
5+
6+
const char *dynamic_register_compile_methods(JNIEnv *env);
7+
8+
#endif // !_DYNAMIC_REGISTER_H_

0 commit comments

Comments
 (0)