Подтвердить что ты не робот

Стек Valgrind пропускает функцию полностью

У меня есть два файла c:

a.c

void main(){
    ...
    getvtable()->function();
}

vtable указывает на функцию, расположенную в b.c:

void function(){
    malloc(42);
}

теперь, если я трассирую программу в valgrind, я получаю следующее:

==29994== 4,155 bytes in 831 blocks are definitely lost in loss record 26 of 28
==29994==    at 0x402CB7A: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==29994==    by 0x40A24D2: (below main) (libc-start.c:226)

поэтому вызов функции полностью омрачивается в стеке! Как это возможно? В случае, если я использую GDB, отображается правильный стек, включающий "функцию".

Включены символы отладки, Linux, 32-разрядные.

Upd:

Отвечая на первый вопрос, я получаю следующий вывод при отладке сервера valgrind GDB. Точка останова не наступает, пока она возникает, когда я отлаживаю непосредственно с помощью GDB.

[email protected]:~$ gdb -q
(gdb) set confirm off
(gdb) target remote | vgdb
Remote debugging using | vgdb
relaying data between gdb and process 11665
[Switching to Thread 11665]
0x040011d0 in ?? ()
(gdb) file /home/stasik/leak.so
Reading symbols from /home/stasik/leak.so...done.
(gdb) break function
Breakpoint 1 at 0x110c: file ../../source/leakclass.c, line 32.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>end
(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0404efcb in ?? ()
(gdb) source thread-frames.py
Stack level 0, frame at 0x42348a0:
 eip = 0x404efcb; saved eip 0x4f2f544c
 called by frame at 0x42348a4
 Arglist at 0x4234898, args:
 Locals at 0x4234898, Previous frame sp is 0x42348a0
 Saved registers:
  ebp at 0x4234898, eip at 0x423489c
Stack level 1, frame at 0x42348a4:
 eip = 0x4f2f544c; saved eip 0x6e492056
 called by frame at 0x42348a8, caller of frame at 0x42348a0
 Arglist at 0x423489c, args:
 Locals at 0x423489c, Previous frame sp is 0x42348a4
 Saved registers:
  eip at 0x42348a0
Stack level 2, frame at 0x42348a8:
 eip = 0x6e492056; saved eip 0x205d6f66
 called by frame at 0x42348ac, caller of frame at 0x42348a4
 Arglist at 0x42348a0, args:
 Locals at 0x42348a0, Previous frame sp is 0x42348a8
 Saved registers:
  eip at 0x42348a4
Stack level 3, frame at 0x42348ac:
 eip = 0x205d6f66; saved eip 0x61746144
---Type <return> to continue, or q <return> to quit---
 called by frame at 0x42348b0, caller of frame at 0x42348a8
 Arglist at 0x42348a4, args:
 Locals at 0x42348a4, Previous frame sp is 0x42348ac
 Saved registers:
  eip at 0x42348a8
Stack level 4, frame at 0x42348b0:
 eip = 0x61746144; saved eip 0x65736162
 called by frame at 0x42348b4, caller of frame at 0x42348ac
 Arglist at 0x42348a8, args:
 Locals at 0x42348a8, Previous frame sp is 0x42348b0
 Saved registers:
  eip at 0x42348ac
Stack level 5, frame at 0x42348b4:
 eip = 0x65736162; saved eip 0x70616d20
 called by frame at 0x42348b8, caller of frame at 0x42348b0
 Arglist at 0x42348ac, args:
 Locals at 0x42348ac, Previous frame sp is 0x42348b4
 Saved registers:
  eip at 0x42348b0
Stack level 6, frame at 0x42348b8:
 eip = 0x70616d20; saved eip 0x2e646570
 called by frame at 0x42348bc, caller of frame at 0x42348b4
 Arglist at 0x42348b0, args:
---Type <return> to continue, or q <return> to quit---
 Locals at 0x42348b0, Previous frame sp is 0x42348b8
 Saved registers:
  eip at 0x42348b4
Stack level 7, frame at 0x42348bc:
 eip = 0x2e646570; saved eip 0x0
 called by frame at 0x42348c0, caller of frame at 0x42348b8
 Arglist at 0x42348b4, args:
 Locals at 0x42348b4, Previous frame sp is 0x42348bc
 Saved registers:
  eip at 0x42348b8
Stack level 8, frame at 0x42348c0:
 eip = 0x0; saved eip 0x0
 caller of frame at 0x42348bc
 Arglist at 0x42348b8, args:
 Locals at 0x42348b8, Previous frame sp is 0x42348c0
 Saved registers:
  eip at 0x42348bc
(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0404efcb in ?? ()
(gdb) continue
Continuing.
4b9b3361

Ответ 1

Хорошо, компилирование всех .so частей, и основная программа с явным -O0, похоже, решает проблему. Похоже, что некоторые из оптимизаций "основной" программы, которая загружала .so(так всегда компилировалась неоптимизированным), ломала стек.

Ответ 2

Я вижу две возможные причины:

  • Valgrind использует другой метод раскрутки стека, чем GDB
  • Макет адресного пространства отличается при запуске вашей программы в двух средах, и вы только наносите вред стеку при использовании Valgrind.

Мы можем получить больше информации, используя встроенный gdbserver Valgrind.

Сохраните этот фрагмент Python до thread-frames.py

import gdb

f = gdb.newest_frame()
while f is not None:
    f.select()
    gdb.execute('info frame')
    f = f.older()

t.gdb

set confirm off
file MY-PROGRAM
break function
commands
silent
end
run
source thread-frames.py
quit

v.gdb

set confirm off
target remote | vgdb
file MY-PROGRAM
break function
commands
silent
end
continue
source thread-frames.py
quit

(Измените MY-PROGRAM, функция в приведенных выше сценариях и приведённые ниже команды)

Получить информацию о кадрах стека в GDB:

$ gdb -q -x t.gdb
Breakpoint 1 at 0x80484a2: file valgrind-unwind.c, line 6.
Stack level 0, frame at 0xbffff2f0:
 eip = 0x80484a2 in function (valgrind-unwind.c:6); saved eip 0x8048384
 called by frame at 0xbffff310
 source language c.
 Arglist at 0xbffff2e8, args: 
 Locals at 0xbffff2e8, Previous frame sp is 0xbffff2f0
 Saved registers:
  ebp at 0xbffff2e8, eip at 0xbffff2ec
Stack level 1, frame at 0xbffff310:
 eip = 0x8048384 in main (valgrind-unwind.c:17); saved eip 0xb7e33963
 caller of frame at 0xbffff2f0
 source language c.
 Arglist at 0xbffff2f8, args: 
 Locals at 0xbffff2f8, Previous frame sp is 0xbffff310
 Saved registers:
  ebp at 0xbffff2f8, eip at 0xbffff30c

Получить те же данные в Valgrind:

$ valgrind --vgdb=full --vgdb-error=0 ./MY-PROGRAM

В другой оболочке:

$ gdb -q -x v.gdb
relaying data between gdb and process 574
0x04001020 in ?? ()
Breakpoint 1 at 0x80484a2: file valgrind-unwind.c, line 6.
Stack level 0, frame at 0xbe88e2c0:
 eip = 0x80484a2 in function (valgrind-unwind.c:6); saved eip 0x8048384
 called by frame at 0xbe88e2e0
 source language c.
 Arglist at 0xbe88e2b8, args: 
 Locals at 0xbe88e2b8, Previous frame sp is 0xbe88e2c0
 Saved registers:
  ebp at 0xbe88e2b8, eip at 0xbe88e2bc
Stack level 1, frame at 0xbe88e2e0:
 eip = 0x8048384 in main (valgrind-unwind.c:17); saved eip 0x4051963
 caller of frame at 0xbe88e2c0
 source language c.
 Arglist at 0xbe88e2c8, args: 
 Locals at 0xbe88e2c8, Previous frame sp is 0xbe88e2e0
 Saved registers:
  ebp at 0xbe88e2c8, eip at 0xbe88e2dc

Если GDB может успешно размотать стек при подключении к " valgrind --gdb", тогда это проблема с алгоритмом разблокировки стека Valgrind. Вы можете тщательно проверить " информационный фрейм" для встроенных и хвостовых фреймов вызовов или по какой-либо другой причине, которая может выбросить Valgrind. В противном случае это может привести к повреждению.

Ответ 3

Это Оптимизация Tail-call в действии.

Функция function вызывает malloc как последнее, что она делает. Компилятор видит это и убивает фрейм стека function, прежде чем он называет malloc. Преимущество заключается в том, что при возврате malloc он возвращается непосредственно к той функции, которая называется function. То есть он избегает malloc, возвращаясь к function только для того, чтобы нажать еще одну команду возврата.

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

Как вы уже выяснили, отключение оптимизации упрощает отладку. Если вы хотите отлаживать оптимизированный код (возможно, для тестирования производительности), то, как уже сказал @Zang MingJie, вы можете отключить эту оптимизацию с помощью -fno-optimize-sibling-calls.