• 《Orange‘s 一个操作系统的实现》第五章


    Linux 下用汇编

    编写 hello.asm

    [section .data] ; 数据段
    
    strHello    db      "Hello, world!", 0Ah
    STRLEN      equ     $ - strHello
    
    [section .text] ; 代码段
    
    global      _start ; 我们必须导出 _start 这个入口,以便让链接器识别
    
    _start:
        mov     edx, STRLEN
        mov     ecx, strHello
        mov     ebx, 1
        mov     eax, 4          ; sys_write
        int     0x80            ; 系统调用
        mov     ebx, 0
        mov     eax, 1          ; sys_exit
        int     0x80            ; 系统调用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    编译链接

    $ nasm -f elf hello.asm -o hello.o
    $ ld -s hello.o -o hello # 或许会报错,我这里是 ld -m elf_i386 -s hello.o -o hello
    ./hello
    
    • 1
    • 2
    • 3

    必须要定义入口点,且要通过 global 关键字将它导出,这样链接程序才能找到它。

    汇编与 C 一同使用

    foo.asm

    extern choose   ; extern 声明本文件外的函数
                    ; int choose(int a, int b)
    
    [section .data] ; 数据段
    
    num1st      dd 3
    num2nd      dd 4
    
    [section .text] ; 代码段
    
    global _start   ; 导出入口点,以便链接器识别
    global myprint  ; 导出这个函数,便于让 c 调用
    
    _start:
        ; 调用 choose(num1st, num2nd)
        push    dword [num2nd]
        push    dword [num1st]
        call    choose
        add     esp, 8
    
        mov     ebx, 0
        mov     eax, 1      ; sys_write
        int     0x80        ; 系统调用
    
    ; void myprint(char* msg, int len)
    myprint:
        mov     edx, [esp + 8]      ; len
        mov     ecx, [esp + 4]      ; msg
        mov     ebx, 1
        mov     eax, 4              ; sys_write
        int     0x80                ; 系统调用
        ret
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    bar.c

    void myprint(char* msg, int len);
    
    int choose(int a, int b) {
        if(a >= b)
            myprint("the 1st one\n", 13);
        else
            myprint("the 2nd one\n", 13);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    编译链接

    $ nasm -f elf -o foo.o foo.asm
    $ gcc -c -o bar.o bar.c # 若平台是 64 位,则默认会以 64 位生成目标文件
    # $ gcc -m32 -c bar.c -o bar.o # 上面一条的解决方案,指定 32 位生成目标文件
    $ ld -m elf_i386 -s -o foobar foo.o bar.o # 指定架构生成
    ./foobar
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:

    1. 在 C 中用到汇编中的函数,需要在汇编中使用 global 关键字将其导出。
    2. 汇编用到本文件之外的函数,需要使用 extern 关键字声明。
    3. 无论是汇编还是 C,都遵循 C 的调用约定,后面的参数先入栈,并由调用者清理堆栈。

    ELF

    简介

    在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的文件格式。

    组成

    ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。

    在这里插入图片描述

    ELF header

    #define EI_NIDENT 16
    typedef struct{
        unsigned char e_ident[EI_NIDENT];
        Elf32_Half e_type;
        Elf32_Half e_machine;
        Elf32_Word e_version;
        Elf32_Addr e_entry;
        Elf32_Off e_phoff;
        Elf32_Off e_shoff;
        Elf32_Word e_flags;
        Elf32_Half e_ehsize;
        Elf32_Half e_phentsize;
        Elf32_Half e_phnum;
        Elf32_Half e_shentsize;
        Elf32_Half e_shnum;
        Elf32_Half e_shstrndx;
    } Elf32_Ehdr;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    数据类型大小对齐用途
    Elf32_Addr44无符号程序地址
    Elf32_Half22无符号中等大小整数
    Elf32_Off44无符号文件偏移
    Elf32_Sword44有符号大整数
    Elf32_Word44无符号大整数
    unsigned char11无符号小整数

    最开头是16个字节的e_ident, 其中包含用以表示ELF文件的字符,以及其他一些与机器无关的信息。开头的4个字节值固定不变,为0x7f和ELF三个字符。

    • e_type:它标识的是该文件的类型。
    • e_machine:表明运行该程序需要的体系结构。
    • e_version:表示文件的版本。
    • e_entry:程序的入口地址。
    • e_phoff:表示Program header table 在文件中的偏移量(以字节计数)。
    • e_shoff:表示Section header table 在文件中的偏移量(以字节计数)。
    • e_flags:对IA32而言,此项为0。
    • e_ehsize:表示ELF header大小(以字节计数)。
    • e_phentsize:表示Program header table中每一个条目的大小。
    • e_phnum:表示Program header table中有多少个条目。
    • e_shentsize:表示Section header table中的每一个条目的大小。
    • e_shnum:表示Section header table中有多少个条目。
    • e_shstrndx:包含节名称的字符串是第几个节(从零开始计数)。

    在这里插入图片描述

    第一行(红线部分):e_type,其中开头四个字节(黄色字体)是固定不变的。

    第二行,分别是:e_machine(0x0002)、e_version(0x0003)、e_entry(0x08049000),以此类推。

    Program Header

    Program header描述的是一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小。

    看前一节的图,得到 Program header table 在文件中的偏移量(e_phoff)为 0x34,而 ELF header 大小(e_ehsize)也是 0x34,可见 ELF header 后面紧跟着就是 Program header table。

    typedef struct {
        Elf32_Word p_type;   // 0
        Elf32_Off p_offset;  // 4
        Elf32_Addr p_vaddr;  // 8
        Elf32_Addr p_paddr;  // 12, 0Ch
        Elf32_Word p_filesz; // 14, 0Eh
        Elf32_Word p_memsz;  // 18, 12h
        Elf32_Word p_flags;  // 22, 16h
        Elf32_Word p_align;  // 26, 1Ah
    } Elf32_Phdr;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • p_type:当前Program header所描述的段的类型。

      在这里插入图片描述

    • p_offset:段的第一个字节在文件中的偏移。

    • p_vaddr:段的一个字节在内存中的虚拟地址

    • p_paddr:在物理内存定位相关的系统中,此项是为物理地址保留。

    • p_filesz:段在文件中的长度。

    • p_memsz:段在内存中的长度。

    • p_flags:与段相关的标志。

    • p_align:根据此项值来确定段在文件及内存中如何对齐。

    这些信息可以帮助我们把文件加载进内存。

    从 Loader 到内核

    Loader 的工作:

    • 加载内核到内存
    • 跳入保护模式

    用 Loader 加载 ELF

    加载内核到内存,和加载 Loader 进内存是一样的,区别在于把内核加载进内存需要根据 Program header table 中的值把内核中相应的段放到正确的位置。

    代码和之前 Loader 几乎一样,但多了个 KillMotor 函数,作用是关闭软驱马达,不然软驱的灯会一直亮着。

    区别:

    LABEL_FILENAME_FOUND:   ; 找到 KERNEL.BIN 后,便来到这里。
    	; ...
    	
        push    eax                         ;| 
        mov     eax, [es:di + 01Ch]         ;| // 这些都是新增加的
        mov     dword [dwKernelSize], eax   ;| // 保存 KERNEL.BIN 的文件大小
        pop     eax                         ;| 
    	
    	; ...
    LABEL_GOON_LOADING_FILE:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    ; 关闭软驱马达
    KillMotor:
        push    dx
        mov     dx, 03F2h
        mov     al, 0
        out     dx, al
        pop     dx
        ret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    写一个算不上内核的内核雏形:kernel.asm

    [section .text] ; 代码段
    
    global _start
    
    _start:
        mov     ah, 0Fh
        mov     al, 'K'
        mov     [gs:((80 * 1 + 39) * 2)], ax
        jmp     $
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    编译运行:

    $ nasm -f elf -o kernel.o kernel.asm
    $ nasm loader.asm -o loader.bin
    ld -m elf_i386 -s -o kernel.bin kernel.o
    mount -o loop a.img /mnt/floppy/
    cp kernel.bin /mnt/floppy/ -v
    cp loader.bin /mnt/floppy/ -v
    umount /mnt/floppy/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行效果参考:P131 - 图 5.4

    跳入保护模式

    loader.ams —— GDT

    ; =================================================================
    ; GDT
    ;                           段地址        段界限   属性
    LABEL_GDT:          Descriptor 0,             0, 0
    LABEL_DESC_FLAT_C:  Descriptor 0,       0fffffh, DA_CR | DA_32 | DA_LIMIT_4K
    LABEL_DESC_FLAT_RW: Descriptor 0,       0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K
    LABEL_DESC_VIDEO:   Descriptor 0B8000h,  0ffffh, DA_DRW | DA_DPL3
    
    GdtLen      equ     $ - LABEL_GDT                   ; GDT 长度
    GdtPtr      dw      GdtLen - 1                      ; 段界限
                dd      BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址
    
    ; GDT 选择子
    SelectorFlatC       equ     LABEL_DESC_FLAT_C   -   LABEL_GDT
    SelectorFlatRW      equ     LABEL_DESC_FLAT_RW  -   LABEL_GDT
    SelectorVideo       equ     LABEL_DESC_VIDEO    -   LABEL_GDT + SA_RPL3
    ; =================================================================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    loader.asm 的 32 位代码段 —— 进入保护模式后要执行的代码

    ; ==================================================================================
    ; 保护模式
    ; 32 位代码段,由实模式跳入
    [SECTION .s32]
    
    ALIGN   32
    
    [BITS   32]
    
    LABEL_PM_START:
        mov     ax, SelectorVideo 
        mov     gs, ax ; 初始化 gs 寄存器
    
        ; 初始化各个寄存器
        mov     ax, SelectorFlatRW
        mov     ds, ax
        mov     es, ax
        mov     fs, ax
        mov     ss, ax
        mov     esp, TopOfStack
    	
    	; --------------------------------
    	; 这一块是做准备启动分页机制的工作
        push    szMemChkTitle
        call    DispStr
        add     esp, 4
    
        call    DispMemInfo ; 打印内存信息
        call    SetupPaging ; 开启分页机制
    	; /--------------------------------
    
        mov     ah, 0Fh     ; 黑底白字
        mov     al, 'P'
        mov     [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行,第 39 列
        ;jmp     $
    
        call    InitKernel
    
        ;**************************************************************
        jmp     SelectorFlatC:KernelEntryPointPhyAddr   ; 正式进入内核
        ; 这行代码是在完成“重新放置内核”后添加的
        ;**************************************************************
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    Loader.asm —— 正式进入保护模式

    LABEL_FILE_LOADED:
        call    KillMotor   ; 关闭软驱马达
    
        mov     dh, 1       ; "Ready."
        call    DispStrRealMode     ; 显示字符串
    
        ; 下面准备跳入保护模式
        
        lgdt    [GdtPtr]    ; 加载 GDTR
    
        cli                 ; 关中断
    
        ; 打开地址线 A20
        in      al, 92h
        or      al, 00000010b
        out     92h, al
    
        ; 开启保护模式的开关
        mov     eax, cr0
        or      eax, 1
        mov     cr0, eax
    
        ; 跳入保护模式
        jmp     dword SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    通过 DispMemInfo 函数(该函数在这里就不放出来了)得到内存信息,接着就开始编写启动分页机制的代码;

    ;---------------------------------------------------------------
    ; 函数名:SetupPaging
    ;---------------------------------------------------------------
    ; 功能:启动分页机制
    ;---------------------------------------------------------------
    SetupPaging:
        ; 根据内存大小计算应初始化多少 PDE 以及多少页表
        xor     edx, edx
        mov     eax, [dwMemSize]
        mov     ebx, 400000h        ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
        div     ebx
        mov     ecx, eax            ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
        test    edx, edx
        jz      .no_remainder
        inc     ecx                 ; 若余数不为 0,就增加一个页表
    .no_remainder:
        push    ecx                 ; 暂存页表个数
    
        ; 为简化处理,所有线性地址对应相等的物理地址,并且不考虑内存空洞
    
        ; 首先 初始化页目录
        mov     ax, SelectorFlatRW
        mov     es, ax
        mov     edi, PageDirBase
        xor     eax, eax
        mov     eax, PageTblBase | PG_P | PG_USU | PG_RWW ; 构建 PDE
    .1:
        stosd
        add     eax, 4096
        loop    .1
    
        ; 再初始化所有页表
        pop     eax                 ; 页表个数
        mov     ebx, 1024           ; 每个页表 1024 个 PTE
        mul     ebx
        mov     ecx, eax            ; PTE 个数 = 页表 * 1024
        mov     edi, PageTblBase    ; 此段首地址为 PageTblBase
        xor     eax, eax
        mov     eax, PG_P | PG_USU | PG_RWW ; 构建 PTE
    .2:
        stosd
        add     eax, 4096           ; 每一页都指向 4K 的空间
        loop    .2
    
        mov     eax, PageDirBase
        mov     cr3, eax            ; 将页目录的物理地址保存到 CR3 寄存器中
        mov     eax, cr0
        or      eax, 80000000h      ; 设置 CR0 的最高 PG 位为 1,开启分页机制
        mov     cr0, eax
        jmp     short .3
    .3:
        nop
    
        ret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    运行结果参考:P138 - 图 5.7

    重新放置内核

    我们的任务:根据内核的 Program header table 的信息将所有的 Program Header 复制到内存的正确位置中。

    ;---------------------------------------------------------------
    ; 函数名:InitKernel
    ;---------------------------------------------------------------
    ; 功能:将 KERNEL.BIN 的内容经过整理对其后放到新的位置
    ; 遍历每个 Program Header,根据 Program Header 中的信息
    ; 来确定把什么放进内存,放到什么位置,以及放多少。
    ;---------------------------------------------------------------
    InitKernel:
        xor     esi, esi
        mov     cx, word [BaseOfKernelFilePhyAddr + 2Ch] ; ecx <- ELFheader.e_phnum
        movzx   ecx, cx                                  ; 将 cx 扩展到 32 位,赋值给 ecx
        mov     esi, [BaseOfKernelFilePhyAddr + 1Ch]     ; esi <- ELFheader.e_phoff
        add     esi, BaseOfKernelFilePhyAddr             ; esi <- OffsetOfKernel + ELFheader.e_phoff
                                                         ; 此时 esi 以及构成了一个物理地址
    .Begin:
        mov     eax, [esi + 0]
        cmp     eax, 0                          ; 判断该段是否可用,即该段类型是否位 PT_NULL
        jz      .NoAction
        push    dword [esi + 010h]              ; size  ;\
        mov     eax, [esi + 04h]                        ; |
        add     eax, BaseOfKernelFilePhyAddr            ; | memcpy((void*)(PHeader.p_vaddr), uchCode + PHeader.p_offset, PHeader.p_filesz)
        push    eax                             ; src   ; |
        push    dword [esi + 08h]               ; dst   ;/
        call    MemCpy
        add     esp, 12
    .NoAction:
        add     esi, 020h                       ; esi += ELFheader.e_phentsize 指向下一个 Program Header
        dec     ecx
        jnz     .Begin
    
        ret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Tips: 其中 MemCpy 函数是模仿 C 中的 memcpy() 编写的,用于内存复制。

    内核扩充

    切换堆栈和 GDT

    kernel.asm

    SELECTOR_KERNEL_CS      equ     8
    
    ; 导入函数
    extern cstart
    ; 导入全局变量
    extern gdt_ptr
    
    [SECTION .bss]
    StackSpace      resb    2 * 1024
    StackTop: ; 栈顶
    
    [section .text] ; 代码段
    
    global _start ; 导出入口点
    
    _start:
        ; 把 esp 从 LOADER 挪到 KERNEL
        mov     esp, StackTop   ; 堆栈在 bss 段中
    
        sgdt    [gdt_ptr]       ; cstart() 中将会用到 gdt_ptr
        call    cstart          ; 在此函数中改变了 gdt_ptr,让它指向新的 GDT
        lgdt    [gdt_ptr]       ; 使用新的 GDT
    
        jmp     SELECTOR_KERNEL_CS:csinit
    csinit:
        push    0
        popfd   ;
    
        hlt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    start.c

    #include "type.h"
    #include "const.h"
    #include "protect.h"
    
    PUBLIC void* memcpy(void* pDst, void* pSrc, int iSize);
    
    PUBLIC void* disp_str(char* pszInfo);
    
    PUBLIC u8           gdt_ptr[6];     // 0~15:Limit   16~47:Base
    PUBLIC DESCRIPTOR   gdt[GDT_SIZE];
    
    PUBLIC void cstart() {
    
        disp_str("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                     "-----\"cstart\" begins-----\n");
    
        // 将 LOADER 中的 GDT 复制到新的 GDT 中
        memcpy(&gdt, (void*)(*((u32*)(&gdt_ptr[2]))), *((u16*)(&gdt_ptr[0])) + 1);
        // gdt_ptr[6] 共 6 个字节:0~15:Limit   16~47:Base。用作 sgdt/lgdt 的参数。
        u16* p_gdt_limit = (u16*)(&gdt_ptr[0]);
        u32* p_gdt_base  = (u32*)(&gdt_ptr[2]);
        *p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1;
        *p_gdt_base  = (u32)&gdt;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    函数介绍: void *memcpy(void*dest, const void *src, size_t n);

    作用: 由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内,是用指针进行操作的。

    Makefile 整理代码文件,使其结构化

    我懒了…

    添加中断处理

    需要完成的工作:

    1. 设置 8259A。
    2. 建立 IDT。

    各种头文件:

    • const.h:添加 8259A 的端口。
    • protecth:添加中断向量、描述符等。
    • proto.h:函数声明。
    • type.h:添加函数指针。

    设置 8259A: kernel\i8259A.c 使用 C 语言完成初始化。

    初始化 IDT: start.c

    PUBLIC void cstart() {
    	...
        // idt_ptr[6] 共 6 个字节:0~15:Limit   16~47:Base。用作 sgdt/lgdt 的参数。
        u16* p_idt_limit = (u16*) (&idt_ptr[0]);
        u32* p_idt_base = (u32*) (&idt_ptr[2]);
        *p_idt_limit = IDT_SIZE * sizeof(GATE) - 1;
        *p_idt_base = (u32) &idt;
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    先设置 IDT,然后接下来在 kernel.asm 中加载 IDT。

    _start:
    	...
        lidt    [idt_ptr]       ; 注册 IDT
    	...
    
    • 1
    • 2
    • 3
    • 4

    现在 IDT 已经加载完毕,但其内容是空的,因此需要进行填充。

    /**
     * 初始化 IDT 描述符
     * parms:编号、描述符类型、对应的中断例程、特权级
     */
    PRIVATE void init_idt_desc(unsigned char vector, u8 desc_type, int_handler handler, unsigned char privilege) {
        GATE * p_gate = &idt[vector];
        u32 base = (u32) handler;
        p_gate -> offset_low = base & 0xFFFF;
        p_gate -> selector = SELECTOR_KERNEL_CS;
        p_gate -> dcount = desc_type | (privilege << 5);
        p_gate -> attr = desc_type | (privilege << 5);
        p_gate -> offset_high = (base >> 16) & 0xFFFF;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    PUBLIC void init_prot() {
    	// ...
        init_idt_desc(INT_VECTOR_IRQ0 + 0,      DA_386IGate,
                      hwint00,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 1,      DA_386IGate,
                      hwint01,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 2,      DA_386IGate,
                      hwint02,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 3,      DA_386IGate,
                      hwint03,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 4,      DA_386IGate,
                      hwint04,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 5,      DA_386IGate,
                      hwint05,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 6,      DA_386IGate,
                      hwint06,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ0 + 7,      DA_386IGate,
                      hwint07,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 0,      DA_386IGate,
                      hwint08,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 1,      DA_386IGate,
                      hwint09,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 2,      DA_386IGate,
                      hwint10,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 3,      DA_386IGate,
                      hwint11,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 4,      DA_386IGate,
                      hwint12,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 5,      DA_386IGate,
                      hwint13,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 6,      DA_386IGate,
                      hwint14,                  PRIVILEGE_KRNL);
    
        init_idt_desc(INT_VECTOR_IRQ8 + 7,      DA_386IGate,
                      hwint15,                  PRIVILEGE_KRNL);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    异常

    ld: i386 architecture of input file ‘hello. o’ is incompatible with i386:x86-64 output

    查看架构:

    $ ld -m elf
    ld: 无法辨认的仿真模式: elf
    支持的仿真: elf_x86_64 elf32_x86_64 elf_i386 elf_iamcu elf_l1om elf_k1om i386pep i386pe
    
    • 1
    • 2
    • 3

    根据选择对应架构开始链接:

    $ ld -m elf_i386 -s hello.o -o hello
    
    • 1
  • 相关阅读:
    【gflags】【gflags实践】【gflags的学习使用记录】
    MyBatis配置C3P0连接池
    一个注解解决ShardingJdbc不支持复杂SQL
    ssm杏坛女子书院网站毕业设计源码
    每日刷题记录 (六)
    【AGC】如何快速部署Serverless抽奖模板
    JavaScript Date对象中的常用方法有哪些?
    SFI立昌Ultra Low Capacitance方案与应用
    控制并发流程
    linux进程杀不死
  • 原文地址:https://blog.csdn.net/qq_43098197/article/details/126777854