程序加载器

仅在16位上有效,给别人写的.

作用:模拟如何加载一个APP

实际还要检索内存空间,这里省去

第2个被加载程序,修改了中断

加载器:


;读取硬盘端口 [读取硬盘这部分可直接忽略]
	;0x1f0 数据端口
	;0x1f1 错误端口
	;0x1f2 设置扇区数量
	;0x1f3 ~ 0x1f6 [ 0x1f6 中只占4 bit] 共28bit
	;0x1f6 高4位 1110 , 111 -> LBA, 0 -> 主盘
	;0x1f7 状态控制端口
		; 写入 0x20 -> 准备读取,
		; 读取后 and 0x88 , 如果等于 0x08则说明可以读取,否则等待
	;这部分汇编可以忽略 , 具体看read_file

;被加载程序,从0x10000开始加载,所有段按16字节对齐
;被加载程序头部相当于简化版 PE/elf头
; read_file 读取硬盘,加载到内存
; read_header 读取首个扇区,来确定有N个扇区要读
; 当被加载程序全部加载完后, 需要确认所有段的段地址(+0x10000), 否则无法跳转至入口点(aop)
; 最后跳转至程序的入口开始执行被加载程序




APP_SECTOR_START_HIGH equ 0 
APP_SECTOR_START_LOW equ 100 ;扇区起始位置



; vstart=0x7c00 , 标号从0x7c00 开始算
section app_load align=16 vstart=0x7c00
	jmp far [section_addr]
	section_addr dw start, section.app_load.start
	app_load_addr dd 0x10000

start:	
	mov ax,cs
	mov ds,ax
	mov ax,[app_load_addr]
	mov dx,[app_load_addr+2]
	mov cx,16
	div cx
	mov ds,ax ; 把程序加载到这个位置


	xor ax,ax
	mov ss,ax
	mov sp,ax

	;读取头
	call read_header
	
	;读取首个512字节的头,获取假定的PE头
	mov dx,[ds:2]
	mov ax,[ds:0]
	mov bx,512
	div bx
	cmp dx,0
	jne check_1
	; ax 剩余扇区
	dec ax 

	; 检查是否读完, 或加载剩余扇区
check_1:
	cmp ax,0
	je process

	mov cx,ax ; 剩余扇区
	xor ax,ax
	push ds

	
	;读取剩余扇区
read_left_sector:

	; 每次读取512字节, ds段地址每次加512 ( 0x20 << 4 )
	mov dx,ds
	add dx,0x20 
	mov ds, dx

	; 读取 扇区+1 的位置
	xor dx,dx
	inc ax
	add ax,APP_SECTOR_START_LOW
	adc dx,APP_SECTOR_START_HIGH


	push dx
	push ax
	call read_file
	add sp,4
	loop read_left_sector

	pop ds

;到此剩余扇区全部读完, 处理段地址
process:
	; 处理入口 , aop
	mov dx,[ds:0x08] 
	mov ax,[ds:0x06]
	push dx
	push ax
	call calc_each_segment_addr
	add sp,4

	; 写入aop段地址
	mov dword [ds:0x06], 0
	mov [ds:0x6],ax

	;获取N个重定位表,以及首个重定位表的地址
	mov cx,[ds:0xa] 
	mov si,0x0c
;处理其他段地址
process_left:
	mov dx,[si+0x2]
	mov ax,[si]
	push dx
	push ax
	call calc_each_segment_addr
	add sp,4
	mov dword [ds:si],0
	mov [ds:si],ax
	add si,4
	loop process_left

	;转到被加载的APP中, ds:0x4 获取被加载的段地址:偏移
	push ds
	push dx
	push cx
	call far [ds:0x04]
	pop cx
	pop dx
	pop ds

done:
	jmp done


;重新计算生成16位段地址  参数: [bp+6] 高位, [bp+4]低位 , ax 返回值
calc_each_segment_addr:
	push bp
	mov bp, sp

	; 重新计算被加载的段地址,ax低位相加,进位加到dx, 形成20位段地址
	mov ax,[bp+4]
	mov dx,[bp+6]
	add ax,[cs:app_load_addr]
	adc dx,[cs:app_load_addr+2]

	shr ax,4  ; >> 4 生成16位段地址
	shl dx,12	; 高位只保留4位
	and dx,0xf000 
	or ax,dx


	mov sp,bp
	pop bp
	ret

read_header:
	push APP_SECTOR_START_HIGH
	push APP_SECTOR_START_LOW
	call read_file
	add sp,4
	ret





;读取程序  [bp+2] -> ip , [bp+4] -> 低16位扇区, [bp+6] -> 高16 扇区
read_file:
	push bp
	mov bp, sp
	push ax
	push bx
	push cx
	push dx

	xor bx,bx ; 内存索引

; 设置每次读一个扇区
	mov dx,0x1f2
	mov al,1
	out dx,al

;dx=0x1f3
	inc dx 
	mov ax,[bp+2]
	out dx,al

;dx 0x1f4
	inc dx
	mov al,ah
	out dx,al

;dx 0x1f5
	inc dx
	mov ax,[bp+4]
	out dx,al

;dx 0x1f6
	inc dx
	mov al,0xe0
	and ah,0x0f
	or al,ah    ; 处理最后4位
	out dx,al
	
;设置读, dx 0x1f7
	inc dx
	mov al,0x20
	out dx,al

;开始等待硬盘响应,是否准备好
waiting_read:
	in al,dx
	and al,0x88
	cmp al,0x08
	jne waiting_read


	; 一次读一个扇区 256 * 2字节
	mov cx,256
	;设置读取端口
	mov dx,0x1f0 
	; bx 为数据段索引
begin_read:
	in ax,dx
	mov [ds:bx],ax
	add bx,2
	loop begin_read
	
	
	pop dx
	pop cx
	pop bx
	pop ax
	pop bp

	ret

times 510-($-$$) db 0
dw 0xaa55

被加载程序 :

; 被加载程序,从0x10000开始加载
;header段地址: 0x10000

section header vstart=0
    ;app字节
    app_len dd app_end                   ; [0]
    ;入口
    entry dw start                       ;[0x4]
          dd section.code_main.start  ;
    
    ;每个段重定位表长
    table_len   dd   (header_end-code_segment_main) /4  ;[0xa]

    ;函数段地址
    code_segment_main dd section.code_main.start  ; [0xc]
    ;函数段地址
    code_segment_func dd section.code_func.start  
    ;数据段地址
    data1 dd section.data1.start
    ;数据段地址
    data2 dd section.data2.start 
    ;stack段地址
    stack_segment dd section.stack.start 

    ;header段地址
    header_segment dd section.header.start

    header_end:

;定义stack
section stack align=16 vstart=0
    resb 128 ; times 128 db ?
stack_end:

;加载器最后一条指令 : call far [ds:0x04] 
;本程序其他段寄存器需要重置,否则还指向加载程序.
section code_main align=16 vstart=0

    start:
    ;保存加载器的ss,sp,用于返回
    mov dx,ss
    mov cx,sp

    mov ax,[ds:header_segment]     ;es 指向header段, [1000:header_segment]
    mov es,ax


    mov ax,[es:stack_segment] 
    mov ss,ax
    mov sp,stack_end
    ;保存原来的 ss,sp 在自己stack
    push dx
    push cx

    mov ax,[data1]      
    mov ds,ax


    ;retf => pop ip, pop cs
    ; 相当于 call far ( dw init_start, code_segment_func )
    
    push word [es:code_segment_func]
    mov ax,init_start
    push ax
    retf
    
done:
    ;返回到加载器
    pop cx
    pop dx
    mov ss,dx
    mov sp,cx
    retf
    
    


section code_func align=16 vstart=0
init_start:
    push bp
    mov bp,sp

    push word [es:code_segment_main]
    push word [es:code_segment_func]
    push word [es:data1]
    push word [es:data2]
    push word [es:stack_segment]
    push word [es:header_segment]

  
    mov sp,bp
    pop bp


    push word [es:code_segment_main]
    mov ax,done
    push ax
    retf



section data1 align=16 vstart=0
    msg1 db 'fuck',0

section data2 align=16 vstart=0
    msg2 db 'you',0x0d,0x0a,0

section tail align=16
app_end:

第二个被加载程序:



section header vstart=0
    app_len dd app_end ;[0x0]

    entry dw start 
          dd section.code_section.start ;[0x04]

;表个数
    table_len dw (header_end - code_segment) / 4 ;[0x0a]

    ; 重定位表
    code_segment dd section.code_section.start

    data_segment dd section.data_section.start

    stack_segment dd section.stack_section.start

    header_segment dd section.header.start


header_end:


section code_section vstart=0 align=16

start:
    ;保留加载器ss,sp
    mov dx,ss
    mov cx,sp

    ; es 指向 header段
    mov ax,[ds:header_segment]
    mov es,ax

    ; 初始化stack
    mov ax,[es:stack_segment]
    mov ss,ax
    mov sp,stack_section_end

    ; 初始化ds
    mov ax,[es:data_segment]
    mov ds,ax

    ; 把加载器ss,sp压入stack
    push dx
    push cx

    ;保存原来的0x70入口
    mov ax,0
    mov es,ax
    push word [es:0x70 * 4]
    pop word [ds:addr_off]
    push word [es:0x70 * 4 + 2]
    pop word [ds:addr_seg]

    ;设置新入口
    cli
    mov word [es:0x70 * 4], new_int_func
    mov word [es:0x70 * 4 + 2], cs

    ;设置RTC 周期每秒一次中断, 一秒一次中断  , 这段直接照抄,很麻烦
    mov al,0x0b                        ;RTC寄存器B
    or al,0x80                         ;阻断NMI 
    out 0x70,al
    mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更新结束后中断,BCD码,24小时制
    out 0x71,al                        

    mov al,0x0c
    out 0x70,al
    in al,0x71                         ;读RTC寄存器C,复位未决的中断状态

    ;开启RTC中断
    in al,0xa1                         ;读8259从片的IMR寄存器 
    and al,0xfe                        ;清除bit 0(此位连接RTC)
    out 0xa1,al                        ;写回此寄存器 

    sti

; 睡眠 , 直到中断发生
sleep_func:
    hlt
    jmp sleep_func


done:
    mov ax,[ds:addr_off]
    mov word [es:0x70 * 4],ax
    mov ax,[ds:addr_seg]
    mov word [es:0x70 *4 + 2],ax

    ;跳回加载器
    pop cx
    pop dx
    mov sp,cx
    mov ss,dx
    retf

new_int_func:
    push ax
    push bx
    push cx
    push dx
    push es

is_safe:
    in al,0x71                         ;读寄存器A
    test al,0x80                       ;测试第7位UIP 
    jnz is_safe       


    in al,0x71                         ;每次都要读一下RTC的寄存器C,否则只发生一次中断                     

    ;中断完成
    mov al,0x20                        ;中断结束命令EOI 
    out 0xa0,al                        ;从片
    out 0x20,al                        ;主片 


    pop es
    pop dx
    pop cx
    pop bx
    pop ax

    iret






code_section_end:


section data_section vstart=0 align=16
    addr_off dw 0
    addr_seg dw 0
data_section_end:

section stack_section vstart=0 align=16
    resb 256
stack_section_end:

section trail
app_end:


版权声明:本文为dashoumeixi原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。