2

Here is the short version of the question: is there an efficient way, with bytecode manipulation, to replace a value deep on the JVM stack? To help visualize, the ideal solution would look like this in psuedo-code: stack[offset] = new_value.

Looking at the list of Jasmin instructions, it seems the only way to replace a value "deep" on the stack (really, only more than about 4 stack slots down) is as follows:

  • allocate a collection (e.g. array or list)
  • store each stack value into the collection, consuming each from the stack in the process
  • repeat until the value to replace is at the top of the stack
  • pop off the value to replace
  • push on the new value
  • push the values from the collection back onto the stack (in the correct order, of course)

Is there an more-effecient manner?

Note that the use of local variables is assumed too dangerous as the code here must work with an unknown method, of an unknown class, regardless of its local variable definitions, and without the pre-condition that it can be able to change the local variable definitions for the method, or the fields for the class.

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
ash
  • 4,867
  • 1
  • 23
  • 33
  • 1
    You'd run into the bytecode verifier pretty quickly as it would not know what the type was of the value on the stack if would access it in a random-access manner. If you do it like you suggest, you'd have to pick a type for the array (like `Object[]`) and you'd have to check the type again in bytecode before using it as a reference to something else, so then it wouldn't be a problem. – Erwin Bolwidt Apr 13 '14 at 06:17
  • 1
    +1 for previous comment. I suspect that even your inefficient way won't work ... because there is no Java array or collection type that can hold a mixture of primitive and non-primitive element types. – Stephen C Apr 13 '14 at 06:23
  • Understood. Saving the types and restoring is solvable too, but not ideal. – ash Apr 13 '14 at 06:30
  • @StephenC btw - an Object[] can hold wrappers for primitive types. It just becomes necessary to use methods such as Integer.valueOf() and Integer.intValue() storing values in the array and pulling them back out. – ash Apr 13 '14 at 17:38

2 Answers2

0

No, there is no easy way to do this. The operand stack is designed to be, well, a stack.

If you could explain what precisely you are trying to do, perhaps I could suggest a better way to do it.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • In this case, I'm trying to use a Class Transformer to change a method call from obj1.methodX(...) to obj2.methodX(...). Since the argument lists are variable in length, it's hard to do. I recognize all the potential problems this could raise, such as incompatible method signatures. – ash Apr 13 '14 at 15:56
  • By the way - do you have a lot of experience here? I hope you don't mind me asking - I just recognize there are many people here who will just throw up an answer to get points, and it is hard to validate "there is no easy way" compared to "look at tool xxx", even though I suspect it is the right one. Thank you! – ash Apr 13 '14 at 16:11
  • @ash Well I've written my own bytecode assembler and disassembler as well as written crackmes directly in bytecode. So I'm fairly knowledgeable about the bytecode isa. – Antimony Apr 13 '14 at 17:17
  • @ash If all you're doing is changing the class, that's easy, assuming that obj1.methodX and obj2.methodX have compatible signatures. You don't need to touch the arguments or anything. If they don't have compatible signatures, you have bigger issues on your hands than stack manipulation. – Antimony Apr 13 '14 at 17:19
  • sounds right. I know it's feasible to accomplish the end-goal. Now, I'm just trying to find an efficient way. Creating a temporary collection and pushing arguments into it just seems like a lot of overhead for the ultimate goal. – ash Apr 13 '14 at 17:21
  • @ash I'm confused about which part you're having trouble with or why you beleive that you have to modify the arguments. Are you perhaps getting mixed up with Java level variable arity methods? Those don't exist at the bytecode level - they just take an array parameter. Perhaps it would help if you could post an actual example of precisely what you are trying to accomplish, including the descriptors of the relevant methods. – Antimony Apr 13 '14 at 17:22
  • Don't believe I'm confused. My experience with the java bytecode is limited, so I was just hoping to tap the knowledge of someone, like yourself, with more experience to see if there was a more straight-forward, or efficient way to accomplish the goal. I know that I can use a collection to accomplish the goal. – ash Apr 13 '14 at 17:24
  • I can't immagine any case in which a collection would be useful. Either the methods have the same descriptor or they don't. In the first case, changing it is trivial, in the second case you have bigger issues. Remember, there's no such thing as method overloading or variable argument lists in bytecode. – Antimony Apr 13 '14 at 17:26
  • I'm pretty sure there is a more straight-forward way to do whatever it is you're trying to do but you won't give me enough information to tell you. – Antimony Apr 13 '14 at 17:29
  • That's right. Variable argument list = array in bytecode, which is handy. I'd be interested to tap you for my project - it's too heavily in prototype phase right now to share. – ash Apr 13 '14 at 17:31
  • on the Collection, the point is changing the target object of the method call. Since that's on the stack below the list of method arguments, and it's the only thing that needs to change, for long argument lists, a collection can be used to pop values off the stack and store them temporarily, and then finally get to the object reference and replace it. Hence the ```obj2.methodX(...)``` inplace of ```obj1.methodX(...)```. – ash Apr 13 '14 at 17:33
  • Oh, so you only want to replace one specific instance of the object while leaving other instances of the same class? That does sound a bit tricky to do in full generality. To be fair though, it's impossible to do all but the most trivial change to a completely general classfile. Even adding an instruction will fail if it's already at the max bytecode limit. – Antimony Apr 13 '14 at 17:42
  • In typical compiled code the object will be pushed onto the stack prior to the call and not used for anything else. The simplest method would be to just change it at that point. It won't work for pathological handwritten code, but then again nothing will in general. Incidentally, how are you changing the object in the first place? If you're simply replacing one class with another everywhere, than that's really simple. – Antimony Apr 13 '14 at 17:45
  • ah, thanks for the max bytecode limit tidbit - I was unaware of that. Well, this is for others to use on targetted classes, so it's ok if it doesn't work with some classes, especially if those classes are extreme examples. But now you've caught my curiosity. I'm wishing (yet again) Java had an true object-oriented messaging idea instead of direct method invocation (that allows sending any message to any object - like objectiveC can do). – ash Apr 13 '14 at 17:48
  • Yeah, there's very little you can do to an arbitrary classfile without a chance of breaking it. You can't even rename the class because they might have filled up the ConstantPool. But those limits are never reached in practice unless you deliberately craft a class to do so. The only limit I've seen reached in legitimate code is the bytecode size limit and that's just because idiots keep hardcoding really huge static arrays that generate bloated initialization code. – Antimony Apr 13 '14 at 17:51
  • It's not a global replace of one object. I'm looking to trap all calls originating from the methods of a specific class, so the target objects aren't even known until runtime, and there can be any number of objects involved. Finding the instructions that inject the object to replace with an alternate would be really tricky and involve some serious analyis of the bytecode. – ash Apr 13 '14 at 17:52
  • I appreciate tapping your expertise. – ash Apr 13 '14 at 17:53
  • You might want to look into invokedynamic. It's a new feature in Java 7 designed to make supporting dynamic behavior easier. You can write runtime dispatch without the use of reflection or runtime classfile transformations. – Antimony Apr 13 '14 at 17:57
  • I started to look at invokedynamic and only found a very long blog article more focused on arguing it's purpose, and the JSR in which I found minimal detail. Any link to more detail would be welcome. Make that a better amount of detail - explaining how it works and how to use it. Nothing about motives for its creation in the first place. – ash Apr 13 '14 at 19:07
  • Sadly, I don't think there is that much information about it. Though there is more than one blog article at least. You can find a basic example and explanation online, more advanced usage is mostly a matter of reading the CallSite documentation. – Antimony Apr 13 '14 at 22:34
0

Using a LocalVariablesSorter from the ASM bytecode manipulation framework you can freely introduce new local variables and transparently remap existing ones. There are examples in the ASM User Guide.

Eugene Kuleshov
  • 31,461
  • 5
  • 66
  • 67