【问题标题】:Tracing code from user to hardware跟踪从用户到硬件的代码
【发布时间】:2012-06-13 22:05:00
【问题描述】:

我很好奇是否有人可以在这里为我指出正确的方向。我正在学习计算机系统编程(基础知识),并且我正在尝试通过不同级别跟踪代码以查看每个级别如何与其他级别交互。例如,在 C 中调用 fgets() 函数或在 C++ 中调用 getline() 或类似函数。这两个都会调用系统吧?有没有一种简单的方法可以查看被调用的代码?

我在 Unix (Ubuntu) 上工作。这是 Windows 和 Apple 专有的东西吗?这种事情有什么好的资源吗?一如既往,谢谢大家!

【问题讨论】:

  • 哦,来吧,伙计们。这是一个完全合理的问题,很多初学者都想知道。这当然是“建设性的”,并且有明确的答案。

标签: c system


【解决方案1】:

至少在 UNIX 世界中,答案相当简单:“使用源代码,Luke”。

在您的示例中,您将查看例如 fgetc() 的源代码。那是在 C 标准库中,查找源代码的最简单方法是 google 之类的“C 库 fgets() 源代码”。

当您获得该源代码时,您会看到一堆代码处理缓冲区等,以及一个系统调用,可能是为了read(2)。那里的“2”告诉您它记录在手册的第 2 章中(例如,您可以使用 man 2 read 找到它)。

系统调用是在内核中实现的,所以需要阅读内核源码。从那里继续。

现在,您需要找到这一切而无需在源代码中随机阅读(尽管很多人都是这样学习的,但效率不高)是获得一本关于 Linux 的书,比如 Kerrisk 的The Linux Programming Interface,它解释了其中一些内容,而不仅仅是来源。

【讨论】:

  • 这真的很有帮助,非常感谢!去看看我的大学是否有内核接口的资源......
  • MCP,图书馆里应该有无数关于这些东西的书。如果您想阅读一些实现基础的简单代码,请查看 Lions 书籍:en.wikipedia.org/wiki/…
【解决方案2】:

fgets 位于libc 内。也就是说,它是一个与大多数 C 二进制文件链接的用户空间库。查看glibc,这是目前最常见的实现。

最终,libc 将开始对内核进行系统调用。您可以在kernel.org 获取源代码。查看KGDB 进行内核调试。进行内核调试的最简单方法是使用通过空模型电缆连接的第二台机器。

【讨论】:

    【解决方案3】:

    在 Windows 上,您可以通过一些事情获得一些见解。首先,您需要与您要调查的二进制文件相对应的符号文件。符号文件将文本名称与程序中浮动的全局/堆栈/堆变量相关联。因此,要将内存中的地址映射到函数 fgets,并在某些程序中查看 fgets,您需要具有 Microsoft 实现的 C std 库版本的符号。幸运的是,MS 制作了他们的符号freely available

    其次,您需要捕获比 fgets 更深的调用堆栈。最明显的方法是成为 Microsoft 开发人员并将崩溃引入深层 MS dll,然后使用调试器和符号分析崩溃转储,但不幸的是我们不能这样做。你可以做的是使用所谓的采样分析器,就像在this one 中免费提供的Microsoft 一样。采样分析器通过定期对程序的调用堆栈进行快照来分析您的代码。使用 Microsoft 的符号文件,我们可以将该调用堆栈消化成有意义的东西。

    鉴于这两条信息,构建程序并深入了解 fgets 调用的内容并不难。然后,您可以使用带有 Microsoft 符号的采样分析器来了解您的程序期间发生了什么。

    按照这些思路,我构建了以下程序来尝试一下:

    int FgetSTest()
    {
        FILE* fp;
        fp = fopen("C:/test.txt", "w");
        char data[100];
        int sum = 0;
        for (int i = 0; i < 100; ++i)
        {
            fgets(data, 100, fp);
            sum += data[0];
        }
        fclose(fp);
        return sum;
    
    }
    
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        int sum  = 0;
        for (int i = 0; i < 100; ++i)
        {
            sum += FgetSTest();
        }
        std::cout << sum;
        return 0;
    }
    

    假设您已将其编译为一个程序(我已将其编译为一个名为 perfPlay.exe 的程序),您可以在该 exe 上运行 MS 的采样分析器,如下所示:

    C:\path\to\exe>vsperfcmd /start:sample /output:perfPlay.vsp
    Microsoft (R) VSPerf Command Version 9.0.30729 x86
    Copyright (C) Microsoft Corp. All rights reserved.
    
    
    
    C:\path\to\exe\>vsperfcmd /launch:perfPlay.exe
    Microsoft (R) VSPerf Command Version 9.0.30729 x86
    Copyright (C) Microsoft Corp. All rights reserved.
    
    
    Successfully launched process ID:3700 perfPlay.exe
    sum is:40000
    C:\path\to\exe>vsperfcmd /shutdown
    Microsoft (R) VSPerf Command Version 9.0.30729 x86
    Copyright (C) Microsoft Corp. All rights reserved.
    
    
    Shutting down the Profile Monitor
    ------------------------------------------------------------
    

    获取分析器输出,注意“符号路径”开关将命令指向微软的符号服务器:

     C:\path\to\exe>vsperfreport perfplay.vsp /summary:all /symbolpath:srv*c:\symbols*htt
    

    p://msdl.microsoft.com/download/symbols

    您可以直接检查调用者-被调用者报告的 csv,或者找到一个好的查看器,例如 the one I've been working on,您可以了解 fgets 大部分时间花在哪里:

    可悲的是,并不是很有洞察力。不幸的是,使用这种方法会遇到的一个问题是,许多 fgets 在发布模式下调用的函数很可能是inlined——也就是说,它们几乎作为函数从最终程序及其内容中删除了直接“粘贴”到使用它们的位置。

    您可以尝试在调试模式下重复上述操作以查看您得到的结果,因为内联的机会较小。

    【讨论】:

      【解决方案4】:

      首先要做的事;这项任务需要好的工具。在导航源代码时,我发现 etagscscopegid(来自 GNU idutils)是必不可少的工具。弄清楚如何将其中的一个或多个集成到您最喜欢的编辑器或 IDE 中。切换编辑器或 IDE 以获取这些功能,没有任何借口可以使用糟糕的工具。如果您正在寻找有关某个方面的建议,我喜欢 vim,很多人都在争论 emacs,还有一些人喜欢他们的 Eclipse。

      您需要本地的来源; lxr 是一个了不起的工具,但是对于任何严肃的工作来说,重复的 Web 请求所涉及的延迟都会让人厌烦。在 Debian 派生系统上,这很容易;将目录更改为您希望存储源代码的位置并运行apt-get source eglibc 以下载glibc 源代码。我建议通过 http://www.kernel.orgcloning the master git repository 的 tarball 获取内核源代码(如果您想阅读变更日志或轻松获取更新,这是一个更好的选择——尽管截至 2012 年 6 月它确实扩展到 2.7 GB,所以显然不是t 适合所有人)。

      为 C 库构建标签文件后,您只需运行:vim -t fgets,它将直接打开 libio/bits/stdio2.hfgets() 例程的源代码。 (它的可读性比您希望的要低得多。)遵循这些,直到您最终到达read() 系统调用。 (可能需要一段时间。)

      现在切换到内核源代码。看看fs/read_write.c这个:

      SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
      

      内核使用宏定义系统调用的方式的一个缺点是它使搜索函数变得复杂。 vim -t 直接找不到这个。查找系统调用时最简单的方法是运行gid -s SYSCALL_DEFINE | grep read。 (如果你找到更好的工具,请告诉我。)一旦你找到了系统调用入口点,阅读内核源代码的其余部分就会容易得多。 (我通常也发现它比glibc 来源更易读——尽管距离块级bread() 调用五六个函数调用的日子已经一去不复返了。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多