Debugging system with portable debug environment-independent client and non-portable platform-specific server5815653Abstract A system for debugging software uses a portable debug environment-independent client debugger object and at least one non-portable server debugger object with platform-specific debugging logic. The client debugger object has a graphic user interface which allows a user to control and manipulate the server debugger object with debug environment-independent debug requests. The server debugger object performs a platform-specific debug operation on the software to be debugged. The platform-specific results generated by the debugging operation are translated to debug environment-independent results and returned to the client debugger object. This operation allows the same client debugger object to be used with one or more server debugger objects running on different platforms. Claims Having thus described our invention, what we claim as new, and desire to secure by Letters Patent is as follows: Description COPYRIGHT NOTIFICATION
______________________________________
class MDebuggerCollectible {
public:
MDebuggerCollectible( );
virtual MDebuggerCollectible( );
virtual TDebuggerStream&
operator>>=(TDebuggerStream&)
const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&);
virtual MDebuggerCollectible*
X.sub.-- Clone( ) const;
virtual long Hash( ) const;
virtual bool IsEqual(const MDebuggerCollectible*)
const;
virtual bool IsSame(const MDebuggerCollectible*)
const;
bool operator==(const
MDebuggerCollectible&)
const;
bool operator|=(const
MDebuggerCollectible&)
const;
};
______________________________________
The abstract base class defines no data members. The operator>>= streaming function allows the object to stream itself out into the provided stream parameter. Likewise, the operator<<= streaming function allows the object to stream itself in from the provided stream parameter. The x.sub.-- Clone function is used to create copies of the object being used. The implementation of this clone requires macros to perform the mechanical work of instantiating a copy of the object. The Hash member function allows the object to describe itself in terms of a scalar hash value. The IsEqual function allows comparison of objects. The test will return true if equal and false if not. Another function, ISsame, is called for testing identity, meaning that if the object specified as the parameter is the same as the one whose IS same function is being called, the result will be true. Otherwise, the result is false. The member function operator== is provided for testing equality (which calls the IsEqual function). Similarly, the member function operator|= is provided for testing inequality. TPDCollection The abstract base class used to define collections is called TPDCollection. This class defines protocol which is common to all collection classes. In the following example C++ class interface, the collection class has a parameterized type named AObject. As defined by the C++ language standard, when the template class is instantiated, the compiler will pick up a parameterized type, inserting the formal type as necessary where the AObject symbol is used. In FIG. 4, the collection 41 represents a possible layout for the memory locations in which pointers to the objects of type AObject are stored. Memory locations 411, 412, 413, 414, 415, and 416 contain pointers to the individual objects that are stored in the collection class. The actual implementation of the class is dependent on the type of class. The base class TPDCollection only specifies the protocol for access, metrics for sizing, and iteration. In FIG. 4, memory location 411 points to an object 42 which is said to be stored or owned by the collection class. Likewise, memory location 412 points to an object 43 and memory location 415 points to an object 44.
______________________________________
template<class AObject>
class TPDCollection {
public:
TPDCollection( );
TPDCollection (const
TPDCollection<AObject>& source);
virtual TPDCollection( );
virtual TDebuggerStream& operator>>=(TDebuggerStream& dstStream)
const = 0;
virtual TDebuggerStream& operator<<=(TDebuggerStream& srcStream)
= 0;
virtual PrimitiveDbgCollCount
GetCount( ) const = 0;
virtual PrimitiveDbgCollCount
GetSize( ) const = 0;
virtual AObject*
Get(const AObject&) const = 0;
virtual AObject*
Remove(const AObject&) = 0;
virtual void DeleteAll( ) = 0;
virtual TPDCollectionIterator<AObject>*
CreateIterator( ) = 0;
};
______________________________________
The TPDCollection class can be constructed by default or by copying another TPDCollection class (also parameterized over the same type). The latter of these guarantees deep-copy semantics. The operator>>= streaming function allows the object to stream itself out into the provided stream parameter. Likewise, the operator<<= streaming function allows the object to stream itself in from the provided stream parameter. The contained objects (of the parameterized type AObject) are streamed out monomorphically. To determine the number of objects stored in the collection, a program would call the GetCount member function. To determine the effective storage size of the collection, i.e. the allocated space used by the collection, the Get Size function would be called. Retrieval from the collection has a uniform, polymorphic interface. This is achieved by calling the Get function which takes an object of the parameterized type. FIG. 3 includes a description of two objects with the same structure containing differing amounts of data. Depending on the subclass, a mechanism is used whereby objects are constructed in one of two ways: an "identifier" style object 31 and a "full" style object 32. The "identifier" style of object contains an identification information 311 necessary to test that the object 31 being retrieved is the same as the one that is stored 32. In object 31, there may be no data stored. However in object 32, the data members stored as instance data is stored in the remainder of the object 322. An object can be removed by specifying an identification object and passing it to the function Remove. When the objects held by the collection class need to be deleted from memory, the DeleteAll member function is called. This deallocates and destructs each AObject object stored by the collection class and any pointer memory or storage that refers to the objects are also marked as empty. Finally, the CreateIterator function polymorphically creates an iterator which is specific to the class and instance of collection class. TPDCollectionIterator
______________________________________
template<class AObject>
class TPDCollectionIterator {
public:
TPDCollectionIterator(
TPDCollection<AObject>*);
virtual .about.TPDCollectionIterator( );
virtual AObject* Next( ) = 0;
virtual AObject* First( ) = 0;
protected:
TPDCollectionIterator( );
TPDCollection<AObject>*
GetCollection( );
private:
TPDCollection<AObject>*
fCollection;
};
______________________________________
The PDS uses the collections to store, retrieve, and iterate through data. The act of iteration requires creating an iterator which is bound specifically to an instance of a collection class. Accordingly, the iterator must be created with the correct type. The AObject type for the iterator class must be the same as the AObject type specified for a collection class whose objects are to be iterated over. The pointer to the collection over which iteration is to occur is specified when the iterator is constructed. After construction, the owner of the iterator may call the First function, which returns a pointer to the first object in the list. It is not required that the iterator return objects in any particular order unless the contract of the collection class with the client of the class specifies one. For instance, an ordered set class may require that individual objects returned by the iterator are in a particular order but a set class would not. Whenever it is required that the iteration is to start over again, the First function is called again. After receiving a pointer to the first iterated object, the client may call the Next function which will return the next object after the last one returned. Subsequent calls will return objects to subsequent objects in the collection. The data member fCollection is a pointer to the collection over which is being iterated. In FIG. 4, an example of an iterator is shown by iterator object 6, which contains an index to the memory locations which contain pointers to the objects 2, 3, and 4. As the iterator walks through the data structure, first 11, then 12, then 13, and so on until 16, it will only return when it returns values which contain non-NIL values. TPDSet One concrete implementation of the TPDCollection abstract base class is the TPDSet class. This is a very generic class which is also used to support storage of a wide variety of types. Objects can be of any type and are stored in an unordered manner.
______________________________________
template<class AObject>
class TPDSet : public TPDCollection<AObject> {
public:
friend class TPDSetIterator<AObject>;
TPDSet ( );
virtual .about.TPDSet( );
TPDSet<AObject>& operator=(const
TPDSet<AObject>&);
virtual TDebuggerStream&
operator>>=(TDebuggerStream&
dstStream) const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&
srcStream);
PrimitiveDbgCollCount
GetCount( ) const;
PrimitiveDbgCollCount
GetSize( ) const;
virtual void Add(const AObject&);
virtual void Adopt(AObject*);
virtual AObject* Get(const AObject&) const;
virtual AObject* Remove(const AObject&);
virtual void Delete(const AObject&);
virtual void DeleteAll( );
virtual TPDSetIterator<AObject>*
CreateIterator( );
protected:
PrimitiveDbgCollCount
FindEmptySlot( ) const;
PrimitiveDbgCollCount
FindObject(const AObject&)
const;
private:
PrimitiveDbgCollCount
fCount;
PrimitiveDbgCollCount
fCollectionSize;
AObject** fObjectList;
};
______________________________________
This simple implementation is constructed with no arguments. A set can be copied in whole using deep-copy semantics using the operator= member function. The operator>>= streaming function allows the object to stream itself out into the provided stream parameter. Likewise, the operator<<= streaming function allows the object to stream itself in from the provided stream parameter. The contained objects (of the parameterized type AObject) are streamed out monomorphically. To determine the number of objects stored in the set, a program would call the GetCount member function. To determine the effective storage size of the set, i.e. the allocated space used by the collection, the GetSize function would be called. When an object is to be stored in the collection, the Add function is used. It will make a copy of the object which is passed in as a parameter and store the copy in the collection. Similarly, the Adopt function will take the object itself and store it directly into the collection, but without first making a copy. Retrieval from the collection has a uniform, polymorphic interface. This is achieved by calling the Get function which takes an object of the parameterized type. An object can be removed by specifying an identification object and passing it to the function Remove. When the objects held by the collection class need to be deleted from memory, the DeleteAll member function is called. This deallocates and destructs each AObject object stored by the collection class and any pointer memory or storage that refers to the objects are also marked as empty. Finally, the CreateIterator function polymorphically creates a TPDSetIterator (below). The actual implementation of the set varies. The underlying implementation may use an array or a hash table. TPDSetIterator
______________________________________
template<class AObject>
class TPDSetIterator : public TPDCollectionIterator<AObject> {
public:
TPDSetIterator(TPDSet<AObject>*);
virtual .about.TPDSetIterator( );
virtual AObject* Next( );
virtual AObject* First( );
private:
void Reset( );
private:
PrimitiveDbgCollCount
fIndex;
};
______________________________________
The TPDSetIterator class derives from the TPDCollectionIterator class and uses an index into a hash table or array, stored in the f Index data member. The pointer to the TPDSet over which iteration is to occur is specified when the iterator is constructed. After construction, the owner of the iterator may call the First function, which returns a pointer to the first object in the list. It is not required that the iterator return objects in any particular order. Whenever it is required that the iteration is to start over again, the First function is called again. After receiving a pointer to the first iterated object, the client may call the Next function which will return the next object after the last one returned. Subsequent calls will return objects to subsequent objects in the collection. The Reset function is incidental to this particular implementation of the TPDsetIterator class and is called to reset the internal index before the First member function is called. TScalarKeyValuePair Although collections only store individual objects within them, the objects can be composed of multiple objects themselves. One such class that describes this is the TScalarKeyValuePair, which contains a scalar identifier, or key, and a pointer to a value object. In FIG. 5, storage is allocated in this templatized class object 51 for the AScalarKey identifier 511, and the pointer to AValue, item 512. Elsewhere in memory is the actual object 52 which 512 refers to. The C++ class interface for TScalarKeyValuePair is defined as:
______________________________________
template<class AScalarKey, class AValue>
class TScalarKeyValuePair : public MDebuggerCollectible {
public:
TScalarKeyValuePair( );
TScalarKeyValuePair(AScalarKey,
AValue*);
TScalarKeyValuePair(const
TScalarKeyValuePair<
AScalarKey, AValue>&);
virtual .about.TScalarKeyValuePair( );
TScalarKeyValuePair<AScalarKey, AValue>& operator=(
const TScalarKeyValuePair<
AScalarKey,
AValue>&);
virtual TDebuggerStream&
operator>>=(TDebuggerStream&)
const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&);
virtual long Hash( ) const;
virtual bool IsEqual(const
MDebuggerCollectible*)
const;
virtual bool IsSame(const
MDebuggerCollectible*)
const;
AScalarKey GetKey( ) const;
AValue* GetValue( ) const;
protected:
AScalarKey fKey;
AValue* fValue;
};
______________________________________
The constructor provides default construction, in which no key or value is specified. It also provides a constructor which takes both these values. And finally, it takes a copy constructor which deep-copies another TScalarKeyValuePair object. The standard operator=, Hash, IsEqual functions are defined as specified by the base class protocol. The Hash function returns a value based on the fKey. The IsEqual function only tests the fKey for equality; the fValue portion is not tested as described above because only the identifier portion of the object is tested. This class can be used when objects are not streamable. Because this class deviates from the base protocol, the operator>>= and operator<<= functions do not actually stream any objects. Another function, IsSame, is called for testing identity, meaning that if the object specified as the parameter is the same as the one whose IsSame function is being called, the result will be true. Otherwise, the result is false. To access the data within the object, the GetKey function returns the scalar value. To return a pointer to the value, the GetValue function is returned. TStreamableScalarKeyvaluePair The TStreamableScalarKeyValuePair class is identical to the TScalarKeyValuePair class except that the objects stored within are streamable.
______________________________________
template <class AScalarKey, class AValue>
class TStreamableScalarKeyValuePair {
public TScalarKeyValuePair<AScalarKey, AValue> {
public:
TStreamableScalarKeyValuePair( );
TStreamableScalarKeyValuePair(
AScalarKey, AValue*);
TStreamableScalarKeyValuePair(const
TStreamableScalarKeyValuePair<
AScalarKey, AValue>&);
virtual
.about.TStreamableScalarKeyValuePair( );
TDebuggerStream& operator>>=(TDebuggerStream&)
const;
TDebuggerStream& operator<<=(TDebuggerStream&);
};
______________________________________
TPDIDCollection The server ensures uniqueness of the identifiers of objects stored in some of its data structures. To guarantee this uniqueness, the TPDIDCollection class specifies storage for an identifier and a function for generating new, unique identifiers.
______________________________________
template <class AObject, class AScalar>
class TPDIDCollection : public TPDSet<AObject> {
public:
TPDIDCollection( );
TPDIDCollection(const
TPDIDCollection<
AObject, AScalar>&);
virtual .about.TPDIDCollection( );
TPDIDCollection<AObject, AScalar>& operator=(const
TPDIDCollection<
AObject, AScalar>&);
virtual AScalar MintID( );
private:
AScalar fNextID;
};
______________________________________
In all respects, the TPDIDCollection is identical to the TPDSet class. An additional data member, fNextID, of templatized type AScalar, contains a scalar value which is initialized in the collections construction. When the MintID function is called, an new identifier is generated and returned to the caller. TPDArray A simple collection for storing objects is the TPDArray class. It is used to easily access individual objects in the array. The array can grow as necessary, as more objects are added to the collection. An initial size is specified by default:
______________________________________
const PrimitiveDbgCollCount kInitialPrimitiveArraySize = 20;
______________________________________
The C++ class interface for the TPDArray class is defined as follows:
______________________________________
template<class AObject>
class TPDArray : public TPDCollection<AObject> {
public:
friend class TPDArrayIterator<AObject>;
TPDArray (PrimitiveDbgCollCount
initialSize =
kInitialPrimitiveArraySize);
TPDArray(const
TPDArray<AObject>&);
virtual .about.TPDArray( );
TPDArray<Object>&
operator=(const
TPDArray<AObject>&);
virtual TDebuggerStream&
operator>>=(TDebuggerStream&)
const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&);
PrimitiveDbgCollCount
GetCount( ) const;
PrimitiveDbgCollCount
GetSize( ) const;
virtual AObject* Get(const AObject&) const;
virtual AObject* Remove(const AObject&);
virtual void DeleteAll( );
virtual AObject* At(PrimitiveDbgCollCount index)
const;
virtual void AtPut(PrimitiveDbgCollCount
index,
AObject&);
virtual void AtAdopt(PrimitiveDbgCollCount
index,
AObject*);
virtual TPDArrayIterator<AObject>*
CreateIterator( );
protected:
PrimitiveDbgCollCount
FindObject(const AObject&)
const;
AObject** Reallocate(
PrimitiveDbgCollCount
newSize,
AObject* currentList› !,
PrimitiveDbgCollCount
oldSize);
private:
PrimitiveDbgCollCount
fSize;
AObject** fObjectList;
};
______________________________________
All functions are implemented according to the base class protocol. Access to the objects within the TPDArray class can be achieved using the Get function, or the At function which specifies an index into the array. To store an object of type AObject, the pointer to the object is passed into the AtAdopt function, storing the pointer at the index specified. Similarly, to copy an object first before storing, the AtPut function is called with an index and a pointer to the object. Protected member function FindObject simply searches through the array of pointers to objects. The Reallocate function will reallocate a pointer to the memory block containing the pointers to objects that are stored in fObjectList. TPDArrayIterator The TPDArrayIterator class derives from the TPDCollectionIterator class and uses an index into a hash table or array, stored in the fIndex data member. The pointer to the TPDArray over which iteration is to occur is specified when the iterator is constructed. After construction, the owner of the iterator may call the First function, which returns a pointer to the first object in the list. It is required that the iterator return objects in order of the array. Whenever it is required that the iteration is to start over again, the First function is called again. After receiving a pointer to the first iterated object, the client may call the Next function which will return the next object after the last one returned. Subsequent calls will return objects to subsequent objects in the collection. The Reset function is incidental to this particular implementation of the TPDArrayIterator class and is called to reset the internal index before the Firs t member function is called.
______________________________________
template<class AObject>
class TPDArrayIterator : public TPDCollectionIterator<AObject> {
public:
TPDArrayIterator(TPDArray<AObject>*);
virtual .about.TPDArrayIterator( );
virtual AObject* Next( );
virtual AObject* First( );
private:
TPDArrayIterator( );
void Reset( );
private:
PrimitiveDbgCollCount
fIndex;
};
______________________________________
Linked Lists A primitive linked list class is used by the debugger framework. The MDebuggerLinkable base class provides functionality which is similar to the MLinkable class found in the CommonPoint class frameworks. Objects that are required to be stored in linked list data structures derive from the MDebuggerLinkable class. A linked list class, TPDLinkedList contains the head of the linked list object that derives from MDebuggerLinkable. To iterate over all of the objects in a linked list, the TPDLinkedListIterator is used. MDebuggerLinkable Any object whose use requires storage within a linked list must derive from MDebuggerLinkable. This class provides, as data members fNext and fPrevious, pointers to the next and previous elements in the linked list. The C++ class interface for the MDebuggerLinkable class follows:
______________________________________
class MDebuggerLinkable : public MDebuggerCollectible {
public:
MDebuggerLinkable( );
MDebuggerLinkable*
Next( ) const;
void SetNext(MDebuggerLinkable*
next);
MDebuggerLinkable*
Previous( ) const;
void SetPrevious(MDebuggerLinkable*
previous);
private:
MDebuggerLinkable*
fNext;
MDebuggerLinkable*
fPrevious;
};
______________________________________
In FIG. 6 the data structures for an example linked list are shown. Linkable objects 611, 62, 63, and 64 contain fPrevious data members 612, 621, 631, and 641, respectively. They also contain fNext data members 613, 622, 632, and 642, respectively. Each pointer for fPrevious points to the previous linked object in the list, and vice versa for fNext. To traverse from one MDebuggerLinkable object to the next, the Next function is called, returning a pointer to the next object. Conversely, to get to the previous object, the Previous function is called. When an MDebuggerLinkable object is being added within a linked list, the SetNext function is called to add a new object after the object whose function is being called. And of course, in reverse, the SetPrevious function is called when an object is inserted in the list before the current object whose function is being called. TPDLinkedList The templatized TPDLinkedList class is itself implemented as a single MDebuggerLinkable data member as well as a comparison function from which to use for comparisons. Its C++ class interface is defined as follows:
______________________________________
template<class AItem>
class TPDLinkedList {
public:
TPDLinkedList( );
TPDLinkedList(const
TPDLinkedList<AItem>&);
TPDLinkedList(
MDebuggerCollectibleCompareFn);
virtual .about.TPDLinkedList( );
TPDLinkedList<AItem>&
operator=(const
TPDLinkedList<AItem>&);
bool operator==(const
TPDLinkedList<AItem>&);
virtual void DeleteAll( );
virtual void RemoveAll( );
virtual void AddLast(AItem*);
AItem* First( ) const;
AItem* Last( ) const;
virtual AItem* Find(const AItem&) const;
virtual AItem* Remove(const AItem& key);
virtual AItem* Remove(AItem*);
TPDLinkedListIterator<class AItem>* CreateIterator( );
private:
MDebuggerCollectibleCompareFn
fTestFn;
MDebuggerLinkable
fSentinel;
friend class TPDLinkedListIterator<AItem>;
};
______________________________________
The linked list provides functions for removing all elements out of the list, e.g. RemoveAll, as well as for deleting all elements out of the list, e.g. DeleteAll. To add a new linkable item to the end of the list, the AddLast function is called with the object to be added. The first item in the list is returned by the First function; the last item by the Last function. To search for a specific object in the linked list, using an identifier object, the Find function is called. Items whose identifiers are specified by the key passed into the Remove function can be removed from the list; if the object is found in the list, it is returned otherwise a NIL pointer is returned. The polymorphic CreateIterator creates an TPDLinkedListIterator<class AItem>object which can be used by the caller to iterate over all items in the linked list. TPDLinkedListIterator As with other iterator classes as defined above, the TPDLinkedListIterator templatized class also follows the same semantics of the TPDCollectionIterator class.
______________________________________
template<class AItem>
class TPDLinkedListIterator {
public:
TPDLinkedListIterator(
TPDLinkedList<AItem>*);
virtual .about.TPDLinkedListIterator( );
AItem* First( );
AItem* Next( );
private:
TPDLinkedListIterator( );
TPDLinkedList<AItem>*
fLinkedList;
AItem* fCurrent;
MDebuggerLinkable*
fSentinel;
};
______________________________________
A pointer to a linked list is used to construct an iterator object; it is stored in the fLinkedList data member. When the First function is called, the first item in the linked list is returned. A pointer to the object being returned is stored in the data member fCurrent. To maintain currency checks, the fSentinel points to the same internal MDebuggerLinkable object stored in the TPDLinkedList object being iterated over. When the pointer to the fCurrent matches pointer in the fSentinel object, the Next function returns NIL. Addressing An addressing abstraction is presented which allows the use of target memory addresses in a portable fashion. The use of static compiler types such as void* in the C and C++ languages do not properly express this as a portable abstraction since they are limited to the static size as implemented by the compiler used to compile the debugger code. Two major classes are used to describe the abstraction itself; other subclasses are created to describe processor-specific addressing data. The first class, a monomorphic class called TDebuggerAddress, represents a single target address. It does not restrict the size of addressing to an arbitrary size such as 32 bits, nor does it restrict the mode of addressing to a particular memory model such as a flat 32-bit address space. It is defined to be a statically fixed-sized object, encapsulating a polymorphic implementation whose size varies. This allows static allocation of the TDebuggerAddress object in fixed-size data structures or stack-based allocation. Bits16 and Bits32 Two types are defined to provide an exact size of 16 and 32 bits. These data types depend on the compiler being used since languages such as C and C++ do not specify exact sizes of scalar types. By defining these types, the following address class are ensured of a guarantee that the data is ordered correctly. TDebuggerAddress Following is the C++ class interface to an embodiment of the TDebuggerAddress abstraction.
______________________________________
class TDebuggerAddress : public MDebuggerCollectible {
public:
TDebuggerAddress( );
TDebuggerAddress(const
TDebuggerAddress&);
TDebuggerAddress(const
TUniversalAddress& address);
// not adopted
virtual .about.TDebuggerAddress( );
virtual TDebuggerStream& operator>>=(TDebuggerStream&) const;
virtual TDebuggerStream& operator<<=(TDebuggerStream&);
virtual long Hash( ) const;
virtual bool IsEqual(const MDebuggerCollectible*)
const;
virtual int GetWidthInBytes( ) const;
inline int GetWidthInBits( ) const;
virtual int GetDataWidthInBytes( ) const;
inline int GetDataWidthInBits( ) const;
virtual ByteOrder
GetByteOrder( ) const;
virtual Segmentation
GetSegmentation( ) const;
virtual void* GetStorage( ) const;
inline bool IsntNil( ) const;
virtual bool IsNil( ) const;
virtual operator Bits16( ) const;
virtual operator Bits32( ) const;
TDebuggerAddress&
operator=(const TDebuggerAddress&
source);
virtual TDebuggerAddress&
operator+=(const TDebuggerAddress&
operand);
virtual TDebuggerAddress&
operator-=(const TDebuggerAddress&
operand);
virtual TDebuggerAddress operator+(const TDebuggerAddress&
source);
virtual TDebuggerAddress operator-(const TDebuggerAddress&
source);
TUniversalAddress*
GetUniversalAddress( ) const;
protected:
virtual TUniversalAddress*
InstantiateByType(long
universalAddressType);
public:
static const TDebuggerAddress&
fgInvalidAddress,
protected:
TUniversalAddress*
fAddress;
};
______________________________________
FIG. 18 shows the object relationship between the TDebuggerAddress object and the TUniversalAddress it contains. The monomorphic TDebuggerAddress contains a pointer 1811 to a polymorphic TUniversalAddress 1802 which can define storage 1821 of any size. This affords unlimited flexibility in the ability to represent any address. The object can be constructed with no parameters (default constructor), with a copy constructor, in which case it completely deep-copies the copy constructor parameter, or using a TUniversalAddress to create the object from a platform-specific debugger class. The operator>>= streaming function allows the object to stream itself out into the provided stream parameter. Likewise, the operator<<= streaming function allows the object to stream itself in from the provided stream parameter. These operations provide polymorphic streaming such that in the operator>>= function the polymorphic TUniversalAddress object will write its own type out into the stream using, allowing the corresponding object read in during the operator<<= function to recreate the same type. The mechanism to do this can be implemented in two ways. FIG. 7 describes the control flow for the two different cases of streaming. Streaming starts with a function call to the operator>>= 71. When using the Taligent CommonPoint system, the operator>>= function will call Flatten 73, which will write an encoded class name and library name into the stream. This case is shown in FIG. 7, number 72 "compiled/linked with CommonPoint is true." On the reading (operator<<=) side, the encoded class name and library name will be read out before the TUniversalAddress. An object is dynamically constructed using the library name and class name using the default constructor and the data is filled in. The second method to achieve this in a manner that does not use the Taligent CommonPoint system is to encode a scalar value that corresponds to the type that is assigned to the particular TUniversalAddress class. This case is shown in FIG. 7, number 72 "compiled/linked with CommonPoint is false." Each TuniversalAddress subclass will have its GetType function which is called in 74 return a different scalar value based on its type. Each value is unique. On the receiver side of the connection, the object is read out of the stream in FIG. 8. The two different cases, compiling with the CommonPoint system 83 which causes objects to be resurrected with their dynamic type; and without the CommonPoint system 84, 85, and 86 which show how to simulate this feature without using the Resurrect function. The streaming class, TDebuggerStream, ensures scalar values are maintained correctly on different platforms. Even though a pointer class on one platform may exist in big-endian ordering, it will be correctly interpreted as a portable scalar value on all platforms. The Hash member function allows the object to describe itself in terms of a scalar hash value. This is used when storing the addresses in collection classes which use the hashing function. The hash function uses a 32-bit scalar value which is returned by the fDebuggerAddress data member. The IsEqual function allows comparison of objects. The test will return true if equal and false if not. To determine the actual size of the underlying addressing width, the GetWidthInBytes returns a scalar value equivalent to the number of bytes (an 8-bit datum) used to form the address. Likewise, the number of bits are also returned by the GetwidthInBits function. Addresses point to blocks of data. Each block of data, or the addressable unit, is determined by a processor architecture. The GetDataWidthInBytes function will return the total number bytes that are addressed by a single address. For instance, an address space that uses 32-bit addresses where each address can indicate a single byte in that address space will have a data width of one byte. Although a processor may be limited to reading and writing on boundaries on which the addressing may occur, e.g. "four-byte boundaries," the data width will still be one byte even though there is a processor limitation. However, if the individual size of a datum referred to by an address is a larger value, such as 32-bit words, the data width would be 32-bits; in this case pointers refer to 32-bits words, and not individual bytes. The ordering of addresses vary across processors. Some processors place their addresses in "big endian" order, such as the Motorola M68000 family of microprocessors which specifies that a target address found in a target address space would be in the order most-significant bit through least-significant bit. In "little endian" order, as found in the Intel x86 family of microprocessors, the least significant byte is followed by the next-most significant byte, etc., followed by the most significant byte. Some processors support a mixed mode, such as the Motorola/IBM PowerPC MPC601 processor, which allows mixed addressing depending on a processor register. In this case, the debugger address class can support mixed modes of addressing dynamically, using a single debugger for both purposes. The ByteOrder enumeration defines two different ordering, kBigEndian, and kLittleEndian. The GetSegmentation function returns the type of segmentation being used in the target address space; either the memory is kLinear (flat, uniform addressing), or kSegmented. Other enumerations can be added. To test the pointer to determine it is NIL (address refers to a well-known NIL pointer), the IsNil and IsntNil functions can be called; boolean values of true or false are returned. Conversion operators are provided for convenience. In many cases it is necessary to convert an abstract class into simple scalar values. For instance, the value for the length of functions may be defined by a compiler and runtime system to be limited to 32 bits. In this case, a subclass may use this additional information to turn the length of a function into a value that is a primitive scalar type. The conversion operators operator Bits16 and operator Bits32 can be called, and the result returned are values of the according 16- and 32-bit data types. Assignment results in the return value when using the operator= function. Because the class is designed to be used as a monomorphic class, direct assignment from object to object affords a direct copy using a fixed object size. The underlying implementation is a polymorphic class, which will copied polymorphically. Addition and subtraction of addresses are frequent operations in a debugger. The operator+= and operator-= functions allow lvalue addition and subtraction, respectively. The parameter is added to the object whose member function is called. Operations can only occur in a manner whereby the underlying TuniversalAddress object can take an addition to its value without changing the original semantics. By checking using types, the operand as specified by the parameters to operator+= and operator-= are tested so that they will not cause overflow. For instance, if a TDebuggerAddress containing a 64-bit address is passed as an argument to TDebuggerAddress:: operator+= on an object which contains a 32-bit address, the operation will be invalid because of the potential for overflow. An example of this is found in the implementation for T32BitAddress::operator+=:
______________________________________
TUniversalAddress&
T32BitAddress::operator+=(const TUniversalAddress& operand)
if (operand.IsA(this)) {
const T32BitAddress& operand32 = (const T32BitAddress&)
operand;
fStorage += operand32.fStorage;
} else {
fail;
}
}
______________________________________
Creating expressions composed of TDebuggerAddress objects and addition or subtraction operations is possible by calling the operator+ and operator- functions. An 40 example of C++ code which might be used to determine the address of an instruction within which is calculated by adding an address and an offset is:
______________________________________
TDebuggerAddress functionStart = GetFunctionStart( );
TDebuggerAddress functionOffset = GetFunctionOffset( );
TDebuggerAddress instructionAddress = functionStart +
functionOffset;
______________________________________
The code to perform the above addition of the address and offset is abstract, expressive, and portable. It is abstract in that the TDebuggerAddress objects express the individual data elements being used; they represent individual addresses and also define a protocol for addition; the syntax allows a programmer to form an expression in the language which is identical to the syntax used with expressions of primitive types. It is expressive because of ability to concisely combine expressions formed by the addition of two operands and a single assignment within a program statement. Finally, it is portable in that the single line of code is not only compilable on different target platforms to execute with different processor address classes, but it can be compiled on platforms other than the target host platform for cross-development. In addition, because of the polymorphic nature of the underlying TUniversalAddress object, the single line of code is used for not just a single target execution environment, but for all target execution environments. This allows a single instance of a debugger program, i.e. a single process with a single set of code, to target multiple environments simultaneously. The last public function, GetUniversalAddress is provided to return a pointer to the underlying TUniversalAddress object for future extension. When developing without the CommonPoint system, the InstantiateByType function allows a standalone debugger to dynamically instantiate objects based on a streamed scalar value. TUniversalAddress Following is the C++ class interface to an embodiment of the TUniversalAddress abstract base class. The concept of a universal address is that it can represent any possible processor address. The address itself can be unbounded in size, and of any byte ordering, and can refer to data of arbitrary size. Although there are no size restrictions, the compiler used on all platforms must at least specify a type that is capable of storing 32 bits in a scalar datum. If this is not the case, operations which require the Bits32 address type will be truncated to the largest type. The TUniversalAddress class exhibits polymorphism. As one well-versed in the art understands, a function call is made and then bound at runtime such that the called member function is determined by rules of the language such that the most derived definition of the member function in a class hierarchy will be called. The dynamic nature of the call allows a base framework to utilize code that was created by a different source than the original framework designer. It is in this capacity that allows the debugger framework--in this case, the debugger address abstraction--to be extended to function on target processor architectures beyond what was originally defined when the framework was first created.
__________________________________________________________________________
class TUniversalAddress : public MDebuggerCollectible {
public:
virtual int GetWidthInBytes( ) const = 0;
inline int GetWidthInBits( ) const;
virtual int GetDataWidthInBytes( ) const = 0;
inline int GetDataWidthInBits( ) const;
virtual ByteOrder
GetByteOrder( ) const = 0;
virtual Segmentation
GetSegmentation( ) const = 0;
virtual void* GetStorage( ) const = 0;
inline bool InstNil( ) const;
virtual bool IsNil( ) const = 0;
virtual long Hash( ) const = 0;
virtual bool IsEqual(const MDebuggerCollectible*)
const = 0;
virtual TDebuggerStream&
operator>>=(TDebuggerStream&) const =
0;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&) = 0;
virtual operator Bits16( ) const = 0;
virtual operator Bits32( ) const = 0;
virtual TUniversalAddress&
operator+=(const TUniversalAddress&
operand) = 0;
virtual TUniversalAddress&
operator-=(const TUniversalAddress&
operand) = 0;
virtual bool IsA(const TUniversalAddress*) const =
0;
virtual int GetType( ) const = 0;
};
__________________________________________________________________________
The abstract base class TUniversalAddress defines the abstraction of an address. It does not contain any data, nor does it define implementation for many of the function. However, the interface defines a protocol in which a framework or client code can use the functions with polymorphic behavior; when a polymorphic function call (as labeled with the C++ keyword, virtual, before the type and member function name in the class declaration) is made, the runtime system will correctly bind the caller with the called function in an implementation subclass. The class hierarchy, FIG. 9 shows the inheritance graph for sample classes. As defined earlier in TDebuggerAddress, the interfaces defines the member function protocol for determining the size of address pointers in bytes (GetWidthInBytes), the size of address pointers in bits (GetwidthInBits), the size of data addressed by the pointers in bytes (GetDataWidthInBytes), and the same in bits (GetDataWidthInBits). Further, the protocol is also identical for the hashing function (Hash), the equality testing function (IsEqual), streaming operators (operator>>= and operator<<=), lvalue addition and subtraction (operator+= and operator-=), and 16- and 32-bit conversion operators (operator Bits16 and operator Bits32). The GetType function allows individual classes to return a value for its type, which is a scalar value. Each class is assigned a constant value for this. Two objects' types can be compared by comparing the results returned from the objects' GetType function. Although the Taligent CommonPoint type system provides this functionality, the explicit interface in this class allows the debugger to execute independent of the CommonPoint execution environment. A function which provides a way of determining an objects' relative location in TUniversalAddress class hierarchy, is the IsA function. Each class defines its own IsA implementation which determines if the object is the same as another object, or a direct descendent of the object. For example, the following lines of code show to determine whether one object is in a class that derives from another class, a T 32BitAddress 92 is a base class for 32-bit address values. A subclass of this, TM68000Address 95, implements the functions which describe addresses on the Motorola M68000 family of microprocessors. The following lines show this example in the implementation of the TM68000Address: IsA function.
______________________________________
bool
TM68000Address::IsA(const TUniversalAddress* addr) const
return (addr->GetType( ) == this->GetType( )) .parallel.
T32BitAddress::IsA(addr);
}
______________________________________
TInvalidUniversalAddress While TUniversalAddress subclasses generally represent real addresses on real microprocessors, they can represent values which don't exist at all. The TInvalidUniversalAddress 93 is one such class. This class allows a TDebuggerAddress to be defined as "invalid" meaning that it doesn't point to any memory. This is different from pointers using primitive types such as the C++ type, void*, which can only represent the address space as defined by the type; therefore it is not possible to represent all values in the data type and an invalid value as well. With TInvaliduniversalAddress, this class will not compare to any other address types. T32BitAddress A base class for 32-bit addresses 92 is provided. It contains the base functionality defining 32-bit address types. It defines a 32-bit address range with one-byte data sizes. By default, it defines a big-endian addressing mode (which is overridden in various subclasses).
______________________________________
class T32BitAddress : public TUniversalAddress {
public:
T32BitAddress( );
T32BitAddress(const T32BitAddress&
src);
T32BitAddress(Bits32 addr);
const T32BitAddress&
operator=(Bits32);
TUniversalAddress&
operator=(const TUniversalAddress&
source);
virtual TUniversalAddress&
operator+=(const TUniversalAddress&
operand);
virtual TUniversalAddress&
operator-=(const TUniversalAddress&
operand);
virtual TDebuggerStream&
operator>>=(TDebuggerStream&) const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&);
virtual long Hash( ) const;
virtual bool IsEqual(const MDebuggerCollectible*)
const;
virtual bool IsA(const TUniversalAddress*) const;
virtual int GetType( ) const;
virtual int GetWidthInBytes( ) const;
virtual int GetDataWidthInBytes( ) const;
virtual ByteOrder
GetByteOrder( ) const = 0;
virtual Segmentation
GetSegmentation( ) const = 0;
virtual void* GetStorage( ) const;
virtual bool IsNil( ) const;
virtual operator Bits16( ) const;
virtual operator Bits32( ) const;
public:
static const int&
kType;
private:
static const int
gType;
private:
Bits32 fStorage;
};
______________________________________
Because this class requires actual storage for a 32-bit value, it contains the fStorage data member which is defined as a 32-bit data type (Bits32). TM68000Address
______________________________________
class TM68000Address : public T32BitAddress {
public:
TM68000Address( );
TM68000Address(const T32BitAddress& src);
TM68000Address(Bits32 addr);
virtual ByteOrder
GetByteOrder( ) const;
virtual Segmentation
GetSegmentation( ) const;
virtual bool IsA(const TUniversalAddress*) const;
virtual int GetType( ) const;
public:
static const int&
kType;
private:
static const int
gType;
};
______________________________________
The TM68000Address 95 defines a subclass of T32BitAddress. It overrides the GetType function to return a unique scalar type, the GetByteOrder function to ensure that the kBigEndian ordering is returned, the GetSegmentation function to return kLinearSegmentation, and the IsA function to test the parameter's type for inheritance. The static data members, kType, is used to define a reference to the constant value. An additional static data member contains the actual value for the datum. This indirect mechanism allows compilation of the class into a shared library and is implementation-specific. TiAPX86Address Another example of a 32-bit address is the TiAPX86Address 96 which is used to represent 32-bit addresses on the Intel 80.times.86 family of microprocessors. TPowerPCAddress Another example of a 32-bit address is the TPowerPCAddress 97 which is used to represent 32-bit addresses on the PowerPC family of microprocessors. T64BitAddress The address classes are not limited to 32-bit address sizes. The following class interfaces define a 64-bit class which can be used to represent addresses on 64-bit processors. The first class, T64Bits, is just a storage type. Because some compilers may not be able to properly express a 32-bit scalar as a primitive type, this class defines storage and protocol for handling 64-bit values in a portable manner.
______________________________________
class T64Bits {
public:
T64Bits( );
T64Bits(Bits32 ms, Bits32 ls);
const T64Bits&
operator=(const T64Bits&);
TDebuggerStream&
operator>>=(TDebuggerStream&) const;
TDebuggerStream&
operator<<=(TDebuggerStream&);
inline Bits32
GetMostSignificant( ) const;
inline void SetMostSignificant(Bits32 msw);
inline Bits32
GetLeastSignificant( ) const;
inline void SetLeastSignificant(Bits32 lsw);
public:
Bits32 fStorageWords›2!;
};
______________________________________
The two constructors can initialize an instance of the T64Bits class either by default or with two 32-bit values. The operator= function allows assignment from one T64Bits object into another. FIG. 10 shows the structure for a 64-bit address object. To access the 64-bit object as 32-bit low- and high-order parts, (also called least significant 1011 and most significant 1012), the members functions GetMostSignificant and GetLeastSignificant will return the respective portions of the 64-bit scalar. Likewise, the 64-bit value can be stored as two 32-bit halves using the SetMostsignificant and SetLeastSignificant member functions. The T64BitAddress 94 subclass of TUniversalAddress provides an abstract base class for 64-bit addresses. Its C++ class declaration follows:
______________________________________
class T64BitAddress : public TUniversalAddress {
public:
T64BitAddress( );
T64BitAddress(const T64BitAddress&
src);
T64BitAddress(const T64Bits& addr);
TUniversalAddress&
operator=(const TUniversalAddress&
source);
virtual TUniversalAddress&
operator+=(const TUniversalAddress&
operand);
virtual TUniversalAddress&
operator-=(const TUniversalAddress&
operand);
virtual TDebuggerStream&
operator>>=(TDebuggerStream&) const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&);
virtual long Hash( ) const;
virtual bool IsEqual(const MDebuggerCollectible*)
const;
virtual bool IsA(const TUniversalAddress*) const;
virtual int GetType( ) const;
virtual int GetWidthInBytes( ) const;
virtual int GetDataWidthInBytes( ) const;
virtual ByteOrder
GetByteOrder( ) const = 0;
virtual Segmentation
GetSegmentation( ) const = 0;
virtual void* GetStorage( ) const;
virtual bool IsNil( ) const;
virtual operator Bits16( ) const;
virtual operator Bits32( ) const;
public:
static const int&
kType;
private:
static const int
gType;
private:
T64Bits fStorage;
};
______________________________________
Note that the storage data member, fStorage, contains the datum representing the address bits. TMIX64Address The class TMIX64Address 98 shows an example implementation using the T64BitAddress class. Lengths and Pointer Differences Sizes of objects and differences between pointers are represented as TDebuggerAddress objects as well. Since the address object represents a scalar value, it can be used for these two purposes as well. To explicitly declare that the scalar is used for length, the following type is defined:
______________________________________
typedef TDebuggerAddress TDebuggerLength;
______________________________________
Names and Strings Text strings are used in PDS to represent names of functions, libraries, processes, threads, and exceptions. The TPrimitiveString class provides functionality independent of other classes. It is able to encode a 16-bit Unicode strings of arbitrary length. The string can be converted to and from primitive 8-bit character strings. The type called PSLength defines a length type:
______________________________________
typedef unsigned long PSLength;
______________________________________
and a 16-bit Unicode character type:
______________________________________
typedef unsigned short UniChar;
______________________________________
TPrimitiveString The C++ class declaration follows:
______________________________________
class TPrimitiveString : MDebuggerCollectible {
public:
TPrimitiveString( );
TPrimitiveString(const char* text);
TPrimitiveString(const UniChar* text,
PSLength length);
TPrimitiveString(const
TPrimitiveString&);
virtual .about.TPrimitiveString( );
virtual TPrimitiveString&
operator=(const TPrimitiveString&);
virtual TDebuggerStream&
operator>>=(TDebuggerStream&
dstStream)
const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&
srcStream);
PSLength Length( ) const;
virtual PSLength
Extract(PSLength start, PSLength
length,
char result› !) const;
virtual PSLength
Extract(char result› !) const;
protected:
virtual UniChar*
CreateUniCharBuffer(PSLength
resultLength) const;
virtual UniChar*
CreateUniCharString(const char* text,
PSLength& resultLength)
const;
virtual UniChar*
CreateUniCharString(const UniChar*
text,
const PSLength length,
PSLength& resultLength)
const;
virtual void DeleteUniCharString(UniChar*) const;
private:
UniChar* fString;
PSLength fLength;
};
______________________________________
The TPrimitiveString class derives from MDebuggerCollectible. The default constructor creates an empty object. The constructor taking a const char* parameter will convert the argument, a zero-terminated C string, into the 16-bit storage. Each individual 8-bit character is extended into a 16-bit character. The third constructor will take a pointer to a UniChar* string array and a length; these will copied into the object's own storage. Finally, the fourth/last constructor will copy the contents of the argument, which is another TPrimitiveString. The operator= function follows the convention of the C++ language. It will deep-copy the contents of the data stored within the object argument. To provide monomorphic streaming, the length and 16-bit UniChar data are streamed out in the operator>>= function. The stream data are written into the TDebuggerStream argument provided. An example of this code is shown below.
______________________________________
TDebuggerStream&
TPrimitiveString::operator>>=(TDebuggerStream& dstStream) const
fLength >>= dstStream;
dstStream.Write((void*) fString, (size.sub.-- t) fLength * 2);
return dstStream;
}
______________________________________
In the operator<<= function, the length is read, and the rest of the data is streamed into a buffer pointed to by the data member fString. An example of this is shown below.
______________________________________
TDebuggerStream&
TPrimitiveString::operator<<=(TDebuggerStream& srcStream)
fLength <<= srcStream;
DeletUniCharString(fString);
fString = CreateUniCharBuffer(fLength);
srcStream.Read((void*) fString, (size.sub.-- t) fLength * 2);
return srcStream;
}
______________________________________
The TPrimitiveString class provides functions to determine length (Length), and extract the contents of the string as primitive 8-bit char* types, or as 16-bit Unichar* types. Internal allocation of data occurs using the CreateUniCharBuffer functions. Internal deallocation of data is provided via the DeleteunicharString function. These two functions encapsulate the class' data storage needs. Type definitions are provided for library names, function names, process names, and thread names. These are named TLibraryName, TFunctionName, TProcessName, and TThreadName, respectively:
______________________________________
typedef TPrimitiveString
TLibraryName;
typedef TPrimitiveString
TFunctionName;
typedef TPrimitiveString
TProcessName;
typedef TPrimitiveString
TThreadName;
______________________________________
Process identification varies on different operating systems. Generally they are identified as scalar types such as 16-bit values, but they may also be represented by memory locations or by objects. The token of communication for representing processes on all operating systems is the TargetProcess identifier type:
______________________________________
typedef unsigned long
TargetProcess; // ID type
______________________________________
This type will encode a process identifier that is unique to the debugger server that a client is registered with. The identifier is guaranteed uniqueness such that two processes on the same server will not use the same identifier. Use of a single TargetProcess type throughout the client-side debugger ensures portability and reuse of the PDS software. The defining type must meet the requirements of having sufficient T-stability for the defined needs of the debugger application. A compiler that defines a 32-bit value will ensure an extremely high T-stability if the C and C++ long type is used. Uniqueness is not guaranteed across multiple servers. For instance, a debugger server may start allocating TargetProcess values starting at zero, incrementing them as programs are attached or started. Another debugger server on a different host may use the same allocation strategy. The TargetProcess values cannot be compared directly, therefore, to detect equality of target processes. Instead, an additional name qualification of the TargetHost would need to be compared first. An example of the TargetHost class is:
______________________________________
typedef unsigned long TargetHost;
______________________________________
TTargetProcess The TargetProcess type only defines an identifier for processes. The closely related TTargetProcess class defines a protocol which can be used by both debugger client and debugger server to further describe additional information about a target process. The C++ class interface for TTargetProcess follows:
______________________________________
class TTargetProcess : public MDebuggerCollectible {
public:
TTargetProcess( );
TTargetProcess(TargetProcess
processID);
TTargetProcess(const
TTargetProcess&
targetProcess);
virtual .about.TTargetProcess( );
virtual
TDebuggerStream&
operator>>=(TDebuggerStream&
dstStream)const;
virtual
TDebuggerStream&
operator<<=(TDebuggerStream&
srcStream);
virtual long Hash( ) const;
virtual bool IsEqual(const
MDebuggerCollectible*)const;
//
// Process information
//
TargetProcess GetProcessID( ) const;
void SetProcessID(TargetProcess);
virtual void GetName(TProcessName&) const;
virtual void SetName(const TProcessName&);
virtual void GetThreadList(TTargetThreadList&
resultList);
virtual void GetAddressSpace(
TTargetAddressSpace&);
private:
TargetProcess fProcessID;
TProcessName fName;
};
______________________________________
The TTargetProcess class can be constructed using the default constructed, or from a TargetProcess. It can be copied using the copy constructor. The operator>>= streaming function allows the object to stream itself out into the provided stream parameter. Likewise, the operator<<= streaming function allows the object to stream itself in from the provided stream parameter. The Hash member function allows the object to describe itself in terms of a scalar hash value. This is used when storing the addresses in collection classes which use the hashing function. The hash function returns the TargetProcess scalar value. The IsEqual function allows comparison of objects. The test will return true if equal and false if not. To get or set the process identifier, the GetProcessID and SetProcessID functions are provided. The name of the process being executed may be stored and retrieved from within the TTargetProcess. This storage provides a simple mechanism by which to transfer the name between the server and client. Its use is explained in detail in the methods for accessing program name. The base class only provides a simple identification protocol, but it can be further extended within the debugger server through subclassing by using inheritance. TTargetProcessList and TTargetProcessListIterator Collections of process objects may be stored within a list. These lists are used in enumerating a list of processes. The primitive TPDSet class is parameterized with the TTargetProcess class; likewise, the TPDSetIterator class is-parameterized to return TTargetProcess objects when iterating through the list. Type definitions are declared for use of these classes:
______________________________________
typedef TPDSet<TTargetProcess> TTargetProcessList;
typedef TPDSetIterator<TTargetProcess> TTargetProcessListIterator;
______________________________________
TargetThread
______________________________________
typedef unsigned long
TargetThread;
// ID type
______________________________________
This type will encode a thread identifier that is unique to the debugger server that a client is registered with. The identifier is guaranteed uniqueness such that two threads on the same server will not use the same identifier. Use of a single TargetThread type throughout the client-side debugger ensures portability and reuse of the PDS software. The defining type must meet the requirements of having sufficient T-stability for the defined needs of the debugger application. A compiler that defines a 32-bit value will ensure an extremely high T-stability if the C and C++ long type is used. Uniqueness is not guaranteed across multiple servers. For instance, a debugger server may start allocating TargetThread values starting at zero, incrementing them as threads are attached, started, or discovered. Another debugger server on a different host may use the same allocation strategy. The TargetThread values cannot be compared directly, therefore, to detect equality of target threads. Instead, an additional qualification of the TargetHost and TargetProcess would need to be compared first. Two constants are defined to describe undefined threads and to name all threads within a process:
______________________________________
const TargetThread
kUndefinedThread = 0;
const TargetThread
kAllThreads = -2;
______________________________________
TTargetThread The TargetThread type only defines an identifier for thread. The closely related TTargetThread class defines a protocol which can be used by both debugger client and debugger server to further describe additional information about a target thread. The C++ class interface for TTargetThread follows:
______________________________________
class TTargetThread : public MDebuggerCollectible {
public:
TTargetThread( );
TTargetThread(const TTargetProcess&,
const TargetThread&
threadID);
TTargetThread(const TTargetProcess&,
const TargetThread&
threadID,
const TThreadName&
threadName);
TTargetThread(const TTargetThread&);
virtual .about.TTargetThread( );
virtual void PrintDebugInfo(bool verhose =
false) const;
virtual TDebuggerStream&
operator>>=(TDebuggerStream&
dstStream)
const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&
stream);
virtual long Hash( ) const;
virtual bool IsEqual(const MDebuggerCollectible*)
const;
//
// Thread information
//
TargetProcess GetProcessID( ) const;
void SetProcessID(TargetProcess);
TargetThread GetThreadID( ) const;
void SetThreadID(TargetThread);
virtual void GetName(TThreadName& threadName);
private:
TargetProcess fProcessID;
TargetThread fThreadID;
TThreadName fName;
};
______________________________________
The TTargetThread class can be constructed using the default constructed; from a TargetProcess process identifier and TargetThread thread identifier; and also from a TargetProcess process identifier, TargetThread thread identifier and a name. It can be copied using the copy constructor. The operator>>= streaming function allows the object to stream itself out into the provided stream parameter. Likewise, the operator<<= streaming function allows the object to stream itself in from the provided stream parameter. The Hash member function allows the object to describe itself in terms of a scalar hash value. This is used when storing the addresses in collection classes which use the hashing function. The hash function returns the TargetThread scalar value. The IsEqual function allows comparison of objects. The test will return true if equal and false if not. To get or set the process identifier, the GetProcessID and SetProcessID functions are provided. Similarly, to get or set the thread identifier, the GetThreadID and SetThreadID functions are provided. The name of the process being executed may be stored and retrieved from within the TTargetThread. This storage provides a simple mechanism by which to transfer the name between the server and client. Its use is explained in detail in the methods for accessing a thread name. The base class only provides a simple identification protocol, but it can be further extended within the debugger server through subclassing by using inheritance. TTargetThreadList and TTargetThreadListIterator Collections of process objects may be stored within a list. These lists are used in enumerating a list of threads. The primitive TPDSet class is parameterized with the TTargetThread class; likewise, the TPDSetIterator class is parameterized to return TTargetThread objects when iterating through the list. Type definitions are declared for use of these classes:
______________________________________
typedef TPDSet<TTargetThread>
TTargetThreadList;
typedef TPDSetIterator<TTargetThread>
TTargetThreadListIterator;
______________________________________
Breakpoints Breakpoints are specified by process, thread, and address. They can have several conditions which describe what causes a program to stop, and they can describe what kind of behavior should occur to individual threads and threads in a process when the breakpoint is reached. Breakpoints are identified uniquely through an identifier called TargetBreakpoint. The identifier can be used to indicate individual logical breakpoints. Logical breakpoints may be composed of single or multiple breakpoints at one or more addresses. A logical breakpoint may be stored in more than one physical location. Each address may have more than one logical breakpoint associated with it. TargetBreakpoint
______________________________________
typedef long TargetBreakpoint;
// breakpoint ID
______________________________________
The TargetBreakpoint type definition is used to identify logical breakpoints. Logical breakpoints are unique within a host. The breakpoint IDs are assigned by the debugger server. TPrimitiveBreakpoint A primitive breakpoint class allows the client to specify individual breakpoints. The C++ class declaration for this class follows:
______________________________________
class TPrimitiveBreakpoint : public MDebuggerCollectible {
public:
enum BreakType {
kSpuriousBreak,
// breakpoint wasn't in a table
kTemporaryBreak,
// one-shot breakpoint
kUnconditionalBreak,
// unconditional breakpoint
kConditionalBreak,
// conditional breakpoint
kProgramStartBreak
// start of program breakpoint
};
enum BreakStopType {
// ordering is important for union
function
kNonStoppingBreak,
// breakpoint was nonstopping kind
// (reported only)
kStoppingBreakSingleThread,
// breakpoint caused thread to
stop
// (suspend on thread)
kStoppingBreak // breakpoint caused thread to stop
// (suspend all threads)
};
public:
TPrimitiveBreakpoint( );
TPrimitiveBreakpoint(const
TPrimitiveBreakpoint&);
TPrimitiveBreakpoint(
const TargetProcess&
process,
const TargetThread& thread,
const TDebuggerAddress&
address,
BreakType breakType =
kUnconditionalBreak,
BreakStopType breakStopType
kStoppingBreak);
virtual .about.TPrimitiveBreakpoint( );
virtual long Hash( ) const;
virtual bool IsEqual(const
MDebuggerCollectible*)
const;
virtual
TDebuggerStream&
operator>>=(TDebuggerStream&)
const;
virtual
TDebuggerStream&
operator<<=(TDebuggerStream&);
TPrimitiveBreakpoint&
operator=(const
TPrimitiveBreakpoint&);
BreakType GetBreakType( ) const;
void SetBreakType(BreakType);
BreakStopType GetBreakStopType( ) const;
void SetBreakStopType(BreakStopType);
TDebuggerAddress
GetAddress( ) const;
void SetAddress(const
TDebuggerAddress&);
TargetProcess GetProcessID( ) const;
void SetProcessID(TargetProcess);
TargetThread GetThreadID( ) const;
void SetThreadID(TargetThread);
private:
TargetProcess fProcessID;
TargetThread fThreadID;
TDebuggerAddress
fAddress;
BreakType fBreakType,
BreakStopType fBreakStopType;
};
______________________________________
FIG. 16 illustrates the memory layout for the object for the TPrimitiveBreakpoint class. Breakpoint types are broken down into two different dimensions, the BreakType and the BreakStopType. The BreakType defines what kind of breakpoint to set, or what kind of breakpoint occurred. The different enumerated constants have the following meanings: kSpuriousBreak is reported as part of a notification if a breakpoint was detected as a spurious breakpoint, indicating the debugger server did not expect for a breakpoint to be reached. kTemporaryBreak can be specified when a breakpoint is to be removed when it is reached. kUnconditionasBreak indicates a breakpoint which should always cause the thread or process to stop when the breakpoint is reached. kConditionalBreak indicates a breakpoint which has a condition associated with it and should only stop when the condition has been met. kProgramStartBreak is a special type of breakpoint which is reached as a program just starts to execute. The debugger server gains control and notifies debugger clients that the process has been started but in a suspended state until the user determines to continue The other dimension in which breakpoints can differ is specified by the BreakStopType type. When a breakpoint is reached by a thread, the thread does not stop when the kNonStoppingBreak type is specified. This is used for notifying the client asynchronously when a thread has reached a program location without requiring that the program stop execution. Another type is the kStoppingBreakSingleThread which is a thread-specific breakpoint. When this kind of breakpoint is reached, only one thread is suspended while other threads continue executing. Operating system restrictions may prevent this from functioning on all systems. Finally, another type of breakpoint is the kStoppingBreak which indicates that when one thread stops, all threads stop in the target process. The breakpoint can be constructed in three ways: the default constructor, the copy constructor, and a constructor which has parameters specifying the process, thread, breakpoint address, the type of breakpoint, and the stopping behavior. Standard protocol is used for the hashing function (Hash), the equality testing function (IsEqual), streaming operators (operator>>= and operator<=), and assignment operator (operator=). To set or determine the BreakType 1614 for the primitive breakpoint, call the GetBreakType and SetBreakType functions, respectively. To set or determine the BreakStopType 1615 for the primitive breakpoint, call the GetBreakStopType and SetBreakStopType functions, respectively. To set or determine the address 1613 associated with the primitive breakpoint, call the GetAddress and SetAddress functions, respectively. The address 1613 contains a pointer to a (polymorphic) TUniversalAddress object 1602 which contains the target processor-specific address data 1621. To set or determine the process 1611 associated with the primitive breakpoint, call the GetProcess ID and SetProcessID functions, respectively. To set or determine the thread 1612 associated with the primitive breakpoint, call the GetThreadID and SetThreadID functions, respectively. When the thread specified in these functions or as a parameter to the constructor is the kAllThreads constant, a breakpoint will stop when any of the threads in a process reach the location indicated by the breakpoint address. TTargetBreakpoint In a manner similar to the TTargetProcess and TTargetThread class, the breakpoint identifier, TargetBreakpoint, is encapsulated within a class called TTargetBreakpoint. This class has one data member, the fBreakpointID scalar which identifies a logical breakpoint.
______________________________________
class TTargetBreakpoint : public MDebuggerCollectible {
public:
TTargetBreakpoint( );
TTargetBreakpoint(TargetBreakpoint);
TTargetBreakpoint(const
TTargetBreakpoint&);
virtual .about.TTargetBreakpoint( );
virtual long Hash( ) const;
virtual bool IsEqual(const
MDebuggerCollectible*)
const;
virtual TDebuggerStream&
operator>>=(TDebuggerStream&)
const;
virtual TDebuggerStream&
operator<<=(TDebuggerStream&);
TTargetBreakpoint&
operator=(const
TTargetBreakpoint&);
TargetBreakpoint GetTargetBreakpoint( );
private:
TargetBreakpoint fBreakpointId;
};
______________________________________
All base class protocol is implemented. A GetTargetBreakpoint allows a client to determine the breakpoint identifier. Streaming operations will cause the object to write itself into and read itself out of the specified stream. TTargetBreakpointPair Breakpoints are stored in lists using the templatized TStreamableScalarKeyValuePair. These objects are created as a pair of the TargetBreakpoint identifier and the TPrimitiveBreakpoint. The class combining these two objects is called TTargetBreakpointPair. The C++ type definition is declared as:
______________________________________
typedef TStreamableScalarKeyValuePair<TargetBreakpoint,
TPrimitiveBreakpoint>
TTargetBreakpointPair;
______________________________________
TTargetBreakpointTable A breakpoint table is used to store breakpoints internally in the debugger server framework. The entries contained within the table uses TargetBreakpointPair classes for storage and generates unique TargetBreakpoint identifiers in its MintID function. Their C++ type definition is declared as:
______________________________________
typedef TPDIDCollection<TTargetBreakpointPair, TargetBreakpoint>
TTargetBreakpointTable;
______________________________________
TTargetBreakpointTableIterator The object used for iterating through all breakpoints stored in the TTargetBreakpointTable is the
______________________________________
typedef TPDSetIterator<TTargetBreakpointPair>
TTargetBreakpointTableIterator;
______________________________________
TTargetBreakpointList When breakpoints are returned as notification between the server and client, a set of breakpoints are sent to signal which breakpoints were reached. Since more than one logical breakpoint may be reached at a single location, all logical breakpoints are added to the list. A list of primitive breakpoints can also be added to a list to the specify a single logical breakpoint. The C++ type definition for TTargetBreakpointList is declared as:
______________________________________
typedef TPDSet<TTargetBreakpoint>
TTargetBreakpointList;
______________________________________
TTargetBreakpointListIter The object used for iterating through all breakpoints stored in the TTargetBreakpointList is the TTargetBreakpointListIter class.
______________________________________
typedef TPDSetIterator<TTargetBreakpoint>
TTargetBreakpointListIter;
______________________________________
Watchpoints Programs can also be stopped when data access occurs. This can happen from a data reference or a data write in a thread. TPrimitiveWatchpoint The class interface for TPrimitiveWatchpoint is nearly identical | ||||||
