5

I have been professionally coding in C for a while but am still stumped by some pointer related questions. I would really appreciate SO community's help in understanding below problem.

Following code crashed and generated core file.

void func1()    // Frame 1 in GDB stack trace.  
{ 
    UTYPE  *ptr;  // pointer to user defined type  
    ...

    // data is of type UTYPE and has valid contents.
    // lets say its address is 0x100 
    ptr = &data;     --- (1)  
    ...

    func2(ptr);      --- (2) 
    ...
} 

void func2(UTYPE *inp)    // Frame 0 in GDB stack trace.  
{
    if(!inp)         --- (3) 
        return; 
    ...

    // another_ptr is of UTYPE * which is a NULL.  
    inp = another_ptr;   ---- (4)  

    /* Did not check for NULL and dereference inp and CRASH */    ---- (5) 
} 

Simplified backtrace from GDB:

Frame 0: 
    func2(inp = 0x0) 
    // crash at line (5) due to dereference 

Frame 1: 
    func1: func2(0x0)  
    // `ptr` at line (2) is 0x0. Why is this so? 

Why is ptr shown as 0x0 (NULL) in Frame 1?

When func2() is called, its call stack looks as follows:

  | //local vars  | 
  |               | 
  | another_ptr = |
  |      NULL     |
  +---------------+
  | return addr   |
  +---------------+
  | input args    |
  | copy of ptr   |
  |   contents    |
  |     0x100     |

For func1(), its call stack should look like:

  |               | 
  | ptr = 0x100   |
  |               |
  +---------------+
  | return addr   |
  +---------------+
  | input args    |
  |  none in this |
  |  func         |

When inp becomes NULL in func2() in line (4), how is it reflected in func1()?

Bhaskar
  • 2,549
  • 1
  • 21
  • 23
  • I like your representation. – Grijesh Chauhan Jan 30 '14 at 19:52
  • 1
    That's odd. I'd expect the backtrace for Frame 1 to say `func1() at thelinenumberoflocationlabeled(2)` and not display the call to `func2`. Are you using the `bt` command or something else? – Mark Plotnick Jan 30 '14 at 20:03
  • @MarkPlotnick I am using `bt` command. It prints all stack frames and not just Frame 1. My representation of GDB backtrace above is just to help explain my question. Please let me know if something is amiss. – Bhaskar Jan 30 '14 at 20:06
  • OK. My gdb's `bt` command displays a Frame 0 with a call to `func2()` and a Frame 1 with a call to `func1`. Its display of Frame 1 does not show a call to `func2` as yours does. I'm just having trouble reproducing your exact problem. – Mark Plotnick Jan 30 '14 at 20:12

2 Answers2

2

GDB constructs the call stack from the stack, and since inp, which is a parameter for func2, is 0, GDB assumes that the passed parameter is 0, and so it says that func2 was called with 0.

Stack at crash time is something like that:

another_ptr = 0  ( func2 local variable )
return address to func1                   
inp = 0          ( func2 parameter )
ptr              ( func1 local variable )
MByD
  • 135,866
  • 28
  • 264
  • 277
  • Oh! In that case, the info in older frames are inaccurate, especially pointer variables. – Bhaskar Jan 30 '14 at 19:58
  • In above stack representation, when `inp` becomes `0`, why is that reflected on `ptr` in GDB? They are in separate function stacks, right? – Bhaskar Jan 30 '14 at 20:11
  • `func2(0x0)` doesn't mean that ptr is null, it refers to `func2` parameter (inp) as well. Both function frames are on the same stack (as any other function frame in the same thread... ) – MByD Jan 30 '14 at 20:13
  • Ok. Thanks for the explanation. – Bhaskar Jan 30 '14 at 20:15
  • `bt full` also says `ptr` is `0x0` – Bhaskar Jan 30 '14 at 20:31
2

When a function is called in C, the parameters are copied into registers or pushed onto the stack. The called function can reuse those registers and stack locations for any purpose. Often, but not always, a parameter is kept in the same register or stack location for the entire lifetime of the function call.

On a 32-bit system, the first argument to a function - in your case, inp - is often located on the stack, 12 bytes away from the location that the stack frame's base pointer points to. See stackoverflow.com: what exactly is program stack's growth direction.

When gdb does a backtrace, the only guidance it has from the compiler is something like "the first argument to func2 is named inp and is a 4-byte value of type *UTYPE located at a 12-byte offset from the %ebp register".

If, somewhere in func2, you alter inp, as you do at location (4), then any backtrace from that point on may very well show the altered value of inp, in your case, 0. The value that inp had when func2 was entered is lost forever, unless the compiler has been clever enough to include guidance like "the first argument to func2 is named inp and is a 4-byte value of type *UTYPE and its value upon entry to func2 can be found by unwinding the stack to the previous frame and looking at the value of ptr, which is located at a -4-byte offset from the %ebp register." The newer versions of the DWARF debugging format can specify things like this, I believe.

I cannot explain why your gdb's backtrace shows ptr in func1's frame as having the value 0. Setting inp to NULL should have no effect on ptr's value nor on gdb's ability to show ptr's value.

Community
  • 1
  • 1
Mark Plotnick
  • 9,598
  • 1
  • 24
  • 40