写点什么

自制操作系统日记(8):变量显示

作者:
  • 2022-11-25
    四川
  • 本文字数:4021 字

    阅读完需:约 13 分钟

代码仓库地址:https://github.com/freedom-xiao007/operating-system

简介

上篇中,我们显示了静态的字符串在桌面上,本篇进一步探索,能将定义的整型变量的值显示在桌面上

探索历程说明

本来想着应该是一两天能搞定的,但事与愿违,哎,卡的很,很不顺利,今天在看书的时候突然灵光一闪,有了其他的思路,然后尝试,发现成功了,喜极而泣,太难了


下面说说本次实现中的大坑:数字转字符串!


在书中是使用的 sprintf 函数,想着跟着用也没有啥问题,但结果是编译后并没有自动生成这个 sprintf 函数的实现!


想着去 github 搜一搜,复制粘贴一把,一看,这也太复杂了吧,尝试抄了,好吧,太菜了,抄不动(抄代码都不会了,艹),抄啊抄的一天时间过去了


第二天想着不就是数字转字符串吗,我自己用汇编写一个不行?然后就在网上搜索相关代码,很好,有的,复制粘贴改吧改吧,一气呵成,结果一看战绩 0-8,超鬼,艹,又 2 天过去了


行吧,突破失败,只能积攒修为,看书,又开始翻那三本书,同时看看其他,不能一直卡在这,学点其他的


昨天晚上看 go 的编译器相关的东西的时候,突然灵光一闪:可以先不再桌面上显示,先调试通无桌面时字符串显示,再将数字转字符串在无桌面时显示,最后上桌面显示


不能熬夜,来一套睡前冥想,睡一觉起来再说


今天 4 点半醒来就开搞,噗嗤噗嗤搞了三四个小时,终于成功了,喜极而泣


下面是相关关键节点的记录说明:

实现说明

1.无桌面时打印字符串

首先看看我们不是图形化的时候是怎么打印字符串,在我们的 head.asm 文件中,处于 32 模式下的字符串显示方法如下:


mov  esi,asmmsg                 ;保护模式DS=0,数据用绝对地址访问mov  cl, 0x09                   ;蓝色字体mov  edi, 0xb8000+21*160        ;指定显示在某行,显卡内存地址也需用绝对地址访问     call printnew
printnew: ;保护模式下显示字符串, 以'$'为结束标记 mov bl ,[ds:esi] cmp bl, '$' je printover mov byte [ds:edi],bl inc edi mov byte [ds:edi],cl ;字符颜色 inc esi inc edi jmp printnewprintover: ret
复制代码


如上所示,asmmsg 是字符串,想将其地址赋给 esi 寄存器,然后设置颜色和位置,最后调用 printnew 函数,我们实现的话,也就复用这套就行了,在我们的 func.asm 文件中增加一个函数,如下:


_print_s:        mov  esi,[esp+4H]                 ;保护模式DS=0,数据用绝对地址访问        mov  cl, 0x09                   ;蓝色字体        mov  edi, 0xb8000+22*160        ;指定显示在某行,显卡内存地址也需用绝对地址访问             call printnew        RET
复制代码


如上所示,[esp+4H]是我们传入变量的内存地址,这个博主是怎么知道在下面说明


下面是关于 C 语言函数传参和汇编 NASM 码的关系


没有写过汇编没有关系,咱就是抄,抄着抄着就会了,我们先编写两个函数,看看转成汇编码是什么样子的:


C 代码如下:就是读入两个键盘字符串(没有实现,空的),然后将这两个字符串传入另外一个函数


int add(char* a, char* b);char* get_char();
void start(void){
char *a = get_char(); char *b = get_char(); int c = add(a, b);
if (c == 3) { char *s = "hello world2$"; }}
int add(char* a, char* b) { if (a == "a") { return 1; }; if (b == "b") { return 2; } return 3;}
复制代码


下面的对应的关键汇编码:


_start: ; Function begin        // 下面是从函数中取得字符串(怎么对应到ebx和eax的其实我不是很理解,但先跳过)        push    ebx                                     ; 0120 _ 53        sub     esp, 72                                 ; 0121 _ 83. EC, 48        call    _get_char                               ; 0124 _ E8, 00000000(rel)        mov     ebx, eax                                ; 0129 _ 89. C3        call    _get_char                               ; 012B _ E8, 00000000(rel)        // 查资料是说参数是直接压入栈中,下面就是压入了两个参数,然后调用add函数        mov     dword [esp], ebx                        ; 0130 _ 89. 1C 24        mov     dword [esp+4H], eax                     ; 0133 _ 89. 44 24, 04        call    _add       
_add: sub esp, 28 ; 0000 _ 83. EC, 1C // ?_010对应的就是‘a’,这里就是判断a变量是不是等于a,那[esp+20H]就是变量a,下面变量b类似 cmp dword [esp+20H], ?_010 ; 0003 _ 81. 7C 24, 20, 00000000(d) jz ?_003 ; 000B _ 74, 3B cmp dword [esp+24H], ?_011 ; 000D _ 81. 7C 24, 24, 00000002(d) jz ?_002 ; 0015 _ 74, 19 mov eax, 3 ; 0023 _ B8, 00000003?_001: add esp, 28 ; 0028 _ 83. C4, 1C ret
复制代码


如上所示,我们通过看对应的汇编,得到了取参数是从栈里面取的,但这里的栈先是减了 28,然后又加回来,而且看变量都是+4 递增,得到这些,我们就可以尝试下了


下面就在 C 中对应新增我们的字符串打印函数:


C 代码:我们就 c=3 时,打印 hello world


void print_s(char* s);void start(void){
char *a = get_char(); char *b = get_char(); int c = add(a, b);
if (c == 3) { // 目前显示函数是以$为结束标识 char *s = "hello world2$"; print_s(s); }}
复制代码


汇编 func.asm 新增如下:esp 在最终的尝试下取+4H 即可


_print_s:        mov  esi,[esp+4H]                 ;保护模式DS=0,数据用绝对地址访问        mov  cl, 0x09                   ;蓝色字体        mov  edi, 0xb8000+22*160        ;指定显示在某行,显卡内存地址也需用绝对地址访问             call printnew        RET
复制代码


先把界面相关设置关掉,去掉 setup.asm 开始函数下面的代码:


   MOV    AL,0x13      ; VGA显卡,320x200x8bit     MOV    AH,0x00     INT    0x10     MOV    BYTE [VMODE],8  ; 屏幕的模式(参考C语言的引用)     MOV    WORD [SCRNX],320     MOV    WORD [SCRNY],200     MOV    DWORD [VRAM],0x000a0000
复制代码


最后,编译运行,嘿嘿,成功!如下图:


2.数字转字符串无桌面显示

尝试过写汇编,失败了


然后发现用 C 直接可以实现,所以我们采用 C 原生实现这个功能,如下:


void print_s(char* s);char* int_2_string(int num, char *s);
void start(void){ int num = 320; char s[40]; int_2_string(num, s); print_s(s);}
// 目前默认处理正整数,转换成十进制,先简单点char* int_2_string(int num, char *s) { // 如1 % 10 得到1,则其对应的字符就是'1',目前这种方式最简单 char num_index[] = "0123456789"; // 首先将数字转为十进制,这里是:320 -> '023' // 每次余数是最小位数 char temp[40]; int i = 0; int remain = num % 10; temp[i] = num_index[remain]; num = num / 10; while (num > 0) { i = i + 1; remain = num % 10; temp[i] = num_index[remain]; num = num / 10; }
// 上面是逆序的,所以我们反一下就得到我们想要的 '023' -> '320' int j = 0; while(i > -1) { s[j] = temp[i]; j = j + 1; i = i - 1; }
// 结尾标识符 s[j] = '$'; return s;}
复制代码


函数大致的实现思路就如上所示,当然还好很多处理不完善,但时间紧迫,先跑通再说


写完后,运行,完美显示,如下:


3.桌面显示

先恢复我们的桌面设置,setup.asm 开始函数加入下面的代码


   MOV    AL,0x13      ; VGA显卡,320x200x8bit     MOV    AH,0x00     INT    0x10     MOV    BYTE [VMODE],8  ; 屏幕的模式(参考C语言的引用)     MOV    WORD [SCRNX],320     MOV    WORD [SCRNY],200     MOV    DWORD [VRAM],0x000a0000
复制代码


把我们的函数加进去,C 代码如下:


......
void int_2_string(int num, char *s);
void start(void){ ...... putfont8_asc(vram, xsize, 8, 8, COL8_FFFFFF, "Hollo OS 123456!");
char s[10]; int_2_string(xsize, s); putfont8_asc(vram, xsize, 16, 64, COL8_FFFFFF, s);
for (; ; ) { io_hlt(); }}
// 目前默认处理正整数,转换成十进制,先简单点void int_2_string(int num, char *s) { char num_index[] = "0123456789"; char temp[40]; int i = 0; int remain = num % 10; temp[i] = num_index[remain]; num = num / 10; while (num > 0) { i = i + 1; remain = num % 10; temp[i] = num_index[remain]; num = num / 10; }
int j = 0; while(i > -1) { s[j] = temp[i]; j = j + 1; i = i - 1; }] // 界面显示中是以0x00结尾的,我们对应的进行适配 s[j] = 0x00; return;}
复制代码


如上所示,就函数中的结尾符修改下就行了


然后就是运行,终于成功了!NICE,如下图:


总结

这一步虽然有些坎坷,但搞完还是有很大收获的:知道了 C 函数中的参数传递对应到汇编中是使用栈的:使用栈的话就可以传很多参数了


也看看书后面的内容,哎,感觉有点难,提前抄了,但跑不动,哎,祝我好运

参考链接

发布于: 刚刚阅读数: 3
用户头像

关注

还未添加个人签名 2018-09-09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
自制操作系统日记(8):变量显示_操作系统_萧_InfoQ写作社区