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

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

代码仓库地址: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  printnew
printover:
        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

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

image.png

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;
}

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

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

image.png

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,如下图:

image.png

总结

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

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

参考链接

相关文章
|
6月前
|
程序员 C++
开心档之 C++ 引用
开心档之 C++ 引用
|
安全 程序员 C++
开心档之C++ 引用
【摘要】 C++ 引用引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。C++ 引用 vs 指针引用很容易与指针混淆,它们之间有三个主要的不同:不存在空引用。引用必须连接到一块合法的内存。一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。引用必须在创建时被初始化。指针可以在任何时间...
|
存储 编译器 C++
开心档之开发入门网-C++ 变量类型
开心档之开发入门网-C++ 变量类型
|
Linux C语言 Python
自制操作系统日记(5):跳转到C语言执行
在上篇中切换了CPU的64位模式,但后面是失败的,并没有真正切换,也没有相关的验证代码,本篇中终于修正并执行了C代码
|
Python
自制操作系统日记(7):字符串显示
上篇中我们在屏幕上画出了界面的大致轮廓,系统有了点模样,本篇继续跟着书籍,让程序中的字符串显示在屏幕上
|
编译器
自制操作系统日记(一):显示hello world开始旅程
最近看了不少底层方面的东西,但还是得动手才能真正掌握,感觉操作系统也能整整了,于是就有了这系列,惯例的以hello开始
|
Rust Shell Linux
自制操作系统日记(三):加载其他文件执行
上篇中我们成功将软盘数据读取到内存并显示到屏幕上,接下来我们将加载其他的文件并执行文件代码
|
Python
自制操作系统日记(四):进入64位模式
在上篇中从初始启动文件中加载了loader文件进行执行,本篇将在loader进行CPU模式切换,进入64位模式
|
C语言
自制操作系统日记(6):静态桌面初步
在上篇中我们成功的加载跳转执行了C语言的代码,本篇中将跟随书籍,初步展示了一个系统页面的初步界面,看到桌面那刻还是有点意思的
|
JSON 小程序 前端开发
零基础学小程序 —— 页面配置和网络数据请求(五)(完结)
零基础学小程序 —— 页面配置和网络数据请求(五)(完结)
184 0
零基础学小程序 —— 页面配置和网络数据请求(五)(完结)