diff --git a/core/java/jdbg/jdbg.go b/core/java/jdbg/jdbg.go index c4cf1cc624..21e509be8b 100644 --- a/core/java/jdbg/jdbg.go +++ b/core/java/jdbg/jdbg.go @@ -304,8 +304,8 @@ func (j *JDbg) String(val string) Value { return j.object(str) } -// findVar finds the variable with the given name in the given frame -func (j *JDbg) findVar(name string, frame jdwp.FrameInfo) jdwp.VariableRequest { +// findArg finds the argument with the given name/index in the given frame +func (j *JDbg) findArg(name string, index int, frame jdwp.FrameInfo) jdwp.VariableRequest { table, err := j.conn.VariableTable( jdwp.ReferenceTypeID(frame.Location.Class), frame.Location.Method) @@ -323,39 +323,61 @@ func (j *JDbg) findVar(name string, frame jdwp.FrameInfo) jdwp.VariableRequest { } } - if variable.Index == -1 { - j.fail("Could not find variable with name %s", name) + if variable.Index != -1 { + return variable } + + // Fallback to looking for the argument by index. + slots := table.ArgumentSlots() + + // Find the "this" argument. It is always labeled and the first argument slot. + thisSlot := -1 + for i, slot := range slots { + if slot.Name == "this" { + thisSlot = i + break + } + } + if thisSlot < 0 { + j.fail("Could not find argument with name %s (no 'this' found)", name) + } + + if thisSlot+1+index >= len(slots) { + j.fail("Could not find argument with name %s (not enough slots)", name) + } + + variable.Index = slots[thisSlot+1+index].Slot + variable.Tag = slots[thisSlot+1+index].Signature[0] return variable } -// GetStackObject returns an object by name that exists in the current -// stack-frame. -func (j *JDbg) GetStackObject(name string) Value { +// GetArgument returns the method argument of the given name and index. First, +// this attempts to retrieve the argument by name, but falls back to looking for +// the argument by index (e.g. in the case the names have been stripped from the +// debug info). +func (j *JDbg) GetArgument(name string, index int) Variable { frames, err := j.conn.GetFrames(j.thread, 0, 1) if err != nil { j.fail("GetFrames() returned: %v", err) } - variable := j.findVar(name, frames[0]) + variable := j.findArg(name, index, frames[0]) values, err := j.conn.GetValues(j.thread, frames[0].Frame, []jdwp.VariableRequest{variable}) if err != nil { j.fail("GetValues() returned: %v", err) } - return j.value(values[0]) + return Variable{j.value(values[0]), variable} } -// SetStackObject sets and object in the current stack-frame to the -// given value. -func (j *JDbg) SetStackObject(name string, val Value) { +// SetVariable sets the value of the given variable. +func (j *JDbg) SetVariable(variable Variable, val Value) { frames, err := j.conn.GetFrames(j.thread, 0, 1) if err != nil { j.fail("GetFrames() returned: %v", err) } - variable := j.findVar(name, frames[0]) v := val.val.(jdwp.Value) - assign := jdwp.VariableAssignmentRequest{variable.Index, v} + assign := jdwp.VariableAssignmentRequest{variable.variable.Index, v} err = j.conn.SetValues(j.thread, frames[0].Frame, []jdwp.VariableAssignmentRequest{assign}) if err != nil { j.fail("GetValues() returned: %v", err) diff --git a/core/java/jdbg/value.go b/core/java/jdbg/value.go index 1928b89d18..c6b66759ec 100644 --- a/core/java/jdbg/value.go +++ b/core/java/jdbg/value.go @@ -97,3 +97,9 @@ func (v Value) SetArrayValues(values interface{}) { j.fail("Failed to set array (type %s) values (type %T): %v", arrayTy, values, err) } } + +// Variable is a named Value. +type Variable struct { + Value Value + variable jdwp.VariableRequest +} diff --git a/core/java/jdwp/types.go b/core/java/jdwp/types.go index f642da5f41..a34691904f 100644 --- a/core/java/jdwp/types.go +++ b/core/java/jdwp/types.go @@ -14,7 +14,10 @@ package jdwp -import "fmt" +import ( + "fmt" + "sort" +) // TaggedObjectID is a type and object identifier pair. type TaggedObjectID struct { @@ -146,3 +149,19 @@ func (i ArrayTypeID) String() string { return fmt.Sprintf("ArrayTypeID<%d>", func (i MethodID) String() string { return fmt.Sprintf("MethodID<%d>", uint64(i)) } func (i FieldID) String() string { return fmt.Sprintf("FieldID<%d>", uint64(i)) } func (i FrameID) String() string { return fmt.Sprintf("FrameID<%d>", uint64(i)) } + +// ArgumentSlots returns the slots that could possibly be method arguments. +// Slots that could be method arguments are slots that are acessible at +// location 0 and have a length > 0. Returns the result sorted by slot index. +func (v *VariableTable) ArgumentSlots() []FrameVariable { + r := []FrameVariable{} + for _, slot := range v.Slots { + if slot.CodeIndex == 0 && slot.Length > 0 { + r = append(r, slot) + } + } + sort.Slice(r, func(i, j int) bool { + return r[i].Slot < r[j].Slot + }) + return r +} diff --git a/gapii/client/jdwp_loader.go b/gapii/client/jdwp_loader.go index 2f65b620bd..b1410039af 100644 --- a/gapii/client/jdwp_loader.go +++ b/gapii/client/jdwp_loader.go @@ -35,10 +35,13 @@ import ( ) var ( - getClassLoaderSignatures = []string{ - "(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Ljava/lang/ClassLoader;", - "(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/ClassLoader;", - "(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/ClassLoader;", + getClassLoaderSignatures = []struct { + signature string + argIndex int + }{ + {"(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Ljava/lang/ClassLoader;", 3}, + {"(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/ClassLoader;", 3}, + {"(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/ClassLoader;", 3}, } ) @@ -106,18 +109,19 @@ func waitForOnCreate(ctx context.Context, conn *jdwp.Connection) (*jdwp.EventMet // and then suspends the thread. // This function is what is used to tell the vulkan loader where to search for // layers. -func waitForVulkanLoad(ctx context.Context, conn *jdwp.Connection) (*jdwp.EventMethodEntry, error) { +func waitForVulkanLoad(ctx context.Context, conn *jdwp.Connection) (*jdwp.EventMethodEntry, int, error) { loaders, err := conn.GetClassBySignature("Landroid/app/ApplicationLoaders;") if err != nil { - return nil, err + return nil, 0, err } for _, sig := range getClassLoaderSignatures { - if getClassLoader, err := conn.GetClassMethod(loaders.ClassID(), "getClassLoader", sig); err == nil { - return conn.WaitForMethodEntry(ctx, loaders.ClassID(), getClassLoader.ID) + if getClassLoader, err := conn.GetClassMethod(loaders.ClassID(), "getClassLoader", sig.signature); err == nil { + entry, err := conn.WaitForMethodEntry(ctx, loaders.ClassID(), getClassLoader.ID) + return entry, sig.argIndex, err } } - return nil, fmt.Errorf("no known ApplicationLoaders.getClassLoader method found") + return nil, 0, fmt.Errorf("no known ApplicationLoaders.getClassLoader method found") } // loadAndConnectViaJDWP connects to the application waiting for a JDWP @@ -201,7 +205,7 @@ func (p *Process) loadAndConnectViaJDWP( } log.I(ctx, "Waiting for ApplicationLoaders.getClassLoader()") - getClassLoader, err := waitForVulkanLoad(ctx, conn) + getClassLoader, argIdx, err := waitForVulkanLoad(ctx, conn) if err == nil { // If err != nil that means we could not find or break in getClassLoader // so we have no vulkan support. @@ -212,8 +216,8 @@ func (p *Process) loadAndConnectViaJDWP( } libsPath := gapidAPK.LibsPath(abi) newLibraryPath := j.String(":" + libsPath) - obj := j.GetStackObject("librarySearchPath").Call("concat", newLibraryPath) - j.SetStackObject("librarySearchPath", obj) + arg := j.GetArgument("librarySearchPath", argIdx) + j.SetVariable(arg, arg.Value.Call("concat", newLibraryPath)) return nil }) if err != nil {