views:

373

answers:

5

I have inherited a fairly large codebase, 90% C++, and I need to get up to speed on it quickly. There are hundreds of .cc files in a wide directory tree structure.

It's fairly complex, and has no logging. In order to figure out how some major subsystems work, I want to insert a function call into every function.

E.g., given a .cc file full of stuff like this:

void A::foo(int a, int b) {
    // ...
}

void A::bar() {
    // ...
}

void B::bleh(const string& in) {
    // ...
}

I'd like to get this:

void A::foo(int a, int b) {
    LOG(debug) << "A::foo() called.";
    // ...
}

void A::bar() {
    LOG(debug) << "A::bar() called.";
    // ...
}

void B::bleh(const string& in) {
    LOG(debug) << "B::bleh() called.";
    // ...
}

This can be done via python script, CMD script, power shell script, etc. If there is a way to make VS do it, great. Whatever works. Doesn't have to be pretty, I'm not checking any of this in.

Also, it doesn't necessarily need to get everything. E.g. nested classes, implementations in header files, etc.

+1  A: 

A run-time profiler will kind of give you that information: it will tell what subroutines were called from each routine, and how many times (but not, in what sequence).

ChrisW
Yeah, I've tried a bunch of different (free) profilers, including the one from Microsoft. Either they all suck or I'm too stupid to figure out how to use them. If you have a link to a decent tool and some instructions on how to get the above information you promise, I would be more than willing to give it a go. At this point it just seems easier to insert some logging.
jeffamaphone
I haven't used one in ages. The one I used (from Numega) has been sold (to Compuware), and then resold is now owned by "Micro Focus": http://www.microfocus.com/products/DevPartner/StudioProfessionalEditionCapabilities.asp#6 Their web site says you can download a trial version of it.
ChrisW
+2  A: 

I did it some years ago in VS.
Regex will help you.
BTW, it not nesasary to insert different string. You can add the same string like:


LOG(debug) << __FUNCTION__ << " called.";

EDIT

something like this regexp (valid for VS only):


(void|char|int):b+:i\:\::i\([^(]*\):b*\{

You should extend the regexp depending of your needs.

Dmitriy
Ah yes, __FUNCTION__... good point. You wouldn't happen to have a halfway decent regex, would you?
jeffamaphone
@jeffamaphone: I add a sample of the regexp to my answer
Dmitriy
This might work if all of your function declarations follow a simple pattern, but it's unlikely to catch all functions in a large, complex codebase since function declarations can take very unusual forms (some of which are hard to distinguish from other types of declarations).
j_random_hacker
@j_random_hacker: You are right. Indeed, I used about 10 regexp to find functions. Using it I found about 95% of functions (in very complex project) - not bad at all.
Dmitriy
+1  A: 

Have you considered running the code within a debugger, and simply stepping through the entire application (or otherwise setting a breakpoint on the code you're interested in and just stepping through that)? I find that to sometimes be a useful technique when faced with a large legacy code base that I didn't write.

Alternatively, if you're compiling in the VS world, consider taking a look at the /Gh and /GH switches to cl.exe. They seem to allow you to hook function entry/exit and call some other routine. I've never used them before, but they seem to directly address your need.

Reuben
Oh yeah, I do that all day. There's just a lot of async, multi-threaded stuff going on, and I'm looking to get a higher-level picture of how this thing works.
jeffamaphone
+4  A: 

Since you're using Visual C++, and it seems you only need the name of the function called, it might be possible to automate this further using the following command-line switches to cl.exe:

  • /Gh: Enable _penter function call
  • /GH: Enable _pexit function call

Basically, providing these switches means that the compiler will automatically inject calls to functions named _penter() and _pexit() whenever any function begins or ends. You can then provide a separately-compiled module that implements these two functions, which either (a) calls some helper library such as DbgHelp to determine the name of the called function, or (b) just grabs the return address from the stack and prints it verbatim -- afterwards, write a script to transform these addresses into function names by looking at e.g. the linker map file produced if you pass /link /MAP:mymapfile.txt to cl.exe.

Of course, you'll need to put your _penter() and _pexit() in a separate module with /Gh and /GH turned off to avoid infinite recursion! :)

j_random_hacker
Hey, that might be fun. Thanks.
jeffamaphone
You do not need a separate module for the prolog/epilog functions. Both functions '_penter()' and '_pexit()' are to be defined like this: 'extern "C" void __declspec(naked) _cdecl _penter( void )'. Naked functions are not instrumented so there is no chance of recursion. You do have to worry about recursion on children functions but that can be solved using a thread local variable check to early out in _penter() and _pexit() before calling any children.
Adisak
+4  A: 

Had something similar for adding profiling code using Macros in VS, here's the code (this also groups everything under a single "undo" command and lists all of the changes in its own output window)

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics

Public Module Module1

    Function GetOutputWindowPane(ByVal Name As String, Optional ByVal show As Boolean = True) As OutputWindowPane
        Dim window As Window
        Dim outputWindow As OutputWindow
        Dim outputWindowPane As OutputWindowPane

        window = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
        If show Then window.Visible = True
        outputWindow = window.Object
        Try
            outputWindowPane = outputWindow.OutputWindowPanes.Item(Name)
        Catch e As System.Exception
            outputWindowPane = outputWindow.OutputWindowPanes.Add(Name)
        End Try
        outputWindowPane.Activate()
        Return outputWindowPane
    End Function

    Const ToInsert As String = "/* Inserted text :D */"

    Sub AddProfilingToFunction(ByVal func As CodeFunction2)
        Dim editPoint As EditPoint2 = func.StartPoint.CreateEditPoint()
        While editPoint.GetText(1) <> "{"
            editPoint.CharRight()
        End While

        editPoint.CharRight()
        editPoint.InsertNewLine(1)

        Dim insertStartLine As Integer = editPoint.Line
        Dim insertStartChar As Integer = editPoint.LineCharOffset
        editPoint.Insert(ToInsert)

        GetOutputWindowPane("Macro Inserted Code").OutputString( _
            editPoint.Parent.Parent.FullName & _
            "(" & insertStartLine & "," & insertStartChar & _
            ") : Inserted Code """ & ToInsert & """" & vbCrLf)
    End Sub

    Sub AddProfilingToProject(ByVal proj As Project)
        If Not proj.CodeModel() Is Nothing Then
            Dim EventTitle As String = "Add Profiling to project '" & proj.Name & "'"
            GetOutputWindowPane("Macro Inserted Code").OutputString("Add Profiling to project '" & proj.Name & "'" & vbCrLf)
            DTE.UndoContext.Open(EventTitle)
            Try
                Dim allNames As String = ""
                For i As Integer = 1 To proj.CodeModel().CodeElements.Count()
                    If proj.CodeModel().CodeElements.Item(i).Kind = vsCMElement.vsCMElementFunction Then
                        AddProfilingToFunction(proj.CodeModel().CodeElements.Item(i))
                    End If
                Next
            Finally
                DTE.UndoContext.Close()
            End Try
            GetOutputWindowPane("Macro Inserted Code").OutputString(vbCrLf)
        End If
    End Sub

    Sub AddProfilingToSolution()
        GetOutputWindowPane("Macro Inserted Code").Clear()
        If Not DTE.Solution Is Nothing And DTE.Solution.IsOpen() Then
            For i As Integer = 1 To DTE.Solution.Projects.Count()
                AddProfilingToProject(DTE.Solution.Projects.Item(i))
            Next
        End If
    End Sub

End Module

P.S Remember to change the "Const ToInsert As String = ..." to the code you actually want to be inserted

Grant Peters
This is an interesting approach! +1. I would bet that the VS IDE does a better job of discovering the beginning of function definitions than a hand-built regex. But assuming "CodeModel" is the same thing as IntelliSense uses, there are a few cases it will still choke on in my experience.
j_random_hacker
This is pretty nifty as well. I'll give it a shot.
jeffamaphone
Is there a way to exclude header files (.h and .inl)? Also, perhaps exclude templates?
jeffamaphone
don't really know about excluding templates, but i'm sure the information is there (might try looking at the parent object of the function, and if thats a class, check what type of class it is, check out "kind" variables). As for excluding the .h/.inl files, "editPoint.Parent.Parent.FullName" will return the name of the file being edited, should just be able to check the last few characters to see if it matches the file types you want to include/exclude.
Grant Peters