8    Run-Time Information

The symbol table contains information that debuggers must interpret to find symbols at run time. This chapter describes the information that the static symbol table structures provides. Algorithms for determining run-time symbol addresses are included.

8.1    New or Changed Run-Time Information Features

Tru64 UNIX V5.1B includes the following new or changed features:

Tru64 UNIX V5.1 includes the following new or changed features:

Version 3.14 of the symbol table includes the following new or changed features:

Version 3.13 of the symbol table includes the following new or changed features:

8.2    Structures, Fields, and Values for Run-Time Information

Unless otherwise specified, all structures described in this section are declared in the header file sym.h, and all constants are defined in the header file symconst.h.

8.2.1    File Descriptor Entry (FDR)

typedef struct fdr {
        coff_addr       adr;    
        coff_long       cbLineOffset;   
        coff_long       cbLine;         
        coff_long       cbSs;           
        coff_int        rss;            
        coff_int        issBase;        
        coff_int        isymBase;      
        coff_int        csym;         
        coff_int        ilineBase;    
        coff_int        cline;       
        coff_int        ioptBase;   
        coff_int        copt;      
        coff_int        ipdFirst;       
        coff_int        cpd;            
        coff_int        iauxBase;      
        coff_int        caux;        
        coff_int        rfdBase;    
        coff_int        crfd;           
        coff_uint       lang : 5;      
        coff_uint       fMerge : 1;  
        coff_uint       fReadin : 1;
        coff_uint       fBigendian : 1;
        coff_uint       glevel : 2;    
        coff_uint       fTrim : 1;    
        coff_uint       reserved : 4;  
        coff_uint       fullExternals : 1; (SV3.14 - )
        coff_ushort     vstamp;            (SV3.13 - )
        coff_uint       reserved2;
} FDR, *pFDR;

SIZE - 96 bytes, ALIGNMENT - 8 bytes

See Section 6.3.2 for related information.

File Descriptor Table Entry Fields

adr

Address of first instruction generated from this source file, which should be the same value as found in the PDR.adr field of the first procedure descriptor for this file. If no instructions are associated with this source file, this field should be set to 0. File descriptors that have been merged by source language in locally-stripped objects will have this field set to addressNil (-1).


Version Note

This use of addressNil is supported in symbol table format V3.13 and greater.


cbLineOffset

Byte offset from start of packed line numbers to start of entries for this file.

cbLine

Byte size of packed line numbers for this file.

cbSs

Byte size of local string table entries for this file.

rss

Byte offset from start of file's local string table entries to source file name; set to issNil (-1) to indicate the source file name is unknown.

issBase

Start of local strings for this file.

isymBase

Starting index of local symbol entries for this file.

csym

Count of local symbol entries for this file.

ilineBase

Debuggers and other tools expand the packed line numbers, producing an array of line numbers with an entry for each machine instruction in the program. This field is an index for this file's first line number entry in the expanded line number array.

cline

See the preceding description of ilineBase. This field is a count of this file's entries in the expanded line number array.

ioptBase

Byte offset from start of optimization symbol table to optimization symbol entries for this file.

copt

Byte size of optimization symbol entries for this file.

ipdFirst

Starting index of procedure descriptors for this file.

cpd

Count of procedure descriptors for this file.

iauxBase

Starting index of auxiliary symbol entries for this file.

caux

Count of auxiliary symbol entries for this file.

rfdBase

Starting index of relative file descriptors for this file.

crfd

Count of relative file descriptors for this file.

lang

Source language for this file (see Table 8-1).

fMerge

Informs linker whether this file can be merged.

fReadin

True if file was read in (as opposed to just created).

fBigendian

Unused.

glevel

Symbolic information level with which this file was compiled. This value is not the same as the user's idea of debugging levels. The value mapping from the user level -g option to the symbol table value is:

Debug switch glevel contents
-g0 2
-g1 1
-g2 0
-g3 3

fTrim

Unused.

fullExternals

Indicates which of two usage policies apply concerning the use of stExternal entries in the local symbols associated with the file descriptor.

If the flag is clear, then the local symbol table is produced under the assumption that a debugger will search the external symbol table for a name that is not found in the current local scope.

If the flag is set, then the local symbol table is produced under the assumption that a debugger will not search the external symbol table for a name that is not found in the current local scope. All external symbols that are visible in a local scope are identified with stExternal/ scInfo local symbol table entries in file scope or lower.


Version Note

The fullExternals field is supported on Tru64 UNIX V5.1B and greater for symbol table version V3.14 and greater.


vstamp

Symbol table version stamp (HDRR.vstamp) value from the original object module (.o file) that is recorded by the linker. The linker may combine objects that were compiled at different times and potentially contain different versions of the symbol table. In post-link objects, this value may or may not match the version stamp in the symbolic header. For pre-link objects, the value in this field will either be zero or the same as the symbolic header stamp.


Version Note

The vstamp field is supported on Tru64 UNIX V5.0 and greater for symbol table version V3.13 and greater.


reserved

Must be zero.

reserved2

Must be zero.

General Notes:

The i*Base fields provide the starting indices of this file's subtables within the symbol table sections. If the associated count fields are set to 0, the base fields will also be set to zero.

For an explanation of packed and expanded line number entries, see the discussion in Section 7.3.1.

Table 8-1:  Source Language (lang) Constants

Name Value Commant
langC 0  
langPascal 1  
langFortran 2  
langAssembler 3  
langMachine 4  
langNil 5  
langAda 6  
langPl1 7  
langCobol 8  
langStdc 9  
langMIPSCxx 10 Unused.
langDECCxx 11  
langCxx 12  
langFortran90 13 Not used by all compilers - langFortran might be used instead for both f77 and f90
langBliss 14  
langMax 31 Number of language codes available

8.2.2    Procedure Descriptor Entry (PDR)

struct pdr {
        coff_addr       adr;    
        coff_long       cbLineOffset;   
        coff_int        isym;          
        coff_int        iline;        
        coff_uint       regmask;     
        coff_int        regoffset;  
        coff_int        iopt;          
        coff_uint       fregmask;     
        coff_int        fregoffset;  
        coff_int        frameoffset;
        coff_int        lnLow;          
        coff_int        lnHigh;        
        coff_uint       gp_prologue : 8; 
        coff_uint       gp_used : 1;   
        coff_uint       reg_frame : 1;
        coff_uint       prof : 1;      
        coff_uint       gp_tailcall : 1;   (V5.1 - )
        coff_uint       reserved4 : 4;     (V5.1B - )
        coff_uint       no_stack_data : 1; (V5.1B - )
        coff_uint       reserved : 7;
        coff_uint       localoff : 8; 
        coff_ushort     framereg;     
        coff_ushort     pcreg;         
} PDR, *pPDR;
#endif

SIZE - 64 bytes, ALIGNMENT - 8 bytes

See Section 8.3 for related information.

Procedure Descriptor Table Entry Fields

adr

The start address of this procedure. Set to addressNil (-1) for procedures with no text.


Version Note

Prior to symbol table format V3.13 this field may not be updated by the linker. To determine the procedure start address for symbol table formats V3.10 - V3.12, use the algorithm described in Section 8.3.1.


cbLineOffset

Byte offset to the start of this procedure's packed line numbers from the start of the file descriptor entry (FDR.cbLineOffset).

isym

Start of local symbols for this procedure. This symbol is the symbol for the procedure (symbol type stProc). The name of the procedure can be obtained from the iss field of the symbol table entry.

If the object is stripped of local symbol information, this field contains an external symbol table index for the procedure symbol's entry.

If this procedure has no symbols associated with it, this field should be set to isymNil (-1). This situation occurs for a static procedure in an object stripped of local symbol information.

iline

Start of line number entries (if expanded) for this procedure. Set to ilineNil (-1) to indicate that this procedure does not have line numbers.

regmask

Saved general register mask.

regoffset

Offset from the virtual frame pointer to the general register save area in the stack frame.

iopt

Start of procedure's optimization symbol entries. Set to ioptNil (-1) to indicate that this procedure does not have optimization symbol entries.

fregmask

Saved floating-point register mask.

fregoffset

Offset from the virtual frame pointer to the floating-point register save area in the stack frame.

frameoffset

Size of the fixed part of the stack frame. The actual frame size can exceed this value. A routine can extend its own frame size for frame sizes larger than 2 GB or for dynamic stack allocation requests.

lnLow

Lowest source line number within this file for the procedure. This is typically the line number of the first instruction in the procedure, but not always. Code optimizations can rearrange or remove instructions making the first instruction map to a different line number.

lnHigh

Highest source line number within this file for the procedure. This field contains a value of -1 for alternate entry points, which is how an alternate entry point is identified.

gp_prologue

Byte size of GP prologue.

gp_used

Flag set if the procedure uses GP.

reg_frame

True if the procedure is a light-weight or null-weight procedure. See the General Notes section following these definitions for more details on procedure weights.

prof

True if the procedure has been compiled with -pg for gprof profiling.

gp_tailcall

Indicates that a call to this procedure may result in a tail call return from a different GP domain. This bit is used exclusively for tail call optimizations.


Version Note

The gp_tailcall field is supported in Tru64 UNIX V5.1 and greater.


reserved4

Must be zero.

no_stack_data

Indicates that no data is being passed on the stack to this procedure. This flag will always be zero for procedures with a variable number of arguments.


Version Note

The no_stack_data field is supported in Tru64 UNIX V5.1B and greater.


reserved

Must be zero.

localoff

Bias value for accessing local symbols on the stack at run time.

framereg

Frame pointer register number.

pcreg

PC (Program Counter) register number.

General Notes:

For more information on call frames, see Section 8.3.2.

If the value of gp_prologue is zero and gp_used is 1, a gp prologue is present but was scheduled into the procedure prologue. Otherwise, the gp_prologue field gives the number of bytes occupied by the GP prologue instructions at the procedure's start address.

If there is a chain of tail call procedures, some of which are in the same GP domain, and some that are in a different GP domain, then gp_tailcall must be set for all procedures in the chain. For example, suppose there is a tail call from A to B, and a tail call from B to C. A and B are in the same GP domain, but C is in a different GP domain. In this case gp_tailcall must be set in both A's and B's PDR, because callers can't rely on the standard definition of GP after calling A. See the Alpha Architecture Reference Manual for additional details.

For an explanation of packed and expanded line number entries, see the discussion in Section 7.3.1.

A procedure may be heavy-, light-, or null-weight. The weight of a procedure can be determined from its descriptor by using the following guidelines:

Weight Indications
Heavy reg_frame is 0 and bit 26 of the register mask (regmask) is on
Light reg_frame is 1 and regoffset is ra_save
Null reg_frame is 1 and regoffset is 26

See the Calling Standard for Alpha Systems for details on the calling conventions for different weight procedures. Note that a calling routine does not need to know the weight of the routine being called.

8.3    Run-Time Information Usage

8.3.1    Procedure Addresses

The following pseudocode describes an algorithm for determining the procedure start address:

if (HDRR.vstamp >= 0x30D || PDR.isym == isymNil) 
    return(PDR.adr)
else
    foreach FDR in HDRR
        foreach PDR in FDR
            if PDR matches
                if (FDR.csym == 0)  /* Use external symbol */
                    return (EXTR[PDR.isym].asym.value)
                else                /* Use local symbol */
                    return (SYMR[FDR.isymbase + PDR.isym].value)

If local symbol information is present for the given PDR, the isym field identifies the local symbol table entry that contains the start address of the procedure. If no local symbol information is present, the isym field identifies the external symbol table entry containing the start address of the procedure. If no symbol information is present for the PDR, the isym field is set to isymNil and the adr field will contain a reliable start address.


Version Note

The PDR.adr field is reliably updated by the linker for symbol table format V3.13. The preceding algorithm is recommended for determining procedure addresses in symbol table formats less than V3.13.


8.3.2    Stack Frames

A stack frame is a run-time memory structure that is created whenever a procedure is called. The Calling Standard for Alpha Systems specifies the stack frame format and related code requirements. This section explains how to interpret procedure descriptor fields related to the stack frame.

Two types of stack frames are supported: fixed-size frames and variable-size frames. The variable frame format is used for procedures that dynamically allocate memory and for those with very large frames. Figure 8-1 shows a fixed-size frame and Figure 8-2 shows a variable-sized frame.

From the procedure descriptor, you can determine which type of stack frame the procedure has. The field PDR.framereg stores the frame pointer register number. If this field has a value of 30 ($sp), the stack frame is a fixed-size frame. If it has a value of 15 ($fp), the stack frame is a variable-size frame.

Figure 8-1:  Fixed-Size Stack Frame

Figure 8-2:  Variable-Size Stack Frame

For both types of stack frames, the value of PDR.frameoffset is the size of the fixed part of the stack frame. In the case of a fixed-size frame, it is the entire frame size. For a variable-sized frame, the entire frame size cannot be determined from the symbol table. The code may dynamically increase and decrease the size of the frame multiple times during procedure execution.

The virtual frame pointer represents the contents of the frame pointer register at procedure entry, prior to prologue execution. The (real) frame pointer is the contents of the frame pointer register after prologue execution. The difference between the virtual and real frame pointer values is the fixed frame size, which is subtracted from the $sp contents during the procedure prologue. Note that stack offsets recorded in the symbol table are relative to the virtual frame pointer, not the real value used at run time.

The contents of the frame pointer register at are used at run time as the base address for accessing data, such as parameters and local variables, on the stack. See Section 8.3.3 for details.

8.3.3    Local Symbol Addresses

Local variables and parameters may be stored in registers or on the stack. Those stored in registers (identified by a storage class of scRegister) do not have addresses. For local variables and parameters with addresses, this section explains how to calculate their run-time locations from the symbol table information.

To calculate the run-time address for a local variable ( stLocal) based on its symbol table value:

Frame pointer - PDR.localoff + SYMR.value

To calculate the run-time address for a parameter ( stParam) based on its symbol table value:

Frame pointer - argument_home_area_size + SYMR.value

The argument home area is a portion of the stack frame designated for parameter storage. See Figure 8-1 for an illustration. For historical reasons, the size of this area is always 48 bytes.

The calculations above must be performed at run time when the actual frame pointer value is known. Note that the value becomes valid only after the procedure prologue has executed.

To calculate the locations based on static information, convert the symbol's value to an offset from the real frame pointer:

Local:

PDR.frameoffset - PDR.localoff + SYMR.value

Parameter:

PDR.frameoffset - 48 + SYMR.value

The resulting offsets are always positive values because the frame pointer contains the address of the lowest memory in the fixed part of the stack frame at run time.

8.3.4    Uplevel Links


Version Note

Uplevel links are supported in symbol table format V3.13 and greater.


An uplevel link is the real frame pointer of an ancestor of a nested routine. The routine nesting may be a feature of the language (such as Pascal), or the nesting may occur in optimized code which has been decomposed for parallel execution into smaller routines. Uplevel links provide debuggers a method of finding all local symbols associated with the ancestor routine.

When a procedure is passed a static link, that static link will be represented within the scope of the procedure definition as a local automatic symbol with a special name beginning with "__StaticLink.". The lifetime of this symbol begins after the procedure prologue has been executed.

The static link symbol will occur between the procedure's parameter definitions and the first stBlock symbol.

The full name of the symbol will be "__StaticLink." followed by a positive decimal integer with no leading zeros. This integer value identifies the number of levels up the ancestor tree the static link points to.

For example, if the name is "__StaticLink.3" it will contain the static link of the procedure in which it is defined, and that procedure's static link points to a stack frame that is three levels up in the procedure's ancestor tree, the great-grandfather of the procedure.

Figure 8-3:  Representation of Uplevel Reference

Debuggers of Tru64 UNIX object files need to use the uplevel link information to determine which symbols are visible at a location in the program and to compute the addresses of local symbols in ancestor routines. When the debugger needs the current value or address of a name that might be defined as an uplevel reference, two separate actions may be required: finding the procedure that defines the currently visible instance of that name, and finding the address of the currently visible instance of that name. If only type information is required, finding the procedure that defines the name may be sufficient.

Finding the defining procedure is accomplished by repeatedly looking up the name in the local symbol table of a chain of procedures that extends from the current procedure through its chain of ancestors until either the name is found in a procedure or the end of the chain of ancestors is reached without finding the name. If this search terminates without finding the name, the debugger should conclude that the name is not visible by uplevel reference at the current location in the program.

When searching for the desired procedure, the debugger should count how many levels in the ancestor chain were traversed before finding the name. If zero levels were traversed, the name is defined within the current procedure and is not an uplevel reference. The number of levels traversed is assumed to be in the variable LevelsToGo in the algorithm below.

Finding the address for the name involves locating static link values and dereferencing them with appropriate offsets. Basically, while the number of levels to be traversed is greater than zero, find the static link symbol for the current level and obtain its value. Finally, add the desired symbol's offset from the real frame pointer to the final static link value.

The recommended algorithm for finding the address is as follows:

LevelsToGo = <from name lookup above>
NewProc = CurrentProcedure
NewFrame = FramePointerValue(CurrentProcedure)
Failed = false
while (LevelsToGo > 0 && !Failed)
    StaticLink = FindStaticLinkSym(NewProc)
    if (StaticLink == NULL)
        Failed = true
    else
        NewFrame = *(NewFrame + StaticLink->symbol.offset)
        Levels = StaticLinkLevels(StaticLink)
        LevelsToGo = LevelsToGo - Levels
        for (; Levels > 0; Levels--)
            NewProc = NewProc->proc.parent

if Failed is true after executing this algorithm, required information about static links is missing in the symbol table, and an error has occurred. If LevelsToGo ends up less than zero, the optimizer's static link optimization has eliminated a static link level that would be needed to compute the address of the name. It is recommended that debuggers inform the user that optimization prevents the debugger from computing the address of the name.

If Failed is false and LevelsToGo is equal to zero, the address for the currently visible instance of the name is NewFrame plus the offset of the name with respect to the real frame pointer for NewProc.

The function StaticLinkLevels returns the integer at the end of the name for the indicated static link symbol.

8.3.5    Finding Thread Local Storage (TLS) Symbols

This section explains how to interpret symbolic information for TLS symbols (identified by a storage class of scTlsData or scTlsBss). See Section 3.3.9 or the Programmer's Guide for general information on TLS.

A TLS symbol's value contains its offset from the start of the TLS region for that object. This offset can be used at process execution time to determine the address of the TLS symbol for a particular thread.

A debugger can calculate TLS symbol addresses by looking up the address of the TLS region using run-time structures and adding the offset of the TLS symbol to that address. The following formula can be used to calculate TLS symbol addresses.

TLS sym address = *(TEB.TSD + __tlskey) + SYMR.value

A detailed description of this formula follows:

  1. Get the address of the Thread Environment Block (TEB).

  2. Get the address of the Thread Specific Data (TSD) array from the TEB structure.

  3. Get the offset of the TLS pointer in the TSD array.

    This offset is normally stored in a .lita or .got entry. This value should be accessed using the symbol __tlskey . In spite of the fact that __tlskey is a label symbol, no ampersand is used in this context because the value that the label points to is being retrieved. The address of __tlskey will need to be adjusted by the address mapping displacement in the same manner that the debugger adjusts addresses of text and data symbols.

    For static executables, the .lita entry contains the constant offset (2048). This offset identifies the first and only TSD slot (256) that will be allocated for the TLS pointer.

    For shared objects, the .got entry labeled by __tlskey is initially 0, indicating that the TSD slot has not been allocated yet. After the object's initialization routines have run, a TSD key will be allocated and the .got entry will contain its offset.

  4. Get the TLS pointer value. The TLS pointer is a 64-bit address set to the start of the TLS Region.

  5. Calculate the address of the TLS symbol by adding the offset of the TLS symbol to the TLS pointer value.

TLS common symbols ( scTlsCommon) should not occur in linked objects, so debuggers should not need to support them. Executables and shared libraries can only reference TLS symbols that they define, so successfully linked objects should have not TLS undefined or TLS common symbols.