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:
no_stack_data flag in procedure descriptors (see Section 8.2.2)
Tru64 UNIX V5.1 includes the following new or changed features:
Tail call flag used in procedure call optimization (see Section 8.2.2)
Version 3.14 of the symbol table includes the following new or changed features:
fullExternals flag in file descriptors to identify the usage policy for stExternal entries (see Section 8.2.1)
Version 3.13 of the symbol table includes the following new or changed features:
vstamp field in file descriptors to identify the symbol table version for an individual compilation unit (see Section 8.2.1)
Uplevel links for referencing local symbols in an outer scope (see Section 8.3.4)
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
adrAddress 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
addressNilis supported in symbol table format V3.13 and greater.
cbLineOffsetByte offset from start of packed line numbers to start of entries for this file.
cbLineByte size of packed line numbers for this file.
cbSsByte 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.
issBaseStart of local strings for this file.
isymBasecsymCount of local symbol entries for this file.
ilineBaseDebuggers 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.
clineSee the preceding
description of
ilineBase.
This field is a count
of this file's entries in the expanded line number array.
ioptBaseByte offset from start of optimization symbol table to optimization symbol entries for this file.
coptByte size of optimization symbol entries for this file.
ipdFirstcpdCount of procedure descriptors for this file.
iauxBasecauxCount of auxiliary symbol entries for this file.
rfdBasecrfdCount of relative file descriptors for this file.
langSource language for this file (see Table 8-1).
fMergeInforms linker whether this file can be merged.
fReadinTrue if file was read in (as opposed to just created).
fBigendianUnused.
glevelSymbolic 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 |
fTrimUnused.
fullExternalsIndicates
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
fullExternalsfield 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
vstampfield is supported on Tru64 UNIX V5.0 and greater for symbol table version V3.13 and greater.
reservedMust be zero.
reserved2Must 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
adrThe 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.
regmaskSaved general register mask.
regoffsetOffset 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.
fregmaskSaved floating-point register mask.
fregoffsetOffset from the virtual frame pointer to the floating-point register save area in the stack frame.
frameoffsetSize 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.
lnLowLowest 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.
lnHighHighest 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_prologuegp_usedFlag set if the procedure uses GP.
reg_frameTrue 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_tailcallIndicates 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_tailcallfield is supported in Tru64 UNIX V5.1 and greater.
reserved4Must be zero.
no_stack_dataIndicates 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_datafield is supported in Tru64 UNIX V5.1B and greater.
reservedMust be zero.
localoffBias value for accessing local symbols on the stack at run time.
frameregFrame pointer register number.
pcregPC (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.adrfield 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.
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:
Get the address of the Thread Environment Block (TEB).
Get the address of the Thread Specific Data (TSD) array from the TEB structure.
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.
Get the TLS pointer value. The TLS pointer is a 64-bit address set to the start of the TLS Region.
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.