目录
- 一、问题描述
- 二、解决思路
-
- [2.1 尝试从编译命令层面解决](#2.1 尝试从编译命令层面解决)
- [2.2 反汇编kernel.bin分析原因](#2.2 反汇编kernel.bin分析原因)
- [2.3 制作test文件验证链接是否存在问题](#2.3 制作test文件验证链接是否存在问题)
- [2.4 修改loader.S解决问题](#2.4 修改loader.S解决问题)
- 三、结尾
一、问题描述
在进行第七章中断实验时,出现一个问题。当使用lidt
加载中断向量表命令后,使用bochs的调试命令Info idt,发现中断号对应的中断函数地址有问题:
从图中可以看到,DPL正确,选择子正确,但是基址有问题,全为0
二、解决思路
先说结论:问题出现在lodaer.S
中,在加载elf
文件的program
段时,只运行了一次加载过程,导致只加载了.text段,也就是代码段,没加载.data段。
2.1 尝试从编译命令层面解决
完成该实验时ubuntu的gcc版本为7.5,根据有的博主描述,需要降版本到4.4,于是进行降版本,参考文章:
《操作系统真象还原》第五章 ---- 轻取物理内存容量 启用分页畅游虚拟空间 力斧直斩内核先劈一角 闲庭信步摸谈特权级
并深入学习了nasm编译和ld链接的原理,以为是链接出现了问题,于是修改编译命令,最终使用的编译命令如下:
bash
#var for boot
#Var for GCC
# 此处可能需要加stck_protect那个编译选项
CFLAGS += -fno-builtin
INCLUDE := \
-Ikernel/ \
-Iinclude/ \
-Ilib/ \
-Ilib/kernel/ \
-Ilib/kernel/include
GCC32 += -m32
GCC_MARCH += -march=i386
#Var for ld
BUILD_SRC := build/main.o \
build/init.o \
build/interrupt.o \
build/print.o \
build/kernel.o
BUILD_DES := build/kernel.bin
LD_ENTRY_ADDR := -Ttext 0xc0001500
LD_ENTRY_FUNC := main
#Var for gene
CURRENT_PATH := $(shell pwd)
_all:
all: _all
@echo "==========================start building...===================================="
@echo "=============================#1 start compile=============================="
gcc ${GCC32} ${INCLUDE} -c ${CFLAGS} -o build/main.o kernel/main.c
nasm -f elf -o build/print.o lib/kernel/print.S
nasm -f elf -o build/kernel.o kernel/kernel.S
gcc ${GCC32} ${INCLUDE} -c ${CFLAGS} -o build/interrupt.o drivers/interrupt/interrupt.c
gcc ${GCC32} ${INCLUDE} -c ${CFLAGS} -o build/init.o init/init.c
@echo "=============================#2 start ld=================================="
ld -m elf_i386 ${LD_ENTRY_ADDR} -e ${LD_ENTRY_FUNC} -o ${BUILD_DES} ${BUILD_SRC}
dd if=${CURRENT_PATH}/${BUILD_DES} of=${CURRENT_PATH}/hd60M.img bs=512 count=200 seek=9 conv=notrunc
@echo "build successful!"
@echo "objdump..............."
objdump -d build/kernel.bin > build/asm/kernel.bin_gcc_4.4.7.asm
第七章实验的文件结构如下,大致仿照linux的结构来建立的文件夹:
然而,此阶段并没有解决问题,仍然存在该问题。
2.2 反汇编kernel.bin分析原因
上一步没有结果,反复对照各博主实验过程与《操作系统真相还原》书中给出的代码,可以保证C代码和汇编代码kernel.S没有任何问题。于是反汇编kernel.bin
,分析问题。通过gcc的反汇编工具objdump
来反汇编:
bash
objdump -d build/kernel.bin > build/asm/kernel.bin_gcc_4.4.7.asm
结合interrupt.c中的C代码:
c
interrupt.c:
/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function){
p_gdesc->func_offset_low_word=(uint32_t)function & 0x0000ffff;
p_gdesc->selector=SELECTOR_K_CODE; // 中断后进入内核代码
p_gdesc->dcount=0;
p_gdesc->attribute=attr;
p_gdesc->func_offset_high_word=((uint32_t)function & 0xffff0000)>>16;
}
/*初始化中断描述符表*/
static void idt_desc_init(void){
int i;
for (i=0;i<IDT_DESC_CNT;++i){
make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
}
put_str(" idt_desc_init done\n");
}
定位汇编中的问题命令:
可以看到,中断向量函数基址作为函数调用的第三个参数,被赋值给了eax并进行压栈。在此处打断点并查看eax寄存器的值,发现为0。说明此处没有把中断处理函数基址赋值给eax寄存器。
此处令我疑惑,不可能找不到中断处理函数基址,因为链接已经成功了,可以看到反汇编后的代码中有这些中断处理函数:
此处未能发现问题,主要是汇编代码太多了,不容易分析问题。于是做了一个简单的实验,来分析gcc编译文件与nasm编译文件的链接问题
2.3 制作test文件验证链接是否存在问题
制作main_test.c:
c
#include "stdint.h"
typedef void* intr_handler;
extern intr_handler intr_entry_table[0x21];
void main(void)
{
uint32_t test_low = (uint32_t)intr_entry_table[0] & 0x0000ffff;
uint32_t test_high = ((uint32_t)intr_entry_table[0] & 0xffff0000)>>16;
while(1);
}
制作kernel_test.S:
c
[bits 32]
[section .data]
global intr_entry_table
intr_entry_table:
%macro VECTOR 1
[section .text]
intr%1entry: ;每个中断处理程序都要压入中断向量号,一个中断类型一个中断处理程序
out 0xa0,al ;向从片发送
iret ;从中断返回
[section .data]
dd intr%1entry ;存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacro
VECTOR 0x0
VECTOR 0x1
VECTOR 0x2
VECTOR 0x3
编译链接命令:
bash
debug: _all
@echo "==========================Debug...===================================="
gcc ${GCC_MARCH} ${GCC32} ${INCLUDE} -c ${CFLAGS} -o debug/build/main_test.o debug/main_test.c
nasm -f elf -o debug/build/kernel_test.o debug/kernel_test.S
ld -m elf_i386 ${LD_ENTRY_ADDR} -e ${LD_ENTRY_FUNC} -o debug/build/kernel_test.bin debug/build/main_test.o debug/build/kernel_test.o
objdump -d debug/build/kernel_test.bin > debug/build/asm/kernel_test.bin.asm
objdump -d debug/build/kernel_test.o > debug/build/asm/kernel_test.o.asm
objdump -d debug/build/main_test.o > debug/build/asm/main_test.o.asm
debug_dd: _all
dd if=${CURRENT_PATH}/debug/build/kernel_test.bin of=${CURRENT_PATH}/hd60M.img bs=512 count=200 seek=9 conv=notrunc
对kernel_test.bin进行反汇编,结果如下:
想说的都在汇编中加了注释了,关键就是0xc000252c
这个地址中的数据,是不是intr0x0entry
的起始地址,也就是是不是0xc0001520
。但是在反汇编之后的文件中根本没找到0xc000252c
这个地址,这也很让我疑惑。
将该kernel_test.bin载入bochs中运行,使用xp/2 0x252c(实地址,对应虚拟地址0xc000252c),查看0xc000252c
这个地址中的数据,发现全是0。这个时候想起来了,在kernel.S中,把中断函数的地址,存在了data段中。所以说0xc000252c
这个地址,应该是data段的地址。于是解析kernel_test.bin这个elf文件:
发现确实是data段在内存中的首地址,所以只要看一眼data段的首地址的内容(文件内偏移0x052c),就能确定是不是编译链接有问题了。通过16进制打开kernel_test.bin文件:
发现0x052c地址(对应虚拟地址0xc000252c
)中是有内容的,并不是0,而且就是c0001520
,也就是第0个中断处理函数的地址:
至此可以确定,编译没有问题,出问题的是data段的加载有问题,可能根本没有加载进来,也可能加载到了错误的位置。
2.4 修改loader.S解决问题
知道问题在哪了,就好改多了。回头看loader.S,错误很快就发现了:
在写loader.S时没有加loop这条命令,导致只加载了一段,也就是只加载了text段,没加载其他段。
三、结尾
这个问题说大不大说小不小,现在比较忙,每天只能抽出不到一个小时来做一下这个实验,一出现这种问题就心烦意乱,花了四五天的时间来搜索解决方案,完全不想调试。趁着周末沉下心来一看,一步步调试其实也能找到问题。