4

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

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

当我这样做时,调试被调用者显示传递的记录包含未初始化的数据。尽管有导出指令,但 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 $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;

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

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

2 回答 2

4

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

对于 parameter_association 的评估:首先评估实际参数。对于访问参数,详细说明了 access_definition,它创建了匿名访问类型。对于通过引用传递的(任何模式的)参数(参见 6.2),将评估实际参数到形式参数的名义子类型的视图转换,并且形式参数表示该转换。对于通过复制传递的 in 或 in out 参数(参见 6.2),会创建形参对象,并将实参的值转换为形参的名义子类型并分配给形参。

此外,第 6.2 节描述了参数的传递方式:

6.2 形式参数模式

parameter_specification 声明模式 in、in out 或 out 的形式参数。静态语义

参数通过复制或引用传递。复制传递参数时,形参表示与实参不同的对象,两者之间的任何信息传递只发生在子程序执行前后。当参数通过引用传递时,形式参数表示(视图)由实际参数表示的对象;形参的读取和更新直接引用实参对象。

如果一个类型是一个基本类型,或者它是一个私有类型的后代,而该私有类型的完整类型是一个拷贝类型,那么它就是一个拷贝类型。复制类型的参数通过复制传递,除非形式参数显式别名。

如果类型是以下之一的后代,则该类型是按引用类型:

标记类型;

任务或受保护类型;

明确限制的记录类型;

具有引用类型的子组件的复合类型;

一个私有类型,其完整类型是按引用类型。

引用类型的参数通过引用传递,任何类型的显式别名参数也是如此。引用类型的每个值都有一个关联对象。对于带括号的表达式、qualified_expression 或 type_conversion,此对象是与操作数关联的对象。对于 conditional_expression,此对象是与评估的dependent_expression 关联的对象。

对于其他参数,不指定参数是复制传递还是引用传递。

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

于 2019-10-16T18:30:05.517 回答
1

也许你已经解决了这个问题,但如果没有,那么你可能还想看看interruptGCC 提供的函数属性(见这里)。我已经翻译了 GCC 测试套件的测试,它将值推送到堆栈(如英特尔 SDM的第 6.12 节所述)并在 ISR 中读回它们。翻译后的 Ada 版本似乎运行良好。有关原始 C 版本,请参见此处。有关一些附加信息,请参阅 GCC变更日志。

主文件

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;

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

于 2019-10-23T20:06:37.270 回答