|
C++ Redirection
We can use DDL for redirection c++ methods, but while doing this we
face some interesting issues like name mangling, constructors,
destructors and virtual functions.
When C++ is compiled, the compiler does name mangling to convert the
class and method name into a single unique symbol. Because C++ allows
method overloading (same name but different parameters), the types of
the parameters are also used in the name mangling to produce a unique
name for each method, overloaded or not, in a class. In this way, C++
is invisible to the dynamic linker, and class methods are only
related in that their mangled names all include the same class name.
It is possible to create wrappers in C for methods in a C++ class. Our
redirection mechanism can map a request for the mangled symbol into the
symbol for our C function. In C, we can directly call the mangled
symbol as another C function, with an explicit "this" pointer as the
first argument. The mechanism is even typesafe when compiled with a C++
compiler and using the extern C syntax. However, it is not very
convenient. Here is an example for this kind of wrapper :
//Sample Class
class A
{
public:
void
foo();
void
bar();
private:
int x;
};
//sample C function
wrapper for
method foo() of class A
void wrapper_A_foo(A *this)
{
// turn off
redirection
for my wrapper code
do_redirect = 0;
//
// put my pre-call
monitoring/logging/whatever code in here
//
// now be sure not
to
redirect the call to the original method foo
// foo__1A is the
mangled
string for A::foo()
redirect_ignore_next("foo__1A","libWrapper");
// turn redirection
back
on
do_redirect = 1;
// call the real
method
this->foo();
// turn off
redirection
again, for the rest of my wrapper
do_redirect = 0;
// turn off
skipping
redirection of A::foo()
redirect_ignore_next("","");
//
// put my post-call
monitoring/logging/whatever code here
//
// turn on
redirection
before we leave
do_redirect = 1;
return;
}
With the application coded in an OO methodology, it will be more
approperiate to write wrappers as C++ calss methods. With wrappers in
C++, our wrapper class(es) ought to mirror our application class(es).
Moreover, the wrappers will need to know about (and invoke methods on)
the application classes. To wrap any non-virtual method of a C++ class,
we need to intercept the call to the that method and redirect it to our
own wrapper class method. Our wrapper class should be a subclass of the
target class (the class whose method(s) we want to wrap), and the
wrapper method should have same type signature as the method which we
want to wrap.
Following figure shows the relationship between the application
class A and the wrapper classwrap A.
In practice, we need to translate the declaration of A into a class
declaration A' where all private members have been made public. We do
not create an implementation of A'. This translation is done so that
the wrapper class can have access to the data and methods. Note that A'
is only used in compiling the wrapper, and we assume that this
translation does not effect the binary layout of the class. Thus, the
encapsulation defined for the class still holds for the rest of the
application it is only for the wrapper that we allow the compiler to
give us full access. As shown in the figure above , we can inherit from
A' and redefine both methods to have wrapped versions. Although we have
related the two methods (wrapped and wrapper) through inheritance, we
can not use the relationship to cause an automatic invocation of the
wrapper method, since the application is not necessarily available for
modification (it may only exist in binary form). Rather, at runtime we
use the DDL capabilities to redirect the calls to the method of the
original class to the method of the wrapper class.
The code for the wrapper for foo() looks like:
returnType wrap_A::foo()
{
returnType
result;
// ops
before
actual call
result
=
A::foo(); // actual call
. // ops
after
actual call
return
result;
}
With this approach we do not need any special handling for the object
reference itself. It is important to note that no object of the wrapper
class is ever instantiated. The application creates
objects of type A, calls methods on type A, and is compiled as such. At
runtime, the calls to the methods which we want to wrap are redirected
to the methods of the wrapper class. Although our wrapper is a subtype
of A' (which we consider equivalent to A for our purposes), it does not
add anything new in its class definition and the explicit
superclass call correctly succeeds because the this pointer refers to
an object of type A anyways.
Wrapping constructors and
destructors
Constructors and destructors are special case methods that require
special handling by our wrapping mechanisms. We can not simply redirect
a constructor call to the constructor of our wrapper class because when
the constructor of the wrapper class is called the constructor of the
parent class is called implicitly, which is again redirected to our
wrapper method. This ends up in an infi- nite loop. To resolve this
problem we write a new (nonconstructor) method in our wrapper class and
redirect the call to the constructor to this method. Inside this
method, we would like to make an explicit constructor call, but C++
language semantics demand that an explicit constructor call should
create a temporary object of the class, which is not what we need. Our
solution to this issue is solved by reverting to using the "extern C"
capability and calling the mangled symbol of the parent class
constructor, passing the "this" pointer as an explicit parameter.
Although not elegant, it does work.
There is one more thing which should be taken care of while wrapping
constructors, the assembly code generated by compilers for constructors
and other functions is slightly different. As we are redirecting the
constructor to a normal function, we need to add some assembly code in
our function so that the final assembly code generated is same as the
assemble code of a normal constructor.
With destructors we face a similar problem. If we redirect the
destructor call to the destructor of our wrapper class, the destructor
of the parent class is implicitly called after returning from the
destructor of the wrapper class. Another restriction that we have in
the case of destructors is that some compilers mangle the symbol of a
destructor in such a way that it cannot be explicitly called, even
using the "extern C" mechanism. Thus, similarly to constructors, we
redirect the destructor call to a regular method of the wrapper class;
however, we do not need to call the destructor through a mangled C
function call. Rather, C++ allows us to call the destructor of our own
class (the wrapper class), and when we call the destructor of the
wrapper class the destructor of parent class is called implicitly. The
destructor of the wrapper class is an empty method, since no object of
the wrapper class exists anyways; it only serves to cause an invocation
of the parent class destructor.
The code for constructor and destructor wrappers look like:
extern "C"
{
extern void __1A(A
*this);
}
class Wrap_A : public A
{
public:
void
wrap_constructor()
{
// ops before actual call
__1A(this); // actual call
// ops after actual call
asm("movl
%esi,%eax");
}
~Wrap_A()
{
}
void
wrap_destructor()
{
// ops before actual call
this->~Wrap_A(); // actual call
// ops after actual call
}
}
The destructor wrapper in the above example will only work for
non-virtual destructors.
Wrapping Virtual Methods
The process of linking in the case of virtual methods is quite a bit
different from non-virtual methods. Every object of a class with
virtual methods has a pointer to the class vtable. The entries in the
vtable point to the actual methods for the class, no matter where they
are in the class hierarchy. To wrap virtual methods we need to
intercept the binding of the entries stored in the virtual method table.
Generally, C++ compilers optimize away some of the vtable use on calls
of virtual functions. For example, if an object variable is declared
rather than an object pointer, any calls to virtual methods made on
that variable (using the o.foo() syntax rather than the p->foo()
syntax as on a pointer call) can be resolved at compile time, since the
object type is known and is static. These calls will look like
nonvirtual method calls to the linker, and will use normal symbol
resolution and can easily be handled by DDL as previously explained.
For virtual method calls on object pointers that might point to a
variety of object types,however, the vtable must be used. When
supporting calls through a vtable, the mangled method names are not
even referenced, and will not appear as externally required symbols for
the linking process to resolve.
The object vtable pointer is initialized by the constructor(s), and
these are (generally)in the same object code as the vtable itself,
which is (generally) a static list of symbols that are resolved at
load-time, not runtime. The effect of this is that the basic mechanisms
of DDL run-time linking interception are completely bypassed, and thus
without some new capability, virtual method calls can not in general be
supported.
One way of wrapping virtual functions is to overwrite vtable pointer of
the target class by out wrapper class vtable pointer, in this way when
ever any virtual function is called through vtable, the virtual
function of our wrapper calss will be called. This can be done in
the constructor wrapper, in the constructor wrapper method we can call
the constructor of the wrapper calss and pass "this" pointer to it. The
constructor of the wrapper class will initialize this object and will
add its own vtable pointer to it.
There is an entry of run time type information (RTTI) in the vtable
which should be restored (as this entry of wrapper class vtable will
point to the type information of wrapper class). The code for
constructor for this type of wrapping will look like this:
extern "C" {
void __6Wrap_A(A
*p); //
mangled string of wrapper constructor (Wrap_A::Wrap_A())
}
typedef void
(*void_fptr)(void);
void
Wrap_A::wrap_constructor()
{
do_redirect
=0;
// ops
before
actual call to constructor
__6Wrap_A(this); // call constructor of wrapper class (to get the
vtable of wrapper class)
//Restore the
RTTI entry
static
int
rttiRestored = 0;
if(rttiRestored == 0)
{
A *tempA;
tempA = new A;
void_fptr ** ppfuncptr1 = (void_fptr **)this;
void_fptr ** ppfuncptr2 = (void_fptr **)tempA;
//overwrite RTTI entry
*((*(ppfuncptr1+1))+1) = *((*(ppfuncptr2+1))+1);
delete tempA;
rttiRestored = 1;
}
// ops
after
actual call to constructor
do_redirect
=1;
asm("movl
%esi,%eax");
}
We can not use this technique if we there is no constructor in our
target class. We have solved this problem by building into DDL a hook
for watching load-time symbol resolutions. At this point we do not
allow manipulation of this step, because we have not yet considered all
of its ramifications, but we can record the information about symbols
other than dynamically linked
functions. Since these are resolved early on, by the time our
initialization code is called, the vtable symbols are known, and we can
over-write the vtable entries with pointers to our wrapper
functions.
Wrapping and inheritance
So far we have concentrated on wrappers for individual methods of C++
classes. When we aim to wrap a compete C++ class we face some more
issues. In the object oriented paradigm, a class may inherit methods
from one or more base classes. So if we want to wrap a derived class we
need to wrap the methods of the base class(es) too. All the calls to
these methods will be redirected to the methods of our wrapper class.
But in this, all the calls on methods for objects from other
subclasses, which also inherit the methods and which we do not want to
wrap, will also be redirected to our wrapper class.
Figure above shows our wrapper class in a larger class hierarchy. For
example, suppose we have a class B which has a method B :: foo(), and
two subclasses are inherited from it, S1 and S2. S1 and S2 will use the
method foo() of base class unless it is overridden in them. Here if we
want to wrap class S1, we will inherit a our wrapper class from S1 and
will override the method foo() in it, as shown in the figure. The
problem is that all the calls to B : foo() will be redirected to wrap
S1 :: foo(), even if they are made on an object of type S2. This is
because we are using symbol redirection on the B :: foo symbol.
To resolve this problem we make use of the Run Time Type Information
(RTTI) provided by C++. With this we can find the type of object
calling this method at runtime and bypass any wrapper processing of a
call that is not made from an object of our target class. The wrapper
will still add overhead because it is invoked and needs to pass the
invocation on to the real method, but the functionality of the wrapper
can be applied to only one subclass, even in the presence of
shared base class methods.
|