MyOS(七):C语言结合汇编开发操作系统内核

        在保护模式下,除了寻址空间增大,运算能力增强外,还有一大好处就是能将C语言引入内核开发因为C语言编译后的汇编代码,默认的运行环境就是保护模式,所以,只有进入保护模式,那么C语言才有可能介入到开发流程中

 

汇编与C的结合调用

程序入口在foo.asm 中,程序先从foo.asm中的_start处开始执行,在_start中,调用一个函数叫bar_func, 而bar_func 函数由bar.c模块来实现,而bar.c实现的bar_func函数中,又调用一个来自foo.asm实现的函数,叫foo_print
 

foo_print作用是打印一个字符串到屏幕上的

 

foo.asm

extern bar_func

[section .data]
arg1  dd 3
arg2  dd 4

[section .text]
global _start
global foo_print

_start:

mov   eax, dword[arg1]
push  eax
mov   eax, dword [arg2]
push  eax
call  bar_func
add   esp, 8

mov   ebx,0
mov   eax, 1
int   0x80

foo_print:
mov   edx, [esp + 8]
mov   ecx, [esp + 4]
mov   ebx, 1
mov   eax, 4
int   0x80
ret

;%include "bar.asm"

由于需要调用另一个模块的函数,所以开始先要使用extern 声明,要不然编译时,编译器会报错。

由于_start要导出作为整个可执行程序的入口,因此要用global关键字声明

同时,该模块中的foo_print要导出给其他接口使用,所以需要用global声明。

在_start中,在调用bar_func函数前,需要传入参数,C语言的参数传递是通过堆栈实现的,函数如果有多个参数的话,那么最右边的参数先压入堆栈,由于代码中,我们先压入arg1, 然后再压入arg2,所以就相当于以如下方式调用来自C语言模块的接口:

bar_func(arg2, arg1);

根据C语言的函数调用规则,堆栈的回收由调用者负责,所以在_start中,bar_func调用结束后,需要调整堆栈指针esp,

        add esp,8

将堆栈指针往下移动8字节,这就将开头压入堆栈的两个4字节参数,arg1,arg2从堆栈上删除了

 

 

bar.c

extern void foo_print(char* p, int len);

int  bar_func(int a, int b) {
    if (a > b) {
       foo_print("the 1st one\n", 13);
    } else {
       foo_print("the 2nd one\n", 13);
    }

    return 0;
}

根据bar.c中,对foo_print的调用方式来看,最右边的参数是13,表示的是第一个输入参数,也就是字符串的长度。这么看来在foo.asm的foo_print中,[esp+8] 对应于第二个参数,也就是上面的13,[esp+4]对应第一个参数,也就是输入的字符串。

mov ebx, 1
mov eax, 4
int 0x80

上面三句实现Linux的一个系统调用,该调用的作用是将ecx寄存器中指向的内存地址中的字符信息打印到屏幕上

 

编译运行

编译foo.asm:
nasm -f elf32 -o foo.o foo.asm
编译bar.c
gcc -m32 -c -o bar.o bar.c
将两个模块连接在一起了:
ld -m elf_i386 -o foobar foo.o bar.o

编译完成之后

因为Win10 WSL不支持32位程序(泪奔),所以我们到虚拟机上运行

 

 

 

      虽然,我们基本实现了将汇编和C语言模块结合的目的,但这种做法有一个问题,就是最终编译成的可执行文件是elf格式,elf格式文件就是linux的可执行文件,但我们要开发的是系统内核,如果将内核编译成elf格式,那么就不能直接将内核加载到内存直接执行。所以需要想新的办法。

     

       我们的做法是,将C语言编译成的.o模块反汇编,将反汇编的代码贴到foo.asm里面,从而形成单个asm文件,最后编译这个整合在一起的汇编文件,直接生成二进制可执行代码。

用反汇编结合C语言和汇编语言

①先写好汇编代码和对应的C代码。

②用以下命令编译C代码模块,以便后面反汇编

gcc -m32 -fno-asynchronous-unwind-tables -s -c -o bar.o bar.c

     将bar.c重新编译成bar.o,但这里去掉了符号表和调试信息,去掉了符号表反汇编出来的代码就比较简单


③使用反汇编工具objconv

      通过如下教程使用  https://stackoverflow.com/questions/17676026/converting-c-to-nasm-assembly

                                     https://www.agner.org/optimize/#objconv

④下载完后windows可以直接用里面的objconv.exe

linux进入到source目录然后执行build.sh之后会得到objconv可执行程序

linux下编译会报warning但还是能成功得出objconv可执行程序的

⑤用objconv 反汇编C语言生成的目标文件bar.o

./objconv -fnasm bar.o -o bar.asm

于是目录下便有一个反汇编文件bar.asm

 

bar.asm

; Disassembly of file: bar.o
; Tue May 19 23:28:41 2020
; Mode: 32 bits
; Syntax: YASM/NASM
; Instruction set: 80386


global bar_func: function
global __x86.get_pc_thunk.ax: function

extern foo_print                                        ; near
extern _GLOBAL_OFFSET_TABLE_                            ; byte


SECTION .text   align=1 execute                         ; section number 1, code

bar_func:; Function begin
        push    ebp                                     ; 0000 _ 55
        mov     ebp, esp                                ; 0001 _ 89. E5
        push    ebx                                     ; 0003 _ 53
        sub     esp, 4                                  ; 0004 _ 83. EC, 04
        call    __x86.get_pc_thunk.ax                   ; 0007 _ E8, FFFFFFFC(rel)
        add     eax, _GLOBAL_OFFSET_TABLE_-$            ; 000C _ 05, 00000001(GOT r)
        mov     edx, dword [ebp+8H]                     ; 0011 _ 8B. 55, 08
        cmp     edx, dword [ebp+0CH]                    ; 0014 _ 3B. 55, 0C
        jle     ?_001                                   ; 0017 _ 7E, 18
        sub     esp, 8                                  ; 0019 _ 83. EC, 08
        push    13                                      ; 001C _ 6A, 0D
        lea     edx, [?_003+eax]                        ; 001E _ 8D. 90, 00000000(GOT)
        push    edx                                     ; 0024 _ 52
        mov     ebx, eax                                ; 0025 _ 89. C3
        call    foo_print                               ; 0027 _ E8, FFFFFFFC(PLT r)
        add     esp, 16                                 ; 002C _ 83. C4, 10
        jmp     ?_002                                   ; 002F _ EB, 16

?_001:  sub     esp, 8                                  ; 0031 _ 83. EC, 08
        push    13                                      ; 0034 _ 6A, 0D
        lea     edx, [?_004+eax]                        ; 0036 _ 8D. 90, 0000000D(GOT)
        push    edx                                     ; 003C _ 52
        mov     ebx, eax                                ; 003D _ 89. C3
        call    foo_print                               ; 003F _ E8, FFFFFFFC(PLT r)
        add     esp, 16                                 ; 0044 _ 83. C4, 10
?_002:  mov     eax, 0                                  ; 0047 _ B8, 00000000
        mov     ebx, dword [ebp-4H]                     ; 004C _ 8B. 5D, FC
        leave                                           ; 004F _ C9
        ret                                             ; 0050 _ C3
; bar_func End of function


SECTION .data   align=1 noexecute                       ; section number 2, data


SECTION .bss    align=1 noexecute                       ; section number 3, bss


SECTION .rodata align=1 noexecute                       ; section number 4, const

?_003:                                                  ; byte
        db 74H, 68H, 65H, 20H, 31H, 73H, 74H, 20H       ; 0000 _ the 1st 
        db 6FH, 6EH, 65H, 0AH, 00H                      ; 0008 _ one..

?_004:                                                  ; byte
        db 74H, 68H, 65H, 20H, 32H, 6EH, 64H, 20H       ; 000D _ the 2nd 
        db 6FH, 6EH, 65H, 0AH, 00H                      ; 0015 _ one..


SECTION .text.__x86.get_pc_thunk.ax align=1 execute     ; section number 5, code

__x86.get_pc_thunk.ax:; Function begin
        mov     eax, dword [esp]                        ; 0000 _ 8B. 04 24
        ret                                             ; 0003 _ C3
; __x86.get_pc_thunk.ax End of function


⑥打开foo.asm, 将里面的_start, 修改成main, 这一步在后面我们编译系统内核时可以不用,现在这么做,主要是想编译成linux可执行文件

  在foo.asm末尾,通过语句:%include “bar.asm” 将第五步反汇编的C模块代码引入foo.asm。

然后将bar.asm的开头部分删掉

之前

删掉后

⑦运行命令编译foo.asm

nasm -f elf32 foo.asm

执行这一步后,目录上会出现foo.o二进制文件

 

⑧执行命令

gcc -m32 foo.o -o foo

这一步将foo.o与系统模块连接成可执行文件,编译系统内核时,这一步就不需要。

在这里如果报错的话是因为当前架构的gcc是64位,但需要32位支持文件,因此需要安装gcc-multilib。
      sudo apt install gcc-multilib



10. 运行结果:./foo, 就可以看到运行结果了。

 

 

在之后,我们将主要依赖C语言进行内核的开发,只有当C语言力不能逮,特别是需要操作硬件时,才会使用汇编语言,下一节,我们看看如何使用C语言绘制操作系统GUI.

 

参考:

https://blog.csdn.net/tyler_download/article/details/52468520


https://blog.csdn.net/qq_31567335/article/details/100531788?ops_request_misc=&request_id=&biz_id=102&utm_term=C%E8%AF%AD%E8%A8%80%E7%BB%93%E5%90%88%E6%B1%87%E7%BC%96%E5%BC%80%E5%8F%91%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8%20%20%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E2%80%94&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~sobaiduweb~default-4-100531788

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值