使用GDB+QEMU调试Linux内核环境搭建-进阶篇

 

文章目录

  • 1 前言
  • 2 QEMU命令行参数
  • 3 调试的kernel信息设置
  • 4 调试virtio_net内核模块的start_xmit函数
    • 4.1 启动qemu vm
    • 4.2 启动gdb
    • 4.3 加载virtio-net内核模块的symbol file
      • 4.3.1 获取Guest中virtio-net内核模块的 .text,.data和 .bss
      • 4.3.2 在gdb中添加virtio-net内核模块的symbol信息
      • 4.3.3 gdb调试start_xmit函数
      • 4.3.4 地址随机化问题
        •         方法1 disable KASLR
        •         方法2 重新加载内核镜像

 

1 前言

        首先我们需要搭建gdb调试kernel的环境,这部分我们可以参考之前的博客【使用GDB+QEMU调试Linux内核环境搭建】的文章。这篇文章详细阐述了搭建的过程,我在这里就不再赘述,此处将介绍一个简单的方法。
        【使用GDB+QEMU调试Linux内核环境搭建】这篇文章只是给出了一个简单的VM启动命令,如果我们像调试virtio-net驱动模块,想学习下start_xmit函数在系统运行过程中的函数调用栈,我们有很多方法,例如使用systemtap命令来跟踪start_xmit函数,并列出此函数的参数信息。但是如果希望再跟踪此函数调用栈中的其他函数,及该函数的参数信息时,我们发现我们还需要再次编写systemtap脚本,这样的话所花费的时间较多,而且有时看到的信息也不全。而如果我们使用gdb跟踪,那么这些问题就迎刃而解了。
        下面我们就来介绍下如何使用gdb调试VM中的start_xmit函数,及调用栈上的其他函数的。
 

2 QEMU命令行参数

        我们可以使用我们平常使用的qemu命令行,我们只需要在这些参数中添加一个 “-s” 即可使用gdb来调试。下面是我的qemu命令行参数信息

#!/bin/bash

qemu-system-x86_64 \
-s  \
-device intel-iommu,intremap=on,device-iotlb=on \
-m 1G \
-object memory-backend-file,id=mem0,size=1G,mem-path=/mnt/huge_2MB,share=on,prealloc=yes \
-numa node,memdev=mem0 \
-cpu host \
-enable-kvm \
-machine pc-q35-rhel7.4.0,accel=kvm,kernel-irqchip=off \
-device piix3-usb-uhci,id=usb \
-device usb-tablet,id=input0,bus=usb.0,port=1 \
-smp 2,sockets=2,cores=1,threads=1 \
-drive file=/root/virtual-machine/system/centos7-virtio-display.qcow2,if=none,id=drive-virtio-disk0,format=qcow2,cache=writeback \
-device virtio-blk-pci,scsi=off,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 \
-device virtio-serial-pci,id=virtio-serial-bus \
-chardev socket,id=charchannel0,path=/tmp/qemu.agent.0,server,nowait \
-device virtserialport,bus=virtio-serial-bus.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0 \
-netdev user,id=netuser0,net=192.168.1.22,hostfwd=tcp::5522-:22 \
-device virtio-net-pci,netdev=netuser0,mac=52:54:00:02:d9:03,disable-modern=off \
-net nic,model=virtio,macaddr=00:16:3e:22:22:22 \
-net tap,ifname=virbr0-nic,script=no \
-gdb tcp::1234 \
-vnc 0:1 \
-monitor stdio

        其中命令行中的virtio-net设备参数信息如下

-net nic,model=virtio,macaddr=00:16:3e:22:22:22 \
-net tap,ifname=virbr0-nic,script=no \

        下面的参数信息是配置一个host的5522端口到guest的22端口的映射,这样就可以在host侧通过 【ssh -p 5522 [email protected]】 来连接到guest内部。

-netdev user,id=netuser0,net=192.168.1.22,hostfwd=tcp::5522-:22 \
-device virtio-net-pci,netdev=netuser0,mac=52:54:00:02:d9:03,disable-modern=off \

 

3 调试的kernel信息设置

        我们必须要保证guest内启动的kernel版本与host侧的kernel版本保持一致。例如guest内部启动的kernel是 3.10.0-327.36.4.el7.x86_64,那么我们就需要下载此kernel的对应的源码,然后编译出vmlinux文件。如下面列出的是Guest的kernel信息。

[root@localhost ~]# uname -r   
3.10.0-327.36.4.el7.x86_64

        下面是编译出3.10.0-327.36.4.el7.x86_64对应的kernel信息,如下
在这里插入图片描述
        行至于此,我们的准备工作就做完了,接下来我们将开始调试。

 

4 调试virtio_net内核模块的start_xmit函数

4.1 启动qemu vm

        按照上面的qemu启动参数启动vm,执行后效果如下
在这里插入图片描述
 

4.2 启动gdb

        进入到的linux-3.10.0-327.36.4.el7.x86_64目录执行gdb vmlinux命令如下。执行成功后,在gdb中执行target remote localhost:1234来与qemu vm进行连接。成功后即可进行gdb调试。
使用GDB+QEMU调试Linux内核环境搭建-进阶篇_第1张图片
 
 

4.3 加载virtio-net内核模块的symbol file

4.3.1 获取Guest中virtio-net内核模块的 .text,.data和 .bss

        通过下面的命令我们可以获得virtio-net内核模块的加载地址,可以通过在gdb处使用这三个信息来帮助我们能调试到virtio-net模块内的每个函数,包括start_xmit函数。如果.bss信息不存在的话也可以调试。如下所示,我的环境的.bss信息就是不存在的。

[root@localhost ~]# cat /sys/module/virtio_net/sections/.text
0xffffffffa006d000
[root@localhost ~]# cat /sys/module/virtio_net/sections/.data
0xffffffffa0072000
[root@localhost ~]# cat /sys/module/virtio_net/sections/.bss
cat: /sys/module/virtio_net/sections/.bss: No such file or directory

 

4.3.2 在gdb中添加virtio-net内核模块的symbol信息

        在gdb中使用add-symbol-file内部命令来完成此操作,命令如下

add-symbol-file drivers/net/virtio_net.ko <.text address> -s .data <.data address> -s .bss <.bss address>

 
        其中.text和.data的地址信息是会存在的,.bss有的系统是不存在的,所以,如果没有的话就可以把这部分去掉。代码实例如下

(gdb) add-symbol-file drivers/net/virtio_net.ko 0xffffffffa006d000 -s .data 0xffffffffa0072000
add symbol table from file "drivers/net/virtio_net.ko" at
        .text_addr = 0xffffffffa006d000
        .data_addr = 0xffffffffa0072000
(y or n) y
Reading symbols from drivers/net/virtio_net.ko...done.

 
 

4.3.3 gdb调试start_xmit函数

(gdb) b start_xmit
Breakpoint 1 at 0xffffffffa006db50: file drivers/net/virtio_net.c, line 780.
(gdb) 
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0xffffffffa006db50 in start_xmit at drivers/net/virtio_net.c:780
(gdb) 
(gdb) c
Continuing.
[Switching to Thread 1]

Breakpoint 1, start_xmit (skb=0xffff880039785700, dev=0xffff8800363eb000) at drivers/net/virtio_net.c:780
780     {
(gdb) bt
#0  start_xmit (skb=0xffff880039785700, dev=0xffff8800363eb000) at drivers/net/virtio_net.c:780
#1  0xffffffff81530461 in netdev_start_xmit (more=, txq=, dev=, skb=)
    at include/linux/netdevice.h:3377
#2  xmit_one (more=, txq=, dev=0xffff8800363eb000, skb=0xffff880039785700) at net/core/dev.c:2552
#3  dev_hard_start_xmit (first=, dev=0xffff8800363eb000, txq=, ret=)
    at net/core/dev.c:2568
#4  0xffffffff81550c24 in __netif_tx_lock (cpu=0, txq=0xffff88003639ee00) at include/linux/netdevice.h:3005
#5  sch_direct_xmit (skb=0xffff880039785700, q=0xffff88003a211a00, dev=0xffff8800363eb000, txq=0xffff88003639ee00,
    root_lock=0xffff88003a211a9c) at net/sched/sch_generic.c:164
#6  0xffffffff815308d6 in validate_xmit_skb (dev=0xffff8800363eb000, skb=0xffff880039785700) at net/core/dev.c:2599
#7  __dev_xmit_skb (txq=0xffff88003639ee00, dev=0xffff8800363eb000, q=0xffff88003a211a00, skb=0xffff880039785700)
    at net/core/dev.c:2711
#8  dev_queue_xmit (skb=0xffff880039785700) at net/core/dev.c:2833
#9  0xffffffff81530c21 in spin_lock (lock=0xffff880039785700) at include/linux/spinlock.h:293
#10 __dev_xmit_skb (txq=0xfffffff43fc03bf0, dev=0xffff88003befae00, q=0xffff880039785700, skb=0xffff8800363eb000)
    at net/core/dev.c:2695
#11 dev_queue_xmit (skb=0xffff8800363eb000) at net/core/dev.c:2833
#12 0xffffffff8159d323 in arp_xmit (skb=0xffff880039785700) at net/ipv4/arp.c:684
#13 0xffffffff8159d3c0 in arp_send (type=964187904, ptype=910077952, dest_ip=2684818688, dev=0x0 ,
    src_ip=115488, dest_hw=0xffff88003fc1c320 "", src_hw=0xffff880038ab8cd0 "", target_hw=0xffff88003bd60256 "RT")
    at net/ipv4/arp.c:708
#14 0xffffffff8159dfa3 in ip_rt_put (rt=) at include/net/route.h:203
#15 arp_filter (dev=0xffffffff81a33e80 , tip=, sip=) at net/ipv4/arp.c:430
#16 arp_process (sk=, skb=0xffff880039785e00) at net/ipv4/arp.c:854
#17 0xffffffff8159e12d in arp_rcv (skb=, dev=0xffff8800363eb000, pt=, orig_dev=)
    at net/ipv4/arp.c:964
#18 0xffffffff8152e652 in skb_orphan_frags (gfp_mask=32, skb=0xffff880039785e00) at include/linux/skbuff.h:2078
#19 __netif_receive_skb_core (skb=0xffff880039785e00, pfmemalloc=) at net/core/dev.c:3580
#20 0xffffffff8152e8d8 in deliver_skb (orig_dev=0xffff880039785e00, pt_prev=0xffffffff81a7e600 ,
---Type  to continue, or q  to quit---
    skb=) at net/core/dev.c:1721
#21 __netif_receive_skb_core (skb=0xffff8800363eb8c0, pfmemalloc=) at net/core/dev.c:3574
#22 0xffffffff8152e960 in netif_receive_skb (skb=0xffff880039785700,
    skb@entry=0xffffffff8152e8d8 <__netif_receive_skb_core+2024>) at net/core/dev.c:3642
#23 0xffffffffa006eb48 in receive_buf (len=, buf=, rq=0xffff880035de1c00, vi=0xffff88003fc03dd0)
    at drivers/net/virtio_net.c:459
#24 virtnet_poll (napi=0xffff8800363eb8c0, budget=64) at drivers/net/virtio_net.c:652
#25 0xffffffff8152ed92 in net_rx_action (h=) at net/core/dev.c:4498
#26 0xffffffff810876ff in __do_softirq () at kernel/softirq.c:266
#27 0xffffffff8164f66c in call_softirq () at arch/x86/kernel/entry_64.S:1229
#28 0xffffffff81018005 in do_softirq ()
#29 0xffffffff81087a95 in invoke_softirq () at kernel/softirq.c:340
#30 irq_exit () at kernel/softirq.c:372
#31 0xffffffff81650d25 in ?? ()
#32 0x000000000070a890 in ?? ()
#33 0x0000000000000000 in ?? ()

如果此时我们希望能切换到fram 6 validate_xmit_skb函数进行信息获取,则可按照下面的操作进行。

(gdb) f 6
#6  0xffffffff815308d6 in validate_xmit_skb (dev=0xffff8800363eb000, skb=0xffff880039785700) at net/core/dev.c:2599
2599            if (skb->next)
(gdb) p dev
$1 = (struct net_device *) 0xffff8800363eb000

      这些都是gdb的使用技巧,此部分后续再做详细介绍。至此设置start_xmit函数的准备工作准备就绪,后续根据需要打印相关信息即可。

 

4.3.4 地址随机化问题

        如果出现下面的情况,则很可能是因为Guest内核开启了地址随机化导致。

(gdb) bt
#0  virtblk_done (vq=0xffff88007afbe800) at drivers/block/virtio_blk.c:135
#1  0xffffffffa0046148 in vring_interrupt ()
#2  0xffffffff8111f83e in ?? () at kernel/irq/handle.c:43
#3  0xffff880035661e00 in ?? ()
#4  0x0000000000000046 in irq_stack_union ()
#5  0xffffffff8111fa1d in ?? () at kernel/irq/handle.c:145
#6  0xffff880035661e00 in ?? ()
#7  0xffff880035661e84 in ?? ()
#8  0xffff88007c1b8000 in ?? ()
#9  0xffff88007fc83f28 in ?? ()

        解决方法如下

        方法1 disable KASLR

        最直接了当的方法(也是官方提供的方法) 是关闭地址随机化,

        【1】重新编译内核关闭地址随机化
        【2】在kernel的cmdline 里面直接添加 nokaslr 【推荐使用此种方法】
 

        方法2 重新加载内核镜像

        我们找到内核加载的起始地址, 这个一般是 _text 符号的地址.

# echo 0x$(cat /proc/kallsyms | egrep -e "T _text$" | awk '{print $1}')
# cat /proc/kallsyms | grep -E " _text$"
ffffffff9ee00000 T _text

        然后通过 add-symbol-file 将 vmlinux 的起始地址指定到 0xffffffff9ee00000 位置处.

(gdb)add-symbol-file ./vmlinux 0xffffffff9ee00000

你可能感兴趣的:(Linux,kernel,linux,云计算)