A question about stack alignment in C language

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

A question about stack alignment in C language

Mo Zhou
Hi mentors,

This question tightly associates with my ongoing work for Debian's
BLAS/LAPACK packages, specifically the 32-bit and 64-bit variants.
I encountered a problem that I don't fully understand so I think I
need some help at this point.

Assume we have the following library "libfoo.c":

        #include <stddef.h>
        float sasum64(size_t N, const float *X, size_t incX)
        {
                float asum = 0.;
                for (size_t i = 0; i < N; i++) {
                        asum += (X[i*incX] > 0.) ? X[i*incX] : -X[i*incX];
                }
                return asum;
        }
        float sasum32(int N, const float *X, int incX)
        {
                float asum = 0.;
                for (int i = 0; i < N; i++) {
                        asum += (X[i*incX] > 0.) ? X[i*incX] : -X[i*incX];
                }
                return asum;
        }

compiled as libfoo.so: gcc -shared -fPIC libfoo.c -o libfoo.so
And we have the following application "app.c" which **deliberately**
misuse the index type:

        #include <stdio.h>
        #include <stddef.h>
        float sasum64(int N, const float *X, int incX);
        float sasum32(size_t N, const float *X, size_t incX);

        int main(void)
        {
                float a[] = {1., 2., -3.};
                printf("%f, %f\n", sasum32(3, a, 1), sasum64(3, a, 1));
                return 0;
        }

Then we compile and run the program:

        gcc app.c -fPIC -lfoo -L.
        LD_LIBRARY_PATH=. ./a.out                                                            2:00:56
    >>> 6.000000, 6.000000

My questions are:

        1. Why doesn't the application segfault, since it has already
        misused the index (N and incX) type?

        2. Did we avoid SIGSEGV because the arguments used to call
        sasum32 or sasum64 are aligned in 64-bits? But that's still
        strange due to little-endianess...

        3. How can I make the app.c segfault?

Thanks in advance :-)

Reply | Threaded
Open this post in threaded view
|

Re: A question about stack alignment in C language

Matthew Fernandez

> On Apr 5, 2019, at 19:08, Mo Zhou <[hidden email]> wrote:
>
> Hi mentors,
>
> This question tightly associates with my ongoing work for Debian's
> BLAS/LAPACK packages, specifically the 32-bit and 64-bit variants.
> I encountered a problem that I don't fully understand so I think I
> need some help at this point.
>
> Assume we have the following library "libfoo.c":
>
> #include <stddef.h>
> float sasum64(size_t N, const float *X, size_t incX)
> {
> float asum = 0.;
> for (size_t i = 0; i < N; i++) {
> asum += (X[i*incX] > 0.) ? X[i*incX] : -X[i*incX];
> }
> return asum;
> }
> float sasum32(int N, const float *X, int incX)
> {
> float asum = 0.;
> for (int i = 0; i < N; i++) {
> asum += (X[i*incX] > 0.) ? X[i*incX] : -X[i*incX];
> }
> return asum;
> }
>
> compiled as libfoo.so: gcc -shared -fPIC libfoo.c -o libfoo.so
> And we have the following application "app.c" which **deliberately**
> misuse the index type:
>
> #include <stdio.h>
> #include <stddef.h>
> float sasum64(int N, const float *X, int incX);
> float sasum32(size_t N, const float *X, size_t incX);
>
> int main(void)
> {
> float a[] = {1., 2., -3.};
> printf("%f, %f\n", sasum32(3, a, 1), sasum64(3, a, 1));
> return 0;
> }
>
> Then we compile and run the program:
>
> gcc app.c -fPIC -lfoo -L.
> LD_LIBRARY_PATH=. ./a.out                                                            2:00:56
>>>> 6.000000, 6.000000
>
> My questions are:
>
> 1. Why doesn't the application segfault, since it has already
> misused the index (N and incX) type?
>
> 2. Did we avoid SIGSEGV because the arguments used to call
> sasum32 or sasum64 are aligned in 64-bits? But that's still
> strange due to little-endianess...
>
> 3. How can I make the app.c segfault?
>
> Thanks in advance :-)
>

I do not know why this question was addressed to Debian and Gentoo as it seems to have nothing specific to do with either, but let me attempt a response. With nothing further to go on, I am taking a guess that your platform is x86-64. The 32-bit values passed to the mis-prototyped sasum64 as N and incX will be zero extended to 64-bit values as per the ABI. I know neither why nor where you expect this program to segfault, so unfortunately I can’t comment further. You might want to try Stack Overflow for something like this.
Reply | Threaded
Open this post in threaded view
|

Re: A question about stack alignment in C language

Adam Borowski-3
In reply to this post by Mo Zhou
On Sat, Apr 06, 2019 at 02:08:17AM +0000, Mo Zhou wrote:
> Hi mentors,

Unlike Matthew, I don't consider code questions to be completely off topic.
And I hate Stack Overflow.  So let's continue to the fun. :)

> float sasum64(size_t N, const float *X, size_t incX)
> float sasum32(int N, const float *X, int incX)

> compiled as libfoo.so: gcc -shared -fPIC libfoo.c -o libfoo.so

> float sasum64(int N, const float *X, int incX);
> float sasum32(size_t N, const float *X, size_t incX);

> printf("%f, %f\n", sasum32(3, a, 1), sasum64(3, a, 1));

> My questions are:
>
> 1. Why doesn't the application segfault, since it has already
> misused the index (N and incX) type?

On 32-bit archs, all these types have same widths.

On 64-bit, you have undefined behaviour portability-wise as registers that
are being used to pass these values might or might not have their upper bits
cleared.

On arm64, this is explicitly the case for a store to lower half (w0 of x0):

# w0 through w30 - for 32-bit-wide access (same registers - upper 32 bits
# are either cleared on load or sign-extended (set to the value of the most
# significant bit of the loaded value)).

On amd64, this is also the case for stores to lower 32-bits of a 64-bit
register (eax of rax).  That's very surprising for those of us who did most
of their asm programming in 16-bit days: a store to al does _not_ clear the
upper half of ax.

I have no idea about other archs -- they very well could behave like 8086.
But the problem is, everyone among us has an amd64 box and a bunch of
arm64/armhf boards, but a trip to a porterbox for fringe archs tends to be
something you do only upon suspecting a bug.

Someone more knowledgeable may fill us in.

> 2. Did we avoid SIGSEGV because the arguments used to call
> sasum32 or sasum64 are aligned in 64-bits? But that's still
> strange due to little-endianess...

Registers don't care about endianness nor alignment.  Any modern ABI passes
such arguments in the registers rather than stack, at most spilling once you
have too many.

On the stack, all 64-bit archs I'm aware of require 64-bit values to be
aligned; whether passing two 32-bit values side-to-side requires one or two
words depends on the ABI (stack is different from structs).  In no case you
mention there's two 32-bit args.

> 3. How can I make the app.c segfault?

Possibly use a musty old arch (not sure about that)?  Have more args that
include two 32-bit ones?  Use negative values (size_t vs int, sign
extension)?

(And thanks for the question: TIL that eax -> rax zero/sign extends while
al -> ax did not.)


Meow!
--
⢀⣴⠾⠻⢶⣦⠀ It's all millenials' fault.  I've discussed this with a cow
⣾⠁⢠⠒⠀⣿⡁ orker, we just couldn't agree on a definition -- I say
⢿⡄⠘⠷⠚⠋⠀ millenials start at 1979, he claims it's 1985+.  But it's
⠈⠳⣄⠀⠀⠀⠀ certain: smelly millenials are the cause of all problems.