Add dex example that dynamically loads a java class from a byte

stream.
This commit is contained in:
Greg 2019-10-31 16:16:56 -04:00
parent dad071293c
commit 45f4e38ee3
10 changed files with 544 additions and 13 deletions

6
.gitignore vendored
View File

@ -1,3 +1,9 @@
*.ast *.ast
*.apk *.apk
*.jar
*.dex
*.class
examples/sensors/sensors examples/sensors/sensors
examples/jni/jni
examples/dex/dex
examples/permissions/permissions

237
examples/dex/bindata.go Normal file
View File

@ -0,0 +1,237 @@
// Code generated by go-bindata.
// sources:
// java/classes.dex
// DO NOT EDIT!
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _javaClassesDex = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x91\xbf\x4e\xc2\x50\x14\xc6\x7f\xa7\xe5\x4f\x22\x06\x11\x4d\x5c\x88\xe9\xa0\xab\x57\x23\x4e\x18\x13\xe3\x22\x09\xd1\x8d\xa5\x53\x15\x24\x25\x88\xc4\x56\x61\xd4\x84\x91\x47\xd0\xcd\x47\xf0\x11\x1c\x1c\x1c\xf4\x21\xdc\x58\xdc\x1d\xcc\xed\xbd\x40\xe3\x49\xda\xef\xe4\xf4\x9e\xef\x9c\xfe\x6e\xab\x3d\x5a\xda\xdd\x3f\xa0\x5e\xfd\x2c\xbf\xff\x3e\x7f\x7f\xbd\x6c\xbe\xf9\xcd\x1f\xff\xd6\x5f\x9f\xf8\x8d\xca\xf6\xb2\x03\x03\x60\xd4\xac\x96\xb1\x31\x16\xc8\x63\xea\x19\x60\x02\x38\xc0\x13\x8b\x70\x81\x57\x40\x80\x0f\xc0\x13\x98\x02\x15\x81\x2d\x81\x3d\x81\xaa\xc0\xa9\x40\x20\x70\x25\xc6\xc3\xb5\x9e\x39\xeb\x39\x8b\x9c\xfd\x46\xe2\x29\x89\x3a\x73\x85\xac\x55\xb1\x0f\x29\x8d\x64\xd1\xa7\xd3\x50\xcc\x8c\x41\x49\x57\x8b\x73\x9f\xbe\x98\x19\x6b\xac\xb2\xa2\x67\x1e\x86\xfd\x30\x3e\xa2\x70\x7c\xd2\x0b\xa2\x68\xa7\x1b\xdc\x07\x48\x9d\x72\x43\x67\xaa\x17\xf4\x3b\xea\xfc\xa2\xdb\xbe\x8c\x6b\x6c\x34\xa2\x58\x0d\x6f\x86\xaa\x13\xc6\xaa\xd5\x1e\x29\xd3\x53\xc3\x3d\xbb\xbb\x46\x9a\xb8\xe4\x8b\x64\xf5\x0b\x44\xe4\xf1\x21\x33\x15\x47\xc6\x7a\xe7\xc2\xbf\x7d\xb5\xce\xf8\x3a\x29\xc6\x6e\x8a\x73\x36\xc5\x38\x97\xe2\x2c\x9e\x39\xa3\x59\x3b\x9e\xf1\xd1\xcc\x5d\x5b\xd7\xff\x8e\x67\xce\x27\x5c\x4a\x26\xd7\x77\xfa\x17\x00\x00\xff\xff\xe4\x6b\xad\x5e\x0c\x02\x00\x00")
func javaClassesDexBytes() ([]byte, error) {
return bindataRead(
_javaClassesDex,
"java/classes.dex",
)
}
func javaClassesDex() (*asset, error) {
bytes, err := javaClassesDexBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "java/classes.dex", size: 524, mode: os.FileMode(420), modTime: time.Unix(1572550715, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"java/classes.dex": javaClassesDex,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"java": &bintree{nil, map[string]*bintree{
"classes.dex": &bintree{javaClassesDex, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

206
examples/dex/dex_android.go Normal file
View File

@ -0,0 +1,206 @@
package main
/*
#cgo LDFLAGS: -landroid -llog
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
static jobject gClassLoader;
static jmethodID gFindClassMethod;
jobject
CreateObject(JNIEnv* env, jclass cls) {
jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "()V");
return (*env)->NewObject(env, cls, init);
}
jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig) {
return (*env)->GetMethodID(env, cls, name, sig);
}
jclass
FindClass(JNIEnv* env, char* name) {
jstring strClassName = (*env)->NewStringUTF(env, name);
return (*env)->CallObjectMethod(env, gClassLoader, gFindClassMethod, strClassName);
}
void
SetupJVM(JNIEnv* env, jobject context, void* buf, int len) {
jclass cls = (*env)->GetObjectClass(env, context);
jmethodID mid = (*env)->GetMethodID(env, cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
jobject loader = (*env)->CallObjectMethod(env, context, mid);
gClassLoader = (*env)->NewGlobalRef(env, loader);
cls = (*env)->GetObjectClass(env, loader);
gFindClassMethod = (*env)->GetMethodID(env, cls, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
__android_log_write(ANDROID_LOG_DEBUG, "gio", "Looking for InMemoryDexClassLoader");
cls = (*env)->FindClass(env, "dalvik/system/InMemoryDexClassLoader");
if (cls == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "NewDirectByteBuffer");
jobject bbuf = (*env)->NewDirectByteBuffer(env, buf, (jlong)len);
if (bbuf == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "failed");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for <init>");
mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
if (mid == NULL) {
__android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
return;
}
__android_log_write(ANDROID_LOG_DEBUG, "gio", "calling <init>");
jobject dexLoader = (*env)->NewObject(env, cls, mid, bbuf, gClassLoader);
// make the dexLoader our new class loader
gClassLoader = (*env)->NewGlobalRef(env, dexLoader);
}
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for AClass");
// cls = FindClass(env, "st/wow/git/dex/AClass");
// if (cls == NULL) {
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
// return;
// }
//
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "calling CreateObject");
// jobject obj = CreateObject(env, cls);
// if (obj == NULL) {
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed");
// return;
// }
//
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for <init>");
// mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
// if (mid == NULL) {
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
// return;
// }
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "creating object");
// obj = (*env)->NewObject(env, cls, mid);
// if (obj == NULL) {
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed");
// return;
// }
//
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for Num");
// mid = (*env)->GetMethodID(env, cls, "Num", "()I");
// if (mid == NULL) {
// __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found");
// return;
// }
// jint val = (*env)->CallIntMethod(env, obj, mid);
// __android_log_print(ANDROID_LOG_DEBUG, "gio", "value = %d", val);
jint
CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
return (*env)->CallIntMethod(env, obj, methodID);
}
jint
GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
return (*vm)->GetEnv(vm, (void **)env, version);
}
jint
AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
}
jint
DetachCurrentThread(JavaVM *vm) {
return (*vm)->DetachCurrentThread(vm);
}
*/
import "C"
import (
"errors"
"fmt"
"runtime"
"unsafe"
"gioui.org/app"
)
var theJVM *C.JavaVM
func SetJVM() {
h := app.PlatformHandle()
theJVM = (*C.JavaVM)(unsafe.Pointer(h.JVM))
dex := MustAsset("java/classes.dex")
RunInJVM(func(env *C.JNIEnv) {
labchan <- "Set up JVM"
C.SetupJVM(
env,
(C.jobject)(h.Context),
unsafe.Pointer(&dex[0]),
C.int(len(dex)),
)
})
}
func CallJNI() {
labchan <- "Call JNI"
RunInJVM(func(env *C.JNIEnv) {
labchan <- "FindClass"
cls := JniFindClass(env, "st/wow/git/dex/AClass")
labchan <- "GetMethodID"
mid := JniGetMethodID(env, cls, "Num", "()I")
labchan <- "CreateObject"
obj := JniCreateObject(env, cls)
val := JniCallIntMethod(env, obj, mid)
labchan <- fmt.Sprintf("val = %d", val)
})
}
func JniFindClass(env *C.JNIEnv, name string) C.jclass {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return C.FindClass((*C.JNIEnv)(env), cname)
}
func JniCreateObject(env *C.JNIEnv, cls C.jclass) C.jobject {
return C.CreateObject(env, (C.jclass)(cls))
}
func JniCallIntMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) int {
return (int)(C.CallIntMethod(env, obj, methodID))
}
func JniGetMethodID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jmethodID {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
return C.GetMethodID(env, cls, cname, csig)
}
func RunInJVM(f func(env *C.JNIEnv)) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var env *C.JNIEnv
var detach bool
if res := C.GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
if res != C.JNI_EDETACHED {
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
}
if C.AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK {
panic(errors.New("runInJVM: AttachCurrentThread failed"))
}
detach = true
}
if detach {
defer func() {
C.DetachCurrentThread(theJVM)
}()
}
f(env)
}

View File

@ -0,0 +1,9 @@
package main
func SetJVM() {
dex := MustAsset("java/classes.dex")
_ = dex
}
func CallJNI() {
}

View File

@ -0,0 +1,9 @@
package st.wow.git.dex;
public class AClass {
public int Num() {
return 17;
}
}

5
examples/dex/java/gen.go Normal file
View File

@ -0,0 +1,5 @@
//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar AClass.java
//go:generate $ANDROID_HOME/build-tools/28.0.3/dx --dex --output=classes.dex --no-strict AClass.class
//go:generate rm AClass.class
package java

70
examples/dex/main.go Normal file
View File

@ -0,0 +1,70 @@
//go:generate go generate ./java
//go:generate go-bindata java/classes.dex
package main
import (
"gioui.org/app"
"gioui.org/layout"
"gioui.org/io/system"
"gioui.org/unit"
"gioui.org/widget/material"
"gioui.org/font/gofont"
)
var (
labchan chan string
)
func main() {
labchan = make(chan string)
go eventloop()
app.Main()
}
func eventloop() {
gofont.Register()
w := app.NewWindow(app.Title("Dex"))
th := material.NewTheme()
gtx := &layout.Context{Queue: w.Queue()}
sysinset := &layout.Inset{}
margin := layout.UniformInset(unit.Dp(10))
labels := []material.Label{}
list := &layout.List{Axis: layout.Vertical}
go func() {
labchan <- "Starting"
SetJVM()
CallJNI()
}()
resetSysinset := func(x system.Insets) {
sysinset.Top = x.Top
sysinset.Bottom = x.Bottom
sysinset.Left = x.Left
sysinset.Right = x.Right
}
for { select {
case x := <-labchan:
labels = append(labels, th.Body1(x))
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return
case system.FrameEvent:
gtx.Reset(e.Config, e.Size)
resetSysinset(e.Insets)
sysinset.Layout(gtx, func() {
margin.Layout(gtx, func() {
list.Layout(gtx, len(labels), func(i int) {
labels[i].Layout(gtx)
})
})
})
e.Frame(gtx.Ops)
}
}}
}

View File

@ -26,13 +26,6 @@ func main() {
log.Print("app.Main() returned") log.Print("app.Main() returned")
} }
func diffInsets(x, y system.Insets) bool {
return x.Top != y.Top ||
x.Bottom != y.Bottom ||
x.Left != y.Left ||
x.Right != y.Right
}
func eventloop() { func eventloop() {
w := app.NewWindow( w := app.NewWindow(
app.Size(unit.Dp(400), unit.Dp(400)), app.Size(unit.Dp(400), unit.Dp(400)),

View File

@ -11,7 +11,7 @@ import (
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget/material" "gioui.org/widget/material"
_ "gioui.org/font/gofont" "gioui.org/font/gofont"
) )
var ( var (
@ -34,6 +34,7 @@ func diffInsets(x, y system.Insets) bool {
} }
func eventloop() { func eventloop() {
gofont.Register()
w := app.NewWindow( w := app.NewWindow(
app.Size(unit.Dp(400), unit.Dp(400)), app.Size(unit.Dp(400), unit.Dp(400)),
app.Title("Hello")) app.Title("Hello"))

5
go.mod
View File

@ -1,8 +1,3 @@
module git.wow.st/gmp/android-go module git.wow.st/gmp/android-go
go 1.13 go 1.13
require (
gioui.org v0.0.0-20191018181617-5ef176af8145
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a
)