Search code examples
clinuxgdbelfdwarf

How to extract the arguments of current function in C?


I'm trying to implement a function that retrieves the call stack unwinding from the caller. Take the following program as an example:

// test.c
void func2(int a, double b, int* c)
{
    my_backtrace_func(); // line 4
}

void func1(int a)
{
    func2(5, 1.2, 0xabcd); // line 9
}

int main()
{
    func1(1); // line 14
}

Like gdb backtrace does, My function will print stuff like this:

stack #1: func2 in test.c, line 4.
stack #2: func1 in test.c, line 9.
stack #3: main in test.c, line 14.

Now I wish to extend its functionality: Extract all the arguments of the caller, just like gdb info args does:

stack #1: func2 (a = 5, b = 1.2, c = 0xabcd) in test.c, line 4.
stack #2: func1 in test.c, line 9.
stack #3: main in test.c, line 14.

The program is assumed to be compiled with -g on ARM64, which means I have access to .debug_ sections and x0~x30 registers.

As I'm not an experienced developer of a debugger, I assume I need a parser of dwarf format, find AT_DW_location of arguments in .debug_info, maybe then refer to .debug_loc for location list or .eh_frame for DW_OP_call_frame_cfa .

However, the actual storage of parameters is changeable, and different compile levels such as -O0 and -O2 may require different processing. I don't know if this is a feasible and correct solution.

So far, I have not referred to gdb source. I guess there will be something meaningful, but it's hard for me to read.

Is there any simple and clear way to implement this? how?


Solution

  • Is there any simple and clear way to implement this?

    No.

    To implement this, you would need to have a detailed understanding of how the debug info is encoded, and how to interpret it.

    I assume I need a parser of dwarf format, find AT_DW_location of arguments in .debug_info, maybe then refer to .debug_loc for location list ...

    Yes, and more. You could compile your example program, and then study the output from readelf -wi a.out.

    Look for DW_TAG_subprogram, followed by DW_TAG_formal_parameters. Each parameter will have DW_AT_type and DW_AT_location, which will tell you the type and location of the parameter.

    I don't know if this is a feasible

    It's feasible, but by the time you are done you will have implemented 30-50% of a real debugger. At which point a reasonable question is: why not use a real debugger which exists already?

    Update:

    What are you trying to achieve?

    I'm providing an API for collecting stack addresses. ... I want to restore the callstack information and parameters for possible debugging behaviors.

    You are mixing debugging and logging. They are closely related, but they are not the same.

    While logging can help debugging, logging all parameters to all functions in the call stack is

    1. unlikely to be fast enough
    2. isn't going to work reliably in optimized builds
    3. isn't going to be useful, because in any non-trivial program the interesting parameters aren't going to be simple types -- they are going to be pointers (or references) to data structures, which contain pointers to other data structures, etc. The value or the pointer is going to be useless most of the time.

    If you try to solve issue#3 by printing the contents of pointed-to structures, that will only exacerbate issue#1.