分类: LINUX
2015-09-02 10:15:54
原文地址:GRUB2加载linux-2.6.21 x86_64内核 作者:xlpang
使用GRUB2不能正确引导X86_64 linux-2.6.21 bzImage内核。为解决这一问题,本文展开研究GRUB2加载方式,X86_64 bzImage生成结构、解压缩和启动过程。
bzImage的生成步骤如下:
1) 内核vmlinux是由arch/x86_64/kernel/vmlinux.lds文件链接规则由LD链接而成的elf64格
式文件;本文称之为第一个vmlinux;
2) objcopy将vmlinux转成二进制的vmlinux.bin;本文称之为第一个vmlinux.bin;
3) gzip将vmlinux.bin压缩成vmlinux.bin.gz;
4) vmlinux.bin.gz由arch/x86_64/boot/compressed/vmlinux.scr链接规则链接成piggy.o,此文件只有数据段;vmlinux.scr链接规则如下:
SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data) input_data = .; *(.data) input_data_end = .; } } |
5) arch/x86_64/boot/compressed/下的misc.c, head.S以及生成的piggy.o链接成另外一个压缩vmlinux elf32格式的文件,head.S和misc.c是为了解压piggy.o中包含的vmlinux.bin.gz所用;本文称之为第二个vmlinux;
6) objcopy将压缩vmlinux转成vmlinux.bin二进制格式文件;本文称之为第二个vmlinux.bin;
7) arch/x86_64/boot/tools/build.c编译生成build程序,arch/x86_64/boot/bootsect.S和arch/x86_64/boot/setup.S编译生成二进制类型的bootsect和setup程序;
8) build程序将setup和bootsect二进制格式文件,以及步骤6)中生成的vmlinux.bin二进制格式文件打包成一个最终的bzImage.
因此,bzImage组成结构:
1) bzImage: bootsect+setup+vmlinux.bin
2) vmlinux.bin:head.S+misc.c+vmlinux.bin.gz
3) vmlinux.bin.gz: gzip压缩的vmlinux.bin即最终的内核
一个C程序的生成过程包括:预处理、编译、汇编和链接过程。
Linux下的链接过程是由LD完成的,它可以控制生成目标程序的格式,以及各个部分的组成特点,包括加载地址(LMA)和运行地址(VMA),这些行为可以通过配置链接文件完成,可理解为链接文件是LD程序执行的参数配置文件,决定了链接出来的目标文件的组成;程序由许多节(section)组成,因此链接文件主要是围绕节的定义展开的。
本小节通过简单分析arch/x86_64/kernel/vmlinux.lds.S链接文件来介绍LD的用法:
a) OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")
OUTPUT_FORMAT命令表示生成的目标默认格式,大端字节序时的目标格式以及小端
字节序时的目标格式;
b) OUTPUT_ARCH(i386:x86-64)
OUTPUT_ARCH表示特定的输出机器架构
c) ENTRY(phys_startup_64)
ENTRY表示目标程序的入口地址;
有多种不同的方法来设置入口点,连接器会通过按顺序尝试以下的方法来设置入口点,
如果成功了就会停止:
* ‘-e’入口命令行选项
* 连接脚本中的ENTRY(SYMBOL)命令
* 如果定义了start,就使用start的值
* 如果存在,就使用’.text’节的首地址
* 地址0
d) SECTIONS{}含有目标程序的各个节的定义
e) 节中的定义:. = __START_KERNEL;
.在链接文件表示一种特殊的符号,对它赋值可以改变当前的地址,此语句即将目标
文件的起始地址设成了__START_KERNEL,定义为CONFIG_PHYSICAL_START +0xffffffff80000000,CONFIG_PHYSICAL_START为menuconfig时设定的内核代码开始物理地址(默认为0x1000000);
另外,-Ttext
更详细的GNU LD详细用法请自行阅读:
启动qemu至rootfs,将/dev/hda用fdisk分区并格式成ext2或ext3,并挂载到rootfs的/boot/目录上:
1) 将grub_install/拷至/xlpang/中(安装时的放置路径需确保和编译时的路径一致;也可以
修改/xlpang/grub_install/sbin/ grub-install脚本文件开头的变量路径),执行:
/xlpang/grub_install/sbin/grub-install --no-floppy /dev/hda命令将grub2安装到/boot目录下(实际硬盘挂载后的/目录下),并将boot.img, diskboot.img和kernel.img写入其相应扇区中,其它文件安装在/boot/grub/目录下。
注意事项:
由于busybox不支持grep –qx选项,需要修改grub-install脚本,否则在执行过程中会报错
修改前: ……… if [ "x${devabstraction_module}" = "x" ] ; then if [ x"${install_device}" != x ]; then if echo "${install_device}" | grep -qx "(.*)" ; then install_drive="${install_device}" ……… |
修改后:(蓝色部分是对grepqx函数的实现) ……… grepqx() { local line="$1" local file="$2" local ret ret=$(sed -n "/^${line}$/p" ${file}) if [[ -z ${ret} ]] then return 1 else return 0 fi } if [ "x${devabstraction_module}" = "x" ] ; then if [ x"${install_device}" != x ]; then if echo "${install_device}" | grepqx "(.*)" ; then install_drive="${install_device}" ……… |
2) 生成/boot/grub/grub.cfg文件(在hda1挂载的根目录下放置bzImage内核映像):
编辑/xlpang/grub_install/etc/grub.d/40_custom文件,增加:
menuentry "linux_x86_64-2.6.21" { set root=(hd0,1) linux /bzImage ro initrd /initramfs } |
3) 执行:/home/grub-mkconfig -o /boot/grub/grub.cfg
注意事项:
如果“/”是挂载到ramdisk文件系统,/boot是mount到第一块硬盘第一个分区(/dev/hda1),且grub是安装在/boot上,则grub-mkconfig执行时会报如下错误:
解决方法:
修改grub-mkconfig脚本,设置grub-probe --target=device的路径为/boot,即grub安装路径,如下所示:
修改前: # Device containing our userland. Typically used for root= parameter. GRUB_DEVICE="`${grub_probe} --target=device /`" GRUB_DEVICE_UUID="`${grub_probe} --device ${GRUB_DEVICE} --target=fs_uuid 2> /dev/null`" || true |
修改后: # Device containing our userland. Typically used for root= parameter. GRUB_DEVICE="`${grub_probe} --target=device /boot`" |
1) 使用仿真硬盘启动并进入调试模式命令:
qemu-x86_64.exe –S -boot c -hda hda500M.qcow -net tap,ifname=CEEE_TAP -m 512 -smp 4
-S表示进入调试模式,-boot c表示从硬盘启动,后面紧跟创建的虚拟硬盘文件
2) 进入后按ctrl+alt+2进入调试界面,输入”gdbserver”在qemu上启动调试服务器;按
ctrl+alt+1可返回qemu运行界面;
3) 使用gdb_linux_x86.exe或gdb_linux_x8664.exe进行相关调试
a) 运行gdb_linux_x86.exe vmlinux,此处vmlinux为第二个vmlinux
b) 连接qemu的gdbserver:target remote localhost:1234
连接成功后有类似如下打印:
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
[New Thread 1]
warning: shared library handler failed to enable breakpoint
0x00000001 in ?? ()
c)后续就类似于标准gdb调试应用程序的流程了,附几个常用命令:
b *0x100000 /* 在0x100000处设置断点 */
x/32i 0x1000000 /* 查看0x1000000开始处的指令 */
info reg /* 查看寄存器 */
info b /* 查看断点 */
d
stepi /* 汇编级单步调试 */
c /* 程序继续运行 */
qemu-x86/qemu_x86_64在启动过程中注册了一组特殊的io端口用来显示bochs bios的调试输出。我们将这组特殊的io端口称为boch调试端口。qemu-x86的启动函数pc_init1()中调用了boch端口注册函数bochs_bios_init完成调试端口的注册过程;写入调试端口0x402,0x403的数据将被打印出来,因此在内核启动初期可以采用向端口0x402或者ox403端口写入数据来完成信息的打印。
由于x86_64的qemu用gdb调试有问题,可以通过添加打印来查看某此信息。
字符打印函数
可以采用如下x86汇编函数完成一个字符的打印。要求输入参数存储在eax寄存器中。
################################ # function print char ################################ outchar: #pramater al push %dx movw $0x402,%dx outb %al,%dx pop %dx ret |
数字打印函数
8字节打印函数
打印一个8bit数,输入参数放置到寄存器al中
outnum8: #pramater %al push %bx push %cx push %dx movb %al,%bl movw $0x402,%dx mov %bl,%cl #print highest half byte shrb $4, %bl cmpb $10,%bl movb $48,%al jb ship_add1 addb $7,%al ship_add1: addb %al,%bl movb %bl,%al outb %al,%dx #print lower half byte movb %cl,%bl andb $0x0f,%bl cmpb $10,%bl movb $48,%al jb ship_add2 addb $7,%al ship_add2: addb %al,%bl movb %bl,%al outb %al,%dx pop %dx pop %cx pop %bx ret |
16bit、32bit数字打印函数
outnum16: #pramater ax push %bx movw %ax,%bx shrw $8,%ax call outnum8 movw %bx,%ax andw $0xff,%ax call outnum8 pop %bx ret outnum32: #pramater eax push %ebx mov %eax,%ebx shr $16,%eax call outnum16 mov %ebx,%eax call outnum16 pop %ebx ret |
eip寄存器打印函数
##################################################### # function print eip ################################################### prt_eip: movl (%esp), %eax call outnum32 ret |
例子
我们可以通过以上定义的函数在内核启动初期Setup.S中打印一些信息。一下是一个使用实例:
打印esi的值
#zhangxy printf esi push %eax push %esi mov %esi,%eax call outnum32 pop %esi pop %eax |
在setup.S代码中加打印eip的代码:
#pangxunlei print ip push %ax call prt_eip pop %ax |
有了以上对各流程的理解和准备,可以逐步有序开展问题定位工作。
采用GRUB2启动X86_64内核失败,初步怀疑与重定向有关,由于LD链接时会对bzImage生成0x100000的符号地址。
arch/x86_64/boot/Makefile中:$(obj)/bzImage: IMAGE_OFFSET := 0x100000
arch/x86_64/boot/ compressed /Makefile中:
LDFLAGS_vmlinux := -Ttext $(IMAGE_OFFSET) -e startup_32 -m elf_i386 /* 参考第3节 */
因此第二个vmlinux会采用0x100000代码段起始地址(compressed中的head.S和misc.c)。
1) arch/x86_64/boot/compressed/head.S的执行入口startup_32,CPU处于保护模式(未
开启分页);
2) 设置了段寄存器、堆栈和一些初始化后,执行decompress_kernel函数(位于misc.c);
esi寄存器指向实模式下搜集的一些实模式数据起始物理内存地址;
3) decompress_kernel()检查是大内核后,进入gunzip解压代码,将内核解压至2个区域:
a) 记low_buffer_end为esi > 0x90000? 0x90000 : (unsigned int)esi) & ~0xfff;
区间1:[0x2000, low_buffer_end);
区间2:[0x1000000+ 区间的大小, 内核数据结束位置)
注:0x1000000为内核编译起始地址CONFIG_PHYSICAL_START
b) 之后再进入head.S中,将区间1搬运到区间2的开头,形成0x1000000开头的完
整内核数据;
为排除解压过程发生错误,修改decompress_kernel()行为:直接解压至0x1000000处,并且head.S中不进入搬运流程,然后跳转至内核入口,仍然启动失败。
用gdb查看0x1000000处的指令,如下:
1) 设置b decompress_kernel断点,显示在0x00105826内存地址处;说明bzImage被
GRUB2放置在了0x100000处;显示0x1000000处的值为:“x1000000: int3”,不是内核数据,因为还没完成解压;
2) x/32i 0x100000显示compressed/head.S在内存中的指令,并在call decompress_kernel
之后设置断点(保证内核解压完成),重新执行qemu;
3) 断住后,再次查看内核解压数据,x/10i 0x1000000:
(gdb) x/3i 0x1000000
0x1000000: mov $0x18,%eax
0x1000005: movl %eax,%ds
0x1000007: lgdtl 0x1000f00
结论:与arch/x86_64/kernel/head.S开头的相符,至此可以表明内核解压缩正常,排
除bzImage加载位置不对以及内核解压部分的因素。
精简compressed/head.S代码如下,修改解压代码如下(执行环境:32位保护模式,未开启分页):
.code32 .globl startup_32
startup_32: /* 0x100000入口地址 */ cld cli movl $(__KERNEL_DS),%eax movl %eax,%ds movl %eax,%es movl %eax,%fs movl %eax,%gs
lss stack_start,%esp movl 0x000000,%ecx xorl %eax,%eax
ljmp $(__KERNEL_CS), $0x100000 /* 从头开始。用grub2加载会不停返回到grub2的加载界面;不用grub2加载就不会有问题,即会一直循环解压不退出 */ |
结论:在ljmp远指针远跳转指令时,使用grub2会出问题,但一直理不清为什么会跳转回grub2
加载界面。
compressed/head.S分析不下去了,转战setup.S,呵呵!
arch/x86_64/boot/setup.S为bzImage中的第二个512B开始的代码,大小在bootsect中有记录,setup的功能如下:
1) 在ACPI兼容的系统中,调用BIOS例程建立描述系统物理内存布局的表。在早期系统中,它调用BIOS例程返回系统可以的RAM大小;
2) 设置键盘的重复延迟和速率;
3) 初始化显卡;
4) 检测IBM MCA总线、PS/2鼠标设备、APM BIOS支持等;
5) 如果BIOS支持Enhanced Disk Drive Services (EDD),将调用正确的BIOS例程建立描述系统
可用硬盘的表;
6) 如果内核加载在低RAM地址0x00010000,则把它移动到0x00001000处;如果映像加载在高内存1MB位置,则不动;
7) 启动位于8042键盘控制器的A20 pin;
8) 建立一个中断描述表IDT和全局描述表GDT表;
9) 如果有的话,重启FPU单元;
10) 对可编程中断控制器进行重新编程,屏蔽所以中断,级连PIC的IRQ2不需要;
11) 设置CR0状态寄存器的PE位使CPU从实模式切换到保护模式,PG位清0,禁止分页功能;
12) 跳转到startup_32()汇编函数, jmpi 0x100000, __BOOT_CS,终于进入内核解压head.S;
setup二进制程序开头部分是Linux内核与bootloader的协议约定(linux2.6.21用的是protocol 2.04),请参考documentation/i386/boot.txt的“THE REAL-MODE KERNEL HEADER”。
GRUB2在loader/i386/pc/linux.c的grub_cmd_linux()函数中处理此实模式头部信息(位于bzImage的0x200开头的512B内容中)的解析,根据这些信息来加载bzImage至相应的位置,GRUB2关于这些字段的信息位于include/grub/i386/linux.h中,其中:
“#define GRUB_LINUX_BZIMAGE_ADDR 0x100000”定义了第二个vmlinux.bin文件被放置的内存地址,与5.1节中的分析一致,也即bzImage的解压部分被放置在0x100000处。
在qemu上,正常流程(不使用GRUB2加载)setup.S被放置在0x10200处(可用qemu的调试功能验证)。
在qemu上,使用GRUB2加载bzImage,在0x10200处设置断点,发现断点不命中。
结论:qemu上,GRUB2对bzImage的加载流程发生了改变。
最终在setup.S中,增加了打印地址的代码(参考4.3节,关于qemu的特色打印功能)想看看setup.S被加载到了哪个位置,发现打印不出来,比较奇怪,于是干脆在setup.S的入口处增加了死循环,结果GRUB2加载却没有死循环,仍然不停的异常重启。
结论:GRUB2没有加载setup
GRUB加载的时候已经处于保护模式,正常的流程是:加载bzImage->切至实模式->跳至setup执行->切至保护模式->跳至compressed/head.S中执行。
GRUB2则是加载完bzImage后,完成了一些setup.S的工作后(如生成实模式数据,并设置esi),从保护模式直接跳转至了compressed/head.S中。
GRUB2加载linux-2.6.21 X86_64时不停的异常重启GRUB2,是在远跳转指令发生的,远跳转指令会更改CS寄存器,由于是保护模式会用到GDT,应该是由于没有正确设置GDT而造成的,只要正确设置GDT,而不用GRUB2的时候会跳转至setup.S中设置GDT,因此,我们相信只要将setup.S中的GDT移至compressed/head.S中,就可以完成GRUB2的正确加载,又由于是重复setup.S中的设置,不会影响不用GRUB2时的加载流程。
在compressed/head.S最后添加:
.data pxl_gdt: .word 0, 0, 0, 0 # dummy
.word 0, 0, 0, 0 # unused
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb) .word 0 # base address = 0 .word 0x9A00 # code read/exec .word 0x00CF # granularity = 4096, 386 # (+5th nibble of limit)
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb) .word 0 # base address = 0 .word 0x9200 # data read/write .word 0x00CF # granularity = 4096, 386 # (+5th nibble of limit) .align 256 pxl_gdt_48: .word 31 # gdt limit .long pxl_gdt # gdt base
|
在compressed/head.S开始处添加:
startup_32: cld cli
movl $(__KERNEL_DS),%eax movl %eax,%ds movl %eax,%es movl %eax,%fs movl %eax,%gs lss stack_start,%esp lgdt pxl_gdt_48 |
AMD64架构设计原因:无缝兼容legacy x86,更大的寻址空间和高性能(尤其是大块数据操作)。表现为更多的寄存器,64bit的寄存器,64bit的虚拟地址和物理地址。
AMD64内存模型兼容传统的(Legacy)X86内存模型,系统软件通过AMD64内存模型可以安全地管理用户程序和数据。类似于X86,AMD64设计了硬件转换机制将虚拟地址映射到物理地址,这样就能保证系统软件透明地重定向物理内存空间(或操作系统管理的硬盘驱动器)的应用程序和数据。
AMD64包括2种运行模式:长模式和传统模式。长模式的64-bit子模式实现了平坦内存模型(flat-memory model),传统模式实现了X86架构的所有内存模型。
X86支持4种运行模式,不同模式下提供了不同的内存管理、虚拟内存大小、物理内存大小,以及保护;4种模式为:
Ø 实模式(Real Mode)
Ø 保护模式(Protected Mode)
Ø 虚8086模式(Virtual-8086 Mode)
Ø 系统管理模式(System Management Mode)
AMD64支持X86的所有运行模式,称为“传统模式”(Legacy Mode);并设计了一种全
新的模式,称为“长模式”(Long Mode);因此,AMD64共有2种模式:传统模式和长模式;传统模式包括了X86的4种子模式,长模式包括64-bit模式和兼容模式。
软件可以实现在这些模式之间的切换,如下图所示。
长模式包含2个子模式:64-bit模式(64-bit模式)和兼容模式(Compatible Mode)。64-bit模式支持一些新特性,如64-bit虚拟地址空间;兼容模式允许在64位的系统软件(如64位操作系统)下,运行16位/32位应用程序(是二进制兼容的,不需要重新编译);长模式下取消了硬件多任务机制。
声明:使用“长模式”描述时表示64-bit模式和兼容模式共有的特性,当子模式特有的功能时,会直接使用子模式的名称进行描述。
使能和激活长模式(通过改变EFER.LME位,EFER:Extended Feature Enable Register)之前,系统软件必须先使能操作模式(即只能从保护模式进入长模式)。
AMD64引入了长模式和它的2个子模式:64-bit模式和兼容模式。
64-bit模式 完全支持64位的应用程序和系统软件,开启64-bit模式,需要64位的操作系统和64位的工具链支持。
兼容模式 此模式实现了在64位的操作系统上兼容运行16位/32位X86架构的应用程序,这些应用程序不需要重新编译;
长模式不支持如下2种模式:
Ø 虚8086模式 – 虚8086模式位(EFLAGS.VM)在长模式下被处理器忽略;当长模式使能时,试图开启虚8086模式将自动被处理器忽略,需要进入虚8086模式的系统软件必须先退出长模式。
Ø 实模式 – 进入长模式需要事先开启保护模式,所以长模式下不支持实模式。
传统X86模式
AMD64架构支持纯粹的传统X86模式,此模式二进制兼容16位/32位X86架构应用程序;更进一步地,与兼容模式不同的是,传统模式二进制兼容16位/32位操作系统。传统模式支持实模式、保护模式和虚8086模式。复位时处理器总是处于传统模式(实模式)下,直到系统软件激活长模式。
64-bit模式支持64-bit的系统软件和应用程序,引入了如下新新性:
Ø 64-bit虚拟地址空间(具体处理器可能实现较小的空间)
Ø 通过REX指令前缀扩展寄存器:
- 增加R8-R15通用寄存器
- 所有通用寄存器扩充到64位
- 增加了8个YMM/XMM8-15 SSE 256/ 128位寄存器
Ø 64位RIP寄存器
Ø 增加RIP相对数据寻址
Ø 平坦段地址空间
64-bit模式通过CS寄存器使能(控制64-bit模式和兼容模式之间的切换),传统的段机制
被禁用,分页机制需要使能,软件方面需要64位的系统软件和工具来运行此模式。
对现有的X86下的16位/32位应用程序保持二进制兼容,此模式下运行有长模式下的64位系统软件。
兼容模式下,应用程序只能够访问4GB虚拟地址空间;CS寄存器控制兼容模式和64-bit模式之间的切换;与64-bit模式另一个不同,段机制和X86的保护模式保持一致,但与X86不同的是,分页机制是使能的。
从应用程序的角度来看,兼容模式和开启了分页机制的X86保护模式是一致的;从系统软件(操作系统)的角度来看,兼容模式下运行的是长模式的系统软件、地址转换机制、中断异常处理,和系统数据结构。
AMD64的传统模式包括:实模式、保护模式和虚8086模式。传统模式下不仅能提供与X86架构的16位/32位应用程序兼容,还能保持16位/32位系统软件的兼容(相比,兼容模式下的系统软件必须是64位的)。
实模式
又称为实地址模式。此模式下,处理器支持1MB的物理内存空间,操作数默认是16位,AMD64的实模式可能通过指令前缀(操作数大小指令前缀,66h)变成32位的;中断处理和地址生成和80286处理机保持一致;不支持分页;所有软件在特权等级0下运行。
上电或重启时,处理器会运行在实模式下;长模式下没有实模式,原因是长模式需要从开启分页的保护模式切入。
保护模式
保护模式支持4GB的虚拟内存空间,4GB的物理内存空间,16位或32位操作数;所有的段式转换,段保护和硬件多任务机制都是可用的;分页是可选的;系统软件可以通过分段在虚拟地址空间中重定向有效地址。
保护模式下,软件可以在特权等级0~3运行;通常应用程序运行在特权等级3,系统软件运行在特权等级0和1;80286处理器首先引入了16位保护模式特性。
虚8086模式
通过保护模式切入;虚8086模式下,可以运行16位的实模式下运行的软件,在8086, 8088, 80186和80188处理上运行的程序都可以在此模式下运行,但运行特权等级为3;支持虚拟地址空间1MB,操作数大小为16位,AMD64虚8086模式下可以通过操作数大小指令前缀来使用32位的操作数;使用实地址模式转换;与实模式不同的是,分页是可选的,并且开启分页后的物理地址是32位的。
EFLAGS.VM位控制虚8086模式的切入切出,POPF指令不能改变VM位,VM位只能通过2种方式访问:
a) 任务切换时,从TSS段中载入EFLAGS;
b) 特权软件执行IRET指令(中断发生时,硬件会自动将EFLAGS压栈)。
长模式不支持虚8086模式,当长模式使能时,岂图使能虚8086模式将自动被硬件忽略。
AMD64支持X86支持的所有形式的段,然而大部分现在系统软件并没有利用段来隔离任务,取而代之的是分页机制。因此AMD64长模式中的64-bit(子)模式取消了段的设计,使用了平坦内存模型,这使得新的64-bit系统软件的设计变得更加简单和高效。
长模式的地址转换过程如下图所示:
传统模式下的代码/数据段描述符如下:
长模式下的代码/数据段描述符如下:
图中灰色的部分表示忽略,长模式下忽略了段机制,只保留了几个字段用于控制作用。
抛开灰色字段被忽略的行为,X86和AMD64两种架构的数据段字段定义是一样的,因此只要不发生远跳转,CPU就可以正常的执行指令。
注意到arch/x86_64/kernel/head.S中的GDT表(cpu_gdt_table)的数据段描述符的值和setup.S中设置的是一样的,第一个远跳转指令为进入长模式的指令(已经进入了长模式):
ljmp $__KERNEL_CS, $(startup_64 - __START_KERNEL_map),因此,可以做如下修改:
a) compressed/head.S中,将文件中的3处远跳转指令替换为近跳转指令,示例:
ljmp $(__KERNEL_CS), $__PHYSICAL_START
修改为 ->
movl $__PHYSICAL_START, %ebp
jmp *%ebp
b) arch/x86_64/kernel/head.S中,将
movl $__KERNEL_DS,%eax
movl %eax,%ds
lgdt pGDT32 - __START_KERNEL_map
修改为 ->
lgdt pGDT32 - __START_KERNEL_map
movl $__KERNEL_DS,%eax
movl %eax,%ds
c) 针对SMP,arch/x86_64/kernel/trampoline.S中,跳至arch/x86_64/kernel/head.S前即
ljmpl $__KERNEL32_CS, $(startup_32-__START_KERNEL_map)前
增加 ->
mov $__KERNEL_DS, %ax
mov %ax, %ds