Как установить точку останова в GDB, где функция возвращает?
У меня есть функция C++, которая имеет много операторов возврата в разных местах. Как установить точку останова в операторе возврата, куда функция фактически возвращается?
А что значит команда "break" без аргумента?
7 ответов
break без аргументов останавливает выполнение следующей инструкции в текущем выбранном кадре стека. Вы выбираете рамку с помощью frame
или же up
а также down
команды. Если вы хотите отладить точку, в которой вы фактически покидаете текущую функцию, выберите следующий внешний кадр и разбейте его там.
Вопреки ответам до сих пор, большинство компиляторов создадут одну инструкцию по сборке возврата независимо от того, сколько return
операторы находятся в функции (это удобно для компилятора, поэтому есть только одно место для выполнения очистки всего стека).
Если вы хотите остановиться на этой инструкции, все, что вам нужно сделать, это disas
и искать retq
(или какой-либо другой инструкцией возврата для вашего процессора) и установите для нее точку останова. Например:
int foo(int x)
{
switch(x) {
case 1: return 2;
case 2: return 3;
default: return 42;
}
}
int main()
{
return foo(0);
}
(gdb) disas foo
Dump of assembler code for function foo:
0x0000000000400448 <+0>: push %rbp
0x0000000000400449 <+1>: mov %rsp,%rbp
0x000000000040044c <+4>: mov %edi,-0x4(%rbp)
0x000000000040044f <+7>: mov -0x4(%rbp),%eax
0x0000000000400452 <+10>: mov %eax,-0xc(%rbp)
0x0000000000400455 <+13>: cmpl $0x1,-0xc(%rbp)
0x0000000000400459 <+17>: je 0x400463 <foo+27>
0x000000000040045b <+19>: cmpl $0x2,-0xc(%rbp)
0x000000000040045f <+23>: je 0x40046c <foo+36>
0x0000000000400461 <+25>: jmp 0x400475 <foo+45>
0x0000000000400463 <+27>: movl $0x2,-0x8(%rbp)
0x000000000040046a <+34>: jmp 0x40047c <foo+52>
0x000000000040046c <+36>: movl $0x3,-0x8(%rbp)
0x0000000000400473 <+43>: jmp 0x40047c <foo+52>
0x0000000000400475 <+45>: movl $0x2a,-0x8(%rbp)
0x000000000040047c <+52>: mov -0x8(%rbp),%eax
0x000000000040047f <+55>: leaveq
0x0000000000400480 <+56>: retq
End of assembler dump.
(gdb) b *0x0000000000400480
Breakpoint 1 at 0x400480
(gdb) r
Breakpoint 1, 0x0000000000400480 in foo ()
(gdb) p $rax
$1 = 42
Вы можете использовать обратную отладку, чтобы узнать, где функция действительно возвращается. Завершите выполнение текущего кадра, сделайте обратный шаг, и затем вы должны остановиться на только что возвращенном утверждении.
(gdb) record
(gdb) fin
(gdb) reverse-step
Перерыв на все функции текущей функции
Эта команда Python ставит точку останова на каждом retq
инструкция текущей функции:
class BreakReturn(gdb.Command):
def __init__(self):
super().__init__(
'break-return',
gdb.COMMAND_RUNNING,
gdb.COMPLETE_NONE,
False
)
def invoke(self, arg, from_tty):
frame = gdb.selected_frame()
# TODO make this work if there is no debugging information, where .block() fails.
block = frame.block()
# Find the function block in case we are in an inner block.
while block:
if block.function:
break
block = block.superblock
start = block.start
end = block.end
arch = frame.architecture()
pc = gdb.selected_frame().pc()
instructions = arch.disassemble(start, end - 1)
for instruction in instructions:
if instruction['asm'].startswith('retq '):
gdb.Breakpoint('*{}'.format(instruction['addr']))
BreakReturn()
Источник это с:
source gdb.py
и используйте команду как:
break-return
continue
Теперь вы должны быть в retq
,
Шаг до ретк
Просто для удовольствия, другая реализация, которая останавливается, когда retq
найдено (менее эффективно из-за отсутствия аппаратной поддержки):
class ContinueReturn(gdb.Command):
def __init__(self):
super().__init__(
'continue-return',
gdb.COMMAND_RUNNING,
gdb.COMPLETE_NONE,
False
)
def invoke(self, arg, from_tty):
thread = gdb.inferiors()[0].threads()[0]
while thread.is_valid():
gdb.execute('ni', to_string=True)
frame = gdb.selected_frame()
arch = frame.architecture()
pc = gdb.selected_frame().pc()
instruction = arch.disassemble(pc)[0]['asm']
if instruction.startswith('retq '):
break
ContinueReturn()
Это будет игнорировать ваши другие точки останова. ТОДО: можно избежать?
Не уверен, что это быстрее или медленнее, чем reverse-step
,
Версия, которая останавливается на указанном коде операции, может быть найдена по адресу: /questions/43932753/kak-slomat-instruktsiyu-s-opredelennyim-kodom-operatsii-v-gdb/43932758#43932758
rr
обратная отладка
Похож на GDB record
упоминается по адресу /questions/13115347/kak-ustanovit-tochku-ostanova-v-gdb-gde-funktsiya-vozvraschaet/13115354#13115354, но гораздо более функциональный, чем GDB 7.11, по сравнению с rr
4.1.0 в Ubuntu 16.04.
Примечательно, что он правильно работает с AVX:
- Обратная отладка GDB завершается с ошибкой "Запись процесса не поддерживает инструкцию 0xf0d по адресу"
- "target record-full" в gdb приводит к сбою команды "n" в printf с "Process record не поддерживает инструкцию 0xc5 по адресу 0x7ffff7dee6e7"?
что мешает ему работать со стандартными вызовами библиотеки по умолчанию.
Установите Ubuntu 16.04.
sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
Но также подумайте о компиляции из исходного кода, чтобы получить последние обновления, это было не сложно.
Тестовая программа:
int where_return(int i) {
if (i)
return 1;
else
return 0;
}
int main(void) {
where_return(0);
where_return(1);
}
скомпилируйте и запустите:
gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay
Теперь вы остались внутри сеанса GDB, и вы можете правильно отменить отладку:
(rr) break main
Breakpoint 1 at 0x56057c458619: file a.c, line 9.
(rr) continue
Continuing.
Breakpoint 1, main () at a.c:9
9 where_return(0);
(rr) step
where_return (i=0) at a.c:2
2 if (i)
(rr) finish
Run till exit from #0 where_return (i=0) at a.c:2
main () at a.c:10
10 where_return(1);
Value returned is $1 = 0
(rr) reverse-step
where_return (i=0) at a.c:6
6 }
(rr) reverse-step
5 return 0;
Сейчас мы на правильной обратной линии.
Если вы можете изменить исходный код, вы можете использовать некоторые хитрости с препроцессором:
void on_return() {
}
#define return return on_return(), /* If the function has a return value != void */
#define return return on_return() /* If the function has a return value == void */
/* <<<-- Insert your function here -->>> */
#undef return
Затем установите точку останова на on_return
и идти один кадр up
,
Внимание: это не будет работать, если функция не возвращается через return
заявление. Поэтому убедитесь, что это последняя строка return
,
Пример (беспардонно скопирован из кода C, но будет работать и в C++):
#include <stdio.h>
/* Dummy function to place the breakpoint */
void on_return(void) {
}
#define return return on_return()
void myfun1(int a) {
if (a > 10) return;
printf("<10\n");
return;
}
#undef return
#define return return on_return(),
int myfun2(int a) {
if (a < 0) return -1;
if (a > 0) return 1;
return 0;
}
#undef return
int main(void)
{
myfun1(1);
myfun2(2);
}
Первый макрос изменится
return;
в
return on_return();
Который действителен, так как on_return
также возвращает void
,
Второй макрос изменится
return -1;
в
return on_return(), -1;
Который позвонит on_return()
а затем вернуть -1 (благодаря ,
-оператором).
Это очень грязный трюк, но, несмотря на использование пошагового перехода назад, он будет работать в многопоточных средах и встроенных функциях.
Break без аргумента устанавливает точку останова на текущей строке.
Для одной точки останова невозможно отловить все пути возврата. Либо установите точку останова на вызывающем абоненте сразу после его возвращения, либо прервите его вообще return
заявления.
Поскольку это C++, я полагаю, вы могли бы создать локальный сторожевой объект и, тем не менее, разбить его деструктор.