模拟链接器 重定位符号表

模拟连接器创建文件头

user_app.asm 的头部模拟各种段选择子填充, 符号表的占位

app_core.asm 用于模拟加载程序

                        realloc_user_symbol 用来重定位符号表

                        load_user_app 用来加载用户程序

                        mk_gdt_d 建立一个新的描述符

                        add_gdt_d 新增一个描述符到GDT

                        

app_mbr.asm 用来加载一个加载器

摘要 user_app.asm 自定义构建头部

1.所有的段选择子将被加载器计算后填充

2.各个段的长度用于计算界限值

3.stack由加载器动态分配

4.符号表为了方便起见, 每个符号定长32字节,不足则用0填充字符串

5.符号地址表被用来填充加载器提供的函数( 系统函数 )

  每个系统函数占6个字节(偏移 低4字节, 段选择子 高2字节)

max_api_str_len equ 32  ;api字符串定长32字节
const_symbol_table_len equ (header_end-symbol_table)/max_api_str_len

        
        app_len dd app_end            ;程序长度,需要确认加载N个扇区

        header_len dd header_end       ;头部长度, 用于确定界限值,动态建立描述符

        ;头部段选择子需要此段选择子来定位符号表
        header_selector dd section.header.start

        stack_selector dd 0   ;stack段选择子,加载器来动态分配
        stack_len dd 1        ;stack长度,粒度4K

        entry dd start      ; aop 入口偏移地址

        ;用于计算基址,被计算后将修改成段选择子
        code_selector dd section.user_code.start    
        ;代码段长度 , 用于确定界限值
        code_len dd user_code_end

        ;数据区段选择子, 被计算后会修改成段选择子
        data_selector dd section.user_data.start
        ;长度
        data_len dd user_data_end               

        ;有几个符号需要解析
        symbol_table_len dd const_symbol_table_len

        ; 获取符号地址表的偏移地址
        symbol_addr_fetch dd symbol_addr

        ;符号表, 为了方便循环,每个符号最长32字节
        symbol_table:
        
        printf db 'printf'
                times max_api_str_len-($-printf) db 0
        
        exit_process db 'exit_process'
                times max_api_str_len-($-exit_process) db 0
        
        
        ;符号地址表
        symbol_addr:
                times const_symbol_table_len*6 db 0


header_end:

app_mbr.asm

CORE_ADDR equ 0x40000
CORE_SECTOR EQU 0x1
DEFAULT_GDT_OFFSET equ 0X28

mov eax,cs
mov ds,eax

;获取gdt 地址
mov eax,[gdt_addr + 0x7c00]
xor edx,edx
mov ebx, 16
div ebx

mov ds,eax
mov esi,edx

;设置描述符 index:0
mov dword [esi],0
mov dword [esi+4],0

;1, 数据段,基址:0, 界限:fffff, G=1
mov dword [esi+8],0x0000ffff
mov dword [esi+12],0x00cf9200

;2,代码段(MBR),基址:0x7c00, 界限:1ff , G=0 
mov dword [esi + 16],0x7c0001ff
mov dword [esi+20] , 0x00409800

;3, stack, 基址:0x7c00,界限: ffffe, G=1
mov dword [esi+24],0x7c00fffe
mov dword [esi + 28],0x00cf9600

;4,显存, 基址:0xb8000, 界限:7fff,G=0
mov dword [esi+32],0x80007fff
mov dword [esi +36],0x0040920b

mov word [cs:gdt_limit + 0x7c00],39
lgdt [cs:gdt_limit +0x7c00]

in al,0x92
or al,10b
out 0x92,al

cli

mov eax,cr0
or eax,1
mov cr0,eax

;16位下使用32位操作数 , 添加 0x66前缀
jmp dword 0x10:init


    [bits 32]
init:
;初始化ds , 数据段,索引1
xor eax,eax
mov eax,1000b
mov ds,eax

;stack
xor eax,eax
mov eax,11_000b
mov ss,eax
xor esp,esp

;把内核代码加载到CORE_ADDR , 从CORE_SECTOR扇区开始读取
mov esi,CORE_ADDR
mov ebx,esi
mov eax,CORE_SECTOR
call read_sector

;内核代码首4字节为程序长度 
;读取程序多大
mov eax,[esi]
xor edx,edx
mov ecx,512
div ecx

or edx,edx
jnz check
dec eax

check:
or eax,eax
jz read_done

;从下个扇区开始读
mov ecx,eax
mov eax,CORE_SECTOR
inc eax

;一直读完
process_left:
call read_sector
inc eax
loop process_left


read_done:

;初始化内核代码描述符
;0x4 , sys_func 段
;0x8 , core_data 段
;0xc, core_code 段
;0x10, app_entry 入口

;获取gdt表 , 增加内核代码描述符
mov esi,[0x7c00+gdt_addr]

;sys_func 代码段
mov edi,CORE_ADDR
mov eax,[edi+4]      ;sys_func偏移地址
mov edx,[edi+8]      ;core_data 偏移 
;sys_func段界限
sub edx,eax
dec edx
mov ebx,edx
;sys_func 基址
add eax,edi
mov ecx,0x00409800     ;sys_func 段属性, 可执行, G=0

;创建sys_func 描述符
call mk_gdt_d
mov [esi+DEFAULT_GDT_OFFSET],eax
mov [esi+DEFAULT_GDT_OFFSET+4],edx


; core_data 数据段
mov eax,[edi+8]      ; core_data 偏移
mov ebx,[edi+12]     ; core_code 偏移
sub ebx,eax
dec ebx              ; core_data 段界限
add eax,edi          ; core_data 基址
mov ecx,0x00409200     ; core_data 段属性, G=0
;安装core_data 数据段描述符
call mk_gdt_d
mov [esi + DEFAULT_GDT_OFFSET + 8],eax
mov [esi + DEFAULT_GDT_OFFSET + 12],edx

;core_code 代码段
mov eax,[edi+ 12]    ; core_code 偏移
mov ebx,[edi]        ; 程序总长度
sub ebx,eax
dec ebx              ; core_code 段界限
add eax,edi          ; core_code 基址
mov ecx,0x00409800     ; core_code 段属性,G=0

call mk_gdt_d
;安装core_code 代码段描述符
mov [esi+DEFAULT_GDT_OFFSET + 16],eax
mov [esi+DEFAULT_GDT_OFFSET + 20],edx

;修改GDT界限,重新加载gdt
mov word [0x7c00 + gdt_limit],8*8-1
lgdt [0x7c00 + gdt_limit]

; 从mbr转入内核代码, edi:基址, 偏移:0x10 为代码入口
; mbr 主要功能加载内核代码
jmp far [edi+0x10] ; jmp 0x38:[edi+0x10] ; cs:0x38 , eip:[edi+0x10]

;结束
done:
       hlt

;构建一个描述符
;eax gdt基址
;ebx gdt界限
;ecx gdt段属性
;返回 edx 高32 , eax 低32位
mk_gdt_d:
       mov edx, eax
       and edx,0xffff0000
       ; 基址低16位放入 eax 高16位
       shl eax,16
       ; eax 低16位 放入 段界限低16位
       or ax,bx

       ; 基址高8位 放入低8位,然后交换位置,构建出高16位段基址
       rol edx,8
       ;低8位跟高8位交换, 中间交换(中间都是0 无所谓)
       bswap edx
       ;处理高32位中 还有高4位的段界限
       xor bx,bx
       or edx,ebx

       ;最后加上段属性
       or edx,ecx

       ret


;eax : 扇区号, ds:ebx:输出缓冲区地址
read_sector:                       
                                         
         push eax 
         push ecx
         push edx
      
         push eax
         
         mov dx,0x1f2
         mov al,1
         out dx,al                       ;读取的扇区数

         inc dx                          ;0x1f3
         pop eax
         out dx,al                       ;LBA地址7~0

         inc dx                          ;0x1f4
         mov cl,8
         shr eax,cl
         out dx,al                       ;LBA地址15~8

         inc dx                          ;0x1f5
         shr eax,cl
         out dx,al                       ;LBA地址23~16

         inc dx                          ;0x1f6
         shr eax,cl
         or al,0xe0                      ;第一硬盘  LBA地址27~24
         out dx,al

         inc dx                          ;0x1f7
         mov al,0x20                     ;读命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                      ;不忙,且硬盘已准备好数据传输 

         mov ecx,256                     ;总共要读取的字数
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [ebx],ax
         add ebx,2
         loop .readw

         pop edx
         pop ecx
         pop eax
      
         ret


gdt_limit dw 0
gdt_addr dd 0x7e00

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

app_core.asm


    ;段选择子,固定值
    sys_func_selector equ 0x28     ;系统调用
    core_data_seletor equ 0x30     ;数据段
    core_code_seletor equ 0x38     ;代码段
    screen_selector equ 0x20       ;显存
    core_stack_selector equ 0x18   ;stack
    mem_selector equ 0x8           ;4G内存空间
    max_api_str_len equ 32         ; api定长32字节



    ;用户程序起始扇区号
    user_app_sector equ 50
    


    ;0x00 , 代码长度
    core_len dd core_length 

    ;下面3个段地址只在被加载时用来计算基址
    ;0x04 
    sys_func_seg_addr dd section.sys_func.start

    ;0x08
    core_data_seg_addr dd section.core_data.start

    ;0x0c
    core_code_seg_addr dd section.core_code.start

    ;0x10 , 代码入口点
    entry  dd start
            dw core_code_seletor

[bits 32]

section sys_func vstart=0

;eax 内核符号索引 
;es:edx 用户符号地址表
;ebx 用户符号索引
fill_user_symbol:
       push ds
       push esi
       push eax
       push ecx
       push edi

       ;ds:esi -> 内核符号地址表
       ;es:edi -> 用户符号地址表
       xor esi,esi
       mov esi, core_data_seletor
       xor edi,edi
       mov edi,edx

       mov ds,esi
       mov esi,symbol_addr
       ;获取内核系统函数偏移地址
       xor edx,edx
       mov ecx,6
       mul ecx
       push eax      ;保存当前地址
       mov ecx,[ds:esi+eax]

       ;填充用户表
       mov eax,ebx
       xor edx,edx
       mov ebx,6
       mul ebx
       push eax      ;保存当前地址
       mov [es:edi+eax],ecx

       ;段选择子
       pop ebx       ;用户地址
       pop eax       ;内核地址
       mov ax,[ds:esi+eax+2]
       mov [es:edi+ebx+2],ax

       pop edi
       pop ecx
       pop eax
       pop esi
       pop ds
       ret

;eax :用户符号表段选择子, ebx:符号表偏移 ecx N个符号需要被解析 edx 被填充偏移地址
realloc_user_symbol:
       push es
       push ds
       push edi
       
       ;es:edi 指向用户符号表
       mov es,eax
       mov edi,ebx

       mov eax,core_data_seletor
       mov ds,eax


       xor ebx,ebx   ;ebx作为用户符号表的索引
       xor eax,eax

begin_realloc:       
       ;入参: es:edi 
       call match_symbol
       cmp byte [bool_matched_symbol],0
       jz loop_next
       ;eax 返回内核索引,ebx 用户符号索引,es:edx 用户符号地址表
       call fill_user_symbol



loop_next:
       inc ebx
       add edi,max_api_str_len
       loop begin_realloc


       mov byte [bool_matched_symbol],0

       pop edi
       pop ds
       pop es
       retf

;内部使用,符号表匹配一次
cmp_symbol_once:
       push ecx
       cld
       xor eax,eax
       mov ecx,8
       repe cmpsd
       jnz not_match
       or eax,1
not_match:
       pop ecx
       ret

;入参 es:edi, 用户程序的符号表
;ds:esi 指向内核符号表
; eax 返回内核符号索引
match_symbol:
       push ds
       push esi
       push ecx
       push ebx

       ;开始比较字符串
       mov ebx,core_data_seletor
       mov ds,ebx
       mov ecx,symbol_table_len
       mov esi,symbol_table
       mov byte [bool_matched_symbol],0
       xor ebx,ebx

begin_match_symbol:
       call cmp_symbol_once
       or eax,0
       jnz matched
       add esi,max_api_str_len
       inc ebx
       loop begin_match_symbol

       jmp match_symbol_done

matched:
       mov byte [bool_matched_symbol],1
       mov eax,ebx
       
match_symbol_done:
       pop ebx
       pop ecx
       pop esi
       pop ds
       ret

;增加一个描述符
;描述符 edx:eax 入参 ,
;返回 eax : 此描述符的段选择子( 更新后的界限值 / 8)
add_gdt_d:
       push ds
       push es
       push ebx
       push edx
       
       ;ds 指向内核数据段选择子
       mov ebx,core_data_seletor
       mov ds,ebx

       ;es 0-4G选择子
       mov ebx,mem_selector
       mov es,ebx

       ;把GDTR里的数据 放到指定内存地址,6个字节
       sgdt [ds:gdt_limit]

       ;计算新增的gdt地址: gdt表地址 + gdt界限 +1
       xor ebx,ebx
       mov bx,[ds:gdt_limit]
       inc bx
       add ebx,[ds:gdt_addr]

       ;放入新增描述符
       mov [es:ebx],eax
       mov [es:ebx+4],edx

       ;修改gdt界限
       add word [ds:gdt_limit],8

       ;计算当前描述符的段选择子
       xor eax,eax
       xor edx,edx
       mov ax,[ds:gdt_limit]
       mov bx,8
       div bx
       shl ax,3 ; ti,rpl 3位均0
       
       ;重新加载gdtr
       lgdt [ds:gdt_limit]



       

       pop edx
       pop ebx
       pop es
       pop ds
       retf



;eax 基址
;ebx 界限
;ecx 属性
;返回 高32位edx:低32位eax  描述符
mk_gdt_d:
       push edx

       ;构建低32位 ,eax == [16位基址][16段界限]
       mov edx,eax
       and edx,0xffff0000
       shl eax,16
       or ax,bx
       
       ;构建高32位
       rol edx,8
       bswap edx
       xor bx,bx
       or edx,ebx
       or edx,ecx



       pop edx
       retf


;ecx 需要分配的字节数
calc_mem_addr:
       push ebx
       push edx
       push eax
       push ds

       mov eax, core_data_seletor
       mov ds,eax

       ;eax :下个可分配地址
       mov eax,[ds:mem_alloc_addr]
       add eax,ecx
       mov ecx,[ds:mem_alloc_addr]


       ;为下个地址对齐
       ;ebx 必是一个4字节对齐的地址
       mov ebx,eax
       and ebx,0xfffffffc 
       add ebx,4
       ;如果eax 最后2位有1位是1,则不是能被4整除的数
       test eax,0x03
       ;根据条件赋值
       cmovnz eax,ebx

       mov [ds:mem_alloc_addr],eax

       


       pop ds
       pop eax
       pop edx
       pop ebx
       retf


;eax 扇区号
;ds:ebx 写入地址
read_sector:                           
                                       
         push eax 
         push ecx
         push edx
         push eax
         
         mov dx,0x1f2
         mov al,1
         out dx,al                          ;读取的扇区数

         inc dx                             ;0x1f3
         pop eax
         out dx,al                          ;LBA地址7~0

         inc dx                             ;0x1f4
         mov cl,8
         shr eax,cl
         out dx,al                          ;LBA地址15~8

         inc dx                             ;0x1f5
         shr eax,cl
         out dx,al                          ;LBA地址23~16

         inc dx                             ;0x1f6
         shr eax,cl
         or al,0xe0                         ;第一硬盘  LBA地址27~24
         out dx,al

         inc dx                             ;0x1f7
         mov al,0x20                        ;读命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                         ;不忙,且硬盘已准备好数据传输 

         mov ecx,256                        ;总共要读取的字数
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [ebx],ax
         add ebx,2
         loop .readw

         pop edx
         pop ecx
         pop eax
      
         retf                              

printf:                                 
                                            ;DS:EBX=字符串地址
         push ecx
  .getc:
         mov cl,[ebx]
         or cl,cl
         jz .exit
         call put_char
         inc ebx
         jmp .getc

  .exit:
         pop ecx
         retf                               ;段间返回

put_char:                                   
         pushad

         ;以下取当前光标位置
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                           
         in al,dx                          
         mov ah,al

         dec dx                             
         mov al,0x0f
         out dx,al
         inc dx                            
         in al,dx                           
         mov bx,ax                         

         cmp cl,0x0d                        
         jnz .put_0a
         mov ax,bx
         mov bl,80
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

  .put_0a:
         cmp cl,0x0a                       
         jnz .put_other
         add bx,80
         jmp .roll_screen

  .put_other:                               
         push es
         mov eax,screen_selector          ;0xb8000段的选择子
         mov es,eax
         shl bx,1
         mov [es:bx],cl
         pop es

         shr bx,1
         inc bx

  .roll_screen:
         cmp bx,2000                       
         jl .set_cursor

         push ds
         push es
         mov eax,screen_selector
         mov ds,eax
         mov es,eax
         cld
         mov esi,0xa0                      
         mov edi,0x00                       
         mov ecx,1920
         rep movsd
         mov bx,3840                      
         mov ecx,80                         
  .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         pop es
         pop ds

         mov bx,1920

  .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             
         mov al,bh
         out dx,al
         dec dx                             
         mov al,0x0f
         out dx,al
         inc dx                             
         mov al,bl
         out dx,al

         popad
         ret  

section core_data vstart=0
       ;分配用户程序内存物理起始地址
       mem_alloc_addr dd 0x100000 

       ;用来修改gdt, 指令sgdt把GDTR寄存器的数据放到指定内存
       gdt_limit     dw 0
       gdt_addr      dd 0


       welcome_msg db 'welcome to mini core code',0x0d,0x0a,0
       load_user_app_msg  db 'loading user app...',0x0d,0x0a,0
       load_user_app_done db 'user app loaded!',0x0d,0x0a,0
       exit_msg db 'user app exit',0x0d,0x0a,0

       cpu_info_begin db 0x0d,0x0a,0
       cpu_info times 128 db 0
       cpu_info_end db 0x0d,0x0a,0

       ; 首个扇区缓存
       first_sector_buf times 512 db 0

       ;是否匹配
       bool_matched_symbol db 0

       ;内核栈顶
       stack_esp dd 0

       ;用户程序段选择子 (头)
       user_app_header_selector dw 0

       ;内核符号表
       ;用于匹配用户程序调用的函数,并填充对应的 段选择子:偏移
       
       symbol_table:
       __printf      db 'printf'
                     times max_api_str_len-($-__printf) db 0
       
                     
       __read_sector db 'read_sector'
                     times max_api_str_len-($-__read_sector) db 0
                     
       
       __exit_process db 'exit_process'
                     times max_api_str_len-($-__exit_process) db 0

       symbol_table_len equ ($-symbol_table)/max_api_str_len
       
       ;符号地址表
       symbol_addr:
       __printf_addr dd printf
                     dw sys_func_selector

       __read_sector_addr dd read_sector
                     dw sys_func_selector
       
       __exit_process_addr dd exit_process
                     dw core_code_seletor

core_data_end:




section core_code vstart=0
start:
       ;进入内核代码
       ;ds数据区使用自己的core_data
       ;ss使用mbr中已经分配的4K

    mov eax,core_data_seletor
    mov ds,eax

    ;输出欢迎信息, ebx:参数 , ds:数据段
    mov ebx, welcome_msg
    ; call 0x28:printf , push cs push eip 
    call sys_func_selector:printf

    mov eax,0
    ;cpuid 返回cpu 信息,特性 ... 
    ; eax 参数
    ; 参数0 返回最大功能号, ebx, ecx,edx 返回其他信息
    cpuid
    mov [cpu_info + 0], ebx
    mov [cpu_info + 4],ecx
    mov [cpu_info + 8],edx

       ;输出CPU 信息
    mov ebx,cpu_info_begin
    call sys_func_selector:printf
    mov ebx,cpu_info
    call sys_func_selector:printf
    mov ebx,cpu_info_end
    call sys_func_selector:printf

       ;输出加载用户程序信息
    mov ebx,load_user_app_msg
    call sys_func_selector:printf

       ;分配内存地址
       ;加载用户程序
       ;建立描述符
       ;匹配符号表,把匹配到的api符号 填入用户程序中
    mov esi,user_app_sector
    call load_user_app
    
    mov ebx,load_user_app_done
    call sys_func_selector:printf

       ;保留当前内核栈顶,用户程序将切换成自己的栈
    mov [ds:stack_esp],esp

    xor eax,eax
    mov eax,[ds:user_app_header_selector]
    mov ds,eax
    jmp far [ds:0x14] ; cs:用户代码段选择子, eip:start


exit_process:
       ;恢复内核数据段,stack
       mov eax,core_data_seletor
       mov ds,eax
       mov eax,core_stack_selector
       mov ss,eax
       mov esp,[ds:stack_esp]

       mov ebx,exit_msg
       call sys_func_selector:printf
       hlt



    

;参数 : esi 起始扇区号
load_user_app:
       pushad
       push ds
       push es

       mov eax , core_data_seletor
       mov ds, eax

       ;读用户APP首个扇区
       mov eax,esi
       mov ebx,first_sector_buf
       call sys_func_selector:read_sector

       ;计算用户程序扇区数量
       ;加载用户程序的起始地址由内核代码决定,这里由mem_alloc_addr 作为起始地址
       ;这里起始地址以4字节对齐, 程序总字节以512对齐(读取扇区以512字节为单位)
       ;由于读取扇区是512字节对齐的,因此分配地址至少也是要512字节对齐的
       ;calc_mem_addr 传入已经对齐的字节数, 同时计算下个可分配的地址
       mov eax,[ds:first_sector_buf]
       mov ecx,512
       xor edx,edx
       div ecx

       or edx,edx
       jz begin_calc_mem_addr
       inc eax

       ;计算分配内存地址
       begin_calc_mem_addr:
       mov ebx,eax   ; ebx :扇区数
       mov ecx,512
       mul ecx
       mov ecx,eax   ;以512对齐的字节数
       call sys_func_selector:calc_mem_addr


       ;计算好用户加载的起始地址后, 开始加载用户程序
       mov edi,ecx   ;edi 用户程序加载首地址
       xchg ebx,ecx  ; 交换后, ecx 扇区数  , ebx 起始地址
       mov eax,mem_selector ; 0~4G的数据描述符
       mov ds,eax
       mov eax,esi
       ; eax 扇区号, ecx 扇区数 , ebx 分配的内存起始地址
read_user_app:
       call sys_func_selector:read_sector
       inc eax
       loop read_user_app
       
       
       ;构建头描述符
       ;当前 ds : 0~4G 的段选择子
       ;eax 基址, ebx 段界限 , ecx 属性
       mov eax,edi
       mov ebx,[edi+0x04]
       dec ebx       ;段界限
       mov ecx, 0x00409200    ;属性,读写
       ;建立描述符
       call sys_func_selector:mk_gdt_d
       ;增加描述符,eax作为返回值
       call sys_func_selector:add_gdt_d
       ;段选择子写回应用程序
       mov [edi+0x08],eax

       ;代码段描述符
       mov eax,edi
       add eax,[edi+0x18]   ;基址
       mov ebx,[edi+0x1c]   ;代码段长度
       dec ebx              ;界限
       mov ecx,0x00409800 ; 属性,执行
       call sys_func_selector:mk_gdt_d
       call sys_func_selector:add_gdt_d
       mov [edi+0x18],eax

       ;数据段
       mov eax,edi
       add eax,[edi+0x20] ;基址
       mov ebx,[edi+0x24] ;长度
       dec ebx            ;界限
       mov ecx,0x00409200     ; 读写
       call sys_func_selector:mk_gdt_d
       call sys_func_selector:add_gdt_d
       mov [edi+0x20],eax

       ;stack
       ;stack段界限从高地址往低,因此从0xFFFFF开始减
       ;当前stack段属性的G=1
       mov eax,[edi+0x10]   ;获取长度单位
       xor ebx,ebx
       mov ebx,0xfffff
       sub ebx,eax          ;stack段界限
       ;给stack计算分配的地址,ecx 入参(字节数)
       mov eax,0x1000
       xor edx,edx
       mul dword [edi+0x10]
       mov ecx,eax
       call sys_func_selector:calc_mem_addr
       ;返回的地址为栈底
       ;设置eax基址(栈顶)
       add eax,ecx
       mov ecx,0x00c09600   ;stack属性
       call sys_func_selector:mk_gdt_d
       call sys_func_selector:add_gdt_d
       mov [edi+0x0c],eax


       ;开始匹配系统函数,重定位用户程序符号
       ;eax 用户符号表段选择子
       ;ebx 偏移地址
       ;ecx N个符号需要解析
       ;edx 用户符号表被填充偏移地址

       mov eax,[edi+0x08]
       mov ebx,0x30
       mov ecx,[edi+0x28]
       mov edx,[edi+0x2c]
       call sys_func_selector:realloc_user_symbol

       ;在core_data中填入用户程序(头)段选择子,用于jmp进入入口
       mov eax,core_data_seletor
       mov es,eax
       mov eax,[edi+0x08]
       mov [es:user_app_header_selector],eax
       

       pop es
       pop ds
       popad
       ret


section core_end
core_length:

user_app.asm


max_api_str_len equ 32  ;api字符串定长32字节
const_symbol_table_len equ (header_end-symbol_table)/max_api_str_len

;模拟连接器生成文件头
section header vstart=0
        ;程序长度
        app_len dd app_end
        ;头部长度
        ;0x04
        header_len dd header_end
        ;头部段选择子,会被修改
        ;0x08
        header_selector dd section.header.start


        ;偏移0xc
        stack_selector dd 0 ;stack段选择子,由内核代码来分配
        ;0x10 
        ;stack长度由用户程序指定,由内核代码分配
        ;stack_len == 1, 以4K为粒度
        stack_len dd 1        ;stack长度

        ;偏移0x14
        entry dd start      ; aop

        ;用于计算基址,被计算后将修改成段选择子
        ;偏移0x18
        code_selector dd section.user_code.start    
        ;代码段长度
        ;偏移0x1c
        code_len dd user_code_end

        ;数据区,0x20
        data_selector dd section.user_data.start
        ;长度 ,0x24   
        data_len dd user_data_end               

        ;有几个符号需要解析
        ;0x28
        symbol_table_len dd const_symbol_table_len

        ;0x2c
        symbol_addr_fetch dd symbol_addr

        ;符号表
        ;0x30
        symbol_table:
        
        printf db 'printf'
                times max_api_str_len-($-printf) db 0
        
        exit_process db 'exit_process'
                times max_api_str_len-($-exit_process) db 0
        
        
        ;符号地址表
        symbol_addr:
                times const_symbol_table_len*6 db 0


header_end:

[bits 32]
section user_data vstart=0
    welcome_msg db '!!!! user app is running , welcome!',0x0d,0x0a,0
user_data_end:

section user_code vstart=0
start:

        ;当前ds是头段选择子
        mov eax,ds
        mov es,eax

        ;切换用户程序自己的数据段 ,stack
        mov eax,[ds:data_selector]
        mov ds,eax

        mov eax,[es:stack_selector]
        mov ss,eax
        xor esp,esp

        ;call printf
        mov ebx,welcome_msg
        call far [es:symbol_addr]

        ;跳回内核
        jmp far [es:symbol_addr + 6]




      
      
        
user_code_end:

section app_tail
app_end:


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