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()?
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.
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 )
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With