【问题标题】:Script to insert logging into every function in a project?将日志记录插入项目中每个功能的脚本?
【发布时间】:2009-09-18 03:18:08
【问题描述】:

我继承了一个相当大的代码库,90% 是 C++,我需要快速上手。宽目录树结构中有数百个 .cc 文件。

它相当复杂,并且没有日志记录。为了弄清楚一些主要子系统是如何工作的,我想在每个函数中插入一个函数调用。

例如,给定一个 .cc 文件,其中包含以下内容:

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

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

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

我想要这个:

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.";
    // ...
}

这可以通过 python 脚本、CMD 脚本、power shell 脚本等来完成。如果有办法让 VS 做到这一点,那就太好了。什么都行。不一定要漂亮,我不会检查这些。

此外,它不一定需要获取所有内容。例如。嵌套类、头文件中的实现等

【问题讨论】:

  • 你是怎么做到的?

标签: c++ windows logging scripting


【解决方案1】:

在 VS 中使用宏添加分析代码时有类似的东西,这里是代码 (这也将所有内容分组在一个“撤消”命令下,并在其自己的输出窗口中列出所有更改)

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

附言 请记住将“Const ToInsert As String = ...”更改为您实际要插入的代码

【讨论】:

  • 这是一个有趣的方法! +1。我敢打赌,VS IDE 在发现函数定义的开头方面比手工构建的正则表达式做得更好。但假设“CodeModel”与 IntelliSense 使用的是相同的东西,根据我的经验,在某些情况下它仍然会卡住。
  • 这也很漂亮。我试试看。
  • 有没有办法排除头文件(.h 和 .inl)?另外,也许排除模板?
  • 真的不知道排除模板,但我确定信息在那里(可能会尝试查看函数的父对象,如果那是一个类,请检查它是什么类型的类是,检查“种类”变量)。至于排除 .h/.inl 文件,“editPoint.Parent.Parent.FullName”将返回正在编辑的文件的名称,应该能够检查最后几个字符以查看它是否与您想要的文件类型匹配包括/排除。
  • 如何运行这个宏?有什么建议吗?
【解决方案2】:

由于您使用的是 Visual C++,而且您似乎只需要调用函数的名称,因此可以使用以下命令行切换到 cl.exe 来进一步自动化此操作:

  • /Gh:启用_penter函数调用
  • /GH:启用_pexit函数调用

基本上,提供这些开关意味着编译器将在任何函数开始或结束时自动注入对名为_penter() 和_pexit() 的函数的调用。然后,您可以提供一个单独编译的模块来实现这两个函数,它可以 (a) 调用一些帮助程序库,例如 DbgHelp 来确定被调用函数的名称,或者 (b) 只是从堆栈中获取返回地址并逐字打印——然后,编写一个脚本,通过查看例如将这些地址转换为函数名称。将/link /MAP:mymapfile.txt 传递给cl.exe 时生成的链接器映射文件。

当然,您需要将_penter() 和_pexit() 放在单独的模块中,关闭/Gh 和/GH 以避免无限递归! :)

【讨论】:

  • 嘿,这可能很有趣。谢谢。
  • 前言/结语功能不需要单独的模块。函数 '_penter()' 和 '_pexit()' 的定义如下: 'extern "C" void __declspec(naked) _cdecl _penter( void )'。裸函数没有被检测,因此没有递归的机会。您确实必须担心子函数的递归,但这可以在调用任何子函数之前使用线程局部变量检查在 _penter() 和 _pexit() 中提前解决。
【解决方案3】:

几年前我在 VS 中做过。
正则表达式将为您提供帮助。
顺便说一句,插入不同的字符串并不需要。您可以添加相同的字符串,如:


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

编辑

类似这样的正则表达式(仅对 VS 有效):


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

您应该根据需要扩展正则表达式。

【讨论】:

  • 啊,是的,FUNCTION...好点。你不会碰巧有一个中规中矩的正则表达式,对吗?
  • @jeffamaphone:我在答案中添加了一个正则表达式示例
  • 如果你的所有函数声明都遵循一个简单的模式,这可能会起作用,但它不太可能捕获大型复杂代码库中的所有函数,因为函数声明可能采用非常不寻常的形式(其中一些很难区别于其他类型的声明)。
  • @j_random_hacker:你是对的。事实上,我使用了大约 10 个正则表达式来查找函数。使用它我发现了大约 95% 的功能(在非常复杂的项目中)——一点也不差。
【解决方案4】:

运行时分析器会为您提供这样的信息:它会告诉您从每个例程中调用了哪些子例程,以及调用了多少次(但不是以什么顺序调用)。

【讨论】:

  • 是的,我尝试了许多不同的(免费)分析器,包括来自 Microsoft 的分析器。要么他们都很烂,要么我太愚蠢了,不知道如何使用它们。如果您有一个不错的工具的链接以及有关如何获得上述信息的说明,我将非常愿意试一试。此时插入一些日志似乎更容易。
  • 我已经很久没用过了。我使用的那个(来自 Numega)已经卖掉了(给 Compuware),然后转售现在归“Micro Focus”所有:microfocus.com/products/DevPartner/…他们的网站说你可以下载它的试用版。
【解决方案5】:

您是否考虑过在调试器中运行代码,然后单步执行整个应用程序(或者在您感兴趣的代码上设置断点并单步执行)?我发现在面对大量不是我编写的遗留代码库时,这有时是一种有用的技术。

或者,如果您在 VS 世界中进行编译,请考虑查看 /Gh 和 /GH 切换到 cl.exe。它们似乎允许您挂钩函数进入/退出并调用其他例程。我以前从未使用过它们,但它们似乎直接满足了您的需求。

【讨论】:

  • 哦,是的,我整天都在这样做。有很多异步、多线程的东西在发生,我希望能更深入地了解它是如何工作的。
猜你喜欢
  • 2014-09-12
  • 2011-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
相关资源
最近更新 更多