赞
踩
课程链接:
利用C语言绘制操作系统图像界面
课程的目的是在屏幕上绘制一个图形界面,其实只需要往显存对应的缓冲区写入表示颜色的字符就行了。循环写入显然是一个不错的办法,用汇编写一个循环?一般人的汇编水平可能并不太够。所以作者提供的办法是先用c语言写,再反汇编成汇编代码。
%include "pm.inc" org 0x9000 VRAM_ADDRESS equ 0x000a0000 jmp LABEL_BEGIN [SECTION .gdt] ; 段基址 段界限 属性 LABEL_GDT: Descriptor 0, 0, 0 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW LABEL_DESC_VRAM: Descriptor 0, 0ffffffffh, DA_DRW LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32 GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen - 1 dd 0 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT SelectorStack equ LABEL_DESC_STACK - LABEL_GDT SelectorVram equ LABEL_DESC_VRAM - LABEL_GDT [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov al, 0x13 mov ah, 0 int 0x10 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_STACK mov word [LABEL_DESC_STACK + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK + 4], al mov byte [LABEL_DESC_STACK + 7], ah xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT mov dword [GdtPtr + 2], eax lgdt [GdtPtr] cli ;关中断 in al, 92h or al, 00000010b out 92h, al mov eax, cr0 or eax , 1 mov cr0, eax jmp dword SelectorCode32: 0 [SECTION .s32] [BITS 32] LABEL_SEG_CODE32: ;initialize stack for c code mov ax, SelectorStack mov ss, ax mov esp, TopOfStack mov ax, SelectorVram mov ds, ax C_CODE_ENTRY: %include "write_vga.asm" io_hlt: ;void io_hlt(void); HLT RET SegCode32Len equ $ - LABEL_SEG_CODE32 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 TopOfStack equ $ - LABEL_STACK
针对以上代码,自己的一些理解:
A0000~AFFFF: VGA图形模式显存空间
B0000~B7FFF: MDA单色字符模式显存空间
B8000~BFFFF: CGA彩色字符模式显存空间
C0000~C7FFF: 显卡ROM空间(后来被改造成多种用途,也可以映射显存)
C8000~FFFFE: 留给BIOS以及其它硬件使用(比如硬盘ROM之类的)
参考:
https://www.zhihu.com/question/269649445/answer/351632444
LABEL_DESC_CODE32和前面的课程一样,都是为了跳转到代码处去执行。其实有一个疑问,在关中断前后的这段代码中:
cli ;关中断 in al, 92h or al, 00000010b out 92h, al mov eax, cr0 or eax , 1 mov cr0, eax jmp dword SelectorCode32: 0 [SECTION .s32] [BITS 32] LABEL_SEG_CODE32: ;initialize stack for c code mov ax, SelectorStack
jmp命令后面紧接着的就是想要执行的LABEL_SEG_CODE32对应的代码,直接顺序执行不就行了吗?还要jmp命令干啥。我的理解是,CS寄存器和IP寄存器决定了cpu将要执行的指令的位置。但是在实模式与保护模式下的寻址方式是不同的。
实模式寻址方式:CS寄存器*16+IP寄存器
保护模式寻址方式:CS寄存器存储的实际上是段选择符,找到GDT表中对应的表项,然后找到段基址+IP寄存器(段内偏移)
也就是说,在CS寄存器和IP寄存器不发生改变的情况下,从实模式切换到保护模式,寻址得到的地址也不一样。所以说,切换成保护模式之后,需要使用jmp模式,修改CS寄存器和IP寄存器的值,才能定位到想要执行的代码处。
栈是程序运行的过程中需要的一段内存,那么这段内存就必须得提前规划好,并写入GDT表中,用SS寄存器来寻址。栈在工作的过程中,需要3个寄存器:
SS寄存器:存储栈地址对应的段选择符。
EBP寄存器:存储栈底在段中的偏移。
ESP寄存器:存储栈顶在段中的偏移。
所以,必须要提前申请一段内存,放入GDT表中,并修改SS寄存器的值,使其指向GDT表中对应的位置。所以栈的大小是有限的,如果超出了栈的大小,就是造成堆栈溢出的错误。
栈是设计成向下增长的,所以mov esp, TopOfStack
将esp设置为申请的内存段的最高地址(段内偏移),此时ebp还不需要设置值,因为ebp的作用其实就是在esp移动的时候保存esp的值。
因为程序中只申请了512字节的栈空间,如果使用超过512字节的空间:
char buf[513];
会导致程序发生致命错误,无法运行。
从代码可以看出,这个地址是最终被放到了ds寄存器中:
mov ax, SelectorVram
mov ds, ax
而且,LABEL_DESC_VRAM的段基地址是0,段长度是ffff ffffh,也就是4G,也就是整个线性地址空间。
为什么要这么做呢?原来,我们在c代码中,直接操作了地址a0000地址附近的一段空间,而且这个地址,我们希望访问的就是对应的线性地址,那就只能把段基地址设置为0。而且段长度一定要大于要访问的最大地址。将分配的这段内存的段基地址写入到ds寄存器中,程序就可以访问这段内存了。这样,程序其实可以操作整个4G的空间,所以程序自身一定要保证不要操作到其他代码段使用的地址,否则将发生不可预知的错误。
可以做一个小例子来验证一下以上理解:
如果修改段长度到小于0xaffff,程序将发生错误:
LABEL_DESC_VRAM: Descriptor 0, 0afffeh, DA_DRW
段长度到大于等于0xaffff,程序则可以正常运行。
LABEL_DESC_VRAM: Descriptor 0, 0affffh, DA_DRW
感兴趣的同学可以尝试一下。
LABEL_DESC_VRAM: Descriptor 0, 0a0fffh, DA_DRW
的效果:
EAX is the full 32-bit value
AX is the lower 16-bits
AL is the lower 8 bits
AH is the bits 8 through 15 (zero-based)
RAX, which hold a 64-bit value, and where EAX is mapped to the lower 32 bits.
参考:https://stackoverflow.com/questions/15191178/how-do-ax-ah-al-map-onto-eax
; Disassembly of file: write_vga.o ; Sat Sep 7 21:55:43 2019 ; Mode: 32 bits ; Syntax: YASM/NASM ; Instruction set: 80386 ;;;global CMain: function ;;;extern io_hlt ; near ;;;SECTION .text align=1 execute ; section number 1, code CMain: ; Function begin push ebp ; 0000 _ 55 mov ebp, esp ; 0001 _ 89. E5 sub esp, 24 ; 0003 _ 83. EC, 18 mov dword [ebp-0CH], 0 ; 0006 _ C7. 45, F4, 00000000 mov dword [ebp-10H], 655360 ; 000D _ C7. 45, F0, 000A0000 jmp ?_002 ; 0014 _ EB, 14 ?_001: mov eax, dword [ebp-10H] ; 0016 _ 8B. 45, F0 mov dword [ebp-0CH], eax ; 0019 _ 89. 45, F4 mov eax, dword [ebp-10H] ; 001C _ 8B. 45, F0 mov edx, eax ; 001F _ 89. C2 mov eax, dword [ebp-0CH] ; 0021 _ 8B. 45, F4 mov byte [eax], dl ; 0024 _ 88. 10 add dword [ebp-10H], 1 ; 0026 _ 83. 45, F0, 01 ?_002: cmp dword [ebp-10H], 720895 ; 002A _ 81. 7D, F0, 000AFFFF jle ?_001 ; 0031 _ 7E, E3 ?_003: call io_hlt ; 0033 _ E8, FFFFFFFC(rel) jmp ?_003 ; 0038 _ EB, F9 ; CMain End of function ;;;SECTION .data align=1 noexecute ; section number 2, data ;;;SECTION .bss align=1 noexecute ; section number 3, bss
一个分号的是反编译之后自带的注释,三个分号的是手工添加的注释。
之前基本还没研究过汇编代码,所以当看到一段c代码对应的汇编代码,并读懂的时候,觉得很激动,很神奇。
说说对这段代码的理解。
push ebp ; 0000 _ 55
mov ebp, esp ; 0001 _ 89. E5
sub esp, 24 ; 0003 _ 83. EC, 18
每个函数执行开始前的标准范式,将ebp寄存器保存起来。
并将esp保存到ebp中。
然后将esp减去24,表示需要24个字节的空间。但是不太理解,这个24是怎么计算出来的。
也没明白,后面的ebp-10H和ebp-0CH是怎么计算出来的。
ebp-0CH表示的是变量p。
ebp-10H表示的是变量i。变量i被赋初值655360(0xa0000)。
?_001: mov eax, dword [ebp-10H] ; 0016 _ 8B. 45, F0
mov dword [ebp-0CH], eax ; 0019 _ 89. 45, F4
表示p=i。
mov eax, dword [ebp-10H] ; 001C _ 8B. 45, F0
mov edx, eax ; 001F _ 89. C2
表示将i的值存到edx中。
mov eax, dword [ebp-0CH] ; 0021 _ 8B. 45, F4
mov byte [eax], dl
则表示将i的低8位写入到位置i。
要访问位置i对应的内存,cpu需要进行一次寻址。ds段寄存器的基地址被我们在前面设置成了0,所以i就对应着线性地址。
?_002: cmp dword [ebp-10H], 720895 ; 002A _ 81. 7D, F0, 000AFFFF
jle ?_001
一次循环完之后进行一次判断,决定是否需要继续循环。
通过objconv反编译出的代码一直包含着这个函数。
然后使用nasm编译kernel.asm时一直报错:
Invalid operand type error
对比作者的代码,发现我反汇编出的代码多了__x86.get_pc_thunk.ax这个函数。在网上搜了一下,原来这个函数是32位 机器特有的一个指令,作用貌似是获取下一条指令的地址,并设置指令寄存器为对应的值。
我编译的环境确实是32位的debian。
所以切换到64位的机器下进行操作,编译出的代码便是正常的。__x86.get_pc_thunk.ax相关的原理与机制,未来得及深入研究。
Invalid operand type error
i386 Linux下Elf动态链接分析
What is __i686.get_pc_thunk.bx? Why do we need this call?
将*p = i & 0x0f;
修改为*p = i & 0xff;
,画出来的效果有一些不一样:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。