我第一次花了几天时间玩Bootloader。这篇文章构建了一个带有几个键盘快捷键的文本编辑器。我';我将于1月27日在HackerNights进行一次基于这项工作的虚拟演讲。
肯定有很多漏洞。但它';It’很难找到引导程序编程的中间资源,所以其中的一部分可能会有用。
如果你已经了解了基础知识和中级课程,并且只想要一个中级+教程,也许可以试试这个。非常好。
这篇帖子上的代码可以在Github上找到,但是它';这比我平常的计划更像艾姆斯。
安装qemu(在macOS或Linux上)、nasm并复制snake。从那篇博文中下载源代码到磁盘。
$cat snake。asm[位16];Pragma,告诉汇编程序我们;处于16位模式(这是从软盘引导时的x86状态)。[org 0x7C00];布拉格马,告诉装配工;代码将被加载。mov bl,1;蠕虫的起始方向。按0xa000;将VRAM的地址加载到es中。流行音乐游戏:mov si,320*100+160;蠕虫';s的起始位置,中心位置;屏幕设置视频模式。模式13h是VGA(每像素1字节,实际颜色存储在调色板中),总大小为320x200。重新启动时;这也会清除屏幕。mov ax,0x0013 int 0x10;画边界。我们假设默认调色板对我们有用;我们还假设从底部开始绘制2176个像素;环绕并最终绘制上下边框。mov di,320*199 mov cx,2176 repdraw_循环:stosb;给某人画右边框;绘制左边框添加di,318 jnc绘制_循环;注意在中间的跳跃;给某人指示。游戏_循环:;我们从端口0x60读取键盘输入。这也会从中读取字节;鼠标,所以我们只需要在al中处理[up(0x48)、left(0x4b)、right(0x4d)、down(0x50)]、0x60 cmp al、0x48 jb kb_handle_end cmp al、0x50 ja kb_handle_end;在末端,bx包含偏移位移(+1,-1,+320,-320);基于按下/释放的键盘键。我打赌有几个字节;根据上面的边界检查,在这里刮胡子。aaa cbw dec ax dec ax jc kb_handle sub al,2 imul ax,ax,byte-0x50kb_handle:mov bx,axkb_handle_end:add si,bx;原始代码使用set PALLTE命令(10h/0bh)等待;垂直回程。今天';然而,美国的计算机速度太快,所以;我们用int 15h 86h代替。这也会减少几个字节;注:你';如果你在虚拟机上运行,你必须调整cx+dx;机器vs真正的硬件。随意的测试似乎表明虚拟机;等待时间比物理硬件长约3-4倍。mov ah,0x86 mov dh,0xef int 0x15;绘制蜗杆并检查是否与奇偶校验相冲突;(偶数奇偶=碰撞)。mov-ah,0x45-xor[es:si],ah;回到游戏主循环。jpo game_loop;我们撞到了墙或虫子。重新开始游戏。jmp重启游戏时间510-($-$$)db 0;用0dw 0xaa55填充扇区的其余部分;引导加载程序末尾的引导签名
我';我不会在这篇文章中达到那种复杂程度,但我认为它';这是巨大的动力。
引导加载程序是汇编编程和BIOS API的混合体,用于I/O;我们正在考虑你已经知道的引导加载程序。现在你要做的就是学习API。
hello world引导程序已经被详细解释过(参见这里、这里和这里)soI won';不要逐行深入太多。
你好。16位;告诉NASM这是16位代码组织0x7c00;告诉NASM开始输出偏移量0x7c00boot处的内容:mov si,您好;将si寄存器指向hello标签内存位置mov ah,0x0e;0x0e表示';以TTY模式写入字符';。回路:lodsb或al,al;al=0吗?jz暂停;如果(al==0)跳转到停止标签int 0x10;运行BIOS中断0x10-视频服务jmp。loophalt:cli;清除中断标志hlt;停止执行你好:db";你好,世界",0次510-($-$$)分贝0;用zeroesdw 0xaa55填充剩余的510字节;magic bootloader magic-标记这个512字节的扇区可引导!
但除了文书设置(16位汇编,程序存在于内存中,填充到512字节)之外,唯一真正的引导加载程序ymagic是int 0x10,一个BIOS中断。
BIOS中断是API调用。就像userland程序中的系统调用一样,它们有一个特定的寄存器约定和编号来调用API家族。
当你编写引导程序时,你';首先,我会花大部分时间来理解各种BIOS API的行为。
我们';本文将讨论键盘系列(此处为文档)和显示器系列(此处为文档)。
我们在上面调用的将字符写入显示的特定函数是INT10,例如0x10是调用int关键字的参数(例如int 0x10)。E是0x10系列中的特定功能。在调用int之前,E被写入AH寄存器。要写入的ASCII码被放入AL寄存器。
既然输出有了一些意义,那就让';我们做输入。在键盘服务文档中,您可能会注意到INT 16,0提供了一种阻止用户输入的方法。根据该页面,当中断返回时,ASCI字符将在AL中。
您可能已经注意到,在我们的程序运行之前,会显示一些文本。我们可以使用INT0x10,0清除屏幕。
由于display函数从inputfunction输出到的同一个寄存器中读取数据,所以我们可以在两个中断之后调用它们。把这个循环起来,我们就拥有了这个世界#39;他是最差的编辑。
$cat ioloop。asmbits 16org 0x7c00main:;;清除屏幕mov ah,0x00 mov al,0x03 int 0x10。循环:;;读取字符mov ah,0 int 0x16;;打印字符mov ah,0x0e int 0x10 jmp。循环时间510-($-$$)db 0;用zeroesdw 0xaa55填充剩余的510字节;magic bootloader magic-标记这个512字节的扇区可引导!
顺便说一句,这里的主标签(如hello.asm中的引导标签)只是为了帮助读者。这不是BIOS使用的东西。
bits 16org 0x7c00jmp主清除屏幕:;;清除屏幕mov ah、0x00 mov al、0x03 int 0x10 retmain:调用清除屏幕。循环:;;读取字符mov ah,0 int 0x16;;打印字符mov ah,0x0e int 0x10 jmp。循环时间510-($-$$)db 0;用zeroesdw 0xaa55填充剩余的510字节;magic bootloader magic-标记这个512字节的扇区可引导!
位16org 0x7c00jmp main%宏cls 0;零是参数mov-ah、0x00-mov-al、0x03-int、0x10%endmacromain:cls的数量。循环:;;读取字符mov ah,0 int 0x16;;打印字符mov ah,0x0e int 0x10 jmp。循环时间510-($-$$)db 0;用zeroesdw 0xaa55填充剩余的510字节;magic bootloader magic-标记这个512字节的扇区可引导!
nasm宏甚至可以通过在它们前面加%%来编写宏安全标签,如果宏中有条件或循环,这很有用。
我猜宏的好处是你';We’我们没有用完鞋钉。函数调用的好处是';不要在使用宏的每个地方重复代码。生成的代码量最终在引导加载程序中变得非常重要,因为代码必须适合512字节。
读取ASCII字符并不像我们上面看到的那样复杂。但是,如果我们想要构建像ctrl-a这样的读线式快捷键,以便跳到行的开头,那又是什么呢?
我们可以像上面那样使用INT 16,0。但是,内存中有一部分包含按下的字符和按下的控制字符,而不仅仅是读取该函数调用的结果。
根据该存储区域(此处或此处)的文档,我们可以构建一个宏来读取按下的字符:
%宏mov_将字符_读入1 mov eax,[0x041a]添加eax,0x03fe;从0x0400到sizeof(uint16)的偏移量(因为磁头指向下一个空闲插槽,而不是最后一个/当前插槽)和eax、0xFFFF mov%1、[eax]和%1、0xFF%endmacro
%宏mov_将\u ctrl_flag_读入1 mov%1、[0x0417]和%1、0x04;获取第三位:%1&;0b0100%endmacro
最后我们';我们将使用一些游标API,这些API允许我们处理新的行、行的第一列上的退格和ctrl-a(跳转到行的开始)。
%宏获取位置0 mov ah,0x03 int 0x10%endmacro%宏设置位置0 mov ah,0x02 int 0x10%endmacro
但是有';我的goto_end of_line函数有点问题。有时它可以工作,有时它只是在屏幕上无限循环地跳转。部分问题在于编辑记忆就是视频卡。光标位置只存储在那里,而不是像在高级环境/语言中那样以某种程序状态存储。
转到第二行:;;获取当前字符mov-ah,0x08 int 0x10;;迭代,直到字符为null cmp al,0 jz。done inc dl set_position jmp goto_end_线。完成:ret
%宏读取_字符0;;读取字符mov ah,0 int 0x16%endmacro%macro print_字符1 mov ax,%1 mov ah,0x0e int 0x10%endmacro
%宏获取位置0 mov ah,0x03 int 0x10%endmacro%宏设置位置0 mov ah,0x02 int 0x10%endmacrogoto_end_线:;;获取当前字符mov-ah,0x08 int 0x10;;迭代,直到字符为null cmp al,0 jz。done inc dl set_position jmp goto_end_线。完成:ret
%宏mov_将\u ctrl_flag_读入1 mov%1、[0x0417]和%1、0x04;获取第三位:%1&;0b0100%endmacro%macro mov_read_character_为1 mov eax,[0x041a]add eax,0x03fe;从0x0400到sizeof(uint16)的偏移量(因为磁头指向下一个空闲插槽,而不是最后一个/当前插槽)和eax、0xFFFF mov%1、[eax]和%1、0xFF%endmacro
现在我们可以启动编辑器循环,在那里等待按键并处理它。
唐';如果按下的键是箭头键,则不能打印ASCII垃圾。什么都不做。(总的来说,这不是一个好的编辑行为,但我们的编辑行为很好。)
;; 忽略箭头键cmp ah,0x4b;左jz。完成cmp ah,0x50;下jz。完成cmp ah,0x4d;对,jz。完成cmp啊,0x48;上jz。完成
;; 手柄退格cmp al,0x08 jz。is_backspace cmp al,0x7F;适用于mac键盘jnz。退格完成。是退格:获得位置
否则,如果不是在行首按backspace,只需用ASCII 0覆盖最后一个字符(代码0而不是数字0)。
否则你';you’你在队伍的开头,你需要跳到前一行的末尾。
.在_行的_开始_处退格_:;递减行设置位置调用转到行的结束。覆盖字符:mov al,0 mov ah,0x0a int 0x10 jmp。完成。完成\u退格:
接下来我们处理回车键。这会将光标移动到下一行,并将列设置回零。
;; 句柄将mov_read_character_输入ax cmp al,0x0d jnz。完成输入获取位置有限公司dh;增量行mov dl,0;重置列设置位置jmp。完成。完成输入:
;; 控制ctrl-快捷键;;选中ctrl键mov_read_ctrl_flag_进入ax jz。ctrl_not_set;;句柄ctrl——将字符移动到ax cmp al中的快捷方式,1;由于ctlr的某些原因,这些是从a-z jnz偏移的。而不是;;重置列mov dl,0设置位置jmp。完成。非控制a:
;; 将ctrl-e快捷键mov_read_character_移动到ax cmp al,5 jnz中。不按ctrl键调用jmp行的goto_end_。完成。不是:jmp。完成。ctrl\u未设置:
最后,如果这些情况都不满足,只需打印按下的字符并返回即可。
主播:cls。循环:调用编辑器_action jmp。循环时间510-($-$$)db 0;用zeroesdw 0xaa55填充剩余的510字节;magic bootloader magic-标记这个512字节的扇区可引导!
我写了一篇关于我第一次探索bootloader基础知识的新帖子!发现BIOS API并花一些时间在汇编中编写代码,而不是仅仅生成或模拟它,这是一个很好的选择。https://t.co/7iP6Nib620照片。啁啾com/xSyG1IXgEB
-菲尔·伊顿(@Phil_Eaton)2022年1月23日