调试分析Linux 0.00引导程序

调试分析Linux 0.00引导程序

head.s 的工作原理

首先,header.s在32位的保护模式下运行。

  1. 设置GDT和IDT表

首先加载数据段寄存器、堆栈段寄存器SS和堆栈指针ESP。

movl $0x10,%eax
mov %ax,%ds
lss init_stack,%esp

然后在新的位置重新设置IDT和GDT表。设置IDT,并将256个中断门都填默认处理过程的描述符;设置GDT,在改变了GDT之后重新加载所有段寄存器

call setup_idt
call setup_gdt
movl $0x10,%eax
  1. 设置系统定时芯片

把计数器通道0设置成每隔10毫秒向中断控制器发送一个中断请求信号。

movb $0x36,%al # 设置计数初值采用2进制
movl $0x43,%edx 
outb %al,%dx
movl $LATCH,%eax # 初始计数器的值设置为LATCH
movl $0x40,%edx
outb %al,%dx # 分两次把初始计数器写入通道0
movb %ah,%al
outb %al,%dx
  1. 在IDT表中8和128表项的位置分别设置中断门描述符和系统调用陷阱门描述符

中断程序属于内核,也就是说eax的高字节是内核代码段的选择符号 0x0008

设置中断门描述符之后,取定时中断处理程序地址。

设置系统调用陷阱门描述符。取系统调用处理程序地址。

  1. 移动到任务0来操作堆栈并建立返回场景

复位eflags寄存器中的嵌套任务标志

把任务0的TSS段选择符号加载到任务寄存器TR

把任务0的LDT段选择符号加载到LDTR

存储当前任务号在current变量中

堆栈指针入栈;标志寄存器值入栈;局部空间代码段入栈;代码指针入栈;执行中断返回

另外从LDT和GDT的函数代码可知

setup_gdt 使用6字节操作数设置GDT表位置和长度,暂时设置IDT表中多有256个终端们描述符都是一个默认值,都是用默认的中断处理过程 ignore_int。

setup_idt 和设置定时终端门描述符的方法一样。选择符为0x0008

timer_interrupt 先让DS指向内核的数据段,然后立刻允许其他的硬件中断,接着判断当前任务,如果是任务1就去执行任务0,反之亦然。

je 1f
movl %eax, current  # 如果当前的任务是0,就把1存入current,并转到任务1
ljmp $TSS1_SEL, $0  # 去执行,虽然没有偏移值但是需要写
jmp 2f 
movl $0,current # 如果当前任务是1,就把0存入current,并转到任务0
ljmp $TSS0_SEL,$0 # 执行

ignore_int 默认的中断处理程序。每个任务在执行的时候会首先把一个字符的ASCII码值放入寄存器AL中,然后调用系统默认的中断处理程序0x80,显示过字符之后任务代码就会使用循环语句延迟一段时间,然后跳转到另一个任务继续循环执行。任务A显示A,任务B显示B,如果系统产生了其他的中断会显示为C

head.s 内存分布状况

代码段的分布状况

代码段编号名称起始地址终止地址
1startup_320x000xac
2setup_gdt0xad0xb4
3setup_idt0xb50xcd
4rp_idt0xd20xe4
5write_char0xe50x113
6ignore_int0x1140x129
7timer_interrupt0x12a0x165
8system_interrupt0x1660x17c
9task00x10e00x10f3
10task10x10f40x1107

数据段的分布状况

数据段编号名称起始地址终止地址
1current0x17d0x180
2scr_loc0x1810x184
3lidt_opcode0x1860x18b
4lgdt_opcode0x18c0x191
5idt0x1980x997
6gtd0x9980x9d7
7ldt00xbe00xbf7
8tss00xbf80xc5f
9ldt10xc600xe77
10tss10xe780xedf

堆栈段的分布状况

堆栈段编号名称起始地址终止地址
1init_stack0x9d80xbd8
2krn_stk00xc600xe60
3krn_stk10xe000x10e0
4user_stk10x11080x1308

header.s 57行至62行

pushl $0x17			# 把任务0当前局部空间数据段(堆栈段)选择符入栈
pushl $init_stack 	# 把堆栈指针入栈
pushfl 				# 把标志寄存器值入栈
pushl $0x0f 		# 把当前局部空间代码段选择符入栈
pushl $task0 		# 把代码指针入栈
iret 				# 执行中断返回命令,从而切换到特权级别为3的任务0中执行

PC在iret执行后找到下一条指令的过程

执行iret之后,讲推入堆栈的段地址和偏移地址从栈里面弹出,让程序的上下文返回到发生中断的位置上。具体的实现方法就是:

  1. 恢复IP的值
  2. 恢复CS的值
  3. 恢复中断之前的状态
  4. 返回的权限发生变化
  5. 恢复SS、ESP

PC这之后再根据CS和IP来形成段的基地址,再加上偏移地址就得到了下一条指令的地址。

栈在iret前后发生的变化

在iret执行之前的栈如下:

在iret执行之后的栈如下:

image-20231016164428402

可以看到在执行iret之前,栈顶指针为0xbc4,此时的SS值为0x10,即将跳转的地址可以看出是CS值为0x0f,IP的值为0x10e0;用户栈的栈顶地址为0x17;0x0bd8可以计算出来对应的栈顶地址位置为0xbd8的位置。正好与下方的iret执行后的表现相同。

执行之后栈顶就换成了在跳转之前指定的0x0BD8栈顶,而且程序跳转到了0x0f:0x10e0位置进行执行。这就完成了具有特权级别变换的跳转。

执行int 0x80栈的变化

执行 int 0x80之前:

image-20231016171414236

执行int 0x80之后:

image-20231016171431910

在执行int 0x80之前,栈顶的位置还是指向ss:sp = 0x17:0x0BD8的位置,这个时候的cs:eip = 0x0f:0x10e9,这个时候还没有发生跳转。

在执行int 0x80之后,此时ss:esp可以看到切换到了0x10:0xe4c的位置,并且cs:eip跳转到了中断处理程序的入口也就是0x08:0x0166的位置。同时也可以观察到压入的部分是从0x17开始的,这个压入的是之前的用户栈的栈顶的值,0xe5c为ss的值,0xe58压入的是sp的值,0xe54压入的是eflags寄存器内的值,0xf代表的是对应的cs的值,0x10eb代表的原本的eip的下一个指令偏移地址位置。

所以可以看出在调用的过程中,先是将内核栈的栈顶切换到0x10:0xe4c的位置,然后将用户栈栈顶(ss:sp)、eflags寄存器的值和用户程序的返回地址(cs:eip)都压到内核的堆栈里。

  • 22
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值