基于Trace的调试方法

简介

最简单的gem5调试方法是让gem5自己打印其运行时的跟踪信息。gem5模拟器本身已经包含了许多DPRINTF语句,这些语句可以打印潜在关键事件的跟踪描述信息,而且每个DPRINTF语句都与一个调试标志(debug flag)相关联(例如,总线、缓存、以太网、磁盘等)。

用户可以使用--debug-flags命令行参数来打开特定标志的消息,并可以通过给出一个字符串列表同时指定多个标志(flags),比如: 

build/<ISA>/gem5.opt --debug-flags=Bus,Cache configs/examples/fs.py

上述命令会打开一组与指令执行过程(execution)相关的调试标志,但省略Tick(计时)信息,这在比较两次模拟仿真运行相同的指令但是执行速度不同的情况下非常有用。

特别需要注意,gem5快速模式的二进制文件(gem5.Fast)不支持跟踪,因为它将DPRINTF代码排除在外再进行编译,这也是它比gem5.opt更快的部分原因。

命令行中的选项是放在模拟脚本之前还是之后,决定了其是用于gem5可执行文件还是模拟脚本,因为调试标志是由gem5处理的,因此--debug-flags命令行选项应该跟在gem5可执行文件之后,但需要在模拟脚本之前。

# 调试选项
-----------------
--debug-break=TIME[,TIME]     # 设置创建断点的tick
--debug-help                  # 打印调试标志相关的帮助信息
--debug-flags=FLAG[,FLAG]     # 设置调试输出的标志(“-FLAG”前导减号禁用相关调试标志) 
--debug-start=TIME            # 在TIME时候开始输出调试信息(必须以tick为单位)
--debug-file=FILE             # 设置调试输出文件[默认值:cout]
--debug-ignore=EXPR           # 忽略EXPR sim objects

如果用户想要看到调试/跟踪标志的完整列表,可以使用--debug-help选项运行gem5。

如果用户感兴趣的事件未被跟踪(traced),那么用户可以自行增加一些DPRINTF来跟踪它们。用户可以向任意SConscript文件(建议使用与新标志被使用最相关的文件)中简单地增加DebugFlag()命令以添加新的调试标志(debug flags)。如果在某C++源文件中使用了调试标志,则必须同时在该文件中包含头文件:debug/<name of debug flag>.hh

gem5的trace(跟踪)调试方法可以简单地识别( identifying)仿真中的特定时间点,这对于一些复杂的bug调试需要深入研究仿真中特定的时间点非常有用。--debug-break选项允许用户在调试器下重新运行模拟仿真,并在 trace(跟踪)识别到特定时候(tick)停止。用户还可以使用调试器自身来调度(schedule)断点,并启用或禁用调试标志,有关信息可参见基于调试器调试的说明页。 

Exec调试标志 

Exec复合调试标志开启了gem5中的指令跟踪,这一功能非常有用。Exec调试标志让模拟器在每条指令执行完成时打印出一个分解后的指令版本,伴随其他一些有用的信息,例如时间、pc指针、地址(如果是内存指令)等。这些单独的信息可以通过基本调试标志Exec控件来打开和关闭。例如,"--debug-flags=Exec,-ExecSymbol"选项关闭ExecSymbol标志,可以禁止使用函数符号名称代替绝对PC地址(可用时)。

gem5的gem5/util目录下的tracediff脚本内附带的注释描述了该脚本的使用方法,如果用户发现一些看似没有影响的修改导致gem5停止正常工作则可以使用该脚本查找具体原因,即使用src/util/tracediff脚本比较更改前后的跟踪输出(trace outputs)。

减少跟踪文件大小

实际应用中跟踪文件可能迅速变得非常大,但跟踪文件也非常容易压缩(压缩比大约90%),只需要在gem5跟踪文档的输出文件名后添加.gz扩展后缀就可以让gem5输出压缩的跟踪文件。例如,如果使用--debug-file=trace.out选项,gem5会像通常一样生成未压缩文件trace.out,但是使用--debug-file=trace.out.gz就会生成一个gzip格式的压缩文件。用户既可以使用zcat程序和管道来处理输出,也可以使用vim编辑器在内存中解压输出的gzip压缩文件。 

tracediff和rundiff实用程序

gem5/util目录下的tracediffrundiff实用程序允许从gem5中对两个trace数据流进行简单的区分,以发现任何差异。它非常方便调试为什么回归测试失败,弄清楚为什么对代码做很小的更改似乎会引起一些不相关的执行问题,或者比较CPU模型的执行过程。

rundiff是一个类似常规diff的简单程序,但是它与常规diff不同,rundiff脚本在对其输入进行比较之前不会读取整个输入,因此它可以用于其他程序管道的冗长(lengthy)输出(例如,gem5的traces)。tracediff是rundiff的前端,它提供了一种简单的方法来运行两个类似的gem5副本,并比较它们的输出:tracediff采用一个带有嵌入式替代方案的通用gem5命令行,并在单独的子目录中执行两个替代(alternative )命令,输出以管道方式传递给rundiff。 

脚本的参数按如下方式统一处理: 

  • 如果参数不包含"|"字符,那么该参数会被附加到各(两个)命令行。
  • 如果参数中包含一个"|"字符,则"|"符号左右两边的文本分别被附加到各自对应的命令行。请注意,用户必须将参数使用单引号(' ')引起(能否使用双引号待确认)或对"|"符号使用反斜杠转义以防止shell错认为管道操作或者在它周围加上引号。
  • 处理"|"时,带"#"字符的参数作为独立项分割出来,然后作为单个参数粘贴回去(粘贴时不带"#")。(有点受C预处理器的"##"标记(token)粘贴操作符的启发。) 

换句话说,参数看起来像待运行的命令行中使用"|"符号同时列出两次运行中,不同方案替换的不同部分。例如: 

% tracediff gem5.opt --opt1 '--opt2|--opt3' --opt4
# 上述命令行相当于对以下两次运行比较:
gem5.opt --opt1 --opt2 --opt4
gem5.opt --opt1 --opt3 --opt4

% tracediff 'path1|path2#/m5.opt' --opt1 --opt2
# 上述命令行相当于对以下两次运行比较:
path1/gem5.opt --opt1 --opt2
path2/gem5.opt --opt1 --opt2

如果用户只想给两个运行当中的其中之一添加参数,只需要在"|"符号的其中一边添加参数文本(--onlyOn1|),还可以向一边同时添加多个参数(例如,使用|-a -b -c为第二个运行添加三个参数)。


tracediff的-n参数允许用户在不运行脚本的条件下预览两个生成的命令行。 

必须启用(enabled)一些跟踪标志,tracediff实用程序才可以实用(useful),与tracediff一同使用的最常见的跟踪标志是--debug-flags=Exec, -ExecTicks,它从每个跟踪中删除时间戳,这使tracediff能够在跟踪中存在轻微的时间差异时进行比较。 

当两个CPU模型运行时,其中一个失败了,而另一个没有,这时使用Tracediff来比较这两个CPU模型也非常有效。这种情况下,建议创建一个问题发生之前的检查点,可以通过创建大量检查点并找到其中一个失败的检查点来实现。具体做法是:如果失败发生在内核代码中,就使用-ExecUser调试标志,相反,如果它发生在用户代码中,则使用-ExecKernel调试标志来隔离跟踪(trace)中的用户代码,然后比较跟踪(trace),检查执行过程在什么时候出现不同。 

在不同的机器间比较轨迹(Trace)

有些时候gem5在不同的环境中执行会莫名其妙的不同,除了尝试在同一台机器上重现这些环境,用户还可以使用netcat工具实现rundiff在网络环境下不同系统上运行的gem5实例跟踪(trace)的比较。

具体做法是,先在一台机器上启动运行rundiff,将之配置为比较gem5本地实例和netcat服务器("server")的跟踪输出,由于网络带宽很有可能成为瓶颈因素,一般应当压缩通过netcat的跟踪,相应地,在跟踪到达的时候也需要对它进行解压缩。举个例子,假设任意选择的端口号为33335,在第一台机器上输入命令行:

util/rundiff 'gem5.opt --debug-flag=Exec <gem5 args> |' 'nc -d -l 33335 | gunzip -c |' >& tracediff.out &

然后,转到第二台机器上启动gem5的一个副本,并将其压缩跟踪输出发送到第一台机器上运行的netcat实例,即输入如下命令行: 

gem5.opt --debug-flag=Exec <gem5 args> |& gzip -c |& nc <hostname> 33335

内部Exec跟踪实现(InstTracer)

上面的“基于跟踪的调试”部分讨论了如何使用Exec跟踪标志来打印每条指令完成时的信息。该功能实际上是由InstTracer对象实现的,该对象在执行时收集有关指令的信息。这些对象可以被换出,不同的对象可以根据它们收集的信息做不同的事情。例如,IntelTrace对象打印出与外部工具兼容的不同格式的跟踪信息。这些对象还可以做更多的事情,而不仅仅是打印一个跟踪。NativeTrace对象通过socket套接字将有关体系结构状态的信息逐个指令发送到状态跟踪工具(如下所述),以验证执行过程。InstTracer对象是分配给每个CPU的tracer参数的SimObjects。如果想安装不同的跟踪程序(tracer),只需将其分配给感兴趣的CPU上的该参数。 

用户编写自己的InstTracer时,至少要编写两个不同的类,一个继承自InstTracer,另一个继承自InstRecordInstTracer类主要负责生成与特定指令相关联的InstRecord对象,通过继承InstTracer类用户能够返回自己特殊版本的InstRecord,这个特殊版本的InstRecord是真正完成大部分工作的类。

InstRecord类有许多保存指令历史信息的字段。例如,InstRecord类记录指令的PC指针,如果该指令访问内存,则还记录它使用的地址,另外还有它产生的“数据”值(不处理多个数据值)等等。InstRecord函数还有一个指向ThreadContext的指针,该指针可以用来读取架构状态。当一条指令完成执行过程时,将调用InstRecord类的dump()虚函数来处理记录(record)过程。默认的InstTracer类,是用户打开Exec调试标志时看到的输出地方,包含了指令的汇编语言形式等的打印内容,而NativeTrace类,则是收集架构状态并发送到statetrace的地方。 

使用第三方反汇编器反汇编指令

大多数继承自上文提到的InstTracer的gem5跟踪程序都将打印/转储(print/dump)动态指令流以及诸如目标寄存器值等的一些其他信息。反汇编是通过查询要跟踪的指令(StaticInst)动态生成的,每个StaticInst都应该定义一个“generateDisassembly”方法,该方法以字符串形式返回指令助记符(操作码+操作数列表)。 

自v23.1版起开始,gem5每个InstTracer上可以挂接不同的反汇编器,反汇编程序必须实现src/sim/insttracer.hh头文件中定义的InstDisassembler接口。 默认情况下使用原生(native)反汇编器(依赖于generateDisassembly),如果要改用自定义的反汇编器(这里假设称之为MyDisassembler),则只需按照如下方式修改配置文件:

cpu.tracer.disassembler = MyDisassembler()

Capstone反汇编器 

自v23.1版起gem5开始引入对Capstone开源的反汇编器的集成capstone反汇编器也被用于QEMU等其他项目中。

如果要编译支持capstone的gem5,则应先安装capstone,然后必须在config脚本中实例化capstone反汇编器。在本文撰写之时,只实现了Arm版本的反汇编器,因此,添加到脚本中的那一行只可能是(假设正好是Arm模拟仿真)以下语句: 

cpu.tracer.disassembler = ArmCapstoneDisassembler()

将trace与真实机器进行比较

statetrace工具伴随gem5一起运行,可以比较工作负载在真实机器上与在gem5中的执行情况。模拟器和真实系统中的工作负载(workload )可以每次只运行一条指令,并在每条指令结束后收集架构状态信息,比较并报告任何差异。设置statetrace工具,使其产生有用的结果(如下所述)可能会比较棘手,但它仍不失为一个非常有价值的调试工具,原因是使用它往往有助于快速准确地定位问题的来源,节约大量的bug调试时间。

Native Trace

在gem5中,需要在感兴趣的工作负载运行的CPU上安装NativeTrace的InstTracer对象(跟踪器,前文所述)。跟踪器(Tracer)在模拟执行过程开始的时候先等待到状态跟踪实用程序与其连接上,然后,在每条指令执行后,使用InstRecord对象中的ThreadContext指针从当前运行的进程中收集架构状态。跟踪器还使用其与状态跟踪实用程序建立的连接,读取状态跟踪(state trace)实用程序收集的架构状态。比较这两个版本的架构状态并报告其中任何有意义的差异,特别要注意,状态的具体组成和比较方法非常依赖特定的指令集架构(ISA),因此针对每个指令集都定义了相应版本的NativeTrace,这些专门的类可以处理诸如预期的寄存器未定义差异,或由于某种原因提前跳过执行过程的情况。

statetrace实用程序

statetrace实用程序也位于gem5/util目录中,它负责在真实机器上运行工作负载。statetrace使用Linux内核提供的ptrace机制对目标进程进行单步跟踪并访问其状态。statetrace也使用scons工具构建,但是它独立于gem5其余部分所使用的scons。用户使用build/${ARCH}/statetrace作为scons构建的target(目标),需要将其中的${ARCH}替换为待研究的指令集(ISA)名称以构建针对相应指令集(ISA)版本的statetrace,目前可以识别的${ARCH}值有amd64armi686sparc四个。用户可以使用名为CXX的scons参数重写所有指令集的编译器,也可以使用针对特定指令集的${ARCH}CXX的scons参数重写相应指令集的编译器。例如,要构建一个arm版本的statetrace,可运行以下命令行: 

cd util/statetrace
scons ARMCXX=arm-softfloat-linux-gnueabi-g++ build/arm/statetrace

statetrace实用工具接受以下四个标志:

  • -h          :用于打印帮助;
  • --host    :用于指定gem5监听的IP和端口;
  • -i           :用于打印初始栈帧上的内容;
  • -nt         :用于禁用跟踪;

statetrace的-nt标志典型的用法是与-i标志一起使用,以达到在不运行跟踪的情况下获取进程初始堆栈的信息的目的。statetrace命令的选项通过使用两个破折号来标记其结尾,其后跟着用户想用statetrace运行的命令行。

程序名称和参数的确切文本非常关键,因为这些将被传递到其堆栈上的进程。更长的值占用堆栈上更多的空间,这将把其他条目移到不同的地址,并且statetrace阻塞了许多不重要的差异。举个例子,如果需要在gem5子目录中运行一个在用户home目录中找到的程序,可按照如下命令行运行statetrace命令:

statetrace -- ~/gem5/my_benchmark arg1 arg2

注意这里必须使用~/gem5/my_benchmark覆盖gem5中的arg0参数。

调整(Tuning)

statetrace是一个非常敏感的系统,模拟执行和真实执行之间的任何微小差异都可能产生大量虚假的差异。为了从statetrace中获得有用的信息,用户需要对真实系统和gem5进行调整,以便一切都完美地对齐。

一种做法是创建一个包含为statetrace对gem5所做的所有修改的补丁,当发现并解决问题时,用户可以轻松地删除或是重新应用这些修改,Mercurial(Mercurial是一个用 Python 编写的分布式版本控制系统)队列非常适合用于管理补丁和修复补丁。以下列举可能需要更正的差异的不完整列表: 

  • 地址随机化:为了提高安全性,Linux会随机化进程的地址空间,在它们的堆栈和堆区域周围移动。这种做法使得攻击者很难预测内存的样子,但它也彻底击败了statetrace。禁用地址随机化需要向/proc/sys/kernel/randomize_va_space文件中相关处写入(echo)0,这通常都需要root权限。 
  • argv值:确保在gem5和真实系统中为用户程序的每个参数使用完全相同的文本,这也包括arg0参数——程序的名称。 
  • 文件块大小:Glibc使用与文件相关联的块大小来决定如何缓冲它,不同的行为会导致执行失败并阻止statetrace工作,用户可以在src/sim/syscall_emul.hh中的convertStatBufconvertStat64Buf函数中更改gem5报告的块大小。
  • 初始堆栈内容:初始堆栈的内容根据使用的Linux版本的不同而可能会有所不同,用户可以使用-i-nt选项在真实机器上打印出初始堆栈的内容。Statetrace试图翻译(interpret)初始堆栈,以便用户可以更容易地看到它上面的内容。用户可能需要调整gem5设置堆栈的方式以匹配自己的真实系统,相关代码通常在对应arch指令集目录下的一个名为process.cc的文件中。gem5的代码已经经过了精心构建,因此它设置的堆栈尽可能与Linux相同,但底层机制可能会发生变化。此外,Linux在初始堆栈上放置了一组辅助向量,它们是成对的类型—值对,这使得内核在进程开始时提供额外的信息。Linux会不时地引入一种新的辅助向量,并将其添加到堆栈中,因此用户可能需要深入研究Linux源代码并模拟任何新的条目。 

警告

由于statetrace对执行过程中的任何变化都非常敏感,所以不能将之用于行为不太可预测的程序。例如,如果一个程序从/dev/random设备中读取一个随机值,并在计算中使用它(或者更糟糕的是在控制流中使用这个随机值),那么该程序就不能使用statetrace。不太明显的是,如果程序依赖于不可预测的系统时间,那么它也不能使用statetrace。一般来说,许多基准程序都非常尽力地确定,以便它们可以被用来生成可复现的数据,这就使得它们可以与statetrace很好地一起使用。 

特别需要注意,statetrace不能在操作系统的级别使用,这至少有两个主要原因。首先,在当下或者可预见的未来。任何操作系统都不会以单步实现。第二,真正的操作系统是不确定的,硬件设备的中断几乎肯定会在不可预测的时间出现。某些设备会返回不可预测的数据,并且,在固件和其他实现细节不再被去抽象的情况下,gem5不太可能完全匹配系统的行为。另外,系统级相关的状态量通常比用户级的大,特别是在像x86这样的复杂指令集中,收集、比较和传输所有额外的状态将显著(significantly)影响性能。

并不是所有ptrace的实现都能正常工作。例如,有一次作者在ARM架构下使用statetrace时,某些函数调用到由内核设置的内存区域,该区域具有针对各种操作的内核特定实现。ptrace依赖于软件断点,其工作原理是将程序中的下一条指令替换为一个会触发陷阱(trap)的指令。因为该片内存区域实际上属于内核,所以ptrace无法修改它来设置断点,进程“逃避(escaped)”了单步执行,并迅速跑至结束(completion),导致gem5一直等待一个不可能到来的更新(update)。
statetrace无法追踪内存的变化情况。由于内存非常大,没有一种方便的方法来检测对它的修改,statetrace只跟踪基于寄存器的架构状态。如果一条指令正确地改变了寄存器,但将错误的值存储到内存以及/或者错误的地址,这个问题对于许多指令来说都可能无法检测得到。幸运的是,这类错误都属于例外(exception)。

将执行过程与真实的机器进行比较的理想做法是使用一台真实的机器,但是,使用qemu这样的模拟器并在其中运行statetrace也是可能的,使用模拟器的速度可能会稍微慢一点,而且与模拟器比较并不是与真正的硬件进行比较,但是进行这种比较还是有助于识别各种错误(bugs)。

对指令集的支持

目前SPARCARMx86架构支持状态。ARM状态的支持是目前最复杂的,只在连接中发送状态差异以提高性能,并且只在差异开始或停止时打印,这减少了输出并提高了可读性。后续计划包括将这些特性移植到其他指令集上,这些代码有望被分离出来并放入基本的NativeTrace类中,以便所有指令集都可以轻松地使用它。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于packet trace的小型企业网络设计主要是通过收集和分析网络数据包来优化网络性能和安全性。首先,需要在企业网络中设置流量监控器,以便实时抓取数据包,并将数据包记录到日志文件中进行分析。通过分析数据包的发出和接收情况,可以了解到网络的拥堵情况、流量分布情况以及可能存在的安全隐患。 在小型企业网络设计中,可以根据packet trace的分析结果进行以下优化:首先,根据数据包的分析结果,调整网络拓扑结构,优化网络设备的部署位置,减少网络拥堵和延迟;其次,在应用层面,通过分析数据包的内容和协议信息,优化网络应用程序的性能和稳定性,提高用户体验;再次,通过检测数据包中的异常流量和不正常的行为,提高网络安全性,保护企业数据不受攻击。 此外,基于packet trace的小型企业网络设计还可以为网络运维和安全团队提供有价值的数据分析和报告,帮助他们更好地管理和维护企业网络。通过持续的数据包分析,可以及时发现并解决网络问题,提高网络的稳定性和可靠性。同时,也可以根据数据包的分析结果,及时采取安全措施,保护企业的网络不受攻击和恶意软件的侵害。 综上所述,基于packet trace的小型企业网络设计能够通过对网络数据包的分析,提高网络性能、优化网络安全,并为网络运维团队提供有价值的数据支持,促进企业网络的可持续发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值