模拟连接器创建文件头
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 0xaa55app_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版权协议,转载请附上原文出处链接和本声明。