Recent

Author Topic: stack check changes registers  (Read 3877 times)

mikerabat

  • New Member
  • *
  • Posts: 39
stack check changes registers
« on: September 11, 2018, 03:38:30 pm »
Just curious:

I needed to define {$S-} in all my 64bit assembler files since I reference the input paramters as
registers.

Linux ABI declares the parameters as: RDI, RSI, RDX, RCX, R8, R9, XMM0–7
Windows ABI as: RCX, RDX, R8, R9

I actually recognized that if the Stack check parameter is enabled in the project setting the
internal "fpc_stackcheck" function (which is called directly before the actuall function call)
changes the registers and as far as I can see FPC assumes the parameters on the stack and
references them from there (which is not often an option for assembler...)

so... 1.) Am I right in my assumption
2.) if so isn't that a compiler bug?

kind regards
   Mike


Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: stack check changes registers
« Reply #1 on: September 11, 2018, 06:52:12 pm »
[$S-/+} is basically for Pascal code and NOT for inline assembler code.
For pure inline assembler routines you can add the nostackframe modifier and set up stack requirements as you need them by hand. The nostackframe modifier doesn't work for Pascal code or mixed code.
If you write mixed mode routines (containing pascal code, not constructs, as well as assembler blocks) you can not assume anything about the registers in {$S-/+} because the compiler needs to insert code, as you noted.
If you need ABI compliant access to registers in pure inline assembler code, use the nostackframe modifier and set up the stack by hand.
This is documented, afaik. I hope this helps.
See http://wiki.freepascal.org/User_Changes_3.0#nostackframe_forbidden_for_Pascal_subroutines
[edit]
It is documented in the fpc 3.0.0 release notes, but the documentation currently lacks a bit, so I submitted a bug against documentation.
https://www.freepascal.org/docs-html/current/ref/refsu78.html#x196-21800014.10.9
https://bugs.freepascal.org/view.php?id=34259
« Last Edit: September 11, 2018, 07:29:27 pm by Thaddy »
Specialize a type, not a var.

mikerabat

  • New Member
  • *
  • Posts: 39
Re: stack check changes registers
« Reply #2 on: September 12, 2018, 09:25:43 am »
Actually I want stack frames - also most of my routines have more than 4 - 6 parameters so I guess it's a necessity....

Anyway this is a WTF moment for me... Why would I have to setup a stack myself when there is a
dcumented ABI for function calls that says in x64 registers are used to push parameters to a function?
That's something one has to rely on....

So... why is that and can I somehow force FPC to ensure that the ABI is ensured? (it can put the first
few params on the stack too - I don't care but according to the x64 ABI the registers are filled)

kind regards
   Mike

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: stack check changes registers
« Reply #3 on: September 12, 2018, 03:09:33 pm »
If and when in S- state, the stack is setup in an ABI platform compliant way on ALL patforms, but the calling convention can be changed through a modifier (except win64 which is always the same whatever, unless you setup yourself) , so  then what's your  problem?
I don't get that.... You have to be more precise in your question. For example what calling convention do you use or expect.
Mind you: if you are using assembler, then my previous advice is also an option.
If you are a beginner in assembler that is not an option.

E.g.: win32 defaults to stdcall for the winapi, but Freepascal defaults to fastcall (borland style fastcall:eax,edx,ecx,stack) unless you use the stdcall modifier.
And loads of bindings expect cdecl as calling convention, even on windows.
and Windows ABI as: RCX, RDX, R8, R9?..... Double check that. https://docs.microsoft.com/en-us/cpp/build/overview-of-x64-calling-conventions?view=vs-2017
« Last Edit: September 12, 2018, 03:26:58 pm by Thaddy »
Specialize a type, not a var.

mikerabat

  • New Member
  • *
  • Posts: 39
Re: stack check changes registers
« Reply #4 on: September 12, 2018, 04:33:52 pm »
Actually I know the calling conventions and according to the definition it's clear that on x64 unix the registers are filled with the values. The thing is that if stack checking is enabled {$S+} or enabled in the project
settings the value of the registers is not reliable so I think it's a clear violation of the ABI.

Guess it's not a problem in pure pascal routines since they put all the parameters on the stack (even the first 4-6 ones) and reference them but if you rely in the assembler routines on values passed
by registers as promised by the x64 calling convention and you don't have them it's bad...

If S- does the trick then I can document that but I'm not sure if it's 100% reliable.

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: stack check changes registers
« Reply #5 on: September 12, 2018, 04:49:15 pm »
Then the answer is rather simple:
- stack checking indeed adds code, but it is meant for Pascal code, not for assembler.
- When you choose to use inline assembler you loose this functionality. YOU ARE ON YOUR OWN.
- You can enable or disable stack checking in a per method/procedure/function basis with {$PUSH}{$S+/-}<your procedure or function>{$POP}
- There is no way any compiler in any language can check the stack - in compiled code! - without code modifications: {$S+} owns the stack.
- Usually there is no need for inline assembler.. always profile: the compiler has a better way of scheduling for the CPU caches for example. A trap many programmers fail to avoid.

The older I get, the less I use pure assembler, merely because of the complexity of cache lines.

Profile.

And the short answer is simply {$S+/-} is useless for assembler routines, which you could have guessed anyway: black can't be white.
Google on "stack canary" and you know why.
[edit] wait I did that for you: https://en.wikipedia.org/wiki/Buffer_overflow_protection
« Last Edit: September 12, 2018, 04:54:23 pm by Thaddy »
Specialize a type, not a var.

mikerabat

  • New Member
  • *
  • Posts: 39
Re: stack check changes registers
« Reply #6 on: September 13, 2018, 08:54:33 am »
Thanks for the answers.

I think we are talking about a few differen things here...
What I'm complaining about is not what the compiler does with the stack - the compiler can add
to stack what he wants as long as the reference to the variables (params and local variables)
is correct I don't care.
What I'm complaining about is that the according to the x64 ABI registers take the first 4 to 6 (unix)
params and that these values are actually changed to garbage.
That is what may not happen.

kind regards
  Mike

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: stack check changes registers
« Reply #7 on: September 13, 2018, 09:11:31 am »
Ah, indeed. That makes it clear. It is not about the stack, but about volatile registers.
In general it is the responsibility of the programmer to preserve any registers marked as volatile for the ABI when using inline assembler.
Any other register can be used at will.
Note you can not make any assumptions about their value: the pascal code will try to use as many registers as possible.
But the compiler will handle all volatile registers in an ABI compliant way.
If you can provide an example that proves otherwise there is indeed a bug, but I checked some platforms and windows32/64 linux64 intel and armhf-linux all work as advertised.
« Last Edit: September 13, 2018, 09:17:24 am by Thaddy »
Specialize a type, not a var.

mikerabat

  • New Member
  • *
  • Posts: 39
Re: stack check changes registers
« Reply #8 on: September 13, 2018, 10:58:05 am »
Hm... I tried to create a small project that would do something similar than my bigger library...
but I couldn't get the compiler to emit the fpc_stackcheck call just before the call to the function call
and the disassembly looks nice. It only hurts if the fpc_stackcheck is inserted (in my lib)....


It would be something like this (2 units:)

Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. uses Unit1;
  4.  
  5. const cNumElem = 10; // multiple of 2!
  6.  
  7. var pMem1, pMem2 : PDouble;
  8.     p1, p2 : PDouble;
  9.     i : integer;
  10.  
  11. begin
  12.      getMem( pMem1, cNumElem*sizeof(double));
  13.      getMem( pMem2, cNumElem*sizeof(double));
  14.  
  15.      p1 := pMem1;
  16.      p2 := pMem2;
  17.  
  18.      for i := 0 to cNumElem - 1 do
  19.      begin
  20.           p1^ := i + 1;
  21.           p2^ := i + 2;
  22.           inc(p1);
  23.           inc(p2);
  24.      end;
  25.  
  26.  
  27.      TestASM1(pMem1, pMem2, cNumElem, 0, 1, 2);
  28.  
  29.      p1 := pMem1;
  30.      p2 := pMem2;
  31.  
  32.      for i := 0 to cNumElem - 1 do
  33.      begin
  34.           Writeln(p1^, '   ', p2^ );
  35.           inc(p1);
  36.           inc(p2);
  37.      end;
  38.  
  39.      readln;
  40.  
  41.      FreeMem(pMem1);
  42.      FreeMem(pMem2);
  43. end.      
             

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. {$S+}
  6.  
  7. type
  8.   TTest = procedure (aValue1 : PDouble; aValue2 : PDouble; numVal : integer; dummy1, dummy2, dummy3 : integer);
  9.  
  10.  
  11. procedure TestASM1(aValue1 : PDouble; aValue2 : PDouble; numVal : integer; dummy1, dummy2, dummy3 : integer);
  12.  
  13.  
  14. implementation
  15.  
  16. {$ASMMODE INTEL}
  17.  
  18. var aTestASM : TTest;
  19.  
  20. procedure TestASM1(aValue1 : PDouble; aValue2 : PDouble; numVal : integer; dummy1, dummy2, dummy3 : integer);
  21. begin
  22.      aTestASM(aValue1, aValue2, numVal, dummy1, dummy2, dummy3);
  23. end;
  24.  
  25. procedure TestASM(aValue1 : PDouble; aValue2 : PDouble; numVal : integer; dummy1, dummy2, dummy3 : integer);
  26. begin
  27. asm
  28.    // x64
  29.    // assumed rcx = aValue1, rdx = aValue2, r8 = numVal
  30.  
  31.    // some crazy operations + write back to the memory
  32.    @@loop:
  33.       movupd xmm0, [rcx];
  34.       movupd xmm1, [rdx];
  35.  
  36.       mulpd xmm0, xmm1;
  37.  
  38.       movupd [rcx], xmm0;
  39.       subpd xmm0, xmm1;
  40.       movupd [rdx], xmm1;
  41.    sub r8, 2;
  42.    jnz @@loop;
  43. end;
  44. end;
  45.  
  46. initialization
  47.    aTestASM := @TestASM;
  48.  
  49. end.      

If one manages to get the stack checking working you would see the problem...


Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: stack check changes registers
« Reply #9 on: September 13, 2018, 01:49:32 pm »
Hm... I tried to create a small project that would do something similar
Well, that's about what I did.
Can it be that your code mixes FPU code with SSEx code? because some of those registers are aliased.
Fpc uses x87 FPU code by default - but not on windows64 - , unless you explicitly tell it to use SSE and/or vector instructions (-Cf option. e.g. -CfSSE3 and the likes)
It may be that your problem goes away that way. I also believe (may be wrong, but quite sure) that the compiler does not insert stack checks for assembler routines.
Specialize a type, not a var.

 

TinyPortal © 2005-2018