dinkelk
dinkelk

Reputation: 2786

Reading from Address 0x0 in Ada

I am running on a bare-board runtime, and reading data from address zero is a valid use case in my software. However, the runtime treats address 0x0 as null, and throws an exception in the following code when compiled with -O2. The code behaves as expected when compiled with -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;

Is there any way around this problem?


Details on my platform:


Details on the failure:

The previous code dies in my runtime in the memcpy function implemented by s-memcop.adb. The code is copied below, with a comment reporting the line which fails. I am unsure of the actual exception that gets thrown. All I can see is the the last chance handler gets called with the information s-memcop.adb:52, which is commented below.

 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;

Is there a way to see the actual exception being thrown?

Upvotes: 2

Views: 813

Answers (2)

dinkelk
dinkelk

Reputation: 2786

I got in contact with AdaCore and received a solution to this problem:

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.

According to the docs the -fdelete-null-pointer-checks flag:

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

Since this is NOT true of my application, and I need to access data at address zero, I needed to disable this switch by passing the compiler the -fno-delete-null-pointer-checks option.

Note: I had issues when the optimization flags of my code and the runtime were not consistent. In this case, compiling my code AND the runtime with the -O2 and -fno-delete-null-pointer-checks options allowed me to freely read/write address 0x0.

Upvotes: 6

DeeDee
DeeDee

Reputation: 5941

Updated

Although not a Cortex-M1, the example below seems to work on both the micro:bit (Cortex-M0, ZFP runtime) and the STM32F407 (SFP runtime, same result, not shown).

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;

debug session (Cortex-M0, using pyocd as server)

$ 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 
$1 = (0 => 0x88, 0x8, 0x0, 0x20)
(gdb) x/4xb 0
0x0 <__vectors>:    0x88    0x08    0x00    0x20
(gdb) p/x Bytes (0)'Address
$2 = 0x0
(gdb) p/x Other_Bytes (0)'Address
$3 = 0x20000848
(gdb)

output (semihosting, run in separate terminal)

$ nc localhost 4444
 136 8 0 32

Note on the usage of System.Null_Address.

Note that I previously used System.Null_Address to reference address 0. In this case (and most of the time) this will work. However, System.Null_Address does not represent memory location 0 by definition. Hence, To_Address (16#0#) might indeed be more safe (see also ARM 34/2 and ARM 37.c).


Hints that might identify cause.

Based on the provided information (unexpected invocation of the SVCall exception handler and different behavior depending on optimization level) I would suspect some memory corruption (e.g. because of writes to incorrect memory locations; in C often caused by dangling pointers; in Ada might be because of using "unsafe" language features like Unchecked_Access, Unrestricted_Access and the Address attribute).

Tracking down memory corruption can be very challenging as the (source code) location where the damage is done may not even be close to where you actually observe the problem.

The first thing I would start to investigate is the invocation of the SVCall exception handler. I would not expect an ARM svc instruction to be executed in your case. Some steps I would try (no guarantee this will bring you closer to the solution):

  • Disassemble parts of the relevant code around near the problem (in your case memcpy) using arm-eabi-objdump -d -S and keep it as reference.
  • Hook up the gdb debugger, load the program and inspect if the disassembly near Other_Bytes := Bytes; and that of memcpy is comparable with what objdump returned. Using my own example:
(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
[...]

and

(gdb) info address memcpy
Symbol "memcpy" is at 0x300 in a file compiled without debugging.
(gdb) x/100i 0x300
[...]
  • Set option to show disassembly per line and set a breakpoint just before reading the memory (i.e. Other_bytes := Bytes;). Again, using my own example:
(gdb) set disassemble-next-line on
(gdb) b 17
  • Start the program (using c) and once halted at the breakpoint, inspect the disassembled instructions again. Continue step-by-step (using stepi and then subsequently pressing <return> to repeat the last command) and monitor the processor instructions shown closely. Check if they are as expected (output of objdump). I expect that, at some point, the actual and expected program flow starts to deviate. Try to see if/where this happens at the level of processor instructions.

Upvotes: 3

Related Questions