【问题标题】:Passing struct/record from assembler to Ada将结构/记录从汇编程序传递给 Ada
【发布时间】:2019-10-15 23:41:24
【问题描述】:

我正在尝试将一个结构从 (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 $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 个或更多变量,它就会变得有点繁重。如果您想知道我为什么要这样做,它是一个异常处理程序,处理器会自动将变量传递到堆栈以显示寄存器状态。

我们将不胜感激。

【问题讨论】:

  • 只是出于好奇:您指的是ARMv7-M reference manual 的B1.5.6 节中描述的处理器行为吗?您是否尝试在 Ada 中(重新)创建 HardFault 异常处理程序(例如 this one)?
  • 我试图实现更接近这里描述的技术:wiki.osdev.org/Interrupt_Service_Routines。您可以在大多数面向 x86 的操作系统中找到类似的实现。

标签: ada gnat ada2012


【解决方案1】:

记录版本不起作用,因为记录未存储在堆栈中。相反,堆栈中存储了 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 声明 mode in 的形式参数,in 出,或出。静态语义

通过复制或引用传递参数。当一个参数 通过复制传递,形式参数表示一个单独的对象 实际参数,以及两者之间的任何信息传输 仅在执行子程序之前和之后发生。当一个 参数通过引用传递,形参表示(一个视图 of) 实参表示的对象;阅读和更新 形参直接引用实参对象。

如果一个类型是一个基本类型,或者它是一个 私有类型的后代,其完整类型是复制类型。一种 复制类型的参数通过复制传递,除非正式 参数显式别名。

一个类型是一个引用类型,如果它是其中之一的后代 以下:

标记类型;

一个任务或受保护的类型;

明确限制的记录类型;

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

一个私有类型,它的完整类型是一个引用类型。

按引用类型的参数通过引用传递,就像一个 任何类型的显式别名参数。引用的每个值 type 有一个关联的对象。对于带括号的表达式, qualified_expression 或 type_conversion,此对象是 与操作数相关联。对于条件表达式,此对象 是与评估的dependent_expression 关联的那个。

对于其他参数,不指定参数是否为 通过复制或引用传递。

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

【讨论】:

  • 我想知道C_Pass_By_Copy (ARM B.3(60.13) 是否可以解决问题。我无法让它工作,但我并没有真正投入到获得解决方案上! (如果 OP 提供了更接近可行的东西,可能会更进一步)。 flyx’s paper 应该增加一些理解。
  • @Jim Rogers 感谢您在上面的解释!你完全正确。我的误解来自我期望使用C 调用约定声明要导出的过程会导致它以与C 处理结构参数相同的方式处理记录参数。我最终采用了类似于您所描述的解决方案。如果您不介意,我将问题多留一天,如果没有其他人可以解决问题,我将接受您的答案作为正确答案!感谢您的帮助。
  • @SimonWright 我已经尝试过使用C_Pass_By_Copy 约定,但它也没有达到预期的结果。你是指我没有提供完整的来源吗?我只是试图将其缩减为仅相关代码来说明我的问题。感谢您的链接。它似乎描述了使用该约定将记录作为参数传递一个C函数,但不是相反。
  • 我认为您对编译指示是正确的。 (完整源代码):我花了一些时间尝试在您的示例周围包装代码,但只能获得 SEGV,并且不确定这是我的代码还是事情的本来面目!我的经验是 Ada ARM 硬故障处理程序,specbody;但是您可以在那里构造一个指向数据所在堆栈帧的指针,并将其作为对记录的引用传递给 Ada。
  • @SimonWright 这也是个好主意!我可以看到如何使您描述的方法适应我自己的用途。我必须承认,我对 ARM 没有太多经验。我最终制作了一个包装函数,它单独获取所有参数并将它们添加到记录中以传递给其他特定的异常处理程序。如果您好奇,这是为处理器异常创建中断服务例程,即 IDT 中的前 31 个条目。感谢您抽出宝贵时间回复!
【解决方案2】:

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

ma​​in.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)。

【讨论】:

  • 这非常有趣!感谢您指出此 pragma 的存在。我会尽快检查一下,看起来很有希望。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-16
  • 2015-01-13
  • 1970-01-01
  • 1970-01-01
  • 2018-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多