4 Replies Latest reply on Jul 8, 2016 10:55 AM by Jahwan Kim

    Is it possible to set local variable AT ENTRY and to use it later AT EXIT?

    Jahwan Kim Newbie

      Hi,

       

      I'm trying to use byteman to instrument certain calls. Say,

       

      HELPER test.RuleHelper

       

      RULE test at entry

      CLASS test.Main

      METHOD void test()

      AT ENTRY

      BIND

        ti:test.TraceItem = start();

      IF TRUE

      DO

        debug(">>>>>>>> entry", ti.toString());

      ENDRULE

       

      Now, I'd like to use the variable ti of type test.TraceItem later:

       

      RULE test at exit

      CLASS test.Main

      METHOD void test()

      AT EXIT

      BIND NOTHING

      IF TRUE

      DO

        end(ti);

        debug(">>>>>>>> exit", ti.toString());

      ENDRULE

       

      But this seems not possible. [First Question] Is this possible, without use of some external storage?

       

      I thought about this for a moment, and even without delving into byteman source code, this seems like an impossible task.

      BUT, I think that this is a very typical use case, and that the world would be a better place if there were ways to to achieve this goal.

      This may require some change in the rule grammar, but what about adding, say AT ENTRY_AND_EXIT?

      (I'll add more comments if anyone gets interested.)

       

      Best,

      Jahwan

        • 1. Re: Is it possible to set local variable AT ENTRY and to use it later AT EXIT?
          Andrew Dinn Master

          Hi Jahwan,

           

          It is possible to do what you want, Indeed, there are several ways to achieve it. Unfortunately, the way you are trying to do it really does not work (it's a nice try but it's based on a misunderstanding). So, first, I'll show the easiest option that will work. Then - for completeness -- I'll explain what is wrong with what you are trying to do. Finally, I'll show you how you can do something else similar to what you tried that will also work. It's probably better to use the first solution but you can decide that.

           

          The best way to do what you want is to use the LinkMap API. This provides 3 built-in methods link(), linked() and unlink(). You don't need a helper to use them - this API is provided by the standard Byteman helper class (you do need to use Byteman 3.0.5 though -- it is a recent addition). If you are going to use your own helper class to provide the tracing behaviour that you want then you need to ensure that your helper class extends the Byteman helper class. If you don't then your rules cannot use the LinkMap builtins (if extending the default Helper class really is not possible then don't worry, the solution I provide at the end of this reply will work without needing to do that).

           

          package test;
          class Helper extends org.jboss.byteman.rule.helper.Helper
          {
            . . .
          }
          

           

          Here is one way you might rewrite your rules

           

          HELPER test.RuleHelper
          
          RULE test at entry
          CLASS test.Main
          METHOD void test()
          AT ENTRY
          BIND
            ti:test.TraceItem = start();
          IF TRUE
          DO
            link("savedValue", ti)
            debug(">>>>>>>> entry", ti.toString());
          ENDRULE
          
          RULE test at exit
          CLASS test.Main
          METHOD void test()
          AT EXIT
          BIND
            ti:test.TraceItem = linked("savedValue")
          IF ti != null
          DO
            end(ti);
            unlink("savedValue");
            debug(">>>>>>>> exit", ti.toString());
          ENDRULE
          

           

          In the first rule builtin method link establishes a link between the the Object provided as its first argument (String "savedValue") and the Object provided as its second argument. The Byteman Helper class ensures that this link is retained from one rule execution to the next by inserting the link into a LinkMap (this is actually a static private hash map belonging to class Helper). In the second rule builtin method linked searches to see if there is an Object linked from its first argument returning the object if a link exists otherwise returning null. The call to unlink in the DO clause is just to clean things up so that the link is removed. You could actually have called unlink in the BIND clause initializer instead of linked since unlink not only removes any link but also returns the value that is being unlinked if it exists (or null if no link is found).

           

          It is worth noticing that linked returns a value of type Object yet this value is being assigned to a rule variable or type TraceItem. If you did this in a normal assignment in the rule condition or action that would lead to a type error. It's allowed in a BIND initialization where Byteman will perform an explicit downcast from Object to TraceItem. That works with these two rules because the linked item is actually a TraceItem. Of course, if you had linked the wrong type of item in the first rule then this second rule would throw a ClassCastException.

           

          This is useful if you want to save a single named property but it's not really good enough in other cases, say, for example, if method test may be called by more than one thread at the same time. Imagine what might happen:

           

            thread A enters test() and calls link("savedValue", t1)

            thread B enters test() and calls link("savedValue", t2)

            thread A exits test() and calls linked("savedValue") retrieving t2!!

            thread A also calls unlink("savedValue") removing t2!!

            thread B exits test() and calls linked("savedValue") retrieving null!!

           

          There is a more general version of the methods link etc which deals with this problem. The calls I showed you above all use the default LinkMap which is identifed using the label "default". For each call the table below shows an equivalent call which provides an extra argument identifying which LinkMap to use.

           

            link("savedValue", t1) -->   link("default", "savedValue", t1)
            linked("savedValue")   -->   linked("default", "savedValue")
            unlink("savedValue")   -->   unlink("default", "savedValue")
          

           

          You don't have to use a String to label a LinkMap -- you can use any Object. Byteman will create a LinkMap when you try to use it if it does not already exist. So, in the above case you could deal with the multi-threading use case as follows

           

            thread A enters test() and calls link(Thread.currentThread(), "savedValue", t1)

            thread B enters test() and calls link(Thread.currentThread(),"savedValue", t2)

            thread A exits test() and calls linked(Thread.currentThread(), "savedValue") retrieving t1

            thread A also calls unlink(Thread.currentThread(), "savedValue") removing t1

            thread B exits test() and calls linked(Thread.currentThread(),"savedValue") retrieving t2

            thread B also calls unlink(Thread.currentThread(), "savedValue") removing t2

           

          Ok, so what was wrong with your version?

           

          The problem is that the value that you created in your first rule was only bound to rule (BIND) variable ti. The scope of a rule variable is limited to the lifetime of the rule execution. Once you exit the rule that binding goes away and the value is lost. If you want a value created in one rule to be available in some other rule then you need to save it from one rule execution to the next. That's exactly what the LinkMap API is for. It saves a value identified in one ruel and allows you to find it again later in another rule.

           

          LinkMaps are not the only way that the standard builtin methods allow you to save values across calls. Class Helper provides several other APIs which save values from one rule execution for use in a laoer rule execution. For example, when you call the Counter API builtin incrementCounter(Thread.currentThread(), $1.size) in one of your rules then Byteman will increment a counter associated with the current thread creating and initializing it with value 0 if it has not already been created. That same counter can then be referred to in another rule by calling readCounter(Thread.currentThread()). Other ways to manage state that is retained from one rule execution to the next are provided by the CountDown, Flag, Waiter, Rendezvous, Timer, Joiner and Waiter APIs. These are all explained in the Programmer's Guide.


          Finally, how could you have made your Helper do this without relying on the LinkMap calls?


          Well, one way to answer that is to look at how the standard Helper implements all these APIs and to give your class a similar API. Your Helper could just implement something like link, linked and unlink. Alternatively, here is a slightly simpler way to do it based on use of a ThreadLocal (which is actually just a hashmap keyed by Thread).


          package test;
          class Helper
          {
            private ThreadLocal<TraceItem> traceItem = new ThreadLocal<Traceitem>();
          
          
            public void start() {
              TraceItem t = makeTraceItem()
          
              traceItem.set(t)
          
            }
          
          
            public void end() {
              TraceItem t = traceItem.get();
              if (t != null) {
                processTraceItem(t);
          
              }
          
            }
            private TraceItem makeTraceItem{ ... }
            private void processTraceItem{ ... }
          }
          
          

           

          Your AT ENTRY rule simply needs to call start() and your AT EXIT rule simply needs to call end(). You might want to change this so that start() and end return the TraceItem instance. That would allow the rule to use the instance in debug statements.

           

          I hope that answers all your questions. Please let me know if you want more help.

           

          regards,

           

           

          Andrew Dinn

          • 2. Re: Is it possible to set local variable AT ENTRY and to use it later AT EXIT?
            Jahwan Kim Newbie

            Hi, Andrew,

             

            Thank you for your prompt reply, and oh, for the wonderful software too.

             

            I thought about this problem for a while, and read programmer's guide as well as the source code

            of Helper. Yes, your suggestion is a way to achieve my goal. Also I thought about the way binding works,

            and your answer made it clear how it works. But what if the original method I'd like to trace is reentrant?

            (Note also that reentrancy may happen due to some bug.) I guess I could use map of stack of TraceItem,

            but it's gonna bring some complexity, esp. to error handling. So it seems to me the natural way is

            to inject *a real local variable*. For example, I know I can use asm directly to do this.

            (But I *really* like the simpler way of byteman.) So I was trying to find a way within byteman, which may

            be against how byteman is structured. Every rule in byteman is a completely independent of one another, right?


            So another feature suggestion is: Isn't it be wonderful if there's a way to use asm directly, within byteman?

            Even to a non-expert like me, the two don't seem to go along at first sight,

            since one uses visitor pattern and the other is controlled by rules. But if

            done properly, this would make byteman the easiest to use as well as the most flexible.


            Best,

            Jahwan

             














            • 3. Re: Is it possible to set local variable AT ENTRY and to use it later AT EXIT?
              Andrew Dinn Master

              Hi Jahwan,

               

              I can think of several ways to deal with the problem of recursive calls from within Byteman using the available builtin operations (I'll come to that in a moment). Injecting a  local variable is not actually a very easy thing to do even when doing it from within Byteman (which includes in its implementation about half of what you might find in a compiler). You might have some fun trying to achieve it but one of the reasons I decided to provide this sort of functionality in the Helper class is because it really is difficult.

               

              It sounds like what you actually need is for your helper (or maybe even the default helper) to provide some sort of Stack API that allows values to be pushed and popped from one rule to the next. That sounds like quite a common use case. So, as well as solving the problem using the existing Byteman builtins I'll show you how you can add that API to your own Helper. Indeed if it looks useful enough I may migrate the code to the default helper (if I do I'll credit you in the release notes for giving me the idea! :-)

               

              So, firstly, how can you do what you need using existing builtins? The basic idea is to use the LinkMap like you did before to save a value on entry and retrieve it one exit. In order to deal with recursion you also have to combine it with a counter which allows you to keep track of multiple versions of the saved value. So, here are the rules for your test method (assuming that method test() may end up calling itself)

               

              HELPER test.RuleHelper
              
              RULE test at entry
              CLASS test.Main
              METHOD void test()
              AT ENTRY
              BIND
                th = Thread.currentThread();
                ti:test.TraceItem = start();
                count:int = incrementCounter(th);
              IF TRUE
              DO
                link(th, "savedValue" + count, ti)
                debug(">>>>>>>> entry", ti.toString());
              ENDRULE
              
              RULE test at exit
              CLASS test.Main
              METHOD void test()
              AT EXIT
              BIND
                th = Thread.currentThread();
                ti:test.TraceItem = linked("savedValue");
                count:int = readCounter(th);
              IF ti != null
              DO
                end(ti);
                unlink(th, "savedValue" + count);
                decrementCounter(th);
                debug(">>>>>>>> exit", ti.toString());
              ENDRULE
              
              
              
              

               

              Each thread has its own counter labelled using the current thread. The countyer is incremented when the method is entered and decremented when it is exited which means it changes whenever a recursive call is made. The label used to save and restore the value of ti is created by appending the counter to the constant string "savedValue". The thread is passed as the label for the LinkMap passed before the label used to name the value.

               

              Ok, so how about providing a way of doing this in your Helper? We could start with a new Stack API which allows you to push and pop Objects onto a single global stack.

               

              class Helper extends org.jboss.byteman/rule.helper.Helper
              {
                public void push(Object value) { ...}
                public Object pop() throws RuntimeException { ... }
              }
              
              

               

              However, we know that this is not enough because we will want more than one stack -- at least one for each thread. So, lets add variants of push and pop which take an extra object to identify the stack to use

               

              class Helper extends org.jboss.byteman/rule.helper.Helper
              {
                public void push(Object value) { push("default", value); }
                public Object pop() throws NoSuchElementException { return pop("default"); }
                public void push(Object stackid, Object  value) { ... }
                public Object pop(Object stackid) throws NoSuchElementException { ... }
              }
              
              

               

              Ok, so how do we implement this? We will need a private hashmap to map stackIds to stacks

               

               

              class Helper extends org.jboss.byteman/rule.helper.Helper
              {
                private HashMap<Object, LinkedList<Object>> stackMap;
                public void push(Object value) { push("default", value); }
                public Object pop() throws NoSuchElementException { return pop("default"); }
                public void push(Object stackid, Object  value) {
                  if (stackId == null) {
                    return;
                  }
                  ArrayList list = null;
                  synchronized (stackMap)
                    list = hashMap.get(stackId);
                    if (list == null) {
                      list = new LinkedList<Object>();
                      hashmap.put(stackId, list);
                    }
                  }
                  synchronized(list) {
                    return list.push(value);
                  }
                }
                public Object pop(Object stackId) {
                  if (stackId == null) {
                    throw new NoSuchElementException();
                  }
                  ArrayList list = null;
                  synchronized (stackMap) {
                    list = hashMap.get(stackId);
                    if (list == null) {
                      throw new NoSuchElementException();
                    }
                  }
                  synchronized(list) {
                    return list.pop();
                  }
                }
              
              

               

              So, now your rules can just use push(Thread.currentThread(), ti) and pop (Thread.currentThread()).

               

              regards,

               

               

              Andrew Dinn

              • 4. Re: Is it possible to set local variable AT ENTRY and to use it later AT EXIT?
                Jahwan Kim Newbie

                Hi Andrew again,

                 

                Thanks again. Well indeed I implemented something similar in my RuleHelper. I'd probably go with what I have,

                but I probably will keep thinking about using asm directly in byteman-like rules.