将 struct/record 从汇编器传递给 Ada

Passing struct/record from assembler to Ada

我正在尝试将结构从 (x86) 汇编程序传递到堆栈上的 Ada。我已经能够在 C 中成功地使用这个模式来接受将从程序集传递的大量参数包装在一个结构中,我想知道这是否会在 Ada 中以类似的方式工作。

这是一个(人为的,最小的)示例:

当我这样做时,调试被调用者显示传递的记录包含未初始化的数据。尽管有 export 指令,但 Ada 似乎以不同的方式解释 C 调用约定。 RM包含有关将结构从Ada传递到C的信息,说它会自动将记录作为指针类型传递,但反之似乎并非如此。如果您接受单个 access 类型,它将简单地用堆栈中的第一个值填充,正如人们对 cdecl 所期望的那样。

(请原谅任何小错误,这不是我的实际代码。)

#####################################################################
#  Caller
#
#  This pushes the values onto the stack and calls the Ada function
#####################################################################
.global __example_function
.type __example_function, @function
__example_function:
    push 
    push 
    push 
    push 
    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;

另一方面,这工作得很好:

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

在这种最小情况下,这没什么大不了的,但如果我要传递 16 个或更多变量,它就会变得有点繁重。如果您想知道我为什么这样做,它是一个异常处理程序,处理器自动将变量传递到堆栈以显示寄存器状态。

如有任何帮助,我们将不胜感激。

记录版本不起作用,因为记录未存储在堆栈中。相反,4 Unsigned_32 个元素存储在堆栈中。如果您真的想使用一条记录而不是四个单独的无符号整数值,您可以在调用 "accepts_struct" 时将这四个值分配给记录的成员。 Ada 希望堆栈中的第一个条目是一条记录,而不是 unsigned_32。 Ada LRM,第 6.4.1 节指出:

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.

另外,参数的传递方式详见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.

您的编译器似乎试图通过引用而不是复制来传递结构。在 C 中,所有参数都是通过副本传递的。

也许您已经解决了问题,但如果没有,那么您可能还想看看 GCC 提供的 interrupt 函数属性(请参阅 here). I've translated a test of the GCC testsuite which pushes values to the stack (as described in section 6.12 of the Intel SDM) and reads them back in an ISR. The translated Ada version seems to work well. See here for the original C version. See the GCC ChangeLog 了解更多信息。

main.adb

with PR68037_1;

procedure Main is
begin
   PR68037_1.Run;
end Main;

pr68037_1.ads

package PR68037_1 is 
   procedure Run;
end PR68037_1;

pr68037_1.adb

with System.Machine_Code;
with Ada.Assertions;
with Interfaces.C;
with GNAT.OS_Lib;

package body PR68037_1 is

   --  Ada-like re-implementation of
   --     gcc/testsuite/gcc.dg/guality/pr68037-1.c

   subtype uword_t is Interfaces.C.unsigned_long;    --  for x86-64

   ERROR : constant uword_t := 16#1234567_0#;
   IP    : constant uword_t := 16#1234567_1#;
   CS    : constant uword_t := 16#1234567_2#;
   FLAGS : constant uword_t := 16#1234567_3#;
   SP    : constant uword_t := 16#1234567_4#;
   SS    : constant uword_t := 16#1234567_5#;

   type interrupt_frame is
      record
         ip    : uword_t;
         cs    : uword_t;
         flags : uword_t;
         sp    : uword_t;
         ss    : uword_t;
      end record
     with Convention => C;

   procedure fn (frame : interrupt_frame; error : uword_t)
     with Export, Convention => C, Link_Name => "__fn";

   pragma Machine_Attribute (fn, "interrupt");


   --------
   -- fn --
   --------

   procedure fn (frame : interrupt_frame; error : uword_t) is
      use Ada.Assertions;
      use type uword_t;
   begin

      --  Using the assertion function here. In general, be careful when
      --  calling subprograms from an ISR. For now it's OK as we will not
      --  return from the ISR and not continue the execution of an interrupted
      --  program.

      Assert (frame.ip    = IP   , "Mismatch IP");
      Assert (frame.cs    = CS   , "Mismatch CS");      
      Assert (frame.flags = FLAGS, "Mismatch FLAGS");
      Assert (frame.sp    = SP   , "Mismatch SP");
      Assert (frame.ss    = SS   , "Mismatch SS");

      -- At the end of this function IRET will be executed. This will
      -- result in a segmentation fault as the value for EIP is nonsense.
      -- Hence, abort the program before IRET is executed.

      GNAT.OS_Lib.OS_Exit (0);

   end fn;

   ---------
   -- Run --
   ---------

   procedure Run is
      use System.Machine_Code;
      use ASCII;
   begin

      --  Mimic the processor behavior when an ISR is invoked. See also:
      --
      --    Intel (R) 64 and IA-32 Architectures / Software Developer's Manual
      --    Volume 3 (3A, 3B, 3C & 3D) : System Programming Guide
      --    Section 6.12: Exception and Interrupt Handling
      --
      --  Push the data to the stack and jump unconditionally to the
      --  interrupt service routine.

      Asm
        (Template =>
           "push %0" & LF &
           "push %1" & LF &
           "push %2" & LF &
           "push %3" & LF &
           "push %4" & LF &
           "push %5" & LF &
           "jmp __fn",
         Inputs =>
           (uword_t'Asm_Input ("l", SS),
            uword_t'Asm_Input ("l", SP),
            uword_t'Asm_Input ("l", FLAGS),
            uword_t'Asm_Input ("l", CS),
            uword_t'Asm_Input ("l", IP),
            uword_t'Asm_Input ("l", ERROR)),
         Volatile => True);

   end Run;

end PR68037_1;

我在 GNAT CE 2019 中使用编译器选项 -g -mgeneral-regs-only 编译了程序(从 GCC 测试中复制)。请注意,参数 interrupt_frame 将通过引用传递(请参阅 RM B.3 69/2)。