磨刀不误砍柴工——实验工具准备

        写在最前面的话:

   后面的一系列博客主要围绕MIT-OS的学习课程来创建;

   为什么会学习mit-os?
   1)对于复杂的事物,一种常见的思维就是从简单到复杂的思维。根据这种思维去学习原始的os,透过它去熟悉os的基本概念与核心构造,从而以此作为基础去学习现代os的各项技术,能够去更新创造与优化它们。
   2)一种典型的西方逻辑思维——原子思想,所有系统都是可以分解为原子结构,然后有序的组合;根据这种思维,很容易将os分割成若干流程或者若干结构的有序组合;而且在学习过程中,发现这种课程是很好的学习模板,它通过代码的方式去引导学习去实现相关的流程与结构,这样更有利于掌握与学习学习内容。
   3)从一个更完整的角度去学习os,因为os是最贴近硬件,所以需要接触硬件的相关知识,特别是处理器的软件参考手册,从一个狭窄的角度会发现,os是处理器的软件驱动;对于os,个人认为一定需要自己尝试去实现相关部分流程或者模块,同时去调试os,才能更与os贴近,从而得到更深刻的理解。

   具体课程可以到如下网址参考:http://pdos.csail.mit.edu/6.828/2012/schedule.html

      俗话说得好磨刀不误砍柴工,有了好刀才能势如破竹的砍柴;也只有当把工具与环境有更好,更深的认识,才能将系统的知识有效的模拟与调试,才能将书本上的知识带入到实际工程中来;以工程的态度去对待所学到的知识,将理论与实践相结合,从而对概念有更深入的理解。

     从某种角度来说,对于代码或者软件来说,更多的是经验的累计与总结;而且它很实在,它可以实现自己的思想,同时也能够被直观的看到与仿真,说白了,就是任何代码或者最好是能够运行它,然后调试它,从实际运行过程中去体会它的意义,从而能够更好的理解,然后被直接使用。目前最常用的代码分析方法,就是看源代码,这样静态的去分析与理解,这种方式很直观而且有效,但是它只是停留在逻辑层面,忽略了实实在在运行环境,这样很容易只是理解思想,很难对学习者有更好的启发与提升;所以我的建议是,任何代码都请去调试它,或者自己去实现它,在模拟环境中调试之。

        对于操作系统的调试,目前有现成的解决方案就是用虚拟机去运行之,透过虚拟机平台去调试它;所以对于MIT—OS的实验课程所需要的工具与环境就是虚拟机。当然,以此作为平台,我们也可以去调试linux的内核。还有需要了解的一种工具就是链接源代码与系统的工具——编译器。所以我们会从如下2个方面去准备我们的工具与实验环境,1个实验环节来展示它们的力量。

       前面两个方面可以参考如下的部分:http://pdos.csail.mit.edu/6.828/2012/tools.html

一)虚拟机——qemu

        为什么要选择它呢?开源,而且功能强大——不仅能完全模拟pc环境,效率高,更重要的是它能与gdb调试工具直接进行交互,进行系统调试。其他更多的了解可以到官网http://wiki.qemu.org/Main_Page。当然它有很多的用处,但对于目前我们来说,更多是熟悉它的使用,能够运行我们的os,然后去调试之。

       1.下载源码:从官网http://wiki.qemu.org/Download,解压缩它。

       2.编译它,为了防止仿真的系统直接将我们的终端给控制了,所以我建议编译一个带简单窗口的版本,这样更方便我们调试系统——所以我用的是SDL的图形界面的——因为它足够简单。

      a)解决依赖关系如下:

     qemu -->zlib

              -->glib-->libffi

             -->sdl

      最新的qemu依赖于zlibglibsdl,同时glib依赖libffi。所以为了成功编译qemu,需要提前编译安装如上4个包,编译方法就是linux的源码安装3步曲——configuremakemake install

      b)配置编译参数:

 ./configure --target-list=i386-softmmu,x86_64-softmmu --enable-sdl --with-sdlabi=2.0 --enable-debug –-prefix=$YROOT

      因为我们主要模拟x86环境中的实现,所以我们只需要支持的虚拟机器构架为 --target-list=i386-softmmu,x86_64-softmmu,当然如果需要支持其他构架的实现,也可以添加其他的;支持sdl图形窗口:--enable-sdl --with-sdlabi=2.0 ;将qemu建议安装到我们的实验环境——由环境变量决定YROOT

     c)编译与安装:

     make && make install

     d)生成使用文档——html版本:qemu-doc.html

     make html

     因为生成时,有依赖关系:makeinfo-->textinfo.

     3.常用的使用命令,汇总如下:如需要更详细的参考可以用qemu-doc.html或者从官网获取http://wiki.qemu.org/Manual。如下的描述很多从qemu-doc.html摘取,而且以i386平台的工具使用,同时我们也主要使用它。

     a)基本使用方式:

      qemu-system-i386 [options] [disk_image]

      options为控制参数。

      disk_image为仿真使用的磁盘镜像,就像我们电脑使用的硬盘。

     b)处理器与内存的参数:

     -smp X :处理器核数,比如双核:-smp 2

     -m Y:内存大小,比如1G:-m 1G

     c)硬盘添加:

     -hdX(a-d) file:添加磁盘镜像文件,比如:-hdb basic.img

     d)不是用图形窗口:

      -nographic:默认qemu是会使用图形窗口,如果不是用则禁用之,它会在调试linux用到,qemu保存log到文件的功能没有用。

     e)保存debug系统信息到文件:

     -d xxx -D file:调试系统的xxx信息到文件file,比如:查看系统的反汇编——qemu-system-i386 -d in_asm -D test-d

当然可以查看系统的其他信息: qemu-system-i386 -d help

     f)添加串口:

     -serial xxx:给系统添加调试串口,这在系统调试中很重要,比如:添加当前终端为串口——-serial stdio。当然也可以查询其他的串口:qemu-system-i386 -serial [TAB][TAB]

     g)停止处理器:-S 

     h)设置gdb调试:-gdb tcp::port;使用tcp端口进行调试。这实际上就模拟了嵌入式linux系统的gdb远程调试。详细步骤可以参考《qemu-doc.html3.12 GDB usage》:当然可以参考之前我的博客关于gdb的使用。快捷gdb调试设置:-s(等价于-gdb tcp::1234)。

     i)监视器的使用:

  -monitor xxx;使用qemu监视器,通过它可以发现系统的状态,同时动态配置系统,极大的方便了系统调试。但是我们没有用。

     j)直接启动linux

qemu-system-i386 -kernel arch/i386/boot/bzImage -hda root-2.4.20.img -append "root=/dev/hda"

     -kernel直接加载内核,-append为添加给内核的参数。

二)交叉编译工具

      为什么需要交叉编译工具呢?目前我们有最多的参考资料,最成熟的技术,是建立在x8632位系统中的,但是目前我们电脑已经到了64位,所以我们需要在64位系统中编译出32位运行系统,这样尽可能方便调试系统。而对于64位系统或者其他处理器——比如:ARM,可以看作是系统的扩展与移植,当我们熟悉x8632位系统中,就可以将某些概念与技术进行推广,然后通过差异化去理解相关的概念。

       网上有很多交叉编译工具的编译方法,都是我们的参考,但是能不能成功,这就需要自己动手实验了。

       如下所描述的内容都是参考,只有自己动手去尝试才能保证其正确性,因为每个人的实验环境不一样。

所以如下为老生常谈的内容,同时交叉编译工具的制作是很麻烦的,而且需要很多配置的信息,大都需要详细参考gcc官网的相关编译注释,从而我们为了简化这个流程,同时保证制作的稳定性,将相关的编译部分制作成脚本,来自动生成相关工具。在我的博客对应的资源中有脚本的实现,其中有较详细的注释,保证能够编译成功。

   参考文档:

   官方文档:https://gcc.gnu.org/install/

   编译测试结果:https://gcc.gnu.org/ml/gcc-testresults/2015-05/msg00687.html

   另外一个有效的参考文档为lfs(Linux From Scratch)相关编译步骤,其中也包含了交叉编译工具的制作——只不过它是在模拟root的方式下实现的:

从如下网址,下载最新的参考文档:http://www.linuxfromscratch.org/lfs/download.html

编译步骤如下:

    1.下载源码:

gcc-->glibc   -->glibc-ports,mpfr,mpc,gmp,islkernel头文件

      -->binutils

     因为编译器gcc依赖glibcbinutils,而glibc又依赖于glibc-ports,mpfr,mpc,gmp,islkernel头文件。

所以需要到各自的官网上下载最新的源代码即可,然后放到目录tools-src路径下。

   2.设置与创建编译,安装环境:

   ctools_instl_path=`pwd`/tools----工具安装路径

   #编译根目录

    compile_top=`pwd`/build------编译的路径

    ctarget=i486-yiye-linux------目标主机的构架,格式如下:处理器构架-制造商-运行os

    glibc_instl_path=$ctools_instl_path/$ctarget--glibc安装路径

    sysroot_path=$glibc_instl_path----系统的模拟根路径

    根据如上配置我们编译出来的交叉工具为:i486-yiye-linux-gcc,然后根据如上目录创建对应的目录。不能使用i386来标记处理器构架,因为glibc已经认为i386构架过时了,至少需要i486构架,但是不影响我们描述与使用。

     3.以如下的步骤编译所有源码:

     install_kernel_headers-------编译与安装内核头文件

     install_binutils--------编译与安装binutils工具

     install_gcc_step1------第一遍编译与安装gcc

     install_glibc---------编译与安装glibc

     install_gcc_step2-------第二遍编译与安装gcc

     如上所述,gcc被编译了两次,第一次编译是为了编译glibc,第二次编译是为了gcc能够引用我们编译出来的gcc;详细的安装步骤与解释请参考我的脚步,主要是很多细节需要去设置,当然有时可能需要去查看相关源代码,根据官方文档去猜测其真实用途。唯一一个需要多次调整gcc的使用相关库的路径。

     4.脚本的使用:

./auto_compile.sh tools-src

     然后就会生成交叉编译工具了,如下是我编译的5.1.0的 i486-yiye-linux-gcc的相关路径。

tools/

├── qemu

│   ├── glib-2.44.0

│   ├── qemu-2.3.0

│   ├── texinfo-5.2

│   └── zlib-1.2.8

├── sdl

│   └── SDL2-2.0.3

├── sources

│   ├── binutils-2.25.tar.gz

│   ├── gcc-5.1.0.tar.bz2

│   ├── glibc-2.21.tar.xz

│   ├── glibc-ports-2.16.0.tar.xz

│   ├── gmp-6.0.0a.tar.xz

│   ├── isl-0.14.tar.bz2

│   ├── linux-4.0.2.tar.xz

│   ├── mpc-1.0.3.tar.gz

│   └── mpfr-3.1.2.tar.xz

├── test

│   ├── a.out

│   ├── binutils-2.25

│   └── main.c

└── tools

    ├── bin

    ├── i486-yiye-linux

    ├── include

    ├── lib

    ├── lib64

    ├── libexec

    └── share

     编译出来的版本测试如下:

[yiye@yiye pc_prj]$ i486-yiye-linux-gcc -v

Using built-in specs.

COLLECT_GCC=i486-yiye-linux-gcc

COLLECT_LTO_WRAPPER=/home/yiye/study/pc_prj/tools/tools/libexec/gcc/i486-yiye-linux/5.1.0/lto-wrapper

Target: i486-yiye-linux

Configured with: ../gcc-5.1.0/configure --prefix=/home/yiye/study/pc_prj/tools/tools --target=i486-yiye-linux --with-sysroot=/home/yiye/study/pc_prj/tools/tools/i486-yiye-linux --with-build-sysroot=/home/yiye/study/pc_prj/tools/tools/i486-yiye-linux --with-local-prefix=/home/yiye/study/pc_prj/tools/tools/i486-yiye-linux --with-native-system-header-dir=/include --disable-nls --enable-shared --disable-static --enable-languages=c,c++ --enable-__cxa_atexit --enable-c99 --enable-long-long --enable-threads=posix --disable-multilib --with-system-zlib --enable-checking=release --enable-libstdcxx-time

Thread model: posix

gcc version 5.1.0 (GCC) 


三)单步调试linux内核

      为什么要调试linux内核?一个很直接的原因就是梳理内核的启动流程与调试内核的关键流程,能够对内核进行动态分析,不仅仅停留在静态分析上,更深入去理解内核。推荐一个查看内核源代码的工具——LXRhttp://lxr.oss.org.cn/ident

      我们以如下步骤进行调试:

      1.下载内核:最新内核www.kernel.org

      2.配置内核:

make CROSS_COMPILE=i486-yiye-linux- ARCH=i386 menuconfig

      3.编译内核:

make -j8 && make install

     4.制作根文件系统镜像,因为内核启动过程中,需要一个根文件系统,同时也是为了制作基于linux的最小系统进行调试。制作根文件系统可以参考《Embedded Linux Primer: A Practical, Real-World ApproachChapter 6. System Initialization。详情见博客资源。因为制作根文件系统是一个比较麻烦的过程,所以我也创建了一个脚本自动生成硬盘镜像;详情见附件:create-simple-root.sh.一定要以root方式来运行。

     在linux环境下制作根文件镜像,需要如下工具:

      a)创建镜像文件

dd if=/dev/zero of=disk.img bs=1m count=512

      b)将镜像与设备联系起来

loseup -f disk.img

loseup /dev/loop0 disk.img

      c)对设备文件进行分区

fdisk /dev/loop0

      d)读取镜像文件的分区信息

kpartx -a /dev/loop0

      f)对分区格式化

mkfs /dev/mapper/loop0p1

      g)安装grub

grub-install --target=XXX --boot-directoty=YYYY /dev/loop0

     h)挂载设备loop

mount -t extX /dev/mapper/loopXpY YYY

     i)卸载

umount YYY

kpartx -d /dev/loop0

loseup -d /dev/loop0

     制作根文件系统的相关内容:

     根文件系统需要一个简单的工具集,所以busybox能够帮上忙了。所以需要下载最新的busyboxhttp://www.busybox.net/编译方法和内核一样。

     然后以如下步骤创建最小的根文件系统:

     a)创建基本文件系统目录

mkdir -pv tmp/{dev,proc,sys,run}

     b)创建默认节点

mknod -m 600 tmp/dev/console c 5 1

mknod -m 666 tmp/dev/null c 1 3

      c)创建初始化文件

mkdir -pv tmp/etc/init.d

cat > tmp/etc/init.d/rcS << EOF

#this is simple start script!!!

echo "mount add part " > /dev/console

mount -t proc /proc

mount -t sysfs /sys

mount -t tmpfs /run

umask 002

echo "this is first exe script" > /dev/console

export PATH=/bin:/sbin/:/usr/bin:/usr/sbin

EOF

chmod a+x tmp/etc/init.d/rcS

 

cat > tmp/etc/fstab << EOF

#dev  mount-point  type     options           dump fsck-order

/dev/sda1 /        ext3     defaults            1 1

proc   /proc    proc     nosuid,noexec,nodev 0 0

sysfs     /sys     sysfs    nosuid,noexec,nodev 0 0

tmpfs     /run     tmpfs    defaults            1 1

devpts    /dev/pts devpts   gid=5,mode=620      0 0

devtmpfs  /dev     devtmpfs mode=0755,nosuid    0 0

EOF

 

     d)拷贝busybox依赖的库

      因为busybox依赖于ld-linux.so/libc.so/libm.so等库这些库在编译glibc时编译出来了;拷贝它们到lib中。

     使用自动创建脚本:

./create-simple-root.sh busybox-1.23.2/yiyeout/ i486-yiye-linux-

     5.使用qemu进行调试linux内核

     a)第一步使用qemu加载内核

qemu-system-i386 -S -s -kernel linux-4.0.2/arch/x86/boot/bzImage -hdb basic.img -append "root=/dev/sda1 console=ttyS0" -nographic

     -S -s启动gdb同时将程序挂起。

     -hdb basic.img将基本根分区,加载为hdb

     -kernel linux-4.0.2/arch/x86/boot/bzImage -append "root=/dev/sda1 console=ttyS0"加载内核与内核参数。

     -nographic 不是用图形界面,保证得到完整的打印信息。

     b)启动新终端,使用gdb调试内核:

gdb linux-4.0.2/vmlinux 

     设置断点:

b start_kernel

      执行之后:

(gdb) b start_kernel

Breakpoint 1 at 0xc19ca77c: file init/main.c, line 490.

      链接到qemu的模拟远程环境:

target remote localhost:1234

      执行之后

(gdb) target remote localhost:1234

Remote debugging using localhost:1234

      继续运行调试内核:

c

      之后程序会被停在设置的断点,linux-c代码开始的地方。

(gdb) c

Continuing.

 

Breakpoint 1, start_kernel () at init/main.c:490

     后续就是使用gdb的相关命令进行内核单步调试或者程序跟踪了。


     一叶说:根据如上的步骤已经创建好了一个基本的实验环境,而且能够调试内核了。这样来说,对于系统的理解,就可以很直观的运行与追踪了。在如上的过程中,由两个脚本起到了很重要的作用,其实,这是一种有效的学习与总结方式,将好的思想与方法用代码的方式总结,这样不仅能加深思考,同时也方便使用,提高效率。





你可能感兴趣的:(qemu,交叉编译工具,根文件系统,MIT-OS,单步调试linux)