我很好奇是否有人可以在这里指出我正确的方向。我正在学习计算机系统编程(基础知识),并且我正在尝试通过不同级别跟踪代码以查看每个级别如何与其他级别交互。一个例子是在 C 或C++ 或类似语言中调用fgets()
函数。getline()
这两个都会调用系统吧?有没有一种简单的方法可以查看被调用的代码?
我正在使用 Unix(Ubuntu)。这是 Windows 和 Apple 专有的东西吗?这种事情有什么好的资源吗?一如既往,谢谢大家!
至少在 UNIX 世界中,答案相当简单:“使用源代码,Luke”。
在您的示例中,您将查看 fgetc() 的源代码。那是在 C 标准库中,找到源代码的最简单方法是谷歌,比如“C 库 fgets() 源代码”。
当你得到那个源代码时,你会看到一堆代码处理缓冲区等,以及一个系统调用,可能是read(2)。那里的“2”告诉您它记录在手册的第 2 章中(例如,您可以使用 找到它man 2 read
)。
系统调用是在内核中实现的,所以需要阅读内核源码。从那里继续。
现在,你需要找到这一切,而不必在源代码中随机阅读(尽管很多人都是这样学习的,但效率不高)是拿到一本关于 Linux 的书,比如 Kerrisk 的The Linux Programming Interface,它解释了其中一些东西,而不仅仅是源代码。
某物fgets
位于 内libc
。也就是说,它是一个与大多数 C 二进制文件链接的用户空间库。Check out glibc
,这是目前最常见的实现。
最终,libc
将开始对内核进行系统调用。您可以在kernel.org获得源代码。查看KGDB进行内核调试。进行内核调试的最简单方法是使用通过空模型电缆连接的第二台机器。
在 Windows 上,您可以通过一些事情获得一些见解。首先,您需要与您要调查的二进制文件相对应的符号文件。符号文件将文本名称与程序中浮动的全局/堆栈/堆变量相关联。因此,要将内存中的地址映射到函数 fgets,并在某些程序中查看 fgets,您需要具有 Microsoft 实现的 C std 库版本的符号。幸运的是你 MS 使他们的符号免费提供
其次,您需要捕获一个比 fgets 更深的调用堆栈。最明显的方法是成为 Microsoft 开发人员并将崩溃引入深层 MS dll,然后使用调试器和符号分析崩溃转储,但不幸的是我们不能这样做。你可以做的是使用所谓的采样分析器,就像微软免费提供的一样。采样分析器通过定期对程序的调用堆栈进行快照来分析您的代码。使用 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
------------------------------------------------------------
获取探查器输出,注意“符号路径”开关将命令指向 Microsoft 的符号服务器:
C:\path\to\exe>vsperfreport perfplay.vsp /summary:all /symbolpath:srv*c:\symbols*htt
p://msdl.microsoft.com/download/symbols
你可以直接检查调用者-被调用者报告的 csv,或者找到一个好的查看器,就像我一直在研究的那样,你可以了解 fgets 大部分时间花在哪里:
可悲的是,不是非常有见地。不幸的是,使用这种方法您会遇到的一个问题是,许多 fgets 在发布模式下调用的函数很可能被内联——也就是说,它们几乎作为函数从最终程序及其内容中直接删除“粘贴”到使用它们的位置。
您可以尝试在调试模式下重复上述操作以查看您得到的结果,因为内联的机会较少。
首先要做的事;这项任务需要好的工具。在浏览源代码时,我发现etags
、cscope
和gid
(来自 GNU idutils
)必不可少的工具。弄清楚如何将其中的一个或多个集成到您最喜欢的编辑器或 IDE 中。切换编辑器或 IDE 来获得这些功能,没有任何借口可以使用糟糕的工具。如果你正在寻找一个建议,我喜欢vim
,很多人都在争论emacs
,而且有些人喜欢他们的 Eclipse。
您将需要本地资源;lxr是一个了不起的工具,但是对于任何严肃的工作来说,重复的 Web 请求所涉及的延迟都会让人厌烦。在 Debian 派生系统上,这很容易;将目录更改为您希望存储源代码的位置并运行apt-get source eglibc
以下载glibc
源代码。我建议通过来自http://www.kernel.org的 tarball 获取内核源代码或克隆主git
存储库(如果您想阅读更改日志或轻松获取更新,这是一个更好的选择——尽管截至 6 月它确实扩展到 2.7 GB 2012,所以它显然不适合所有人)。
一旦你为 C 库构建了标签文件,你就可以运行:vim -t fgets
它会直接打开例程libio/bits/stdio2.h
的源代码。fgets()
(它的可读性比您希望的要低得多。)遵循这些,直到您最终获得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()
调用的日子已经一去不复返了。)