12

In Python, I have a a variable var of type gdb.Value that corresponds to a C++ struct.

The struct has a method void foo().

I can evaluate this expression var['foo']. But var['foo']\() will complain saying

RuntimeError: Value is not callable (not TYPE_CODE_FUNC)

I believe the value type will be gdb.TYPE_CODE_METHOD (not sure, but var['foo'].type.code returns 16) in my case.

So I guess the question is:

Does python API support calls to class methods, and if not, is there a workaround?

Thanks!

1

4 Answers 4

10

OK, I think I was able to do what I want using the Tom's advice and another workaround.

The problem I need an extra workaround was (as I mentioned in the comment above) that I didn't have the variable name in order to compose a string of form: myval.method() to pass to gdb.parse_and_eval.

So the workaround for that one is to get the address of the variable and then cast it to the type and then add a method call to the string.

Both type and address exist in python api for gdb.Value. So the solution looks like the following:

    eval_string = "(*("+str(self.val.type)+"*)("+str(self.val.address)+")).method()"
    return gdb.parse_and_eval(eval_string);
Sign up to request clarification or add additional context in comments.

5 Comments

I tried to use this trick, but found it to be very unstable in the project that could benefit from it: 50% of my gdb session have failed using it. Can't wait for a GDB fix :)
Wrapping the type in single quote might work better for mangled names with spaces or similar.
Because the str may output something like 0x555556567094 <cur_input+4> it's better to use str(hex(...)) instead.
@LoneWanderer Do you have some example files that? There might be some workarounds (if there's no address of the variable it's possible to dynamically allocate memory in gdb and call that; but it won't mutate the original value, works in most cases)
Unfortunatly no : I moved out on other projects since. Sry.
3

It's just a missing feature that nobody has implemented yet. You might see if it is in bugzilla, and, if not, file a bug.

A typical workaround is to substitute the "this" argument's value into a string and make the call via gdb.parse_and_eval. This usually works but is, of course, distinctly second-best.

2 Comments

Tom, thanks for the response. I'd like to try the workaround you suggest, but here is my problem. I use the python api for pretty printing. In the class I register for pretty printing, I will only get the value of the variable in the ctor and I don't have the variable name I don't think unless the gdb.Value has it. In std::string example below, "val" is the gdb.Value and not variable name available, right? class StdStringPrinter(object): "Print a std::string" def __init__(self, val): self.val = val def to_string(self): return self.val['_M_dataplus']['_M_p']
Here is a very similar request on the bugzilla for our reference: link
1

Method call is not (yet?) supported by GDB. https://gdb.sourceware.narkive.com/KVTUIAvl/c-method-call-for-a-gdb-value-object-from-within-python

However function call is supported. If you have opportunity to make a function wrapper which will call your method:

void func(mystruct& arg) { arg.method(); }

Then you can find this func via global gdb search, and call:

sym = gdb.lookup_global_symbol('namespace::func(mystruct&)')
result = sym.value()(this.val)

This method does not require taking address of the variable, which fails 50% of the time due to compiler optimizations.

4 Comments

This only works for non-templated structs, unfortunately. In cases such as template<class T> struct A {}; template<class T> void function(A<T>) you would have to specify for example print 'function<int>'(a), type function[TAB][TAB] to see the list.
Unfortunately, gdb has some bugs that prevent the correct overload from being detected in a few rare cases.
For the template issue, it can be partially circumvented by making friend function inside the struct (need instantiation to use from gdb); however gdb.parse_and_eval may still return the wrong overload.
Issues: ■ if the function is template<class T>, to call it you have to specify the <T> part explicitly. Alternatively, just overload the functions e.g. if it takes in a struct, make it friend of that struct. ■ also, Python API cannot use this (gdb.parse_and_eval(function) will return one arbitrary overload), but can use convenience variable, see stackoverflow.com/a/71419746/5267751 for an example. ■ in a few rare cases (function returning std::string) the resolved overload is wrong.
0

These methods applies to static methods too.

  1. Use convenience variable.

    gdb.set_convenience_variable("tmp",
            gdb.parse_and_eval("c").address
            )
    gdb.execute("print $tmp->f()")
    
  2. If the type is known, you can do:

    gdb.parse_and_eval("C::f")(
            gdb.parse_and_eval("c").address
    )
    

    (alternatively use the mangled name _ZN1C1fEv. nm --demangle can be used for this for example)

  3. If only the type of the target function is known, you can do

    cftype=gdb.parse_and_eval("C::f").type  # ← this is known
    
    gdb.parse_and_eval("c")["f"].cast(cftype)(
            gdb.parse_and_eval("c").address
    )
    
  4. Some flexibility is not allowed by gdb, but no guarantee:

    cftype=gdb.parse_and_eval("* (void (*) (void*)) 0").type
    
    gdb.parse_and_eval("c")["f"].cast(cftype)(
            gdb.parse_and_eval("c").address
    )
    

(if it's static function you don't need to "fake" it as void const*, can also do directly in C++ code)


Example C++ code:

#include<iostream>

struct C{
    int x;
    void f() {
        ++x;
        std::cout<<x<<"\n";
    }
};

int main(){
    C c{5};
    c.f();
    __builtin_trap();
}

In addition, if the struct is local however, you'll have to apply some workarounds described in https://stackoverflow.com/a/70254108/5267751.

Example C++ code

int main(){
    struct C{
        int x;
        void f() {
            ++x;
            std::cout<<x<<"\n";
        }
    };
    
    C c{5};
    c.f();
    __builtin_trap();
}

Example Python code

gdb.parse_and_eval("'main::C::f'")(gdb.parse_and_eval("c").address)

Alternatively use _ZZ4mainEN1C1fEv.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.