Как установить точку останова в GDB, где функция возвращает? - PullRequest
21 голосов
/ 06 сентября 2010

У меня есть функция C ++, которая имеет много операторов возврата в разных местах.Как установить точку останова в операторе возврата, где функция на самом деле возвращает?

А что означает команда "break" без аргумента?

Ответы [ 7 ]

20 голосов
/ 07 сентября 2010

Вопреки ответам на данный момент, большинство компиляторов создадут одну инструкцию сборки возврата, независимо от того, сколько операторов 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
18 голосов
/ 06 сентября 2010

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

(gdb) record
(gdb) fin
(gdb) reverse-step
7 голосов

Перерыв на все retq текущей функции

Эта команда 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.

Версия, которая останавливается на данном коде операции, может быть найдена в: https://stackoverflow.com/a/31249378/895245

6 голосов
/ 06 сентября 2010

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

4 голосов

rr обратная отладка

Аналогично GDB record, упомянутому в https://stackoverflow.com/a/3649698/895245, но гораздо более функционально, чем 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;

Мы сейчас на правильной линии возврата.

2 голосов
/ 06 сентября 2010

Break без аргумента устанавливает точку останова на текущей строке.

Для одной точки останова невозможно отловить все пути возврата. Либо установите точку останова на вызывающем абоненте сразу же после его возврата, либо остановите все операторы return.

Поскольку это C ++, я полагаю, вы могли бы создать локальный сторожевой объект и разбить его деструктор.

1 голос
/ 07 июля 2015

Если вы можете изменить исходный код, вы можете использовать некоторые хитрости с препроцессором:

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 (благодаря оператору ,).

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

...