Quantcast
Channel: Active questions tagged gcc - Stack Overflow
Viewing all articles
Browse latest Browse all 22016

Different compiler behaviors for uninitialized data on the stack

$
0
0

I have the following program to test the outcome of uninitialized pointers by different compilers

#include<stdio.h>
#include<stdlib.h>

typedef struct intNode
{
    int intval;
    struct intNode* link;
}* TOKEN;

TOKEN talloc()
{
    TOKEN tok = (TOKEN)malloc(sizeof(struct intNode));
    tok->intval = 0;
    tok->link = NULL;
    return tok;
}


TOKEN makefloat(TOKEN tok)
{
    TOKEN result;
    if (tok->intval > 10)
        result = tok;
    else
    {
        TOKEN result = talloc();
        result->intval = -10;
    }
    return result;
}

void printexpr(TOKEN tok)
{
    printf("tok: %p, tok->link: %p\n", tok, tok->link);
    if (tok != NULL)
    {
        printf("%d ", tok->intval);
        while(tok->link != NULL)
        {
            tok = tok->link;
            printf("-> %d ", tok->intval);
        }
        printf("\n");
    }
    else
        printf("Tok is NULL\n");
}


int main(int argc, char *argv[])
{
    TOKEN rhs, lhs;
    rhs = talloc();
    lhs = talloc();
    printf("BEFORE Get the address of rhs: %p, lhs: %p\n", rhs, lhs);
    lhs->intval = 1982;
    rhs->intval = 5;
    rhs = makefloat(rhs);
    printf("AFTER Get the address of rhs: %p, lhs: %p\n", rhs, lhs);
    lhs->link = rhs;
    printexpr(lhs);

    return 0;
}

And my test platform is a Ubuntu 18.04 desktop. I tried to compile the program with gcc-5, gcc-6, gcc-7, gcc-8, gcc-9, and clang-9 and I got different outputs as follows:

Result of gcc-5, gcc-6, gcc-7:

AFTER Get the address of rhs: 0x1, lhs: 0x5629d3959280
tok: 0x5629d3959280, tok->link: 0x1
1982 Segmentation fault (core dumped)

Every time I run the executable, the address of lhs (tok) is different, since it is malloc-ed. But the address of rhs (tok->link) is always 0x1. I find out the reason after some debugging, which is explained later.

Result of gcc-8 and gcc-9:

AFTER Get the address of rhs: 0xc2, lhs: 0x557f7dbdf280
tok: 0x557f7dbdf280, tok->link: 0xc2
1982 Segmentation fault (core dumped)

The difference from the previous result is that now the address of rhs(tok->link) is always 0xc2. The reason is explained later.

Result of clang-9 (with printf before calling printexpr in main):

AFTER Get the address of rhs: 0x7ffc10d6c418, lhs: 0x141d280
tok: 0x141d280, tok->link: 0x7ffc10d6c418
1982 -> 282510568 Segmentation fault (core dumped)

Every time I run the executable, the addresses of both rhs and lhs are different. And because the address of rhs is a valid address (instead of 0xc2 or 0x1 in the previous cases), its intval also gets printed.

However, if I remove the printf before calling printexpr in main, the result of clang-9 is as follows:

tok: 0x1a7c280, tok->link: 0xc2
1982 Segmentation fault (core dumped)

I got the same result as for gcc-8 and gcc-9. (I tried to add printf's inside printexpr but it makes no difference; I also tried to add fflush(stdout) after printf's but it still makes no difference.)

I find that gcc-x always gives consistent results whether there are printf's before calling printexpr or not.

My question 1: Why does the output of clang-9 depend on printf? Or why it seems to be.

My debugging findings: When I use gdb to debug the program, I find that the return value of makefloat is an uninitialized 8-byte data on the stack starting from $rbp-0x10. Here is the dump assembly of makefloat()

Dump of assembler code for function makefloat:
   0x00005555555547b7 <+0>:     push   %rbp
   0x00005555555547b8 <+1>:     mov    %rsp,%rbp
   0x00005555555547bb <+4>:     sub    $0x20,%rsp
   0x00005555555547bf <+8>:     mov    %rdi,-0x18(%rbp)
   0x00005555555547c3 <+12>:    mov    -0x18(%rbp),%rax
   0x00005555555547c7 <+16>:    mov    (%rax),%eax
   0x00005555555547c9 <+18>:    cmp    $0xa,%eax
   0x00005555555547cc <+21>:    jle    0x5555555547d8 <makefloat+33>
   0x00005555555547ce <+23>:    mov    -0x18(%rbp),%rax
   0x00005555555547d2 <+27>:    mov    %rax,-0x10(%rbp)
   0x00005555555547d6 <+31>:    jmp    0x5555555547f0 <makefloat+57>
   0x00005555555547d8 <+33>:    mov    $0x0,%eax
   0x00005555555547dd <+38>:    callq  0x555555554785 <talloc>
   0x00005555555547e2 <+43>:    mov    %rax,-0x8(%rbp)
   0x00005555555547e6 <+47>:    mov    -0x8(%rbp),%rax
   0x00005555555547ea <+51>:    movl   $0xfffffff6,(%rax)
   0x00005555555547f0 <+57>:    mov    -0x10(%rbp),%rax   <--- this is the return value of makefloat
   0x00005555555547f4 <+61>:    leaveq 
   0x00005555555547f5 <+62>:    retq   
End of assembler dump.

The address of the 8-byte data is always the same every time I run the program. Therefore, I try to see the value in this address at the beginning of _start() and I always get 0xc2. Note that this is true for all compilers.

So, gcc-8 and gcc-9 generated code does not overwrite this address. However, gcc-5,6,7 overwrite the address with 0x1 somewhere between _start() and makefloat(). After some more debugging, I find that the devil is in frame_dummy() function, which is called in __libc_csu_init(), which is called in __libc_start_main(). Here is the frame_dummy() function generated by gcc-8 and gcc-9:

Dump of assembler code for function frame_dummy:
=> 0x0000555555554780 <+0>: jmpq   0x555555554700 <register_tm_clones>
End of assembler dump.

which jumps directly to register_tm_clones without pushing anything on the stack. Here is the result generated by gcc-5,6,7:

Dump of assembler code for function frame_dummy:
   0x00005555555547a0 <+0>: push   %rbp
   0x00005555555547a1 <+1>: mov    %rsp,%rbp
   0x00005555555547a4 <+4>: pop    %rbp
   0x00005555555547a5 <+5>: jmpq   0x555555554710 <register_tm_clones>
End of assembler dump.

which pushes $rbp on the stack (and later pops out) before jumping to register_tm_clones. This happens to overwrite the 8-byte data on the stack, which will be used as the return value of makefloat. The pushed %rbp value in this case is 0x1.

This explains the 0x1 value in the output of gcc-5,6,7. But it still does not explain my second question:

My question 2: Why the initial value in the 8-byte stack memory always 0xc2? I have a very shallow understanding of the linking and loading process of x86-64 executables. But I would like to read more. It is highly appreciated if anyone can tell me where to start to investigate.


Viewing all articles
Browse latest Browse all 22016

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>