GDB 调试 libvirt 源码之 domblkinfo 命令源码跟踪记

最近发现环境中 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 开始跟踪他:

gdb libvirtd 1907

首先在源码中全局搜索 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://imagehost-cdn.frytea.com/images/2021/09/02/domblkinfoac4ecdcf5caa1926.png

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

参考文献

附件

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

解决 Clash for windows 端口为 0 导致无法使用

今天更新完 Windows 重启后发现上不了网了,检查 clash for windows 发现监听端口为 0

https://imagehost-cdn.frytea.com/images/2021/08/30/20210830095030627b1fa801f19241.png

这就不正常了,检查了一下 `C:\Users\

\.config\clash\logs` 的日志,发现这行报错: “`perl level=error msg=”Start Mixed(http and socks) server error: listen tcp 127.0.0.1:7890: bind: An attempt was made to access a socket in a way forbidden by its access permissions.” “` 貌似是端口无法被正常绑定,网上找了一下原因,发现遇到该问题的人不少,大致这样解决: `CMD` 执行这行指令 `netsh int ipv4 show dynamicport tcp` 发现起始端口变成了1024。 管理员身份运行 CMD 执行这些命令: “`bash # 这两条命令来自博客 https://blog.csdn.net/tian2342/article/details/108934646 netsh int ipv4 set dynamicport tcp start=49152 num=16383 确定。 netsh int ipv4 set dynamicport udp start=49152 num=16383 确定。 # 这条命令来自 https://github.com/Fndroid/clash_for_windows_pkg/issues/671 netsh int ipv4 set dynamic tcp start=49152 num=16384 “` 然后检查结果 “`bash netsh int ipv4 show dynamicport tcp “` 端口正常后**重启计算机,恢复正常**。 ## 参考文献 – [WIN10更新后端口显示为0的解决方法 #671](https://github.com/Fndroid/clash_for_windows_pkg/issues/671) – [关于Windows端口没被占用提示An attempt was made to access a socket in a way forbidden by its access permissions](https://blog.csdn.net/tian2342/article/details/108934646) – [Clash端口显示为0的解决方法](https://www.cnblogs.com/anyview/p/15056008.html)

Perl 程序后台执行示例

最近阅读 PVE 源码发现一处源码这样使用了 fork() 方法:

$spid = fork();
    if (!defined ($spid)) {
        die "can't put server into background - fork failed";
    } elsif ($spid) { # parent
        exit (0);
    }

自己写示例发现这种方法可以使程序进入后台执行状态,大概原理是 fork 子进程,退出主进程,使得程序被 1 号父进程接管,在终端表现则是进入了后台执行状态

以下是实例代码:

#!/usr/bin/perl

sub mainThread() {
    print "---------- Main Thread! ------------\n";
    $spid = fork();
    if (!defined ($spid)) {
        die "can't put server into background - fork failed";
    } elsif ($spid) { # parent
        exit (0);
    }
    for(;;)
    {
        print "Hello, world in main thread!\n";
        sleep 1;
    }
}

mainThread();

看下进程状态:

https://imagehost-cdn.frytea.com/images/2021/08/26/_1629948977368e83558ffb3dfcdb2.png

退出程序则是指定 PID 即可:

$ kill -9 3300

参考文献

Perl 面向对象之基类(use base)

use base somemodule;

# 相当于以下两句的结合:

BEGIN{
    use somemodule ();
    push @ISA, qw(somemodule);
}

# 也可以同时 use base 两个或者两个以上的模块,即多继承,例如:

use base qw(Foo Bar);

BEGIN {
    use Foo ();
    use Bar ();
    push @ISA, qw(Foo Bar);
}
  • Perl 里 类方法通过 @ISA 数组继承,这个数组里面包含其他包(类)的名字,变量的继承必须明确设定。
  • 多继承就是这个 @ISA 数组包含多个类(包)名字。
  • 通过 @ISA 只能继承方法不能继承数据

参考文献

Perl 模块路径指定(调试环境)

在调试 Perl 测试程序时,常常需要在测试路劲执行 Perl 脚本,相应的 .pm 模块测试程序也需并不在 Perl 默认的模块路径下,使用以下语句即可指定模块检索路径。

#!/usr/bin/perl
use lib './';
use Person;
# Person 包模块与当前脚本同级,可用上面两行代码指定包位置
...

参考文献

QEMU 编译报错 undefined reference to g_app_info_launch_default_for_uri_finish 解决过程

编译 QEMU 时报如下错误:

/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_app_info_launch_default_for_uri_finish'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_type_check_instance_is_fundamentally_a'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_app_info_launch_default_for_uri_async'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_strv_contains'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_list_model_get_type'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_drive_is_removable'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_application_get_resource_base_path'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_log_structured_standard'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_type_get_instance_count'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_list_model_get_n_items'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_file_enumerator_iterate'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_param_spec_get_name_quark'
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/libgtk-3.so: undefined reference to `g_list_model_get_item'
collect2: error: ld returned 1 exit status
make[1]: *** [qemu-system-x86_64] Error 1
make: *** [subdir-x86_64-softmmu] Error 2

先看一下报错的动态链接库依赖了哪些库:

$ ldd /lib64/libgtk-3.so       
        linux-vdso.so.1 =>  (0x00007ffc89762000)
        libgdk-3.so.0 => /lib64/libgdk-3.so.0 (0x00007f19938ab000)
        libgmodule-2.0.so.0 => /lib64/libgmodule-2.0.so.0 (0x00007f19936a7000)
        libpangocairo-1.0.so.0 => /lib64/libpangocairo-1.0.so.0 (0x00007f1993499000)
        libX11.so.6 => /lib64/libX11.so.6 (0x00007f199315b000)
        libXi.so.6 => /lib64/libXi.so.6 (0x00007f1992f4b000)
        libXfixes.so.3 => /lib64/libXfixes.so.3 (0x00007f1992d45000)
        libcairo-gobject.so.2 => /lib64/libcairo-gobject.so.2 (0x00007f1992b3c000)
        libcairo.so.2 => /lib64/libcairo.so.2 (0x00007f1992805000)
        libgdk_pixbuf-2.0.so.0 => /lib64/libgdk_pixbuf-2.0.so.0 (0x00007f19925dd000)
        libatk-1.0.so.0 => /lib64/libatk-1.0.so.0 (0x00007f19923b7000)
        libatk-bridge-2.0.so.0 => /lib64/libatk-bridge-2.0.so.0 (0x00007f1992188000)
        libwayland-client.so.0 => /lib64/libwayland-client.so.0 (0x00007f1991f79000)
        libepoxy.so.0 => /lib64/libepoxy.so.0 (0x00007f1991c4d000)
        libpangoft2-1.0.so.0 => /lib64/libpangoft2-1.0.so.0 (0x00007f1991a37000)
        libpango-1.0.so.0 => /lib64/libpango-1.0.so.0 (0x00007f19917f1000)
        libfontconfig.so.1 => /lib64/libfontconfig.so.1 (0x00007f19915af000)
        libgio-2.0.so.0 => /lib64/libgio-2.0.so.0 (0x00007f199120f000)
        libgobject-2.0.so.0 => /lib64/libgobject-2.0.so.0 (0x00007f1990fbe000)
        libglib-2.0.so.0 => /lib64/libglib-2.0.so.0 (0x00007f1990ca8000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f19909a6000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f199078a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f19903bc000)
        libXinerama.so.1 => /lib64/libXinerama.so.1 (0x00007f19901b9000)
        libXrandr.so.2 => /lib64/libXrandr.so.2 (0x00007f198ffae000)
        libXcursor.so.1 => /lib64/libXcursor.so.1 (0x00007f198fda3000)
        libXcomposite.so.1 => /lib64/libXcomposite.so.1 (0x00007f198fba0000)
        libXdamage.so.1 => /lib64/libXdamage.so.1 (0x00007f198f99d000)
        libxkbcommon.so.0 => /lib64/libxkbcommon.so.0 (0x00007f198f75d000)
        libwayland-cursor.so.0 => /lib64/libwayland-cursor.so.0 (0x00007f198f555000)
        libwayland-egl.so.1 => /lib64/libwayland-egl.so.1 (0x00007f198f353000)
        libXext.so.6 => /lib64/libXext.so.6 (0x00007f198f141000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f198ef39000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f198ed35000)
        libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f198ead3000)
        libfreetype.so.6 => /lib64/libfreetype.so.6 (0x00007f198e814000)
        libxcb.so.1 => /lib64/libxcb.so.1 (0x00007f198e5ec000)
        libpixman-1.so.0 => /lib64/libpixman-1.so.0 (0x00007f198e343000)
        libEGL.so.1 => /lib64/libEGL.so.1 (0x00007f198e12f000)
        libpng15.so.15 => /lib64/libpng15.so.15 (0x00007f198df04000)
        libxcb-shm.so.0 => /lib64/libxcb-shm.so.0 (0x00007f198dd00000)
        libxcb-render.so.0 => /lib64/libxcb-render.so.0 (0x00007f198daf2000)
        libXrender.so.1 => /lib64/libXrender.so.1 (0x00007f198d8e7000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f198d6d1000)
        libGL.so.1 => /lib64/libGL.so.1 (0x00007f198d445000)
        libatspi.so.0 => /lib64/libatspi.so.0 (0x00007f198d214000)
        libdbus-1.so.3 => /lib64/libdbus-1.so.3 (0x00007f198cfc4000)
        libffi.so.6 => /lib64/libffi.so.6 (0x00007f198cdbc000)
        libharfbuzz.so.0 => /lib64/libharfbuzz.so.0 (0x00007f198cb1f000)
        libthai.so.0 => /lib64/libthai.so.0 (0x00007f198c913000)
        libfribidi.so.0 => /lib64/libfribidi.so.0 (0x00007f198c6f7000)
        libexpat.so.1 => /lib64/libexpat.so.1 (0x00007f198c4cd000)
        libuuid.so.1 => /lib64/libuuid.so.1 (0x00007f198c2c8000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f198c0a1000)
        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f198be87000)
        libmount.so.1 => /lib64/libmount.so.1 (0x00007f198bc44000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f198ba2e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f199449d000)
        libbz2.so.1 => /lib64/libbz2.so.1 (0x00007f198b81e000)
        libXau.so.6 => /lib64/libXau.so.6 (0x00007f198b61a000)
        libGLdispatch.so.0 => /lib64/libGLdispatch.so.0 (0x00007f198b364000)
        libGLX.so.0 => /lib64/libGLX.so.0 (0x00007f198b132000)
        libsystemd.so.0 => /lib64/libsystemd.so.0 (0x00007f198af01000)
        libgraphite2.so.3 => /lib64/libgraphite2.so.3 (0x00007f198acd3000)
        libblkid.so.1 => /lib64/libblkid.so.1 (0x00007f198aa93000)
        libcap.so.2 => /lib64/libcap.so.2 (0x00007f198a88e000)
        liblzma.so.5 => /lib64/liblzma.so.5 (0x00007f198a668000)
        liblz4.so.1 => /lib64/liblz4.so.1 (0x00007f198a459000)
        libgcrypt.so.11 => /lib64/libgcrypt.so.11 (0x00007f198a1d8000)
        libgpg-error.so.0 => /lib64/libgpg-error.so.0 (0x00007f1989fd3000)
        libdw.so.1 => /lib64/libdw.so.1 (0x00007f1989d82000)
        libattr.so.1 => /lib64/libattr.so.1 (0x00007f1989b7d000)
        libelf.so.1 => /lib64/libelf.so.1 (0x00007f1989965000)

观察输出,所有依赖的动态链接库都有指向一个内存地址,说明所依赖的链接库都已经被加载入内存,排除了链接库不存在情况,下面就有可能是某个链接库有问题了,接下来做两件事:

  • 使用 objdump -T <lib name and path> |grep <funcname> 命令检索报错函数属于哪一个链接库;
  • 使用 find / -name <lib name> 命令查找是否有哪一个报错链接库在系统的动态链接库搜索目录中有多个;

经过一番检索,发现 [libgio-2.0.so](http://libgio-2.0.so) 在系统so检索目录有两个,分别是 /usr/local/lib/libgio-2.0.so/lib64/libgio-2.0.so ,其中 /lib64 为系统默认链接库存放位置,而 /usr/local 为编译安装库的默认安装位置,移除/usr/local/lib/libgio-2.0.so 之后再次尝试编译发现报错减少了。

此时发现系统曾编译安装了 glib ,可能是那时引入了一些错误的 so 库,因此进入编译目录 make uninstall 移除此前安装的错误的库,再次尝试编译发现编译通过。

总结

本次编译错误排查了很久,最后在大佬的协助下终于解决,此类缺少依赖错误排查错误思路可以总结为 检查链接库是否存在 -> 检查是否存在重复链接库 -> 移除错误链接库

参考文献

鲲鹏ARM环境编译升级虚拟化组件(QEMU+libvirt)

在 鲲鹏 arm 环境下可以直接使用 yum 安装相关虚拟化组件(以 centos 为例):

yum -y install qemu* libvirt* AAVMF virt-install

但是软件库中的虚拟化组件版本较老,不支持 spice 等,而且对端口有限制,无法使用 virt-manager ,也无法对接 openstack 使用,因此需要分别升级 QEMU, libvirt。

(本文内容主要来自华为鲲鹏支持官网文档

鲲鹏 ARM 编译升级 QEMU(带有 OpenStack 相关组件)

安装依赖包。

yum -y install glib2-devel zlib-devel pixman-devel libaio-devel glib libffi-devel gcc gcc-c++ automake libtool bzip2-devel libuuid-devel spice-protocol spice-server-devel usbredir-devel gtk3-devel  SDL2-devel libjpeg-turbo-devel crudini librbd-devel snappy-devel

编译安装

说明:QEMU 默认安装在“/usr/local”下,源码包的下载请参见获取软件包。 使用的是 qemu-4.0.0 版本。该 arm 版本暂不支持虚拟机热迁移功能(支持冷迁移),若有虚拟机热迁移需求,可根据 openEuler 中的 patch 包进行补丁升级,链接如下:https://gitee.com/src-openeuler/qemu/tree/openEuler-20.03-LTS/

wget https://download.qemu.org/qemu-4.0.0.tar.xz

1, 解压并进入 QEMU 目录。

tar -xvf qemu-4.0.0.tar.xz
cd qemu-4.0.0

2, 配置安装,若需对接 openstack 请包含相关依赖:

## 普通配置安装
./configure --target-list=aarch64-softmmu  --enable-linux-aio

## 配置安装,同时带有 openstack 相关依赖
../configure --prefix=/usr --target-list="aarch64-softmmu" \
      --enable-rbd --enable-debug --enable-vnc --enable-vnc-jpeg --enable-vnc-png \
      --enable-kvm --enable-spice --enable-curl --enable-snappy --enable-tools --enable-spice --enable-libusb \
      --enable-usb-redir --enable-linux-aio

编译安装

# 多线程编译
make -j64 
make install

# 链接 qemu-kvm ,若链接存在请先删除
ln -s /usr/bin/qemu-system-aarch64 /usr/bin/qemu-kvm
ln -s /usr/bin/qemu-system-aarch64 /usr/libexec/qemu-kvm

3, 添加 lib 库。

添加 lib 库路径。

vim /etc/ld.so.conf
include /usr/local/lib

使 lib 库更改生效。

ldconfig

4, 检验 QEMU 版本。

qemu-img --version

鲲鹏 ARM 环境编译升级 libvirtd

说明:

官方提供的 src.rpm 包在编译时,有一定几率会失败,需多次尝试。

安装 edk2

  • 在线安装

执行如下命令在线安装 edk2

wget https://www.kraxel.org/repos/firmware.repo -O /etc/yum.repos.d/firmware.repo
yum -y install edk2.git-aarch64
  • 离线安装

在有外网的环境下访问https://www.kraxel.org/repos/jenkins/edk2/,获取 rpm 包并拷贝至目标服务器系统相应位置。执行如下命令离线安装 edk2,如图2所示。

rpm -ivh edk2.git-aarch64*.rpm

安装依赖包

说明:本章节的操作需要外网可用或已配置本地源。

yum -y install libxml2-devel readline-devel ncurses-devel libtasn1-devel gnutls-devel libattr-devel libblkid-devel augeas systemd-devel libpciaccess-devel yajl-devel sanlock-devel libpcap-devel libnl3-devel libselinux-devel dnsmasq radvd cyrus-sasl-devel libacl-devel parted-devel device-mapper-devel xfsprogs-devel librados2-devel librbd1-devel glusterfs-api-devel glusterfs-devel numactl-devel libcap-ng-devel fuse-devel netcf-devel libcurl-devel audit-libs-devel systemtap-sdt-devel nfs-utils dbus-devel scrub numad

编译安装

说明:源码包的下载请参见获取软件包,本章以 libvirt-5.6.0 为例。该 Arm 版本暂不支持虚拟机热迁移功能(支持冷迁移),若有虚拟机热迁移需求,可根据 openEuler 中的 patch 包进行补丁升级,链接如下:https://gitee.com/src-openeuler/libvirt/tree/openEuler-20.03-LTS/

1, 安装 src.rpm 源码包。

rpm -i libvirt-5.6.0-1.fc30.src.rpm

2, 生成 rpm 包。

cd /root/rpmbuild/SPECS/
rpmbuild -ba libvirt.spec

说明: 官方提供的 src.rpm 包在编译时,有一定几率会失败,需多次尝试。

3, 安装 rpm 包。

cd /root/rpmbuild/RPMS/aarch64/yum -y install *.rpm

4, 修改配置文件。

打开 qemu.conf 文件。

vim /etc/libvirt/qemu.conf

添加如下配置。

nvram = ["/usr/share/edk2.git/aarch64/QEMU_EFI-pflash.raw:/usr/share/edk2.git/aarch64/vars-template-pflash.raw"]

5, 重启 libvirtd 服务。

service libvirtd restart
systemctl restart libvirtd

6, 关闭 SELinux。

setenforce 0

参考文献