├── 10 ├── option.go └── runtime.go ├── .gitignore ├── LICENSE ├── README.md ├── errors.go ├── extend ├── app.go ├── build.go ├── content.go ├── crypto.go ├── exception.go ├── extend.go ├── intent.go ├── io.go ├── network.go ├── preference.go ├── security.go ├── socket.go ├── stddef.go ├── system.go └── util.go ├── go.mod ├── internal ├── 10 │ ├── auxv.go │ ├── bionic.go │ ├── debugger.go │ ├── globals.go │ ├── linker.go │ ├── option.go │ ├── pthread.go │ ├── runtime.go │ ├── symbols.go │ └── tls.go ├── apk │ ├── empty.go │ ├── info.go │ ├── loader.go │ └── manifest.go ├── arm │ └── syscall.go ├── arm64 │ └── syscall.go ├── env.go ├── fs.go ├── jni │ └── jni.go ├── module.go ├── package.go ├── reloc.go ├── runtime.go ├── symbol.go ├── virtual │ ├── buffer.go │ ├── fs.go │ ├── info.go │ └── random.go ├── x86 │ └── syscall.go └── x86_64 │ └── syscall.go ├── java ├── array.go ├── boolean.go ├── byte.go ├── c.go ├── character.go ├── class.go ├── classes.go ├── double.go ├── factory.go ├── field.go ├── float.go ├── integer.go ├── iterable.go ├── jni.go ├── long.go ├── map.go ├── method.go ├── modifier.go ├── number.go ├── object.go ├── property.go ├── set.go ├── short.go └── string.go ├── jni.go ├── module.go ├── option.go ├── package.go ├── res ├── res.go ├── string.go ├── table.go └── xml.go ├── runtime.go ├── symbol.go └── wrapper └── fake.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | 5 | .DS_Store 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | # Go workspace file 23 | go.work 24 | go.work.sum 25 | go.sum 26 | 27 | # env file 28 | .env 29 | 30 | # vscode 31 | .vscode 32 | .vscode/ -------------------------------------------------------------------------------- /10/option.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | android "github.com/wnxd/microdbg-android" 5 | internal "github.com/wnxd/microdbg-android/internal/10" 6 | ) 7 | 8 | func WithApkPath(name string) android.Option { 9 | return internal.WithApkPath(name) 10 | } 11 | 12 | func WithRuntimeDir(name string) android.Option { 13 | return internal.WithRuntimeDir(name) 14 | } 15 | 16 | func WithRootDir(name string) android.Option { 17 | return internal.WithRootDir(name) 18 | } 19 | 20 | func WithJNIEnv(env android.JNIEnv) android.Option { 21 | return internal.WithJNIEnv(env) 22 | } 23 | -------------------------------------------------------------------------------- /10/runtime.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | android "github.com/wnxd/microdbg-android" 5 | android10 "github.com/wnxd/microdbg-android/internal/10" 6 | "github.com/wnxd/microdbg/emulator" 7 | ) 8 | 9 | func NewRuntime(emu emulator.Emulator, options ...android.Option) (android.Runtime, error) { 10 | return android10.NewRuntime(emu, options...) 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microdbg-android 2 | Allows you to emulate Android native libraries, based on MicroDbg 3 | 4 | # Thanks 5 | - [microdbg](https://github.com/wnxd/microdbg) 6 | - [unidbg](https://github.com/zhkl0228/unidbg) 7 | - [bionic](https://android.googlesource.com/platform/bionic.git) 8 | - [unicorn](https://github.com/unicorn-engine/unicorn) 9 | - [usercorn](https://github.com/lunixbochs/usercorn) 10 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrOptionUnsupported = errors.New("option unsupported") 7 | ErrMethodNotFound = errors.New("method not found") 8 | ) 9 | -------------------------------------------------------------------------------- /extend/app.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | gava "github.com/wnxd/microdbg-android/java" 5 | java "github.com/wnxd/microdbg-java" 6 | ) 7 | 8 | func (ex *extend) defineApp() { 9 | pkg := ex.art.Package() 10 | 11 | Application := ex.cf.DefineClass("android.app.Application", ex.cf.GetClass("android.content.ContextWrapper")) 12 | 13 | ActivityThread := ex.cf.DefineClass("android.app.ActivityThread") 14 | activityThread := ActivityThread.NewInstance() 15 | app := Application.NewInstance() 16 | ActivityThread.DefineMethod("currentActivityThread", "()Landroid/app/ActivityThread;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 17 | return activityThread 18 | }) 19 | ActivityThread.DefineMethod("currentPackageName", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 20 | return gava.FakeString(pkg.Name()) 21 | }) 22 | ActivityThread.DefineMethod("currentProcessName", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 23 | return gava.FakeString(pkg.Name()) 24 | }) 25 | ActivityThread.DefineMethod("currentApplication", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 26 | return app 27 | }) 28 | ActivityThread.DefineMethod("getApplication", "()Landroid/app/Application;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 29 | return app 30 | }) 31 | ActivityThread.DefineMethod("getProcessName", "()Ljava/lang/String;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 32 | return gava.FakeString(pkg.Name()) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /extend/build.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "strings" 7 | "unsafe" 8 | 9 | gava "github.com/wnxd/microdbg-android/java" 10 | java "github.com/wnxd/microdbg-java" 11 | "github.com/wnxd/microdbg/debugger" 12 | ) 13 | 14 | func (ex *extend) defineBuild() { 15 | var getprop = func(string) string { return "" } 16 | if libc, err := ex.art.FindModule("libc.so"); err != nil { 17 | } else if propGet, err := libc.FindSymbol("__system_property_get"); err != nil { 18 | } else if addr, err := ex.art.Debugger().MemAlloc(0x100); err == nil { 19 | defer ex.art.Debugger().MemFree(addr) 20 | var buf [0x100]byte 21 | emu := ex.art.Emulator() 22 | getprop = func(name string) string { 23 | var len int32 24 | err := propGet.Call(context.TODO(), debugger.Calling_Default, &len, name, uintptr(addr)) 25 | if err != nil { 26 | return "" 27 | } 28 | err = emu.MemReadPtr(addr, uint64(len), unsafe.Pointer(unsafe.SliceData(buf[:]))) 29 | if err != nil { 30 | return "" 31 | } 32 | return string(buf[:len]) 33 | } 34 | } 35 | 36 | Build := ex.cf.DefineClass("android.os.Build") 37 | Build.Set("BOARD", gava.FakeString(getprop("ro.product.board"))) 38 | Build.Set("BOOTLOADER", gava.FakeString(getprop("ro.bootloader"))) 39 | Build.Set("BRAND", gava.FakeString(getprop("ro.product.brand"))) 40 | Build.Set("SUPPORTED_32_BIT_ABIS", gava.ArrayOf(gava.FakeStringArrayClass, strings.Split(getprop("ro.product.cpu.abilist32"), ","))) 41 | Build.Set("SUPPORTED_64_BIT_ABIS", gava.ArrayOf(gava.FakeStringArrayClass, strings.Split(getprop("ro.product.cpu.abilist64"), ","))) 42 | abilist := strings.Split(getprop("ro.product.cpu.abilist"), ",") 43 | Build.Set("SUPPORTED_ABIS", gava.ArrayOf(gava.FakeStringArrayClass, abilist)) 44 | Build.Set("CPU_ABI", gava.FakeString(abilist[0])) 45 | if len(abilist) > 1 { 46 | Build.Set("CPU_ABI2", gava.FakeString(abilist[1])) 47 | } else { 48 | Build.Set("CPU_ABI2", gava.FakeString("")) 49 | } 50 | Build.Set("DEVICE", gava.FakeString(getprop("ro.product.device"))) 51 | Build.Set("DISPLAY", gava.FakeString(getprop("ro.build.display.id"))) 52 | Build.Set("FINGERPRINT", gava.FakeString(func() string { 53 | finger := getprop("ro.build.fingerprint") 54 | if finger == "" { 55 | finger = getprop("ro.product.brand") + "/" + 56 | getprop("ro.product.name") + "/" + 57 | getprop("ro.product.device") + ":" + 58 | getprop("ro.build.version.release") + "/" + 59 | getprop("ro.build.id") + "/" + 60 | getprop("ro.build.version.incremental") + ":" + 61 | getprop("ro.build.type") + "/" + 62 | getprop("ro.build.tags") 63 | } 64 | return finger 65 | }())) 66 | Build.Set("HARDWARE", gava.FakeString(getprop("ro.hardware"))) 67 | Build.Set("HOST", gava.FakeString(getprop("ro.build.host"))) 68 | Build.Set("ID", gava.FakeString(getprop("ro.build.id"))) 69 | Build.Set("MANUFACTURER", gava.FakeString(getprop("ro.product.manufacturer"))) 70 | Build.Set("MODEL", gava.FakeString(getprop("ro.product.model"))) 71 | Build.Set("ODM_SKU", gava.FakeString(getprop("ro.boot.product.hardware.sku"))) 72 | Build.Set("PRODUCT", gava.FakeString(getprop("ro.product.name"))) 73 | Build.Set("RADIO", gava.FakeString("unknown")) 74 | Build.Set("SERIAL", gava.FakeString(getprop("no.such.thing"))) 75 | Build.Set("SKU", gava.FakeString(getprop("ro.boot.hardware.sku"))) 76 | Build.Set("SOC_MANUFACTURER", gava.FakeString("unknown")) 77 | Build.Set("SOC_MODEL", gava.FakeString("unknown")) 78 | Build.Set("TAGS", gava.FakeString(getprop("ro.build.tags"))) 79 | Build.Set("TIME", java.JLong(must(strconv.Atoi(getprop("ro.build.date.utc")))*1000)) 80 | Build.Set("TYPE", gava.FakeString(getprop("ro.build.type"))) 81 | Build.Set("USER", gava.FakeString(getprop("ro.build.user"))) 82 | 83 | Build_VERSION := ex.cf.DefineClass("android.os.Build$VERSION") 84 | Build_VERSION.Set("BASE_OS", gava.FakeString(getprop("ro.build.version.base_os"))) 85 | Build_VERSION.Set("CODENAME", gava.FakeString(getprop("ro.build.version.codename"))) 86 | Build_VERSION.Set("INCREMENTAL", gava.FakeString(getprop("ro.build.version.incremental"))) 87 | Build_VERSION.Set("MEDIA_PERFORMANCE_CLASS", java.JInt(0)) 88 | Build_VERSION.Set("PREVIEW_SDK_INT", java.JInt(0)) 89 | Build_VERSION.Set("RELEASE", gava.FakeString(getprop("ro.build.version.release"))) 90 | Build_VERSION.Set("RELEASE_OR_CODENAME", gava.FakeString(getprop("ro.build.version.release_or_codename"))) 91 | Build_VERSION.Set("RELEASE_OR_PREVIEW_DISPLAY", gava.FakeString(getprop("ro.build.version.release_or_preview_display"))) 92 | sdk := getprop("ro.build.version.sdk") 93 | Build_VERSION.Set("SDK", gava.FakeString(sdk)) 94 | Build_VERSION.Set("SDK_INT", java.JInt(must(strconv.Atoi(sdk)))) 95 | Build_VERSION.Set("SDK_INT_FULL", java.JInt(must(strconv.Atoi(sdk)))) 96 | Build_VERSION.Set("SECURITY_PATCH", gava.FakeString(getprop("ro.build.version.security_patch"))) 97 | } 98 | 99 | func must[V, E any](r V, _ E) V { 100 | return r 101 | } 102 | -------------------------------------------------------------------------------- /extend/crypto.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "hash" 10 | 11 | gava "github.com/wnxd/microdbg-android/java" 12 | java "github.com/wnxd/microdbg-java" 13 | ) 14 | 15 | type secretKey struct { 16 | data java.IByteArray 17 | algorithm java.IString 18 | } 19 | 20 | type mac struct { 21 | hash.Hash 22 | h func() hash.Hash 23 | } 24 | 25 | func (ex *extend) defineCrypto() { 26 | SecretKey := ex.cf.DefineClass("javax.crypto.SecretKey", nil, ex.cf.GetClass("java.security.Key")) 27 | 28 | SecretKeySpec := ex.cf.DefineClass("javax.crypto.spec.SecretKeySpec", gava.FakeObjectClass, SecretKey) 29 | SecretKeySpec.DefineMethod(gava.ConstructorMethodName, "([BLjava/lang/String;)V", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 30 | return SecretKeySpec.NewObject(&secretKey{ 31 | data: args[0].(java.IByteArray), 32 | algorithm: args[1].(java.IString), 33 | }) 34 | }) 35 | SecretKeySpec.DefineMethod("getAlgorithm", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 36 | key := obj.(gava.FakeObject).Value().(*secretKey) 37 | return key.algorithm 38 | }) 39 | SecretKeySpec.DefineMethod("getEncoded", "()[B", gava.Modifier_PUBLIC|gava.Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 40 | key := obj.(gava.FakeObject).Value().(*secretKey) 41 | return key.data 42 | }) 43 | SecretKeySpec.DefineMethod("getFormat", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 44 | return gava.FakeString("RAW") 45 | }) 46 | 47 | Mac := ex.cf.DefineClass("javax.crypto.Mac") 48 | Mac.DefineMethod("getInstance", "(Ljava/lang/String;)Ljavax/crypto/Mac;", gava.Modifier_PUBLIC|gava.Modifier_STATIC|gava.Modifier_FINAL).BindCall(func(obj java.IObject, args ...any) any { 49 | algorithm := args[0].(java.IString).String() 50 | switch algorithm { 51 | case "HmacMD5": 52 | return Mac.NewObject(&mac{h: md5.New}) 53 | case "HmacSHA1": 54 | return Mac.NewObject(&mac{h: sha1.New}) 55 | case "HmacSHA224": 56 | return Mac.NewObject(&mac{h: sha256.New224}) 57 | case "HmacSHA256": 58 | return Mac.NewObject(&mac{h: sha256.New}) 59 | case "HmacSHA384": 60 | return Mac.NewObject(&mac{h: sha512.New384}) 61 | case "HmacSHA512": 62 | return Mac.NewObject(&mac{h: sha512.New}) 63 | } 64 | return nil 65 | }) 66 | Mac.DefineMethod("init", "(Ljava/security/Key;)V", gava.Modifier_PUBLIC|gava.Modifier_FINAL).BindCall(func(obj java.IObject, args ...any) any { 67 | mac := obj.(gava.FakeObject).Value().(*mac) 68 | key := args[0].(gava.FakeObject) 69 | mac.Hash = hmac.New(mac.h, gava.GetBytes(key.FindMethod("getEncoded", "()[B").CallPrimitive(key).(java.IByteArray))) 70 | return nil 71 | }) 72 | Mac.DefineMethod("reset", "()V", gava.Modifier_PUBLIC|gava.Modifier_FINAL).BindCall(func(obj java.IObject, args ...any) any { 73 | mac := obj.(gava.FakeObject).Value().(*mac) 74 | mac.Hash.Reset() 75 | return nil 76 | }) 77 | Mac.DefineMethod("update", "([B)V", gava.Modifier_PUBLIC|gava.Modifier_FINAL).BindCall(func(obj java.IObject, args ...any) any { 78 | mac := obj.(gava.FakeObject).Value().(*mac) 79 | mac.Hash.Write(gava.GetBytes(args[0].(java.IByteArray))) 80 | return nil 81 | }) 82 | Mac.DefineMethod("doFinal", "([B)[B", gava.Modifier_PUBLIC|gava.Modifier_FINAL).BindCall(func(obj java.IObject, args ...any) any { 83 | mac := obj.(gava.FakeObject).Value().(*mac) 84 | if args[0] != nil { 85 | mac.Hash.Write(gava.GetBytes(args[0].(java.IByteArray))) 86 | } 87 | return gava.BytesOf(mac.Hash.Sum(nil)) 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /extend/exception.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | func (ex *extend) defineException() { 4 | AndroidException := ex.cf.DefineClass("android.util.AndroidException") 5 | _ = AndroidException 6 | } 7 | -------------------------------------------------------------------------------- /extend/extend.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "sync" 5 | 6 | android "github.com/wnxd/microdbg-android" 7 | gava "github.com/wnxd/microdbg-android/java" 8 | "github.com/wnxd/microdbg/debugger" 9 | "github.com/wnxd/microdbg/socket" 10 | ) 11 | 12 | type Extend interface { 13 | EnableDebug() 14 | Network(handler Network) 15 | SystemProperties(handler SystemProps) 16 | SharedPreference(name string, handler SharedPreference) 17 | RegisterIntent(name string, handler Intent) 18 | } 19 | 20 | type extend struct { 21 | debugger.DefaultFileHandler 22 | debug bool 23 | art android.Runtime 24 | cf gava.ClassFactory 25 | net Network 26 | props SystemProps 27 | pref sync.Map 28 | intent sync.Map 29 | } 30 | 31 | func Define(art android.Runtime, cf gava.ClassFactory) Extend { 32 | ex := &extend{art: art, cf: cf} 33 | ex.defineSystem() 34 | ex.defineIO() 35 | ex.defineSecurity() 36 | ex.defineUtil() 37 | ex.defineCrypto() 38 | ex.defineException() 39 | ex.defineBuild() 40 | ex.defineContent() 41 | ex.defineApp() 42 | return ex 43 | } 44 | 45 | func (ex *extend) EnableDebug() { 46 | ex.debug = true 47 | } 48 | 49 | func (ex *extend) Network(handler Network) { 50 | ex.net = handler 51 | } 52 | 53 | func (ex *extend) SystemProperties(handler SystemProps) { 54 | ex.props = handler 55 | } 56 | 57 | func (ex *extend) SharedPreference(name string, handler SharedPreference) { 58 | ex.pref.Store(name, handler) 59 | } 60 | 61 | func (ex *extend) RegisterIntent(name string, handler Intent) { 62 | ex.intent.Store(name, handler) 63 | } 64 | 65 | func (ex *extend) NewSocket(network socket.Network) (socket.Socket, error) { 66 | return virtualSocket{ 67 | Socket: socket.New(network), 68 | ex: ex, 69 | }, nil 70 | } 71 | -------------------------------------------------------------------------------- /extend/intent.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "net/url" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type Intent interface { 10 | GetData() *url.URL 11 | GetBooleanExtra(name string, defValue java.JBoolean) java.JBoolean 12 | GetByteExtra(name string, defValue java.JByte) java.JByte 13 | GetCharExtra(name string, defValue java.JChar) java.JChar 14 | GetShortExtra(name string, defValue java.JShort) java.JShort 15 | GetIntExtra(name string, defValue java.JInt) java.JInt 16 | GetLongExtra(name string, defValue java.JLong) java.JLong 17 | GetFloatExtra(name string, defValue java.JFloat) java.JFloat 18 | GetDoubleExtra(name string, defValue java.JDouble) java.JDouble 19 | GetStringExtra(name string) java.IString 20 | } 21 | 22 | type DefaultIntent struct { 23 | } 24 | 25 | func (DefaultIntent) GetData() *url.URL { 26 | return nil 27 | } 28 | 29 | func (DefaultIntent) GetBooleanExtra(name string, defValue java.JBoolean) java.JBoolean { 30 | return defValue 31 | } 32 | 33 | func (DefaultIntent) GetByteExtra(name string, defValue java.JByte) java.JByte { 34 | return defValue 35 | } 36 | 37 | func (DefaultIntent) GetCharExtra(name string, defValue java.JChar) java.JChar { 38 | return defValue 39 | } 40 | 41 | func (DefaultIntent) GetShortExtra(name string, defValue java.JShort) java.JShort { 42 | return defValue 43 | } 44 | 45 | func (DefaultIntent) GetIntExtra(name string, defValue java.JInt) java.JInt { 46 | return defValue 47 | } 48 | 49 | func (DefaultIntent) GetLongExtra(name string, defValue java.JLong) java.JLong { 50 | return defValue 51 | } 52 | 53 | func (DefaultIntent) GetFloatExtra(name string, defValue java.JFloat) java.JFloat { 54 | return defValue 55 | } 56 | 57 | func (DefaultIntent) GetDoubleExtra(name string, defValue java.JDouble) java.JDouble { 58 | return defValue 59 | } 60 | 61 | func (DefaultIntent) GetStringExtra(name string) java.IString { 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /extend/io.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | gava "github.com/wnxd/microdbg-android/java" 7 | java "github.com/wnxd/microdbg-java" 8 | ) 9 | 10 | func (ex *extend) defineIO() { 11 | ex.art.Debugger().AddFileHandler(ex) 12 | 13 | File := ex.cf.DefineClass("java.io.File") 14 | File.DefineMethod("toString", "()Ljava/lang/String;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 15 | fake := obj.(gava.FakeObject) 16 | name := fake.Value().(string) 17 | return gava.FakeString(name) 18 | }) 19 | File.DefineMethod("getName", "()Ljava/lang/String;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 20 | fake := obj.(gava.FakeObject) 21 | name := fake.Value().(string) 22 | return gava.FakeString(filepath.Base(name)) 23 | }) 24 | File.DefineMethod("getPath", "()Ljava/lang/String;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 25 | fake := obj.(gava.FakeObject) 26 | name := fake.Value().(string) 27 | return gava.FakeString(name) 28 | }) 29 | File.DefineMethod("getAbsolutePath", "()Ljava/lang/String;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 30 | fake := obj.(gava.FakeObject) 31 | name := fake.Value().(string) 32 | return gava.FakeString(name) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /extend/network.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "net" 5 | "unsafe" 6 | ) 7 | 8 | const ( 9 | IFNAMSIZ = 16 10 | 11 | AF_INET = 2 12 | AF_INET6 = 10 13 | ) 14 | 15 | const ( 16 | IFF_UP = 1 << 0 17 | IFF_BROADCAST = 1 << 1 18 | IFF_DEBUG = 1 << 2 19 | IFF_LOOPBACK = 1 << 3 20 | IFF_POINTOPOINT = 1 << 4 21 | IFF_NOTRAILERS = 1 << 5 22 | IFF_RUNNING = 1 << 6 23 | IFF_NOARP = 1 << 7 24 | IFF_PROMISC = 1 << 8 25 | IFF_ALLMULTI = 1 << 9 26 | IFF_MASTER = 1 << 10 27 | IFF_SLAVE = 1 << 11 28 | IFF_MULTICAST = 1 << 12 29 | IFF_PORTSEL = 1 << 13 30 | IFF_AUTOMEDIA = 1 << 14 31 | IFF_DYNAMIC = 1 << 15 32 | IFF_LOWER_UP = 1 << 16 33 | IFF_DORMANT = 1 << 17 34 | IFF_ECHO = 1 << 18 35 | ) 36 | 37 | type Network interface { 38 | Interfaces() ([]NetworkInterface, error) 39 | InterfaceByIndex(index int) (*NetworkInterface, error) 40 | InterfaceByName(name string) (*NetworkInterface, error) 41 | } 42 | 43 | type NetworkInterface struct { 44 | net.Interface 45 | Addrs []net.Addr 46 | } 47 | 48 | type ifconf struct { 49 | ifc_len int32 50 | ifc_ifcu uintptr 51 | } 52 | 53 | type ifreq struct { 54 | ifr_ifrn [IFNAMSIZ]byte 55 | ifr_ifru [IFNAMSIZ]byte 56 | } 57 | 58 | type sockaddr struct { 59 | sa_family sa_family_t 60 | sa_data [14]byte 61 | } 62 | 63 | type sockaddr_in struct { 64 | sa_family sa_family_t 65 | sin_port uint16 66 | sin_addr [4]byte 67 | } 68 | 69 | func (req *ifreq) ifru_addr() *sockaddr { 70 | return (*sockaddr)(unsafe.Pointer(&req.ifr_ifru)) 71 | } 72 | 73 | func (req *ifreq) ifr_flags() *int16 { 74 | return (*int16)(unsafe.Pointer(&req.ifr_ifru)) 75 | } 76 | 77 | func (req *ifreq) ifr_ifindex() *int32 { 78 | return (*int32)(unsafe.Pointer(&req.ifr_ifru)) 79 | } 80 | -------------------------------------------------------------------------------- /extend/preference.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import java "github.com/wnxd/microdbg-java" 4 | 5 | type SharedPreference interface { 6 | Contains(key string) java.JBoolean 7 | GetBoolean(key string, defValue java.JBoolean) java.JBoolean 8 | GetFloat(key string, defValue java.JFloat) java.JFloat 9 | GetInt(key string, defValue java.JInt) java.JInt 10 | GetLong(key string, defValue java.JLong) java.JLong 11 | GetString(key string, defValue java.IString) java.IString 12 | } 13 | 14 | type SharedPreferenceEditor interface { 15 | Clear() 16 | SetBoolean(key string, value java.JBoolean) 17 | SetFloat(key string, value java.JFloat) 18 | SetInt(key string, value java.JInt) 19 | SetLong(key string, value java.JLong) 20 | SetString(key string, value java.IString) 21 | Remove(key string) 22 | } 23 | 24 | type DefaultPreference struct { 25 | } 26 | 27 | func (DefaultPreference) Contains(key string) java.JBoolean { 28 | return false 29 | } 30 | 31 | func (DefaultPreference) GetBoolean(key string, defValue java.JBoolean) java.JBoolean { 32 | return defValue 33 | } 34 | 35 | func (DefaultPreference) GetFloat(key string, defValue java.JFloat) java.JFloat { 36 | return defValue 37 | } 38 | 39 | func (DefaultPreference) GetInt(key string, defValue java.JInt) java.JInt { 40 | return defValue 41 | } 42 | 43 | func (DefaultPreference) GetLong(key string, defValue java.JLong) java.JLong { 44 | return defValue 45 | } 46 | 47 | func (DefaultPreference) GetString(key string, defValue java.IString) java.IString { 48 | return defValue 49 | } 50 | 51 | func (DefaultPreference) Clear() { 52 | } 53 | 54 | func (DefaultPreference) SetBoolean(key string, value java.JBoolean) { 55 | } 56 | 57 | func (DefaultPreference) SetFloat(key string, value java.JFloat) { 58 | } 59 | 60 | func (DefaultPreference) SetInt(key string, value java.JInt) { 61 | } 62 | 63 | func (DefaultPreference) SetLong(key string, value java.JLong) { 64 | } 65 | 66 | func (DefaultPreference) SetString(key string, value java.IString) { 67 | } 68 | 69 | func (DefaultPreference) Remove(key string) { 70 | } 71 | -------------------------------------------------------------------------------- /extend/security.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import gava "github.com/wnxd/microdbg-android/java" 4 | 5 | func (ex *extend) defineSecurity() { 6 | Key := ex.cf.DefineClass("java.security.Key") 7 | Key.SetModifiers(gava.Modifier_PUBLIC | gava.Modifier_INTERFACE | gava.Modifier_ABSTRACT) 8 | Key.DefineMethod("getAlgorithm", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_ABSTRACT) 9 | Key.DefineMethod("getEncoded", "()[B", gava.Modifier_PUBLIC|gava.Modifier_ABSTRACT) 10 | Key.DefineMethod("getFormat", "()Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_ABSTRACT) 11 | } 12 | -------------------------------------------------------------------------------- /extend/socket.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "iter" 5 | "net" 6 | "strings" 7 | "unsafe" 8 | 9 | "github.com/wnxd/microdbg/debugger" 10 | "github.com/wnxd/microdbg/socket" 11 | ) 12 | 13 | type virtualSocket struct { 14 | socket.Socket 15 | ex *extend 16 | } 17 | 18 | func (vs virtualSocket) Control(op int, arg any) error { 19 | const ( 20 | SIOCADDRT = 0x890B 21 | SIOCDELRT = 0x890C 22 | SIOCRTMSG = 0x890D 23 | SIOCGIFNAME = 0x8910 24 | SIOCSIFLINK = 0x8911 25 | SIOCGIFCONF = 0x8912 26 | SIOCGIFFLAGS = 0x8913 27 | ) 28 | 29 | if vs.ex.net != nil { 30 | switch op { 31 | case SIOCGIFNAME: 32 | return vs.gifname(arg) 33 | case SIOCGIFCONF: 34 | return vs.gifconf(arg) 35 | case SIOCGIFFLAGS: 36 | return vs.gifflags(arg) 37 | } 38 | } 39 | return vs.Socket.Control(op, arg) 40 | } 41 | 42 | func (vs virtualSocket) gifname(arg any) error { 43 | addr, ok := arg.(emuptr) 44 | if !ok { 45 | return debugger.ErrArgumentInvalid 46 | } 47 | dbg := vs.ex.art.Debugger() 48 | var req ifreq 49 | err := dbg.MemExtract(addr, &req) 50 | if err != nil { 51 | return err 52 | } 53 | ifi, err := vs.ex.net.InterfaceByIndex(int(*req.ifr_ifindex())) 54 | if err != nil { 55 | return err 56 | } 57 | n := copy(req.ifr_ifrn[:], []byte(ifi.Name)) 58 | if n != IFNAMSIZ { 59 | req.ifr_ifrn[n] = 0 60 | } 61 | dbg.MemWrite(addr, req) 62 | return nil 63 | } 64 | 65 | func (vs virtualSocket) gifconf(arg any) error { 66 | addr, ok := arg.(emuptr) 67 | if !ok { 68 | return debugger.ErrArgumentInvalid 69 | } 70 | dbg := vs.ex.art.Debugger() 71 | var conf ifconf 72 | err := dbg.MemExtract(addr, &conf) 73 | if err != nil { 74 | return err 75 | } 76 | ifs, err := vs.ex.net.Interfaces() 77 | if err != nil { 78 | return err 79 | } 80 | size := int32(IFNAMSIZ + IFNAMSIZ) 81 | if dbg.PointerSize() == 8 { 82 | size += 8 83 | } 84 | count := conf.ifc_len / size 85 | ptr := emuptr(conf.ifc_ifcu) 86 | var i int32 87 | for ifi, ip := range rangeInterfaces(ifs) { 88 | if i == count { 89 | break 90 | } else if ip = ip.To4(); ip == nil { //skip IPv6 91 | continue 92 | } 93 | var req ifreq 94 | copy(req.ifr_ifrn[:], []byte(ifi.Name)) 95 | in := (*sockaddr_in)(unsafe.Pointer(req.ifru_addr())) 96 | in.sa_family = AF_INET 97 | copy(in.sin_addr[:], ip) 98 | dbg.MemWrite(ptr, req) 99 | ptr += emuptr(size) 100 | i++ 101 | } 102 | conf.ifc_len = i * size 103 | dbg.MemWrite(addr, conf) 104 | return nil 105 | } 106 | 107 | func (vs virtualSocket) gifflags(arg any) error { 108 | addr, ok := arg.(emuptr) 109 | if !ok { 110 | return debugger.ErrArgumentInvalid 111 | } 112 | dbg := vs.ex.art.Debugger() 113 | var req ifreq 114 | err := dbg.MemExtract(addr, &req) 115 | if err != nil { 116 | return err 117 | } 118 | name := strings.TrimRight(string(req.ifr_ifrn[:]), "\x00") 119 | ifi, err := vs.ex.net.InterfaceByName(name) 120 | if err != nil { 121 | return err 122 | } 123 | flags := req.ifr_flags() 124 | if ifi.Flags&net.FlagUp != 0 { 125 | *flags |= IFF_UP 126 | } 127 | if ifi.Flags&net.FlagBroadcast != 0 { 128 | *flags |= IFF_BROADCAST 129 | } 130 | if ifi.Flags&net.FlagLoopback != 0 { 131 | *flags |= IFF_LOOPBACK 132 | } 133 | if ifi.Flags&net.FlagPointToPoint != 0 { 134 | *flags |= IFF_POINTOPOINT 135 | } 136 | if ifi.Flags&net.FlagMulticast != 0 { 137 | *flags |= IFF_MULTICAST 138 | } 139 | if ifi.Flags&net.FlagRunning != 0 { 140 | *flags |= IFF_RUNNING 141 | } 142 | dbg.MemWrite(addr, req) 143 | return nil 144 | } 145 | 146 | func rangeInterfaces(list []NetworkInterface) iter.Seq2[*NetworkInterface, net.IP] { 147 | return func(yield func(*NetworkInterface, net.IP) bool) { 148 | for i := range list { 149 | for _, addr := range list[i].Addrs { 150 | var ip net.IP 151 | if in, ok := addr.(*net.IPNet); ok { 152 | ip = in.IP 153 | } else if ia, ok := addr.(*net.IPAddr); ok { 154 | ip = ia.IP 155 | } 156 | if !yield(&list[i], ip) { 157 | return 158 | } 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /extend/stddef.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | type emuptr = uint64 4 | type emutptr[T any] emuptr 5 | 6 | type long_t int 7 | type ulong_t uint 8 | type size_t ulong_t 9 | type ssize_t long_t 10 | type time_t long_t 11 | type suseconds_t long_t 12 | type clockid_t int32 13 | type off_t long_t 14 | type dev_t ulong_t 15 | type ino_t ulong_t 16 | type mode_t uint32 17 | type nlink_t uint32 18 | type uid_t uint32 19 | type gid_t uint32 20 | type pid_t int32 21 | type sa_family_t uint16 22 | 23 | const emunullptr = emuptr(0) 24 | -------------------------------------------------------------------------------- /extend/system.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | 7 | gava "github.com/wnxd/microdbg-android/java" 8 | java "github.com/wnxd/microdbg-java" 9 | ) 10 | 11 | type SystemProps interface { 12 | Get(key string) (string, bool) 13 | } 14 | 15 | func (ex *extend) defineSystem() { 16 | getProperty := func(key string, defValue java.IString) java.IString { 17 | if ex.props == nil { 18 | } else if prop, ok := ex.props.Get(key); ok { 19 | return gava.FakeString(prop) 20 | } 21 | switch key { 22 | case "http.agent": 23 | return getHttpAgent(ex.cf) 24 | } 25 | if ex.debug { 26 | log.Printf("[%s] System property undefined\n", key) 27 | } 28 | return defValue 29 | } 30 | 31 | System := ex.cf.DefineClass("java.lang.System") 32 | System.DefineMethod("getProperty", "(Ljava/lang/String;)Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 33 | key := args[0].(java.IString).String() 34 | return getProperty(key, nil) 35 | }) 36 | System.DefineMethod("getProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 37 | key := args[0].(java.IString).String() 38 | defValue := gava.ToObject[java.IString](args[1]) 39 | return getProperty(key, defValue) 40 | }) 41 | } 42 | 43 | func getHttpAgent(cf gava.ClassFactory) java.IString { 44 | Build := cf.GetClass("android.os.Build") 45 | Build_VERSION := cf.GetClass("android.os.Build$VERSION") 46 | var result strings.Builder 47 | result.WriteString("Dalvik/2.1.0 (Linux; U; Android ") 48 | version := gava.ToObject[java.IString](Build_VERSION.GetField("RELEASE", "Ljava/lang/String;").Get(Build_VERSION)) 49 | if version == nil || version.Length() == 0 { 50 | result.WriteString("1.0") 51 | } else { 52 | result.WriteString(version.String()) 53 | } 54 | if gava.FakeString("REL").Equals(Build_VERSION.GetField("CODENAME", "Ljava/lang/String;").Get(Build_VERSION)) { 55 | model := gava.ToObject[java.IString](Build.GetField("MODEL", "Ljava/lang/String;").Get(Build)) 56 | if model != nil && model.Length() > 0 { 57 | result.WriteString("; ") 58 | result.WriteString(model.String()) 59 | } 60 | } 61 | id := gava.ToObject[java.IString](Build.GetField("ID", "Ljava/lang/String;").Get(Build)) 62 | if id != nil && id.Length() > 0 { 63 | result.WriteString(" Build/") 64 | result.WriteString(id.String()) 65 | } 66 | result.WriteByte(')') 67 | return gava.FakeString(result.String()) 68 | } 69 | -------------------------------------------------------------------------------- /extend/util.go: -------------------------------------------------------------------------------- 1 | package extend 2 | 3 | import ( 4 | "github.com/Xuanwo/go-locale" 5 | gava "github.com/wnxd/microdbg-android/java" 6 | java "github.com/wnxd/microdbg-java" 7 | "golang.org/x/text/language" 8 | ) 9 | 10 | func (ex *extend) defineUtil() { 11 | Locale := ex.cf.DefineClass("java.util.Locale") 12 | tag, _ := locale.Detect() 13 | defaultLocale := Locale.NewObject(tag) 14 | Locale.DefineMethod("getDefault", "()Ljava/util/Locale;", gava.Modifier_PUBLIC|gava.Modifier_STATIC).BindCall(func(obj java.IObject, args ...any) any { 15 | return defaultLocale 16 | }) 17 | Locale.DefineMethod("getLanguage", "()Ljava/lang/String;", gava.Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 18 | tag := obj.(gava.FakeObject).Value().(language.Tag) 19 | lang, _ := tag.Base() 20 | return gava.FakeString(lang.String()) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wnxd/microdbg-android 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/Xuanwo/go-locale v1.1.3 7 | github.com/google/uuid v1.6.0 8 | github.com/wnxd/microdbg v0.0.0-20250207073549-199b158d7a7d 9 | github.com/wnxd/microdbg-java v0.0.0-20250206041613-753119c42393 10 | github.com/wnxd/microdbg-linux v0.0.0-20250207074013-621bce751379 11 | github.com/wnxd/microdbg-loader v0.0.0-20250125085505-ff5705dfc5e6 12 | go.mozilla.org/pkcs7 v0.9.0 13 | golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 14 | golang.org/x/text v0.22.0 15 | ) 16 | 17 | require ( 18 | github.com/ebitengine/purego v0.8.2 // indirect 19 | github.com/go-ole/go-ole v1.3.0 // indirect 20 | github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect 21 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect 22 | github.com/shirou/gopsutil/v4 v4.25.1 // indirect 23 | github.com/tklauser/go-sysconf v0.3.14 // indirect 24 | github.com/tklauser/numcpus v0.9.0 // indirect 25 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 26 | golang.org/x/sys v0.30.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /internal/10/auxv.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | const ( 4 | AT_NULL = iota 5 | AT_IGNORE 6 | AT_EXECFD 7 | AT_PHDR 8 | AT_PHENT 9 | AT_PHNUM 10 | AT_PAGESZ 11 | AT_BASE 12 | AT_FLAGS 13 | AT_ENTRY 14 | AT_NOTELF 15 | AT_UID 16 | AT_EUID 17 | AT_GID 18 | AT_EGID 19 | AT_PLATFORM 20 | AT_HWCAP 21 | AT_CLKTCK 22 | AT_SECURE = 23 23 | AT_BASE_PLATFORM = 24 24 | AT_RANDOM = 25 25 | AT_HWCAP2 = 26 26 | AT_EXECFN = 31 27 | ) 28 | -------------------------------------------------------------------------------- /internal/10/bionic.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/wnxd/microdbg/emulator" 7 | ) 8 | 9 | type Pointer interface { 10 | ~uint32 | ~uint64 11 | } 12 | 13 | type fdTable[P Pointer] struct { 14 | version uint32 15 | error_level int32 16 | entries [128]uint64 17 | overflow P 18 | } 19 | 20 | type staticTlsLayout[P Pointer] struct { 21 | offset_ P 22 | alignment_ P 23 | overflowed_ bool 24 | offset_bionic_tcb_ P 25 | offset_bionic_tls_ P 26 | } 27 | 28 | type bionicSmallObjectAllocator[P Pointer] struct { 29 | type_ uint32 30 | block_size_ P 31 | blocks_per_page_ P 32 | free_pages_cnt_ P 33 | page_list_ P 34 | } 35 | 36 | type bionicAllocator[P Pointer] struct { 37 | allocators_ P 38 | allocators_buf_ [7]bionicSmallObjectAllocator[P] 39 | } 40 | 41 | type mntent[P Pointer] struct { 42 | mnt_fsname P 43 | mnt_dir P 44 | mnt_type P 45 | mnt_opts P 46 | mnt_freq int32 47 | mnt_passno int32 48 | } 49 | 50 | type group[P Pointer] struct { 51 | gr_name P 52 | gr_passwd P 53 | gr_gid uint32 54 | gr_mem P 55 | } 56 | 57 | type group_state[P Pointer] struct { 58 | group_ group[P] 59 | group_members_ [2]P 60 | group_name_buffer_ [32]byte 61 | getgrent_idx P 62 | } 63 | 64 | type tlsModules32 struct { 65 | generation uint32 66 | generation_libc_so uint32 67 | rwlock pthread_rwlock32 68 | module_count uint32 69 | module_table emulator.Uintptr32 70 | } 71 | 72 | type tlsModules64 struct { 73 | generation uint64 74 | generation_libc_so uint64 75 | rwlock pthread_rwlock64 76 | module_count uint64 77 | module_table emulator.Uintptr64 78 | } 79 | 80 | type passwd32 struct { 81 | pw_name emulator.Uintptr32 82 | pw_passwd emulator.Uintptr32 83 | pw_uid uint32 84 | pw_gid uint32 85 | pw_dir emulator.Uintptr32 86 | pw_shell emulator.Uintptr32 87 | } 88 | 89 | type passwd64 struct { 90 | pw_name emulator.Uintptr64 91 | pw_passwd emulator.Uintptr64 92 | pw_uid uint32 93 | pw_gid uint32 94 | pw_gecos emulator.Uintptr64 95 | pw_dir emulator.Uintptr64 96 | pw_shell emulator.Uintptr64 97 | } 98 | 99 | type passwd_state32 struct { 100 | passwd_ passwd32 101 | name_buffer_ [32]byte 102 | dir_buffer_ [32]byte 103 | sh_buffer_ [32]byte 104 | getpwent_idx int32 105 | } 106 | 107 | type passwd_state64 struct { 108 | passwd_ passwd64 109 | name_buffer_ [32]byte 110 | dir_buffer_ [32]byte 111 | sh_buffer_ [32]byte 112 | getpwent_idx int64 113 | } 114 | 115 | type bionicTls32 struct { 116 | key_data [130]pthread_key_data[uint32] 117 | locale emulator.Uintptr32 118 | basename_buf [4096]byte 119 | dirname_buf [4096]byte 120 | mntent_buf mntent[emulator.Uintptr32] 121 | mntent_strings [1024]byte 122 | ptsname_buf [32]byte 123 | ttyname_buf [64]byte 124 | strerror_buf [255]byte 125 | strsignal_buf [255]byte 126 | group group_state[emulator.Uintptr32] 127 | passwd passwd_state32 128 | } 129 | 130 | type bionicTls64 struct { 131 | key_data [130]pthread_key_data[uint64] 132 | locale emulator.Uintptr64 133 | basename_buf [4096]byte 134 | dirname_buf [4096]byte 135 | mntent_buf mntent[emulator.Uintptr64] 136 | mntent_strings [1024]byte 137 | ptsname_buf [32]byte 138 | ttyname_buf [64]byte 139 | strerror_buf [255]byte 140 | strsignal_buf [255]byte 141 | group group_state[emulator.Uintptr64] 142 | passwd passwd_state64 143 | } 144 | 145 | func (layout *staticTlsLayout[P]) reserve_bionic_tls() { 146 | if unsafe.Sizeof(P(0)) == 4 { 147 | layout.offset_bionic_tls_ = layout.reserve(P(unsafe.Sizeof(bionicTls32{})), P(unsafe.Alignof(bionicTls32{}))) 148 | } else { 149 | layout.offset_bionic_tls_ = layout.reserve(P(unsafe.Sizeof(bionicTls64{})), P(unsafe.Alignof(bionicTls64{}))) 150 | } 151 | } 152 | 153 | func (layout *staticTlsLayout[P]) reserve(size, alignment P) P { 154 | offset := align(layout.offset_, alignment) 155 | if offset < layout.offset_ { 156 | layout.overflowed_ = true 157 | } 158 | layout.offset_ = offset + size 159 | if layout.offset_ < offset { 160 | layout.overflowed_ = true 161 | } 162 | layout.alignment_ = max(layout.alignment_, alignment) 163 | return offset 164 | } 165 | -------------------------------------------------------------------------------- /internal/10/debugger.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io/fs" 7 | "sync" 8 | "unsafe" 9 | 10 | "github.com/wnxd/microdbg-android/internal" 11 | "github.com/wnxd/microdbg-android/internal/arm" 12 | "github.com/wnxd/microdbg-android/internal/arm64" 13 | "github.com/wnxd/microdbg-android/internal/x86" 14 | "github.com/wnxd/microdbg-android/internal/x86_64" 15 | linux "github.com/wnxd/microdbg-linux" 16 | kernel "github.com/wnxd/microdbg-linux/kernel" 17 | "github.com/wnxd/microdbg-loader/elf" 18 | "github.com/wnxd/microdbg/debugger" 19 | "github.com/wnxd/microdbg/debugger/extend" 20 | "github.com/wnxd/microdbg/emulator" 21 | "github.com/wnxd/microdbg/filesystem" 22 | ) 23 | 24 | type dbg struct { 25 | extend.ExtendDebugger 26 | *kernel.Kernel 27 | linker 28 | symbols 29 | nrMap map[uint64]linux.NR 30 | errModuels sync.Map 31 | } 32 | 33 | func newDbg(emu emulator.Emulator) (*dbg, error) { 34 | dbg, err := extend.New[*dbg](emu) 35 | if err != nil { 36 | return nil, err 37 | } 38 | releases := []func() error{dbg.Close} 39 | defer func() { 40 | for i := len(releases) - 1; i >= 0; i-- { 41 | releases[i]() 42 | } 43 | }() 44 | dbg.Kernel, err = kernel.NewKernel(dbg) 45 | if err != nil { 46 | return nil, err 47 | } 48 | releases = append(releases, dbg.Kernel.Close) 49 | err = dbg.linker.ctor(dbg) 50 | if err != nil { 51 | return nil, err 52 | } 53 | releases = append(releases, func() error { return dbg.linker.dtor(dbg) }) 54 | err = dbg.symbols.ctor(dbg, map[string]any{ 55 | "__loader_shared_globals": dbg.loader_shared_globals, 56 | "__loader_android_get_application_target_sdk_version": dbg.loader_android_get_application_target_sdk_version, 57 | "__loader_dlopen": dbg.loader_dlopen, 58 | "__loader_dlsym": dbg.loader_dlsym, 59 | }) 60 | if err != nil { 61 | return nil, err 62 | } 63 | releases = nil 64 | switch emu.Arch() { 65 | case emulator.ARCH_ARM: 66 | dbg.nrMap = arm.NRMap 67 | case emulator.ARCH_ARM64: 68 | dbg.nrMap = arm64.NRMap 69 | case emulator.ARCH_X86: 70 | dbg.nrMap = x86.NRMap 71 | case emulator.ARCH_X86_64: 72 | dbg.nrMap = x86_64.NRMap 73 | } 74 | return dbg, nil 75 | } 76 | 77 | func (dbg *dbg) Close() error { 78 | dbg.symbols.dtor() 79 | dbg.linker.dtor(dbg) 80 | dbg.Kernel.Close() 81 | return dbg.ExtendDebugger.Close() 82 | } 83 | 84 | func (dbg *dbg) FindModule(name string) (debugger.Module, error) { 85 | module, err := dbg.ExtendDebugger.FindModule(name) 86 | if err == nil { 87 | return module, nil 88 | } 89 | if err, ok := dbg.errModuels.Load(name); ok { 90 | return nil, err.(error) 91 | } 92 | var file filesystem.File 93 | switch dbg.Arch() { 94 | case emulator.ARCH_ARM, emulator.ARCH_X86: 95 | file, err = dbg.OpenFile("/system/lib/"+name, filesystem.O_RDONLY, 0) 96 | case emulator.ARCH_ARM64, emulator.ARCH_X86_64: 97 | file, err = dbg.OpenFile("/system/lib64/"+name, filesystem.O_RDONLY, 0) 98 | } 99 | if err != nil { 100 | dbg.errModuels.Store(name, debugger.ErrModuleNotFound) 101 | return nil, debugger.ErrModuleNotFound 102 | } 103 | module, err = dbg.loadModule(context.TODO(), file.(fs.File)) 104 | file.Close() 105 | if err != nil { 106 | err = errors.Join(debugger.ErrModuleNotFound, err) 107 | dbg.errModuels.Store(name, err) 108 | return nil, err 109 | } 110 | return module, nil 111 | } 112 | 113 | func (dbg *dbg) FindSymbol(name string) (debugger.Module, uint64, error) { 114 | addr, err := dbg.symbols.find(name) 115 | if err == nil { 116 | return debugger.InternalModule, addr, nil 117 | } 118 | return dbg.ExtendDebugger.FindSymbol(name) 119 | } 120 | 121 | func (dbg *dbg) NR(no uint64) linux.NR { 122 | return dbg.nrMap[no] 123 | } 124 | 125 | func (dbg *dbg) Errno() linux.Errno { 126 | if dbg.linker.errno.IsNil() { 127 | return 0 128 | } 129 | var err int32 130 | dbg.linker.errno.MemReadPtr(4, unsafe.Pointer(&err)) 131 | return linux.Errno(err) 132 | } 133 | 134 | func (dbg *dbg) SetErrno(err linux.Errno) { 135 | if dbg.linker.errno.IsNil() { 136 | return 137 | } 138 | dbg.linker.errno.MemWritePtr(4, unsafe.Pointer(&err)) 139 | } 140 | 141 | func (dbg *dbg) loadModule(ctx context.Context, file fs.File) (elf.Module, error) { 142 | module, err := elf.ImportFile(dbg, file) 143 | if err != nil { 144 | return nil, err 145 | } 146 | dbg.Load(module) 147 | internal.AndroidReloc(dbg, module) 148 | err = module.Init(ctx) 149 | if err != nil { 150 | dbg.Unload(module) 151 | module.Close() 152 | return nil, err 153 | } 154 | return module, nil 155 | } 156 | -------------------------------------------------------------------------------- /internal/10/globals.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import "github.com/wnxd/microdbg/emulator" 4 | 5 | type globals32 struct { 6 | fd_table fdTable[emulator.Uintptr32] 7 | initial_linker_arg_count int32 8 | auxv emulator.Uintptr32 9 | abort_msg_lock pthread_mutex32 10 | abort_msg emulator.Uintptr32 11 | static_tls_layout staticTlsLayout[uint32] 12 | tls_modules tlsModules32 13 | tls_allocator bionicAllocator[emulator.Uintptr32] 14 | init_progname emulator.Uintptr32 15 | init_environ emulator.Uintptr32 16 | } 17 | 18 | type globals64 struct { 19 | fd_table fdTable[emulator.Uintptr64] 20 | initial_linker_arg_count int32 21 | auxv emulator.Uintptr64 22 | abort_msg_lock pthread_mutex64 23 | abort_msg emulator.Uintptr64 24 | static_tls_layout staticTlsLayout[uint64] 25 | tls_modules tlsModules64 26 | tls_allocator bionicAllocator[emulator.Uintptr64] 27 | init_progname emulator.Uintptr64 28 | init_environ emulator.Uintptr64 29 | } 30 | -------------------------------------------------------------------------------- /internal/10/linker.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "context" 5 | "unsafe" 6 | 7 | "github.com/wnxd/microdbg-android/internal" 8 | "github.com/wnxd/microdbg/debugger" 9 | "github.com/wnxd/microdbg/emulator" 10 | emu_arm "github.com/wnxd/microdbg/emulator/arm" 11 | emu_arm64 "github.com/wnxd/microdbg/emulator/arm64" 12 | emu_x86 "github.com/wnxd/microdbg/emulator/x86" 13 | "golang.org/x/exp/constraints" 14 | ) 15 | 16 | var environ = []string{ 17 | "ANDROID_DATA=/data", 18 | "ANDROID_ROOT=/system", 19 | "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin", 20 | "NO_ADDR_COMPAT_LAYOUT_FIXUP=1", 21 | } 22 | 23 | type linker struct { 24 | mem emulator.MemRegion 25 | addr uint64 26 | progname uint64 27 | env []uint64 28 | auxv map[uint64]uint64 29 | bionicTls uint64 30 | errno emulator.Pointer 31 | globals uint64 32 | } 33 | 34 | func (l *linker) ctor(dbg debugger.Debugger) (err error) { 35 | l.mem, err = dbg.MapAlloc(0x2000, emulator.MEM_PROT_READ|emulator.MEM_PROT_WRITE) 36 | return 37 | } 38 | 39 | func (l *linker) dtor(dbg debugger.Debugger) error { 40 | if l.bionicTls != 0 { 41 | dbg.MemFree(l.bionicTls) 42 | } 43 | return dbg.MapFree(l.mem.Addr, l.mem.Size) 44 | } 45 | 46 | func (l *linker) init(art internal.Runtime) error { 47 | l.addr = l.mem.Addr 48 | err := l.initEnvironment(art) 49 | if err != nil { 50 | return err 51 | } 52 | err = l.initTLS(art) 53 | if err != nil { 54 | return err 55 | } 56 | return l.initSharedGlobals(art) 57 | } 58 | 59 | func (l *linker) pushData(emu emulator.Emulator, ptr unsafe.Pointer, size uint64) (uint64, error) { 60 | addr := l.addr 61 | err := emu.MemWritePtr(addr, size, ptr) 62 | if err != nil { 63 | return 0, err 64 | } 65 | l.addr += size 66 | return addr, nil 67 | } 68 | 69 | func (l *linker) pushString(emu emulator.Emulator, str string) (uint64, error) { 70 | addr, err := l.pushData(emu, unsafe.Pointer(unsafe.StringData(str)), uint64(len(str))) 71 | if err != nil { 72 | return 0, err 73 | } 74 | l.addr += 1 75 | return addr, nil 76 | } 77 | 78 | func (l *linker) initEnvironment(art internal.Runtime) error { 79 | emu := art.Emulator() 80 | processName := art.Package().Name() 81 | addr, err := l.pushString(emu, processName) 82 | if err != nil { 83 | return err 84 | } 85 | l.progname = addr 86 | for _, v := range environ { 87 | addr, err = l.pushString(emu, v) 88 | if err != nil { 89 | return err 90 | } 91 | l.env = append(l.env, addr) 92 | } 93 | l.auxv = make(map[uint64]uint64) 94 | l.auxv[AT_PAGESZ] = emu.PageSize() 95 | var random [16]byte 96 | addr, err = l.pushData(emu, unsafe.Pointer(unsafe.SliceData(random[:])), uint64(len(random))) 97 | if err != nil { 98 | return err 99 | } 100 | l.auxv[AT_RANDOM] = addr 101 | return nil 102 | } 103 | 104 | func (l *linker) initTLS(art internal.Runtime) error { 105 | dbg := art.Debugger() 106 | emu := art.Emulator() 107 | arch := emu.Arch() 108 | switch arch { 109 | case emulator.ARCH_ARM, emulator.ARCH_ARM64, emulator.ARCH_X86, emulator.ARCH_X86_64: 110 | default: 111 | return emulator.ErrArchUnsupported 112 | } 113 | task, err := dbg.GetMainTask(context.TODO()) 114 | if err != nil { 115 | return err 116 | } 117 | defer task.Close() 118 | var thread, stackGuard, pointerSize uint64 119 | switch arch { 120 | case emulator.ARCH_ARM, emulator.ARCH_X86: 121 | l.bionicTls, err = dbg.MemAlloc(0x2b44) 122 | if err != nil { 123 | return err 124 | } 125 | l.addr = align(l.addr, 8) 126 | t := pthread[emulator.Uintptr32]{tid: uint32(task.ID())} 127 | thread, err = l.pushData(emu, unsafe.Pointer(&t), uint64(unsafe.Sizeof(t))) 128 | if err != nil { 129 | return err 130 | } 131 | l.errno = dbg.ToPointer(thread).Add(0x260) 132 | pointerSize = 4 133 | case emulator.ARCH_ARM64, emulator.ARCH_X86_64: 134 | l.bionicTls, err = dbg.MemAlloc(0x2fa0) 135 | if err != nil { 136 | return err 137 | } 138 | l.addr = align(l.addr, 16) 139 | t := pthread[emulator.Uintptr64]{tid: uint32(task.ID())} 140 | thread, err = l.pushData(emu, unsafe.Pointer(&t), uint64(unsafe.Sizeof(t))) 141 | if err != nil { 142 | return err 143 | } 144 | l.errno = dbg.ToPointer(thread).Add(0x2B8) 145 | pointerSize = 8 146 | } 147 | stackGuard, err = l.pushData(emu, unsafe.Pointer(&stackGuard), pointerSize) 148 | if err != nil { 149 | return err 150 | } 151 | bionicTls := l.bionicTls 152 | var tls uint64 153 | switch arch { 154 | case emulator.ARCH_ARM, emulator.ARCH_ARM64: 155 | tls = l.mem.Addr + l.mem.Size - (ARM_MAX_TLS_SLOT * pointerSize) 156 | emu.MemWritePtr(tls+(unsigned(ARM_TLS_SLOT_BIONIC_TLS)*pointerSize), pointerSize, unsafe.Pointer(&bionicTls)) 157 | emu.MemWritePtr(tls+(ARM_TLS_SLOT_THREAD_ID*pointerSize), pointerSize, unsafe.Pointer(&thread)) 158 | emu.MemWritePtr(tls+(ARM_TLS_SLOT_STACK_GUARD*pointerSize), pointerSize, unsafe.Pointer(&stackGuard)) 159 | case emulator.ARCH_X86, emulator.ARCH_X86_64: 160 | tls = l.mem.Addr + l.mem.Size - (X86_MAX_TLS_SLOT * pointerSize) 161 | emu.MemWritePtr(tls+(X86_TLS_SLOT_BIONIC_TLS*pointerSize), pointerSize, unsafe.Pointer(&bionicTls)) 162 | emu.MemWritePtr(tls+(X86_TLS_SLOT_THREAD_ID*pointerSize), pointerSize, unsafe.Pointer(&thread)) 163 | emu.MemWritePtr(tls+(X86_TLS_SLOT_STACK_GUARD*pointerSize), pointerSize, unsafe.Pointer(&stackGuard)) 164 | } 165 | switch arch { 166 | case emulator.ARCH_ARM: 167 | task.Context().RegWrite(emu_arm.ARM_REG_C13_C0_3, tls) 168 | case emulator.ARCH_ARM64: 169 | task.Context().RegWrite(emu_arm64.ARM64_REG_TPIDR_EL0, tls) 170 | case emulator.ARCH_X86: 171 | task.Context().RegWrite(emu_x86.X86_REG_GS, tls) 172 | case emulator.ARCH_X86_64: 173 | task.Context().RegWrite(emu_x86.X86_REG_FS, tls) 174 | } 175 | return nil 176 | } 177 | 178 | func (l *linker) initSharedGlobals(art internal.Runtime) error { 179 | emu := art.Emulator() 180 | switch emu.Arch() { 181 | case emulator.ARCH_ARM, emulator.ARCH_X86: 182 | var globals globals32 183 | globals.auxv = emulator.Uintptr32(l.addr) 184 | for typ, val := range l.auxv { 185 | l.pushData(emu, unsafe.Pointer(&typ), 4) 186 | l.pushData(emu, unsafe.Pointer(&val), 4) 187 | } 188 | { 189 | null := uint32(AT_NULL) 190 | l.pushData(emu, unsafe.Pointer(&null), 4) 191 | l.pushData(emu, unsafe.Pointer(&null), 4) 192 | } 193 | globals.init_progname = emulator.Uintptr32(l.progname) 194 | globals.init_environ = emulator.Uintptr32(l.addr) 195 | for _, v := range l.env { 196 | l.pushData(emu, unsafe.Pointer(&v), 4) 197 | } 198 | { 199 | var null uint32 200 | l.pushData(emu, unsafe.Pointer(&null), 4) 201 | } 202 | globals.static_tls_layout.reserve_bionic_tls() 203 | l.addr = align(l.addr, 8) 204 | l.globals, _ = l.pushData(emu, unsafe.Pointer(&globals), uint64(unsafe.Sizeof(globals))) 205 | case emulator.ARCH_ARM64, emulator.ARCH_X86_64: 206 | var globals globals64 207 | globals.auxv = l.addr 208 | for typ, val := range l.auxv { 209 | l.pushData(emu, unsafe.Pointer(&typ), 8) 210 | l.pushData(emu, unsafe.Pointer(&val), 8) 211 | } 212 | { 213 | null := uint64(AT_NULL) 214 | l.pushData(emu, unsafe.Pointer(&null), 8) 215 | l.pushData(emu, unsafe.Pointer(&null), 8) 216 | } 217 | globals.init_progname = l.progname 218 | globals.init_environ = l.addr 219 | for _, v := range l.env { 220 | l.pushData(emu, unsafe.Pointer(&v), 8) 221 | } 222 | { 223 | var null uint64 224 | l.pushData(emu, unsafe.Pointer(&null), 8) 225 | } 226 | globals.static_tls_layout.reserve_bionic_tls() 227 | l.addr = align(l.addr, 16) 228 | l.globals, _ = l.pushData(emu, unsafe.Pointer(&globals), uint64(unsafe.Sizeof(globals))) 229 | } 230 | return nil 231 | } 232 | 233 | func unsigned(v int64) uint64 { 234 | return uint64(v) 235 | } 236 | 237 | func align[I constraints.Integer](a, b I) I { 238 | a += b - 1 239 | mask := -b 240 | a &= mask 241 | return a 242 | } 243 | -------------------------------------------------------------------------------- /internal/10/option.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | android "github.com/wnxd/microdbg-android" 5 | ) 6 | 7 | type ApkPathOption interface { 8 | setApkPath(string) error 9 | } 10 | 11 | type RuntimeDirOption interface { 12 | setRuntimeDir(string) error 13 | } 14 | 15 | type RootDirOption interface { 16 | setRootDir(string) error 17 | } 18 | 19 | type JNIEnvOption interface { 20 | setJNIEnv(android.JNIEnv) error 21 | } 22 | 23 | func WithApkPath(name string) android.Option { 24 | return func(art android.Runtime) error { 25 | if option, ok := art.(ApkPathOption); ok { 26 | return option.setApkPath(name) 27 | } 28 | return android.ErrOptionUnsupported 29 | } 30 | } 31 | 32 | func WithRuntimeDir(name string) android.Option { 33 | return func(art android.Runtime) error { 34 | if option, ok := art.(RuntimeDirOption); ok { 35 | return option.setRuntimeDir(name) 36 | } 37 | return android.ErrOptionUnsupported 38 | } 39 | } 40 | 41 | func WithRootDir(name string) android.Option { 42 | return func(art android.Runtime) error { 43 | if option, ok := art.(RootDirOption); ok { 44 | return option.setRootDir(name) 45 | } 46 | return android.ErrOptionUnsupported 47 | } 48 | } 49 | 50 | func WithJNIEnv(env android.JNIEnv) android.Option { 51 | return func(art android.Runtime) error { 52 | if option, ok := art.(JNIEnvOption); ok { 53 | return option.setJNIEnv(env) 54 | } 55 | return android.ErrOptionUnsupported 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/10/pthread.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | type pthread[P any] struct { 4 | next P 5 | prev P 6 | tid uint32 7 | _filling [30]P 8 | _buf [512]byte 9 | } 10 | 11 | type pthread_key_data[P any] struct { 12 | seq P 13 | data P 14 | } 15 | 16 | type pthread_mutex32 struct { 17 | __private [1]int32 18 | } 19 | 20 | type pthread_mutex64 struct { 21 | __private [10]int32 22 | } 23 | 24 | type pthread_rwlock32 struct { 25 | __private [10]int32 26 | } 27 | 28 | type pthread_rwlock64 struct { 29 | __private [14]int32 30 | } 31 | -------------------------------------------------------------------------------- /internal/10/runtime.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "archive/zip" 5 | "context" 6 | "io/fs" 7 | 8 | android "github.com/wnxd/microdbg-android" 9 | "github.com/wnxd/microdbg-android/internal" 10 | "github.com/wnxd/microdbg-android/internal/apk" 11 | "github.com/wnxd/microdbg/debugger" 12 | "github.com/wnxd/microdbg/emulator" 13 | "github.com/wnxd/microdbg/filesystem" 14 | ) 15 | 16 | type art struct { 17 | dbg *dbg 18 | hybrid internal.HybridFS 19 | internal.Environ 20 | } 21 | 22 | func NewRuntime(emu emulator.Emulator, options ...android.Option) (android.Runtime, error) { 23 | dbg, err := newDbg(emu) 24 | if err != nil { 25 | return nil, err 26 | } 27 | releases := []func() error{dbg.Close} 28 | defer func() { 29 | for i := len(releases) - 1; i >= 0; i-- { 30 | releases[i]() 31 | } 32 | }() 33 | r := &art{dbg: dbg} 34 | r.Environ.APK = apk.EmptyPackage 35 | err = r.hybrid.Ctor(dbg) 36 | if err != nil { 37 | return nil, err 38 | } 39 | releases = append(releases, func() error { return r.hybrid.Dtor(dbg) }) 40 | err = android.SetOption(r, options...) 41 | if err != nil { 42 | return nil, err 43 | } 44 | err = r.dbg.linker.init(r) 45 | if err != nil { 46 | return nil, err 47 | } 48 | err = r.Environ.Init(dbg) 49 | if err != nil { 50 | return nil, err 51 | } 52 | releases = nil 53 | return r, nil 54 | } 55 | 56 | func (r *art) Close() error { 57 | r.Environ.Close() 58 | r.hybrid.Dtor(r.dbg) 59 | return r.dbg.Close() 60 | } 61 | 62 | func (r *art) Debugger() debugger.Debugger { 63 | return r.dbg 64 | } 65 | 66 | func (r *art) Emulator() emulator.Emulator { 67 | return r.dbg.Emulator() 68 | } 69 | 70 | func (r *art) LoadModule(ctx context.Context, file fs.File) (android.Module, error) { 71 | module, err := r.dbg.loadModule(ctx, file) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return internal.ModuleOf(module, r), nil 76 | } 77 | 78 | func (r *art) FindModule(name string) (android.Module, error) { 79 | module, err := r.dbg.FindModule(name) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return internal.ModuleOf(module, r), nil 84 | } 85 | 86 | func (r *art) LinkFS(name string, handler filesystem.FS) error { 87 | return r.hybrid.Link(name, handler) 88 | } 89 | 90 | func (r *art) setApkPath(name string) error { 91 | info, err := apk.Load(name) 92 | if err != nil { 93 | return err 94 | } 95 | r.Environ.APK = info 96 | info.Link(r) 97 | return nil 98 | } 99 | 100 | func (r *art) setRuntimeDir(name string) error { 101 | fs, err := zip.OpenReader(name) 102 | if err != nil { 103 | return err 104 | } 105 | if r.hybrid.Base != nil { 106 | r.hybrid.Base.Close() 107 | } 108 | r.hybrid.Base = fs 109 | return nil 110 | } 111 | 112 | func (r *art) setRootDir(name string) error { 113 | r.hybrid.Sys = filesystem.SysDirFS(name) 114 | return nil 115 | } 116 | 117 | func (r *art) setJNIEnv(env android.JNIEnv) error { 118 | r.Environ.JNI = env 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /internal/10/symbols.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "github.com/wnxd/microdbg/debugger" 5 | ) 6 | 7 | type symbols struct { 8 | symbols map[string]debugger.ControlHandler 9 | } 10 | 11 | func (s *symbols) ctor(dbg debugger.Debugger, handler map[string]any) error { 12 | s.symbols = make(map[string]debugger.ControlHandler) 13 | var err error 14 | for name, fn := range handler { 15 | s.symbols[name], err = dbg.AddControl(s.handleSymbol, fn) 16 | if err != nil { 17 | s.dtor() 18 | return err 19 | } 20 | } 21 | return nil 22 | } 23 | 24 | func (s *symbols) dtor() error { 25 | for _, sym := range s.symbols { 26 | sym.Close() 27 | } 28 | s.symbols = nil 29 | return nil 30 | } 31 | 32 | func (s *symbols) find(name string) (uint64, error) { 33 | if sym, ok := s.symbols[name]; ok { 34 | return sym.Addr(), nil 35 | } 36 | return 0, debugger.ErrSymbolNotFound 37 | } 38 | 39 | func (s *symbols) handleSymbol(ctx debugger.Context, data any) { 40 | if fn, ok := data.(func(debugger.Context) any); ok { 41 | r := fn(ctx) 42 | ctx.RetWrite(r) 43 | } else if fn, ok := data.(func(debugger.Context)); ok { 44 | fn(ctx) 45 | } 46 | ctx.Return() 47 | } 48 | 49 | func (dbg *dbg) loader_shared_globals(ctx debugger.Context) any { 50 | return uintptr(dbg.linker.globals) 51 | } 52 | 53 | func (dbg *dbg) loader_android_get_application_target_sdk_version(ctx debugger.Context) any { 54 | const __ANDROID_API__ = 10000 55 | 56 | return int32(__ANDROID_API__) 57 | } 58 | 59 | func (dbg *dbg) loader_dlopen(ctx debugger.Context) any { 60 | var filename string 61 | ctx.ArgExtract(debugger.Calling_Default, &filename) 62 | module, err := dbg.FindModule(filename) 63 | if err != nil { 64 | return uintptr(0) 65 | } 66 | return uintptr(module.BaseAddr()) 67 | } 68 | 69 | func (dbg *dbg) loader_dlsym(ctx debugger.Context) any { 70 | var handle uintptr 71 | var symbol string 72 | ctx.ArgExtract(debugger.Calling_Default, &handle, &symbol) 73 | module := dbg.GetModule(uint64(handle)) 74 | if module == nil { 75 | return uintptr(0) 76 | } 77 | addr, err := module.FindSymbol(symbol) 78 | if err != nil { 79 | return uintptr(0) 80 | } 81 | return uintptr(addr) 82 | } 83 | -------------------------------------------------------------------------------- /internal/10/tls.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | const ( 4 | ARM_TLS_SLOT_BIONIC_TLS = iota - 1 5 | ARM_TLS_SLOT_DTV 6 | ARM_TLS_SLOT_THREAD_ID 7 | ARM_TLS_SLOT_APP 8 | ARM_TLS_SLOT_OPENGL 9 | ARM_TLS_SLOT_OPENGL_API 10 | ARM_TLS_SLOT_STACK_GUARD 11 | ARM_TLS_SLOT_SANITIZER 12 | ARM_TLS_SLOT_ART_THREAD_SELF 13 | 14 | ARM_MIN_TLS_SLOT = ARM_TLS_SLOT_BIONIC_TLS 15 | ARM_MAX_TLS_SLOT = ARM_TLS_SLOT_ART_THREAD_SELF 16 | 17 | ARM_BIONIC_TLS_SLOTS = ARM_MAX_TLS_SLOT - ARM_MIN_TLS_SLOT + 1 18 | ) 19 | 20 | const ( 21 | X86_TLS_SLOT_SELF = iota 22 | X86_TLS_SLOT_THREAD_ID 23 | X86_TLS_SLOT_APP 24 | X86_TLS_SLOT_OPENGL 25 | X86_TLS_SLOT_OPENGL_API 26 | X86_TLS_SLOT_STACK_GUARD 27 | X86_TLS_SLOT_SANITIZER 28 | X86_TLS_SLOT_ART_THREAD_SELF 29 | X86_TLS_SLOT_DTV 30 | X86_TLS_SLOT_BIONIC_TLS 31 | 32 | X86_MIN_TLS_SLOT = X86_TLS_SLOT_SELF 33 | X86_MAX_TLS_SLOT = X86_TLS_SLOT_BIONIC_TLS 34 | 35 | X86_BIONIC_TLS_SLOTS = X86_MAX_TLS_SLOT - X86_MIN_TLS_SLOT + 1 36 | ) 37 | -------------------------------------------------------------------------------- /internal/apk/empty.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import "io/fs" 4 | 5 | type emptyFS struct{} 6 | 7 | var EmptyPackage = (&info{fs: emptyFS{}, name: "microdbg"}).init() 8 | 9 | func (emptyFS) Close() error { 10 | return nil 11 | } 12 | 13 | func (emptyFS) Open(string) (fs.File, error) { 14 | return nil, fs.ErrNotExist 15 | } 16 | -------------------------------------------------------------------------------- /internal/apk/info.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | "encoding/base64" 7 | "io" 8 | "io/fs" 9 | "strings" 10 | 11 | "github.com/google/uuid" 12 | android "github.com/wnxd/microdbg-android" 13 | "github.com/wnxd/microdbg-android/internal/virtual" 14 | "github.com/wnxd/microdbg/emulator" 15 | "go.mozilla.org/pkcs7" 16 | ) 17 | 18 | type version struct { 19 | name string 20 | code int 21 | } 22 | 23 | type sdk struct { 24 | min int 25 | target int 26 | } 27 | 28 | type info struct { 29 | fs interface { 30 | io.Closer 31 | fs.FS 32 | } 33 | name string 34 | label string 35 | version version 36 | sdk sdk 37 | permission []string 38 | rdn string 39 | code string 40 | lib string 41 | files string 42 | } 43 | 44 | func (info *info) init() *info { 45 | b := [16]byte(uuid.New()) 46 | b64 := base64.URLEncoding.EncodeToString(b[:]) 47 | info.rdn = info.name + "-" + b64 48 | info.code = "/data/app/" + info.rdn + "/base.apk" 49 | info.files = "/data/data/" + info.name + "/files" 50 | return info 51 | } 52 | 53 | func (info *info) Close() error { 54 | return info.fs.Close() 55 | } 56 | 57 | func (info *info) Link(art android.Runtime) { 58 | switch art.Debugger().Arch() { 59 | case emulator.ARCH_ARM: 60 | info.lib = "/data/app/" + info.rdn + "/lib/arm" 61 | case emulator.ARCH_ARM64: 62 | info.lib = "/data/app/" + info.rdn + "/lib/arm64" 63 | case emulator.ARCH_X86: 64 | info.lib = "/data/app/" + info.rdn + "/lib/x86" 65 | case emulator.ARCH_X86_64: 66 | info.lib = "/data/app/" + info.rdn + "/lib/x86_64" 67 | } 68 | if lib, err := fs.Sub(info.fs, "lib/armeabi-v7a"); err == nil { 69 | art.LinkFS("/data/app/"+info.rdn+"/lib/arm", virtual.FS(lib)) 70 | } 71 | if lib, err := fs.Sub(info.fs, "lib/arm64-v8a"); err == nil { 72 | art.LinkFS("/data/app/"+info.rdn+"/lib/arm64", virtual.FS(lib)) 73 | } 74 | if lib, err := fs.Sub(info.fs, "lib/x86"); err == nil { 75 | art.LinkFS("/data/app/"+info.rdn+"/lib/x86", virtual.FS(lib)) 76 | } 77 | if lib, err := fs.Sub(info.fs, "lib/x86_64"); err == nil { 78 | art.LinkFS("/data/app/"+info.rdn+"/lib/x86_64", virtual.FS(lib)) 79 | } 80 | } 81 | 82 | func (info *info) Name() string { 83 | return info.name 84 | } 85 | 86 | func (info *info) Label() string { 87 | return info.label 88 | } 89 | 90 | func (info *info) Version() (string, int) { 91 | return info.version.name, info.version.code 92 | } 93 | 94 | func (info *info) UsesSdk() (int, int) { 95 | return info.sdk.min, info.sdk.target 96 | } 97 | 98 | func (info *info) Permission() []string { 99 | return info.permission 100 | } 101 | 102 | func (info *info) CodePath() string { 103 | return info.code 104 | } 105 | 106 | func (info *info) LibraryDir() string { 107 | return info.lib 108 | } 109 | 110 | func (info *info) FilesDir() string { 111 | return info.files 112 | } 113 | 114 | func (info *info) Certificate() []*x509.Certificate { 115 | var certs []*x509.Certificate 116 | fs.WalkDir(info.fs, "META-INF", func(path string, d fs.DirEntry, err error) error { 117 | if !strings.HasSuffix(path, ".RSA") && !strings.HasSuffix(path, ".DSA") { 118 | return err 119 | } 120 | file, err := info.fs.Open(path) 121 | if err != nil { 122 | return nil 123 | } 124 | data, err := io.ReadAll(file) 125 | if err != nil { 126 | return nil 127 | } 128 | p7, err := pkcs7.Parse(data) 129 | if err != nil { 130 | return nil 131 | } 132 | certs = append(certs, p7.Certificates...) 133 | return nil 134 | }) 135 | return certs 136 | } 137 | 138 | func (info *info) LoadModule(ctx context.Context, art android.Runtime, name string) (android.Module, error) { 139 | var path string 140 | switch art.Debugger().Arch() { 141 | case emulator.ARCH_ARM: 142 | path = "lib/armeabi-v7a/lib" + name + ".so" 143 | case emulator.ARCH_ARM64: 144 | path = "lib/arm64-v8a/lib" + name + ".so" 145 | case emulator.ARCH_X86: 146 | path = "lib/x86/lib" + name + ".so" 147 | case emulator.ARCH_X86_64: 148 | path = "lib/x86_64/lib" + name + ".so" 149 | default: 150 | return nil, emulator.ErrArchUnsupported 151 | } 152 | file, err := info.fs.Open(path) 153 | if err != nil { 154 | return nil, err 155 | } 156 | defer file.Close() 157 | return art.LoadModule(ctx, file) 158 | } 159 | -------------------------------------------------------------------------------- /internal/apk/loader.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import ( 4 | "archive/zip" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/wnxd/microdbg-android/internal" 9 | "github.com/wnxd/microdbg-android/res" 10 | ) 11 | 12 | func Load(name string) (internal.Package, error) { 13 | z, err := zip.OpenReader(name) 14 | if err != nil { 15 | return nil, err 16 | } 17 | return loadWithZip(z) 18 | } 19 | 20 | func loadWithZip(z *zip.ReadCloser) (internal.Package, error) { 21 | manifestFile, err := z.Open("AndroidManifest.xml") 22 | if err != nil { 23 | z.Close() 24 | return nil, err 25 | } 26 | defer manifestFile.Close() 27 | decoder, err := res.NewXMLDecoder(manifestFile) 28 | if err != nil { 29 | z.Close() 30 | return nil, err 31 | } 32 | var manifest Manifest 33 | err = decoder.Decode(&manifest) 34 | if err != nil { 35 | z.Close() 36 | return nil, err 37 | } 38 | resourcesFile, err := z.Open("resources.arsc") 39 | if err != nil { 40 | z.Close() 41 | return nil, err 42 | } 43 | defer resourcesFile.Close() 44 | resources, err := res.ParseTable(resourcesFile) 45 | if err != nil { 46 | z.Close() 47 | return nil, err 48 | } 49 | info := &info{ 50 | fs: z, 51 | name: manifest.Package, 52 | version: version{name: manifest.VersionName}, 53 | permission: make([]string, len(manifest.UsesPermission)), 54 | } 55 | info.version.code, _ = strconv.Atoi(manifest.VersionCode) 56 | info.sdk.min, _ = strconv.Atoi(manifest.UsesSdk.MinSdkVersion) 57 | info.sdk.target, _ = strconv.Atoi(manifest.UsesSdk.TargetSdkVersion) 58 | label := strings.TrimPrefix(manifest.Application.Label, "0x") 59 | id, _ := strconv.ParseInt(label, 16, 0) 60 | if value, ok := resources.Get(int(id)); ok { 61 | info.label = value.Value.(res.Value).String 62 | } 63 | for i := range manifest.UsesPermission { 64 | info.permission[i] = manifest.UsesPermission[i].Name 65 | } 66 | return info.init(), nil 67 | } 68 | -------------------------------------------------------------------------------- /internal/apk/manifest.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | type Manifest struct { 4 | CompileSdkVersion string `xml:"compileSdkVersion,attr"` 5 | CompileSdkVersionCodename string `xml:"compileSdkVersionCodename,attr"` 6 | PlatformBuildVersionName string `xml:"platformBuildVersionName,attr"` 7 | PlatformBuildVersionCode string `xml:"platformBuildVersionCode,attr"` 8 | Package string `xml:"package,attr"` 9 | VersionName string `xml:"versionName,attr"` 10 | VersionCode string `xml:"versionCode,attr"` 11 | 12 | UsesSdk UsesSdk `xml:"uses-sdk"` 13 | UsesPermission []UsesPermission `xml:"uses-permission"` 14 | Application Application `xml:"application"` 15 | } 16 | 17 | type UsesSdk struct { 18 | MinSdkVersion string `xml:"minSdkVersion,attr"` 19 | TargetSdkVersion string `xml:"targetSdkVersion,attr"` 20 | } 21 | 22 | type UsesPermission struct { 23 | Name string `xml:"name,attr"` 24 | } 25 | 26 | type Application struct { 27 | Label string `xml:"label,attr"` 28 | } 29 | -------------------------------------------------------------------------------- /internal/fs.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | "strings" 7 | 8 | "github.com/wnxd/microdbg-android/internal/virtual" 9 | "github.com/wnxd/microdbg/debugger" 10 | "github.com/wnxd/microdbg/filesystem" 11 | ) 12 | 13 | type HybridFS struct { 14 | debugger.DefaultFileHandler 15 | fs filesystem.VirtualFS 16 | Base interface { 17 | io.Closer 18 | fs.FS 19 | } 20 | Sys filesystem.DirFS 21 | } 22 | 23 | func (h *HybridFS) Ctor(dbg debugger.Debugger) error { 24 | h.fs = filesystem.NewVirtualFS() 25 | h.fs.Link("/dev/random", virtual.RandomFS("random")) 26 | h.fs.Link("/dev/urandom", virtual.RandomFS("urandom")) 27 | // h.fs.Link("/proc/self/exe", filesystem.SoftLink("/system/bin/app_process", nil)) 28 | dbg.AddFileHandler(h) 29 | return nil 30 | } 31 | 32 | func (h *HybridFS) Dtor(dbg debugger.Debugger) error { 33 | dbg.RemoveFileHandler(h) 34 | if h.Base != nil { 35 | h.Base.Close() 36 | } 37 | return nil 38 | } 39 | 40 | func (h *HybridFS) Open(name string) (fs.File, error) { 41 | return filesystem.Open(h, name) 42 | } 43 | 44 | func (h *HybridFS) OpenFile(name string, flag filesystem.FileFlag, perm fs.FileMode) (filesystem.File, error) { 45 | name = strings.TrimPrefix(name, "/") 46 | if h.Base != nil && flag == filesystem.O_RDONLY { 47 | file, err := h.Base.Open(name) 48 | if err == nil { 49 | return virtual.SeekerOf(file), nil 50 | } 51 | } 52 | if h.Sys != nil { 53 | file, err := h.Sys.OpenFile(name, flag, perm) 54 | if err == nil { 55 | return file, nil 56 | } 57 | } 58 | return h.fs.OpenFile(name, flag, perm) 59 | } 60 | 61 | func (h *HybridFS) Stat(name string) (fs.FileInfo, error) { 62 | name = strings.TrimPrefix(name, "/") 63 | if h.Base != nil { 64 | info, err := fs.Stat(h.Base, name) 65 | if err == nil { 66 | return info, nil 67 | } 68 | } 69 | if h.Sys != nil { 70 | info, err := fs.Stat(h.Sys, name) 71 | if err == nil { 72 | return info, nil 73 | } 74 | } 75 | return fs.Stat(h.fs, name) 76 | } 77 | 78 | func (h *HybridFS) ReadDir(name string) ([]fs.DirEntry, error) { 79 | name = strings.TrimPrefix(name, "/") 80 | if h.Base != nil { 81 | arr, err := fs.ReadDir(h.Base, name) 82 | if err == nil { 83 | return arr, nil 84 | } 85 | } 86 | if h.Sys != nil { 87 | arr, err := fs.ReadDir(h.Sys, name) 88 | if err == nil { 89 | return arr, nil 90 | } 91 | } 92 | return fs.ReadDir(h.fs, name) 93 | } 94 | 95 | func (h *HybridFS) Mkdir(name string, perm fs.FileMode) (filesystem.DirFS, error) { 96 | name = strings.TrimPrefix(name, "/") 97 | if h.Sys != nil { 98 | dir, err := h.Sys.Mkdir(name, perm) 99 | if err == nil { 100 | return dir, nil 101 | } 102 | } 103 | return h.fs.Mkdir(name, perm) 104 | } 105 | 106 | func (h *HybridFS) Readlink(name string) (string, error) { 107 | return h.fs.Readlink(name) 108 | } 109 | 110 | func (h *HybridFS) Link(name string, handle filesystem.FS) error { 111 | return h.fs.Link(name, handle) 112 | } 113 | -------------------------------------------------------------------------------- /internal/module.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "debug/elf" 6 | "io" 7 | "strings" 8 | 9 | android "github.com/wnxd/microdbg-android" 10 | "github.com/wnxd/microdbg-android/internal/jni" 11 | gava "github.com/wnxd/microdbg-android/java" 12 | java "github.com/wnxd/microdbg-java" 13 | loader "github.com/wnxd/microdbg-loader/elf" 14 | "github.com/wnxd/microdbg/debugger" 15 | "github.com/wnxd/microdbg/emulator" 16 | ) 17 | 18 | type module struct { 19 | debugger.Module 20 | art android.Runtime 21 | } 22 | 23 | func ModuleOf(m debugger.Module, art android.Runtime) android.Module { 24 | return &module{Module: m, art: art} 25 | } 26 | 27 | func (m *module) Close() error { 28 | m.art.Debugger().Unload(m.Module) 29 | return m.Module.Close() 30 | } 31 | 32 | func (m *module) FindSymbol(name string) (android.Symbol, error) { 33 | addr, err := m.Module.FindSymbol(name) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return NewSymbol(m.art.Debugger(), name, addr), nil 38 | } 39 | 40 | func (m *module) Symbols(yield func(android.Symbol) bool) { 41 | if it, ok := m.Module.(debugger.SymbolIter); ok { 42 | it.Symbols(func(sym debugger.Symbol) bool { 43 | return yield(NewSymbol(m.art.Debugger(), sym.Name, sym.Value)) 44 | }) 45 | } 46 | } 47 | 48 | func (m *module) CallEntry(ctx context.Context) error { 49 | sym := NewSymbol(m.art.Debugger(), "start", m.Module.EntryAddr()) 50 | return sym.Call(ctx, debugger.Calling_Default, nil) 51 | } 52 | 53 | func (m *module) CallOnLoad(ctx context.Context) (java.JInt, error) { 54 | sym, err := m.FindSymbol("JNI_OnLoad") 55 | if err != nil { 56 | return 0, err 57 | } 58 | var r java.JInt 59 | err = sym.Call(ctx, debugger.Calling_Default, &r, m.art.JavaVM(), nil) 60 | return r, err 61 | } 62 | 63 | func (m *module) FindNativeMethod(vm java.JavaVM, clazz java.IClass, name, sig string) (android.NativeMethod, error) { 64 | bind := func(method android.NativeMethod) android.NativeMethod { 65 | return method 66 | } 67 | fake, ok := clazz.(gava.FakeClass) 68 | if ok { 69 | method := fake.FindMethod(name, sig) 70 | if method != nil && gava.IsNative(method) { 71 | return method.CallPrimitive, nil 72 | } 73 | bind = func(method android.NativeMethod) android.NativeMethod { 74 | fake.DefineMethod(name, sig, gava.Modifier_NATIVE).BindCall(method) 75 | return method 76 | } 77 | } 78 | prefix := nameEscape(clazz, name) 79 | for sym := range m.Symbols { 80 | name := sym.Name() 81 | if !strings.HasPrefix(name, prefix) { 82 | continue 83 | } 84 | args := name[len(prefix):] 85 | if len(args) == 0 { 86 | _, ret, _ := strings.Cut(sig, ")") 87 | return bind(newNativeMethod(m.art.Debugger(), vm, sym.Address(), ret)), nil 88 | } else if !strings.HasPrefix(args, "__") { 89 | continue 90 | } 91 | arg, ret, _ := strings.Cut(sig, ")") 92 | if sigEscape(arg[1:]) == args[2:] { 93 | return bind(newNativeMethod(m.art.Debugger(), vm, sym.Address(), ret)), nil 94 | } 95 | } 96 | return nil, android.ErrMethodNotFound 97 | } 98 | 99 | func (m *module) Dump(w io.Writer) error { 100 | start, size := m.Module.Region() 101 | _, err := io.Copy(w, io.NewSectionReader(emulator.ToPointer(m.art.Emulator(), start), 0, int64(size))) 102 | return err 103 | } 104 | 105 | func AndroidReloc(dbg debugger.Debugger, module loader.Module) { 106 | const ( 107 | DT_ANDROID_REL = elf.DT_LOOS + 2 + iota 108 | DT_ANDROID_RELSZ 109 | DT_ANDROID_RELA 110 | DT_ANDROID_RELASZ 111 | ) 112 | 113 | sz := module.DynValue(DT_ANDROID_RELSZ) 114 | for i, v := range module.DynValue(DT_ANDROID_REL) { 115 | sr := io.NewSectionReader(dbg.ToPointer(module.BaseAddr()), int64(v), int64(sz[i])) 116 | var magic [4]byte 117 | sr.Read(magic[:]) 118 | if string(magic[:]) != "APS2" { 119 | continue 120 | } 121 | switch module.Class() { 122 | case elf.ELFCLASS32: 123 | androidRel32(module, sr) 124 | case elf.ELFCLASS64: 125 | androidRel64(module, sr) 126 | } 127 | } 128 | sz = module.DynValue(DT_ANDROID_RELASZ) 129 | for i, v := range module.DynValue(DT_ANDROID_RELA) { 130 | sr := io.NewSectionReader(dbg.ToPointer(module.BaseAddr()), int64(v), int64(sz[i])) 131 | var magic [4]byte 132 | sr.Read(magic[:]) 133 | if string(magic[:]) != "APS2" { 134 | continue 135 | } 136 | switch module.Class() { 137 | case elf.ELFCLASS32: 138 | androidRela32(module, sr) 139 | case elf.ELFCLASS64: 140 | androidRela64(module, sr) 141 | } 142 | } 143 | } 144 | 145 | func androidRel32(module loader.Module, r io.Reader) { 146 | for rela := range newRelocIter[uint32](r) { 147 | module.Reloc(elf.Rel32{Off: rela.Off, Info: rela.Info}) 148 | } 149 | } 150 | 151 | func androidRel64(module loader.Module, r io.Reader) { 152 | for rela := range newRelocIter[uint64](r) { 153 | module.Reloc(elf.Rel64{Off: rela.Off, Info: rela.Info}) 154 | } 155 | } 156 | 157 | func androidRela32(module loader.Module, r io.Reader) { 158 | for rela := range newRelocIter[uint32](r) { 159 | module.Reloc(elf.Rela32{Off: rela.Off, Info: rela.Info, Addend: int32(rela.Addend)}) 160 | } 161 | } 162 | 163 | func androidRela64(module loader.Module, r io.Reader) { 164 | for rela := range newRelocIter[uint64](r) { 165 | module.Reloc(elf.Rela64{Off: rela.Off, Info: rela.Info, Addend: int64(rela.Addend)}) 166 | } 167 | } 168 | 169 | func newNativeMethod(dbg debugger.Debugger, jvm java.JavaVM, addr uint64, returnType string) android.NativeMethod { 170 | vm, ok := jvm.(gava.FakeJavaVM) 171 | if !ok { 172 | return nil 173 | } 174 | sym := NewSymbol(dbg, "", addr) 175 | var cast func(env jni.JNIEnv, v gava.JValue) any 176 | switch returnType { 177 | case "Z": 178 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JBoolean() } 179 | case "B": 180 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JByte() } 181 | case "C": 182 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JChar() } 183 | case "S": 184 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JShort() } 185 | case "I": 186 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JInt() } 187 | case "J": 188 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JLong() } 189 | case "F": 190 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JFloat() } 191 | case "D": 192 | cast = func(env jni.JNIEnv, v gava.JValue) any { return v.JDouble() } 193 | case "V": 194 | cast = func(env jni.JNIEnv, v gava.JValue) any { return nil } 195 | default: 196 | cast = func(env jni.JNIEnv, v gava.JValue) any { return env.GetObject(v.JObject()) } 197 | } 198 | return func(obj java.IObject, args ...any) any { 199 | fake, err := vm.AttachJNIEnv(dbg) 200 | if err != nil { 201 | panic(err) 202 | } 203 | defer vm.DetachJNIEnv(fake) 204 | env := vm.GetJNIEnv(fake).(jni.JNIEnv) 205 | for i := range args { 206 | if obj, ok := args[i].(java.IObject); ok { 207 | args[i] = env.ObjectRef(obj) 208 | } 209 | } 210 | var r gava.JValue 211 | err = sym.Call(context.TODO(), debugger.Calling_Default, &r, append([]any{fake, env.ObjectRef(obj)}, args...)...) 212 | if err != nil { 213 | panic(err) 214 | } 215 | return cast(env, r) 216 | } 217 | } 218 | 219 | func nameEscape(clazz java.IClass, name string) string { 220 | cn := clazz.GetName().String() 221 | cn = strings.ReplaceAll(cn, "_", "_1") 222 | cn = strings.ReplaceAll(cn, ".", "_") 223 | name = strings.ReplaceAll(name, "_", "_1") 224 | return "Java_" + cn + "_" + name 225 | } 226 | 227 | func sigEscape(sig string) string { 228 | sig = strings.ReplaceAll(sig, ";", "_2") 229 | sig = strings.ReplaceAll(sig, "[", "_3") 230 | return sig 231 | } 232 | -------------------------------------------------------------------------------- /internal/package.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "io" 5 | 6 | android "github.com/wnxd/microdbg-android" 7 | ) 8 | 9 | type Package interface { 10 | io.Closer 11 | Link(android.Runtime) 12 | android.Package 13 | } 14 | -------------------------------------------------------------------------------- /internal/reloc.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "io" 5 | "iter" 6 | "unsafe" 7 | 8 | "golang.org/x/exp/constraints" 9 | ) 10 | 11 | type Rel[I constraints.Integer] struct { 12 | Off I 13 | Info I 14 | } 15 | 16 | type Rela[I constraints.Integer] struct { 17 | Rel[I] 18 | Addend I 19 | } 20 | 21 | func newRelocIter[I constraints.Integer](r io.Reader) iter.Seq[Rela[I]] { 22 | const ( 23 | RELOCATION_GROUPED_BY_INFO_FLAG = 1 << iota 24 | RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG 25 | RELOCATION_GROUPED_BY_ADDEND_FLAG 26 | RELOCATION_GROUP_HAS_ADDEND_FLAG 27 | ) 28 | 29 | var reloc Rela[I] 30 | count, err := leb128[I](r) 31 | if err != nil { 32 | return nil 33 | } 34 | reloc.Off, err = leb128[I](r) 35 | if err != nil { 36 | return nil 37 | } 38 | var index, groupIndex, groupSize, groupFlags, groupOffset I 39 | readGroup := func() (err error) { 40 | groupSize, err = leb128[I](r) 41 | if err != nil { 42 | return 43 | } 44 | groupFlags, err = leb128[I](r) 45 | if err != nil { 46 | return 47 | } 48 | if groupFlags&RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG != 0 { 49 | groupOffset, err = leb128[I](r) 50 | if err != nil { 51 | return 52 | } 53 | } 54 | if groupFlags&RELOCATION_GROUPED_BY_INFO_FLAG != 0 { 55 | reloc.Info, err = leb128[I](r) 56 | if err != nil { 57 | return 58 | } 59 | } 60 | switch groupFlags & (RELOCATION_GROUP_HAS_ADDEND_FLAG | RELOCATION_GROUPED_BY_ADDEND_FLAG) { 61 | case RELOCATION_GROUP_HAS_ADDEND_FLAG | RELOCATION_GROUPED_BY_ADDEND_FLAG: 62 | v, err := leb128[I](r) 63 | if err != nil { 64 | break 65 | } 66 | reloc.Addend += v 67 | case RELOCATION_GROUP_HAS_ADDEND_FLAG: 68 | reloc.Addend = 0 69 | } 70 | groupIndex = 0 71 | return nil 72 | } 73 | return func(yield func(Rela[I]) bool) { 74 | for index < count { 75 | if groupIndex == groupSize { 76 | if readGroup() != nil { 77 | break 78 | } 79 | } 80 | if groupFlags&RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG != 0 { 81 | reloc.Off += groupOffset 82 | } else { 83 | v, err := leb128[I](r) 84 | if err != nil { 85 | break 86 | } 87 | reloc.Off += v 88 | } 89 | if groupFlags&RELOCATION_GROUPED_BY_INFO_FLAG == 0 { 90 | v, err := leb128[I](r) 91 | if err != nil { 92 | break 93 | } 94 | reloc.Info = v 95 | } 96 | if groupFlags&(RELOCATION_GROUP_HAS_ADDEND_FLAG|RELOCATION_GROUPED_BY_ADDEND_FLAG) == RELOCATION_GROUP_HAS_ADDEND_FLAG { 97 | v, err := leb128[I](r) 98 | if err != nil { 99 | break 100 | } 101 | reloc.Addend += v 102 | } 103 | index++ 104 | groupIndex++ 105 | if !yield(reloc) { 106 | break 107 | } 108 | } 109 | } 110 | } 111 | 112 | func leb128[I constraints.Integer](r io.Reader) (value I, err error) { 113 | buf := [1]byte{128} 114 | shift := 0 115 | for ; buf[0]&128 != 0; shift += 7 { 116 | _, err = r.Read(buf[:]) 117 | if err != nil { 118 | return 119 | } 120 | value |= (I(buf[0] & 127)) << shift 121 | } 122 | if shift < (int(unsafe.Sizeof(value))*8) && buf[0]&64 != 0 { 123 | value |= -(I(1) << shift) 124 | } 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /internal/runtime.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | android "github.com/wnxd/microdbg-android" 5 | ) 6 | 7 | type Runtime interface { 8 | android.Runtime 9 | } 10 | -------------------------------------------------------------------------------- /internal/symbol.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | 6 | android "github.com/wnxd/microdbg-android" 7 | "github.com/wnxd/microdbg/debugger" 8 | ) 9 | 10 | type Symbol struct { 11 | dbg debugger.Debugger 12 | name string 13 | addr uint64 14 | } 15 | 16 | func NewSymbol(dbg debugger.Debugger, name string, addr uint64) android.Symbol { 17 | return &Symbol{dbg: dbg, name: name, addr: addr} 18 | } 19 | 20 | func (sym Symbol) Name() string { 21 | return sym.name 22 | } 23 | 24 | func (sym Symbol) Address() uint64 { 25 | return sym.addr 26 | } 27 | 28 | func (sym Symbol) Call(ctx context.Context, calling debugger.Calling, ret any, args ...any) error { 29 | task, err := sym.dbg.GetMainTask(ctx) 30 | if err != nil { 31 | task, err = sym.dbg.CreateTask(ctx) 32 | if err != nil { 33 | return err 34 | } 35 | } 36 | defer task.Close() 37 | return sym.call(task, calling, ret, args...) 38 | } 39 | 40 | func (sym Symbol) call(task debugger.Task, calling debugger.Calling, ret any, args ...any) error { 41 | taskCtx := task.Context() 42 | taskCtx.ArgWrite(calling, args...) 43 | err := sym.dbg.CallTaskOf(task, sym.addr) 44 | if err != nil { 45 | return err 46 | } 47 | err = task.SyncRun() 48 | if err != nil { 49 | return err 50 | } 51 | return taskCtx.RetExtract(ret) 52 | } 53 | -------------------------------------------------------------------------------- /internal/virtual/buffer.go: -------------------------------------------------------------------------------- 1 | package virtual 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "io/fs" 8 | ) 9 | 10 | type SeekerFile interface { 11 | fs.File 12 | io.Seeker 13 | } 14 | 15 | type bufferFile struct { 16 | fs.File 17 | s []byte 18 | i int64 19 | } 20 | 21 | func SeekerOf(f fs.File) SeekerFile { 22 | if s, ok := f.(SeekerFile); ok { 23 | return s 24 | } 25 | return &bufferFile{File: f} 26 | } 27 | 28 | func (buf *bufferFile) Read(b []byte) (int, error) { 29 | n := copy(b, buf.s[buf.i:]) 30 | if n == len(b) { 31 | return n, nil 32 | } 33 | x, err := buf.File.Read(b[n:]) 34 | x += n 35 | if err == nil { 36 | buf.s = append(buf.s, b[n:x]...) 37 | buf.i += int64(x) 38 | } 39 | return x, err 40 | } 41 | 42 | func (buf *bufferFile) Seek(offset int64, whence int) (int64, error) { 43 | switch whence { 44 | case io.SeekStart: 45 | buf.i = offset 46 | case io.SeekCurrent: 47 | buf.i += offset 48 | case io.SeekEnd: 49 | b := bytes.NewBuffer(buf.s) 50 | _, err := io.Copy(b, buf.File) 51 | if err != nil { 52 | return 0, err 53 | } 54 | buf.i = int64(b.Len()) + offset 55 | buf.s = b.Bytes() 56 | default: 57 | return 0, errors.New("buffer.Seek: invalid whence") 58 | } 59 | if buf.i < 0 { 60 | return 0, errors.New("buffer.Seek: negative position") 61 | } 62 | n := int64(len(buf.s)) 63 | if buf.i <= n { 64 | return buf.i, nil 65 | } 66 | buf.s = append(buf.s, make([]byte, buf.i-n)...) 67 | x, err := buf.File.Read(buf.s[n:]) 68 | if err != nil { 69 | return 0, err 70 | } 71 | buf.i = n + int64(x) 72 | buf.s = buf.s[:buf.i] 73 | return buf.i, nil 74 | } 75 | -------------------------------------------------------------------------------- /internal/virtual/fs.go: -------------------------------------------------------------------------------- 1 | package virtual 2 | 3 | import ( 4 | "errors" 5 | "io/fs" 6 | 7 | "github.com/wnxd/microdbg/filesystem" 8 | ) 9 | 10 | type vfs struct { 11 | fs fs.FS 12 | } 13 | 14 | func FS(fs fs.FS) filesystem.FS { 15 | return vfs{fs: fs} 16 | } 17 | 18 | func (f vfs) Sub(dir string) (fs.FS, error) { 19 | if sub, ok := f.fs.(fs.SubFS); ok { 20 | return sub.Sub(dir) 21 | } 22 | return nil, errors.ErrUnsupported 23 | } 24 | 25 | func (f vfs) Open(name string) (fs.File, error) { 26 | return filesystem.Open(f, name) 27 | } 28 | 29 | func (f vfs) OpenFile(name string, flag filesystem.FileFlag, perm fs.FileMode) (filesystem.File, error) { 30 | if flag != filesystem.O_RDONLY { 31 | return nil, fs.ErrPermission 32 | } 33 | return f.fs.Open(name) 34 | } 35 | 36 | func (f vfs) ReadDir(name string) ([]fs.DirEntry, error) { 37 | if dir, ok := f.fs.(fs.ReadDirFS); ok { 38 | return dir.ReadDir(name) 39 | } 40 | return nil, errors.ErrUnsupported 41 | } 42 | 43 | func (f vfs) Mkdir(name string, perm fs.FileMode) (filesystem.DirFS, error) { 44 | if dir, ok := f.fs.(filesystem.DirFS); ok { 45 | return dir.Mkdir(name, perm) 46 | } 47 | return nil, errors.ErrUnsupported 48 | } 49 | 50 | func (f vfs) Readlink(name string) (string, error) { 51 | if dir, ok := f.fs.(filesystem.ReadlinkFS); ok { 52 | return dir.Readlink(name) 53 | } 54 | return "", errors.ErrUnsupported 55 | } 56 | -------------------------------------------------------------------------------- /internal/virtual/info.go: -------------------------------------------------------------------------------- 1 | package virtual 2 | 3 | import ( 4 | "io/fs" 5 | "time" 6 | ) 7 | 8 | type fileInfo struct { 9 | name string 10 | size int64 11 | mode fs.FileMode 12 | modTime time.Time 13 | } 14 | 15 | func (fi fileInfo) Name() string { 16 | return fi.name 17 | } 18 | 19 | func (fi fileInfo) Size() int64 { 20 | return fi.size 21 | } 22 | 23 | func (fi fileInfo) Mode() fs.FileMode { 24 | return fi.mode 25 | } 26 | 27 | func (fi fileInfo) ModTime() time.Time { 28 | return fi.modTime 29 | } 30 | 31 | func (fi fileInfo) IsDir() bool { 32 | return fi.mode.IsDir() 33 | } 34 | 35 | func (fi fileInfo) Sys() any { 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/virtual/random.go: -------------------------------------------------------------------------------- 1 | package virtual 2 | 3 | import ( 4 | "crypto/rand" 5 | "io/fs" 6 | 7 | "github.com/wnxd/microdbg/filesystem" 8 | ) 9 | 10 | type RandomFS string 11 | 12 | func (f RandomFS) Open(name string) (fs.File, error) { 13 | return filesystem.Open(f, name) 14 | } 15 | 16 | func (f RandomFS) OpenFile(name string, flag filesystem.FileFlag, perm fs.FileMode) (filesystem.File, error) { 17 | return f, nil 18 | } 19 | 20 | func (f RandomFS) Close() error { 21 | return nil 22 | } 23 | 24 | func (f RandomFS) Stat() (fs.FileInfo, error) { 25 | return &fileInfo{name: string(f), mode: 0666}, nil 26 | } 27 | 28 | func (f RandomFS) Read(b []byte) (int, error) { 29 | n := min(len(b), 256) 30 | return rand.Read(b[:n]) 31 | } 32 | -------------------------------------------------------------------------------- /java/array.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "slices" 7 | "strconv" 8 | "unsafe" 9 | 10 | java "github.com/wnxd/microdbg-java" 11 | ) 12 | 13 | type FakeZArray = fakeArray[java.JBoolean] 14 | type FakeBArray = fakeArray[java.JByte] 15 | type FakeCArray = fakeArray[java.JChar] 16 | type FakeSArray = fakeArray[java.JShort] 17 | type FakeIArray = fakeArray[java.JInt] 18 | type FakeJArray = fakeArray[java.JLong] 19 | type FakeFArray = fakeArray[java.JFloat] 20 | type FakeDArray = fakeArray[java.JDouble] 21 | 22 | type fakeArray[V comparable] []V 23 | 24 | type fakeObjectArray struct { 25 | fakeArray[java.IObject] 26 | cls java.IClass 27 | } 28 | 29 | func (arr fakeArray[V]) HashCode() java.JInt { 30 | ptr := (*struct{ data unsafe.Pointer })(unsafe.Pointer((&arr))).data 31 | return int32(uintptr(ptr)) 32 | } 33 | 34 | func (arr fakeArray[V]) Equals(obj java.IObject) java.JBoolean { 35 | if other, ok := obj.(fakeArray[V]); ok { 36 | return java.JBoolean(slices.Equal(arr, other)) 37 | } 38 | return false 39 | } 40 | 41 | func (arr fakeArray[V]) GetClass() java.IClass { 42 | switch any((*V)(nil)).(type) { 43 | case *java.JBoolean: 44 | return FakeZArrayClass 45 | case *java.JByte: 46 | return FakeBArrayClass 47 | case *java.JChar: 48 | return FakeCArrayClass 49 | case *java.JShort: 50 | return FakeSArrayClass 51 | case *java.JInt: 52 | return FakeIArrayClass 53 | case *java.JLong: 54 | return FakeJArrayClass 55 | case *java.JFloat: 56 | return FakeFArrayClass 57 | case *java.JDouble: 58 | return FakeDArrayClass 59 | default: 60 | panic(errors.New("[PrimitiveArray] type not supported")) 61 | } 62 | } 63 | 64 | func (arr fakeArray[V]) ToString() java.IString { 65 | return FakeString(arr.GetClass().GetName().String() + "@" + strconv.FormatInt(int64(arr.HashCode()), 16)) 66 | } 67 | 68 | func (arr fakeArray[V]) Length() java.JInt { 69 | return java.JInt(len(arr)) 70 | } 71 | 72 | func (arr fakeArray[V]) Get(index java.JInt) V { 73 | return arr[index] 74 | } 75 | 76 | func (arr fakeArray[V]) Set(index java.JInt, value V) { 77 | arr[index] = value 78 | } 79 | 80 | func (arr fakeArray[V]) Elements() []V { 81 | return arr 82 | } 83 | 84 | func (arr fakeObjectArray) Equals(obj java.IObject) java.JBoolean { 85 | if other, ok := obj.(fakeObjectArray); ok { 86 | return java.JBoolean(slices.Equal(arr.fakeArray, other.fakeArray)) 87 | } 88 | return false 89 | } 90 | 91 | func (arr fakeObjectArray) GetClass() java.IClass { 92 | return arr.cls 93 | } 94 | 95 | func (arr fakeObjectArray) ToString() java.IString { 96 | return FakeString(arr.GetClass().GetName().String() + "@" + strconv.FormatInt(int64(arr.HashCode()), 16)) 97 | } 98 | 99 | func BytesOf(arr []byte) java.IArray { 100 | return FakeBArray(unsafe.Slice((*java.JByte)(unsafe.Pointer(unsafe.SliceData(arr))), len(arr))) 101 | } 102 | 103 | func GetBytes(arr java.IByteArray) []byte { 104 | v := arr.Elements() 105 | return unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(v))), len(v)) 106 | } 107 | 108 | func ArrayOf(cls FakeClass, arr any) java.IArray { 109 | if arr == nil { 110 | return nil 111 | } 112 | switch v := arr.(type) { 113 | case []java.JBoolean: 114 | return FakeZArray(v) 115 | case []java.JByte: 116 | return FakeBArray(v) 117 | case []java.JChar: 118 | return FakeCArray(v) 119 | case []java.JShort: 120 | return FakeSArray(v) 121 | case []java.JInt: 122 | return FakeIArray(v) 123 | case []java.JLong: 124 | return FakeJArray(v) 125 | case []java.JFloat: 126 | return FakeFArray(v) 127 | case []java.JDouble: 128 | return FakeDArray(v) 129 | case []java.IObject: 130 | return fakeObjectArray{fakeArray: v, cls: FakeObjectArrayClass} 131 | case []java.IClass: 132 | return fakeObjectArray{fakeArray: arrayConver(v), cls: FakeClassArrayClass} 133 | case []java.IString: 134 | return fakeObjectArray{fakeArray: arrayConver(v), cls: FakeStringArrayClass} 135 | case []byte: 136 | return FakeBArray(unsafe.Slice((*java.JByte)(unsafe.Pointer(unsafe.SliceData(v))), len(v))) 137 | case []string: 138 | arr := fakeObjectArray{fakeArray: make([]java.IObject, len(v)), cls: FakeStringArrayClass} 139 | for i := range v { 140 | arr.fakeArray[i] = FakeString(v[i]) 141 | } 142 | return arr 143 | } 144 | v := reflect.ValueOf(arr) 145 | if v.Kind() != reflect.Slice { 146 | return nil 147 | } 148 | fake := fakeObjectArray{fakeArray: make([]java.IObject, v.Len()), cls: cls} 149 | for i := range fake.fakeArray { 150 | fake.fakeArray[i] = ToObject[java.IObject](v.Index(i).Interface()) 151 | } 152 | return fake 153 | } 154 | 155 | func arrayConver[V java.IObject](arr []V) []java.IObject { 156 | r := make([]java.IObject, len(arr)) 157 | for i := range arr { 158 | r[i] = arr[i] 159 | } 160 | return r 161 | } 162 | -------------------------------------------------------------------------------- /java/boolean.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strconv" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type FakeBoolean java.JBoolean 10 | 11 | func (b FakeBoolean) GetClass() java.IClass { 12 | return FakeBooleanClass 13 | } 14 | 15 | func (b FakeBoolean) HashCode() java.JInt { 16 | if b { 17 | return 1231 18 | } 19 | return 1237 20 | } 21 | 22 | func (b FakeBoolean) Equals(obj java.IObject) java.JBoolean { 23 | return b == obj 24 | } 25 | 26 | func (b FakeBoolean) ToString() java.IString { 27 | return FakeString(strconv.FormatBool(bool(b))) 28 | } 29 | 30 | func init() { 31 | FakeBooleanClass.Set("TYPE", FakeBooleanTYPE) 32 | definePrimitiveMethod(FakeBooleanClass, "booleanValue", "()Z", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 33 | return java.JBoolean(obj.(FakeBoolean)) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /java/byte.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strconv" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type FakeByte java.JByte 10 | 11 | func (b FakeByte) GetClass() java.IClass { 12 | return FakeByteClass 13 | } 14 | 15 | func (b FakeByte) HashCode() java.JInt { 16 | return java.JInt(b) 17 | } 18 | 19 | func (b FakeByte) Equals(obj java.IObject) java.JBoolean { 20 | return b == obj 21 | } 22 | 23 | func (b FakeByte) ToString() java.IString { 24 | return FakeString(strconv.FormatInt(int64(b), 10)) 25 | } 26 | 27 | func init() { 28 | FakeByteClass.Set("TYPE", FakeByteTYPE) 29 | definePrimitiveMethod(FakeByteClass, "byteValue", "()B", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 30 | return java.JByte(obj.(FakeByte)) 31 | }) 32 | definePrimitiveMethod(FakeByteClass, "shortValue", "()S", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 33 | return java.JShort(obj.(FakeByte)) 34 | }) 35 | definePrimitiveMethod(FakeByteClass, "intValue", "()I", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 36 | return java.JInt(obj.(FakeByte)) 37 | }) 38 | definePrimitiveMethod(FakeByteClass, "longValue", "()J", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 39 | return java.JLong(obj.(FakeByte)) 40 | }) 41 | definePrimitiveMethod(FakeByteClass, "floatValue", "()F", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 42 | return java.JFloat(obj.(FakeByte)) 43 | }) 44 | definePrimitiveMethod(FakeByteClass, "doubleValue", "()D", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 45 | return java.JDouble(obj.(FakeByte)) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /java/c.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "errors" 5 | "unsafe" 6 | 7 | java "github.com/wnxd/microdbg-java" 8 | "github.com/wnxd/microdbg/emulator" 9 | ) 10 | 11 | type uniPtr struct { 12 | p emulator.Pointer 13 | } 14 | 15 | type ptr[V any] struct { 16 | uniPtr 17 | } 18 | 19 | type jvaluePtr struct { 20 | ptr[JValue] 21 | } 22 | 23 | type JValue uint64 24 | 25 | func newPtr(p emulator.Pointer) java.Ptr { 26 | return uniPtr{p} 27 | } 28 | 29 | func newTypePtr[V any](p emulator.Pointer) java.TypePtr[V] { 30 | return ptr[V]{uniPtr{p}} 31 | } 32 | 33 | func newJValuePtr(p emulator.Pointer) java.TypePtr[java.JValue] { 34 | return jvaluePtr{ptr[JValue]{uniPtr{p}}} 35 | } 36 | 37 | func (ptr uniPtr) Address() uintptr { 38 | return uintptr(ptr.p.Address()) 39 | } 40 | 41 | func (ptr ptr[V]) Get(index int) (V, error) { 42 | var value V 43 | size := uint64(unsafe.Sizeof(value)) 44 | err := ptr.p.Add(uint64(index)*size).MemReadPtr(size, unsafe.Pointer(&value)) 45 | return value, err 46 | } 47 | 48 | func (ptr ptr[V]) Set(index int, value V) error { 49 | size := uint64(unsafe.Sizeof(value)) 50 | return ptr.p.Add(uint64(index)*size).MemWritePtr(size, unsafe.Pointer(&value)) 51 | } 52 | 53 | func (ptr ptr[V]) ReadAt(b []V, off int64) (int, error) { 54 | count := len(b) 55 | if count == 0 { 56 | return 0, nil 57 | } 58 | size := count * int(unsafe.Sizeof(b[0])) 59 | err := ptr.p.Add(uint64(off)).MemReadPtr(uint64(size), unsafe.Pointer(unsafe.SliceData(b))) 60 | return size, err 61 | } 62 | 63 | func (ptr ptr[V]) WriteAt(b []V, off int64) (int, error) { 64 | count := len(b) 65 | if count == 0 { 66 | return 0, nil 67 | } 68 | size := count * int(unsafe.Sizeof(b[0])) 69 | err := ptr.p.Add(uint64(off)).MemWritePtr(uint64(size), unsafe.Pointer(unsafe.SliceData(b))) 70 | return size, err 71 | } 72 | 73 | func (ptr jvaluePtr) Get(index int) (java.JValue, error) { 74 | return ptr.ptr.Get(index) 75 | } 76 | 77 | func (ptr jvaluePtr) Set(index int, value java.JValue) error { 78 | return errors.ErrUnsupported 79 | } 80 | 81 | func (ptr jvaluePtr) ReadAt(b []java.JValue, off int64) (int, error) { 82 | count := len(b) 83 | if count == 0 { 84 | return 0, nil 85 | } 86 | arr := make([]JValue, count) 87 | n, err := ptr.ptr.ReadAt(arr, off) 88 | if err != nil { 89 | return 0, err 90 | } 91 | for i, v := range arr { 92 | b[i] = v 93 | } 94 | return n, nil 95 | } 96 | 97 | func (ptr jvaluePtr) WriteAt(b []java.JValue, off int64) (int, error) { 98 | return 0, errors.ErrUnsupported 99 | } 100 | 101 | func (v JValue) JBoolean() java.JBoolean { 102 | return *(*java.JBoolean)(unsafe.Pointer(&v)) 103 | } 104 | 105 | func (v JValue) JByte() java.JByte { 106 | return *(*java.JByte)(unsafe.Pointer(&v)) 107 | } 108 | 109 | func (v JValue) JChar() java.JChar { 110 | return *(*java.JChar)(unsafe.Pointer(&v)) 111 | } 112 | 113 | func (v JValue) JShort() java.JShort { 114 | return *(*java.JShort)(unsafe.Pointer(&v)) 115 | } 116 | 117 | func (v JValue) JInt() java.JInt { 118 | return *(*java.JInt)(unsafe.Pointer(&v)) 119 | } 120 | 121 | func (v JValue) JLong() java.JLong { 122 | return *(*java.JLong)(unsafe.Pointer(&v)) 123 | } 124 | 125 | func (v JValue) JFloat() java.JFloat { 126 | return *(*java.JFloat)(unsafe.Pointer(&v)) 127 | } 128 | 129 | func (v JValue) JDouble() java.JDouble { 130 | return *(*java.JDouble)(unsafe.Pointer(&v)) 131 | } 132 | 133 | func (v JValue) JObject() java.JObject { 134 | return *(*Ref)(unsafe.Pointer(&v)) 135 | } 136 | -------------------------------------------------------------------------------- /java/character.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | java "github.com/wnxd/microdbg-java" 5 | ) 6 | 7 | type FakeCharacter java.JChar 8 | 9 | func (c FakeCharacter) GetClass() java.IClass { 10 | return FakeCharacterClass 11 | } 12 | 13 | func (c FakeCharacter) HashCode() java.JInt { 14 | return java.JInt(c) 15 | } 16 | 17 | func (c FakeCharacter) Equals(obj java.IObject) java.JBoolean { 18 | return c == obj 19 | } 20 | 21 | func (c FakeCharacter) ToString() java.IString { 22 | return FakeString(rune(c)) 23 | } 24 | 25 | func init() { 26 | FakeCharacterClass.Set("TYPE", FakeCharTYPE) 27 | } 28 | -------------------------------------------------------------------------------- /java/class.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "sync" 7 | 8 | java "github.com/wnxd/microdbg-java" 9 | ) 10 | 11 | type FakeClass interface { 12 | java.IClass 13 | GetModifiers() java.JInt 14 | SetModifiers(mod Modifier) 15 | FakeProperty 16 | NewObject(v any) FakeObject 17 | NewThrowable(msg string) java.IThrowable 18 | NewArray(length int) java.IArray 19 | FindMethod(name, sig string) FakeMethod 20 | GetMethod(name, sig string) FakeMethod 21 | GetStaticMethod(name, sig string) FakeMethod 22 | DefineMethod(name, sig string, mod Modifier) FakeMethod 23 | ClearNativeMethods() 24 | FindField(name, sig string) FakeField 25 | GetField(name, sig string) FakeField 26 | GetStaticField(name, sig string) FakeField 27 | DefineField(name, sig string, mod Modifier) FakeField 28 | 29 | findMethod(java.JInt) FakeMethod 30 | } 31 | 32 | type wrapClass struct { 33 | fakeClass 34 | cls java.IClass 35 | } 36 | 37 | type fakeClass struct { 38 | cf ClassFactory 39 | name string 40 | super java.IClass 41 | iface []java.IClass 42 | mod Modifier 43 | fakeProperty 44 | methods sync.Map 45 | fields sync.Map 46 | } 47 | 48 | type fakeArrayClass struct { 49 | fakeClass 50 | elem java.IClass 51 | } 52 | 53 | func (cls *wrapClass) IsInterface() java.JBoolean { 54 | return cls.cls.IsInterface() 55 | } 56 | 57 | func (cls *wrapClass) ComponentType() java.IClass { 58 | if arr, ok := cls.cls.(interface{ ComponentType() java.IClass }); ok { 59 | return arr.ComponentType() 60 | } 61 | return nil 62 | } 63 | 64 | func (cls *fakeClass) GetClass() java.IClass { 65 | return FakeClassClass 66 | } 67 | 68 | func (cls *fakeClass) HashCode() java.JInt { 69 | return HashCode(cls.name) 70 | } 71 | 72 | func (cls *fakeClass) Equals(obj java.IObject) java.JBoolean { 73 | if other, ok := obj.(interface { 74 | IsArray() java.JBoolean 75 | ComponentType() java.IClass 76 | }); ok && cls.IsArray() && other.IsArray() { 77 | return cls.ComponentType().Equals(other.ComponentType()) 78 | } else if other, ok := obj.(*fakeClass); ok { 79 | return cls.name == other.name 80 | } else if other, ok := obj.(java.IClass); ok { 81 | return cls.name == other.GetName().String() 82 | } 83 | return false 84 | } 85 | 86 | func (cls *fakeClass) ToString() java.IString { 87 | var prefix string 88 | if cls.IsInterface() { 89 | prefix = "interface " 90 | } else if cls.IsPrimitive() { 91 | prefix = "" 92 | } else { 93 | prefix = "class " 94 | } 95 | prefix += cls.name 96 | return FakeString(prefix) 97 | } 98 | 99 | func (cls *fakeClass) NewInstance() java.IObject { 100 | return cls.NewObject(nil) 101 | } 102 | 103 | func (cls *fakeClass) GetName() java.IString { 104 | return FakeString(cls.name) 105 | } 106 | 107 | func (cls *fakeClass) GetSimpleName() java.IString { 108 | return FakeString(cls.name[strings.LastIndexByte(cls.name, '.')+1:]) 109 | } 110 | 111 | func (cls *fakeClass) GetTypeName() java.IString { 112 | return cls.GetName() 113 | } 114 | 115 | func (cls *fakeClass) DescriptorString() java.IString { 116 | if cls.IsPrimitive() { 117 | switch cls.name { 118 | case "boolean": 119 | return FakeString("Z") 120 | case "byte": 121 | return FakeString("B") 122 | case "char": 123 | return FakeString("C") 124 | case "short": 125 | return FakeString("S") 126 | case "int": 127 | return FakeString("I") 128 | case "long": 129 | return FakeString("J") 130 | case "float": 131 | return FakeString("F") 132 | case "double": 133 | return FakeString("D") 134 | } 135 | } 136 | return FakeString("L" + strings.ReplaceAll(cls.name, ".", "/") + ";") 137 | } 138 | 139 | func (cls *fakeClass) GetSuperclass() java.IClass { 140 | if cls.super != nil { 141 | return cls.super 142 | } else if cls.IsInterface() || cls.name == "java.lang.Object" { 143 | return nil 144 | } 145 | return FakeObjectClass 146 | } 147 | 148 | func (cls *fakeClass) GetInterfaces() []java.IClass { 149 | return cls.iface 150 | } 151 | 152 | func (cls *fakeClass) IsInterface() java.JBoolean { 153 | return cls.mod&Modifier_INTERFACE != 0 154 | } 155 | 156 | func (cls *fakeClass) IsAssignableFrom(clazz java.IClass) java.JBoolean { 157 | for ; clazz != nil; clazz = clazz.GetSuperclass() { 158 | if cls.IsInterface() { 159 | for _, iface := range clazz.GetInterfaces() { 160 | if cls.Equals(iface) { 161 | return true 162 | } 163 | } 164 | } else if cls.Equals(clazz) { 165 | return true 166 | } 167 | } 168 | return false 169 | } 170 | 171 | func (cls *fakeClass) IsPrimitive() java.JBoolean { 172 | return strings.IndexByte(cls.name, '.') == -1 173 | } 174 | 175 | func (cls *fakeClass) IsArray() java.JBoolean { 176 | return strings.HasPrefix(cls.name, "[") 177 | } 178 | 179 | func (cls *fakeClass) IsInstance(obj java.IObject) java.JBoolean { 180 | if obj == nil { 181 | return false 182 | } 183 | return cls.IsAssignableFrom(obj.GetClass()) 184 | } 185 | 186 | func (cls *fakeClass) Cast(obj java.IObject) java.IObject { 187 | if fake, ok := obj.(*fakeObject); ok { 188 | return &fakeObject{cls: cls, val: fake.val} 189 | } 190 | return nil 191 | } 192 | 193 | func (cls *fakeClass) ComponentType() java.IClass { 194 | return nil 195 | } 196 | 197 | func (cls *fakeClass) GetModifiers() java.JInt { 198 | return java.JInt(cls.mod) 199 | } 200 | 201 | func (cls *fakeClass) SetModifiers(mod Modifier) { 202 | cls.mod = mod 203 | } 204 | 205 | func (cls *fakeClass) NewObject(val any) FakeObject { 206 | return &fakeObject{cls: cls, val: val} 207 | } 208 | 209 | func (cls *fakeClass) NewThrowable(msg string) java.IThrowable { 210 | obj := &fakeObject{cls: cls} 211 | obj.Set("detailMessage", FakeString(msg)) 212 | return obj 213 | } 214 | 215 | func (cls *fakeClass) NewArray(length int) java.IArray { 216 | switch cls.name { 217 | case "boolean": 218 | return make(FakeZArray, length) 219 | case "byte": 220 | return make(FakeBArray, length) 221 | case "char": 222 | return make(FakeCArray, length) 223 | case "short": 224 | return make(FakeSArray, length) 225 | case "int": 226 | return make(FakeIArray, length) 227 | case "long": 228 | return make(FakeJArray, length) 229 | case "float": 230 | return make(FakeFArray, length) 231 | case "double": 232 | return make(FakeDArray, length) 233 | case "void": 234 | return nil 235 | default: 236 | return fakeObjectArray{fakeArray: make(fakeArray[java.IObject], length), cls: arrayOf(cls.cf, cls)} 237 | } 238 | } 239 | 240 | func (cls *fakeClass) FindMethod(name, sig string) FakeMethod { 241 | h := HashCode(name) ^ HashCode(sig) 242 | return cls.findMethod(h) 243 | } 244 | 245 | func (cls *fakeClass) GetMethod(name, sig string) FakeMethod { 246 | if method := cls.FindMethod(name, sig); method != nil { 247 | return method 248 | } 249 | return cls.DefineMethod(name, sig, Modifier_PUBLIC) 250 | } 251 | 252 | func (cls *fakeClass) GetStaticMethod(name, sig string) FakeMethod { 253 | if method := cls.FindMethod(name, sig); method != nil { 254 | return method 255 | } 256 | return cls.DefineMethod(name, sig, Modifier_PUBLIC|Modifier_STATIC) 257 | } 258 | 259 | func (cls *fakeClass) DefineMethod(name, sig string, mod Modifier) FakeMethod { 260 | if cls.cf == nil { 261 | panic(fmt.Errorf("[DefineMethod] %s: %s %s not allowed", cls.name, name, sig)) 262 | } 263 | h := HashCode(name) ^ HashCode(sig) 264 | method := cls.cf.DefineMethod(cls, name, sig, mod) 265 | cls.methods.Store(h, method) 266 | return method 267 | } 268 | 269 | func (cls *fakeClass) ClearNativeMethods() { 270 | for h, method := range cls.methods.Range { 271 | if IsNative(method.(FakeMethod)) { 272 | cls.methods.Delete(h) 273 | } 274 | } 275 | } 276 | 277 | func (cls *fakeClass) FindField(name, sig string) FakeField { 278 | h := HashCode(name) ^ HashCode(sig) 279 | if field, ok := cls.fields.Load(h); ok { 280 | return field.(FakeField) 281 | } 282 | if super, ok := cls.GetSuperclass().(FakeClass); ok { 283 | return super.FindField(name, sig) 284 | } 285 | return nil 286 | } 287 | 288 | func (cls *fakeClass) GetField(name, sig string) FakeField { 289 | if field := cls.FindField(name, sig); field != nil { 290 | return field 291 | } 292 | return cls.DefineField(name, sig, Modifier_PUBLIC) 293 | } 294 | 295 | func (cls *fakeClass) GetStaticField(name, sig string) FakeField { 296 | if field := cls.FindField(name, sig); field != nil { 297 | return field 298 | } 299 | return cls.DefineField(name, sig, Modifier_PUBLIC|Modifier_STATIC) 300 | } 301 | 302 | func (cls *fakeClass) DefineField(name, sig string, mod Modifier) FakeField { 303 | if cls.cf == nil { 304 | panic(fmt.Errorf("[DefineField] %s: %s %s not allowed", cls.name, name, sig)) 305 | } 306 | h := HashCode(name) ^ HashCode(sig) 307 | field := cls.cf.DefineField(cls, name, sig, mod) 308 | cls.fields.Store(h, field) 309 | return field 310 | } 311 | 312 | func (cls *fakeClass) findMethod(h java.JInt) FakeMethod { 313 | if method, ok := cls.methods.Load(h); ok { 314 | return method.(FakeMethod) 315 | } 316 | if super, ok := cls.GetSuperclass().(FakeClass); !ok { 317 | } else if method := super.findMethod(h); method != nil { 318 | return method 319 | } 320 | for _, iface := range cls.GetInterfaces() { 321 | if iface, ok := iface.(FakeClass); !ok { 322 | } else if method := iface.findMethod(h); method != nil { 323 | return method 324 | } 325 | } 326 | return nil 327 | } 328 | 329 | func (cls *fakeArrayClass) GetSimpleName() java.IString { 330 | return FakeString(cls.elem.GetSimpleName().String() + "[]") 331 | } 332 | 333 | func (cls *fakeArrayClass) GetTypeName() java.IString { 334 | return FakeString(cls.elem.GetTypeName().String() + "[]") 335 | } 336 | 337 | func (cls *fakeArrayClass) DescriptorString() java.IString { 338 | return FakeString("[" + cls.elem.DescriptorString().String()) 339 | } 340 | 341 | func (cls *fakeArrayClass) ComponentType() java.IClass { 342 | return cls.elem 343 | } 344 | 345 | func ObjectOf(cls FakeClass, v any) java.IObject { 346 | if cls.IsArray() { 347 | return ArrayOf(cls, v) 348 | } 349 | return &fakeObject{ 350 | cls: cls, 351 | val: v, 352 | } 353 | } 354 | 355 | func init() { 356 | definePrimitiveMethod(FakeClassClass, "getName", "()Ljava/lang/String;", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 357 | return obj.(java.IClass).GetName() 358 | }) 359 | definePrimitiveMethod(FakeClassClass, "getSimpleName", "()Ljava/lang/String;", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 360 | return obj.(java.IClass).GetSimpleName() 361 | }) 362 | definePrimitiveMethod(FakeClassClass, "getTypeName", "()Ljava/lang/String;", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 363 | return obj.(java.IClass).GetTypeName() 364 | }) 365 | definePrimitiveMethod(FakeClassClass, "descriptorString", "()Ljava/lang/String;", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 366 | return obj.(java.IClass).DescriptorString() 367 | }) 368 | definePrimitiveMethod(FakeClassClass, "getSuperclass", "()Ljava/lang/Class;", Modifier_PUBLIC|Modifier_NATIVE).BindCall(func(obj java.IObject, _ ...any) any { 369 | return obj.(java.IClass).GetSuperclass() 370 | }) 371 | definePrimitiveMethod(FakeClassClass, "getInterfaces", "()[Ljava/lang/Class;", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 372 | return ArrayOf(FakeClassArrayClass, obj.(java.IClass).GetInterfaces()) 373 | }) 374 | } 375 | -------------------------------------------------------------------------------- /java/classes.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strings" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type defaultFactory map[string]FakeClass 10 | 11 | var ( 12 | FakeBooleanTYPE = &fakeClass{ 13 | name: "boolean", 14 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 15 | } 16 | FakeByteTYPE = &fakeClass{ 17 | name: "byte", 18 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 19 | } 20 | FakeCharTYPE = &fakeClass{ 21 | name: "char", 22 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 23 | } 24 | FakeShortTYPE = &fakeClass{ 25 | name: "short", 26 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 27 | } 28 | FakeIntTYPE = &fakeClass{ 29 | name: "int", 30 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 31 | } 32 | FakeLongTYPE = &fakeClass{ 33 | name: "long", 34 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 35 | } 36 | FakeFloatTYPE = &fakeClass{ 37 | name: "float", 38 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 39 | } 40 | FakeDoubleTYPE = &fakeClass{ 41 | name: "double", 42 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 43 | } 44 | FakeVoidTYPE = &fakeClass{ 45 | name: "void", 46 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 47 | } 48 | FakeObjectClass = &fakeClass{ 49 | name: "java.lang.Object", 50 | mod: Modifier_PUBLIC, 51 | } 52 | FakeClassClass = &fakeClass{ 53 | name: "java.lang.Class", 54 | super: FakeObjectClass, 55 | iface: []java.IClass{FakeSerializableClass}, 56 | mod: Modifier_PUBLIC | Modifier_FINAL, 57 | } 58 | FakeStringClass = &fakeClass{ 59 | name: "java.lang.String", 60 | super: FakeObjectClass, 61 | iface: []java.IClass{FakeSerializableClass, FakeComparableClass, FakeCharSequenceClass}, 62 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 63 | } 64 | FakeBooleanClass = &fakeClass{ 65 | name: "java.lang.Boolean", 66 | super: FakeObjectClass, 67 | iface: []java.IClass{FakeSerializableClass, FakeComparableClass}, 68 | mod: Modifier_PUBLIC | Modifier_FINAL, 69 | } 70 | FakeByteClass = &fakeClass{ 71 | name: "java.lang.Byte", 72 | super: FakeNumberClass, 73 | iface: []java.IClass{FakeComparableClass}, 74 | mod: Modifier_PUBLIC | Modifier_FINAL, 75 | } 76 | FakeCharacterClass = &fakeClass{ 77 | name: "java.lang.Character", 78 | super: FakeObjectClass, 79 | iface: []java.IClass{FakeSerializableClass, FakeComparableClass}, 80 | mod: Modifier_PUBLIC | Modifier_FINAL, 81 | } 82 | FakeShortClass = &fakeClass{ 83 | name: "java.lang.Short", 84 | super: FakeNumberClass, 85 | iface: []java.IClass{FakeComparableClass}, 86 | mod: Modifier_PUBLIC | Modifier_FINAL, 87 | } 88 | FakeIntegerClass = &fakeClass{ 89 | name: "java.lang.Integer", 90 | super: FakeNumberClass, 91 | iface: []java.IClass{FakeComparableClass}, 92 | mod: Modifier_PUBLIC | Modifier_FINAL, 93 | } 94 | FakeLongClass = &fakeClass{ 95 | name: "java.lang.Long", 96 | super: FakeNumberClass, 97 | iface: []java.IClass{FakeComparableClass}, 98 | mod: Modifier_PUBLIC | Modifier_FINAL, 99 | } 100 | FakeFloatClass = &fakeClass{ 101 | name: "java.lang.Float", 102 | super: FakeNumberClass, 103 | iface: []java.IClass{FakeComparableClass}, 104 | mod: Modifier_PUBLIC | Modifier_FINAL, 105 | } 106 | FakeDoubleClass = &fakeClass{ 107 | name: "java.lang.Double", 108 | super: FakeNumberClass, 109 | iface: []java.IClass{FakeComparableClass}, 110 | mod: Modifier_PUBLIC | Modifier_FINAL, 111 | } 112 | FakeNumberClass = &fakeClass{ 113 | name: "java.lang.Number", 114 | super: FakeObjectClass, 115 | iface: []java.IClass{FakeSerializableClass}, 116 | mod: Modifier_PUBLIC | Modifier_ABSTRACT, 117 | } 118 | FakeIterableClass = &fakeClass{ 119 | name: "java.lang.Iterable", 120 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 121 | } 122 | FakeMethodClass = &fakeClass{ 123 | name: "java.lang.reflect.Method", 124 | super: FakeExecutableClass, 125 | mod: Modifier_PUBLIC | Modifier_FINAL, 126 | } 127 | FakeConstructorClass = &fakeClass{ 128 | name: "java.lang.reflect.Constructor", 129 | super: FakeExecutableClass, 130 | mod: Modifier_PUBLIC | Modifier_FINAL, 131 | } 132 | FakeExecutableClass = &fakeClass{ 133 | name: "java.lang.reflect.Executable", 134 | super: FakeAccessibleObjectClass, 135 | iface: []java.IClass{FakeMemberClass}, 136 | mod: Modifier_PUBLIC | Modifier_FINAL, 137 | } 138 | FakeFieldClass = &fakeClass{ 139 | name: "java.lang.reflect.Field", 140 | super: FakeAccessibleObjectClass, 141 | iface: []java.IClass{FakeMemberClass}, 142 | mod: Modifier_PUBLIC | Modifier_FINAL, 143 | } 144 | FakeAccessibleObjectClass = &fakeClass{ 145 | name: "java.lang.reflect.AccessibleObject", 146 | super: FakeObjectClass, 147 | mod: Modifier_PUBLIC, 148 | } 149 | FakeMemberClass = &fakeClass{ 150 | name: "java.lang.reflect.Member", 151 | super: FakeObjectClass, 152 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 153 | } 154 | FakeCloneableClass = &fakeClass{ 155 | name: "java.lang.Cloneable", 156 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 157 | } 158 | FakeSerializableClass = &fakeClass{ 159 | name: "java.io.Serializable", 160 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 161 | } 162 | FakeComparableClass = &fakeClass{ 163 | name: "java.lang.Comparable", 164 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 165 | } 166 | FakeCharSequenceClass = &fakeClass{ 167 | name: "java.lang.CharSequence", 168 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 169 | } 170 | FakeThrowableClass = &fakeClass{ 171 | name: "java.lang.Throwable", 172 | super: FakeObjectClass, 173 | iface: []java.IClass{FakeSerializableClass}, 174 | mod: Modifier_PUBLIC, 175 | } 176 | FakeExceptionClass = &fakeClass{ 177 | name: "java.lang.Exception", 178 | super: FakeThrowableClass, 179 | mod: Modifier_PUBLIC, 180 | } 181 | 182 | FakeZArrayClass = arrayOf(nil, FakeBooleanTYPE) 183 | FakeBArrayClass = arrayOf(nil, FakeByteTYPE) 184 | FakeCArrayClass = arrayOf(nil, FakeCharTYPE) 185 | FakeSArrayClass = arrayOf(nil, FakeShortTYPE) 186 | FakeIArrayClass = arrayOf(nil, FakeIntTYPE) 187 | FakeJArrayClass = arrayOf(nil, FakeLongTYPE) 188 | FakeFArrayClass = arrayOf(nil, FakeFloatTYPE) 189 | FakeDArrayClass = arrayOf(nil, FakeDoubleTYPE) 190 | FakeObjectArrayClass = arrayOf(nil, FakeObjectClass) 191 | FakeClassArrayClass = arrayOf(nil, FakeClassClass) 192 | FakeStringArrayClass = arrayOf(nil, FakeStringClass) 193 | 194 | FakeIteratorClass = &fakeClass{ 195 | name: "java.util.Iterator", 196 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 197 | } 198 | FakeCollectionClass = &fakeClass{ 199 | name: "java.util.Collection", 200 | iface: []java.IClass{FakeIterableClass}, 201 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 202 | } 203 | FakeSetClass = &fakeClass{ 204 | name: "java.util.Set", 205 | iface: []java.IClass{FakeCollectionClass}, 206 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 207 | } 208 | FakeMapClass = &fakeClass{ 209 | name: "java.util.Map", 210 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 211 | } 212 | FakeMapEntryClass = &fakeClass{ 213 | name: "java.util.Map$Entry", 214 | mod: Modifier_PUBLIC | Modifier_INTERFACE | Modifier_ABSTRACT, 215 | } 216 | FakeHashMapClass = &fakeClass{ 217 | name: "java.util.HashMap", 218 | super: FakeObjectClass, 219 | iface: []java.IClass{FakeMapClass, FakeCloneableClass, FakeSerializableClass}, 220 | mod: Modifier_PUBLIC, 221 | } 222 | 223 | defaultClasses = make(defaultFactory) 224 | ) 225 | 226 | func (defaultFactory) WrapClass(java.IClass) FakeClass { 227 | return nil 228 | } 229 | 230 | func (defaultFactory) FindClass(name string) (FakeClass, bool) { 231 | name = strings.ReplaceAll(name, "/", ".") 232 | cls, ok := defaultClasses[name] 233 | return cls, ok 234 | } 235 | 236 | func (defaultFactory) GetClass(name string) FakeClass { 237 | name = strings.ReplaceAll(name, "/", ".") 238 | return defaultClasses[name] 239 | } 240 | 241 | func (defaultFactory) DefineClass(name string, extends ...java.IClass) FakeClass { 242 | cls := &fakeClass{ 243 | name: name, 244 | mod: Modifier_PUBLIC, 245 | } 246 | if len(extends) > 0 { 247 | cls.super = extends[0] 248 | cls.iface = extends[1:] 249 | } 250 | defaultClasses[name] = cls 251 | return cls 252 | } 253 | 254 | func (cf defaultFactory) ArrayOf(elem java.IClass) FakeClass { 255 | cls := arrayOf(cf, elem) 256 | cls.cf = nil 257 | return cls 258 | } 259 | 260 | func (cf defaultFactory) DefineMethod(cls FakeClass, name, sig string, mod Modifier) FakeMethod { 261 | method := &fakeMethod{cls: cls, name: name, mod: mod} 262 | method.parseDescriptor(cf, sig) 263 | return method 264 | } 265 | 266 | func (cf defaultFactory) DefineField(cls FakeClass, name, sig string, mod Modifier) FakeField { 267 | field := &fakeField{cls: cls, name: name, mod: mod} 268 | field.parseDescriptor(cf, sig) 269 | return field 270 | } 271 | 272 | func init() { 273 | list := []FakeClass{FakeBooleanTYPE, FakeByteTYPE, FakeCharTYPE, FakeShortTYPE, FakeIntTYPE, FakeLongTYPE, FakeFloatTYPE, FakeDoubleTYPE, FakeVoidTYPE, FakeObjectClass, FakeClassClass, FakeStringClass, FakeBooleanClass, FakeByteClass, FakeCharacterClass, FakeShortClass, FakeIntegerClass, FakeLongClass, FakeFloatClass, FakeDoubleClass, FakeNumberClass, FakeIterableClass, FakeMethodClass, FakeConstructorClass, FakeExecutableClass, FakeFieldClass, FakeAccessibleObjectClass, FakeMemberClass, FakeCloneableClass, FakeSerializableClass, FakeComparableClass, FakeCharSequenceClass, FakeZArrayClass, FakeBArrayClass, FakeCArrayClass, FakeSArrayClass, FakeIArrayClass, FakeJArrayClass, FakeFArrayClass, FakeDArrayClass, FakeObjectArrayClass, FakeClassArrayClass, FakeStringArrayClass, FakeIteratorClass, FakeCollectionClass, FakeSetClass, FakeMapClass, FakeMapEntryClass, FakeHashMapClass} 274 | 275 | for _, cls := range list { 276 | defaultClasses[cls.GetName().String()] = cls 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /java/double.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | 7 | java "github.com/wnxd/microdbg-java" 8 | ) 9 | 10 | type FakeDouble java.JDouble 11 | 12 | func (d FakeDouble) GetClass() java.IClass { 13 | return FakeDoubleClass 14 | } 15 | 16 | func (d FakeDouble) HashCode() java.JInt { 17 | bits := math.Float64bits(float64(d)) 18 | return (java.JInt)(bits ^ (bits >> 32)) 19 | } 20 | 21 | func (d FakeDouble) Equals(obj java.IObject) java.JBoolean { 22 | return d == obj 23 | } 24 | 25 | func (d FakeDouble) ToString() java.IString { 26 | return FakeString(strconv.FormatFloat(float64(d), 'f', -1, 32)) 27 | } 28 | 29 | func init() { 30 | FakeDoubleClass.Set("TYPE", FakeDoubleTYPE) 31 | definePrimitiveMethod(FakeDoubleClass, "byteValue", "()B", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 32 | return java.JByte(obj.(FakeDouble)) 33 | }) 34 | definePrimitiveMethod(FakeDoubleClass, "shortValue", "()S", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 35 | return java.JShort(obj.(FakeDouble)) 36 | }) 37 | definePrimitiveMethod(FakeDoubleClass, "intValue", "()I", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 38 | return java.JInt(obj.(FakeDouble)) 39 | }) 40 | definePrimitiveMethod(FakeDoubleClass, "longValue", "()J", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 41 | return java.JLong(obj.(FakeDouble)) 42 | }) 43 | definePrimitiveMethod(FakeDoubleClass, "floatValue", "()F", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 44 | return java.JFloat(obj.(FakeDouble)) 45 | }) 46 | definePrimitiveMethod(FakeDoubleClass, "doubleValue", "()D", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 47 | return java.JDouble(obj.(FakeDouble)) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /java/factory.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "hash/fnv" 5 | "strings" 6 | "sync" 7 | "unsafe" 8 | 9 | java "github.com/wnxd/microdbg-java" 10 | ) 11 | 12 | type ClassFactory interface { 13 | WrapClass(clazz java.IClass) FakeClass 14 | FindClass(name string) (FakeClass, bool) 15 | GetClass(name string) FakeClass 16 | DefineClass(name string, extends ...java.IClass) FakeClass 17 | ArrayOf(clazz java.IClass) FakeClass 18 | DefineMethod(clazz FakeClass, name, sig string, mod Modifier) FakeMethod 19 | DefineField(clazz FakeClass, name, sig string, mod Modifier) FakeField 20 | } 21 | 22 | type classFactory struct { 23 | f func(ClassFactory, string) FakeClass 24 | classes sync.Map 25 | } 26 | 27 | func NewClassFactory(f func(ClassFactory, string) FakeClass) ClassFactory { 28 | return &classFactory{f: f} 29 | } 30 | 31 | func (cf *classFactory) WrapClass(cls java.IClass) FakeClass { 32 | if isNil(cls) { 33 | return nil 34 | } else if fake, ok := cls.(FakeClass); ok { 35 | return fake 36 | } 37 | return &wrapClass{ 38 | fakeClass: fakeClass{ 39 | cf: cf, 40 | name: cls.GetName().String(), 41 | super: cls.GetSuperclass(), 42 | iface: cls.GetInterfaces(), 43 | }, 44 | cls: cls, 45 | } 46 | } 47 | 48 | func (cf *classFactory) FindClass(name string) (FakeClass, bool) { 49 | name, n := nameFormat(name) 50 | var fake FakeClass 51 | if cls, ok := defaultClasses[name]; ok { 52 | fake = cls 53 | } else if val, ok := cf.classes.Load(HashCode(name)); ok { 54 | fake = val.(FakeClass) 55 | } else { 56 | return nil, false 57 | } 58 | for i := 0; i < n; i++ { 59 | fake = cf.ArrayOf(fake) 60 | } 61 | return fake, true 62 | } 63 | 64 | func (cf *classFactory) GetClass(name string) FakeClass { 65 | name, n := nameFormat(name) 66 | if cls, ok := defaultClasses[name]; ok { 67 | return cf.arrayOfN(cls, n) 68 | } 69 | h := HashCode(name) 70 | if val, ok := cf.classes.Load(h); ok { 71 | return cf.arrayOfN(val.(FakeClass), n) 72 | } 73 | if cf.f != nil { 74 | if cls := cf.f(cf, name); cls != nil { 75 | return cf.arrayOfN(cls, n) 76 | } else if val, ok := cf.classes.Load(h); ok { 77 | return cf.arrayOfN(val.(FakeClass), n) 78 | } 79 | } 80 | cls := &fakeClass{name: name, mod: Modifier_PUBLIC} 81 | cls.cf = cf 82 | cf.classes.Store(h, cls) 83 | return cf.arrayOfN(cls, n) 84 | } 85 | 86 | func (cf *classFactory) DefineClass(name string, extends ...java.IClass) (fake FakeClass) { 87 | name, n := nameFormat(name) 88 | fake, ok := defaultClasses[name] 89 | if !ok { 90 | h := HashCode(name) 91 | var cls *fakeClass 92 | if val, ok := cf.classes.Load(h); ok { 93 | cls = val.(*fakeClass) 94 | } else { 95 | cls = &fakeClass{cf: cf, name: name, mod: Modifier_PUBLIC} 96 | cf.classes.Store(h, cls) 97 | } 98 | if len(extends) > 0 { 99 | cls.super = extends[0] 100 | cls.iface = extends[1:] 101 | } 102 | fake = cls 103 | } 104 | for i := 0; i < n; i++ { 105 | fake = cf.ArrayOf(fake) 106 | } 107 | return fake 108 | } 109 | 110 | func (cf *classFactory) ArrayOf(elem java.IClass) FakeClass { 111 | return arrayOf(cf, elem) 112 | } 113 | 114 | func (cf *classFactory) DefineMethod(cls FakeClass, name, sig string, mod Modifier) FakeMethod { 115 | method := &fakeMethod{cls: cls, name: name, mod: mod} 116 | method.parseDescriptor(cf, sig) 117 | return method 118 | } 119 | 120 | func (cf *classFactory) DefineField(cls FakeClass, name, sig string, mod Modifier) FakeField { 121 | field := &fakeField{cls: cls, name: name, mod: mod} 122 | field.parseDescriptor(cf, sig) 123 | return field 124 | } 125 | 126 | func (cf *classFactory) arrayOfN(elem FakeClass, n int) FakeClass { 127 | for i := 0; i < n; i++ { 128 | elem = cf.ArrayOf(elem) 129 | } 130 | return elem 131 | } 132 | 133 | func nameFormat(name string) (string, int) { 134 | for i := 0; ; i++ { 135 | switch name[i] { 136 | case 'Z': 137 | name = "boolean" 138 | case 'B': 139 | name = "byte" 140 | case 'C': 141 | name = "char" 142 | case 'S': 143 | name = "short" 144 | case 'I': 145 | name = "int" 146 | case 'J': 147 | name = "long" 148 | case 'F': 149 | name = "float" 150 | case 'D': 151 | name = "double" 152 | case 'V': 153 | name = "void" 154 | case '[': 155 | continue 156 | case 'L': 157 | name = name[i+1 : len(name)-1] 158 | fallthrough 159 | default: 160 | name = strings.ReplaceAll(name, "/", ".") 161 | } 162 | return name, i 163 | } 164 | } 165 | 166 | func HashCode(str string) java.JInt { 167 | h := fnv.New32a() 168 | h.Write(unsafe.Slice(unsafe.StringData(str), len(str))) 169 | return java.JInt(h.Sum32()) 170 | } 171 | 172 | func definePrimitiveMethod(cls *fakeClass, name, sig string, mod Modifier) FakeMethod { 173 | h := HashCode(name) ^ HashCode(sig) 174 | method := defaultClasses.DefineMethod(cls, name, sig, mod) 175 | cls.methods.Store(h, method) 176 | return method 177 | } 178 | 179 | func arrayOf(cf ClassFactory, elem java.IClass) *fakeArrayClass { 180 | var name string 181 | if elem.IsPrimitive() { 182 | name = "[" + elem.DescriptorString().String() 183 | } else if elem.IsArray() { 184 | name = "[" + elem.GetName().String() 185 | } else { 186 | name = "[L" + elem.GetName().String() + ";" 187 | } 188 | return &fakeArrayClass{ 189 | fakeClass: fakeClass{ 190 | cf: cf, 191 | name: name, 192 | super: FakeObjectClass, 193 | iface: []java.IClass{FakeCloneableClass, FakeSerializableClass}, 194 | mod: Modifier_PUBLIC | Modifier_FINAL | Modifier_ABSTRACT, 195 | }, 196 | elem: elem, 197 | } 198 | } 199 | 200 | func isNil(v any) bool { 201 | p := (*struct{ rtype, data unsafe.Pointer })(unsafe.Pointer(&v)) 202 | return p.rtype == nil || p.data == nil 203 | } 204 | -------------------------------------------------------------------------------- /java/field.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unsafe" 7 | 8 | java "github.com/wnxd/microdbg-java" 9 | "github.com/wnxd/microdbg/debugger" 10 | ) 11 | 12 | type FakeField interface { 13 | java.IField 14 | BindGet(getter func(obj java.IObject) any) FakeField 15 | BindSet(setter func(obj java.IObject, value any)) FakeField 16 | } 17 | 18 | type fakeField struct { 19 | cls FakeClass 20 | name string 21 | typ java.IClass 22 | mod Modifier 23 | get func(java.IObject) any 24 | set func(java.IObject, any) 25 | } 26 | 27 | func (field *fakeField) GetClass() java.IClass { 28 | return FakeFieldClass 29 | } 30 | 31 | func (field *fakeField) HashCode() java.JInt { 32 | return java.JInt(uintptr(unsafe.Pointer(field))) 33 | } 34 | 35 | func (field *fakeField) Equals(obj java.IObject) java.JBoolean { 36 | if other, ok := obj.(*fakeField); ok { 37 | return field.cls.Equals(other.cls) && field.name == other.name && field.typ.Equals(other.typ) 38 | } 39 | return false 40 | } 41 | 42 | func (field *fakeField) ToString() java.IString { 43 | var sb strings.Builder 44 | if field.mod != 0 { 45 | sb.WriteString(field.mod.String()) 46 | sb.WriteByte(' ') 47 | } 48 | sb.WriteString(field.typ.GetTypeName().String()) 49 | sb.WriteByte(' ') 50 | sb.WriteString(field.cls.GetTypeName().String()) 51 | sb.WriteByte('.') 52 | sb.WriteString(field.name) 53 | return FakeString(sb.String()) 54 | } 55 | 56 | func (field *fakeField) GetName() java.IString { 57 | return FakeString(field.name) 58 | } 59 | 60 | func (field *fakeField) GetModifiers() java.JInt { 61 | return java.JInt(field.mod) 62 | } 63 | 64 | func (field *fakeField) GetType() java.IClass { 65 | return field.typ 66 | } 67 | 68 | func (field *fakeField) Get(obj java.IObject) java.IObject { 69 | return ToObject[java.IObject](field.GetPrimitive(obj)) 70 | } 71 | 72 | func (field *fakeField) GetPrimitive(obj java.IObject) any { 73 | if field.get != nil { 74 | return field.get(obj) 75 | } else if fake, ok := obj.(FakeProperty); ok { 76 | if val, ok := fake.Get(field.name); ok { 77 | return val 78 | } 79 | } else if IsStatic(field) { 80 | if val, ok := field.cls.Get(field.name); ok { 81 | return val 82 | } 83 | } 84 | panic(fmt.Errorf("%s %w", field.ToString(), debugger.ErrNotImplemented)) 85 | } 86 | 87 | func (field *fakeField) Set(obj java.IObject, value java.IObject) { 88 | field.SetPrimitive(obj, value) 89 | } 90 | 91 | func (field *fakeField) SetPrimitive(obj java.IObject, value any) { 92 | if field.set != nil { 93 | field.set(obj, value) 94 | } else if fake, ok := obj.(FakeProperty); ok { 95 | fake.Set(field.name, value) 96 | } else if IsStatic(field) { 97 | field.cls.Set(field.name, value) 98 | } 99 | } 100 | 101 | func (field *fakeField) BindGet(get func(java.IObject) any) FakeField { 102 | field.get = get 103 | return field 104 | } 105 | 106 | func (field *fakeField) BindSet(set func(java.IObject, any)) FakeField { 107 | field.set = set 108 | return field 109 | } 110 | 111 | func (field *fakeField) parseDescriptor(cf ClassFactory, sig string) { 112 | field.typ = cf.GetClass(sig) 113 | } 114 | -------------------------------------------------------------------------------- /java/float.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | 7 | java "github.com/wnxd/microdbg-java" 8 | ) 9 | 10 | type FakeFloat java.JFloat 11 | 12 | func (f FakeFloat) GetClass() java.IClass { 13 | return FakeFloatClass 14 | } 15 | 16 | func (f FakeFloat) HashCode() java.JInt { 17 | return java.JInt(math.Float32bits(float32(f))) 18 | } 19 | 20 | func (f FakeFloat) Equals(obj java.IObject) java.JBoolean { 21 | return f == obj 22 | } 23 | 24 | func (f FakeFloat) ToString() java.IString { 25 | return FakeString(strconv.FormatFloat(float64(f), 'f', -1, 32)) 26 | } 27 | 28 | func init() { 29 | FakeFloatClass.Set("TYPE", FakeFloatTYPE) 30 | definePrimitiveMethod(FakeFloatClass, "byteValue", "()B", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 31 | return java.JByte(obj.(FakeFloat)) 32 | }) 33 | definePrimitiveMethod(FakeFloatClass, "shortValue", "()S", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 34 | return java.JShort(obj.(FakeFloat)) 35 | }) 36 | definePrimitiveMethod(FakeFloatClass, "intValue", "()I", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 37 | return java.JInt(obj.(FakeFloat)) 38 | }) 39 | definePrimitiveMethod(FakeFloatClass, "longValue ", "()J", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 40 | return java.JLong(obj.(FakeFloat)) 41 | }) 42 | definePrimitiveMethod(FakeFloatClass, "floatValue", "()F", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 43 | return java.JFloat(obj.(FakeFloat)) 44 | }) 45 | definePrimitiveMethod(FakeFloatClass, "doubleValue", "()D", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 46 | return java.JDouble(obj.(FakeFloat)) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /java/integer.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strconv" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type FakeInteger java.JInt 10 | 11 | func (i FakeInteger) GetClass() java.IClass { 12 | return FakeIntegerClass 13 | } 14 | 15 | func (i FakeInteger) HashCode() java.JInt { 16 | return java.JInt(i) 17 | } 18 | 19 | func (i FakeInteger) Equals(obj java.IObject) java.JBoolean { 20 | return i == obj 21 | } 22 | 23 | func (i FakeInteger) ToString() java.IString { 24 | return FakeString(strconv.FormatInt(int64(i), 10)) 25 | } 26 | 27 | func init() { 28 | FakeIntegerClass.Set("TYPE", FakeIntTYPE) 29 | definePrimitiveMethod(FakeIntegerClass, "byteValue", "()B", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 30 | return java.JByte(obj.(FakeInteger)) 31 | }) 32 | definePrimitiveMethod(FakeIntegerClass, "shortValue", "()S", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 33 | return java.JShort(obj.(FakeInteger)) 34 | }) 35 | definePrimitiveMethod(FakeIntegerClass, "intValue", "()I", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 36 | return java.JInt(obj.(FakeInteger)) 37 | }) 38 | definePrimitiveMethod(FakeIntegerClass, "longValue", "()J", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 39 | return java.JLong(obj.(FakeInteger)) 40 | }) 41 | definePrimitiveMethod(FakeIntegerClass, "floatValue", "()F", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 42 | return java.JFloat(obj.(FakeInteger)) 43 | }) 44 | definePrimitiveMethod(FakeIntegerClass, "doubleValue", "()D", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 45 | return java.JDouble(obj.(FakeInteger)) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /java/iterable.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "slices" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type iterator struct { 10 | hasNext func() bool 11 | next func() java.IObject 12 | remove func() 13 | } 14 | 15 | func init() { 16 | definePrimitiveMethod(FakeIterableClass, "iterator", "()Ljava/util/Iterator;", Modifier_PUBLIC|Modifier_ABSTRACT) 17 | 18 | definePrimitiveMethod(FakeIteratorClass, "hasNext", "()Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 19 | fake := obj.(FakeObject) 20 | it := fake.Value().(*iterator) 21 | return it.hasNext() 22 | }) 23 | definePrimitiveMethod(FakeIteratorClass, "next", "()Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 24 | fake := obj.(FakeObject) 25 | it := fake.Value().(*iterator) 26 | return it.next() 27 | }) 28 | definePrimitiveMethod(FakeIteratorClass, "remove", "()V", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 29 | fake := obj.(FakeObject) 30 | it := fake.Value().(*iterator) 31 | it.remove() 32 | return nil 33 | }) 34 | 35 | definePrimitiveMethod(FakeCollectionClass, "iterator", "()Ljava/util/Iterator;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 36 | fake := obj.(FakeObject) 37 | list := fake.Value().(*[]java.IObject) 38 | index := 0 39 | it := &iterator{ 40 | hasNext: func() bool { 41 | return index < len(*list) 42 | }, 43 | next: func() java.IObject { 44 | val := (*list)[index] 45 | index++ 46 | return val 47 | }, 48 | remove: func() { 49 | *list = slices.Delete(*list, index-1, index) 50 | }, 51 | } 52 | return FakeIteratorClass.NewObject(it) 53 | }) 54 | definePrimitiveMethod(FakeCollectionClass, "size", "()I", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 55 | fake := obj.(FakeObject) 56 | list := fake.Value().(*[]java.IObject) 57 | return java.JInt(len(*list)) 58 | }) 59 | definePrimitiveMethod(FakeCollectionClass, "isEmpty", "()Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 60 | fake := obj.(FakeObject) 61 | list := fake.Value().(*[]java.IObject) 62 | return len(*list) == 0 63 | }) 64 | definePrimitiveMethod(FakeCollectionClass, "contains", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 65 | fake := obj.(FakeObject) 66 | list := fake.Value().(*[]java.IObject) 67 | val := ToObject[java.IObject](args[0]) 68 | for _, v := range *list { 69 | if v.Equals(val) { 70 | return true 71 | } 72 | } 73 | return false 74 | }) 75 | definePrimitiveMethod(FakeCollectionClass, "toArray", "()[Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 76 | fake := obj.(FakeObject) 77 | list := fake.Value().(*[]java.IObject) 78 | return ArrayOf(FakeObjectArrayClass, *list) 79 | }) 80 | definePrimitiveMethod(FakeCollectionClass, "toArray", "(Ljava/lang/Object;)[Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 81 | fake := obj.(FakeObject) 82 | list := fake.Value().(*[]java.IObject) 83 | a := args[0].(java.IObjectArray) 84 | copy(a.Elements(), *list) 85 | return a 86 | }) 87 | definePrimitiveMethod(FakeCollectionClass, "add", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 88 | fake := obj.(FakeObject) 89 | list := fake.Value().(*[]java.IObject) 90 | *list = append(*list, ToObject[java.IObject](args[0])) 91 | return true 92 | }) 93 | definePrimitiveMethod(FakeCollectionClass, "remove", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 94 | fake := obj.(FakeObject) 95 | list := fake.Value().(*[]java.IObject) 96 | val := ToObject[java.IObject](args[0]) 97 | for i, v := range *list { 98 | if v.Equals(val) { 99 | *list = slices.Delete(*list, i, i+1) 100 | return true 101 | } 102 | } 103 | return false 104 | }) 105 | definePrimitiveMethod(FakeCollectionClass, "clear", "()V", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 106 | fake := obj.(FakeObject) 107 | list := fake.Value().(*[]java.IObject) 108 | clear(*list) 109 | return nil 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /java/long.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strconv" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type FakeLong java.JLong 10 | 11 | func (l FakeLong) GetClass() java.IClass { 12 | return FakeLongClass 13 | } 14 | 15 | func (l FakeLong) HashCode() java.JInt { 16 | v := uint64(l) 17 | return java.JInt(v ^ (v >> 32)) 18 | } 19 | 20 | func (l FakeLong) Equals(obj java.IObject) java.JBoolean { 21 | return l == obj 22 | } 23 | 24 | func (l FakeLong) ToString() java.IString { 25 | return FakeString(strconv.FormatInt(int64(l), 10)) 26 | } 27 | 28 | func init() { 29 | FakeLongClass.Set("TYPE", FakeLongTYPE) 30 | definePrimitiveMethod(FakeLongClass, "byteValue", "()B", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 31 | return java.JByte(obj.(FakeLong)) 32 | }) 33 | definePrimitiveMethod(FakeLongClass, "shortValue", "()S", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 34 | return java.JShort(obj.(FakeLong)) 35 | }) 36 | definePrimitiveMethod(FakeLongClass, "intValue", "()I", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 37 | return java.JInt(obj.(FakeLong)) 38 | }) 39 | definePrimitiveMethod(FakeLongClass, "longValue", "()J", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 40 | return java.JLong(obj.(FakeLong)) 41 | }) 42 | definePrimitiveMethod(FakeLongClass, "floatValue", "()F", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 43 | return java.JFloat(obj.(FakeLong)) 44 | }) 45 | definePrimitiveMethod(FakeLongClass, "doubleValue", "()D", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 46 | return java.JDouble(obj.(FakeLong)) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /java/map.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "maps" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type Map = map[java.IObject]java.IObject 10 | 11 | type entry struct { 12 | key, value java.IObject 13 | } 14 | 15 | func MapOf(mp Map) java.IObject { 16 | return FakeMapClass.NewObject(mp) 17 | } 18 | 19 | func init() { 20 | definePrimitiveMethod(FakeMapClass, "size", "()I", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, _ ...any) any { 21 | fake := obj.(FakeObject) 22 | mp := fake.Value().(Map) 23 | return java.JInt(len(mp)) 24 | }) 25 | definePrimitiveMethod(FakeMapClass, "isEmpty", "()Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, _ ...any) any { 26 | fake := obj.(FakeObject) 27 | mp := fake.Value().(Map) 28 | return len(mp) == 0 29 | }) 30 | definePrimitiveMethod(FakeMapClass, "containsKey", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 31 | fake := obj.(FakeObject) 32 | mp := fake.Value().(Map) 33 | _, ok := mp[ToObject[java.IObject](args[0])] 34 | return ok 35 | }) 36 | definePrimitiveMethod(FakeMapClass, "containsValue", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 37 | fake := obj.(FakeObject) 38 | mp := fake.Value().(Map) 39 | val := ToObject[java.IObject](args[0]) 40 | for _, v := range mp { 41 | if v.Equals(val) { 42 | return true 43 | } 44 | } 45 | return false 46 | }) 47 | definePrimitiveMethod(FakeMapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 48 | fake := obj.(FakeObject) 49 | mp := fake.Value().(Map) 50 | return mp[ToObject[java.IObject](args[0])] 51 | }) 52 | definePrimitiveMethod(FakeMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 53 | fake := obj.(FakeObject) 54 | mp := fake.Value().(Map) 55 | key := ToObject[java.IObject](args[0]) 56 | old := mp[key] 57 | mp[key] = ToObject[java.IObject](args[1]) 58 | return old 59 | }) 60 | definePrimitiveMethod(FakeMapClass, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 61 | fake := obj.(FakeObject) 62 | mp := fake.Value().(Map) 63 | key := ToObject[java.IObject](args[0]) 64 | old := mp[key] 65 | delete(mp, key) 66 | return old 67 | }) 68 | definePrimitiveMethod(FakeMapClass, "clear", "()V", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 69 | fake := obj.(FakeObject) 70 | mp := fake.Value().(Map) 71 | clear(mp) 72 | return nil 73 | }) 74 | definePrimitiveMethod(FakeMapClass, "keySet", "()Ljava/util/Set;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 75 | fake := obj.(FakeObject) 76 | mp := fake.Value().(Map) 77 | keys := make(map[java.IObject]struct{}, len(mp)) 78 | for k := range mp { 79 | keys[k] = struct{}{} 80 | } 81 | return FakeSetClass.NewObject(keys) 82 | }) 83 | definePrimitiveMethod(FakeMapClass, "values", "()Ljava/util/Collection;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 84 | fake := obj.(FakeObject) 85 | mp := fake.Value().(Map) 86 | maps.Values(mp) 87 | vals := make([]java.IObject, 0, len(mp)) 88 | for _, v := range mp { 89 | vals = append(vals, v) 90 | } 91 | return FakeCollectionClass.NewObject(&vals) 92 | }) 93 | definePrimitiveMethod(FakeMapClass, "entrySet", "()Ljava/util/Set;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 94 | fake := obj.(FakeObject) 95 | mp := fake.Value().(Map) 96 | entries := make(map[java.IObject]struct{}, len(mp)) 97 | for k, v := range mp { 98 | entries[FakeMapEntryClass.NewObject(&entry{k, v})] = struct{}{} 99 | } 100 | return FakeSetClass.NewObject(entries) 101 | }) 102 | 103 | definePrimitiveMethod(FakeMapEntryClass, "getKey", "()Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 104 | fake := obj.(FakeObject) 105 | entry := fake.Value().(*entry) 106 | return entry.key 107 | }) 108 | definePrimitiveMethod(FakeMapEntryClass, "getValue", "()Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 109 | fake := obj.(FakeObject) 110 | entry := fake.Value().(*entry) 111 | return entry.value 112 | }) 113 | definePrimitiveMethod(FakeMapEntryClass, "setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 114 | fake := obj.(FakeObject) 115 | entry := fake.Value().(*entry) 116 | old := entry.value 117 | entry.value = ToObject[java.IObject](args[0]) 118 | return old 119 | }) 120 | 121 | definePrimitiveMethod(FakeHashMapClass, ConstructorMethodName, "(I)V", Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 122 | initialCapacity := args[0].(java.JInt) 123 | return FakeHashMapClass.NewObject(make(Map, initialCapacity)) 124 | }) 125 | } 126 | -------------------------------------------------------------------------------- /java/method.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unsafe" 7 | 8 | java "github.com/wnxd/microdbg-java" 9 | "github.com/wnxd/microdbg/debugger" 10 | ) 11 | 12 | const ConstructorMethodName = "" 13 | 14 | type FakeMethod interface { 15 | java.IMethod 16 | GetReturnType() java.IClass 17 | Descriptor() string 18 | IsConstructor() bool 19 | BindCall(f func(obj java.IObject, args ...any) any) FakeMethod 20 | } 21 | 22 | type fakeMethod struct { 23 | cls java.IClass 24 | name string 25 | sig string 26 | params []java.IClass 27 | ret java.IClass 28 | mod Modifier 29 | f func(java.IObject, ...any) any 30 | } 31 | 32 | func (method *fakeMethod) GetClass() java.IClass { 33 | if method.IsConstructor() { 34 | return FakeConstructorClass 35 | } 36 | return FakeMethodClass 37 | } 38 | 39 | func (method *fakeMethod) HashCode() java.JInt { 40 | return java.JInt(uintptr(unsafe.Pointer(method))) 41 | } 42 | 43 | func (method *fakeMethod) Equals(obj java.IObject) java.JBoolean { 44 | if other, ok := obj.(*fakeMethod); ok { 45 | return method.cls.Equals(other.cls) && method.name == other.name 46 | } 47 | return false 48 | } 49 | 50 | func (method *fakeMethod) ToString() java.IString { 51 | var sb strings.Builder 52 | if method.mod != 0 { 53 | sb.WriteString(method.mod.String()) 54 | sb.WriteByte(' ') 55 | } 56 | if method.IsConstructor() { 57 | sb.WriteString(method.cls.GetTypeName().String()) 58 | } else { 59 | sb.WriteString(method.ret.GetTypeName().String()) 60 | sb.WriteByte(' ') 61 | sb.WriteString(method.cls.GetTypeName().String()) 62 | sb.WriteByte('.') 63 | sb.WriteString(method.name) 64 | } 65 | sb.WriteByte('(') 66 | for i, v := range method.params { 67 | if i != 0 { 68 | sb.WriteByte(',') 69 | } 70 | sb.WriteString(v.GetTypeName().String()) 71 | } 72 | sb.WriteByte(')') 73 | return FakeString(sb.String()) 74 | } 75 | 76 | func (method *fakeMethod) GetName() java.IString { 77 | if method.IsConstructor() { 78 | return method.cls.GetName() 79 | } 80 | return FakeString(method.name) 81 | } 82 | 83 | func (method *fakeMethod) GetModifiers() java.JInt { 84 | return java.JInt(method.mod) 85 | } 86 | 87 | func (method *fakeMethod) GetParameterTypes() []java.IClass { 88 | return method.params 89 | } 90 | 91 | func (method *fakeMethod) GetParameterCount() java.JInt { 92 | return java.JInt(len(method.params)) 93 | } 94 | 95 | func (method *fakeMethod) Call(obj java.IObject, args ...any) java.IObject { 96 | return ToObject[java.IObject](method.CallPrimitive(obj, args...)) 97 | } 98 | 99 | func (method *fakeMethod) CallPrimitive(obj java.IObject, args ...any) any { 100 | if method.f == nil { 101 | panic(fmt.Errorf("%s %w", method.ToString(), debugger.ErrNotImplemented)) 102 | } 103 | return method.f(obj, args...) 104 | } 105 | 106 | func (method *fakeMethod) GetReturnType() java.IClass { 107 | if method.IsConstructor() { 108 | return method.cls 109 | } 110 | return method.ret 111 | } 112 | 113 | func (method *fakeMethod) Descriptor() string { 114 | return method.sig 115 | } 116 | 117 | func (method *fakeMethod) IsConstructor() bool { 118 | return method.name == ConstructorMethodName 119 | } 120 | 121 | func (method *fakeMethod) BindCall(f func(java.IObject, ...any) any) FakeMethod { 122 | method.f = f 123 | return method 124 | } 125 | 126 | func (method *fakeMethod) parseDescriptor(cf ClassFactory, sig string) { 127 | method.sig = sig 128 | method.ret = FakeVoidTYPE 129 | var dim int 130 | for i := 1; i < len(sig); i++ { 131 | var cls FakeClass 132 | switch sig[i] { 133 | case 'Z': 134 | cls = FakeBooleanTYPE 135 | case 'B': 136 | cls = FakeByteTYPE 137 | case 'C': 138 | cls = FakeCharTYPE 139 | case 'S': 140 | cls = FakeShortTYPE 141 | case 'I': 142 | cls = FakeIntTYPE 143 | case 'J': 144 | cls = FakeLongTYPE 145 | case 'F': 146 | cls = FakeFloatTYPE 147 | case 'D': 148 | cls = FakeDoubleTYPE 149 | case 'V': 150 | cls = FakeVoidTYPE 151 | case 'L': 152 | n := strings.IndexByte(sig[i:], ';') + i 153 | cls = cf.GetClass(sig[i+1 : n]) 154 | i = n 155 | case '[': 156 | dim++ 157 | continue 158 | case ')': 159 | method.ret = cf.GetClass(sig[i+1:]) 160 | return 161 | } 162 | for n := 0; n < dim; n++ { 163 | cls = cf.ArrayOf(cls) 164 | } 165 | dim = 0 166 | method.params = append(method.params, cls) 167 | } 168 | } 169 | 170 | func GetMethodDescriptor(method java.IMethod) (string, string) { 171 | if i, ok := method.(interface{ Descriptor() string }); ok { 172 | return method.GetName().String(), i.Descriptor() 173 | } 174 | var sb strings.Builder 175 | sb.WriteByte('(') 176 | for _, typ := range method.GetParameterTypes() { 177 | sb.WriteString(typ.DescriptorString().String()) 178 | } 179 | sb.WriteByte(')') 180 | if m, ok := method.(interface{ GetReturnType() java.IClass }); ok { 181 | sb.WriteString(m.GetReturnType().DescriptorString().String()) 182 | } else { 183 | sb.WriteByte('V') 184 | } 185 | return method.GetName().String(), sb.String() 186 | } 187 | -------------------------------------------------------------------------------- /java/modifier.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strings" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type Modifier java.JInt 10 | 11 | const ( 12 | Modifier_PUBLIC Modifier = 1 << iota 13 | Modifier_PRIVATE 14 | Modifier_PROTECTED 15 | Modifier_STATIC 16 | Modifier_FINAL 17 | Modifier_SYNCHRONIZED 18 | Modifier_VOLATILE 19 | Modifier_TRANSIENT 20 | Modifier_NATIVE 21 | Modifier_INTERFACE 22 | Modifier_ABSTRACT 23 | Modifier_STRICT 24 | ) 25 | 26 | func (mod Modifier) String() string { 27 | var arr []string 28 | if (mod & Modifier_PUBLIC) != 0 { 29 | arr = append(arr, "public") 30 | } 31 | if (mod & Modifier_PROTECTED) != 0 { 32 | arr = append(arr, "protected") 33 | } 34 | if (mod & Modifier_PRIVATE) != 0 { 35 | arr = append(arr, "private") 36 | } 37 | if (mod & Modifier_ABSTRACT) != 0 { 38 | arr = append(arr, "abstract") 39 | } 40 | if (mod & Modifier_STATIC) != 0 { 41 | arr = append(arr, "static") 42 | } 43 | if (mod & Modifier_FINAL) != 0 { 44 | arr = append(arr, "final") 45 | } 46 | if (mod & Modifier_TRANSIENT) != 0 { 47 | arr = append(arr, "transient") 48 | } 49 | if (mod & Modifier_VOLATILE) != 0 { 50 | arr = append(arr, "volatile") 51 | } 52 | if (mod & Modifier_SYNCHRONIZED) != 0 { 53 | arr = append(arr, "synchronized") 54 | } 55 | if (mod & Modifier_NATIVE) != 0 { 56 | arr = append(arr, "native") 57 | } 58 | if (mod & Modifier_STRICT) != 0 { 59 | arr = append(arr, "strictfp") 60 | } 61 | if (mod & Modifier_INTERFACE) != 0 { 62 | arr = append(arr, "interface") 63 | } 64 | return strings.Join(arr, " ") 65 | } 66 | 67 | func IsStatic(v interface{ GetModifiers() java.JInt }) bool { 68 | return Modifier(v.GetModifiers())&Modifier_STATIC != 0 69 | } 70 | 71 | func IsNative(v interface{ GetModifiers() java.JInt }) bool { 72 | return Modifier(v.GetModifiers())&Modifier_NATIVE != 0 73 | } 74 | -------------------------------------------------------------------------------- /java/number.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | func init() { 4 | definePrimitiveMethod(FakeNumberClass, "byteValue", "()B", Modifier_PUBLIC|Modifier_ABSTRACT) 5 | definePrimitiveMethod(FakeNumberClass, "shortValue", "()S", Modifier_PUBLIC|Modifier_ABSTRACT) 6 | definePrimitiveMethod(FakeNumberClass, "intValue", "()I", Modifier_PUBLIC|Modifier_ABSTRACT) 7 | definePrimitiveMethod(FakeNumberClass, "longValue", "()J", Modifier_PUBLIC|Modifier_ABSTRACT) 8 | definePrimitiveMethod(FakeNumberClass, "floatValue", "()F", Modifier_PUBLIC|Modifier_ABSTRACT) 9 | definePrimitiveMethod(FakeNumberClass, "doubleValue", "()D", Modifier_PUBLIC|Modifier_ABSTRACT) 10 | } 11 | -------------------------------------------------------------------------------- /java/object.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "unsafe" 7 | 8 | java "github.com/wnxd/microdbg-java" 9 | ) 10 | 11 | type FakeObject interface { 12 | java.IObject 13 | Value() any 14 | FakeProperty 15 | FindMethod(name, sig string) FakeMethod 16 | } 17 | 18 | type fakeObject struct { 19 | cls FakeClass 20 | val any 21 | fakeProperty 22 | } 23 | 24 | func (obj *fakeObject) GetClass() java.IClass { 25 | return obj.cls 26 | } 27 | 28 | func (obj *fakeObject) HashCode() java.JInt { 29 | return java.JInt(uintptr(unsafe.Pointer(obj))) 30 | } 31 | 32 | func (obj *fakeObject) Equals(other java.IObject) java.JBoolean { 33 | return obj == other 34 | } 35 | 36 | func (obj *fakeObject) ToString() java.IString { 37 | return FakeString(obj.GetClass().GetName().String() + "@" + strconv.FormatInt(int64(obj.HashCode()), 16)) 38 | } 39 | 40 | func (obj *fakeObject) GetMessage() java.IString { 41 | if msg, ok := obj.Get("detailMessage"); ok { 42 | return msg.(java.IString) 43 | } 44 | return nil 45 | } 46 | 47 | func (obj *fakeObject) Value() any { 48 | return obj.val 49 | } 50 | 51 | func (obj *fakeObject) FindMethod(name, sig string) FakeMethod { 52 | method := obj.cls.FindMethod(name, sig) 53 | if method == nil || IsStatic(method) { 54 | return nil 55 | } 56 | return method 57 | } 58 | 59 | func ToObject[O java.IObject](v any) O { 60 | r, _ := v.(O) 61 | return r 62 | } 63 | 64 | func init() { 65 | definePrimitiveMethod(FakeObjectClass, "getClass", "()Ljava/lang/Class;", Modifier_PUBLIC|Modifier_FINAL|Modifier_NATIVE).BindCall(func(obj java.IObject, _ ...any) any { 66 | return obj.GetClass() 67 | }) 68 | definePrimitiveMethod(FakeObjectClass, "hashCode", "()I", Modifier_PUBLIC|Modifier_NATIVE).BindCall(func(obj java.IObject, _ ...any) any { 69 | return obj.HashCode() 70 | }) 71 | definePrimitiveMethod(FakeObjectClass, "equals", "(Ljava/lang/Object;)Z", Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 72 | return obj.Equals(ToObject[java.IObject](args[0])) 73 | }) 74 | definePrimitiveMethod(FakeObjectClass, "clone", "()Ljava/lang/Object;", Modifier_PROTECTED|Modifier_NATIVE).BindCall(func(obj java.IObject, args ...any) any { 75 | panic(fmt.Errorf("[Object.clone] %s not implemented", obj.GetClass().GetName())) 76 | }) 77 | definePrimitiveMethod(FakeObjectClass, "toString", "()Ljava/lang/String;", Modifier_PUBLIC).BindCall(func(obj java.IObject, args ...any) any { 78 | return obj.ToString() 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /java/property.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import "sync" 4 | 5 | type FakeProperty interface { 6 | Get(name string) (value any, ok bool) 7 | Set(name string, value any) 8 | } 9 | 10 | type fakeProperty struct { 11 | data sync.Map 12 | } 13 | 14 | func (prop *fakeProperty) Get(name string) (any, bool) { 15 | return prop.data.Load(HashCode(name)) 16 | } 17 | 18 | func (prop *fakeProperty) Set(name string, value any) { 19 | prop.data.Store(HashCode(name), value) 20 | } 21 | -------------------------------------------------------------------------------- /java/set.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | java "github.com/wnxd/microdbg-java" 5 | ) 6 | 7 | func init() { 8 | definePrimitiveMethod(FakeSetClass, "iterator", "()Ljava/util/Iterator;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 9 | fake := obj.(FakeObject) 10 | set := fake.Value().(map[java.IObject]struct{}) 11 | keys := make([]java.IObject, 0, len(set)) 12 | for k := range set { 13 | keys = append(keys, k) 14 | } 15 | index := 0 16 | it := &iterator{ 17 | hasNext: func() bool { 18 | return index < len(keys) 19 | }, 20 | next: func() java.IObject { 21 | val := keys[index] 22 | index++ 23 | return val 24 | }, 25 | remove: func() { 26 | delete(set, keys[index-1]) 27 | }, 28 | } 29 | return FakeIteratorClass.NewObject(it) 30 | }) 31 | definePrimitiveMethod(FakeSetClass, "size", "()I", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 32 | fake := obj.(FakeObject) 33 | set := fake.Value().(map[java.IObject]struct{}) 34 | return java.JInt(len(set)) 35 | }) 36 | definePrimitiveMethod(FakeSetClass, "isEmpty", "()Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 37 | fake := obj.(FakeObject) 38 | set := fake.Value().(map[java.IObject]struct{}) 39 | return len(set) == 0 40 | }) 41 | definePrimitiveMethod(FakeSetClass, "contains", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 42 | fake := obj.(FakeObject) 43 | set := fake.Value().(map[java.IObject]struct{}) 44 | _, ok := set[ToObject[java.IObject](args[0])] 45 | return ok 46 | }) 47 | definePrimitiveMethod(FakeSetClass, "toArray", "()[Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 48 | fake := obj.(FakeObject) 49 | set := fake.Value().(map[java.IObject]struct{}) 50 | keys := make([]java.IObject, 0, len(set)) 51 | for k := range set { 52 | keys = append(keys, k) 53 | } 54 | return ArrayOf(FakeObjectArrayClass, keys) 55 | }) 56 | definePrimitiveMethod(FakeSetClass, "toArray", "(Ljava/lang/Object;)[Ljava/lang/Object;", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 57 | fake := obj.(FakeObject) 58 | set := fake.Value().(map[java.IObject]struct{}) 59 | a := args[0].(java.IObjectArray) 60 | arr := a.Elements() 61 | i := 0 62 | for v := range set { 63 | if i >= len(arr) { 64 | break 65 | } 66 | arr[i] = v 67 | i++ 68 | } 69 | return a 70 | }) 71 | definePrimitiveMethod(FakeSetClass, "add", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 72 | fake := obj.(FakeObject) 73 | set := fake.Value().(map[java.IObject]struct{}) 74 | set[ToObject[java.IObject](args[0])] = struct{}{} 75 | return true 76 | }) 77 | definePrimitiveMethod(FakeSetClass, "remove", "(Ljava/lang/Object;)Z", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 78 | fake := obj.(FakeObject) 79 | set := fake.Value().(map[java.IObject]struct{}) 80 | delete(set, ToObject[java.IObject](args[0])) 81 | return true 82 | }) 83 | definePrimitiveMethod(FakeSetClass, "clear", "()V", Modifier_PUBLIC|Modifier_ABSTRACT).BindCall(func(obj java.IObject, args ...any) any { 84 | fake := obj.(FakeObject) 85 | set := fake.Value().(map[java.IObject]struct{}) 86 | clear(set) 87 | return nil 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /java/short.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strconv" 5 | 6 | java "github.com/wnxd/microdbg-java" 7 | ) 8 | 9 | type FakeShort java.JShort 10 | 11 | func (s FakeShort) GetClass() java.IClass { 12 | return FakeShortClass 13 | } 14 | 15 | func (s FakeShort) HashCode() java.JInt { 16 | return java.JInt(s) 17 | } 18 | 19 | func (s FakeShort) Equals(obj java.IObject) java.JBoolean { 20 | return s == obj 21 | } 22 | 23 | func (s FakeShort) ToString() java.IString { 24 | return FakeString(strconv.FormatInt(int64(s), 10)) 25 | } 26 | 27 | func init() { 28 | FakeShortClass.Set("TYPE", FakeShortTYPE) 29 | definePrimitiveMethod(FakeShortClass, "byteValue", "()B", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 30 | return java.JByte(obj.(FakeShort)) 31 | }) 32 | definePrimitiveMethod(FakeShortClass, "shortValue", "()S", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 33 | return java.JShort(obj.(FakeShort)) 34 | }) 35 | definePrimitiveMethod(FakeShortClass, "intValue", "()I", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 36 | return java.JInt(obj.(FakeShort)) 37 | }) 38 | definePrimitiveMethod(FakeShortClass, "longValue", "()J", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 39 | return java.JLong(obj.(FakeShort)) 40 | }) 41 | definePrimitiveMethod(FakeShortClass, "floatValue", "()F", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 42 | return java.JFloat(obj.(FakeShort)) 43 | }) 44 | definePrimitiveMethod(FakeShortClass, "doubleValue", "()D", Modifier_PUBLIC).BindCall(func(obj java.IObject, _ ...any) any { 45 | return java.JDouble(obj.(FakeShort)) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /java/string.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "strconv" 5 | "unsafe" 6 | 7 | java "github.com/wnxd/microdbg-java" 8 | ) 9 | 10 | type FakeString string 11 | 12 | func (str FakeString) GetClass() java.IClass { 13 | return FakeStringClass 14 | } 15 | 16 | func (str FakeString) HashCode() java.JInt { 17 | ptr := (*struct{ data unsafe.Pointer })(unsafe.Pointer((&str))).data 18 | return int32(uintptr(ptr)) 19 | } 20 | 21 | func (str FakeString) Equals(obj java.IObject) java.JBoolean { 22 | return str == obj 23 | } 24 | 25 | func (str FakeString) ToString() java.IString { 26 | return FakeString(str.GetClass().GetName().String() + "@" + strconv.FormatInt(int64(str.HashCode()), 16)) 27 | } 28 | 29 | func (str FakeString) Length() java.JInt { 30 | return java.JInt(len(str)) 31 | } 32 | 33 | func (str FakeString) String() string { 34 | return string(str) 35 | } 36 | -------------------------------------------------------------------------------- /jni.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | gava "github.com/wnxd/microdbg-android/java" 5 | java "github.com/wnxd/microdbg-java" 6 | ) 7 | 8 | type JNIContext interface { 9 | ClassFactory() gava.ClassFactory 10 | Throw(java.IThrowable) java.JInt 11 | ExceptionOccurred() java.IThrowable 12 | ExceptionDescribe() 13 | ExceptionClear() 14 | FatalError(string) 15 | } 16 | 17 | type JNIEnv interface { 18 | DefineClass(JNIContext, string, java.IObject, []java.JByte) (java.IClass, error) 19 | FindClass(JNIContext, string) (java.IClass, error) 20 | ThrowNew(JNIContext, java.IClass, string) (java.JInt, error) 21 | GetMethod(JNIContext, java.IClass, string, string) (java.IMethod, error) 22 | GetField(JNIContext, java.IClass, string, string) (java.IField, error) 23 | GetStaticMethod(JNIContext, java.IClass, string, string) (java.IMethod, error) 24 | GetStaticField(JNIContext, java.IClass, string, string) (java.IField, error) 25 | NewString(JNIContext, []java.JChar) (java.IString, error) 26 | NewStringUTF(JNIContext, string) (java.IString, error) 27 | NewObjectArray(JNIContext, java.JSize, java.IClass, java.IObject) (java.IObjectArray, error) 28 | NewBooleanArray(JNIContext, java.JSize) (java.IBooleanArray, error) 29 | NewByteArray(JNIContext, java.JSize) (java.IByteArray, error) 30 | NewCharArray(JNIContext, java.JSize) (java.ICharArray, error) 31 | NewShortArray(JNIContext, java.JSize) (java.IShortArray, error) 32 | NewIntArray(JNIContext, java.JSize) (java.IIntArray, error) 33 | NewLongArray(JNIContext, java.JSize) (java.ILongArray, error) 34 | NewFloatArray(JNIContext, java.JSize) (java.IFloatArray, error) 35 | NewDoubleArray(JNIContext, java.JSize) (java.IDoubleArray, error) 36 | } 37 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | java "github.com/wnxd/microdbg-java" 8 | ) 9 | 10 | type Module interface { 11 | io.Closer 12 | Name() string 13 | BaseAddr() uint64 14 | FindSymbol(name string) (Symbol, error) 15 | Symbols(yield func(Symbol) bool) 16 | CallEntry(ctx context.Context) error 17 | CallOnLoad(ctx context.Context) (java.JInt, error) 18 | FindNativeMethod(vm java.JavaVM, clazz java.IClass, name, sig string) (NativeMethod, error) 19 | Dump(w io.Writer) error 20 | } 21 | 22 | type NativeMethod = func(obj java.IObject, args ...any) any 23 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | type Option func(Runtime) error 4 | 5 | func SetOption(v Runtime, options ...Option) error { 6 | for _, option := range options { 7 | if err := option(v); err != nil { 8 | return err 9 | } 10 | } 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /package.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | ) 7 | 8 | type Package interface { 9 | Name() string 10 | Label() string 11 | Version() (name string, code int) 12 | UsesSdk() (min, target int) 13 | Permission() []string 14 | CodePath() string 15 | LibraryDir() string 16 | FilesDir() string 17 | Certificate() []*x509.Certificate 18 | LoadModule(ctx context.Context, art Runtime, name string) (Module, error) 19 | } 20 | -------------------------------------------------------------------------------- /res/res.go: -------------------------------------------------------------------------------- 1 | package res 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/xml" 6 | "io" 7 | "math" 8 | "strconv" 9 | "unsafe" 10 | ) 11 | 12 | const ( 13 | RES_NULL_TYPE = 0x0000 14 | RES_STRING_POOL_TYPE = 0x0001 15 | RES_TABLE_TYPE = 0x0002 16 | RES_XML_TYPE = 0x0003 17 | 18 | RES_XML_FIRST_CHUNK_TYPE = 0x0100 19 | RES_XML_START_NAMESPACE_TYPE = 0x0100 20 | RES_XML_END_NAMESPACE_TYPE = 0x0101 21 | RES_XML_START_ELEMENT_TYPE = 0x0102 22 | RES_XML_END_ELEMENT_TYPE = 0x0103 23 | RES_XML_CDATA_TYPE = 0x0104 24 | RES_XML_LAST_CHUNK_TYPE = 0x017f 25 | 26 | RES_XML_RESOURCE_MAP_TYPE = 0x0180 27 | 28 | RES_TABLE_PACKAGE_TYPE = 0x0200 29 | RES_TABLE_TYPE_TYPE = 0x0201 30 | RES_TABLE_TYPE_SPEC_TYPE = 0x0202 31 | ) 32 | 33 | type ResChunkHeader struct { 34 | Type uint16 35 | HeaderSize uint16 36 | Size uint32 37 | } 38 | 39 | type ResStringPoolHeader struct { 40 | Header ResChunkHeader 41 | StringCount uint32 42 | StyleCount uint32 43 | Flags uint32 44 | StringsStart uint32 45 | StylesStart uint32 46 | } 47 | 48 | type ResStringPoolRef int32 49 | type ResTableRef uint32 50 | 51 | type ResValue struct { 52 | Size uint16 53 | Res0 uint8 54 | DataType uint8 55 | Data uint32 56 | } 57 | 58 | type ResXMLTreeHeader struct { 59 | Header ResChunkHeader 60 | } 61 | 62 | type ResXMLTreeNode struct { 63 | Header ResChunkHeader 64 | LineNumber uint32 65 | Comment ResStringPoolRef 66 | } 67 | 68 | type ResXMLTreeCdataExt struct { 69 | Data ResStringPoolRef 70 | TypedData ResValue 71 | } 72 | 73 | type ResXMLTreeNamespaceExt struct { 74 | Prefix ResStringPoolRef 75 | Uri ResStringPoolRef 76 | } 77 | 78 | type ResXMLTreeEndElementExt struct { 79 | Ns ResStringPoolRef 80 | Name ResStringPoolRef 81 | } 82 | 83 | type ResXMLTreeAttrExt struct { 84 | Ns ResStringPoolRef 85 | Name ResStringPoolRef 86 | AttributeStart uint16 87 | AttributeSize uint16 88 | AttributeCount uint16 89 | IdIndex uint16 90 | ClassIndex uint16 91 | StyleIndex uint16 92 | } 93 | 94 | type ResXMLTreeAttribute struct { 95 | Ns ResStringPoolRef 96 | Name ResStringPoolRef 97 | RawValue ResStringPoolRef 98 | TypedValue ResValue 99 | } 100 | 101 | type ResTableHeader struct { 102 | Header ResChunkHeader 103 | PackageCount uint32 104 | } 105 | 106 | type ResTablePackage struct { 107 | Header ResChunkHeader 108 | Id uint32 109 | Name [128]uint16 110 | TypeStrings uint32 111 | LastPublicType uint32 112 | KeyStrings uint32 113 | LastPublicKey uint32 114 | TypeIdOffset uint32 115 | } 116 | 117 | type ResTableTypeSpec struct { 118 | Header ResChunkHeader 119 | Id uint8 120 | Res0 uint8 121 | Res1 uint16 122 | EntryCount uint32 123 | } 124 | 125 | type ResTableType struct { 126 | Header ResChunkHeader 127 | Id uint8 128 | Flags uint8 129 | Reserved uint16 130 | EntryCount uint32 131 | EntriesStart uint32 132 | Config ResTableConfig 133 | } 134 | 135 | type ResTableConfig struct { 136 | Size uint32 137 | Imsi uint32 138 | Locale uint32 139 | ScreenType uint32 140 | Input uint32 141 | ScreenSize uint32 142 | Version uint32 143 | ScreenConfig uint32 144 | ScreenSizeDp uint32 145 | LocaleScript [4]byte 146 | LocaleVariant [8]byte 147 | ScreenConfig2 uint32 148 | LocaleScriptWasComputed bool 149 | _ [3]byte 150 | LocaleNumberingSystem [8]byte 151 | } 152 | 153 | type ResTableEntry struct { 154 | Size uint16 155 | Flags uint16 156 | Key ResStringPoolRef 157 | } 158 | 159 | type ResTableMapEntry struct { 160 | Parent ResTableRef 161 | Count uint32 162 | } 163 | 164 | type ResTableMap struct { 165 | Name ResTableRef 166 | Value ResValue 167 | } 168 | 169 | func NewXMLDecoder(r io.Reader) (*xml.Decoder, error) { 170 | var header ResXMLTreeHeader 171 | err := binary.Read(r, binary.LittleEndian, &header) 172 | if err != nil { 173 | return nil, err 174 | } else if header.Header.Type != RES_XML_TYPE || header.Header.HeaderSize != uint16(unsafe.Sizeof(ResXMLTreeHeader{})) { 175 | return nil, xml.UnmarshalError("invalid format") 176 | } 177 | d := &xmlDecoder{r: r} 178 | if err = d.init(); err != nil { 179 | return nil, err 180 | } 181 | return xml.NewTokenDecoder(d), nil 182 | } 183 | 184 | func ParseTable(r io.Reader) (Table, error) { 185 | t := make(Table) 186 | err := t.parse(r) 187 | if err != nil { 188 | return nil, err 189 | } 190 | return t, nil 191 | } 192 | 193 | func (v ResValue) Display(pool *stringPool) string { 194 | const ( 195 | TYPE_NULL = 0x00 196 | TYPE_REFERENCE = 0x01 197 | TYPE_ATTRIBUTE = 0x02 198 | TYPE_STRING = 0x03 199 | TYPE_FLOAT = 0x04 200 | TYPE_DIMENSION = 0x05 201 | TYPE_FRACTION = 0x06 202 | 203 | TYPE_FIRST_INT = 0x10 204 | 205 | TYPE_INT_DEC = 0x10 206 | TYPE_INT_HEX = 0x11 207 | TYPE_INT_BOOLEAN = 0x12 208 | 209 | TYPE_FIRST_COLOR_INT = 0x1c 210 | 211 | TYPE_INT_COLOR_ARGB8 = 0x1c 212 | TYPE_INT_COLOR_RGB8 = 0x1d 213 | TYPE_INT_COLOR_ARGB4 = 0x1e 214 | TYPE_INT_COLOR_RGB4 = 0x1f 215 | 216 | TYPE_LAST_COLOR_INT = 0x1f 217 | 218 | TYPE_LAST_INT = 0x1f 219 | ) 220 | switch v.DataType { 221 | case TYPE_STRING: 222 | return pool.Get(int(v.Data)) 223 | case TYPE_FLOAT: 224 | return strconv.FormatFloat(float64(math.Float32frombits(v.Data)), 'f', -1, 32) 225 | case TYPE_INT_DEC: 226 | return strconv.FormatInt(int64(v.Data), 10) 227 | case TYPE_REFERENCE, TYPE_INT_HEX: 228 | return "0x" + strconv.FormatInt(int64(v.Data), 16) 229 | case TYPE_INT_BOOLEAN: 230 | return strconv.FormatBool(v.Data != 0) 231 | } 232 | return "" 233 | } 234 | -------------------------------------------------------------------------------- /res/string.go: -------------------------------------------------------------------------------- 1 | package res 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/xml" 7 | "io" 8 | "unicode/utf16" 9 | "unsafe" 10 | ) 11 | 12 | type stringPool struct { 13 | utf8 bool 14 | start uint32 15 | offsets []uint32 16 | buff []byte 17 | } 18 | 19 | func (sp *stringPool) Len() int { 20 | return len(sp.offsets) 21 | } 22 | 23 | func (sp *stringPool) Get(index int) string { 24 | if index < 0 || index >= len(sp.offsets) { 25 | return "" 26 | } 27 | offset := sp.start + sp.offsets[index] 28 | r := bytes.NewReader(sp.buff[offset:]) 29 | var lens [2]byte 30 | _, err := io.ReadFull(r, lens[:]) 31 | if err != nil { 32 | return "" 33 | } 34 | if sp.utf8 { 35 | buf := make([]byte, lens[1]) 36 | io.ReadFull(r, buf) 37 | return string(buf) 38 | } else { 39 | buf := make([]uint16, lens[0]) 40 | binary.Read(r, binary.LittleEndian, buf) 41 | return string(utf16.Decode(buf)) 42 | } 43 | } 44 | 45 | func (sp *stringPool) Range(yield func(int, string) bool) { 46 | for i := 0; i < sp.Len(); i++ { 47 | if !yield(i, sp.Get(i)) { 48 | break 49 | } 50 | } 51 | } 52 | 53 | func (sp *stringPool) parse(r io.Reader) error { 54 | var pool ResStringPoolHeader 55 | err := binary.Read(r, binary.LittleEndian, &pool) 56 | if err != nil { 57 | return err 58 | } else if pool.Header.Type != RES_STRING_POOL_TYPE || pool.Header.HeaderSize != uint16(unsafe.Sizeof(ResStringPoolHeader{})) { 59 | return xml.UnmarshalError("invalid format") 60 | } 61 | const ( 62 | SORTED_FLAG = 1 << 0 63 | UTF8_FLAG = 1 << 8 64 | ) 65 | sp.utf8 = pool.Flags&UTF8_FLAG != 0 66 | sp.start = pool.StringsStart - uint32(pool.Header.HeaderSize) - (pool.StringCount+pool.StyleCount)*4 67 | sp.offsets = make([]uint32, pool.StringCount) 68 | err = binary.Read(r, binary.LittleEndian, sp.offsets) 69 | if err != nil { 70 | return err 71 | } 72 | err = binary.Read(r, binary.LittleEndian, make([]uint32, pool.StyleCount)) 73 | if err != nil { 74 | return err 75 | } 76 | size := pool.Header.Size - uint32(pool.Header.HeaderSize) - (pool.StringCount+pool.StyleCount)*4 77 | sp.buff = make([]byte, size) 78 | _, err = io.ReadFull(r, sp.buff) 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /res/table.go: -------------------------------------------------------------------------------- 1 | package res 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "strings" 8 | "unicode/utf16" 9 | "unsafe" 10 | ) 11 | 12 | type Table map[int]TablePackage 13 | 14 | type TablePackage struct { 15 | Name string 16 | Types []TableType 17 | } 18 | 19 | type TableType struct { 20 | Name string 21 | Entries []TableEntry 22 | } 23 | 24 | type TableEntry struct { 25 | Name string 26 | Value TableValue 27 | } 28 | 29 | type TableValue interface { 30 | IsComplex() bool 31 | } 32 | 33 | type Value struct { 34 | DataType uint8 35 | Data uint32 36 | String string 37 | } 38 | 39 | type Complex []struct { 40 | Name ResTableRef 41 | Value Value 42 | } 43 | 44 | func (t Table) Get(id int) (TableEntry, bool) { 45 | pkg, ok := t[(id>>24)&0xFF] 46 | if !ok { 47 | return TableEntry{}, false 48 | } 49 | tid := (id >> 16) & 0xFF 50 | if tid <= 0 || tid >= len(pkg.Types) { 51 | return TableEntry{}, false 52 | } 53 | entries := pkg.Types[tid].Entries 54 | id &= 0xFFFF 55 | if id < 0 || id >= len(entries) { 56 | return TableEntry{}, false 57 | } 58 | return entries[id], true 59 | } 60 | 61 | func (t Table) parse(r io.Reader) error { 62 | var header ResTableHeader 63 | err := binary.Read(r, binary.LittleEndian, &header) 64 | if err != nil { 65 | return err 66 | } else if header.Header.Type != RES_TABLE_TYPE || header.Header.HeaderSize != uint16(unsafe.Sizeof(ResTableHeader{})) { 67 | return errors.New("[ResTableHeader] invalid format") 68 | } 69 | var pool stringPool 70 | err = pool.parse(r) 71 | if err != nil { 72 | return err 73 | } 74 | for i := 0; i < int(header.PackageCount); i++ { 75 | var pkg ResTablePackage 76 | err = binary.Read(r, binary.LittleEndian, &pkg) 77 | if err != nil { 78 | return err 79 | } else if pkg.Header.Type != RES_TABLE_PACKAGE_TYPE || pkg.Header.HeaderSize != uint16(unsafe.Sizeof(ResTablePackage{})) { 80 | return errors.New("[ResTablePackage] invalid format") 81 | } 82 | r := io.LimitReader(r, int64(pkg.Header.Size)-int64(pkg.Header.HeaderSize)) 83 | var types, keys stringPool 84 | if err = types.parse(r); err != nil { 85 | return err 86 | } else if err = keys.parse(r); err != nil { 87 | return err 88 | } 89 | tp := TablePackage{ 90 | Name: strings.TrimRight(string(utf16.Decode(pkg.Name[:])), "\x00"), 91 | Types: make([]TableType, types.Len()+1), 92 | } 93 | for i, v := range types.Range { 94 | tp.Types[i+1] = TableType{Name: v} 95 | } 96 | var chunk ResChunkHeader 97 | for binary.Read(r, binary.LittleEndian, &chunk) == nil { 98 | r := io.LimitReader(r, int64(chunk.Size)-int64(unsafe.Sizeof(ResChunkHeader{}))) 99 | if chunk.Type == RES_TABLE_TYPE_TYPE { 100 | var typ ResTableType 101 | _, err := io.ReadFull(r, unsafe.Slice((*byte)(unsafe.Pointer(&typ)), unsafe.Sizeof(ResTableType{}))[unsafe.Sizeof(ResChunkHeader{}):]) 102 | if err != nil { 103 | return err 104 | } 105 | io.CopyN(io.Discard, r, int64(typ.EntriesStart)-int64(chunk.HeaderSize)) 106 | entries := make([]TableEntry, typ.EntryCount) 107 | err = t.parseEntry(r, &pool, &keys, entries) 108 | id := int(typ.Id) 109 | tp.Types[id].Entries = append(tp.Types[id].Entries, entries...) 110 | if err != nil && err != io.EOF { 111 | return err 112 | } 113 | } 114 | io.Copy(io.Discard, r) 115 | } 116 | t[int(pkg.Id)] = tp 117 | } 118 | return nil 119 | } 120 | 121 | func (t Table) parseEntry(r io.Reader, strings, keys *stringPool, entries []TableEntry) error { 122 | for i := range entries { 123 | var entry ResTableEntry 124 | err := binary.Read(r, binary.LittleEndian, &entry) 125 | if err != nil { 126 | return err 127 | } else if entry.Size != uint16(unsafe.Sizeof(ResTableEntry{})) && entry.Size != uint16(unsafe.Sizeof(ResTableEntry{})+unsafe.Sizeof(ResTableMapEntry{})) { 128 | return errors.New("[ResTableEntry] invalid format") 129 | } 130 | te := TableEntry{Name: keys.Get(int(entry.Key))} 131 | const ( 132 | FLAG_COMPLEX = 1 << iota 133 | FLAG_PUBLIC 134 | FLAG_WEAK 135 | ) 136 | if entry.Flags&FLAG_COMPLEX == 0 { 137 | var value ResValue 138 | err = binary.Read(r, binary.LittleEndian, &value) 139 | if err != nil { 140 | return err 141 | } 142 | te.Value = Value{ 143 | DataType: value.DataType, 144 | Data: value.Data, 145 | String: value.Display(strings), 146 | } 147 | } else { 148 | var mapEntry ResTableMapEntry 149 | err = binary.Read(r, binary.LittleEndian, &mapEntry) 150 | if err != nil { 151 | return err 152 | } 153 | ec := make(Complex, mapEntry.Count) 154 | te.Value = ec 155 | for i := range ec { 156 | var mp ResTableMap 157 | err := binary.Read(r, binary.LittleEndian, &mp) 158 | if err != nil { 159 | return err 160 | } 161 | ec[i].Name = mp.Name 162 | ec[i].Value = Value{ 163 | DataType: mp.Value.DataType, 164 | Data: mp.Value.Data, 165 | String: mp.Value.Display(strings), 166 | } 167 | } 168 | } 169 | entries[i] = te 170 | } 171 | return nil 172 | } 173 | 174 | func (v Value) IsComplex() bool { 175 | return false 176 | } 177 | 178 | func (c Complex) IsComplex() bool { 179 | return true 180 | } 181 | -------------------------------------------------------------------------------- /res/xml.go: -------------------------------------------------------------------------------- 1 | package res 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/xml" 6 | "io" 7 | "runtime" 8 | "unsafe" 9 | ) 10 | 11 | type xmlDecoder struct { 12 | r io.Reader 13 | strings stringPool 14 | resIds []uint32 15 | } 16 | 17 | func (d *xmlDecoder) Token() (xml.Token, error) { 18 | var node ResXMLTreeNode 19 | err := binary.Read(d.r, binary.LittleEndian, &node) 20 | if err != nil { 21 | return nil, err 22 | } 23 | switch node.Header.Type { 24 | case RES_XML_START_NAMESPACE_TYPE: 25 | var ns ResXMLTreeNamespaceExt 26 | binary.Read(d.r, binary.LittleEndian, &ns) 27 | return xml.Comment{}, nil 28 | case RES_XML_END_NAMESPACE_TYPE: 29 | runtime.Breakpoint() 30 | case RES_XML_START_ELEMENT_TYPE: 31 | return d.getStartElement() 32 | case RES_XML_END_ELEMENT_TYPE: 33 | return d.getEndElement() 34 | case RES_XML_CDATA_TYPE: 35 | runtime.Breakpoint() 36 | } 37 | return nil, nil 38 | } 39 | 40 | func (d *xmlDecoder) init() error { 41 | err := d.strings.parse(d.r) 42 | if err != nil { 43 | return err 44 | } 45 | return d.parseResMapType() 46 | } 47 | 48 | func (d *xmlDecoder) parseResMapType() error { 49 | var res ResChunkHeader 50 | err := binary.Read(d.r, binary.LittleEndian, &res) 51 | if err != nil { 52 | return err 53 | } else if res.Type != RES_XML_RESOURCE_MAP_TYPE || res.HeaderSize != uint16(unsafe.Sizeof(ResChunkHeader{})) { 54 | return xml.UnmarshalError("invalid format") 55 | } 56 | d.resIds = make([]uint32, (res.Size-uint32(res.HeaderSize))/4) 57 | return binary.Read(d.r, binary.LittleEndian, d.resIds) 58 | } 59 | 60 | func (d *xmlDecoder) getStartElement() (se xml.StartElement, err error) { 61 | var element ResXMLTreeAttrExt 62 | err = binary.Read(d.r, binary.LittleEndian, &element) 63 | if err != nil { 64 | return 65 | } 66 | se.Name = xml.Name{ 67 | Space: d.strings.Get(int(element.Ns)), 68 | Local: d.strings.Get(int(element.Name)), 69 | } 70 | se.Attr = make([]xml.Attr, element.AttributeCount) 71 | for i := range se.Attr { 72 | var attr ResXMLTreeAttribute 73 | err = binary.Read(d.r, binary.LittleEndian, &attr) 74 | if err != nil { 75 | return 76 | } 77 | se.Attr[i] = xml.Attr{ 78 | Name: xml.Name{ 79 | Space: d.strings.Get(int(attr.Ns)), 80 | Local: d.strings.Get(int(attr.Name)), 81 | }, 82 | } 83 | if attr.RawValue == -1 { 84 | se.Attr[i].Value = attr.TypedValue.Display(&d.strings) 85 | } else { 86 | se.Attr[i].Value = d.strings.Get(int(attr.RawValue)) 87 | } 88 | } 89 | return 90 | } 91 | 92 | func (d *xmlDecoder) getEndElement() (ee xml.EndElement, err error) { 93 | var element ResXMLTreeEndElementExt 94 | err = binary.Read(d.r, binary.LittleEndian, &element) 95 | if err != nil { 96 | return 97 | } 98 | ee.Name = xml.Name{ 99 | Space: d.strings.Get(int(element.Ns)), 100 | Local: d.strings.Get(int(element.Name)), 101 | } 102 | return 103 | } 104 | -------------------------------------------------------------------------------- /runtime.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "io/fs" 7 | 8 | gava "github.com/wnxd/microdbg-android/java" 9 | java "github.com/wnxd/microdbg-java" 10 | "github.com/wnxd/microdbg/debugger" 11 | "github.com/wnxd/microdbg/emulator" 12 | "github.com/wnxd/microdbg/filesystem" 13 | ) 14 | 15 | type Runtime interface { 16 | io.Closer 17 | Debugger() debugger.Debugger 18 | Emulator() emulator.Emulator 19 | LoadModule(ctx context.Context, file fs.File) (Module, error) 20 | FindModule(name string) (Module, error) 21 | LinkFS(name string, fs filesystem.FS) error 22 | JavaVM() java.JavaVM 23 | Package() Package 24 | ClassFactory() gava.ClassFactory 25 | RegisterNatives(clazz java.IClass, methods []java.JNINativeMethod) java.JInt 26 | UnregisterNatives(clazz java.IClass) java.JInt 27 | } 28 | -------------------------------------------------------------------------------- /symbol.go: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/wnxd/microdbg/debugger" 7 | ) 8 | 9 | type Symbol interface { 10 | Name() string 11 | Address() uint64 12 | Call(ctx context.Context, calling debugger.Calling, ret any, args ...any) error 13 | } 14 | -------------------------------------------------------------------------------- /wrapper/fake.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "unicode/utf16" 7 | 8 | android "github.com/wnxd/microdbg-android" 9 | gava "github.com/wnxd/microdbg-android/java" 10 | java "github.com/wnxd/microdbg-java" 11 | "github.com/wnxd/microdbg/debugger" 12 | ) 13 | 14 | type FakeDefineHandler interface { 15 | DefineClass(FakeContext, string) gava.FakeClass 16 | DefineMethod(FakeContext, gava.FakeClass, string, string) gava.FakeMethod 17 | DefineStaticMethod(FakeContext, gava.FakeClass, string, string) gava.FakeMethod 18 | DefineField(FakeContext, gava.FakeClass, string, string) gava.FakeField 19 | DefineStaticField(FakeContext, gava.FakeClass, string, string) gava.FakeField 20 | } 21 | 22 | type FakeClassHandler interface { 23 | CallMethod(android.JNIContext, gava.FakeObject, string, string, ...any) any 24 | CallStaticMethod(android.JNIContext, gava.FakeClass, string, string, ...any) any 25 | GetField(android.JNIContext, gava.FakeObject, string) any 26 | SetField(android.JNIContext, gava.FakeObject, string, any) 27 | GetStaticField(android.JNIContext, gava.FakeClass, string) any 28 | SetStaticField(android.JNIContext, gava.FakeClass, string, any) 29 | } 30 | 31 | type FakeContext interface { 32 | gava.ClassFactory 33 | Bind(gava.FakeClass, FakeClassHandler) 34 | BindClass(string, FakeClassHandler) 35 | } 36 | 37 | type FakeWrapper interface { 38 | android.JNIEnv 39 | ClassFactory() gava.ClassFactory 40 | Bind(gava.FakeClass, FakeClassHandler) 41 | BindClass(string, FakeClassHandler) 42 | } 43 | 44 | type fakeWrapper struct { 45 | handler FakeDefineHandler 46 | cf gava.ClassFactory 47 | binds sync.Map 48 | } 49 | 50 | type fakeContext struct { 51 | w *fakeWrapper 52 | } 53 | 54 | type fakeJNIContext struct { 55 | android.JNIContext 56 | cf gava.ClassFactory 57 | } 58 | 59 | func NewFake(handler FakeDefineHandler) FakeWrapper { 60 | w := &fakeWrapper{handler: handler} 61 | w.cf = gava.NewClassFactory(w.preDefineClass) 62 | return w 63 | } 64 | 65 | func (w *fakeWrapper) DefineClass(android.JNIContext, string, java.IObject, []java.JByte) (java.IClass, error) { 66 | panic(fmt.Errorf("[FakeWrapper.DefineClass] %w", debugger.ErrNotImplemented)) 67 | } 68 | 69 | func (w *fakeWrapper) FindClass(ctx android.JNIContext, name string) (java.IClass, error) { 70 | return w.cf.GetClass(name), nil 71 | } 72 | 73 | func (w *fakeWrapper) ThrowNew(ctx android.JNIContext, clazz java.IClass, message string) (java.JInt, error) { 74 | fake := clazz.(gava.FakeClass) 75 | ex := fake.NewThrowable(message) 76 | ctx.Throw(ex) 77 | return java.JNI_OK, nil 78 | } 79 | 80 | func (w *fakeWrapper) GetMethod(ctx android.JNIContext, clazz java.IClass, name, sig string) (java.IMethod, error) { 81 | fake := clazz.(gava.FakeClass) 82 | var method gava.FakeMethod 83 | if w.handler != nil { 84 | method = w.handler.DefineMethod(fakeContext{w}, fake, name, sig) 85 | } 86 | if method == nil { 87 | method = w.getMethod(fake, name, sig) 88 | } 89 | if h, ok := w.getHandler(fake); ok { 90 | method.BindCall(func(obj java.IObject, args ...any) any { 91 | return h.CallMethod(fakeJNIContext{ctx, fakeContext{w}}, obj.(gava.FakeObject), name, sig, args...) 92 | }) 93 | } 94 | return method, nil 95 | } 96 | 97 | func (w *fakeWrapper) GetField(ctx android.JNIContext, clazz java.IClass, name, sig string) (java.IField, error) { 98 | fake := clazz.(gava.FakeClass) 99 | var field gava.FakeField 100 | if w.handler != nil { 101 | field = w.handler.DefineField(fakeContext{w}, fake, name, sig) 102 | } 103 | if field == nil { 104 | field = w.getField(fake, name, sig) 105 | } 106 | if h, ok := w.getHandler(fake); ok { 107 | field.BindGet(func(obj java.IObject) any { 108 | return h.GetField(fakeJNIContext{ctx, fakeContext{w}}, obj.(gava.FakeObject), name) 109 | }) 110 | field.BindSet(func(obj java.IObject, value any) { 111 | h.SetField(fakeJNIContext{ctx, fakeContext{w}}, obj.(gava.FakeObject), name, value) 112 | }) 113 | } 114 | return field, nil 115 | } 116 | 117 | func (w *fakeWrapper) GetStaticMethod(ctx android.JNIContext, clazz java.IClass, name, sig string) (java.IMethod, error) { 118 | fake := clazz.(gava.FakeClass) 119 | var method gava.FakeMethod 120 | if w.handler != nil { 121 | method = w.handler.DefineStaticMethod(fakeContext{w}, fake, name, sig) 122 | } 123 | if method == nil { 124 | method = w.getStaticMethod(fake, name, sig) 125 | } 126 | if h, ok := w.getHandler(fake); ok { 127 | method.BindCall(func(obj java.IObject, args ...any) any { 128 | return h.CallStaticMethod(fakeJNIContext{ctx, fakeContext{w}}, fake, name, sig, args...) 129 | }) 130 | } 131 | return method, nil 132 | } 133 | 134 | func (w *fakeWrapper) GetStaticField(ctx android.JNIContext, clazz java.IClass, name, sig string) (java.IField, error) { 135 | fake := clazz.(gava.FakeClass) 136 | var field gava.FakeField 137 | if w.handler != nil { 138 | field = w.handler.DefineStaticField(fakeContext{w}, fake, name, sig) 139 | } 140 | if field == nil { 141 | field = w.getStaticField(fake, name, sig) 142 | } 143 | if h, ok := w.getHandler(fake); ok { 144 | field.BindGet(func(obj java.IObject) any { 145 | return h.GetStaticField(fakeJNIContext{ctx, fakeContext{w}}, fake, name) 146 | }) 147 | field.BindSet(func(obj java.IObject, value any) { 148 | h.SetStaticField(fakeJNIContext{ctx, fakeContext{w}}, fake, name, value) 149 | }) 150 | } 151 | return field, nil 152 | } 153 | 154 | func (w *fakeWrapper) NewString(ctx android.JNIContext, chars []java.JChar) (java.IString, error) { 155 | return gava.FakeString(utf16.Decode(chars)), nil 156 | } 157 | 158 | func (w *fakeWrapper) NewStringUTF(ctx android.JNIContext, bytes string) (java.IString, error) { 159 | return gava.FakeString(bytes), nil 160 | } 161 | 162 | func (w *fakeWrapper) NewObjectArray(ctx android.JNIContext, length java.JSize, elementClass java.IClass, initialElement java.IObject) (java.IObjectArray, error) { 163 | arr := elementClass.(gava.FakeClass).NewArray(int(length)).(java.IObjectArray) 164 | if initialElement != nil { 165 | raw := arr.Elements() 166 | for i := range raw { 167 | raw[i] = initialElement 168 | } 169 | } 170 | return arr, nil 171 | } 172 | 173 | func (w *fakeWrapper) NewBooleanArray(ctx android.JNIContext, length java.JSize) (java.IBooleanArray, error) { 174 | return gava.FakeBooleanTYPE.NewArray(int(length)).(java.IBooleanArray), nil 175 | } 176 | 177 | func (w *fakeWrapper) NewByteArray(ctx android.JNIContext, length java.JSize) (java.IByteArray, error) { 178 | return gava.FakeByteTYPE.NewArray(int(length)).(java.IByteArray), nil 179 | } 180 | 181 | func (w *fakeWrapper) NewCharArray(ctx android.JNIContext, length java.JSize) (java.ICharArray, error) { 182 | return gava.FakeCharTYPE.NewArray(int(length)).(java.ICharArray), nil 183 | } 184 | 185 | func (w *fakeWrapper) NewShortArray(ctx android.JNIContext, length java.JSize) (java.IShortArray, error) { 186 | return gava.FakeShortTYPE.NewArray(int(length)).(java.IShortArray), nil 187 | } 188 | 189 | func (w *fakeWrapper) NewIntArray(ctx android.JNIContext, length java.JSize) (java.IIntArray, error) { 190 | return gava.FakeIntTYPE.NewArray(int(length)).(java.IIntArray), nil 191 | } 192 | 193 | func (w *fakeWrapper) NewLongArray(ctx android.JNIContext, length java.JSize) (java.ILongArray, error) { 194 | return gava.FakeLongTYPE.NewArray(int(length)).(java.ILongArray), nil 195 | } 196 | 197 | func (w *fakeWrapper) NewFloatArray(ctx android.JNIContext, length java.JSize) (java.IFloatArray, error) { 198 | return gava.FakeFloatTYPE.NewArray(int(length)).(java.IFloatArray), nil 199 | } 200 | 201 | func (w *fakeWrapper) NewDoubleArray(ctx android.JNIContext, length java.JSize) (java.IDoubleArray, error) { 202 | return gava.FakeDoubleTYPE.NewArray(int(length)).(java.IDoubleArray), nil 203 | } 204 | 205 | func (w *fakeWrapper) ClassFactory() gava.ClassFactory { 206 | return w.cf 207 | } 208 | 209 | func (w *fakeWrapper) Bind(clazz gava.FakeClass, handler FakeClassHandler) { 210 | if clazz == nil { 211 | return 212 | } 213 | w.binds.Store(clazz.HashCode(), handler) 214 | } 215 | 216 | func (w *fakeWrapper) BindClass(name string, handler FakeClassHandler) { 217 | w.Bind(w.cf.GetClass(name), handler) 218 | } 219 | 220 | func (w *fakeWrapper) getHandler(clazz gava.FakeClass) (FakeClassHandler, bool) { 221 | if val, ok := w.binds.Load(clazz.HashCode()); ok { 222 | h, ok := val.(FakeClassHandler) 223 | return h, ok 224 | } 225 | return nil, false 226 | } 227 | 228 | func (w *fakeWrapper) getMethod(cls gava.FakeClass, name, sig string) gava.FakeMethod { 229 | if method := cls.FindMethod(name, sig); method != nil { 230 | return method 231 | } 232 | return w.cf.DefineMethod(cls, name, sig, gava.Modifier_PUBLIC) 233 | } 234 | 235 | func (w *fakeWrapper) getStaticMethod(cls gava.FakeClass, name, sig string) gava.FakeMethod { 236 | if method := cls.FindMethod(name, sig); method != nil { 237 | return method 238 | } 239 | return w.cf.DefineMethod(cls, name, sig, gava.Modifier_PUBLIC|gava.Modifier_STATIC) 240 | } 241 | 242 | func (w *fakeWrapper) getField(cls gava.FakeClass, name, sig string) gava.FakeField { 243 | if field := cls.FindField(name, sig); field != nil { 244 | return field 245 | } 246 | return w.cf.DefineField(cls, name, sig, gava.Modifier_PUBLIC) 247 | } 248 | 249 | func (w *fakeWrapper) getStaticField(cls gava.FakeClass, name, sig string) gava.FakeField { 250 | if field := cls.FindField(name, sig); field != nil { 251 | return field 252 | } 253 | return w.cf.DefineField(cls, name, sig, gava.Modifier_PUBLIC|gava.Modifier_STATIC) 254 | } 255 | 256 | func (w *fakeWrapper) preDefineClass(cf gava.ClassFactory, name string) gava.FakeClass { 257 | if w.handler != nil { 258 | return w.handler.DefineClass(fakeContext{w}, name) 259 | } 260 | return nil 261 | } 262 | 263 | func (ctx fakeContext) WrapClass(cls java.IClass) gava.FakeClass { 264 | return ctx.w.cf.WrapClass(cls) 265 | } 266 | 267 | func (ctx fakeContext) FindClass(name string) (gava.FakeClass, bool) { 268 | return ctx.w.cf.FindClass(name) 269 | } 270 | 271 | func (ctx fakeContext) GetClass(name string) gava.FakeClass { 272 | return ctx.w.cf.GetClass(name) 273 | } 274 | 275 | func (ctx fakeContext) DefineClass(name string, extends ...java.IClass) gava.FakeClass { 276 | return ctx.w.cf.DefineClass(name, extends...) 277 | } 278 | 279 | func (ctx fakeContext) ArrayOf(elem java.IClass) gava.FakeClass { 280 | return ctx.w.cf.ArrayOf(elem) 281 | } 282 | 283 | func (ctx fakeContext) DefineMethod(clazz gava.FakeClass, name, sig string, mod gava.Modifier) gava.FakeMethod { 284 | return ctx.w.cf.DefineMethod(clazz, name, sig, mod) 285 | } 286 | 287 | func (ctx fakeContext) DefineField(clazz gava.FakeClass, name, sig string, mod gava.Modifier) gava.FakeField { 288 | return ctx.w.cf.DefineField(clazz, name, sig, mod) 289 | } 290 | 291 | func (ctx fakeContext) Bind(clazz gava.FakeClass, handler FakeClassHandler) { 292 | ctx.w.Bind(clazz, handler) 293 | } 294 | 295 | func (ctx fakeContext) BindClass(name string, handler FakeClassHandler) { 296 | ctx.w.BindClass(name, handler) 297 | } 298 | 299 | func (ctx fakeJNIContext) ClassFactory() gava.ClassFactory { 300 | return ctx.cf 301 | } 302 | --------------------------------------------------------------------------------