许多时间旅行电影中的共同主题是及时回去找出出了问题并解决了它。开发人员也有希望及时回去,并找到代码破坏并修复它的原因。但是,通常,这是一个重要的一步,很久以前就出了问题,信息不再可用。
RR项目让程序员检查C或C ++程序运行的整个寿命,并重播代码执行,以查看过去导致“令人恐惧的事情”中的哪些操作。 RR在Red Hat Enterprise Linux(RHEL)中为Enterprise Linux(EPEL)提供额外的软件包,以及Fedora 31,32,33和34。
RR记录有关执行应用程序的跟踪信息。此信息允许您重复重播特定记录故障并在GNU调试器(GDB)中检查它以更好地调查原因。除了重播跟踪之外,RR允许您反向运行程序,实质上允许您“倒带磁带”以查看程序执行前发生的事情。
RR提供用于记录再现的技术以进行进一步检查的技术可以是传统核心转储和回溯的有用的补充,这在特定时刻提供了一个问题的快照。 RR录制可以为开发人员提供一种方法,以进一步调查间歇性问题,其中一些应用程序运行失败。
让我们看看如何设置RR并在一个示例中使用它以更好地说明其实用程序。
由于RR使用了许多较新的Linux内核功能和特定的处理器性能监控硬件,因此它运行的环境是有限的。较新的Fedora内核具有所需的支持。但是,要正确跟踪和重新创建,当异步事件发生在线程中时,RRUS性能监视硬件事件计数器提供了由异步事件中断线程时的确定性计数。目前,RR仅支持使用Westmere或更新的微体系结构的英特尔处理器的这些计数。
在Fedora上安装RR是一个简单的任务,需要单个RPM。如果您在RHEL上,您需要启用EPEL。一旦访问相应的存储库,就可以使用以下命令安装RR:
由于硬件性能监视计数器可能泄漏特权信息的可能性,许多内核默认为限制其监视功能。您需要运行以下命令以允许RR访问性能监视计数器:
如果要使该设置进行性能监控硬件永久性,您还可以将以下行添加到/etc/sysctl.conf:
以下是一个玩具程序,可在数组中计算2次0,1,2和3并打印信息:
#include< stdio.h> #define size 4. 空白零(char * a,int尺寸) { 而(size> 0) [size-] = 0; } void initialize(char * a,int size) { 零(A,尺寸); } void乘法(Char * A,Int Size,Int Mult) { INT I; for(i = 0; i< size; i ++) [i] = i * mult; } void pr_array(char * a,int尺寸) { INT I; for(i = 0; i< size; i ++) printf(" f(%d)=%d \ n"我,a [i]); } int main(int argc,char ** argv) { char a [size]; int mult = 2; 初始化(a,size); 乘以(A,尺寸,mult); pr_array(a,size); 返回0; }
使用常用命令编译它,如下所示,并包含调试选项-g以稍后调试:
当您运行乘以时,结果可能非常令人惊讶。所有结果都为零。主函数通过2到乘法函数。该功能中的循环非常简单。这可能怎么可能出错了?
您可以调查RR出现问题。记录运行乘法程序,该程序展示了以下命令的问题。如果问题是间歇性的,您可以在问题上看到多个运行:
$ rr记录。/ multiply RR:将执行保存到跟踪目录`/ home / wcohen / .local / share / rr / multiply-0`。 f(0)= 0 f(1)= 0 F(2)= 0 f(3)= 0 要开始调试此问题,请使用RR Replay命令,将您带入GDB会话: (rr)在哪里 #0 0x00007f7f56b1f110在_start()中的/llib64/ld-linux-x86-64.so.2 #1 0x00000000000000000001在?? () #2 0x00007ffe0bea6889在?? () #3 0x000000000000000000在?? () 您可以从一开始就继续该程序,并查看它与以前具有相同的结果: (rr)c 继续。 f(0)= 0 f(1)= 0 F(2)= 0 f(3)= 0 程序收到信号Sigkill,杀死。 0x0000000070000002在?? () 您可以在主函数中设置返回0的断点,并从那里向后工作: (rr)断裂倍增.c:37 断点1在0x401258:文件乘以.c,第37行。 (rr)c 继续。 f(0)= 0 f(1)= 0 F(2)= 0 f(3)= 0 断点1,main(Argc = 1,argv = 0x7ffe0bea5c58)在多次次数上:37 37返回0;
首先,我们检查数组中的值。也许pr_array函数打印错误的值。但这不是问题,因为根据GDB,值都是0:
(rr)打印a [0] $ 5 = 0' \ 000' (rr)打印a [1] $ 6 = 0' \ 000' (rr)打印a [2] $ 7 = 0' \ 000' (rr)打印a [3] $ 8 = 0' \ 000'
也许pr_array损坏了值。让我们在条目上设置一个断点到pr_Array,并使用反向继续命令向后继续执行,以查看在执行PR_Array函数之前的状态。看起来pr_Array正在打印正确的值:
(rr)打破pr_array 断点2在0x4011cc:文件乘以.c,第25行。 (rr)反向继续 继续。 断点2,PR_ARRAY(a = 0x7ffe0bea5b58"",大小= 4),在multiply.c:25 25(i = 0; i< size; i ++) (rr)打印a [0] $ 9 = 0' \ 000' (rr)打印a [1] $ 10 = 0' \ 000' (rr)打印a [2] 11美元= 0' \ 000' (rr)打印a [3] $ 12 = 0' \ 000'
繁殖函数中可能出现问题。让我们在它上设置一个断点并反转 - 继续它:
RR)断裂乘法 断点3在0x401184:文件乘以.c,第18行。 (rr)反向继续 继续。 断点3,乘以(a = 0x7ffe0bea5b58"",size = 4,multi = 0),在multiply.c:18 18 for(i = 0; i0) (rr)c 继续。
Mult参数发生了什么?它应该是2.任何零时间都是零。这解释了结果。但是,主函数的局部变量MULL最终是零的如何?它以主要初始化并仅传递给计算函数。让我们在Mult上设置硬件观察点,并继续执行程序的反向执行:
(rr) #1 0x0000000000000121247在main(argc = 1,argv = 0x7ffe0bea5c58),在多次数:35 35乘以(a,尺寸,mult); (RR)观察-L mult 硬件观察点4: - 分配mult (rr)反向继续 继续。 硬件观察点4: - 分配mult 旧值= 0 新值= 2 零(a = 0x7ffe0bea5b58"" size = 3)在多元化.c:7 7 A [尺寸 - ] = 0;
啊,现在它变得明确出了问题。零函数写过一个数组的末尾并覆盖多变量,即使它未通过。大小 - 语句应该是 - 尺寸。我们可以在初始化函数中查看隐藏Zero的呼叫:
(rr)在哪里 #0零(a = 0x7ffe0bea5b58"" size = 3)在多次数:7 在初始化中#1 0x000000000000401173(a = 0x7ffe0bea5b58""大小= 4) 在多次数:12 main(Argc = 1,Argv = 0x7FFe0BEA5C58)中的#2 0x0000000000004012133
如果我们想要的话,我们可以使用常规GDB继续并通过我们早先设置的那些断点来播放并进行:
(rr)c 继续。 硬件观察点4: - 分配mult 旧值= 2 新值= 0 零(a = 0x7ffe0bea5b58"" size = 3),在多次数下:6 6时(尺寸> 0) (rr)c 继续。 断点3,乘以(a = 0x7ffe0bea5b58"",size = 4,multi = 0),在multiply.c:18 18(i = 0; i< size; i ++) (rr)c 继续。 断点2,PR_ARRAY(a = 0x7ffe0bea5b58"",大小= 4),在multiply.c:25 25(i = 0; i< size; i ++)
现在我们已经确定了问题,我们可以在“新的和改进的”乘以2.c程序中修复零函数,并按预期工作的工作:
虽然RR是对帮助开发人员在程序中找到问题的工具的有用补充,但它确实有局限性:
RR记录在单个核心上运行的所有线程,因此多线程应用程序将更慢。 RR Syscall Monitoring不完整,因此某些Syscalls可能会穿过裂缝,而不是在轨迹中录制。 rr使用ptrace监视应用程序,并与使用ptrace的应用程序不符。 RR不会监视其在录制内容的子项外的进程,并通过共享内存对外流程进行任何通信。 RR在执行程序的执行中返回返回的能力,以调查导致问题的前面的事件是对开发人员的一组工具的有用补充。 本文中的示例说明了如何使用RR跟踪问题。 该示例只是一个玩具,但RR已被用来追踪更大的大量应用程序,例如JavaScriptCore,Apache Httpd Server和Ruby上的Ruby。