Function tracing

I’d like to have a command (Python script?) that allows me to trace function calls and exits.

One possible implementation seems that I could:

  • Set up a breakpoint on function entry.

  • Run some code to log the arguments passed to the function.

  • Use SBThread::StepOut() to get to where the function has been exited.

This leaves me with a number of questions though:

  • Is this a good or even valid approach?

  • Is all of this safe against re-entry? What happens if I’m trying X and X gets invoked again (during the thread plan execution)?

  • How do I get the return value for the function that I’ve just exited via StepOut?

  • What happens if X has been inlined? Will this work still for all entries and exits?

  • I also saw SBThreadPlan was added since I last looked into this. Does anything use this? (I didn’t see any uses in-tree.)

Thanks!

  • Bruce

I'd like to have a command (Python script?) that allows me to trace function calls and exits.

One possible implementation seems that I could:

  • Set up a breakpoint on function entry.
  • Run some code to log the arguments passed to the function.
  • Use SBThread::StepOut() to get to where the function has been exited.

This leaves me with a number of questions though:

  • Is this a good or even valid approach?

Sure, this should work. I presume that you're doing this for functions for which you have debug info:? Otherwise getting return values when you step out is going to be tricky...

  • Is all of this safe against re-entry? What happens if I'm trying X and X gets invoked again (during the thread plan execution)?

The most straightforward way to do this would be to write something that just drives the event loop a la:

http://llvm.org/svn/llvm-project/lldb/trunk/examples/python/process_events.py

Then recursion won't bother you. The step plans stack, so if you do your "step-out" and before the step out completes you hit the breakpoint again, that's okay, the first step-out plan is still on the plan stack. Do your "step-out" for the second breakpoint hit, and when that completes, "continue" and the original step-out plan will then get a chance to complete. And since plans are per thread, doing this on multiple threads won't cause any problems either.

  • How do I get the return value for the function that I've just exited via StepOut?

The thread has "SBThread::GetStopReturnValue()". Any time a stop on that thread generates a return value that lldb can figure out (which is only for "step-out" at present) it will stuff it in the thread's return value. You need to get it before you continue that thread.

  • What happens if X has been inlined? Will this work still for all entries and exits?

We don't know how to get return values for inlined functions. Step out often works, but not always since the line-tables for inlined functions are often unreliable. You can check whether you are an inlined block with the SB API. Since you can't get the return value in this case, it might be easier to log the hit, and note that you can't get a return value and continue...

  • I also saw SBThreadPlan was added since I last looked into this. Does anything use this? (I didn't see any uses in-tree.)

This was a toy I was starting to play with. You could definitely use it for the same purposes, and I'd love to have somebody try this out. But if you have real work to get done, you might want to stick with the more tried method of running the event loop from outside the thread plans.

Jim

Jim,

Thanks for all of this great info!

Just two more questions for now …

It will. When one thread stops all threads stop. Though multiple threads can stop, each at their own breakpoint, so you should be able to keep things going along. If one thread fails to step out due to a deadlock, just be sure to let other threads that did stop at breakpoints run.

Jim,

Thanks for all of this great info!

Just two more questions for now ...

> • Is all of this safe against re-entry? What happens if I'm trying X and X gets invoked again (during the thread plan execution)?

The most straightforward way to do this would be to write something that just drives the event loop a la:

http://llvm.org/svn/llvm-project/lldb/trunk/examples/python/process_events.py

Then recursion won't bother you. The step plans stack, so if you do your "step-out" and before the step out completes you hit the breakpoint again, that's okay, the first step-out plan is still on the plan stack. Do your "step-out" for the second breakpoint hit, and when that completes, "continue" and the original step-out plan will then get a chance to complete. And since plans are per thread, doing this on multiple threads won't cause any problems either.

Ahh, I was going to do this from a command inside the LLDB CLI for now... Will that work well?

You will still have to do some event handling in your command, since you want to stop, run a bit, then stop again. But other than that it should be okay.

Also, will this prevent other threads from running while I have a function trace happening? (The comments in the code about this aren't clear to me.)

The thread plans all have different policies about letting other threads run, and all the execution control commands should have a --run-mode option to control this behavior. In general, though, the default for all the plans will be to let all threads run whenever we're doing something that could run arbitrary amounts of code. For instance, step-out will generally let all threads run while doing the step-out.

If you actually want to suspend the other threads while tracing, and deal with deadlocks yourself, you can set the --run-mode to "this thread" or use "thread suspend/resume" manually (if those are supported on your platform.

Jim