Как получить название функции, которая совершила вызов данной функции?

Рейтинг: 2Ответов: 3Опубликовано: 06.03.2011

Пишу лабораторную работу, нужно логирование, и для того чтобы не запутываться, хотелось бы иметь в логах что-то вроде:

Error: init_link - not enough memory

Сейчас ничего, кроме передачи в логгер строки-названия функции, в голову не приходит. Есть ли более изящный метод?

Update: Вот что я имею в виду:

int foo(int bar)
{
    int foobar = -1;
    check_something(foobar/bar);
    return foobar/bar;
}

Теперь в check_something должен вызваться логгер, в котором сказано о том, что check_something был вызван из foo

Ответы

▲ 4Принят

Вопрос очень интересный, попробую предложить решение, хотя не уверен, то ли это, что видится автору вопроса.

На мой взгляд, хороший логгер должен выводить весь путь вызовов к точке вывода сообщения, наподобие вывода команды where в интерактивном отладчике gdb .

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

Можно, наверное, предложить 'ручной' вариант. При входе в функцию, имя которой должно появиться в списке логгера, вызовем My_set_fname(fname), а для печати My_logger(message); fname ДОЛЖНА быть локальной переменной (располагаться в стеке программы).

My_set_fname(fname) будет вести свой стек (например, связанный список в куче или достаточно большой статический массив (собственно большой - это если My_set_fname() будут вызываться из рекурсии)). В этот стек надо помещать адрес параметра (fname). Здесь надо иметь в виду, что стек исполнения программы растет от больших адресов к меньшим, поэтому при очередном вызове My_set_fname() будем корректировать (путем удаления) верхнюю часть своего стека, так чтобы в нем содержались только адреса больше, чем адрес нашего аргумента. Эти соображения определяют требование к размещению аргумента. Константы и куча не подходят.

Ну, логика My_logger(), по-моему, уже очевидна (или скажем так: формат вывода - дело вкуса).

▲ 5

Есть такой макрос: __FUNCTION__, который возвращает имя функции, в которой вызван. Думаю, можно составить макрос, который неявно этот параметр будет конечной функции и передавать.

Можете посмотреть и другие макросы в статье: Predefined Macros.

▲ 2

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

В NT такое логгирование используется очень часто, например, бактрейс - это первое действие при анализе крэшдампов. Есть полноценные апи для работы с бактрейсом (напр. RtlLogStackBackTrace() etc.) и утилиты для его просмотра (UMDH, WinDbg etc.).