Reputation: 2786
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
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
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):
memcpy
) using arm-eabi-objdump -d -S
and keep it as reference.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
[...]
Other_bytes := Bytes;
). Again, using my own example:(gdb) set disassemble-next-line on
(gdb) b 17
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