ubuntu内核编译下编译Linux内核的时候一直显示symbolmap:00000001:invalid section 是什么情况?

用gdb调试内核类型于调试应用程序進程kgdb支持gdb的执行控制命令、栈跟踪和线程分析等。但kgdb不支持watchpointkgdb通过gdb宏来执行watchpoint。

在测试机上运行命令"ls -l /boot/vmlinuz"显示一个符号链接在测试机上,内核会运行到上述断点处并暂停。然后将断点信息传回开发机。在开发机上用户可以查看栈或运行其他调试命令。例如:运行栈跟踪命令显示的结果列出如下:

系统调用readlink在函数system_call进入内核,该函数显示在af_packet.c中这是不对的,因为对于汇编语言文件的函数gdb不能指出正常的玳码行。但gdb可以正确处理在C语言文件中内联汇编代码更多的调用层次是:sys_readlink和ext2_readlink。

使用gdb栈跟踪命令通常足够找出一个函数的调用层次关系泹当其中一个栈帧在扩展的内联函数中,或者从一个内联函数访问另一个内联函数时栈跟踪命令是不够用的,栈跟踪仅显示在内联函数Φ的源代码文件名和语句的行号通过查看外面的函数,这可能知道调用的内联函数但如果调用了两次内联函数,它就不知道是哪个内聯函数了

在栈跟踪中,gdb还与函数名一起显示代码地址在调用了一个内联函数的语句中,gdb显示了这些代码调用和被调用的地址脚本disasfun.sh可鼡来反汇编,源代码从vmlinux文件引用一个内核函数文件vmlinux含有内核函数的绝对地址,因此在汇编代码中看见的地址是在内存中的地址。

就像gdb顯示的一样函数sys_vfork调用函数do_fork,再看栈跟踪显示的第2帧gdb显示它在文件semaphore.h中的行号是120,显示的行号虽然没有用但是正确的查看该文件可得到確认,方法如下:

上述代码中在箭头所指示语句处,得到的信息仅是它在do_fork的一个扩展的内联函数down中gdb还打印了在do_fork中从下一个被调用的函數开始代码的绝对地址:0xc011433b。这里我们用脚本disasfun找出该地址所对应的代码行命令"disasfun vmlinux do_fork"输出的部分结果显示如下:

gdb具有分析应用程序线程的特征,咜提供了应用程序创建的线程的列表它允许开发者查看其中任何一个线程。gdb的特征可用来与kgdb一起查看内核线程gdb能提供内核中所有线程嘚列表。开发者可指定一个线程进行分析像backtrace,info regi这样的gdb命令接着可以显示指定线程上下文的信息

应用程序创建的所有线程分享同一地址涳间,相似地所有内核线程共享内核地址空间。每个内核线程的用户地址空间可能不同因此,gdb线程可较好地分析内核代码和驻留在内核空间的数据结构

如上所显示,gdb为每个线程设定在gdb中唯一的id当gdb内部引用一个线程时,可以使用这个id例如:线程7(PID 7)具有gdb id 8。为了分析內核线程8 我们指定线程9给gdb。gdb接着切换到该线程中准备做更多的分析。

kgdb stub使用x86处理器的调试特征支持硬件断点这些断点不需要代码修改。它们使用调试寄存器x86体系中的ia32处理器有4个硬件断点可用。每个硬件断点可以是下面三个类型之一:

写断点 当系统对在断点地址的内存位置进行写操作时触发一个写断点。写断点可以放置可变长度的数据写断点的长度指示为观察的数据类型长度,1表示为字节数据2表礻为2字节数据,3表示为4字节数据

用户模式Linux(User Mode Linux,UML)不同于其他Linux虚拟化项目UML尽量将它自己作为一个普通的程序。UML与其他虚拟化系统相比優点说明如下:

UML编译成本地机器的代码,像主机上的其他已编译应用程序一样运行它比在软件上应用整个硬件构架的虚拟机快得多。另┅方面UML不需要考虑依赖于特定CPU的虚拟化系统的硬件特异性。

每次Linux的改进UML自动得到这些功能,虚拟化系统并不一定能从更新中获益

内核需要与硬件或虚拟硬件交互,但UML可将交互看作其他方式例如:可以将这些交互转换成共享的库,其他程序可以在使用时连接该库它還可作为其他应用程序的子shell启动,能任何其他程序的stin/stdout使用

从Linux2.6.9版本起,用户模式Linux(User mode Linux,UML)已随Linux内核源代码一起发布它存放于arch/um目录下。编译好UML嘚内核之后可直接用gdb运行编译好的内核并进行调试。

用户模式Linux(User mode Linux,UML)将Linux内核的一部分作为用户空间的进程运行称为客户机内核。UML运行在基于Linux系统调用接口所实现的虚拟机UML运行的方式如图1所示。UML像其他应用程序一样与一个"真实"的Linux内核(称为"主机内核")交互应用程序还可運行在UML中,就像运行在一个正常的Linux内核下

使用UML的优点列出如下:

如果UML崩溃,主机内核还将运行完好

可以用非root用户运行UML。

可以像正常进程一样调试UML

在不中断任何操作下与内核进行交互。

用UML作为测试新应用程序的"沙箱"用于测试可能有伤害的程序。

可以用UML安全地开发内核

可以同时运行不同的发布版本。

由于UML基于以Linux系统调用接口实现的虚拟机UML无法访问主机的硬件设备。因此UML不适合于调试与硬件相关的驅动程序。

编译UML模式客户机Linux内核

(2)配置UML模式内核

如果使用缺省配置那么,方法如下:

如果运行配置界面方法如下:

如果不使用缺省配置defconfig,那么内核编译将使用主机的配置文件,该配置文件在主机/boot目录下对于UML模式内核来说,这是不对的它将编译产生缺乏重要的驱動程序和不能启动的UML。

当再次配置时可以先运行下面的命令清除所有原来编译产生的影响:

内核提供了配置选项用于内核调试,这些选項大部分在配置界面的kernel hacking菜单项中一般需要选取CONFIG_DEBUG_INFO选项,以使编译的内核包含调试信息

(3)编译UML模式内核

当编译完成时,系统将产生名为"linux"嘚UML二进制查看方法如下:

由于UML加入了调试符号,UML模式内核变得很大删除这些符号将会大大缩小内核的大小,变为与标准内核接近的UML二進制

现在,用户可以启动新的UML模式内核了

使用UML和管理UML的工具说明如下:

UMLd – 用于创建UML实例、管理实例启动/关闭的后台程序。

umlmgr –用于管理囸运行的UML实例的前台工具程序

UML Builder – 编译根文件系统映像(用于UML模式操作系统安装)。

uml switch2 用于后台传输的用户空间虚拟切换

VNUML – 基于XML的语言,萣义和启动基于UML的虚拟网络场景

UMLazi – 配置和运行基于虚拟机的UML的管理工具。

vmon – 运行和监管多个UML虚拟机的轻量级工具用Python 书写。

umvs – umvs是用C++和Bash脚夲写的工具用于管理UML实例。该应用程序的目的是简化UML的配置和管理它使用了模板,使得编写不同的UML配置更容易

MLN - MLN (My Linux Network) 是一个perl程序,用于从配置文件创建UML系统的完整网络使得虚拟网络的配置和管理更容易。MLN基于它的描述和简单的编程语言编译和配置文件系统模板并用一种組织方式存储它们。它还产生每个虚拟主机的启动和停止脚本在一个网络内启动和停止单个虚拟机。MLN可以一次使用几个独立的网络、项目甚至还可以将它们连接在一起。

Marionnet – 一个完全的虚拟网络实验基于UML,带有用户友好的图形界面

为了运行UML实例,用户需要运行Linux操作系統主机和带有自己文件系统的UML客户机用户可以从下载UML(如:kernel)和客户机文件系统(如:root_fs),运行UML实例的方法如下:

上述命令中参数mem指萣虚拟机的内存大小;参数ubda表示根文件系统root_fs作为虚拟机第一个块设备,虚拟机用/dev/udba表示虚拟机的第一个块设备与Linux主机系统的第一个物理块設备/dev/sda类似。

用户还可以自己创建虚拟块设备例如:建立交换分区并在UML上使用它的方法如下:

上述命令,创建了128M的交换分区作为第二个塊设备ubdb,接着启动UML模式内核,用ubdb作为它的交换分区

预打包的文件系统有一个带有"root"密码的root帐户,还有一个带有"user"密码的user帐户用户登录后鈳以进入虚拟机。预打包的文件系统已安装了各种命令和实用程序用户还可容易地添加工具或程序。

还有一些其他登录方法说明如下:

每个已配置(设备存在于/dev,并且/etc/inittab在上面运行了一个getty)的虚拟终端有它自己的xterm.

在启动输出中,找到类似下面的一行:

粘贴用户喜爱的终端程序到相应的tty如:minicom,方法如下:

如果网络正运行用户可用telnet连接到虚拟机。

虚拟机运行后用户可像一般Linux一样运行各种shell命令和应用程序。

可以粘附UML串行线和控制台到多个类型的主机I/O通道通过命令行指定,用户可以粘附它们到主机ptys, ttys, 文件描述子和端口常用连接方法说明洳下:

让UML控制台出现在不用的主机控制台上。

将两个虚拟机连接在一起一个粘到pty,另一个粘附到相应的tty

创建可从网络访问的虚拟,粘附虚拟机的控制台到主机的一个端口

用选项"con"或"ssl"(分别代表控制台和串行线)指定设备。例如:如果用户想用3号控制台或10号串行线交互命令行选项分别为"con3"和"ssl10"。

例如:指定pty给每个串行线的样例选项列出如下:

可以粘附UML设备到多个不同类型的通道每个类型有不同的指定方法,分别说明如下:

UML分配空闲的主机伪终端给用户可以通过粘附终端程序到相应的tty访问伪终端,方法如下:

kermit #启动它打开设备,然后连接設备

UML将粘附设备到指定的tty例如:一个样例选项列出如下:

上面语句将粘附UML的控制台1到主机的/dev/tty3。如果用户指定的tty是tty/pty对的slave端则相应的pty必须巳打开。

UML将运行一个xterm并且将设备粘附到xterm。

上述选项将粘附UML设备到指定的主机端口例如:粘附控制台1到主机的端口9000,方法如下:

粘附所囿串行线到主机端口方法如下:

用户可以通过telnet到该端口来访问这些设备,每个激活的telnet会话得到不同的设备如果有比粘附到端口的UML设备哆的telnet连接到一个端口,格外的telnet会话将阻塞正存在的telnet断线或直到其他设备变为激活(如:通过在/etc/inittab中设置激活)。

已存在的文件描述子:device=文件描述子

如果用户在UML命令行中建立了一个文件描述子他可以粘附UML设备到文件描述子。这最常用于在指定所有其他控制台后将主控制台放囙到stdin和stdout上方法如下:

与"none"选项相比,上述选项允许打开设备但读将阻塞,并且写将成功但数据会被丢掉。

上述选项将引起设备消失洳果你正使用devfs,设备将不出现在/dev下如果设备出现,尝试打开它将返回错误-ENODEV

用户还可以指定不同的输入和输出通道给一个设备,最常用嘚用途是重粘附主控制到stdin和stdout例如:一个样例选项列出如下:

上述诗句将引起在主机/dev/tty3上的串行线3接受输入,显示输出在xterm上

如果用户决定將主控制台从stdin/stdout移开,初始的启动输出将出现在用户正运行UML所在的终端然而,一旦控制台驱动程序已初始化启动及随后的输出将出现在控制台0所在的地方。

UML实例可以用网络访问主机、本地网络上的其他机器和网络的其他部分新的辅助程序uml_net进行主机建立时需要root权限。

TUN/TAP, ethertap, slip和slirp传輸允许UML实例与主机交换包它们可定向到主机或主机可扮作路由器提供对其他物理或虚拟机的访问。

pcap传输是一个综合的仅读接口用libpcap二进淛从主机上的接口收集包并过滤包。这对于构建预配置的交通监管器或sniffer来说是有用的。

后台和多播传输提供了完全虚拟的网络络其他虚擬机器该网络完全从物理网络断开,除非某一个虚拟机扮作网关

如何选择这些主机传输类型 '用户可根据用途进行选择,选择方法说明洳下:

ethertap – 如果用户想对主机网络进行访问并且运行以2.2以前版本时,使用它

TUN/TAP – 如果用户想访问主机网络,可使用它TUN/TAP 运行在2.4以后的版本,比ethertap 更有效率TUN/TAP 传输还能使用预置的设备,避免对uml_net辅助程序进行setuid操作

Multicast – 如果用户期望建立一个纯虚拟网络,并且仅想建立UML就使用它。

茭换机后台 – 如果用户想建立一个纯虚拟网络并且不介意为了得到较好执行效率而建立后台,就使用它

slirp – 如果用户在主机上没有root权限對建立网络进行访问,或者如果用户不想分配对UML分配IP时使用它。

pcap – 对实际的网络连接没有太多用途但用于主机上监管网络交通很有用。

(1)网络建立通用步骤

首先用户必须已在UML中打开虚拟网络。如果运行下载的预编译的内核则已打开虚拟网络。如果用户自己编译内核则在配置界面上的"Network device support"菜单中,打开"Network device support"和三种传输选项

下一步是提供网络设备给虚拟机,通过在内核命令行中进行描述格式如下:

例如:一个虚拟以太网设备可以如下粘附到一个主机ethertap上:

上述语句在虚拟机内部建立eth0,粘附它自己到主机/dev/tap0指定一个以太网地址,并指定给主機tap0接口一个IP地址

一旦用户决定如何建立设备后,就可以启动UML、登录、配置设备的UML侧并设置对外界的路由。此后UML就可以与网络任何其怹机器(物理或虚拟的)通信。

从下载工具uml_net和uml_switch编译并安装。uml_switch是在UML系统之间管理虚拟网络的后台而不用连接到主机系统的网络。uml_switch将在UNIX域嘚socket上监听连接并在连接到UNIX域的客户端之间转发包。

TUN/TAP, ethertap和daemon接口允许用户给虚拟以太网设备指定硬件地址但通常不需要指定硬件地址。如果命令行没有指定硬件地址它将提供地址为fe:fd:nn:nn:nn:nn,其中nn.nn.nn.nn是设备IP地址。这种方法通常足够保证有唯一的硬件地址

一旦用命令行描述网络设备,用户在启动UML和登录后第一件事应是建立接口,方法如下:

此时用户应可以ping通主机。为了能查看网络用户设置缺省的路由为到达主機,方法如下:

例如:主机IP为192.168.0.4设置路由方法如下:

注意:如果UML不能与物理以太网上其他主机通信,可能是因为网络路由自动建立可以運行"route –n"查看路由,结果类似如下:

掩码不是255.255.255.255因此,就使用到用户主机的路由替换它方法如下:

添加缺省的路由到主机,将允许UML与用户鉯太网上任何机器交换包

在多个UML之间建立一个虚拟网络的最简单方法是使用多播传输。用户的系统必须在内核中打开多播(multicast)并且在主机上必须有一个多播能力的网络设备。通常它是eth0

为了使用多播,运行两个UML命令行带有"eth0=mcast"选项。登录后用户在每个虚拟机上用不同的IP哋址配置以太网设备,方法如下:

这两个虚拟机应能相互通信

传输设置的整个命令行选项列出如下:

TUN/TAP驱动程序实现了虚拟网卡的功能,TUN 表示虚拟的是点对点设备TAP表示虚拟的是以太网设备,这两种设备针对网络包实施不同的封装利用TUN/TAP驱动,可以将tcp/ip协议栈处理好的网络分包传给任何一个使用TUN/TAP驱动的进程由进程重新处理后再发到物理链路中。

TUN/TAP是与主机交换包的较好机制主机建立TUN/TAP较简单的方法是使用uml_net辅助程序,它包括插入tun.o内核模块、配置设备、建立转发IP、路由和代理ARP

如果在设备的主机侧指定了IP地址,uml_net将在主机上做所有的建立工作粘附設备到TUN/TAP设备的命令行格式列出如下:

例如:下面参数将粘附UML的eth0到下一个可用的tap设备,指定IP地址192.168.0.254么tap设备的主机侧并指定一个基于IP地址的以呔网地址。

如果用户没有更好的uml_net可以预先建立TUN/TAP。步骤如下:

用工具tunctl创建tap设备方法如下:

上述命令中,uid是用户的ID或UML将运行登录的用户名

配置设备IP地址,方法如下:

建立路由和ARP方法如下:

注意:这个配置没有重启机时失效,每次主机启动时应重新设置它。最好的方法昰用一个小应用程序每次启动时,读出配置文件重新建立设置的配置

为了不使用2个IP地址和ARP,还可通过对UML使用网桥提供对用户LAN直接访问方法如下:

一旦设备建立好后,运行UML命令格式为: eth0=tuntap,devicename,例如:一个样例列出如下:

如果用户不再使用tap设置可以用下面命令删除它:

最後,tunctl有一个"-b"(用于简捷模式)切换仅输出它所创建的tap设备的名字。它很适合于被一个脚本使用方法如下:

交换机后台uml_switch以前称为uml_router,它提供了创建整个虚拟网络的机制缺省下,它不提供对主机网络的连接

首先,用户需要运行uml_switch无参数运行时,表示它将监听缺省的unix域socket使鼡选项"-unix socket"可指定不同的socket,"-hub"可将交换机后台变为集线器(Hub)如果用户期望交换机后台连接到主机网络(允许UML访问通过主机访问外部的网络),可使用选项"-tap tap0"

uml_switch还可作为后台运行,方法如下:

内核命令行交换机的通用命令行格式列出如下:

通常只需要指定参数"daemon"其他使用缺省参数,如果用户运行没有参数的交换机后台在同一台机器上使用选项"eth0=daemon"运行UML,etho驱动程序会直接粘附它自己到交换机后台参数socket为unix域socket的文件名,鼡于uml_switch和UML之间网络通信

slirp通常使用外部程序/usr/bin/slirp,仅通过主机提供IP网络连接它类似于防火墙的IP伪装,跃然传输有用户空间进行而不是由内核進行。slirp不在主机上建立任何接口或改变路由slirp在主机上不需要root权限或运行setuid。

slirp命令行的通用格式为:

在UML上用户应使用没有网关IP的etho设置缺省蕗由,方法如下:

pcap对网络上传输的数据包进行截获和过滤通过命令行或pcap传输粘附到UML以太网设备uml_mconsole工具,语法格式如下:

一个设置pcap的样例列絀如下:

上述语句将引起在主机eth0上的UML eth0将所有的tcp发出并且在主机eth0上的UML eth1将发出所有非tcp包。

主机上的网络设备需要配置IP地址还需要用值为1484的mtu配置tap设备。slip设置还需要配置点到点(pointopoint)地址方法如下:

如果正建立tap设备,就将路由设置到UML IP方法如下:

为了允许网络上其他主机看见这個虚拟机,化理ARP设置如下:

最后将主机设置到路由包,方法如下:

在虚拟机间共享文件系统

在虚拟机间共享文件系统的方法是使用ubd(UML Block Device)塊设备驱动程序的写拷贝(copy-on-writeCOW)分层能力实现。COW支持在仅读的共享设备上分层读写私有设备一个虚拟机的写数据存储在它的私有设备上,而读来自任一请求块有效的设备如果请求的块有效,读取私有设备如果无效,就读取共享设备

用这种方法,数据大部分在多个虚擬机间共享每个虚拟机有多个小文件用于存放虚拟机所做的修改。当大量UML从一个大的根文件系统启动时这将节约大量磁盘空间。它还提供执行性能因为主机可用较小的内存缓存共享数据,主机的内存而不是硬盘提供UML硬盘请求服务

可通过简单地加COW文件的名字到合适的ubd,实现加一个COW层到存在的块设备文件方法如下:

上述语句中,"root_fs_cow"是私有的COW文件"root_fs_debian_22"是存在的共享文件系统。COW文件不必要存在如果它不存在,驱动程序将创建并初始化它一旦COW文件已初始化,可在以命令行中使用它方法如下:

后备文件(backing file)的名字存在COW文件头中,因此在命令荇中继续指定它将是多余的

COW文件是稀疏的,因此它的长度不同于硬盘的实际使用长度可以用命令"ls –ls"查看硬盘的实际消耗,用"ls –l"查看COW文件和后备文件(backing file)的长度方法如下:

从上述显示结构,用户会发现COW文件实际硬盘消耗小于1M面不是492M。

一旦文件系统用作一个COW文件的仅读後备文件不要直接从它启动或修改它。这样会使使用经的任何COW文件失效。后备文件在创建时它的修改时间mtime和大小size存放在COW文件头中它們必须相匹配。如果不匹配驱动程序将拒绝使用COW文件。

如果用户手动地改变后备文件或COW头将得到一个崩溃的文件系统。

操作COW文件的方法说明如下:

由于UML存放后备文件名和它的修改时间mtime在COW头中如果用户删除后文件文件,这些信息将变成无效的因此,删除后备文件的步驟如下:

用保护时间戳的方式删除文件通常,使用"-p"选项拷贝操作命令"cp –a"中的"-a"隐含了"-p"。

通过启动UML更新COW头命令行指定COW文件和新的后备文件的位置,方法如下:

UML将注意到命令行和COW头之间的不匹配检查新后文件路径的大小和修改时间mtime,并更新COW头

如果当用户删除后备文件时莣记保留时间戳,用户可手动整理mtime方法如下:

注意如果对真正修改过而不是刚删除的后备文件进行上述操作,那么将会文件崩溃用户將丢失文件系统。

(2)uml_moo :将COW文件与它的后备文件融合

依赖于用户如何使用UML和COW设备系统可能建议每隔一段时间融合COW文件中的变化到后备文件Φ。用户可以用工具uml_moo完成该操作方法如下:

由于信息已在COW文件头中,因此不必指定后备文件。

uml_moo在缺省下创建一个新的后备文件它还囿一个破坏性的融合选项,直接将融合COW文件到它当前的后备文件当后备文件仅有一个COW文件与它相关时,该选项很有用如果多个COW与一个後备文件相关,融合选项"-d"将使所有其他的COW无效但是,如果硬盘空间不够时使用融合选项"-d"很方便快捷,方法如下:

正常创建COW文件的方法昰以UML命令行中指定一个不存在的COW文件让UML创建COW文件。但是用户有时想创建一个COW文件,但不想启动UML此时,可以使用uml_mkcow工具方法如下:

如果用户想销毁一个存在的COW文件,可以加"-f"选项强制重写旧的COW文件方法如下:

如果根文件系统硬盘空间不够大,或者想使用不同于ext2的文件系統用户就可能想创建和挂接新的UML文件系统,用户可以用如下方法创建UML的根文件系统:

(1)创建文件系统的文件

使用命令dd创建一个合适尺団的空文件用户可以创建稀疏文件,该文件直到实际使用时才分配硬盘空间例如:下面的命令创建一个100M填满0的稀疏文件:

(2)指定文件给一个UML设备

在UML命令行上加入下面的选项:

上述命令中,ubdd应确保没被使用

(3)创建和挂接文件系统

创建和挂接文件系统方法如下:

如果鼡户在UML中想访问主机上的文件,用户可将主机当作独立的机器可以使用nfs从主机挂接目录,或者用scp和rcp拷贝文件到虚拟机因为UML运行在主机仩,它能象其他进程一样访问这些文件并使它们在虚拟机内部可用,而不需要使用网络

还可以使用hostfs虚拟文件系统,用户通过它可以挂接一个主机目录到UML文件系统并像在主机上一样访问该目录中的文件。

首先确认虚拟机内部是否有hostfs可用,方法如下:

如果没有列出hostfs则需要重编译内核,配置hostfs将它编译成一个内核模块,并用"insmod"插入该内核模块

挂接hostfs文件系统,例如:将hostfs挂接到虚拟机的/mnt/host下方法如下:

如果鼡户不想挂接主机的root目录,他可以用"-o"选项指定挂接的子目录例如:挂接主机的/home到虚拟机的/mnt/home,方法如下:

在UML命令行选项可使用hostfs选项用来指定多个hostfs挂接到一个主机目录或阻止hostfs用户从主机上销毁数据,方法如下:

当前可用的选项是"append"用来阻止所有的文件在追加方式打开,并不尣许删除文件

(3)hostfs作为根文件系统

还可以通过hostfs从主机上的目录而不是在一个文件中的标准文件系统启动UML。最简单的方法是用loop挂接一个存茬的root_fs文件方法如下:

用户需要将/etc/fstab中的文件类型改变为"hostfs",fstab中的该行列出如下:

接着用户可以用chown将目录中root拥有的所有文件改变为用户拥有方法如下:

如果用户不想用上面的命令改变文件属主,用户可以用root身份运行UML

接着,确保UML内核编译进hostfs而不是以内核模块方式包含hostfs。那么加入下面的命令行运行UML:

加入上述选项后,UML应该像正常的一样启动

如果hostfs不在内核中,用户需要编译hostfs用户可以将它编译进内核或内核模块。用户在内核配置界面上选项hostfs并编译和安装内核。

因为UML运行为正常的Linux进程用户可以用gdb像调试其他进程一样调试内核,稍微不同的昰:因为内核的线程已用系统调用ptrace进行拦截跟踪因此,gdb不能ptrace它们UML已加入了解决此问题的机制。

为了调试内核用户需要从源代码编译,确保打开CONFIG_DEBUGSYM和CONFIG_PT_PROXY配置选项它们分别用来确保编译内核带有"-g"选项和打开ptrace代理,以便gdb能与UML一起工作调试内核

(1)在gdb下启动内核

用户可以在命囹行中放入"debug"选项,在启动UML时将内核放在gdb的控制之下用户可以得到一个运行gdb的xterm,内核将送一些命令到gdb停在"start_kernel"处,用户可以输入"next", "step"或"cont"运行内核

并非每个bug在当前运行的进程中,有时候当进程在信号量上或其他类似原因死锁时,原本不应该挂起的进程在内核中挂起这种情况下,用户在gdb中用"Ctrl+C"时得到一个跟踪栈,用户将可以看见到不相关的空闲线程

用户本想看到的是不应该睡眠的进程的栈,为了看到睡眠的进程用户可以在主机上用命令ps得到该进程的主机进程id。

用户将gdb与当前线程分离方法如下:

然后将gdb粘附到用户感兴趣的线程上,方法如下:

查看该线程的栈方法如下:

ddd可以工作于UML,用户可以主机上运行ddd它给gdb提供了图形界面。运行ddd的步骤如下:

启动ddd方法如下:

用命令ps可鉯得到ddd启动的gdb的pid。

在gdb中输入"c"UML将继续运行,用户可接着像调试其他进程一样调试了

gdb已支持调试动态装载入进程的代码,这需要在UML下调试內核模块调试内核模块有些复杂,用户需要告诉gdb装入UML的对象文件名以及它在内存中的位置接着,它能读符号表并从装载地址指出所囿的符号。

当用户在rmmod内核模块后重装载它时可得到更多信息。用户必须告诉gdb忘记所有它的符号包括主UML的符号,接着再装载回所有的符號

用户可以使用脚本umlgdb进行内核模块的重装载和读取它的符号表。用户还可以手动进行一步步处理完成符号表的获取工作下面分别说明這两种方法。

1)运行脚本umlgdb调试内核模块

运行脚本umlgd较容易获取内核模块的符号表

首先,用户应告诉内核模块所在的位置在脚本中有一个列表类似如下:

用户将上述列表改为将调试的内核模块的路径,接着从UML的顶层目录运行该脚本,显示如下:

在用户运行UML后用户只需要茬"att 1"按回车,并继续执行它方法如下:

此时,当用户用insmod插入内核模块显示列出如下:

(2)手动调试内核模块

在调试器中启动内核,并用insmod戓modprobe装载内核模块在gdb中执行下面命令:

这是已装载进内核的内核模块列表,通常用户期望的内核模块在module_lis中如果不在,就进入下一个链接查看name域,直到找到用户调试的内核模块获取该结构的地址,并加上module.size_of_struct值gdb可帮助获取该值,方法如下:

从内核模块开始处的偏移偶尔会妀变因此,应检查init和cleanup的地址方法如下:

如果断点不在正确的位置或不工作等 ,用户可以查看内核模块结构init和cleanup域应该类似如下:

如果洺字正确,但它们有偏移那么,用户应该将偏移加到add-symbol-file所在地址上

当用户想装载内核模块的新版本时,需要让gdb删除旧内核模块的所有符號方法如下:

接着,从内核二进制重装载符号方法如下:

然后,重复上面的装载符号过程还需要重打开断点。

(5)粘附gdb到内核

如果鼡户还没有在gdb下运行内核用户可以通过给跟踪线程发送一个SIGUSR1,用于以后粘附gdb到内核控制台第一行的输出鉴别它的id,显示类似如下:

上述命令运行后用户将可看见带有gdb运行的xterm。

如果用户已将mconsole(UML的控制台)编译进UML那么可用mconsole客户端启动gdb,方法如下:

上述命令运行后用户將可看见带有gdb运行的xterm。

(6)使用可替换的调试器

UML支持粘附到一个已运行的调试器而不是启动gdb本身。当gdb是一些UI的子进程(如:emacs或ddd)时这將是有用的。它还被用于在UML上运行非gdb的调试器下面是一个使用strace作为可替代调试器的例子。

如果用户在UI下使用gdb那么,应告诉UML"att 1"那么,UI将粘附到UML

下面以替换调试器strace为例,用户可以用strace调试实际的内核方法如下:

在shell中运行下述命令

strace输出将出现在输出文件中。

注意:运行下面嘚命令结果不同于前面命令。

上述命令将仅strace主UML线程跟踪的线程不做任何实际的内核操作。它仅标识出虚拟机而使用上述的strce将显示虚擬机低层的活动情况。


内核锁验证器(Kernel lock validator)可以在死锁发生前检测到死锁即使是很少发生的死锁。它将每个自旋锁与一个键值相关相似嘚锁仅处理一次。加锁时查看所有已获取的锁,并确信在其他上下文中没有已获取的锁在新获取锁之后被获取。解锁时确信正被解開的锁在已获取锁的顶部。

当加锁动态发生时锁验证器映射所有加锁规则,该检测由内核的spinlocks、rwlocks、mutexes和rwsems等锁机制触发不管何时锁合法性检測器子系统检测到一个新加锁场景,它检查新规则是否违反正存在的规则集如果新规则与正存在的规则集一致,则加入新规则内核正瑺运行。如果新规则可能创建一个死锁场景那么这种创建死锁的条件会被打印出来。

当判断加锁的有效性时所有可能的"死锁场景"会被栲虑到:假定任意数量的CPU、任意的中断上下文和任务上下文群、运行所有正存在的加锁场景的任意组合。在一个典型系统中这意味着有荿千上万个独立的场景。这就是为什么称它为"加锁正确性"验证器对于所有被观察的规则来说,锁验证器用数学的确定性证明死锁不可能發生假定锁验证器实现本身正确,并且它内部的数据结构不会被其他内核子系统弄坏

还有,验证器的属性"所有可能的场景"也使查找变嘚复杂特别是多CPU、多上下文竞争比单个上下文规则复杂得多,

为了增加验证器的效率不是将每个锁实例进行映射,而是映射每个锁类型例如:内核中所有的结构inode对象有inode->inotify_mutex,如果缓存了10000个inode将会有10000个锁对象。但->inotify_mutex是单个锁类型所有->inotify_mutex发生的加锁活动都归入单个锁类型。


验证器操作的基本对象是锁类Lock-class一个锁类是一组锁,逻辑上有同样的加锁规则尽管锁可能有多个实例。例如:在结构inode中的一个锁是一个类洏每个节点有它自己的锁类实例。

验证器跟踪锁类的状态和不同锁类之间的依赖性验证器维护一个有关状态和依赖性是否正确的滚动证據。

不像一个锁实例锁类lock-class它本身从不消失:当lock-class注册使用后,所有随后锁类的使用都会被附加到该lock-class上

}

内核开发比用户空间开发更难的┅个因素就是内核调试艰难内核错误往往会导致系统宕机,很难保留出错时的现场调试内核的关键在于你的对内核的深刻理解。 

在调試一个bug之前我们所要做的准备工作有: 

  • 有一个被确认的bug。

  • 包含这个bug的内核版本号需要分析出这个bug在哪一个版本被引入,这个对于解决問题有极大的帮助可以采用二分查找法来逐步锁定bug引入版本号。

  • 对内核代码理解越深刻越好同时还需要一点点运气。

  • 该bug可以复现如果能够找到复现规律,那么离找到问题的原因就不远了

  • 最小化系统。把可能产生bug的因素逐一排除掉

内核中的bug也是多种多样的。它们的產生有无数的原因同时表象也变化多端。从隐藏在源代码中的错误到展现在目击者面前的bug其发作往往是一系列连锁反应的事件才可能絀发的。虽然内核调试有一定的困难但是通过你的努力和理解,说不定你会喜欢上这样的挑战 

三  内核调试配置选项

学习编写驱动程序偠构建安装自己的内核(标准主线内核)。最重要的原因之一是:内核开发者已经建立了多项用于调试的功能但是由于这些功能会造成額外的输出,并导致能下降因此发行版厂商通常会禁止发行版内核中的调试功能。 

为了实现内核调试在内核配置上增加了几项:

启用選项例如: 

从内核2.5开发,为了检查各类由原子操作引发的问题内核提供了极佳的工具。 
内核提供了一个原子操作计数器它可以配置成,一旦在原子操作过程中进城进入睡眠或者做了一些可能引起睡眠的操作,就打印警告信息并提供追踪线索 
所以,包括在使用锁的时候调用schedule()正使用锁的时候以阻塞方式请求分配内存等,各种潜在的bug都能够被探测到 
下面这些选项可以最大限度地利用该特性: 

一些内核調用可以用来方便标记bug,提供断言并输出信息最常用的两个是BUG()和BUG_ON()。

当调用这两个宏的时候它们会引发OOPS,导致栈的回溯和错误消息的打茚 

有些时候,只需要在终端上打印一下栈的回溯信息来帮助你调试这时可以使用dump_stack()。这个函数只在终端上打印寄存器上下文和函数的跟蹤线索 

内核提供的格式化打印函数。 

      健壮性是printk最容易被接受的一个特质几乎在任何地方,任何时候内核都可以调用它(中断上下文、進程上下文、持有锁时、多处理器处理时等) 

      在系统启动过程中,终端初始化之前在某些地方是不能调用的。如果真的需要调试系统啟动过程最开始的地方有以下方法可以使用: 

  • 使用串口调试,将调试信息输出到其他终端设备

  • 使用early_printk(),该函数在系统启动初期就有打印能力但它只支持部分硬件体系。

的功能将这两个字符串组合在一起组合的结果是将日志级别和用户指定的格式字符串包含在一个字符串中。 
内核使用这个指定LOG级别与当前终端LOG等级console_loglevel来决定是不是向终端打印 
下面是可使用的LOG等级: 

级别以上的日志消息会被记录)。由于默認值存在变化所以在使用时最好指定LOG级别。有LOG级别的一个好处就是我们可以选择性的输出LOG比如平时我们只需要打印KERN_WARNING级别以上的关键性LOG,但是调试的时候我们可以选择打印KERN_DEBUG等以上的详细LOG。而这些都不需要我们修改代码只需要通过命令修改默认日志输出级别: 

0

第一项定義了 printk API 当前使用的日志级别。这些日志级别表示了控制台的日志级别、默认消息日志级别、最小控制台日志级别和默认控制台日志级别printk_delay 值表示的是 printk 消息之间的延迟毫秒数(用于提高某些场景的可读性)。注意这里它的值为 0,而它是不可以通过 /proc 设置的printk_ratelimit 定义了消息之间允许嘚最小时间间隔(当前定义为每 5 秒内的某个内核消息数)。消息数量是由 printk_ratelimit_burst 定义的(当前定义为 10)如果您拥有一个非正式内核而又使用有帶宽限制的控制台设备(如通过串口), 那么这非常有用注意,在内核中速度限制是由调用者控制的,而不是在printk 中实现的如果一个 printk 鼡户要求进行速度限制,那么该用户就需要调用printk_ratelimit 函数 

  ① 消息被读出到用户空间时,此消息就会从环形队列中删除 
  ② 当消息缓冲区满时,如果再有printk()调用时新消息将覆盖队列中的老消息。 
  ③ 在读写环形队列时同步问题很容易得到解决。 

  ※ 这个纪录缓冲区之所以称为环形是因为它的读写都是按照环形队列的方式进行操作的。

在标准的Linux系统上用户空间的守护进程klogd从纪录缓冲区中获取内核消息,再通过syslogd守護进程把这些消息保存在系统日志文件中klogd进程既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息默认情况下,它选择读取/proc方式实现klogd守护进程在消息缓冲区有新的消息之前,一直处于阻塞状态一旦有新的内核消息,klogd被唤醒读出内核消息并进行处理。默认情况下處理例程就是把内核消息传给syslogd守护进程。syslogd守护进程一般把接收到的消息写入/var/log/messages文件中不过,还是可以通过/etc/syslog.conf文件来进行配置可以选择其他嘚输出文件。

dmesg 命令也可用于打印和控制内核环缓冲区这个命令使用 klogctl 系统调用来读取内核环缓冲区,并将它转发到标准输出(stdout)这个命囹也可以用来清除内核环缓冲区(使用 -c 选项),设置控制台日志级别(-n 选项)以及定义用于读取内核日志消息的缓冲区大小(-s 选项)。紸意如果没有指定缓冲区大小,那么 dmesg 会使用 klogctl

a) 虽然printk很健壮但是看了源码你就知道,这个函数的效率很低:做字符拷贝时一次只拷贝一个芓节且去调用console输出可能还产生中断。所以如果你的驱动在功能调试完成以后做性能测试或者发布的时候千万记得尽量减少printk输出做到仅茬出错时输出少量信息。否则往console输出无用信息影响性能 

8 内核printk和日志系统的总体结构

动态调试是通过动态的开启和禁止某些内核代码来获取额外的内核信息。 
可以通过简单的查询语句来筛选需要显示的信息 

-行号(包括指定范围的行号)

MEMWATCH 由 Johan Lindh 编写,是一个开放源代码 C 语言内存错误检测工具您可以自己下载它。只要在代码中添加一个头文件并在 gcc 语句中定义了 MEMWATCH 之后您就可以跟踪程序中的内存泄漏和错误了。MEMWATCH 支持ANSIC它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous

清单 1 中的代码将分配两个 512 字节的内存块然后指向第一个内存块的指针被設定为指向第二个内存块。结果第二个内存块的地址丢失,从而产生了内存泄漏 

当您运行 test1 程序后,它会生成一个关于泄漏的内存的报告清单 2 展示了示例 memwatch.log 输出文件。 

MEMWATCH 为您显示真正导致问题的行如果您释放一个已经释放过的指针,它会告诉您对于没有释放的内存也一樣。日志结尾部分显示统计信息包括泄漏了多少内存,使用了多少内存以及总共分配了多少内存。

YAMD 显示我们已经释放了内存而且存茬内存泄漏。让我们在清单 4 中另一个样本程序上试试 YAMD 

MEMWATCH 和 YAMD 都是很有用的调试工具,它们的使用方法有所不同对于 MEMWATCH,您需要添加包含文件memwatch.h 並打开两个编译时间标记对于链接(link)语句,YAMD 只需要 -g 选项 

多数 Linux 分发版包含一个 Electric Fence 包,不过您也可以选择下载它Electric Fence 是一个由 Bruce Perens 编写的malloc()调试库。它就在您分配内存后分配受保护的内存如果存在 fencepost 错误(超过数组末尾运行),程序就会产生保护错误并立即结束。通过结合 Electric Fence 和 gdb您鈳以精确地跟踪到哪一行试图访问受保护内存。ElectricFence 的另一个功能就是能够检测内存泄漏 

strace 命令是一种强大的工具,它能够显示所有由用户空間程序发出的系统调用strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息而且不需要以任何特殊的方式来构建内核。将跟踪信息发送到应用程序及内核开发者都很有用在清单 6 中,分区的一种格式有错误清单显示了 strace 的开头部分,内容是关于调出创建文件系统操作(mkfs

OOPS(也称 Panic)消息包含系统错误的细节如 CPU 寄存器的内容等。是内核告知用户有不幸发生的最常用的方式 
内核只能发布OOPS,这个过程包括向终端上输出错误消息输出寄存器保存的信息,并输出可供跟踪的回溯线索通常,发送完OOPS之后内核会处于一种不稳定的状态。 
OOPS的產生有很多可能原因其中包括内存访问越界或非法的指令等。 

※ 作为内核的开发者必定将会经常处理OOPS。

※ OOPS中包含的重要信息对所有體系结构的机器都是完全相同的:寄存器上下文和回溯线索(回溯线索显示了导致错误发生的函数调用链)。

在 Linux 中调试系统崩溃的传统方法是分析在发生崩溃时发送到系统控制台的 Oops 消息。一旦您掌握了细节就可以将消息发送到 ksymoops 实用程序,它将试图将代码转换为指令并将堆栈值映射到内核符号 

接下来,您要确定 jfs_mount 中的哪一行代码引起了这个问题Oops 消息告诉我们问题是由位于偏移地址 3c 的指令引起的。做这件倳的办法之一是对 jfs_mount.o 文件使用 objdump 实用程序然后查看偏移地址 3c。Objdump 用来反汇编模块函数看看您的 C 源代码会产生什么汇编指令。清单 8 显示了使用 objdump 後您将看到的内容接着,我们查看jfs_mount 的 C 代码可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要是因为 Oops 消息将该处标识为引起问题的位置。 

开发版2.5内核引入了kallsyms特性它可以通过定义CONFIG_KALLSYMS编译选项启用。该选项可以载入内核镜像所对应的内存地址的符号名称(即函数名)所鉯内核可以打印解码之后的跟踪线索。相应解码OOPS也不再需要System.map和ksymoops工具了。另外 
这样做,会使内核变大些因为地址对应符号名称必须始終驻留在内核所在内存上。 


 
 


kexec-tools他将捕获内核的地址传递给生产内核,从而在系统崩溃的时候能够找到捕获内核的地址并运行没有 kexec 就没有 kdump。先有 kexec 实现了在一个内核中可以启动另一个内核才让 kdump 有了用武之地。kexec 原来的目的是为了节省 kernel 开发人员重启系统的时间谁能想到这个“偷懒”的技术却孕育了最成功的内存转存机制呢?

Kdump 的概念出现在 2005 左右是迄今为止最可靠的内核转存机制,已经被主要的 linux? 厂商选用kdump是┅种先进的基于 kexec 的内核崩溃转储机制。当系统崩溃时kdump 使用 kexec 启动到第二个内核。第二个内核通常叫做捕获内核以很小内存启动以捕获转儲镜像。第一个内核保留了内存的一部分给第二内核启动用由于 kdump 利用 kexec 启动捕获内核,绕过了 BIOS所以第一个内核的内存得以保留。这是内核崩溃转储的本质
kdump 需要两个不同目的的内核,生产内核和捕获内核生产内核是捕获内核服务的对像。捕获内核会在生产内核崩溃时启動起来与相应的 ramdisk 一起组建一个微环境,用以对生产内核下的内存进行收集和转存

构建系统和 dump-capture 内核,此操作有 2 种方式可选:
1)构建一个單独的自定义转储捕获内核以捕获内核转储;
2) 或者将系统内核本身作为转储捕获内核这就不需要构建一个单独的转储捕获内核。
方法(2)只能用于可支持可重定位内核的体系结构上;目前 i386x86_64,ppc64 和 ia64 体系结构支持可重定位内核构建一个可重定位内核使得不需要构建第二个內核就可以捕获转储。但是可能有时想构建一个自定义转储捕获内核以满足特定要求

在内核崩溃之前所有关于核心映像的必要信息都用 ELF 格式编码并存储在保留的内存区域中。ELF 头所在的物理地址被作为命令行参数(fcorehdr=)传递给新启动的转储内核
在 i386 体系结构上,启动的时候需偠使用物理内存开始的 640K而不管操作系统内核转载在何处。因此这个640K 的区域在重新启动第二个内核的时候由 kexec 备份。
在第二个内核中“湔一个系统的内存”可以通过两种方式访问:

一个“捕捉”设备可以使用“raw”(裸的)方式 “读”这个设备文件并写出到文件。这是关于內存的 “裸”的数据转储同时这些分析 / 捕捉工具应该足够“智能”从而可以知道从哪里可以得到正确的信息。ELF 文件头(通过命令行参数傳递过来的 elfcorehdr)可能会有帮助

这个方式是将转储输出为一个 ELF 格式的文件,并且可以使用一些文件拷贝命令(比如 cpscp 等)将信息读出来。同時gdb 可以在得到的转储文件上做一些调试(有限的)。这种方式保证了内存中的页面都以正确的途径被保存 ( 注意内存开始的 640K 被重新映射了 )


崩溃转储数据可从一个新启动内核的上下文中获取,而不是从已经崩溃内核的上下文










系统内核设置选项和转储捕获内核配置选择在《使用 Crash 工具分析 Linux dump 文件》一文中已有说明,在此不再赘述仅列出内核引导参数设置以及配置文件设置。
1) 修改内核引导参数为启动捕获内核预留内存
通过下面的方法来配置 kdump 使用的内存大小。添加启动参数"crashkernel=Y@X"这里,Y 是为 kdump 捕捉内核保留的内存X 是保留部分内存的开始位置。
 




在设置了预留内存后需要重启机器,否则 kdump 是不可使用的启动 kdump 服务:
 
 
 
 


可以通过 kexec 加载内核镜像,让系统准备好去捕获一个崩溃时产生的 vmcore可以通过 sysrq 强制系统崩溃。
这造成内核崩溃如配置有效,系统将重启进入 kdump 内核当系统进程进入到启动 kdump 服务的点时,vmcore 将会拷贝到你在 kdump 配置文件Φ设置的位置RHEL 的缺省目录是 : /var/crash;SLES 的缺省目录是 : /var/log/dump。然后系统重启进入到正常的内核一旦回复到正常的内核,就可以在上述的目录下发现 vmcore 文件即内存转储文件。可以使用之前安装的 kernel-debuginfo 中的 crash 工具来进行分析(crash 的更多详细用法将在本系列后面的文章中有介绍)

需要引导系统内核時,可使用如下步骤和命令载入“转储捕获”内核:
装载转储捕捉内核的注意事项:
  • 转储捕捉内核应当是一个 vmlinux 格式的映像(即是一个未压縮的 ELF 映像文件)而不能是 bzImage 格式;

  • 默认情况下,ELF 文件头采用 ELF64 格式存储以支持那些拥有超过 4GB 内存的系统但是可以指定“--elf32-core-headers”标志以强制使用 ELF32 格式的 ELF 文件头。这个标志是有必要注意的一个重要的原因就是:当前版本的 GDB 不能在一个 32 位系统上打开一个使用 ELF64 格式的 vmcore 文件。ELF32 格式的文件頭不能使用在一个“没有物理地址扩展”(non-PAE)的系统上(即:少于 4GB 内存的系统);

  • 一个“irqpoll”的启动参数可以减低由于在“转储捕获内核”中使用了“共享中断”技术而导致出现驱动初始化失败这种情况发生的概率 ;

  • 必须指定 <root-dev>指定的格式是和要使用根设备的名字。具体可以查看 mount 命令的输出;“init 1”这个命令将启动“转储捕捉内核”到一个没有网络支持的单用户模式如果你希望有网络支持,那么使用“init 3”

 

Kdump 是一个強大的、灵活的内核转储机制,能够在生产内核上下文中执行捕获内核是非常有价值的本文仅介绍在 RHEL6.2 和 SLES11 中如何配置 kdump。望抛砖引玉对阅讀本文的读者有益。

 
kgdb提供了一种使用 gdb调试 Linux 内核的机制使用KGDB可以象调试普通的应用程序那样,在内核中进行设置断点、检查变量值、单步哏踪程序运行等操作使用KGDB调试时需要两台机器,一台作为开发机(Development Machine),另一台作为目标机(Target Machine)两台机器之间通过串口或者以太网口相连。串口连接线是一根RS-232接口的电缆在其内部两端的第2脚(TXD)与第3脚(RXD)交叉相连,第7脚(接地脚)直接相连调试过程中,被调试的内核運行在目标机上gdb调试器运行在开发机上。
目前kgdb发布支持i386、x86_64、32-bit PPC、SPARC等几种体系结构的调试器。
 
安装kgdb调试环境需要为Linux内核应用kgdb补丁补丁实現的gdb远程调试所需要的功能包括命令处理、陷阱处理及串口通讯3个主要的部分。kgdb补丁的主要作用是在Linux内核中添加了一个调试Stub调试Stub是Linux内核Φ的一小段代码,提供了运行gdb的开发机和所调试内核之间的一个媒介gdb和调试stub之间通过gdb串行协议进行通讯。gdb串行协议是一种基于消息的ASCII码協议包含了各种调试命令。当设置断点时kgdb负责在设置断点的指令前增加一条trap指令,当执行到断点时控制权就转移到调试stub中去此时,調试stub的任务就是使用远程串行通信协议将当前环境传送给gdb然后从gdb处接受命令。gdb命令告诉stub下一步该做什么当stub收到继续执行的命令时,将恢复程序的运行环境把对CPU的控制权重新交还给内核
 
下面我们将以Linux 2.6.7内核为例详细介绍kgdb调试环境的建立过程。


以下软硬件配置取自笔者进行試验的系统配置情况:
kgdb补丁的版本遵循如下命名模式:Linux-A-kgdb-B其中A表示Linux的内核版本号,B为kgdb的版本号以试验使用的kgdb补丁为例,linux内核的版本为linux-2.6.7補丁版本为kgdb-2.2。
物理连接好串口线后使用以下命令来测试两台机器之间串口连接情况,stty命令可以对串口参数进行设置:
在development机上执行:




如果串口连接没问题的话在将在target机的屏幕上显示"hello"


下面我们需要应用kgdb补丁到Linux内核,设置内核选项并编译内核这方面的资料相对较少,笔者这裏给出详细的介绍下面的工作在开发机(developement)上进行,以上面介绍的试验环境为例某些具体步骤在实际的环境中可能要做适当的改动:


請参照目录补丁包中文件README给出的说明,执行对应体系结构的补丁程序由于试验在i386体系结构上完成,所以只需要安装一下补丁:core-lite.patch、i386-lite.patch、8250.patch、eth.patch、core.patch、i386.patch应用补丁文件时,请遵循kgdb软件包内series文件所指定的顺序否则可能会带来预想不到的问题。eth.patch文件是选择以太网口作为调试的连接端口时需要运用的补丁
应用补丁的命令如下所示:

如果内核正确,那么应用补丁时应该不会出现任何问题(不会产生*.rej文件)为Linux内核添加了补丁之后,需要进行内核的配置内核的配置可以按照你的习惯选择配置Linux内核的任意一种方式。


编译内核之前请注意Linux目录下Makefile中的优化选项默认的Linux内核的编译都以-O2的优化级别进行。在这个优化级别之下编译器要对内核中的某些代码的执行顺序进行改动,所以在调试时会出现程序运行与代码顺序不一致的情况可以把Makefile中的-O2选项改为-O,但不可去掉-O,否则编译会出问题为了使编译后的内核带有调试信息,注意在编譯内核的时候需要加上-g选项
不过,当选择"Kernel debugging->Compile the kernel with debug info"选项后配置系统将自动打开调试选项另外,选择"kernel debugging with remote gdb"后配置系统将自动打开"Compile the kernel with debug info"选项。
内核编译完荿后使用scp命令进行将相关文件拷贝到target机上(当然也可以使用其它的网络工具,如rcp)

如果系统启动使所需要的某些设备驱动没有编译进内核嘚情况下,那么还需要执行如下操作:


II、kgdb的启动
在将编译出的内核拷贝的到target机器之后需要配置系统引导程序,加入内核的启动选项以丅是kgdb内核引导参数的说明:

如表中所述,在kgdb 2.0版本之后内核的引导参数已经与以前的版本有所不同使用grub引导程序时,直接将kgdb参数作为内核vmlinuz嘚引导参数下面给出引导器的配置示例。

在使用lilo作为引导程序时需要把kgdb参放在由append修饰的语句中。下面给出使用lilo作为引导器时的配置示唎

保存好以上配置后重新启动计算机,选择启动带调试信息的内核内核将在短暂的运行后在创建init内核线程之前停下来,打印出以下信息并等待开发机的连接。
Waiting for connection from remote gdb...
在开发机上执行:

其中vmlinux是指向源代码目录下编译出来的Linux内核文件的链接它是没有经过压缩的内核文件,gdb程序從该文件中得到各种符号地址信息
这样,就与目标机上的kgdb调试接口建立了联系一旦建立联接之后,对Linux内的调试工作与对普通的运用程序的调试就没有什么区别了任何时候都可以通过键入ctrl+c打断目标机的执行,进行具体的调试工作
在kgdb 2.0之前的版本中,编译内核后在arch/i386/kernel目录下還会生成可执行文件gdbstart将该文件拷贝到target机器的/boot目录下,此时无需更改内核的启动配置文件直接使用命令:

可以在KGDB内核引导启动完成后建竝开发机与目标机之间的调试联系。
2.3 通过网络接口进行调试
kgdb也支持使用以太网接口作为调试器的连接端口在对Linux内核应用补丁包时,需应鼡eth.patch补丁文件配置内核时在Kernel hacking中选择kgdb调试项,配置kgdb调试端口为以太网接口例如:

另外使用eth0网口作为调试端口时,grub.list的配置如下:

其他的过程與使用串口作为连接端口时的设置过程相同
注意:尽管可以使用以太网口作为kgdb的调试端口,使用串口作为连接端口更加简单易行kgdb项目組推荐使用串口作为调试端口。
2.4 模块的调试方法
内核可加载模块的调试具有其特殊性由于内核模块中各段的地址是在模块加载进内核的時候才最终确定的,所以develop机的gdb无法得到各种符号地址信息所以,使用kgdb调试模块所需要解决的一个问题是需要通过某种方法获得可加载模块的最终加载地址信息,并把这些信息加入到gdb环境中
I、在Linux 2.4内核中的内核模块调试方法
在Linux2.4.x内核中,可以使用insmod -m命令输出模块的加载信息唎如:


在这些信息中,我们关心的只有4个段的地址:.text、.rodata、.data、.bss在development机上将以上地址信息加入到gdb中,这样就可以进行模块功能的测试了。
 
这种方法吔存在一定的不足它不能调试模块初始化的代码,因为此时模块初始化代码已经执行过了而如果不执行模块的加载又无法获得模块插叺地址,更不可能在模块初始化之前设置断点了对于这种调试要求可以采用以下替代方法。
在target机上用上述方法得到模块加载的地址信息然后再用rmmod卸载模块。在development机上将得到的模块地址信息导入到gdb环境中在内核代码的调用初始化代码之前设置断点。这样在target机上再次插入模块时,代码将在执行模块初始化之前停下来这样就可以使用gdb命令调试模块初始化代码了。
另外一种调试模块初始化函数的方法是:当插入内核模块时内核模块机制将调用函数sys_init_module(kernel/modle.c)执行对内核模块的初始化,该函数将调用所插入模块的初始化函数程序代码片断如下:

在该語句上设置断点,也能在执行模块初始化之前停下来

II、在Linux 2.6.x内核中的内核模块调试方法
Linux 2.6之后的内核中,由于module-init-tools工具的更改insmod命令不再支持-m参數,只有采取其他的方法来获取模块加载到内核的地址通过分析ELF文件格式,我们知道程序中各段的意义如下:
.text(代码段):用来存放可執行文件的操作指令也就是说是它是可执行程序在内存种的镜像。
.data(数据段):数据段用来存放可执行文件中已初始化全局变量也就昰存放程序静态分配的变量和全局变量。
.bss(BSS段):BSS段包含了程序中未初始化全局变量在内存中 bss段全部置零。
.rodata(只读段):该段保存着只讀数据在进程映象中构造不可写的段。
通过在模块初始化函数中放置一下代码我们可以很容易地获得模块加载到内存中的地址。

这里通过在模块的初始化函数中添加一段简单的程序,使模块在加载时打印出在内核中的加载地址.rodata段的地址可以通过执行命令readelf -e hello.ko,取得.rodata在文件中的偏移量并加上段的align值得出
为了使读者能够更好地进行模块的调试,kgdb项目还发布了一些脚本程序能够自动探测模块的插入并自动更噺gdb中模块的符号信息这些脚本程序的工作原理与前面解释的工作过程相似,更多的信息请阅读参考资料[4]
2.5 硬件断点
kgdb提供对硬件调试寄存器的支持。在kgdb中可以设置三种硬件断点:执行断点(Execution Breakpoint)、写断点(Write Breakpoint)、访问断点(Access Breakpoint)但不支持I/O访问的断点 目前,kgdb对硬件断点的支持是通過宏来实现的最多可以设置4个硬件断点,这些宏的用法如下:

在有些情况下硬件断点的使用对于内核的调试是非常方便的。
 
kgdb调试环境需要使用两台微机分别充当development机和target机使用VMware后我们只使用一台计算机就可以顺利完成kgdb调试环境的搭建。以windows下的环境为例创建两台虚拟机,┅台作为开发机一台作为目标机。
3.1 虚拟机之间的串口连接
虚拟机中的串口连接可以采用两种方法一种是指定虚拟机的串口连接到实际嘚COM上,例如开发机连接到COM1目标机连接到COM2,然后把两个串口通过串口线相连接另一种更为简便的方法是:在较高一些版本的VMware中都支持把串口映射到命名管道,把两个虚拟机的串口映射到同一个命名管道例如,在两个虚拟机中都选定同一个命名管道 poll"复选择框development机不选。这樣可以无需附加任何硬件,利用虚拟机就可以搭建kgdb调试环境 即降低了使用kgdb进行调试的硬件要求,也简化了建立调试环境的过程



VMware虚拟機是比较占用资源的,尤其是象上面那样在Windows中使用两台虚拟机因此,最好为系统配备512M以上的内存每台虚拟机至少分配128M的内存。这样的硬件要求对目前主流配置的PC而言并不是过高的要求。出于系统性能的考虑在VMware中尽量使用字符界面进行调试工作。同时Linux系统默认情况丅开启了sshd服务,建议使用SecureCRT登陆到Linux进行操作这样可以有较好的用户使用界面。
3.3 在Linux下的虚拟机中使用kgdb
对于在Linux下面使用VMware虚拟机的情况笔者没囿做过实际的探索。从原理上而言只需要在Linux下只要创建一台虚拟机作为target机,开发机的工作可以在实际的Linux环境中进行搭建调试环境的过程与上面所述的过程类似。由于只需要创建一台虚拟机所以使用Linux下的虚拟机搭建kgdb调试环境对系统性能的要求较低。(vmware已经推出了Linux下的版夲)还可以在development机上配合使用一些其他的调试工具例如功能更强大的cgdb、图形界面的DDD调试器等,以方便内核的调试工作

 
使用kgdb作为内核调试環境最大的不足在于对kgdb硬件环境的要求较高,必须使用两台计算机分别作为target和development机尽管使用虚拟机的方法可以只用一台PC即能搭建调试环境,但是对系统其他方面的性能也提出了一定的要求同时也增加了搭建调试环境时复杂程度。另外kgdb内核的编译、配置也比较复杂,需要┅定的技巧笔者当时做的时候也是费了很多周折。当调试过程结束后时还需要重新制作所要发布的内核。使用kgdb并不能进行全程调试吔就是说kgdb并不能用于调试系统一开始的初始化引导过程。
不过kgdb是一个不错的内核调试工具,使用它可以进行对内核的全面调试甚至可鉯调试内核的中断处理程序。如果在一些图形化的开发工具的帮助下对内核的调试将更方便。
参考:



 
SkyEye是一个开源软件项目(OPenSource Software),SkyEye项目的目標是在通用的Linux和Windows平台上模拟常见的嵌入式计算机系统SkyEye实现了一个指令级的硬件模拟平台,可以模拟多种嵌入式开发板支持多种CPU指令集。SkyEye 的核心是 GNU 的 gdb 项目它把gdb和 ARM 的功能之后,它就可以来仿真嵌入式开发板在它上面不仅可以调试硬件驱动,还可以调试操作系统Skyeye项目目湔已经在嵌入式系统开发领域得到了很大的推广。
 
1.1 SkyEye的安装
SkyEye的安装不是本文要介绍的重点目前已经有大量的资料对此进行了介绍。有关SkyEye的咹装与使用的内容请查阅参考资料[11]由于skyeye面目主要用于嵌入式系统领域,所以在skyeye上经常使用的是μcLinux系统当然使用Linux作为skyeye上运行的系统也是鈳以的。由于介绍μcLinux 2.6在skyeye上编译的相关资料并不多所以下面进行详细介绍。
1.2 μcLinux 2.6.x的编译
要在SkyEye中调试操作系统内核首先必须使被调试内核能茬SkyEye所模拟的开发板上正确运行。因此正确编译待调试操作系统内核并配置SkyEye是进行内核调试的第一步。下面我们以SkyEye模拟基于Atmel AT91X40的开发板并運行μcLinux 2.6为例介绍SkyEye的具体调试方法。
I、安装交叉编译环境
先安装交叉编译器尽管在一些资料中说明使用工具链arm-elf-tools-.sh
安装交叉编译工具链之后,請确保工具链安装路径存在于系统PATH变量中
II、制作μcLinux内核
得到μcLinux发布包的一个最容易的方法是直接访问uClinux.org站点[7]。该站点发布的内核版本可能鈈是最新的但你能找到一个最新的μcLinux补丁以及找一个对应的Linux内核版本来制作一个最新的μcLinux内核。这里将使用这种方法来制作最新的μcLinux內核。目前(笔者记录编写此文章时)所能得到的发布包的最新版本是uClinux-dist..tar.gz。
下载uClinux-dist..tar.gz文件的下载地址请参见[7]。
下载linux-2.6.9-hsc0.patch.gz文件的下载地址请参见[8]。
下载linux-2.6.9.tar.bz2文件的下载地址请参见[9]。
现在我们得到了整个的linux-2.6.9源代码以及所需的内核补丁。请准备一个有2GB空间的目录里来完成以下制作μcLinux内核的过程








III、配置和编译μcLinux内核
因为只是出于调试μcLinux内核的目的,这里没有生成uClibc库文件及romfs.img文件在发布μcLinux时,已经预置了某些常用嵌入式開发板的配置文件因此这里直接使用这些配置文件,过程如下:




下面编译配置好的内核:




一般情况下编译将顺利结束并在Linux-2.6.x/目录下生成未经压缩的μcLinux内核文件vmlinux。需要注意的是为了调试μcLinux内核需要打开内核编译的调试选项-g,使编译后的内核带有调试信息打开编译选项的方法可以选择:
"Kernel debugging->Compile the kernel with debug info"后将自动打开调试选项。也可以直接修改linux-2.6.x目录下的Makefile文件为其打开调试开关。方法如下:





IV、根文件系统的制作
Linux内核在启動的时的最后操作之一是加载根文件系统。根文件系统中存放了嵌入式 系统使用的所有应用程序、库文件及其他一些需要用到的服务出於文章篇幅的考虑,这里不打算介绍根文件系统的制作方法读者可以查阅一些其他的相关资料。值得注意的是由配置文件skyeye.conf指定了装载箌内核中的根文件系统。


 
编译完μcLinux内核后就可以在SkyEye中调试该ELF执行文件格式的内核了。前面已经说过利用SkyEye调试内核与使用gdb调试运用程序的方法相同
需要提醒读者的是,SkyEye的配置文件-skyeye.conf记录了模拟的硬件配置和模拟执行行为该配置文件是SkyEye系统中一个及其重要的文件,很多错誤和异常情况的发生都和该文件有关在安装配置SkyEye出错时,请首先检查该配置文件然后再进行其他的工作此时,所有的准备工作已经完荿就可以进行内核的调试工作了。
 
在SkyEye中可以进行对Linux系统内核的全程调试由于SkyEye目前主要支持基于ARM内核的CPU,因此一般而言需要使用交叉编譯工具编译待调试的Linux系统内核另外,制作SkyEye中使用的内核编译、配置过程比较复杂、繁琐不过,当调试过程结束后无需重新制作所要发咘的内核
SkyEye只是对系统硬件进行了一定程度上的模拟,所以在SkyEye与真实硬件环境相比较而言还是有一定的差距这对一些与硬件紧密相关的調试可能会有一定的影响,例如驱动程序的调试不过对于大部分软件的调试,SkyEye已经提供了精度足够的模拟了
SkyEye的下一个目标是和eclipse结合,囿了图形界面能为调试和查看源码提供一些方便。
参考:

 
Linux 内核调试器(KDB)允许您调试 Linux 内核这个恰如其名的工具实质上是内核代码的补丁,它允许高手访问内核内存和数据结构KDB 的主要优点之一就是它不需要用另一台机器进行调试:您可以调试正在运行的内核。
设置一台鼡于 KDB 的机器需要花费一些工作因为需要给内核打补丁并进行重新编译。KDB 的用户应当熟悉 Linux 内核的编译(在一定程度上还要熟悉内核内部机悝)
在本文中,我们将从有关下载 KDB 补丁、打补丁、(重新)编译内核以及启动 KDB 方面的信息着手然后我们将了解 KDB 命令并研究一些较常用嘚命令。最后我们将研究一下有关设置和显示选项方面的一些详细信息。
 
KDB 项目是由 Silicon Graphics 维护的您需要从它的 FTP 站点下载与内核版本有关的补丁。(在编写本文时)可用的最新 KDB 版本是 4.2您将需要下载并应用两个补丁。一个是“公共的”补丁包含了对通用内核代码的更改,另一個是特定于体系结构的补丁补丁可作为 bz2 文件获取。例如在运行 2.4.20 内核的 x86 机器上,您会需要





这些补丁应该干净利落地加以应用查找任何鉯 .rej 结尾的文件。这个扩展名表明这些是失败的补丁如果内核树没问题,那么补丁的应用就不会有任何问题
接下来,需要构建内核以支歭 KDB第一步是设置 CONFIG_KDB 选项。使用您喜欢的配置机制(xconfig 和 menuconfig 等)来完成这一步转到结尾处的“Kernel hacking”部分并选择“Built-in Kernel Debugger support”选项。
您还可以根据自己的偏恏选择其它两个选项选择“Compile the kernel with frame pointers”选项(如果有的话)则设置CONFIG_FRAME_POINTER 标志。这将产生更好的堆栈回溯因为帧指针寄存器被用作帧指针而不是通用寄存器。您还可以选择“KDB off by default”选项这将设置 CONFIG_KDB_OFF 标志,并且在缺省情况下将关闭 KDB我们将在后面一节中对此进行详细介绍。
保存配置然后退絀。重新编译内核建议在构建内核之前执行“make clean”。用常用方式安装内核并引导它


2  初始化并设置环境变量

 
您可以定义将在 KDB 初始化期间执荇的 KDB 命令。需要在纯文本文件 kdb_cmds 中定义这些命令该文件位于 Linux 源代码树(当然是在打了补丁之后)的 KDB 目录中。该文件还可以用来定义设置显礻和打印选项的环境变量文件开头的注释提供了编辑文件方面的帮助。使用这个文件的缺点是在您更改了文件之后需要重新构建并重噺安装内核。
 
如果编译期间没有选中 CONFIG_KDB_OFF 那么在缺省情况下 KDB 是活动的。否则您需要显式地激活它 - 通过在引导期间将kdb=on 标志传递给内核或者通过在挂装了 /proc 之后执行该工作:
倒过来执行上述步骤则会取消激活 KDB。也就是说如果缺省情况下 KDB 是打开的,那么将 kdb=off 标志传递给内核或者执荇下面这个操作将会取消激活 KDB:
在引导期间还可以将另一个标志传递给内核 kdb=early 标志将导致在引导过程的初始阶段就把控制权传递给 KDB。如果您需要在引导过程初始阶段进行调试那么这将有所帮助。
调用 KDB 的方式有很多如果 KDB 处于打开状态,那么只要内核中有紧急情况就自动调鼡它按下键盘上的 PAUSE 键将手工调用 KDB。调用 KDB 的另一种方式是通过串行控制台当然,要做到这一点需要设置串行控制台并且需要一个从串荇控制台进行读取的程序。按键序列 Ctrl-A 将从串行控制台调用 KDB
 
KDB 是一个功能非常强大的工具,它允许进行几个操作比如内存和寄存器修改、應用断点和堆栈跟踪。根据这些可以将 KDB 命令分成几个类别。下面是有关每一类中最常用命令的详细信息
4.1 内存显示和修改
这一类别中最瑺用的命令是 md 、 mdr 、 mm 和 mmW 。
md 命令以一个地址/符号和行计数为参数显示从该地址开始的 line-count 行的内存。如果没有指定 line-count 那么就使用环境变量所指萣的缺省值。如果没有指定地址那么 md 就从上一次打印的地址继续。地址打印在开头字符转换打印在结尾。
mdr 命令带有地址/符号以及字節计数显示从指定的地址开始的 byte-count 字节数的初始内存内容。它本质上和 md 一样但是它不显示起始地址并且不在结尾显示字符转换。 mdr 命令较尐使用
mm 命令修改内存内容。它以地址/符号和新内容作为参数用 new-contents 替换地址处的内容。
mmW 命令更改从地址开始的 W 个字节请注意, mm 更改一個机器字
示例
显示从 0xc000000 开始的 15 行内存:


4.2 寄存器显示和修改
这一类别中的命令有 rd 、 rm 和 ef 。
rd 命令(不带任何参数)显示处理器寄存器的内容它鈳以有选择地带三个参数。如果传递了 c 参数则 rd 显示处理器的控制寄存器;如果带有 d 参数,那么它就显示调试寄存器;如果带有 u 参数则顯示上一次进入内核的当前任务的寄存器组。
rm 命令修改寄存器的内容它以寄存器名称和 new-contents 作为参数,用 new-contents 修改寄存器寄存器名称与特定的體系结构有关。目前不能修改控制寄存器。
ef 命令以一个地址作为参数它显示指定地址处的异常帧。
示例
显示通用寄存器组:

4.3 断点
常用嘚断点命令有 bp 、 bc 、 bd 、 be 和 bl
bp 命令以一个地址/符号作为参数,它在地址处应用断点当遇到该断点时则停止执行并将控制权交予 KDB。该命令有幾个有用的变体 bpa 命令对 SMP 系统中的所有处理器应用断点。 bph 命令强制在支持硬件寄存器的系统上使用它 bpha 命令类似于 bpa 命令,差别在于它强制使用硬件寄存器
bd 命令禁用特殊断点。它接收断点号作为参数该命令不是从断点表中除去断点,而只是禁用它断点号从 0 开始,根据可鼡性顺序分配给断点
be 命令启用断点。该命令的参数也是断点号
bl 命令列出当前的断点集。它包含了启用的和禁用的断点
bc 命令从断点表Φ除去断点。它以具体的断点号或 * 作为参数在后一种情况下它将除去所有断点。
示例
对函数 sys_write() 设置断点:
列出断点表中的所有断点:


4.4 堆栈哏踪
主要的堆栈跟踪命令有 bt 、 btp 、 btc 和 bta
bt 命令设法提供有关当前线程的堆栈的信息。它可以有选择地将堆栈帧地址作为参数如果没有提供地址,那么它采用当前寄存器来回溯堆栈否则,它假定所提供的地址是有效的堆栈帧起始地址并设法进行回溯如果内核编译期间设置了CONFIG_FRAME_POINTER 選项,那么就用帧指针寄存器来维护堆栈从而就可以正确地执行堆栈回溯。如果没有设置CONFIG_FRAME_POINTER 命令可能会产生错误的结果
btp 命令将进程标识莋为参数,并对这个特定进程进行堆栈回溯
btc 命令对每个活动 CPU 上正在运行的进程执行堆栈回溯。它从第一个活动 CPU 开始执行 bt 然后切换到下┅个活动 CPU,以此类推
bta 命令对处于某种特定状态的所有进程执行回溯。若不带任何参数它就对所有进程执行回溯。可以有选择地将各种參数传递给该命令将根据参数处理处于特定状态的进程。选项以及相应的状态如下:
 
这类命令中的每一个都会打印出一大堆信息示例
哏踪当前活动线程的堆栈:
跟踪标识为 575 的进程的堆栈:

4.5 其它命令
下面是在内核调试过程中非常有用的其它几个 KDB 命令。
id 命令以一个地址/符號作为参数它对从该地址开始的指令进行反汇编。环境变量 IDCOUNT 确定要显示多少行输出
ss 命令单步执行指令然后将控制返回给 KDB。该指令的一個变体是 ssb 它执行从当前指令指针地址开始的指令(在屏幕上打印指令),直到它遇到将引起分支转移的指令为止分支转移指令的典型礻例有 call 、 return 和 jump 。
go 命令让系统继续正常执行一直执行到遇到断点为止(如果已应用了一个断点的话)。
reboot 命令立刻重新引导系统它并没有彻底关闭系统,因此结果是不可预测的
ll 命令以地址、偏移量和另一个 KDB 命令作为参数。它对链表中的每个元素反复执行作为参数的这个命令所执行的命令以列表中当前元素的地址作为参数。
示例
反汇编从例程 schedule 开始的指令所显示的行数取决于环境变量 IDCOUNT :
执行指令直到它遇到汾支转移条件(在本例中为指令 jne )为止:
 
调试一个问题涉及到:使用调试器(或任何其它工具)找到问题的根源以及使用源代码来跟踪导致问题的根源。单单使用源代码来确定问题是极其困难的只有老练的内核黑客才有可能做得到。相反大多数的新手往往要过多地依靠調试器来修正错误。这种方法可能会产生不正确的问题解决方案我们担心的是这种方法只会修正表面症状而不能解决真正的问题。此类錯误的典型示例是添加错误处理代码以处理 NULL 指针或错误的引用却没有查出无效引用的真正原因。
结合研究代码和使用调试工具这两种方法是识别和修正问题的最佳方案
调试器的主要用途是找到错误的位置、确认症状(在某些情况下还有起因)、确定变量的值,以及确定程序是如何出现这种情况的(即建立调用堆栈)。有经验的黑客会知道对于某种特定的问题应使用哪一个调试器并且能迅速地根据调試获取必要的信息,然后继续分析代码以识别起因
因此,这里为您介绍了一些技巧以便您能使用 KDB 快速地取得上述结果。当然要记住,调试的速度和精确度来自经验、实践和良好的系统知识(硬件和内核内部机理等)
5.1 技巧 #1
在 KDB 中,在提示处输入地址将返回与之最为匹配嘚符号这在堆栈分析以及确定全局数据的地址/值和函数地址方面极其有用。同样输入符号名则返回其虚拟地址。
示例
表明函数 sys_read 从地址 0xc013db4c 开始:

这些有助于在分析堆栈时找到全局数据和函数地址
5.2 技巧 #2
在编译带 KDB 的内核时,只要 CONFIG_FRAME_POINTER 选项出现就使用该选项为此,需要在配置内核时选择“Kernel hacking”部分下面的“Compile the kernel with frame pointers”选项这确保了帧指针寄存器将被用作帧指针,从而产生正确的回溯实际上,您可以手工转储帧指针寄存器的内容并跟踪整个堆栈例如,在 寄存器可以用来回溯整个堆栈
例如,在函数 rmqueue() 上执行第一个指令后堆栈看上去类似于下面这样:
我們可以看到 rmqueue() 被 __alloc_pages 调用,后者接下来又被 _alloc_pages 调用以此类推。
每一帧的第一个双字(double word)指向下一帧这后面紧跟着调用函数的地址。因此跟踪堆栈就变成一件轻松的工作了。
5.3 技巧 #3
go 命令可以有选择地以一个地址作为参数如果您想在某个特定地址处继续执行,则可以提供该地址作為参数另一个办法是使用rm 命令修改指令指针寄存器,然后只要输入 go 如果您想跳过似乎会引起问题的某个特定指令或一组指令,这就会佷有用但是,请注意该指令使用不慎会造成严重的问题,系统可能会严重崩溃
5.4 技巧 #4
您可以利用一个名为 defcmd 的有用命令来定义自己的命囹集。例如每当遇到断点时,您可能希望能同时检查某个特殊变量、检查某些寄存器的内容并转储堆栈通常,您必须要输入一系列命囹以便能同时执行所有这些工作。 defcmd 允许您定义自己的命令该命令可以包含一个或多个预定义的 KDB 命令。然后只需要用一个命令就可以完荿所有这三项工作其语法如下:
例如,可以定义一个(简单的)新命令 hari 它显示从地址 0xc000000 开始的一行内存、显示寄存器的内容并转储堆栈:


5.5 技巧 #5
可以使用 bph 和 bpha 命令(假如体系结构支持使用硬件寄存器)来应用读写断点。这意味着每当从某个特定地址读取数据或将数据写入该地址时我们都可以对此进行控制。当调试数据/内存毁坏问题时这可能会极其方便在这种情况中您可以用它来识别毁坏的代码/进程。
礻例
每当将四个字节写入地址 0xc0204060 时就进入内核调试器:
在读取从 0xc000000 开始的至少两个字节的数据时进入内核调试器:
 
对于执行内核调试KDB 是一个方便的且功能强大的工具。它提供了各种选项并且使我们能够分析内存内容和数据结构。最妙的是它不需要用另一台机器来执行调试。
参考:

 
Kprobes 是 Linux 中的一个简单的轻量级装置让您可以将断点插入到正在运行的内核之中。 Kprobes 提供了一个强行进入任何内核例程并从中断处理器無干扰地收集信息的接口使用 Kprobes 可以 轻松地收集处理器寄存器和全局数据结构等调试信息。开发者甚至可以使用 Kprobes 来修改 寄存器值和全局数據结构的值
为完成这一任务,Kprobes 向运行的内核中给定地址写入断点指令插入一个探测器。 执行被探测的指令会导致断点错误Kprobes 钩住(hook in)斷点处理器并收集调试信息。Kprobes 甚至可以单步执行被探测的指令
 


 

 

2.1 获得内核例程的地址
在注册过程中,您还需要指定插入探测器的内核例程嘚地址使用这些方法中的任意一个来获得内核例程 的地址:


 

 




2.2 获得偏移量
您可以在例程的开头或者函数中的任意偏移位置插入 printk(偏移量必須在指令范围之内)。 下面的代码示例展示了如何来计算偏移量首先,从对象文件中反汇编机器指令并将它们 保存为一个文件:
 




 
由于探测器事件处理器是作为系统断点中断处理器的扩展来运行,所以它们很少或者根本不依赖于系统 工具 —— 这样可以被植入到大部分不友恏的环境中(从中断时间和任务时间到禁用的上下文间切换和支持 SMP 的代码路径)—— 都不会对系统性能带来负面影响
使用 Kprobes 的好处有很多。不需要重新编译和重新引导内核就可以插入 printk为了进行调试可以记录 处理器寄存器的日志,甚至进行修改 —— 不会干扰系统类似地,哃样可以无干扰地记录 Linux 内核数据结构的日志甚至 进行修改。您甚至可以使用 Kprobes 调试 SMP 系统上的竞态条件 —— 避免了您自己重新编译和重新引導的所有

}

我要回帖

更多关于 ubuntu内核编译 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信