惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
O
OpenAI News
WordPress大学
WordPress大学
P
Proofpoint News Feed
J
Java Code Geeks
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Register - Security
The Register - Security
Engineering at Meta
Engineering at Meta
H
Help Net Security
人人都是产品经理
人人都是产品经理
Vercel News
Vercel News
N
Netflix TechBlog - Medium
F
Full Disclosure
U
Unit 42
Latest news
Latest news
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
InfoQ
L
LINUX DO - 最新话题
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
爱范儿
爱范儿
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
小众软件
小众软件
I
Intezer
V
V2EX
S
SegmentFault 最新的问题
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
Security Archives - TechRepublic
Security Archives - TechRepublic
Recent Announcements
Recent Announcements
C
Check Point Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Recorded Future
Recorded Future
博客园 - Franky
Project Zero
Project Zero
S
Securelist
Attack and Defense Labs
Attack and Defense Labs
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
NISL@THU
NISL@THU
云风的 BLOG
云风的 BLOG
S
Secure Thoughts
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed

Oskyla 烹茶室

修复 Joplin on KDE 菜单栏显示问题 Copy Fail:Linux 内核 2017 年至今的高危漏洞(附临时缓解方案) | CVE-2026-31431 Hermes Agent — 在 K3s / K8s 中运行指南 在 K3s 节点上安装并使用 nerdctl Mouser:轻量开源的罗技鼠标驱动替代方案 Claude Opus 4.7:优缺点与评测信息汇总 openFuyao NPU-Operator故障排查 openFuyao 2603 共测测试报告 openFuyao InferNex AI推理集成部署 310P(300I Pro) 环境问题记录及解决 ceph mon Operation not permitted 问题解决 Ascend 310P + openFuyao + NPU-Operator 故障排查 KDE Plasma6 禁用全局菜单,恢复正常应用菜单 终极指南:在 Linux 裸机服务器上快速部署 Moltbot (原 Clawbot) 并集成飞书 Windows 配置 Claude Code 解决 settings.json 不生效 Windows 配置 Claude Code 全流程 2025-12-31 | 年终总结 AI 生图精品提示词|第二期:城市星球 AI 生图精品提示词|第一期 Kubernetes kubectl --raw 使用指南 彻底解决阿里云和 tailscale 冲突 2025-10-21 | 沉淀思维 macOS 单独为鼠标或触控板开启自然滚动 2025-10-16 | 负载高低 2025-10-15 | 睡眠周期 2025-10-14 | 转换情绪与独立观点 go 拉取 gitcode.com 私有 mod Git 将某个文件恢复到其他分支的状态 SSH 通过跳板机连接 lxc 使用 chronyc 构建 ntp 服务 2025-10-13 | 独立思考于未来能源 2025-10-12|AI Review 及高质量工作流 2025-10-11 | 人生阶段 2025-10-10 | AI Logo | 远程操控 AI 2025-10-09 | 去班味|银杏|域名 2025-10-08 2025-10-07 2025-10-06 2025-10-05 优秀的 SubReddit 清单 Synology 群晖重置 Syncthing 密码 让 LLM 看到真实世界的 Playwright MCP 磁盘占用分析利器 ncdu 自建 gitlab 徽标问题导致项目 500 问题解决 harbor Chartmuseum helm 图表缓存刷新 grep exclude 排除 Debian 11 + PVE LACP Mac 冲突问题调查与解决方案 Claude Code 实用技巧 CentOS 7 重置 root 密码 telnet 如何退出 kubernetes 的挂载传播(mount propagation)机制 vim 将命令输出到当前位置 harbor 替换 ssl 证书 AI提效之使用 cherry-studio + k8sgpt 实现 AI 巡检 k8s Claude Code Subagents 快速开始 《我看见的世界:李飞飞自传》 Lyra - AI Prompt Optimization Specialist Linux 自签名 CA 证书安装方法 超级玛丽、魂斗罗等经典小游戏聚合站 | PlayUnb 个人业余开发项目清单 快速选出收益最高的理财产品 | finance-calculator 苹果液态玻璃风格图像生成和模拟器 | LiquedGlass.icu Linux 进程绑定NUMA节点或CPU核心 判断GPT是否降智的几个问题 Harmony next hap 安装 名侦探柯南贝尔摩德出场集数 k3s k8s 快速部署轻量节点监控方案 beszel k3s-k8s 实现 DevOps 方案横向对比 k8s 配置访问私有镜像仓库 GoAccess 分析多网站日志方法 Octant - 以开发人员为中心的开源 Kubernetes Web 界面 Tailscale 自建 DERP 并配置 SSL 完整教程 OpenManus 使用记录 Plausible 缺失 location 信息的研究 解决 Nginx Ingress returns 413 Entity Too Large 绘图模型效果对比之城市气象 【转】k8s 认知路线 OpenFOAM 两大分支的详细比较 第一个 CUDA 程序之矩阵运算计算效能对比 Archlinux KDE Apache JMeter 配置高分屏缩放 解决 gitlab-runner 移除残留文件 permission denied nginx-ingress 配置路由 302 k8s 触发 pod 重新拉取镜像平滑升级的方法 Clickhouse 迁移后 permission denied 问题解决 Linux CPU 运行模式及功耗分析 Linux vim vi 翻页跳转命令快捷键 git 拉取所有 branch 和 tag 到本地并推送到远程 Rails 性能分析工具 rack-mini-profiler 和 bullet 全球国家、城市、地区开源数据库 Rails Active Record 常用命令 Rails Rake 简介与编写 如何调试 Vim 脚本 名侦探柯南松田阵平出场集数(最新1069话) 名侦探柯南妃英理出场集数(最新1069话) 《名侦探柯南》官方人气投票结果 名侦探柯南TV安室透出场集数(最新1110话) 名侦探柯南TV赤井秀一出场集数(更新至1194话) [转]名侦探柯南TV主线集数整理(截止1165集) 架构风格总结 [转译] 如何成为一个好的开发者? 【影评】名侦探柯南·零的执行人
Libvirt domblkinfo 命令源码跟踪记 (GDB)
Tianlun Song · 2021-12-06 · via Oskyla 烹茶室

本文 首发于 🌱 煎茶转载 请注明 来源

使用 GDB 跟踪 libvirt 命令执行过程的具体执行细节.

最近发现环境中 KVM 虚拟机磁

盘利用率查不准,使用 virsh 命令查看磁盘使用情况得到如下结果:

# virsh domblkinfo 20 vda --human
Capacity:       2.000 GiB
Allocation:     2.000 GiB
Physical:       2.000 GiB

显然是有问题的,正常的数值三个应该不通,进入系统查看磁盘使用率也仅有 2% 左右,因此试图通过检查源码的方式查看是否正确。

  • libvirt: 5.6.0
  • os: Centos

跟踪记录

首先找到 libvirtd 的 PID:

ps -aux | grep libvirtd
root      1907  0.0  0.0 1385796 25116 ?       Ssl  Aug26   5:22 /usr/sbin/libvirtd --timeout 120

使用 GDB 开始跟踪他:

首先在源码中全局搜索 domblkinfo 关键字,找到该命令的执行函数: tools/virsh-domain-monitor.c→cmdDomblkinfo

分析源码找到获取信息的函数 src/libvirt-domain.c -> virDomainGetBlockInfo

if (virDomainGetBlockInfo(dom, device, &info, 0) < 0)
    goto cleanup;

if (!cmdDomblkinfoGet(ctl, &info, &cap, &alloc, &phy, human))
    goto cleanup;
vshPrint(ctl, "%-15s %s\n", _("Capacity:"), cap);
vshPrint(ctl, "%-15s %s\n", _("Allocation:"), alloc);
vshPrint(ctl, "%-15s %s\n", _("Physical:"), phy);

这其中的 info 包含了所需信息,看一下填充该字段的 virDomainGetBlockInfo 函数实现,用 GDB 跟一下它吧.

跟踪 src/libvirt-domain.c -> virDomainGetBlockInfo

先打个断点:

(gdb) break virDomainGetBlockInfo
Breakpoint 1 at 0x7f4d4394a760: file libvirt-domain.c, line 6094.

再打开一个终端,执行一下命令:

[root@compute-01 ~]# virsh list
 Id   Name                State
-----------------------------------
 2    instance-000001b6   running
 3    instance-000001b8   running
 4    instance-000001b9   running

[root@compute-01 ~]# virsh domblkinfo 4 vda

此时会发现终端卡住了,看一下 GDB 已经将程序中断,单步调试看一下:

[Switching to Thread 0x7f4d32ef0700 (LWP 1918)]

Breakpoint 1, virDomainGetBlockInfo (domain=domain@entry=0x7f4cfc00aeb0, disk=0x7f4cfc00cc60 "vda", 
    info=info@entry=0x7f4d32eefac0, flags=0) at libvirt-domain.c:6094
6094    {
(gdb) n
6097        VIR_DOMAIN_DEBUG(domain, "info=%p, flags=0x%x", info, flags);
(gdb) n
6094    {
(gdb) n
6097        VIR_DOMAIN_DEBUG(domain, "info=%p, flags=0x%x", info, flags);
(gdb) n
6099        virResetLastError();
(gdb) n
6101        if (info)
(gdb) n
6102            memset(info, 0, sizeof(*info));
(gdb) n
6104        virCheckDomainReturn(domain, -1);
(gdb) n
6105        virCheckNonEmptyStringArgGoto(disk, error);
(gdb) n
6106        virCheckNonNullArgGoto(info, error);
(gdb) n
6110        if (conn->driver->domainGetBlockInfo) {
(gdb) n
6112            ret = conn->driver->domainGetBlockInfo(domain, disk, info, flags);
(gdb) s
qemuDomainGetBlockInfo (dom=0x7f4cfc00aeb0, path=0x7f4cfc00cc60 "vda", info=0x7f4d32eefac0, flags=0)
    at qemu/qemu_driver.c:12413
12413   {

发现在 6112 行跳到了另一个函数,继续跟踪它.

跟踪 src/qemu/qemu_driver.c -> qemuDomainGetBlockInfo

(gdb) n
12421       virCheckFlags(0, -1);
(gdb) n
12413   {
(gdb) n
12414       virQEMUDriverPtr driver = dom->conn->privateData;
(gdb) n
12421       virCheckFlags(0, -1);
(gdb) n
12419       qemuBlockStatsPtr entry = NULL;
(gdb) n
12414       virQEMUDriverPtr driver = dom->conn->privateData;
(gdb) n
12421       virCheckFlags(0, -1);
(gdb) n
12423       if (!(vm = qemuDomObjFromDomain(dom)))
(gdb) n
12426       cfg = virQEMUDriverGetConfig(driver);
(gdb) n
12428       if (virDomainGetBlockInfoEnsureACL(dom->conn, vm->def) < 0)
(gdb) n
12431       if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0)
(gdb) n
12434       if (!(disk = virDomainDiskByName(vm->def, path, false))) {
(gdb) n
12440       if (virStorageSourceIsEmpty(disk->src)) {
(gdb) n
12448       if (!virDomainObjIsActive(vm)) {
(gdb) n
12460       if (qemuDomainBlocksStatsGather(driver, vm, path, true, &entry) < 0)
(gdb) n
12463       if (!entry->wr_highest_offset_valid) {
(gdb) n
12466           if (virStorageSourceGetActualType(disk->src) == VIR_STORAGE_TYPE_FILE &&
(gdb) n
12468               info->allocation = entry->physical;
(gdb) n
12466           if (virStorageSourceGetActualType(disk->src) == VIR_STORAGE_TYPE_FILE &&
(gdb) p info->allocation
$2 = 0
(gdb) n
12470               info->allocation = entry->wr_highest_offset;
(gdb) n
12484       if (entry->physical == 0 || info->allocation == 0 ||
(gdb) p info->allocation
$3 = 32870912
(gdb) p entry->wr_highest_offset
$4 = 32870912

至此,我们知道了 info -> allocation 的值来自 entry->wr_highest_offset ,接下来查看源码, entry->wr_highest_offset 的值应该是在这里被赋予的:

if (qemuDomainBlocksStatsGather(driver, vm, path, true, &entry) < 0)
  goto endjob;

下面将断点打在 qemuDomainBlocksStatsGather 看一下其中的 entry->wr_highest_offset 是在哪里被赋值.

跟踪 src/qemu/qemu_driver.c -> qemuDomainBlocksStatsGather

将之前的断点删除,打上新的断点

(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00007f4d4394a760 in virDomainGetBlockInfo at libvirt-domain.c:6094
        breakpoint already hit 1 time
(gdb) delete 1
(gdb) break qemuDomainBlocksStatsGather
Breakpoint 2 at 0x7f4d208e3700: file qemu/qemu_driver.c, line 11427.

之后在 GDB continue ,之后一直按回车,直到程序正常运行了,再执行一下获取磁盘信息的命令,继续跟踪。

Breakpoint 1, qemuDomainBlocksStatsGather (driver=driver@entry=0x7f4d180f99b0, vm=0x7f4d100b8890, 
    path=path@entry=0x7f4d0c00ae50 "vda", capacity=capacity@entry=true, retstats=retstats@entry=0x7f4d336f0980)
    at qemu/qemu_driver.c:11427
11427   {
(gdb) n
11428       qemuDomainObjPrivatePtr priv = vm->privateData;
(gdb) 
11429       bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
(gdb) 
11439       if (*path) {
(gdb) 
11440           if (!(disk = virDomainDiskByName(vm->def, path, false))) {
(gdb) 
11445           if (blockdev) {
(gdb) 
11448               if (!disk->info.alias) {
(gdb) 
11458       qemuDomainObjEnterMonitor(driver, vm);
(gdb) 
11459       nstats = qemuMonitorGetAllBlockStatsInfo(priv->mon, &blockstats, false);
(gdb) 
11461       if (capacity && nstats >= 0) {
(gdb) 
11465               rc = qemuMonitorBlockStatsUpdateCapacity(priv->mon, blockstats, false);
(gdb) 
11468       if (qemuDomainObjExitMonitor(driver, vm) < 0 || nstats < 0 || rc < 0)
(gdb) 
11471       if (VIR_ALLOC(*retstats) < 0)
(gdb) 
11474       if (entryname) {
(gdb) 
11475           if (!(stats = virHashLookup(blockstats, entryname))) {
(gdb) 
11481           **retstats = *stats;
(gdb) p stats
$12 = (qemuBlockStats *) 0x7f4d0c001000
(gdb) p *stats
$13 = {rd_req = 712, rd_bytes = 17435136, wr_req = 130, wr_bytes = 418816, rd_total_times = 527027278, 
  wr_total_times = 86798718, flush_req = 20, flush_total_times = 94396427, capacity = 2147483648, 
  physical = 2147483648, wr_highest_offset = 32870912, wr_highest_offset_valid = true, write_threshold = 0}
(gdb) c
Continuing.

分析这一调用过程,发现我们跟踪的 restats 变量来自 stats,而该值在这一行被填充:

11475           if (!(stats = virHashLookup(blockstats, entryname))) {

值来自哈希表查询结果,从 blockstats 中查询 entryname ,而该哈希表在这两行被赋值:

11459       nstats = qemuMonitorGetAllBlockStatsInfo(priv->mon, &blockstats, false);
11465       rc = qemuMonitorBlockStatsUpdateCapacity(priv->mon, blockstats, false);

之后就可以跟踪源码了,经过一番探索,发现他们最终都调用了同一个函数来从 QEMU 获取设备信息,即 src/qemu/qemu_monitor_json.c -> qemuMonitorJSONQueryBlock ,看一下它的函数实现:

static virJSONValuePtr
qemuMonitorJSONQueryBlock(qemuMonitorPtr mon)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr devices = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-block", NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0 ||
        qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        goto cleanup;

    devices = virJSONValueObjectStealArray(reply, "return");

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return devices;
}

继续探索会发现 libvirt 在这里调用了 QEMU 提供的 QMP 协议,其中的查询关键词为 query-block ,返回的结果中含有 wr_highest_offset 字段。

最终得到一张 libvirt 查询磁盘使用情况的调用栈示意图:

https://cdn-imagehost.frytea.com/images/2021/09/02/domblkinfoac4ecdcf5caa1926.png

如果继续探索,可能就需要去跟踪 QEMU 源码了,下篇文章见。

参考文献

附件

libvirt-domblkinfo-命令源码调用栈 .xmind