qemu文档翻译-tracing

谭京
2023-12-01

= Tracing =

== 介绍 ==

本文档介绍了QEMU中的trace踪基础结构,以及如何将其用于调试,分析和观察执行。

== 快速开始 ==

  1. 使用simple 作为tracing后端进行构建
./configure --enable-trace-backends=simple
make
  1. 创建一个文件用于追踪你感兴趣的事件:

echo memory_region_ops_read >/tmp/events

  1. 运行qemu来生成trace文件:

qemu --trace events=/tmp/events … # 其他参数

  1. 解析并打印前面生成的trace文件:

./scripts/simpletrace.py trace-events-all trace-* # 使用qemu的pid替换*

== 可跟踪的事件 ==
注: 文档中没有写内容,请参考代码

=== 子目录设置 ===
在源码树下面的每个文件夹里面都可以创建名字叫trace-events的文件,这个文件用来描述静态的trace 事件。所有的trace-events文件必须通过最顶层的Makefile.objs文件的trace-events-subdirs变量列出。在构建的过程中, 会使用tracetool来处理trace-events-subdirs变量列出的文件夹下的每一个trace-events文件,来生成相关的trace-event代码。

各个“ trace-events”文件合并为一个“ trace-events-all”文件,该文件也以“ trace-events”的名称安装到“ /usr/share/qemu”中。
该合并的文件将由“ simpletrace.py”脚本使用,以便以后以simpletrace数据格式分析跟踪。

在子目录中(包含trace-events文件的目录),以下文件将自动生成:

  • trace.c - trace 事件状态描述
  • trace.h - 跟踪事件枚举和探测功能
  • trace-dtrace.h - DTrace事件探针规范
  • trace-dtrace.dtrace - DTrace事件探针帮助程序声明
  • trace-dtrace.o - 二进制DTrace提供程序(由dtrace生成)
  • trace-ust.h - UST事件探针帮助程序声明

子目录下的源文件应该使用#include "trace.h"来导入头文件, 不应该使用子文件夹作为前缀,比如io/channel-buffer.c应该使用#include “trace.h” 来访问io/trace.h文件,虽然io/channel-buffer.c可以引入trace.h, 但是不建议这么做。例外情况就是顶级目录的trace-events文件中定义了一些共享的跟踪事件,顶级目录生成的跟踪文件名为trace-root.h,不是trace.h。 这是为了避免当前目录中trace.h与顶级目录的trace.h发生歧义。
Source files in the sub-directory should #include the local ‘trace.h’ file,
without any sub-directory path prefix. eg io/channel-buffer.c would do

=== 使用trace event ===

跟踪事件的埋点直在写源代码中,如下所示:


    #include "trace.h"  /* needed for trace event prototype */
    
    void *qemu_vmalloc(size_t size)
    {
        void *ptr;
        size_t align = QEMU_VMALLOC_ALIGN;
     
        if (size < align) {
            align = getpagesize();
        }
        ptr = qemu_memalign(align, size);
        trace_qemu_vmalloc(size, ptr);
        return ptr;
    }

=== 声明trace event ===

“ tracetool”脚本生成trace.h头文件,trace.h被导入(#include)到每个使用跟踪事件的源文件中。 由于许多源文件都导入trace.h,因此trace.h使用尽量少的类型和头文件来保持名称空间的整洁并降低编译时间和依赖性。

跟踪事件应使用以下类型:

  • 使用stdin.h中的类型作为固定长度的类型。 大多数偏移值和来宾机上的内存地址最好使用uint32_t 或者uint64_t。使用固定类型来覆盖原始类型不会由于使用不同平台导致长度变化,所以trace event的值不会被截断,也不会导致编译失败。
  • 使用void *作为结构体或者数组指针。trace.h不能包含所有的用户声明的结构体,因此有必要对指向结构的指针使用void *。

-对于其他所有内容,请使用具有适当符号的原始标量类型(char,int,long)。

  • 避免使用浮点类型(float,double)。因为SystemTap不支持。大多数情况下可以将浮点数舍入为整数。也可以将浮点数乘以1000来保留小数点后面的数值。

  • 对于格式化字符串请注意使用PRId64和PRIu64来格式化int64_t和uint64_t类型,以确保32位和64位平台的可移植性。格式字符串不能以换行符结尾,trace event后端实现会添加换行符。

每个事件声明都将以事件名称开头,然后是其参数,最后是格式化字符串。 例如:


> qemu_vmalloc(size_t size, void *ptr) "size %zu ptr %p" 
> qemu_vfree(void *ptr) "ptr %p"

=== 新增事件的提示 ===

  1. 跟踪状态的改变,代码中需要跟踪的点通常涉及状态更改,例如开始,停止,分配,释放。 状态更改是很好的跟踪事件,因为它们可用于了解系统的执行。

  2. 跟踪来宾机的操作。 来宾机的I/O访问(例如读取设备寄存器)是很好的跟踪事件,因为它们可用于了解来宾机的交互。

  3. 使用相关器字段,以便可以理解跟踪输出的单个行的上下文。 例如,跟踪malloc返回的指针并用作free的参数。 这样就可以匹配malloc和free。没有上下文的跟踪事件不是很有用。

  4. Name trace events after their function. If there are multiple trace events
    in one function, append a unique distinguisher at the end of the name.

== 通用接口和监视命令==
你可以使用trace/control.h提供的接口,通过编程来实现与后端无关的状态的控制和查询。

注:这里的后端之trace event后端的实现,qemu支持dtrace ftrace log simple syslog ust

注意一些后端没有提供某些接口的实现, 在这种情况下qemu将打印一个警告(查看trace/control.h了解哪些例程与后端相关)。

trace event状态也可以通过 monitor 命令来查询和修改:

info trace-events

查看可用的trace event和他们的状态。1 代表启用状态, 0代表禁用状态。

trace-event NAME on|off
该命令用于启用trace事件或者事件组(使用通配符控制一组事件)

–trace event= 命令行参数可以启用/禁用中列出的trace event。文件每行描述一个trace event的名称。如果 中的一行以“-”开头代表禁用该行指定的trace event。当使用通配符启用了一些列事件,可以使用“-”来禁用这一系列事件中的某一个事件。

monitor命令和事件列表文件均支持通配符匹配。 这意味着您可以批量启用/禁用具有公共前缀的事件。 例如,可以使用以下监视命令启用virtio-blk跟踪事件:

trace-event virtio_blk_* on

== Trace 后端 ==

“ tracetool”脚本可自动完成繁琐的跟踪事件代码生成,并使跟踪事件声明独立于跟踪后端。 跟踪事件未与特定的跟踪后端紧密结合,例如LTTng或SystemTap。 可以通过扩展“ tracetool”脚本来添加对跟踪后端的支持。

trace后端在编译前执行configure命令的时候确定:

./configure --enable-trace-backends=simple

使用
使用./configure --help 或者参阅下文来查看支持的trace event后端。 可以开启多个后端, trace事件将发送给所有开启的后端进行处理。

如果没有指定具体后端,使用日志作为默认后端。

以下小节描述了受支持的跟踪后端。

=== Nop ===

“ nop”后端生成空的跟踪事件函数,以便编译器可以完全优化跟踪事件。 这不会造成性能损失。

=== Log ===

“日志”后端将跟踪事件直接发送到标准错误。 这个有效地将跟踪事件转换为调试printfs。

这是最简单的后端,可以与使用DPRINTF()的现有代码一起使用。

=== Simpletrace ===

“simple”后端支持常见的用例,并且是QEMU源代码树的一部分。 它可能不像特定于平台或第三方跟踪后端那样强大,但它是可移植的。 除非您对更高级的后端有特殊需求,否则这是推荐的跟踪后端。

=== Ftrace ===

ftrace”后端将跟踪数据写入ftrace标记。 这可以有效地将跟踪事件发送到ftrace环形缓冲区,并且您可以比较qemu跟踪数据和内核(尤其是在使用KVM时为kvm.ko)跟踪数据。

如果使用KVM,在ftrace中启用kvm事件:

echo 1 > /sys/kernel/debug/tracing/events/kvm/enable

以root用户运行qemu后,您可以获取跟踪:

cat /sys/kernel/debug/tracing/trace

限制:“ ftrace”后端仅限于Linux。

=== Syslog ===

“ syslog”后端使用POSIX syslog API发送跟踪事件。 将打开日志,并指定LOG_DAEMON工具和LOG_PID选项(这样,事件将使用生成事件的特定QEMU进程的pid进行标记)。 所有事件均以LOG_INFO级别记录。

注意:syslog可能会挤压重复的连续跟踪事件并对应用速率进行限制。

==== Monitor 命令 ====

trace-file on|off|flush|set

Enable/disable/flush the trace file or set the trace file name.

==== 分析 trace files ====

“simple”后端会生成二进制跟踪文件,可以使用simpletrace.py脚本进行分析。 该脚本依赖“ trace-events-all”文件和生成的二进制trace文件:

./scripts/simpletrace.py trace-events-all trace-12345

请确保使用构建qemu时候生成的trace-event-all文件,否则跟踪事件可能已更改,造成解析结果混乱。

//后面的翻译由于csdn的草稿功能给我弄丢了,决定不翻译了,后面主要描述了其他几种后端的使用。 以及tcg 的trace 功能,以及vcpu的trace功能。
=== LTTng Userspace Tracer ===

The “ust” backend uses the LTTng Userspace Tracer library. There are no
monitor commands built into QEMU, instead UST utilities should be used to list,
enable/disable, and dump traces.

Package lttng-tools is required for userspace tracing. You must ensure that the
current user belongs to the “tracing” group, or manually launch the
lttng-sessiond daemon for the current user prior to running any instance of
QEMU.

While running an instrumented QEMU, LTTng should be able to list all available
events:

lttng list -u

Create tracing session:

lttng create mysession

Enable events:

lttng enable-event qemu:g_malloc -u

Where the events can either be a comma-separated list of events, or “-a” to
enable all tracepoint events. Start and stop tracing as needed:

lttng start
lttng stop

View the trace:

lttng view

Destroy tracing session:

lttng destroy

Babeltrace can be used at any later time to view the trace:

babeltrace $HOME/lttng-traces/mysession-<date>-<time>

=== SystemTap ===

The “dtrace” backend uses DTrace sdt probes but has only been tested with
SystemTap. When SystemTap support is detected a .stp file with wrapper probes
is generated to make use in scripts more convenient. This step can also be
performed manually after a build in order to change the binary name in the .stp
probes:

scripts/tracetool.py --backends=dtrace --format=stap \
                     --binary path/to/qemu-binary \
                     --target-type system \
                     --target-name x86_64 \
                     --group=all \
                     trace-events-all >qemu.stp

To facilitate simple usage of systemtap where there merely needs to be printf
logging of certain probes, a helper script “qemu-trace-stap” is provided.
Consult its manual page for guidance on its usage.

== Trace event properties ==

Each event in the “trace-events-all” file can be prefixed with a space-separated
list of zero or more of the following event properties.

=== “disable” ===

If a specific trace event is going to be invoked a huge number of times, this
might have a noticeable performance impact even when the event is
programmatically disabled.

In this case you should declare such event with the “disable” property. This
will effectively disable the event at compile time (by using the “nop” backend),
thus having no performance impact at all on regular builds (i.e., unless you
edit the “trace-events-all” file).

In addition, there might be cases where relatively complex computations must be
performed to generate values that are only used as arguments for a trace
function. In these cases you can use ‘trace_event_get_state_backends()’ to
guard such computations, so they are skipped if the event has been either
compile-time disabled or run-time disabled. If the event is compile-time
disabled, this check will have no performance impact.

#include "trace.h"  /* needed for trace event prototype */

void *qemu_vmalloc(size_t size)
{
    void *ptr;
    size_t align = QEMU_VMALLOC_ALIGN;

    if (size < align) {
        align = getpagesize();
    }
    ptr = qemu_memalign(align, size);
    if (trace_event_get_state_backends(TRACE_QEMU_VMALLOC)) {
        void *complex;
        /* some complex computations to produce the 'complex' value */
        trace_qemu_vmalloc(size, ptr, complex);
    }
    return ptr;
}

=== “tcg” ===

Guest code generated by TCG can be traced by defining an event with the “tcg”
event property. Internally, this property generates two events:
“_trans” to trace the event at translation time, and
“_exec” to trace the event at execution time.

Instead of using these two events, you should instead use the function
“trace_tcg" during translation (TCG code generation). This function
will automatically call "trace
trans", and will generate the
necessary TCG code to call "trace
_exec” during guest code execution.

Events with the “tcg” property can be declared in the “trace-events” file with a
mix of native and TCG types, and “trace__tcg” will gracefully forward
them to the “_trans” and “_exec” events. Since TCG values
are not known at translation time, these are ignored by the “_trans”
event. Because of this, the entry in the “trace-events” file needs two printing
formats (separated by a comma):

tcg foo(uint8_t a1, TCGv_i32 a2) "a1=%d", "a1=%d a2=%d"

For example:

#include "trace-tcg.h"

void some_disassembly_func (...)
{
    uint8_t a1 = ...;
    TCGv_i32 a2 = ...;
    trace_foo_tcg(a1, a2);
}

This will immediately call:

void trace_foo_trans(uint8_t a1);

and will generate the TCG code to call:

void trace_foo(uint8_t a1, uint32_t a2);

=== “vcpu” ===

Identifies events that trace vCPU-specific information. It implicitly adds a
“CPUState*” argument, and extends the tracing print format to show the vCPU
information. If used together with the “tcg” property, it adds a second
“TCGv_env” argument that must point to the per-target global TCG register that
points to the vCPU when guest code is executed (usually the “cpu_env” variable).

The “tcg” and “vcpu” properties are currently only honored in the root
./trace-events file.

The following example events:

foo(uint32_t a) "a=%x"
vcpu bar(uint32_t a) "a=%x"
tcg vcpu baz(uint32_t a) "a=%x", "a=%x"

Can be used as:

#include "trace-tcg.h"

CPUArchState *env;
TCGv_ptr cpu_env;

void some_disassembly_func(...)
{
    /* trace emitted at this point */
    trace_foo(0xd1);
    /* trace emitted at this point */
    trace_bar(env_cpu(env), 0xd2);
    /* trace emitted at this point (env) and when guest code is executed (cpu_env) */
    trace_baz_tcg(env_cpu(env), cpu_env, 0xd3);
}

If the translating vCPU has address 0xc1 and code is later executed by vCPU
0xc2, this would be an example output:

// at guest code translation
foo a=0xd1
bar cpu=0xc1 a=0xd2
baz_trans cpu=0xc1 a=0xd3
// at guest code execution
baz_exec cpu=0xc2 a=0xd3
 类似资料: