在保护模式下,除了寻址空间增大,运算能力增强外,还有一大好处就是能将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