从 Ada 中的地址 0x0 读取

Reading from Address 0x0 in Ada

我 运行 在裸板运行时,从地址零读取数据是我软件中的一个有效用例。但是,运行时将地址 0x0 视为 null,并在使用 -O2 编译时在以下代码中抛出异常。使用 -O1:

编译时代码的行为符合预期
declare
  use System.Storage_Elements;
  type Byte_Array is array (Natural range 0 .. 3) of Interfaces.Unsigned_8;
  bytes : constant Byte_Array with Import, Convention => Ada, Address => To_Address(Integer_Address(16#00000000#));
begin
  -- Copy bytes to some other byte array in memory:
  other_bytes := bytes; -- Throws exception
end;

有什么办法解决这个问题吗?


我的平台详情:


失败详情:

之前的代码在我的运行时中死于 s-memcop.adb 实现的 memcpy 函数中。下面复制了代码,并附有一条报告失败行的注释。我不确定抛出的实际异常。我所能看到的是最后一次机会处理程序被调用,其中包含信息 s-memcop.adb:52,在下面进行了评论。

 40    function memcpy
 41      (Dest : Address; Src : Address; N : size_t) return Address
 42    is
 43       D : IA     := To_IA (Dest);
 44       S : IA     := To_IA (Src);
 45       C : size_t := N;
 46
 47    begin
 48       --  Try to copy per word, if alignment constraints are respected
 49
 50       if ((D or S) and (Word'Alignment - 1)) = 0 then
 51          while C >= Word_Unit loop
 52             To_Word_Ptr (D).all := To_Word_Ptr (S).all; -- Last_Chance_Handler Called here :(
 53             D := D + Word_Unit;
 54             S := S + Word_Unit;
 55             C := C - Word_Unit;
 56          end loop;
 57       end if;
 58
 59       --  Copy the remaining byte per byte
 60
 61       while C > 0 loop
 62          To_Byte_Ptr (D).all := To_Byte_Ptr (S).all;
 63          D := D + Byte_Unit;
 64          S := S + Byte_Unit;
 65          C := C - Byte_Unit;
 66       end loop;
 67
 68       return Dest;
 69    end memcpy;

有没有办法查看实际抛出的异常?

已更新

虽然不是 Cortex-M1,但下面的示例似乎适用于 micro:bit(Cortex-M0,ZFP 运行time)和 STM32F407(SFP 运行time ,相同的结果,未显示)。

main.adb

with System;
with System.Storage_Elements;
with Interfaces;
with Ada.Text_IO;

procedure Main is

   type Byte_Array is array (Natural range 0 .. 3) of Interfaces.Unsigned_8;

   Bytes : Byte_Array with
     Address => System.Storage_Elements.To_Address (16#0#);

   Other_Bytes  : Byte_Array;

begin

   Other_Bytes := Bytes;    --  Line 17

   --  Output via semihosting.
   for I in Other_Bytes'Range loop
      Ada.Text_IO.Put (Other_Bytes (I)'Image);
   end loop;
   Ada.Text_IO.New_Line;    --  Required to flush semihosting output buffer.

   loop
      null;                 --  Line 26
   end loop;

end Main;

调试会话(Cortex-M0,使用pyocd作为服务器)

$ arm-eabi-gdb main
GNU gdb (GDB) 8.3 for GNAT Community 2019 [rev=gdb-8.3-ref-194-g3fc1095]
Copyright (C) 2019 Free Software Foundation, Inc.
[...]
Reading symbols from main...
(gdb) target remote :3333
Remote debugging using :3333
0x0000020a in _start ()
(gdb) load
Loading section .text, size 0x4fc lma 0x0
Loading section .rodata, size 0x20 lma 0x4e8
Loading section .data, size 0x4 lma 0x4fc
Start address 0x20a, load size 1312
Transfer rate: 3 KB/sec, 437 bytes/write.
(gdb) b 26
Breakpoint 1 at 0x14a: file [...]/src/main.adb, line 26.
(gdb) c
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, main () at [...]/src/main.adb:26
26      null;                 --  Line 26
(gdb) p/x Other_Bytes 
 = (0 => 0x88, 0x8, 0x0, 0x20)
(gdb) x/4xb 0
0x0 <__vectors>:    0x88    0x08    0x00    0x20
(gdb) p/x Bytes (0)'Address
 = 0x0
(gdb) p/x Other_Bytes (0)'Address
 = 0x20000848
(gdb)

输出(半主机,运行在单独的终端)

$ nc localhost 4444
 136 8 0 32

System.Null_Address的使用注意事项.

请注意,我之前使用 System.Null_Address 来引用地址 0。在这种情况下(大多数情况下)这会起作用。但是,根据定义,System.Null_Address 并不表示内存位置 0。因此,To_Address (16#0#) 可能确实更安全(另见 ARM 34/2 and ARM 37.c)。


可能确定原因的提示。

根据提供的信息(意外调用 SVCall 异常处理程序和不同的行为取决于优化级别)我会怀疑一些内存损坏(例如因为写入不正确的内存位置;在 C 中经常导致通过悬挂指针;在 Ada 中可能是因为使用 "unsafe" 语言特性,如 Unchecked_AccessUnrestricted_AccessAddress 属性)。

追踪内存损坏可能非常具有挑战性,因为发生损坏的(源代码)位置甚至可能离您实际观察到问题的位置不近。

我要开始调查的第一件事是 SVCall 异常处理程序的调用。我不希望在您的情况下执行 ARM svc 指令。我会尝试的一些步骤(不保证这会让您更接近解决方案):

  • 使用arm-eabi-objdump -d -S反汇编问题附近的相关代码部分(在你的情况下memcpy)并保留它作为参考。
  • 连接 gdb 调试器,加载程序并检查 Other_Bytes := Bytes;memcpy 附近的反汇编是否与 objdump 返回的反汇编相当。使用我自己的例子:
(gdb) info line main.adb:17
Line 17 of "[...]/src/main.adb" starts at address 0xca <_ada_main+10> and ends at 0xe6 <_ada_main+38>.
(gdb) x/14i 0xca
[...]

(gdb) info address memcpy
Symbol "memcpy" is at 0x300 in a file compiled without debugging.
(gdb) x/100i 0x300
[...]
  • 设置选项以显示每行反汇编并在读取内存之前设置断点(即 Other_bytes := Bytes;)。同样,使用我自己的示例:
(gdb) set disassemble-next-line on
(gdb) b 17
  • 启动程序(使用c)并在断点处停止后,再次检查反汇编指令。继续逐步(使用 stepi,然后按 <return> 重复最后一个命令)并监视密切显示的处理器指令。检查它们是否符合预期(objdump 的输出)。我预计,在某个时候,实际和预期的程序流程会开始出现偏差。尝试查看 if/where 这发生在处理器指令级别。

我联系了 AdaCore 并收到了这个问题的解决方案:

If you compile at -O2 or above, then you also need to pass to the compiler the switch -fno-delete-null-pointer-checks because -fdelete-null-pointer-checks is automatically enabled at -O2 for the ARM architecture.

根据docs -fdelete-null-pointer-checks 标志:

Assume that programs cannot safely dereference null pointers, and that no code or data element resides at address zero.

由于我的应用程序不是这样,而且我需要访问地址为零的数据,因此我需要通过向编译器传递 -fno-delete-null-pointer-checks 选项来禁用此开关。

注意: 当我的代码和运行时的优化标志不一致时,我遇到了问题。在这种情况下,使用 -O2-fno-delete-null-pointer-checks 选项编译我的代码和运行时允许我自由地 read/write 地址 0x0.