Skip to content

Instantly share code, notes, and snippets.

@aont
Last active September 21, 2025 10:08
Show Gist options
  • Select an option

  • Save aont/ee497edfb1efe9b0c30dd1952ce9b9c1 to your computer and use it in GitHub Desktop.

Select an option

Save aont/ee497edfb1efe9b0c30dd1952ce9b9c1 to your computer and use it in GitHub Desktop.

Tracing Function Calls in C++ with GDB and Python

When debugging recursive functions in C++, it can be helpful to trace every entry and exit of a function. With GDB (GNU Debugger) and a custom Python script, we can automate this process.


Step 1: Compile with Debug Information

We first compile our program with debugging symbols enabled and without optimization:

g++ -g -O0 sample.cpp -o sample

This ensures that GDB can read source-level information.


Step 2: Run GDB with a Python Script

Next, we launch GDB and load a Python script (trace.py) that sets breakpoints automatically:

gdb -ex "source trace.py" ./sample

This script parses all functions from the program and sets breakpoints to monitor their execution.


Step 3: Example Program

Here is a simple recursive program (sample.cpp):

#include <cstdio>

void func(int value) {
    fprintf(stderr, "value=%d\n", value);
    if (value==0) return;
    return func(value-1);
}

int main() {
    func(10);
    return 0;
}

The func function calls itself until the value reaches zero.


Step 4: Python Script for Tracing

The trace.py script uses GDB’s Python API. It sets a breakpoint at every function (except main) and prints messages when entering and exiting:

  • >> Entering func
  • >> Exiting func

It does this by using Breakpoint and FinishBreakpoint classes to hook into function calls.


Step 5: Output

When we run the program under GDB, we get the following trace:

>> Entering func
value=10
>> Entering func
value=9
...
>> Entering func
value=0
>> Exiting func
>> Exiting func
...
>> Exiting func

This shows each recursive call entering and leaving in order.


Conclusion

By combining GDB with a Python script, we can automatically trace all function calls in a C++ program. This approach is especially useful for debugging recursive functions, as it provides a clear view of the call stack in action.

#include <cstdio>
void func(int value) {
fprintf(stderr, "value=%d\n", value);
if (value==0) return;
return func(value-1);
}
int main() {
func(10);
return 0;
}
import re
# import json
# import sys
import gdb
def extract_function_name(signature):
"""
関数シグネチャから '(' の直前までの文字列を取得し、
最後のトークンを関数名として抽出する。
例: "static void __mingw_invalidParameterHandler(const wchar_t *, ...)"
-> "__mingw_invalidParameterHandler"
"""
idx = signature.find('(')
if idx != -1:
prefix = signature[:idx]
tokens = prefix.split()
if tokens:
return tokens[-1]
return None
def parse_text(text):
lines = text.splitlines()
result = {
"defined_functions": {},
"non_debugging_symbols": []
}
current_file = None
in_defined_functions = False
in_non_debugging_symbols = False
for line in lines:
line = line.strip()
if not line:
continue
# セクションの切り替え
if line.startswith("All defined functions:"):
in_defined_functions = True
in_non_debugging_symbols = False
continue
if line.startswith("Non-debugging symbols:"):
in_defined_functions = False
in_non_debugging_symbols = True
continue
# defined_functions セクション内の解析
if in_defined_functions:
# "File ..." 行の判定
file_match = re.match(r'^File\s+(.+):$', line)
if file_match:
current_file = file_match.group(1)
result["defined_functions"][current_file] = []
continue
# 行番号と関数定義の解析 (例: "9: void _fpreset(void);")
func_match = re.match(r'^(\d+):\s*(.+)$', line)
if func_match and current_file:
line_num = int(func_match.group(1))
signature = func_match.group(2)
func_name = extract_function_name(signature)
result["defined_functions"][current_file].append({
"line": line_num,
"signature": signature,
"function_name": func_name
})
continue
# non_debugging_symbols セクション内の解析
if in_non_debugging_symbols:
# 例: "0x0000000140001440 __gcc_register_frame"
nd_match = re.match(r'^(0x[0-9A-Fa-f]+)\s+(.+)$', line)
if nd_match:
address = nd_match.group(1)
symbol = nd_match.group(2)
result["non_debugging_symbols"].append({
"address": address,
"symbol": symbol
})
continue
return result
class TraceFunction(gdb.Breakpoint):
def __init__(self, funcname):
# 正規表現で全関数にブレーク(必要に応じてフィルタしてください)
super(TraceFunction, self).__init__(funcname, gdb.BP_BREAKPOINT, internal=False)
def stop(self):
frame = gdb.selected_frame()
func = frame.name()
gdb.write(">> Entering {}\n".format(func))
# 関数終了時にログを出すために FinishBreakpoint を設定
self.trace_finish = TraceFinish(frame, func)
return False # ここで停止せずに実行を継続
class TraceFinish(gdb.FinishBreakpoint):
def __init__(self, frame, func):
super(TraceFinish, self).__init__(frame, internal=True)
self.func = func
def stop(self):
gdb.write(">> Exiting {}\n".format(self.func))
return False
def main():
info_functions_text = gdb.execute("info functions", to_string=True)
structured_data = parse_text(info_functions_text)
# gdb.write(json.dumps(structured_data, indent=2, ensure_ascii=False))
gdb.execute("set pagination off")
for function_info in structured_data["defined_functions"]["sample.cpp"]:
funcname = function_info["function_name"]
if funcname == "main": continue
gdb.write(funcname)
TraceFunction(funcname)
gdb.execute("run")
gdb.execute("quit")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment