GCC 7.1 - entering supervisor mode

C and PASCAL (or any other high-level languages) in here please

Moderators: exxos, simonsunnyboy, Mug UK, Zorro 2, Moderator Team

chicane
Atari freak
Atari freak
Posts: 69
Joined: Mon Jul 02, 2012 11:25 am
Location: Leeds, UK

GCC 7.1 - entering supervisor mode

Postby chicane » Mon Sep 18, 2017 10:18 am

I'm currently in the process of adapting the ST Pole Position code so that it works with GCC 7.1. I've now managed to get it to compile, but upon running it, the executable bombed out with what I think are 8 bombs - I can't see them for long enough to count them! 8 bombs appears to translate to a privilege violation, so I started looking at the area of my code that moves into supervisor mode.

It appears that the call to Super() (to move into supervisor mode) executes correctly, but the executable then bombs out on executing the inline assembly to disable all interrupts. It's as if the machine hasn't entered supervisor mode at all.

In order to try and get to the bottom of the issue, I've created a minimal test case based upon the ctest.c in the bigbrownbuild repo:

Code: Select all

//======================================================================================================================
//  BrownELF GCC example: C++ startup/shutdown tests
//======================================================================================================================

// ---------------------------------------------------------------------------------------------------------------------
//  system headers

#include <mint/sysbind.h>
#include <stdio.h>
#include <stdlib.h>     // for atexit()
#include <stdarg.h>     // for printf va_args etc.

// force GCC to keep functions that look like they might be dead-stripped due to non-use
#define USED __attribute__((used))

int main(int argc, char ** argv)
{
    Super(0);

    __asm__ __volatile__
    (
     "move.w #0x2700,%%sr;"
     :  :  : "cc"
    );

    while (1==1) {}
}


What I'd expect this code to do is enter supervisor mode, disable interrupts and then hang. Instead, it bombs out on the inline assembly with what appears to be 8 bombs. This code (or at least code very similar to it) works fine under Vincent's GCC. I'm wondering if I'm doing something wrong that Vincent's compiler somehow allows but GCC 7.1 is less forgiving of.

I'd be extremely grateful if somebody could help me get a better understanding of what's going wrong here! Thanks in advance for any responses.

User avatar
dml
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 3472
Joined: Sat Jun 30, 2012 9:33 am

Re: GCC 7.1 - entering supervisor mode

Postby dml » Mon Sep 18, 2017 11:26 am

Hi!

While I can't say conclusively what's happening here (because I normally provide a new SSP stack when using Super()), I can say that I had similar problems with this function - and the problems have much to do with the method of changing mode versus the compiler's knowledge of what just happened under its feet. Particularly with changes to the stack, what was on it before the switch, access to it after the switch - but potentially other state as well.

Some other things to be wary of:

1) The newer compilers are a bit more aggressive at high-mid level optimisation and any undefined behaviour (which may include stuff going on inside Super) is more likely to cause invalid code to be produced and actual crashes.

2) The macros used to implement things like Super() via assembly code may not be properly constrained, causing trashed registers etc. which the compiler isn't aware of (mint headers need inspection to check this properly).

In any case you can almost certainly solve the problem by using Supexec(_MyEntrypoint) instead because the context save/restore becomes part of the trap itself, and not left to the compiler to guess at (which is probably making incorrect assumptions about machine state across the switch). This works without the same risks. You just need to be aware that any args need passed via globals or via the trap, since the stack will be swapped and local variables/args disappear (or at least go out of reach) as a result.

I use something like the following pattern:

Code: Select all

#include <mint/sysbind.h>

void SYS_SuperStart()
{
   // main program to run in supervisor mode
}   

int main(int argc, char **argv)
{
   Supexec(&SYS_SuperStart, 0,0,0,0,0);
   return 0;
}


If you want to replace and align the supervisor stack as part of the process you can add an extra level in-between to do this. e.g.

Code: Select all

#define S_SUPER_SSP(_sz_) \
static const int ssp_size = _sz_>>2; \
u32 s_new_ssp[ssp_size]; \
u32 s_old_ssp; \
u32 s_old_usp;


Code: Select all


S_SUPER_SSP(8192)

void SYS_SuperStart()                     \
{                                    \
   __asm__ __volatile__                  \
   (                                 \
      "                              \
      move.l      %0,%%a0;               \
      move.l      %%a0,%%d0;               \
      subq.l      #4,%%d0;               \
      and.w      #-16,%%d0;               \
      move.l      %%d0,%%a0;               \
      move.l      %%sp,-(%%a0);            \
      move.l      %%usp,%%a1;               \
      move.l      %%a1,-(%%a0);            \
      move.l      %%a0,%%sp;               \
      movem.l      %%d1-%%d7/%%a2-%%a6,-(%%sp);   \
      jsr         (%2);                     \
      movem.l      (%%sp)+,%%d1-%%d7/%%a2-%%a6;   \
      movem.l      %%d1-%%d7/%%a2-%%a6,-(%%sp);   \
      jsr         (%1);                     \
      movem.l      (%%sp)+,%%d1-%%d7/%%a2-%%a6;   \
      movem.l      %%d1-%%d7/%%a2-%%a6,-(%%sp);   \
      jsr         (%3);                     \
      movem.l      (%%sp)+,%%d1-%%d7/%%a2-%%a6;   \
      move.l      (%%sp)+,%%a0;            \
      move.l      %%a0,%%usp;               \
      move.l      (%%sp)+,%%sp;            \
      "                              \
      :                              \
      : "p"(&s_new_ssp[ssp_size-16]), "a"(&SYS_EntryPoint), "a"(&SYS_EntryPointPre), "a"(&SYS_EntryPointPost) \
      : "%%d0", "%%a0", "%%a1", "cc"         \
   );                                 \
}                                    \


This is a fragment of AGT's startup sequence - it will replace the stack with C-array 's_new_ssp', align it to 16 bytes, then call SYS_EntryPointPre(), SYS_EntryPoint(), SYS_EntryPointPost() in that order. It also saves/restores SSP/USP properly for exit.

Pay extra attention to the MiNT includes used to specify the Super/Supexec macros and the number of args they expect - the definitions of these are not all the same!

chicane
Atari freak
Atari freak
Posts: 69
Joined: Mon Jul 02, 2012 11:25 am
Location: Leeds, UK

Re: GCC 7.1 - entering supervisor mode

Postby chicane » Mon Sep 18, 2017 1:00 pm

Many thanks Doug for the usual comprehensive and swift reply! I've switched to using Supexec as suggested and that seems to do the trick.

I'm really impressed with the performance improvements in this version - the -flto support in particular makes a big difference to runtime performance! Thanks to you and everybody else involved for making this GCC 7.1 release a reality.

ThorstenOtto
Captain Atari
Captain Atari
Posts: 163
Joined: Sun Aug 03, 2014 5:54 pm

Re: GCC 7.1 - entering supervisor mode

Postby ThorstenOtto » Mon Sep 18, 2017 2:00 pm

dml wrote: (because I normally provide a new SSP stack when using Super())


That's usually a bad idea. If you compile your code with -fomit-frame-pointer, then local variables that are not in registers are accessed through xx(SP). gcc will not recognize that you changed the SP, and therefore access the wrong memory when trying to load/store them.

dml wrote: Supexec(&SYS_SuperStart, 0,0,0,0,0);


That code is also bogus. Taking the address of a function with an extra address operator is undefined. The Supexec() macro in mintlib headers only takes 1 parameter, so that code won't even compile. Beside that, passing extra parameters to the function called by Supexec only works in MiNT.

chicane wrote:I'd be extremely grateful if somebody could help me get a better understanding of what's going wrong here! Thanks in advance for any responses.


The easiest way, if you have a small example like yours that can reproduce the bug, is to compile it with -S and look at the generated assembler code.

User avatar
dml
Fuji Shaped Bastard
Fuji Shaped Bastard
Posts: 3472
Joined: Sat Jun 30, 2012 9:33 am

Re: GCC 7.1 - entering supervisor mode

Postby dml » Mon Sep 18, 2017 2:14 pm

ThorstenOtto wrote:That's usually a bad idea. If you compile your code with -fomit-frame-pointer, then local variables that are not in registers are accessed through xx(SP). gcc will not recognize that you changed the SP, and therefore access the wrong memory when trying to load/store them.


That's what I was explaining above.

dml wrote:
That code is also bogus. Taking the address of a function with an extra address operator is undefined. The Supexec() macro in mintlib headers only takes 1 parameter, so that code won't even compile. Beside that, passing extra parameters to the function called by Supexec only works in MiNT.


Depends on which header defines the macro. mint/sysbind.h defines it this way, and accepts long, not a function pointer.

#define Supexec(a,b,c,d,e,f) (long)trap_14_wllllll(0x26,(long)a,(long)b,(long)c,(long)d,(long)e,(long)f)

The additional arguments are redundant on TOS.

osbind.h defines Supexec as a single argument, accepting 'funcptr'. This was my second point above, although omitting much detail.


Social Media

     

Return to “C / PASCAL etc.”

Who is online

Users browsing this forum: No registered users and 1 guest