Interpreter calling a C method
steve goldman
Steve.Goldman at Sun.COM
Tue Oct 9 11:29:22 PDT 2007
Peter Helfer wrote:
> @Steve: thanks for the quick reply!
>
> Ok, firstly my do_funnymethod is actually just calling tty->print_cr("Here I
> am") which should not cause a safepoint, I assume; so far it worked when
> compiling the rest of make debug_build with that message.
In some sense that is illegal since it is doing i/o and should it block
the jvm will halt. In this exploratory instance it is mostly ok.
In general you need to use valid thread state transitions. While we are
in Java code the thread_state is "in_Java". When you transition to
somewhere where you essentially leave Java (a safepoint could occur) you
need to change the state so that the jvm won't dead lock. In general in
entering the jvm you'll see some sort of entry macro (JRT_ENTRY,
IRT_ENTRY) that defines the type of entry and does the proper state
transitions.
>
> I want to be able to tinker around with the frames just left by the
> interpreter (later on, it should be as well on compiled frames...), so I
> guess I have to stick to call_VM_base. What I've found is the interpreter
> frame layout in frame_i486.hpp:
You want to use call_VM (or call_VM_leaf) leave the _base methods alone.
>
> // A frame represents a physical stack frame (an activation). Frames can be
> // C or Java frames, and the Java frames can be interpreted or compiled.
> // In contrast, vframes represent source-level activations, so that one
> physical frame
> // can correspond to multiple source level frames because of inlining.
> // A frame is comprised of {pc, fp, sp}
>
> // Layout of interpreter frame:
> // [expression stack ] * <- sp
> // [monitors ] \
> // ... | monitor block size
> // [monitors ] /
> // [monitor block size ]
> // [byte code index/pointr] = bcx()
> bcx_offset
> // [pointer to locals ] = locals()
> locals_offset
> // [constant pool cache ] = cache()
> cache_offset
> // [methodData ] = mdp()
> mdx_offset
> // [methodOop ] = method()
> method_offset
> // [last sp ] = last_sp()
> last_sp_offset
> // [old stack pointer ] (sender_sp)
> sender_sp_offset
> // [old frame pointer ] <- fp = link()
> // [return pc ]
> // [oop temp ] (only for native calls)
> // [locals and parameters ]
> // <- sender sp
>
> As well I figured out that the interpreter tries to leave things in regs
> (RAX, RDX) rather than storing it always on stack; this is encoded in
> TosState, what it is. Now when calling call_vm, it should take care of
> those, as you are telling me, right ?
Not if you are doing arbitrary calls at arbitrary places. In the case of
invoke the tosca (top-of-stack-cache) has been flushed (i.e. stored to
the expression stack so that we're in "vtos" mode). If you try this in
arbitrary places to tosca must be flushed so that if a gc occurs it will
see the proper stack state.
>
> To get the vframes beneath the current frame (which is now marked as native
> (=in VM) I believe), I call thread->last_frame to get all the frames (which
> can comprise multiple vframes), or thread->last_java_vframe to get only the
> last java frames, without any native threads ?
The stack walkers can only see a subset of frames. They won't see any
c++ frames. They can see interpreter frames, compiled frames, and stub
frames. When you use a vanilla frame to walk the stack (via sender calls
) you'll see individual frames as they exist on the stack (i.e. an
actual activation). Typically you do those kind of walk be starting with
thread->last_frame(). In the case of a compiled frame that actual
activation may contain multiple Java method frames because of inlining.
You can see every one of these by using a vframeStream. The stream is
constructed using the thread and you don't need to do sender type calls.
>
> In my case is this the right assumption about the stack layout ?
>
> (up, growing to 0x0000)
>
> | local vars | <- %ESP
> | saved ebp | <- %EBP
> | return addr |
> | param1 |
> | ... | the call_vm frame (which is cdecl)
> | param N |
> ++++++++++++++++++
> | java_argN | [ADDR1]
> | ... |
> | java_arg1 |
> | objectref | (as long as we are not static)
> | rest of the |
> | expression- |
> | stack |
> | monitorN |
> | .... |
> | monitor0 |
> | monitorblsize|
I don't know what monitorblsize is. I think this is wrong.
> | bci/bcp |-> pointing to the instruction invokeZZZ in
> methodOop->constMethodOop->codes_offset()+ ~bci~
> | ptrToLocals |-> pointing to ADDR0
> | cpCache |
> | methodData |
> | methodOop |
> | lastSP |-> pointing to ADDR1
> | oldSP |-> pointing to ADDR2 (senderSP)
> | oldFP |-> pointing to ADDR3
> | returnPC |-> the PC of the caller, either interp, c2i or deopt
> -stubcode
> | local0 | [ADDR0]
> | locals1-N |
> | paramN |
> | ... |
> | param0 |
> ++++++++++++++
This last section is almost all wrong. The params of the caller become
the locals of the callee. Since this is a stack and param0 is pushed
first it is at a higher address and it is local[0] for the callee.
param1 is essentially local[-1], etc. Since the callee can have more
locals than params the caller's stack is typically extended by the
"extra locals".
There might be some more things to learn at this blog:
http://gbenson.livejournal.com/
which is a guy from RedHat doing a ppc port. He's using the c++
interpreter but a lot of the basics (like local layout) is the same.
I've left some comments in his blog about questions he has raised trying
to make this clear.
> | either: | [ADDR2]
> | -expression stack
> | -compiled stuff stack
> | |
> | oldFP | [ADDR3]
> |
> |
> 0xFFFF:
>
> Finally, to mess up totally, I could use in call_VM_base
> thread->frame_anchor->set_last_Java_pc(address pc) to change the return
> point to something totally different (to a compiled stub, which knows about
> how to continue with that current expression stack for example) ?
No. The anchor pc is there just to make the frame recognizable. That
mutator is probably only used by the c++ interpreter and if you change
the value you mostly only succeed in crashing the stack walker.
In order to modify a return address you need to use frame::patch_pc().
This only works in very certain circumstances. You can't use it to
change where the VM is going to return to a stub. In general you are
very safe to modify the pc of last_frame(). In even more generality you
are likely to crash something without being very careful about when you
apply a patch like this.
>
>
> One additional question: is there a general rule when a safepoint can happen
> ? I've seen the runtime/interfaceSupport.hpp, which declares all the macros
> JRT_ and IRT_ - which are allocating the
> ThreadInVMfromJava, and HandleMarkCleaner onstack to provoke a transition in
> the constructor/destructor
> Now by whom are safepoints placed ?
safepoint is a very overloaded term in the vm. A thread is at a "safe
point" whenever it is stack walkable and safe for the gc to modify its
stack. A safepoint is also used to mean when the vm thread has brought
all the Java threads a safepoint. Reaching a safe point is a cooperative
process. If a Java thread transitions to native then it is at a
safepoint. Its stack is walkable and it will be blocked from further
execution if it tries to modify Java state. Threads in "vm" state are
not at a safepoint but they do have a stack that is walkable (in the
sense we can find it all). Since the thead is still executing it isn't
quite safe yet. If the vm thread decided to bring the world to a stop
for something like a gc then if a thread in vm attempts to return to
Java (say compiled/interpreted code) it will block itself. Threads
running in Java mode poll to see whether they should block.
--
Steve
More information about the hotspot-runtime-dev
mailing list