adagnatada2012

Passing struct/record from assembler to Ada


I'm attempting to pass a structure from (x86) assembler to Ada on the stack. I've been able to successfully use this pattern in C to accept to wrap a large number of arguments passed from assembly inside a struct and I'm wondering if this will work in a similar way in Ada.

Here is a (contrived, minimal) example:

When I do this, debugging the callee shows that the passed record contains uninitialised data. It appears that Ada is interpreting the C calling convention differently despite the export directive. The RM contains information about passing structs from Ada to C, saying that it will automatically pass a record as a pointer type, but the inverse does not appear to be true. If you accept a single access type it will simply be filled with the first value on the stack, as one would expect from cdecl.

( Please excuse any minor errors, this isn't my actual code. )

#####################################################################
#  Caller
#
#  This pushes the values onto the stack and calls the Ada function
#####################################################################
.global __example_function
.type __example_function, @function
__example_function:
    push $1
    push $2
    push $3
    push $4
    call accepts_struct
    ret
----------------------------------------------------------------------------
--  Accepts_Struct
--
--  Purpose:
--    Attempts to accept arguments pass on the stack as a struct.
----------------------------------------------------------------------------
procedure Accepts_Struct (
  Struct : Struct_Passed_On_Stack
)
with Export,
  Convention    => C,
  External_Name => "accepts_struct";

----------------------------------------------------------------------------
--  Ideally the four variables passed on the stack would be accepted as
--  the values of this struct.
----------------------------------------------------------------------------
type Struct_Passed_On_Stack is
   record
      A : Unsigned_32;
      B : Unsigned_32;
      C : Unsigned_32;
      D : Unsigned_32;
   end record
with Convention => C;

On the other hand, this works just fine:

procedure Accepts_Struct (
  A : Unsigned_32;
  B : Unsigned_32;
  C : Unsigned_32;
  D : Unsigned_32
)
with Export,
  Convention    => C,
  External_Name => "accepts_struct";

That's not a big deal in this minimal case, but if I'm passing 16 or more variables it gets a bit onerous. If you're wondering why I'm doing this, it's an exception handler where the processor automatically passes variables onto the stack to show register states.

Any help here would be greatly appreciated.


Solution

  • The record version does not work because a record is not stored on the stack. Instead 4 Unsigned_32 elements are stored on the stack. If you really want to work with a record instead of four separate unsigned integer values you can assign the four values to the members of your record within the call to "accepts_struct". Ada expects the first entry in the stack to be a record, not an unsigned_32. The Ada LRM, section 6.4.1 states:

    For the evaluation of a parameter_association: The actual parameter is first evaluated. For an access parameter, the access_definition is elaborated, which creates the anonymous access type. For a parameter (of any mode) that is passed by reference (see 6.2), a view conversion of the actual parameter to the nominal subtype of the formal parameter is evaluated, and the formal parameter denotes that conversion. For an in or in out parameter that is passed by copy (see 6.2), the formal parameter object is created, and the value of the actual parameter is converted to the nominal subtype of the formal parameter and assigned to the formal.

    Furthermore, the passing mode for parameters is described in section 6.2:

    6.2 Formal Parameter Modes

    A parameter_specification declares a formal parameter of mode in, in out, or out. Static Semantics

    A parameter is passed either by copy or by reference. When a parameter is passed by copy, the formal parameter denotes a separate object from the actual parameter, and any information transfer between the two occurs only before and after executing the subprogram. When a parameter is passed by reference, the formal parameter denotes (a view of) the object denoted by the actual parameter; reads and updates of the formal parameter directly reference the actual parameter object.

    A type is a by-copy type if it is an elementary type, or if it is a descendant of a private type whose full type is a by-copy type. A parameter of a by-copy type is passed by copy, unless the formal parameter is explicitly aliased.

    A type is a by-reference type if it is a descendant of one of the following:

    a tagged type;

    a task or protected type;

    an explicitly limited record type;

    a composite type with a subcomponent of a by-reference type;

    a private type whose full type is a by-reference type.

    A parameter of a by-reference type is passed by reference, as is an explicitly aliased parameter of any type. Each value of a by-reference type has an associated object. For a parenthesized expression, qualified_expression, or type_conversion, this object is the one associated with the operand. For a conditional_expression, this object is the one associated with the evaluated dependent_expression.

    For other parameters, it is unspecified whether the parameter is passed by copy or by reference.

    It appears that your compiler is trying to pass the struct by reference rather than by copy. In C all parameters are passed by copy.