У меня есть функция С++, которая имеет множество операторов возврата в разных местах. Как установить точку останова в операторе return, где функция действительно возвращается?
И что означает команда "break" без аргумента?
У меня есть функция С++, которая имеет множество операторов возврата в разных местах. Как установить точку останова в операторе return, где функция действительно возвращается?
И что означает команда "break" без аргумента?
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
Перерыв на все retq
текущей функции
Эта команда Python устанавливает retq
останова для каждой инструкции 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
.
Версию, которая останавливается на данном коде операции, можно найти по адресу: fooobar.com/questions/53193/...
rr
- rr
обратной отладки
Подобно record
GDB, упомянутой на fooobar.com/questions/53188/..., но гораздо более функциональной, чем в GDB 7.11 и rr
4.1.0 в Ubuntu 16.04.
Примечательно, что он правильно работает с AVX:
что мешает ему работать со стандартными вызовами библиотеки по умолчанию.
Установите 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;
Сейчас мы на правильной обратной линии.
Разрыв без аргумента устанавливает точку останова в текущей строке.
Невозможно, чтобы одна точка останова поймала все пути возврата. Либо установите точку останова в вызывающем абоненте сразу после ее возвращения, либо разбейте все return
.
Так как это С++, я предполагаю, что вы могли бы создать локальный объект сторожевого устройства и разбить его деструктор.
Если вы можете изменить исходный код, вы можете использовать некоторые трюки с препроцессором:
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-кода, но будет работать и на С++):
#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 (благодаря ,
-оператору).
Это очень грязный трюк, но, несмотря на использование обратного шага, он будет работать в многопоточных средах и встроенных функциях.