ASM-保护模式下编程

保护模式下编程

1.在保护模式下32位CPU仍然可以用20位地址来实现32位地址线寻址

16位CPU: 16位段寄存器+16位偏移地址 (经地址加法器) -> 20位物理内存地址 32位CPU: 32位地址的内存段信息存入在一张内存表中,只需将表的索引存入16寄存器当中即可 保存表中索引的段寄存器称为:段选择子 表中每个表示32位内存段信息称为:段描述符(保存了段的地址和段的长度)。
整张表称为:段描述符表 段选择子16位,其中高13位用来表示描述符表中的索引,其低3位用表示段描述符中所指向的段描述符的属性

asm

;启动程序在屏幕中央打印一行字符串
[BITS 16]
org 07c00h    ;指明程序开始地址是07c00h,而不是原来 的00000
;int 汇编指令    int 10h
jmp main
gdt_table_start:    ;告诉编译器段描述符开始
    ;Intel规定描述符表的第一个描述符必须是空描述符
    gdt_null:
        dd 0h
        dd 0h    ;Intel规定段描述符表的第一个表项必须为0
    gdt_data_addr    equ    $-gdt_table_start    ;数据段的开始位置
    gdt_data    ;数据段描述符
        dw 07ffh ;段界限
        dw 0h    ;段基地址18位
        db 10010010b    ;段描述符的第六个字节属性(数据段)
        db 1100000b    ;段描述符的第七个字节属性
        db 0    ;段描述符的最后一个字节也就是段基地址
    gdt_video_addr equ $-gdt_table_start
    gdt_video:    ;用来描述显存地址空间的段描述符
        dw    0FFH    ;显存段界限就是1M
        dw    8000H
        db    0BH
        db    10010010b
        db    11000000b
        db    0
    gdt_code_addr    equ    $-gdt_table_start    ;代码段的开始位置
    gdt_code:
        dw 07ff    ;段界限
        dw 1h    ;段基地址0~18位
        db 80h    ;段基地址19~23位
        db 10011010b    ;段描述符的第六个字节(代码段)
        db 11000000b    ;段描述符的第七个字节
        db 0            ;段基地址的第二部分
gdt_table_end:

asm

;通过lgdt指令可以把GDTR描述表的大小和起始地址存入gdtr寄存器中
gdtr_addr:
    dw gdt_table_end-gdt_table_start-1    ;段描述表长度
    dd gdt_table_start    ;段描述表基地址
;lgdt [gdtr_addr]    ;让CPU读取gdtr_addr所指向内存内容保存到gdtr寄存器当中
;A20地址线,切换到保护模式时,A20地址线必须开启。地址回绕作用,放弃32位CPU地址线的高12位。?
;端口的读写操作:
    ;in     accume port    ;将端口的内容读到寄存器AL或AX当中,其中accume只能是AL或AX。
    ;out port accume    ;将accume中的内容写到端口中,这里accume可以是其它寄存器
;开启A20地址线
main:
    ;修改数据段描述跟段基地址有关的字节,初始化数据段描述符的基地址
    xor eax,eax    ;清空eax
    add eax,data_32    ;将32位地址信息拷贝到eax中
    mov word [gdt_data+2],ax    ;把ax中的内容拷贝到段描述符的第3、4两个字节当中,因是word类型的拷贝?
    shr eax,16    ;右移16位
    mov byte [gdt_data+4],al    ;将先前eax中的第5个字节移到段描述符当中
    mov byte [gdt_data+7],ah    ;将先前eax中的第8个字节移到段描述符当中
    ;修改代码段描述跟段基地址有关的字节,初始化数据段描述符的基地址
    xor eax,eax
    add eax,code_32
    mov word [gdt_code+2]
    shr eax,16
    mov byte [gdt_code+4],al
    mov byte [gdt_code+7],ah
    ;在转放保护模式之前,必须废除原来的中断向量表,用cli指令可以废除实模式下的中断向量表
    cli
    lgdt [gdtr_addr]    ;让CPU读取gdtr_addr所指向内存内容保存到gdtr寄存器当中
    enable_a20:
        in al,92h    ;只要往0x92号端口中写入信息就可以开启A20地址线
        or al,00000010b    ;00000010表示开启A20地址线的数据
        out 92h,al        ;把设置好的数据写进0x92号端口当中
;转入保护模式,只要将CR0寄存器的第1位(PE位)置为1即可

asm

;80386提供了4个32位的控制寄存器CR0~CR3,其中CR0中的某些位是用来标志是否要进入保护模式
;CR1寄存器保留没有被使用
;CR2和CR3用于分页机制
;CR0的PE位控制分段管理机制,PE=0,CPU运行于实模式;PE=1,CPU运行于保护模式
;CR0的PG位控制分段管理机,PG=0,禁止分页管理机制;PG=1,启用分页管理机制。
    mov eax,cr0
    or eax,1    ;用于把CR0寄存器的第1置为1
    mov cr0,eax    ;把CR0寄存器的第1置为1
;跳转到保护模式中
    jmp gdt_code_addr:0
;在保护模式下编程(在屏幕中央打印hello world)
[BITS 32]
    data_32:
        db    "hello world"
    code_32:
        MOV ax,gdt_data_addr
        mov ds,ax
        mov ax,gdt_video_addr
        mov gs,ax
        mov cx,11
        mov edi,(80*10+12)*2    ;在屏幕中央显示
        mov bx,0
        mov ah,0ch
        s:mov al,[ds:bx]
        mov [gs:edi]
        mov [gs:edi+1],ah
        inc bx
        add edi,2
        loop s
        jmp $    ;死循环
        times 510-($-$$)    db 0
        dw 0aa55h

ok ! ^_^.

1.启动虚拟机,用nasm boot.asm -o boot.bin 编译 2.把程序写到软盘镜像里去,用编译好的写入文件程序写入: ./write_image boot.bin boot.img 3.将boot.img复制到自己在Bochs-2.4.6目录下建的文件夹下,并修改run.bat


其中write_image.c,可以在rad hat的vi编辑器这样写:

#include<stdio.h>
#include<fcnt.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char *argv[])
{
    int fd_source;
    int fd_dest;
    int read_count=0;
    char buffer[512]={0};
    fd_source=open("boot.bin",O_RDONLY);
    IF(fd_source<0)
    {
        perror("open boot.bin error");
        return 0;
    }
    fd_dest=open("virtual_floppy.vfd",O_WRONLY);
    while ((read_count=read(fd_source,buffer,512))>0)
    {
    write(fd_dest,buffer,read_count);
    memset(buffer,0,512);
    }
    printf("wrinte image OK !");
    return 0;
}

保存成write_image.c,然后编译:gcc write_image.c -o write_image. 用虚拟软盘制作工具,制作一个虚拟软盘(取名boot.img),最后再用它将引导程序写入boot.img.

^_^ ok!
启动程序有问题的话,可以用bocsh虚拟机对操作系统进行调试。

bocsh的调试功能bocshdbg

continue(c) 程序继续运行直到遇到断点为止 step(s) 音步跟踪 vbreak(vb) 在虚拟地址上设置一个断点 pbreak(b) 在物理地址上设置一个断点 lbreak(lb) 在线性地址上设置一个断点 disassemble 反汇编指令

2.一个在屏幕中央显示一行字符串的引导程(实模式下写)

;启动程序在屏幕中央打印一行字符串

org 07c00h    ;指明程序开始地址是07c00h,而不是原来 的00000
;int 汇编指令    int 10h
    mov ax,cs
    mov es,ax
    mov bp,msgstr    ;es:bp指向的内容就是我们要显示的字符串地址?
    mov cx,12    ;字符串长度
    mov dh,12    ;显示起始行号
    mov dl,36    ;显示的列号
    mov bh,0    ;显示的页数,在第0页显示
    mov al,1    ;串结构
    mov bl,0c    ;黑底红字
    msgstr: db "hello my os"
    int 10h        ;BIOS中断
    times 510-($-$$) db 0 ;重复N次每次填充值为0
    ;因为BIOS的第一个扇区是512字节,当最后两字节是55AA时,它就是引导程序?
    dw 55aaH
    jmp $    ;为了不让程序结束,设置一个死循环,不断跳转到当前位置

在Linux操作系统下,用nasm 进行编译,命令:# nasm boot.asm -o boot.bin
用 ndisasm boot.bin 可以进行反编译.

参考自大灰狼之视频教程《零基础汇编汇编视频课程》.

版权所有,转载请注明出处 luowei.github.io.