QEMU模拟器

本文更新于 2018.04.15

QEMU 是一个开源的模拟器/虚拟化工具, 可以模拟多种CPU平台, 如 x86, ARM, MIPS, PowerPC等等. 而VMware基本上只能虚拟化跟主机CPU架构一样的系统. 有了QEMU, 我们就可以比较方便地进行移植性地测试, 比如模拟出一套big endian字节序环境, 测试我们的程序能否正确工作.

本文以在Ubuntu 14.04上模拟MIPS 32bit Big Endian机器为例, 介绍如何使用QEMU.

安装软件包

QEMU分为用户模式和系统模式两种运行模式, 用户模式非常简单, 装好QEMU即可运行, 比如我们交叉编译了一个供MIPS 32位机器使用的hello程序, 那么可以通过以下命令调用它:

$qemu-mips ./hello

而系统模式则需要加载做好的磁盘映像和内核文件, 类似于我们使用VMware虚拟机, 可以在上面进行更多的操作.

如果只使用用户模式, 仅需安装QEMU:

$sudo apt-get update
$sudo apt-get install qemu

要使用系统模式, 则还需要准备好内核文件和系统磁盘映像. 对于MIPS平台, debian官方已经提供了做好的文件, 地址在: https://people.debian.org/~aurel32/qemu/mips/

此页面已经给出了虚拟不同系统所需的命令和文件, 启动MIPS 32bit的命令为

  • qemu-system-mips -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mips_standard.qcow2 -append “root=/dev/sda1 console=tty0”
  • qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append “root=/dev/sda1 console=tty0”

启动MIPS 64bit的命令为

  • qemu-system-mips64 -M malta -kernel vmlinux-2.6.32-5-5kc-malta -hda debian_squeeze_mips_standard.qcow2 -append “root=/dev/sda1 console=tty0”
  • qemu-system-mips64 -M malta -kernel vmlinux-3.2.0-4-5kc-malta -hda debian_wheezy_mips_standard.qcow2 -append “root=/dev/sda1 console=tty0”

也可以看出4kc为32bit, 5kc为64bit; squeeze对应2.6.32内核, wheezy对应3.2.0内核. 根据需要下载对应的文件即可.

用户模式

如前所述, 用户模式非常简单, 只需使用交叉编译器编译好程序, 并使用qemu-mips调用即可. 如果程序不是静态链接(一般都不是), 需要依赖其他动态库, 则会报错:

$qemu-mips hello
/lib/ld-uClibc.so.0: No such file or directory

这需要通过-L参数来指定运行根目录, 即交叉编译套件中的sysroot目录:

$qemu-mips -L /opt/trendchip/mips-linux-uclibc-4.9.3/usr/mips-buildroot-linux-uclibc/sysroot  ./hello
hello mips!

有的程序更复杂, 不仅依赖系统库, 还需要依赖位于其他路径的第3方动态库, 这可通过-E参数来配置LD_LIBRARY_PATH环境变量来解决:

$qemu-mips -L /opt/trendchip/mips-linux-uclibc-4.9.3/usr/mips-buildroot-linux-uclibc/sysroot/ -E LD_LIBRARY_PATH=.  ./demo
usage: ./demo <pcap file>

这个demo程序依赖的第3方动态库在当前目录下(.).

QEMU用户模式还支持gdb调试功能, 可以通过-g port参数来启动gdbserver, 在端口port监听:

$qemu-mips -L /opt/trendchip/mips-linux-uclibc-4.9.3/usr/mips-buildroot-linux-uclibc/sysroot/ -g 4444 ./hello

然后可以在另一个终端运行交叉编译工具套件中的gdb连接此端口4444, 来进行远程调试:

$./mips-gdb
GNU gdb (GDB) 7.8.2
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=mips-buildroot-linux-uclibc".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) target remote localhost:4444
Remote debugging using localhost:4444
0x40801f20 in ?? ()
(gdb) file hello
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from hello...done.
(gdb) b main
Breakpoint 1 at 0x40078c: file hello.c, line 5.
(gdb) c
Continuing.

Breakpoint 1, main () at hello.c:5
5           printf("hello mips!\n");
(gdb) n
6           return 0;
(gdb)

系统模式

一般使用

比如我们使用vmlinux-3.2.0-4-4kc-malta和debian_wheezy_mips_standard.qcow2, 而且安装了桌面环境, 那么直接在终端中调用以下命令即可启动QEMU窗口:

$qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0"

如果是以远程SSH登录或不想启动窗口, 则要加上 -nographic 参数. 启动成功后可以用user账户登录, 如下所示:

debian-mips login: user
Password:
Last login: Fri Mar  9 02:46:10 UTC 2018 on tty1
Linux debian-mips 3.2.0-4-4kc-malta #1 Debian 3.2.51-1 mips

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
user@debian-mips:~$ uname -a
Linux debian-mips 3.2.0-4-4kc-malta #1 Debian 3.2.51-1 mips GNU/Linux

此时我们便有了一个MIPS 32bit Linux环境, 但如果需要从主机登录此虚拟机, 以便传文件或调试还是不行, 还需要配置网络.

配置网络

先说明一下当前的网络配置情况:

  • Ubuntu为虚拟机, 通过NAT与宿主Windows主机通信, NAT网络为VMnet8
  • VMnet8子网IP为192.168.128.0/24
  • Windows主机在VMnet8中的地址是192.168.128.2/24
  • Ubuntu配置为固定IP, 192.168.128.6/24

主机的配置如下:

  1. 安装必要的软件包:

    $sudo apt-get install bridge-utils uml-utilities
    
  2. 编辑/etc/network/interfaces, 配置网桥br0:

    auto eth0
    iface lo inet loopback
    
    iface eth0 inet static
    address 192.168.128.6
    netmask 255.255.255.0
    gateway 192.168.128.1
    dns-nameservers 8.8.8.8 114.114.114.114
    
    iface br0 inet static
    address 192.168.128.20
    netmask 255.255.255.0
    gateway 192.168.128.1
    dns-nameservers 8.8.8.8 114.114.114.114
    bridge_ports eth0
    bridge_maxwait 0
    
  3. 编辑QEMU网络启动脚本 /etc/qemu-ifup, 在文件后面加入:

    echo "Bringing $1 for bridged mode ..."
    ifconfig $1 down
    ifconfig $1 0.0.0.0 promisc up
    echo "Adding $1 to br0 ..."
    brctl addif br0 $1
    sleep 3
    
  4. 重启网络服务:

    $sudo /etc/init.d/networking restart
    
  5. 启动桥接网络, 注意, 如果eth0是虚拟机上与真实机器通信的网卡, 它将失效:

    $sudo ifdown eth0
    $sudo ifup br0
    
  6. 启动QEMU虚拟机:

    $sudo qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta \
    -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" \
    -nographic -net nic -net tap
    

MIPS客户机的配置如下:

  1. 配置网卡, 编辑/etc/network/interfaces:

    # The loopback network interface
    auto lo
    iface lo inet loopback
    
    # The primary network interface
    allow-hotplug eth0
    #iface eth0 inet dhcp
    iface eth0 inet static
    address 192.168.128.21
    netmask 255.255.255.0
    gateway 192.168.128.1
    
  2. 启动网卡:

    $sudo ifup eth0
    
  3. 检查IP是否正确:

    user@debian-mips:~$ ip addr
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
        inet6 ::1/128 scope host
        valid_lft forever preferred_lft forever
    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
        link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff
        inet 192.168.128.21/24 brd 192.168.128.255 scope global eth0
        inet6 fe80::5054:ff:fe12:3456/64 scope link
        valid_lft forever preferred_lft forever
    
    user@debian-mips:~$ ping www.baidu.com
    PING www.a.shifen.com (119.75.216.20) 56(84) bytes of data.
    64 bytes from 127.0.0.1 (119.75.216.20): icmp_req=1 ttl=128 time=7.22 ms
    64 bytes from 127.0.0.1 (119.75.216.20): icmp_req=2 ttl=128 time=6.27 ms
    ^C
    --- www.a.shifen.com ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 1004ms
    rtt min/avg/max/mdev = 6.276/6.750/7.224/0.474 ms
    

至此, 网络配置已完成, Window主机, Ubuntu虚拟机, MIPS虚拟机均可互相ping通. 但要注意的是此时Ubuntu虚拟机IP已变成网桥br0的IP 192.168.128.20, 原eth0 IP 192.168.128.6不可用.

此时可以在Ubuntu虚拟机上使用scp把本地程序远程拷贝到MIPS虚拟机上执行:

$scp hello user@192.168.128.21:~
user@192.168.128.21's password:
hello                                          100%   26KB  26.2KB/s   00:00

user@debian-mips:~$ ./hello
hello mips!

也可以通过Windows主机上的SecureCRT使用SSH连接到MIPS虚拟机.

远程gdb调试

有时候不方便在虚拟机里直接运行gdb进行调试, 比如需要调试的是系统本身, 或者由于系统硬件限制无法直接运行全功能的gdb时, 这时候就需要进行远程调试.

远程gdb调试, 需要在MIPS虚拟机上运行gdbserver, 在Ubuntu上运行交叉编译套件中的gdb, 并连接MIPS虚拟机上的gdbserver.

  1. 交叉编译好要调试的可执行程序, 如hello, scp到MIPS虚拟机(/home/user/hello)

  2. 在QEMU MIPS虚拟机上安装gdbserver, 可以在x86主机上交叉编译一个, 也可以直接在MIPS虚拟机上通过apt-get install gdbserver安装

  3. 在MIPS虚拟机上运行gdbserver:

    root@debian-mips:/home/user# gdbserver --multi 192.168.10.20:1234
    Listening on port 1234
    
    其中192.168.10.20是Ubuntu的IP
    
  4. 在Ubuntu上运行交叉编译套件中的gdb, 如果提示找不到 libtermcap.so.2 文件, 可以安装libncurses5-dev, 然后找到其中的libncurses.so.5.x, 做个软链接:

    $sudo ln -s /lib/i386-linux-gnu/libncurses.so.5.9 /lib/i386-linux-gnu/libtermcap.so.2
    
  5. 连接远程gdbserver:

    (gdb) target extended-remote 192.168.128.21:1234
    Remote debugging using 192.168.128.21:1234
    
  6. 指定远程MIPS虚拟机上要调试的可执行文件:

    (gdb) set remote exec-file /home/user/hello
    
  7. 指定本机上要调试的可执行文件:

    (gdb) file /home/zzq/dev/qemu/hello
    Reading symbols from /home/zzq/dev/qemu/hello...done
    
  8. 接下来就可以跟在本机调试一下加断点调试了:

    (gdb) b main
    Breakpoint 1 at 0x4003ac: file hello.c, line 5.
    (gdb) r
    Starting program: /home/zzq/dev/qemu/hello
    
    Breakpoint 1, main () at hello.c:5
    5           printf("hello mips!\n");