The magic of self-patching vtable

Coleen Phillimore coleen.phillimore at oracle.com
Mon Dec 17 13:40:17 PST 2012


On 12/14/2012 05:29 PM, Ioi Lam wrote:
> On 12/14/2012 02:10 PM, Coleen Phillimore wrote:
>> On 12/14/2012 5:04 PM, Ioi Lam wrote:
>>> Moving this discussion to hotspot-dev per John's suggestion.
>>>
>>> Actually, strictly from the point of view of CDS, it would be OK for 
>>> objects in the RO region to have vtables. This is because the _vptr 
>>> is fixed during dump time, and never modified during runtime.
>>>
>>> Only the contents of the vtables (pointed to by _vptr) are updated 
>>> at CDS image load time.
>>
>> The way it works today is that the _vptr in the object is replaced, 
>> so you'd have to change that.
>>
>> Coleen
>>
>
> Hi Coleen,
>
> As far as I can see in today's code, _vptr is replaced here 
> (metaspaceShared.cpp, version 3823:070d523b96a7), which is called at 
> dumping time. During dumping time, the "RO" section is actually writable.

Yes, you are correct.   When you run Java during dumping time, the RO 
section is writable.   When Java runs again with -XX:+UseSharedSpaces 
(-Xshare:auto which means "use the shared region if possible"), the 
regions are mmap'ed back again, and the RO section is mapped 
read-only.   See
MetaspaceShared::map_shared_spaces() in metaspaceShared.cpp.

This comment is trying to document an issue I ran into while working on 
this.  I had an oop_is_instance() call after the self-patching vtables 
were installed in the dumping phase, which nicely restored the original 
vtable for me.   Then when rerunning with UseSharedSpaces, the vtable 
pointed to some text from a terminated process.

Your original idea of generating these vtables without machine dependent 
code sounded really good.

Thanks,
Coleen

>
> // Assumes the vtable is in first slot in object.
> static void patch_klass_vtables(void** vtbl_list, void* 
> new_vtable_start) {
>   int n = _global_klass_objects->length();
>   for (int i = 0; i < n; i++) {
>     Klass* obj = _global_klass_objects->at(i);
>     // Note oop_is_instance() is a virtual call.  After patching vtables
>     // all virtual calls on the dummy vtables will restore the original!
>     if (obj->oop_is_instance()) {
>       InstanceKlass* ik = InstanceKlass::cast(obj);
>       *(void**)ik = find_matching_vtbl_ptr(vtbl_list, 
> new_vtable_start, ik);
>       ConstantPool* cp = ik->constants();
>       *(void**)cp = find_matching_vtbl_ptr(vtbl_list, 
> new_vtable_start, cp);
>       for (int j = 0; j < ik->methods()->length(); j++) {
>         Method* m = ik->methods()->at(j);
>         *(void**)m = find_matching_vtbl_ptr(vtbl_list, 
> new_vtable_start, m);
>       }
>     } else {
>       // Array klasses
>       Klass* k = obj;
>       *(void**)k = find_matching_vtbl_ptr(vtbl_list, new_vtable_start, 
> k);
>     }
>   }
> }
>
> - Ioi
>
>>>
>>> - Ioi
>>>
>>>
>>> On 12/14/2012 01:31 PM, John Rose wrote:
>>>> This is really interesting.
>>>>
>>>> I'll bet there could be a shell script that could detect and flag 
>>>> unintentional vtables on RO classes, at least on Linux or Solaris. 
>>>> That would be enough to block JPRT submissions.
>>>>
>>>> -- John
>>>>
>>>> On Dec 14, 2012, at 12:56 PM, Coleen Phillimore 
>>>> <coleen.phillimore at oracle.com> wrote:
>>>>
>>>>> On 12/14/2012 3:46 PM, Ioi Lam wrote:
>>>>>> Hi Coleen,
>>>>>>
>>>>>> Yes, I could find lots comments for 'how' the self-patching 
>>>>>> vtable works. I just couldn't find a justification for 'why' it 
>>>>>> needs to be done this way.
>>>>>>
>>>>>> The _vptr of all the Metadata objects in the CDS image are 
>>>>>> already fixed during dump time. They are never changed at 
>>>>>> run-time. I.e., immediately after the CDS image is loaded, we 
>>>>>> have this invariant:
>>>>>>
>>>>>> Universe::boolArrayKlassObj()->_vptr == cds_TypeArrayKlass_vtable
>>>>>> Universe::byteArrayKlassObj()->_vptr == cds_TypeArrayKlass_vtable
>>>>>> Universe::charArrayKlassObj()->_vptr == cds_TypeArrayKlass_vtable
>>>>>> ...
>>>>>>
>>>>>> where
>>>>>>
>>>>>> cds_TypeArrayKlass_vtable points to somewhere slightly above the 
>>>>>> "md" region of the mapped CDS image.
>>>>>>
>>>>>> If we can find out the size of the vtable for TypeArrayKlass, 
>>>>>> using the method I proposed below, why can't we simply do this as 
>>>>>> part of CDS image loading?
>>>>>>
>>>>>> {
>>>>>>     TypeArrayKlass o;
>>>>>>     void * real_vptr = *(void**)(&o);// == o._vptr;
>>>>>>     memcpy(cds_TypeArrayKlass_vtable, real_vptr, sizeof(void*) * 
>>>>>> num_vtable_slots_TypeArrayKlass);
>>>>>> }
>>>>> Yeah, that would be good except I can't figure out how that works, 
>>>>> but I'll take your word for it rather than trying to work it out 
>>>>> right now.
>>>>>
>>>>> You'd do this restoration in the function 
>>>>> restore_unshareable_info() instead of loading (lazily) though.
>>>>>
>>>>> Coleen
>>>>>
>>>>>> Thanks
>>>>>> - Ioi
>>>>>>
>>>>>> On 12/14/2012 05:03 AM, Coleen Phillimore wrote:
>>>>>>> Hi Ioi,
>>>>>>>
>>>>>>> There are comments about self-patching vtables in 
>>>>>>> memory/universe.cpp and memory/metaspaceShared.cpp and the 
>>>>>>> target dependent metaspaceShared_<cpu>.cpp code. You have 
>>>>>>> summarized how/why it works quite nicely.     The main reason 
>>>>>>> for vtable patching is that the .text is in different places for 
>>>>>>> the executables which load the shared archive, so we have this 
>>>>>>> code to fix it up in the miscellaneous code section.
>>>>>>>
>>>>>>> The other thing you might have left off which people should be 
>>>>>>> very aware of is that metadata that is shared read-only, like 
>>>>>>> classes ConstMethod, Array<alltypes>, and Symbols. Adding 
>>>>>>> virtual function calls to these will result in them having a 
>>>>>>> self-patching vtable and they will no longer be read only.    Do 
>>>>>>> not add virtual functions to these types! I hope there are 
>>>>>>> enough comments warning of this.   I cannot think of a 
>>>>>>> programmatic way to disallow this.
>>>>>>>
>>>>>>> I have another comment for your [5]-[6] below.   The cpu 
>>>>>>> dependent code is to create the self patching entries per 
>>>>>>> platform.  We can't tell how long the vtable is so picked 200 
>>>>>>> for historical purposes.   But we still need the cpu dependent 
>>>>>>> code because we still need vtable patching because of point [2] 
>>>>>>> and it's done with macro assembler. If the macro assembler was 
>>>>>>> made to be a high level macro assembler, we wouldn't need cpu 
>>>>>>> dependent code. Knowing in advance the size of the vtable would 
>>>>>>> save the hard-coded constant, but that's not really a big benefit.
>>>>>>>
>>>>>>> An aside why 200 was picked.  In the pre-permgen world, the 
>>>>>>> metadata in the shared space were Java objects and were 
>>>>>>> inherited from oopDesc.   Any new virtual function added for GC 
>>>>>>> in oopDesc would regularly blow out the length of the vtable 
>>>>>>> size. This isn't the case anymore since the base class for these 
>>>>>>> types is Metadata and there aren't very many virtual functions 
>>>>>>> there.   The type Klass* has a lot more, but still way under 
>>>>>>> 200.   A sanity check in debug mode might be useful.
>>>>>>>
>>>>>>> thanks,
>>>>>>> Coleen
>>>>>>>
>>>>>>> On 12/14/2012 1:17 AM, Ioi Lam wrote:
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> I am reading the CDS code and came upon the rather intriguing 
>>>>>>>> concept of "self patching vtables" -- i.e., 
>>>>>>>> patch_klass_vtables() and friends in 
>>>>>>>> hotspot/src/share/vm/memory/metaspaceShared.cpp.
>>>>>>>>
>>>>>>>> I couldn't find any comments in the source code for the reasons 
>>>>>>>> for such a complicated mechanism. Here's my understanding of 
>>>>>>>> it. Please let me know if this is correct:
>>>>>>>>
>>>>>>>> [1] Objects of the Metadata types (such as Klass and 
>>>>>>>> ConstantPool) have vtables.
>>>>>>>>     In GCC this is the field <Type>::_vptr, i.e., first word in
>>>>>>>>     the object.
>>>>>>>>
>>>>>>>> [2] Addresses of the vtables and the methods may be different 
>>>>>>>> across JVM runs,
>>>>>>>>     if libjvm.so is loaded at a different base address.
>>>>>>>>
>>>>>>>> [3] Although all of the Metadata objects are mapped R/W in the 
>>>>>>>> CDS image,
>>>>>>>>     at load time, we don't want to rewrite _vptr of each 
>>>>>>>> Metadata object
>>>>>>>>     (to maximize sharing).
>>>>>>>>
>>>>>>>> [4] Therefore, we redirect _vptr to our own vtables at CDS 
>>>>>>>> image dump time.
>>>>>>>>     Then,we patch our own vtables at run time.
>>>>>>>>
>>>>>>>> [5] The problem with [4] is with most C++ compilers (all?), 
>>>>>>>> there is no
>>>>>>>>     easy way to tell the size of the vtable of a given type.
>>>>>>>>
>>>>>>>> [6] We cannot safely copy more than the size of the real 
>>>>>>>> vtable, because the
>>>>>>>>     real vtable may be at the end of the code section; reading 
>>>>>>>> past its end
>>>>>>>>     would cause the VM to crash.
>>>>>>>>
>>>>>>>> As a result, the current design of the 'self patching vtable' 
>>>>>>>> is to
>>>>>>>> create a vtable that's "big enough" (currently with 200 method 
>>>>>>>> slots).
>>>>>>>> Each slot points to a generated stub that knows the C++ type and
>>>>>>>> virtual method index of the invoked method. When the stub is 
>>>>>>>> invoked,
>>>>>>>> it will look up the real method and patch the vtable accordingly.
>>>>>>>>
>>>>>>>> -----
>>>>>>>>
>>>>>>>> If I am right that the whole reason for the self patching 
>>>>>>>> vtables is getting the length of the vtable, wouldn't it be 
>>>>>>>> much easier if we do:
>>>>>>>>
>>>>>>>> class InstanceKlassVTableLengthFinder: public InstanceKlass {
>>>>>>>> public:
>>>>>>>>     virtual void ___end_of_vtable_marker();
>>>>>>>> };
>>>>>>>>
>>>>>>>> and then just search for ___end_of_vtable_marker() from the _vptr?
>>>>>>>>
>>>>>>>> That way we can get rid of all the CPU dependent code related 
>>>>>>>> to self-patching vtables.
>>>>>>>>
>>>>>>>> - Ioi
>>>
>>
>



More information about the hotspot-dev mailing list