8    Procedure Descriptors

Procedure descriptors serve the following functions:

Every procedure, except for null frame procedures, must have an associated procedure descriptor. (Null frame procedures are discussed in Section 3.1.4.)

Note

The term procedure descriptor also appears in the manual Symbol Table/Object File Specification. The use of the term in that manual refers to a structure defined in the file /usr/include/sym.h and should not be confused with procedure descriptors used for exceptions, as described in this manual.

This chapter covers the following procedure-descriptor topics:

8.1    Procedure Descriptor Representation

Procedure descriptors on Tru64 UNIX for Alpha systems use two kinds of structures: code range descriptors and run-time procedure descriptors.

Code range descriptors associate a contiguous sequence of addresses with a run-time procedure descriptor. This mapping can be many-to-one.

Run-time procedure descriptors provide descriptive information about a procedure or part of a procedure.

The creation of procedure descriptors involves the combined actions of compilers and assemblers, the linker, and the loader. The sections that follow focus solely on the result of this composite process and describe the resulting descriptors as seen by the executing run-time environment. The figures present the physical representation of the procedure descriptor structures. Macros, which have the prefix PDSC_, can be used to access the fields of these structures. The physical representation and the macros are defined in the file /usr/include/pdsc.h.

A single run-time procedure descriptor should be used to describe a complete procedure, even if it has multiple entry points, as in FORTRAN. In this case, each entry point typically consists of the following elements:

Although the prologues might contain different code, they must all achieve the same effect: the same registers saved at the same offset, the same frame size, and so on.

Two instructions are said to be part of the same procedure if they are described by the same run-time procedure descriptor (RPD). However, if code has been inserted into a procedure (for example, through inlining or instrumentation), this code may have a separate RPD associated with it. In this situation, the return_address field of the procedure descriptor will have a nonzero value. Before determining whether an instruction within an inserted code range is in the same procedure as another instruction, the RPD chain must be followed until an RPD with a return_address field equal to zero is found. Then, the comparison can be done with the top-level RPD. See Section 5.2.5 for more information.

Notes on binary compatibility:

  1. Earlier versions of this Calling Standard used a separate procedure descriptor for each entry point of a procedure. This method is now obsolete. However, tools must be able to handle applications compiled under the obsolete model as well as the current model.

  2. Exception handling will operate correctly on both obsolete and current models of RPDs.

  3. The property that two instructions are part of the same procedure only if they have the same procedure descriptor cannot be used on applications compiled under the obsolete model.

  4. Using a single RPD to describe a complete procedure was implemented in Tru64 UNIX Version 5.1. Earlier versions used the obsolete model.

8.1.1    Code Range Descriptors

The code-range table is an array of code-range descriptors, as shown in Figure 8-1.

The following macros are used to access data in code range descriptors:

PDSC_CRD_BEGIN_ADDRESS

Specifies the address of the beginning of a code range. The elements of the array are sorted so that the portion of the address space covered by a single element starts at the PDSC_CRD_BEGIN_ADDRESS value contained in that element and extends to, but does not include, the PDSC_CRD_BEGIN_ADDRESS value encoded in the next successive element. Thus, the last array element provides the end address of the last code range and does not start a new range.

PDSC_CRD_PRPD

Specifies the address of the associated run-time procedure descriptor. A PDSC_CRD_PRPD value of zero indicates a null frame procedure for which an implicit run-time procedure descriptor is assumed with the characteristics described in Section 3.1.4.

PDSC_CRD_CONTAINS_PROLOG

Indicates whether the code range begins with a procedure prologue.

PDSC_CRD_MEMORY_SPECULATION

Indicates that memory traps (SIGSEGV, SIGBUS) raised in this procedure should not be delivered.

PDSC_CRD_TYPE_STANDARD
PDSC_CRD_TYPE_CONTEXT
PDSC_CRD_TYPE_NON_CONTEXT
PDSC_CRD_TYPE_NON_CONTEXT_STACK
PDSC_CRD_TYPE_DATA

Indicate the procedure context of the code described by the code range descriptor.

Figure 8-1:  Code Range Descriptor

  1. begin_address

    Contains a longword that is the offset from the base of the code range table to the starting point of the code to which the associated run-time procedure descriptor applies. The low two bits of this longword are reserved for use as flags; they must be masked out before the containing longword is used as an offset.

  2. rpd_offset

    Contains a longword that is the self-relative offset to the associated run-time procedure descriptor. The low two bits of this longword are used as flags; they must be masked out before the containing longword is used as an offset. An rpd_offset value of zero indicates a null frame procedure for which an implicit run-time procedure descriptor is assumed with the characteristics defined in Section 3.1.4.

  3. memory_speculation (bit 1 of rpd_offset)

    Indicates that memory traps (SIGSEGV, SIGBUS) occurring in this procedure should not be delivered.

  4. s, t, n (bits 1,0 of begin_address, bit 0 of rpd_offset)

    These three bits are used together to identify the context of the code that the code range descriptor is describing, listed below. If the rpd_offset is 0, then the values of s, t, and n are zero as well.

    In the following descriptions for the types of code range descriptor, the occurrence of CONTEXT in the name implies that the associated procedure is current for exception handling purposes during that part of the procedure specified by the CRD; similarly, NON_CONTEXT implies the procedure is not current. STACK implies that stack storage has been allocated. A very common special case in indicated by the occurence of STANDARD in the name because it corresponds to a standard sequence of up to three states. DATA implies storage that is not executable code at all.

    The encodings (s=1, t=0, n=0), (s=1, t=1, n=0) and (s=1, t=1, n=1) are reserved. One of these encodings should be used for the future extension of the CRD.

    Note on binary compatibility:

    1. In earlier versions of this Calling Standard, bits 0 and 1 of begin_address were undefined, and bit 0 of rpd_offset was set if no prologue was associated with the code range. The encodings of s, t, and n maintain the binary compatibility of the original specification. Be aware that the macro PDSC_CRD_CONTAINS_PROLOG will return TRUE if bit 0 of rpd_offset is clear.

8.1.2    Run-Time Procedure Descriptors

Run-time procedure descriptors provide information about a procedure needed for exception handling and other tools. Table 3-1 lists this information. There are two forms: long and short. Both forms encode the same information. The short form is used to save space for the most commonly occurring cases. Inserted code must use the long form of the RPD. Figure 8-2 shows the long form; Figure 8-3 shows the short form.

Each figure shows two alternative representations for the first longword. The first representation applies to stack frame procedures and is shown in the main part of the figure. The second representation applies to register frame procedures and is shown as a separate longword at the end of the figure. The PDSC_FLAGS_REGISTER_FRAME flag, which is one of the flags common to both forms, determines which representation applies.

Descriptions of the physical fields follow the figures. These descriptions include the calculations used to obtain the logical field from the physical field. Most fields are common to both procedure descriptor forms. The long form has three additional fields and two additional flags as shown in Figure 8-2. The additional fields are entry_ra, return_address, and a field reserved for future use. The additional flags are PSDC_FLAGS_ARITHMETIC_SPECULATION and PDSC_FLAGS_EXTENDER.

Figure 8-2:  Long Form Run-Time Procedure Descriptor

Figure 8-3:  Short Form Run-Time Procedure Descriptor

  1. The flags field represents PDSC_RPD_FLAGS and encodes the following flags:

  2. The entry_ra field is the number of the register in which the return address is passed to this procedure. The entry_ra field can be accessed with PDSC_RPD_ENTRY_RA.

  3. The rsa_offset field is the signed difference in quadwords between the stack frame base (SP or FP as indicated by PDSC_FLAGS_BASE_REG_IS_FP) and the register save area. (See Section 3.1.2 for information on stack frame procedures.)

    PDSC_RPD_RSA_OFFSET = rsa_offset * 8
    

  4. The sp_set field is the unsigned offset in instructions (longwords) from the entry address of the procedure to the single instruction in the procedure prologue that modifies the stack pointer. This offset must be zero when there is no such instruction because the procedure has a PDSC_RPD_FRAME_SIZE of 0.

    PDSC_RPD_SP_SET = sp_set * 4
    

  5. The entry_length field is the unsigned offset in instructions (longwords) from the entry address to the first instruction in the procedure code segment following the procedure prologue.

    PDSC_RPD_ENTRY_LENGTH = entry_length * 4
    

  6. The frame_size field is the unsigned size in quadwords of the fixed portion of the stack frame for this procedure.

    PDSC_RPD_FRAME_SIZE = frame_size * 8
    

    The value of SP at entry to this procedure can be calculated by adding PDSC_RPD_FRAME_SIZE to the value SP or FP, as indicated by PDSC_FLAGS_BASE_REG_IS_FP. PDSC_RPD_FRAME_SIZE cannot be 0 for a stack frame procedure because the stack frame must include space for the register save area.

    Note: If a procedure needs a frame size that is too large to be represented using the frame_size field, a variable-size stack frame should be used. In this case, the FP register is used to address a fixed size area that needs to be just large enough to include the preserved state. An arbitrarily large stack area can then be covered by the SP register.

  7. The lower two bits of the return_address, r, are reserved for future use. These lower two bits must be masked off the containing longword before the offset is used.

  8. The return_address contains a longword that is the offset from the base of the code range table to the point in the code where the flow of control should return when the code range described by this RPD is complete. The low two bits of this longword are reserved and must be masked out before the containing longword is used as an offset. If this field is not equal to 0, then the code segment that this RPD describes is an inserted code range. See Section 5.2.5 for more information.

    PDSC_RPD_RETURN_ADDRESS_FIELD = return_address & (~3)
     
    PDSC_RPD_RETURN_ADDRESS =
         code_range_table_base_address + PDSC_RPD_RETURN_ADDRESS_FIELD
    

  9. The imask field is a bit vector (0 - 31) specifying the integer registers that are saved in the variable portion of the register save area on entry to the procedure. The least significant bit corresponds to register $0. Bits 31, 30, 28, and the register containing the entry return address of this mask should never be set because $31 is the integer Read-As-Zero register, $30 is the hardware SP, $29 (GP) is always assumed to be destroyed during a procedure call or return, and the return address is saved at known offset zero in the register save area in every stack frame procedure. The imask field can be accessed with PDSC_RPD_IMASK.

  10. The fmask field is a bit vector (0 - 31) specifying the floating-point registers that are saved in the variable portion of the register save area on entry to the procedure. The least significant bit corresponds to register $f0. Bit 31 of this mask should never be set because it corresponds to the floating-point Read-As-Zero register. The fmask field can be accessed with PDSC_RPD_FMASK.

  11. The handler_address field is an absolute procedure value (quadword) for a run-time static exception handling procedure. The handler_address field can be accessed with PDSC_RPD_HANDLER.

    This part of the procedure descriptor is optional. However, it must be supplied if PDSC_FLAGS_HANDLER_VALID is 1. If PDSC_FLAGS_HANDLER_VALID is 0, the contents or existence of PDSC_RPD_HANDLER is unpredictable.

  12. The handler_data_address field is a quadword of data for the exception handler. The handler_data_address field can be accessed with PDSC_RPD_HANDLER_DATA.

    This part of the procedure descriptor is optional. However, it must be supplied if PDSC_FLAGS_HANDLER_VALID is 1. If PDSC_FLAGS_HANDLER_VALID is 0, the contents or existence of PDSC_RPD_HANDLER_DATA is unpredictable.

  13. The save_ra field is the number of the register in which the return address is maintained during the body of the procedure. The save_rafield can be accessed with PDSC_PRD_SAVE_RA.

    If this procedure uses the standard calling conventions and does not modify $26, both PDSC_RPD_ENTRY_RA and PDSC_RPD_SAVE_RA will specify $26.

The short form run-time procedure descriptor differs from the long form in the following ways:

If any of these restrictions cannot be satisfied, the long form run-time procedure descriptor must be used.

8.1.3    Examples

This section contains two examples of how to use code range descriptors and procedure descriptors. The first example involves multiple entry points and the second example involves instrumented code.

8.1.3.1    Multiple Entry Point Example

Consider the following routine, written in a hypothetical C-like language that allows multiple entry points.

double sd;
double ent1(int i, double d) {
 
LX: if (i || ((int)d) < 5) {
        return foo() * 2.0 + PI;
    }
  	  return 0.0;
 
/* Alternate entry point */
double ent2(int i, double d):
    double d2;
    if (!i) goto LX;
 
    if ( (d2 = foo2()) != 0.0) {
        return tailcallee(d2*d2+sd);
    }
    return sd * 10;
}

A sample version of code is annotated below. This case is very contrived to show many types of descriptors.

                                   CRD#       Types               Notes
    .globl  ent1
    .ent    ent1
ent1:
    ldgp    $gp, 0($27)               0      non-context            (1)
    bne     $16, lab1                 0
    lda     $sp, -32($sp)             0
 
    cvttq   $f17, $f16                1      non-context-stack
    stt     $f16, ($sp)               1
    ldq     $0, ($sp)                 1
    cmplt   $0, 5, $1                 1
    bne     $1, lab2                  1
    lda     $sp, 32($sp)              1
 
lab0:
    fclr    $f0                       2      non-context            (2)
    ret     $26                       2
lab1:
    lda     $sp, -32($sp)             3      standard               (5)
lab2:
    stq     $26, ($sp)                3                             (6)
 
    .prologue 1
    .frame  $sp,32,$26
    ...call to foo...                 3
    ldq     $f1, 2.0                  3
    ldq     $26, ($sp)                3
    lda     $sp, 32($sp)              3
 
    mult    $f0, $f1, $f0             4      non-context            (2)
    addt    $f0, $f16, $f0            4
    ret     $26                       4
 
    .globl  ent2
    .aent   ent2
 
ent2:
    bne     $16, lab0                 5      non-context            (1)
 
lab3:
    ldgp    $gp, 0($27)               5                             (4)
    lda     $sp, -32($sp)             5
 
    stq     $26, ($sp)                6      non-context-stack
 
    .prologue 1
    .frame  $sp,32,$26
    ...call to foo2...                7      context
    ldq     $f16, sd                  7
    fbeq    $f0, lab5                 7
    mult    $f0, $f0, $f0             7
    ldq     $26, ($sp)                7
    lda     $sp, 32($sp)              7
 
    addt    $f0, $f16, $f16           8      non_context            (2)
    br      tailcallee                8                             (3)
 
lab5:
    ldq     $f0, 10.0                 9      context
    lda     $sp, 32($sp)              9
 
    mult    $f0, $f16, $f0           10      non_context            (2)
    ret     $26

Notes:

  1. The code from ent1 through to lab1 is a shrinkwrap, where some code is moved outside of full procedure context, as is the bne instruction following ent2. In this very contrived example, the savings are minimal for the first case (only the return register save is avoided).

  2. This code is a floating exit sequence where the stack deallocation was scheduled away from the corresponding return. See Section 3.2.6.2.1 for more information.

  3. This is a tailcall, where control does not transfer back to the caller.

  4. In this hypothetical case, the compiler chose to prefer the bne over being able to skip the gp prologue for the entry.

  5. If the routine has a stack frame, it is an error for a primary prologue to not contain a stack adjustment because the adjustment is required in order to get a correct sp_set value.

The following tables show the code range descriptors and the run-time procedure descriptors associated with the preceding example.

Code range descriptors:

crd begin_address crd_type rpd_offset
CRD0 ent1 non_context PD0
CRD1 ent1+12 non_context_stack PD0
CRD2 lab0 non_context PD0
CRD3 lab1 standard PD0
CRD4 lab2+20 non_context PD0
CRD5 ent2 non_context PD0
CRD6 lab3+8 non_context_stack PD0
CRD7 lab3+12 context PD0
CRD8 lab3+36 non_context PD0
CRD9 lab5 context PD0
CRD10 lab5+8 non_context PD0

Run-time procedure descriptors:

rpd sp_set entry_length frame_size return_address
PD0 0 2 4 0

8.1.3.2    Instrumented Code Example

The following example displays an instrumentation point inserted by ATOM, a post-link tool. The standard "Hello world" test case is instrumented with the ATOM tool Third Degree.

main()
{
        printf("Hello world!\n");
}

The following program listing is a disassembly of main. The code range inserted by a post-link tool begins at 0x120063984 and ends at 0x1200639ac, inclusively.

                                                 CRD#   Type             Notes
      main:
0x120063978: ldah    gp, 8186(r27)                 0    standard          (1)
0x12006397c: lda     gp, 18184(gp)                 0
0x120063980: lda     sp, -16(sp)                   0
0x120063984: lda     sp, -48(sp)                   1    non-context       (2)
0x120063988: stq     r16, 24(sp)                   2    non-context-stack
0x12006398c: stq     r17, 16(sp)                   2
0x120063990: stq     r26, 8(sp)                    2
0x120063994: lda     r16, 4(r31)                   2
0x120063998: lda     r17, 48(sp)                   2
0x12006399c: bsr     r26, 0x1200069f0(r31)         2
0x1200639a0: ldq     r16, 24(sp)                   2
0x1200639a4: ldq     r17, 16(sp)                   2
0x1200639a8: ldq     r26, 8(sp)                    2
0x1200639ac: lda     sp, 48(sp)                    2
0x1200639b0: stq     r26, 0(sp)                    3    non-context-stack (3)
0x1200639b4: ldq     r16, -32736(gp)               4    context
0x1200639b8: ldq     r27, -32608(gp)               4
0x1200639bc: jsr     r26, (r27), 0x1200625d4(r31)  4
0x1200639c0: ldah    gp, 8186(r26)                 4
0x1200639c4: lda     gp, 18112(gp)                 4
0x1200639c8: bis     r31, r31, r0                  4
0x1200639cc: ldq     r26, 0(sp)                    4
0x1200639d0: lda     sp, 16(sp)                    4
0x1200639d4: ret     r31, (r26), 1                 4    implicitly non-   (4)
                                                          context

Notes:

  1. This CRD is still of type CRD_TYPE_STANDARD because sp_set is still relative to this code range. The entry_length field is no longer relative to this code range, and should be zero.

  2. This instruction has a type of CRD_TYPE_NON_CONTEXT because the previous stack allocation was for a different RPD.

  3. This instruction has a type of CRD_TYPE_NON_CONTEXT_STACK because the stack was allocated at main+8 and this is still part of the prologue.

  4. The return instructions at the end of the procedure is implicitly non-context because of the reserved instruction sequence rules defined in Section 3.2.6.2.1. Using an additional CRD here would be redundant, but not incorrect.

The following tables show the code range descriptors and the run-time procedure descriptors associated with the preceding example.

Code range descriptors:

crd begin_address crd_type rpd_offset
CRD0 main standard PD0
CRD1 main+12 standard PD1
CRD2 main+16 non_context_stack PD1
CRD3 main+56 non_context_stack PD0
CRD4 main+58 context PD0

Run-time procedure descriptors:

rpd sp_set entry_length frame_size return_address
PD0 2 0 2 0
PD1 0 0 6 CRD3

For the purpose of comparison, this is the non-instrumented version of main:

[hw.c:  1] 0x120001120:    27bb2000     ldah    gp, 8192(r27)
[hw.c:  1] 0x120001124:    23bd6f60     lda     gp, 28512(gp)
[hw.c:  1] 0x120001128:    23defff0     lda     sp, -16(sp)
[hw.c:  1] 0x12000112c:    b75e0000     stq     r26, 0(sp)
[hw.c:  3] 0x120001130:    a61d8020     ldq     r16, -32736(gp)
[hw.c:  3] 0x120001134:    a77d80a0     ldq     r27, -32608(gp)
[hw.c:  3] 0x120001138:    6b5b7b05     jsr     r26, (r27), 0x11ffffd50(r31)
[hw.c:  3] 0x12000113c:    27ba2000     ldah    gp, 8192(r26)
[hw.c:  3] 0x120001140:    23bd6f44     lda     gp, 28484(gp)
[hw.c:  4] 0x120001144:    47ff0400     bis     r31, r31, r0
[hw.c:  4] 0x120001148:    a75e0000     ldq     r26, 0(sp)
[hw.c:  4] 0x12000114c:    23de0010     lda     sp, 16(sp)
[hw.c:  4] 0x120001150:    6bfa8001     ret     r31, (r26), 1

The following tables show the code range descriptors and run-time procedure descriptors for the preceding example.

Code range descriptors (noninstrumented):

crd begin_address crd_type rpd_offset
CRD0 main standard PD0

Run-time procedure descriptors (noninstrumented):

rpd sp_set entry_length frame_size return_address
PD0 2 4 2 0

8.2    Procedure Descriptor Access Routines

A thread can obtain information from the descriptor of any procedure in the thread's virtual address space by calling system library functions.

In the course of running and debugging a program, there are times when it is necessary to identify which procedure is currently executing. During normal thread execution, the current procedure must be determinable any time an exception arises so that the proper handlers will be invoked. In addition, a debugger must know which procedure invocation is currently executing so it can obtain information about the current state of the execution environment.

To determine precisely the current execution context, two pieces of information are required:

This context of the current procedure and the specific instance of that procedure invocation are referred to as the current procedure invocation or simply, current procedure. At any point in the execution of a thread, only one procedure is considered to be the current procedure.

In this calling standard, the value in the PC is used to indicate the current procedure by means of the code range table described in Section 8.1.1.

The following system-supplied routine is used to obtain the address of the procedure descriptor that corresponds with any given PC value within the current address space.

exc_lookup_function_entry (ControlPC)

Arguments:

ControlPC Specifies a PC value in the current address space for which the procedure value is to be returned.

Function Value:

PROC_DESC Specifies the address of the code range descriptor for the procedure containing the requested PC. If the return value is null, the PC is not currently mapped.

The following system-supplied routine is used to obtain the address of the base of the code range array for the procedure descriptor that corresponds B to any given PC value within the current address space.

exc_lookup_function_table (ControlPC)

Arguments:

ControlPC Specifies a PC value in the current address space for which the code range base address is to be returned.

Function Value:

PROC_CRD Specifies the address of the base of the code range descriptor array for the procedure descriptor of the procedure containing the requested PC. If the return value is null, the PC is not currently mapped.

At times, it is useful to acquire the GOT segment value for a procedure; that is, the value of the GP register. The following system-supplied routine is used to obtain the GP value corresponding to any given PC value within the current address space.

exc_lookup_gp (ControlPC)

Arguments:

ControlPC Specifies a PC value in the current address space for which the GP value is to be returned.

Function Value:

GP_VALUE Specifies a GP value for the procedure containing the requested PC. If the return value is null, the PC is not currently mapped.

8.3    Run-Time Generated Code

Code generated at run time is important for applications that include:

To maintain stack traceability when code generated at run time is executed, procedure descriptors must be provided for that code. Such procedure descriptors must describe correctly the characteristics of the code and the environment within which that code executes.

Before run-time generated code that uses any exception facilities (directly or indirectly) can be executed, system library functions must be called to communicate the code ranges, procedure descriptors, and GP values to the execution environment. This communication is accomplished by calling the following two system-supplied routines:

exc_add_pc_range_table (PROC_DESC_ADDR, LENGTH)

Arguments:

PROC_DESC_ADDR Specifies the base address of the code range array for the procedure descriptors.
LENGTH Specifies the number of code range elements in the array.

An exception is raised if the exc_add_pc_range() operation cannot be completed successfully.

exc_add_gp_range (BEGIN_ADDRESS, LENGTH, GP_VALUE)
 

Arguments:

BEGIN_ADDRESS Specifies the first address for which GP_VALUE applies.
LENGTH Specifies the number of bytes from BEGIN_ADDRESS for which GP_VALUE applies.
GP_VALUE Specifies the GP value to be associated with the addresses in the range BEGIN_ADDRESS .. BEGIN_ADDRESS + LENGTH + 1.

An exception is raised if the exc_add_gp_range() operation cannot be completed successfully.

When procedure information is no longer valid or if the code will not be executed again, two system library routines should be called to remove the procedure mapping information. These system library routines are defined as follows:

exc_remove_pc_range_table (PROC_DESC_ADDR)

Arguments:

PROC_DESC_ADDR Specifies the base address of the code range array for the procedure descriptors.

An exception is raised if the exc_remove_pc_range_table() operation cannot be completed successfully.

exc_remove_gp_range (BEGIN_ADDRESS)

Arguments:

BEGIN_ADDRESS Specifies the beginning address for which GP-value information should be removed.

An exception is raised if the exc_remove_gp_range() operation cannot be completed successfully.

The following steps show how run-time code should be constructed and released:

  1. Allocate memory for the code.

  2. Write the code and any procedure descriptors to memory.

  3. Call exc_add_pc_range_table() and exc_add_gp_range().

  4. Invoke an imb (instruction memory barrier) operation as required by the Alpha architecture.

  5. Execute the code.

  6. Call exc_remove_pc_range_table() and exc_remove_gp_range().

  7. Deallocate the memory containing the code.