Понимание C-указателей с использованием GDB путем изучения ядра и стека вызовов

Я некоторое время занимался профессиональным кодированием в C, но я все еще озадачен некоторыми вопросами, связанными с указателем. Я бы очень признателен за помощь сообщества в понимании проблемы ниже.

После разбиения кода и сгенерированного файла ядра.

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)
}

Упрощенная обратная трассировка из 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?

Почему ptr отображается как 0x0 (NULL) в кадре 1?

Когда вызывается func2(), его стек вызовов выглядит следующим образом:

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

Для func1() его стек вызовов должен выглядеть так:

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

Когда inp становится NULL в func2() в строке (4), как это отражается в func1()?

2 ответа

Когда функция вызывается в C, параметры копируются в регистры или помещаются в стек. Вызываемая функция может повторно использовать эти регистры и местоположения стека для любых целей. Часто, но не всегда, параметр хранится в том же регистре или месте стека на протяжении всего времени вызова функции.

В 32-битной системе первый аргумент функции - в вашем случае inp - часто находится в стеке, на расстоянии 12 байт от местоположения, на которое указывает указатель базы кадров стека. См. тут.

Когда gdb выполняет обратную трассировку, единственное руководство, которое оно имеет от компилятора, похоже на "первый аргумент func2 имеет имя inp и представляет собой 4-байтовое значение типа *UTYPE, расположенное на 12-байтовое смещение от регистра% ***".

Если где-то в func2 вы изменяете inp, как и в местоположении (4), тогда любая обратная трассировка с этой точки может очень хорошо показать измененное значение inp, в вашем случае 0. Значение, которое inp было при вводе func2 было потеряно навсегда, если только компилятор не был достаточно умен, чтобы включить руководство, например, "первый аргумент func2 имеет имя inp и представляет собой 4-байтовое значение type *UTYPE, а его значение при входе в func2 можно найти, открутив стек до предыдущего кадра и посмотрев на значение ptr, которое расположено с 4-байтным смещением из регистра% ***." Более новые версии формата отладки DWARF могут указывать такие вещи, как я полагаю.

Я не могу объяснить, почему ваш backtrace gdb показывает ptr в кадре func1 как имеющее значение 0. Установка inp в NULL не должна влиять на значение ptr или на способность gdb показывать ptr значение.


GDB создает стек вызовов из стека, а поскольку inp, который является параметром для func2, равен 0, GDB предполагает, что переданный параметр равен 0, и поэтому он сообщает, что был вызван func2 с 0.

Стек в момент сбоя выглядит примерно так:

another_ptr = 0 ( func2 local variable )
return address to func1
inp = 0 ( func2 parameter )
ptr ( func1 local variable )

licensed under cc by-sa 3.0 with attribution.