博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用汇编语言编写加载器(加载用户程序)
阅读量:6687 次
发布时间:2019-06-25

本文共 3170 字,大约阅读时间需要 10 分钟。

使用汇编语言编写加载器加载指定格式的用户程序


在计算机加电之后,计算机首先会读取硬盘的主引导扇区,做一些必要的初始化工作,但是硬盘的一个扇区只有512字节,所以我们要实现更多的功能,就要有用户程序,我们需要把控制权限交给用户程序(操作系统暂且也算一种用户程序吧)。

在加载用户程序的过程中,主要分为以下几个大步骤:

  • 一.从硬盘读取用户程序,并加载到内存中的指定位置(自定义)。
  • 二.重定位用户程序(段地址)
  • 三.将控制权交给用户程序

用户程序头部:

SECTION header vstart=0                     ;定义用户程序头部段     program_length  dd program_end          ;程序总长度[0x00]        ;用户程序入口点    code_entry      dw start                ;偏移地址[0x04]                    dd section.code_1.start ;段地址[0x06]         realloc_tbl_len dw (header_end-code_1_segment)/4                                            ;段重定位表项个数[0x0a]        ;段重定位表               code_1_segment  dd section.code_1.start ;[0x0c]    code_2_segment  dd section.code_2.start ;[0x10]    data_1_segment  dd section.data_1.start ;[0x14]    data_2_segment  dd section.data_2.start ;[0x18]    stack_segment   dd section.stack.start  ;[0x1c]        header_end:

一.读取用户程序到内存

从硬盘读取信息,需要五个步骤:

  1. 设置要读取的扇区数量

这个数值要写入0x1f2端口,这是一个8位寄存器,所以可以使用

out dx,al

2.设置要读取的起始LBA扇区号。

这里使用LBA28。28位的扇区号,分给四个端口,0x1f3-0x1f6,从低到高依次存储,最后一个端口也就是0x1f6低四位存储扇区号的最高四位,剩下四位高三位111表示LBA模式,最低以为0表示从盘,1表示主盘。
例如,如果其实扇区是100,也就是0x60,那么设置的代码如下:

mov dx,0x1f3mov al,0x60out dx,alinc dxxor al,alout dx,alinc dxout dx,alinc dxmov al,0xe0out dx,al

3.请求读写

次数值写入0x1f7端口,0x20表示请求读

mov al,0x20mov dx,0x1f7out dx,al

4.等待硬盘空闲

0x1f7这个端口,除了可以请求读意外,还能表示硬盘的状态,第7位0表示空闲,1表示繁忙,第3位为1表示准备好进行数据传输,所以这里我们要等待硬盘空闲才可以传输数据:

waits:    in al,dx    and al,1000_1000B    ;保留第3位和第7位    cmp 0000_1000B    ;第7位为0第3位为1才可以进行读取    jne waits        ;不相等就循环等待

5.读数据

从硬盘读取数据,通过0x1f0端口,这是一个16位寄存器。硬盘是典型的块设备,所以一次必须读取512字节,或者它的倍数。比如,我们要读取一个扇区的数据,并存放在ds:0开始的内存空间:

mov dx,0x1f0    mov cx,256    ;读取一个扇区,512字节,即256字    xor bx,bxread_word    in ax,dx    mov [bx],ax    add bx,2    loop read_word

6.检查用户程序是否读取完整。

刚才我们只读取了一个扇区,即512字节,我们并不能确定用户程序是否已经完全读完,但是我们已经读取了用户程序的头部,这里我们规定用户程序头部的第一个双字必须定义用户程序的长度.所以我们从ds:0的位置读取两个字,第一个字放在ax,第二个字放在dx,这样dx:ax代表了用户程序的总长度,用这个数除以512得到商和余数,可以知道用户程序的读取进度,进而来决定是继续读取还是跳转到后边的步骤(重定位)。
注意:
由于一个逻辑段最大是64kb,从0x0000-0xffff,但是用户程序可能超过这个范围,为了避免这种事情发生,我们每读一个扇区,便把段地址加0x20(512),这样便可以连续存放且不用担心超过逻辑段大小。

;检查用户程序是否读取完整    xor bx,bx    mov ax,[bx]    mov dx,[bx+2]    mov bx,512    div bx        cmp dx,0    jne cmp_ax    dec ax    cmp_ax:    cmp ax,0    je  redirect_entry    ;跳转到重定位;读取剩余扇区    push ds        mov cx,ax    mov si,start_sector    ;start_sector是定义的一个常数,这里等于100(用户程序在100扇区开始)    read_rest:    mov ax,ds    add ax,0x20    mov ds,ax    inc si    call read_disk    loop read_rest        pop ds

二.重定位用户程序

用户程序在编写的时候都是分段的,重定位的目的便是确定每个段的实际段地址。

这里我们规定用户程序头部中定义了每个段的段首位置(汇编地址),转换成16位的段地址并重新写入。

;重定位用户程序;重定位用户入口点的段地址redirect_entry:    mov ax,[0x06]    ;读取头部入口点地址信息    mov dx,[0x08]    call calc_seg_base    mov [0x06],ax    ;重定位其他段的段地址    mov cx,[0x0a]    mov bx,0x0credirect_other_seg:    mov ax,[bx]    mov dx,[bx+2]    call calc_seg_base    mov [bx],ax    add bx,4    loop redirect_other_seg;过程计算段地址;已知dx:ax物理地址,求出段地址并存放在ax中返回    calc_seg_base:    push dx        add ax,[cs:usr_app_base]    adc dx,[cs:usr_app_base+2]    shr ax,4    ror dx,4    and dx,0xf000    or  ax,dx        pop dx        ret

三.将控制权交给用户程序

通过jmp far 命令进行段间跳转,这里是,跳转到重定位后的入口点位置,存放在ds:0x04处

jmp far [0x04]

至此,我们的加载器就基本完成了,可以用它来加载任何符合我们规定格式的用户程序(用户程序头部)。

转载地址:http://wbeao.baihongyu.com/

你可能感兴趣的文章
Excel导入数据库,兼容Excel2003,2007
查看>>
如何创建只读权限oracle账户
查看>>
性能测试--测试流程、APDEX、linux性能知识
查看>>
win7安装virtualbox遇到的问题
查看>>
新闻发布项目——业务逻辑层(UserServiceImpl)
查看>>
PHP开发中比较容易记错的几个重要知识
查看>>
两个科幻短篇观后感
查看>>
C# 随机数类
查看>>
C# 数据类型及转换
查看>>
Linux 远程和本地的一些解决方式
查看>>
如何判断手机收有几张SIM卡
查看>>
repo简介
查看>>
转行啦,转几本.net的书。。。
查看>>
运算符 and or ont
查看>>
对于快速排序算法的递归栈深度的一点改进
查看>>
矩阵乘法2(codevs3147)
查看>>
如何修复Kindle频繁自动锁屏和解锁
查看>>
MongoDB在Windows下安装配置
查看>>
CAFFE安装(3):cuDNN v4
查看>>
hdu 1016 Prime Ring Problem
查看>>