1#!/usr/bin/python 2# 3# Copyright (C) 2023 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Parse ABI functions declarations and generate trampolines. 18 19Input files are C++ files with very limited syntax. They can contain function 20declarations and comments only. 21 22Function declaration should not specify parameter names, just parameter types. 23 24Comments should be C++-comments and they should appear before or after function 25declaration but not inside it. 26 27Special comments define attributes of the subsequent declaration: 28- // BERBERIS_CUSTOM_TRAMPOLINE(<name>) 29 This declaration already has a custom hand-coded trampoline <name>; 30 31Unrecognized comment discards all preceding attributes, so if declaration with 32attributes is commented out, its attributes are not attached to the next 33declaration. 34""" 35 36import json 37import sys 38 39 40_ARG = { 41 'JNIEnv *': { 42 'init': ' JNIEnv* arg_{index} = ToHostJNIEnv(arg_env);' 43 }, 44 '...': { 45 'init': """\ 46 std::vector<jvalue> arg_vector = ConvertVAList( 47 arg_0, arg_{arg_va_method_id}, GuestParamsValues<PFN_callee>(state)); 48 jvalue* arg_{index} = &arg_vector[0];""", 49 }, 50 # Note: we couldn't teach GuestParams class to distinguish Va_list because on 51 # ARM it's simply a char*, not a distint type. 52 'va_list': { 53 'init': """\ 54 std::vector<jvalue> arg_vector = ConvertVAList( 55 arg_0, arg_{arg_va_method_id}, ToGuestAddr(arg_va)); 56 jvalue* arg_{index} = &arg_vector[0];""", 57 }, 58} 59 60 61def _set_callee(decl): 62 param_types = decl['param_types'] 63 # TODO(eaeltsin): introduce attributes to 64 # - indicate this is a virtual method and not a global function 65 # - specify custom thunk 66 # - specify how to convert variable arguments list 67 # instead of the code below. 68 if 'jmethodID' in param_types and 'va_list' in param_types: 69 # NewObjectV, CallObjectMethodV, ... 70 assert decl['name'].endswith('V') 71 decl['callee'] = '(arg_0->functions)->%sA' % decl['name'][:-1] 72 decl['arg_va_method_id'] = param_types.index('jmethodID') 73 elif 'jmethodID' in param_types and '...' in param_types: 74 # NewObject, CallObjectMethod, ... 75 decl['callee'] = '(arg_0->functions)->%sA' % decl['name'] 76 decl['arg_va_method_id'] = param_types.index('jmethodID') 77 else: 78 decl['callee'] = '(arg_0->functions)->%s' % decl['name'] 79 80 81def _print_jni_call(out, args, decl): 82 # TODO(eaeltsin): implement printing using printf-style LOG_JNI! 83 pass 84 85 86def _print_jni_result(out, res, decl): 87 # TODO(eaeltsin): implement printing using printf-style LOG_JNI! 88 pass 89 90 91def _gen_trampoline(out, decl): 92 _set_callee(decl) 93 94 args = decl['param_types'] 95 96 if '...' in args: 97 assert args[-1] == '...' 98 arglist = ['arg_%d' %i for i in range(1, len(args) - 1)] 99 elif 'va_list' in args: 100 assert args[-1] == 'va_list' 101 arglist = ['arg_%d' %i for i in range(1, len(args) - 1)] + ['arg_va'] 102 else: 103 arglist = ['arg_%d' %i for i in range(1, len(args))] 104 assert args[0] == 'JNIEnv *' 105 decl['arglist'] = ', '.join(['arg_env'] + arglist) 106 107 print(""" 108void DoTrampoline_JNIEnv_{name}( 109 HostCode /* callee */, 110 ProcessState* state) {{ 111 using PFN_callee = decltype(std::declval<JNIEnv>().functions->{name}); 112 auto [{arglist}] = GuestParamsValues<PFN_callee>(state);""".format(**decl), file=out) 113 114 for i, type in enumerate(args): 115 if type in _ARG: 116 arg = _ARG[type] 117 print(arg['init'].format(index=i, **decl), file=out) 118 119 _print_jni_call(out, args, decl) 120 if decl['return_type'] == 'void': 121 print(' {callee}('.format(**decl), file=out) 122 else: 123 print(' auto&& [ret] = GuestReturnReference<PFN_callee>(state);', file=out) 124 print(' ret = {callee}('.format(**decl), file=out) 125 for i in range(len(args) - 1): 126 print(' arg_%d,' % i, file=out) 127 print(' arg_%d);' % (len(args) - 1), file=out) 128 if decl['return_type'] != 'void': 129 _print_jni_result(out, 'ret', decl) 130 131 print('}', file=out) 132 133 134def main(argv): 135 # Usage: gen_jni_trampolines.py <gen-header> <abi-def> 136 header_name = argv[1] 137 abi_def_name = argv[2] 138 139 with open(abi_def_name) as json_file: 140 decls = json.load(json_file) 141 142 out = open(header_name, 'w') 143 144 for decl in decls: 145 if 'trampoline' not in decl: 146 _gen_trampoline(out, decl) 147 148 print(""" 149void WrapJNIEnv(void* jni_env) { 150 HostCode* jni_vtable = *reinterpret_cast<HostCode**>(jni_env); 151 // jni_vtable[0] is NULL 152 // jni_vtable[1] is NULL 153 // jni_vtable[2] is NULL 154 // jni_vtable[3] is NULL""", file=out) 155 for i in range(len(decls)): 156 decl = decls[i] 157 print(""" 158 WrapHostFunctionImpl( 159 jni_vtable[%d], 160 DoTrampoline_JNIEnv_%s, 161 "JNIEnv::%s");""" % (4 + i, decl['name'], decl['name']), file=out) 162 print('}', file=out) 163 164 165if __name__ == '__main__': 166 sys.exit(main(sys.argv)) 167