分类:
2010-06-28 13:50:39
【linux 下编程】
一、用NASM与C语言在LINUX平台编程
初次接触linux 还真有点适应困难,那么现在就来慢慢适用它吧!
(在linux 下访问windows共享文件夹可以:
mount -t smbfs -o username=name,password=pwd,ip=192.168.*.* //MachineName/share /home/share)
在linux 用nasm 编写一个Hello World! 需要的:
1、编译:nasm -f elf 386.asm -o a.o 编译一个elf(可执行连接格式)文件,它是32位intel标准可移持二进制格式。
2、链接:ld -s a.o -o a 链接成 elf文件
3、运行 ./a 就可以运行了。
那么就来个asm 与c 同步编写代码吧.
;------------386.asm-----------
extern choose ; int choose(int a, int b);导入函数
[section .data] ; 全局数据段
num1st dd 3
num2nd dd 4
[section .text] ; 代码段
global _start ; 我们必须导出 _start 这个入口,以便让链接器识别。
global myprint ; 导出这个函数为了让 bar.c 使用
_start:
push num2nd ; ┓
push num1st ; ┃
call choose ; ┣ choose(num1st, num2nd);
add esp, 4 ; ┛
mov ebx, 0
mov eax, 1 ; sys_exit
int 0x80 ; 系统调用
; void myprint(char* msg, int len)
myprint:
mov edx, [esp + 8] ; len
mov ecx, [esp + 4] ; msg
mov ebx, 1
mov eax, 4 ; sys_write
int 0x80 ; 系统调用
ret
;------------------bar.c-------------------
void myprint(char* msg, int len);
int choose(int a, int b)
{
if(a >= b){
myprint("the 1st one\n", 13);
}
else{
myprint("the 2nd one\n", 13);
}
return 0;
}
可以看到在汇编代码区有两重要的说明。
一个是Global (导出),再一个是extern(导入)。导出的作用是将当前函数名提供给其它二进制代码块来调用。
不管是汇编语言还是C语言写程序 最终都会汇编或者编译成二进制链接文件.那么此时就可以将这两个二进制块链接起来。定义外部导入函数也好内部导出也好。都是在链接的时候将对应的函数引用改成对应的二进制代码块地址。
;-------------------------------------------------------------------------------------------------------------------
在linux 平台分别用:
nasm -f elf 386.asm -o 386.o ;汇编成elf格式的链接文件
gcc -c bar.c -o bar.o ;gcc 编译成elf格式的链接文件
ld -s 386.o bar.o -o 386bar ;链接386.o与bar.o,变成ELF格式文件 386bar 它可以直接在linux 平台运行。
二、ELF(Executable Linkable Format)可执行可链接文件格式.
下面就是ELF的头部信息:
#define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Haif e_ehsize;
Elf32_Haif e_phentsize;
Elf32_Haif e_phnum;
Elf32_Haif e_shentsize;
Elf32_Haif e_shnum;
Elf32_Haif e_shstrndx;
}Elf32_Ehdr;
为了支持从8位到32位不同架构的处理器,定义了多个字段。以达到支持与机器无关执行代码。
下面就说说这个ELF头结构体各字段的具体意义:
1、e_ident字段:
它里面包含了字符串"ELF",还有其他一些与指令无关的信息。
2、e_type字段:
它标识的是该文件的类型,等于2表示他是exectuable file可执行程序。
3、e_machine字段:
它表示该程序运行的硬件平台,如果是3表示运行在intel 80386体系上。
4、e_version字段:
它表示文件版本信息.
5、e_entry字段:
它表示程序的入口地址,也就是线性地址,一般的地址装入到 0x80488080。
6、e_phoff字段:
program header table 在文件的偏移量(字节单位).。
7、e_shoff字段:
Section header table 在文件中的偏移量(字节单位)。
8、e_flags字段:
对IA32(intel 32)来说此项目为0.
9、e_ehsize字段:
它表示ELF header的大小(字节)。
10、e_phentsize字段:
Program header table 中每一条目的大小。
11、e_phnum字段:
Program header条目总数量。
12、e_shentsize字段:
Section header 单个条目的大小.
13、e_shnum字段:
Section header 条目总数量
14、e_shstrdx字段:
含有所有节名称字符串表的节是在哪个Section条目号,(从零开始数),
;-------------------------------------------------------------------------------------------------------------------
好了一个ELF Header信息说完了。它的大小也就是0x34个字节。接触过WINDOWS PE格式的朋友肯定清楚 它的结构作用与PE有点类似。都是由操作系统的装载器载入的。
ELF Header信息保护了很多文件本身信息,那么这个程序的运行信息保存在哪呢?
它保存在Program Header Table里面。其中一个Program header项就代表一个段。
它的结构是这样的(它的偏移位置在e_phoff处):
typef struct{
Elf32_Word p_type;段类型
Elf32_ Off p_offset;段的第一个字节在文件中的偏移
Elf32_Addr p_vaddr ; 相当内存的VA值
Elf32_Addr p_paddr ; 物理地址保留
Elf32_Word p_filesz ;段在文件中占用的大小
Elf32_Word p_memsz ;段在内存中的大小
Elf32_Word p_flags ;与段相关的标志
Elf32_Word p_aglin ;段对齐方式
}Elf32_Phdr;
链接了ELF Header 与Program Header Table 后我们就可以正常的加载ELF文件到内存中了。
那么接下就开始行动.
;----------------------------------------------------------------------------------------------------------------------
三、加载C语言编写的ELF文件内核到内存:
1、准备引导程序boot.asm,
它的功能是引导一个软驱。并且在软驱的根目下寻找 loader.bin (用来加载Kernel.bin内核的加载器程序);
找到loader.bin 后并把主控权交给Loader.bin。
2、准备加载内核的加载器Loader.asm.
它是从引导程序跳转过来的。这段程序的主要功能是在根目录下寻址Kernel.bin内核程序。
找到Kernel.bin内核模块后。交控制权交给Kernel.bin。
3、准备Kernel.asm 内核代码块。
它是被Loader加载到8000h物理地址段的。它的大部分模块可以用C语言来写.只要在开始的汇编程序里导出
global _start 就行了。
导出用关键字global.
导入用关键字extern
可以看出来 现在我们的程序 已经从引导到加载内核再到系统内核了。那么接下来的工作就是要编写内核代码了。不过既然我们是386保护模式编程,那么在编写内核之前还要在Loader.bin 里转换一个进入386保护模式。!
可以在Loader.asm 里面加入跳转代码:
; 加载 GDTR
lgdt [GdtPtr]
...
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
jmp SeclectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)
跳到Loader.bin里面的保护模式段内。进行保护模式的初始化工作.
在保护模式的中断。TSS 等一些毕业的信息定义好后,就可以加载Kernel 内核到内存中了。那么这个时候的主控制权就交给了ELF格式的Kernel 文件了.
四、【总结】:
从开机引导到装入Kernel 转入保护模式具体步骤:
1、Boot.bin
Boot.bin 里面包含了引导扇区的信息,它是FAT12文件系统.
它主功能就是被BIOS装入7c00,然后执行本身的指令。boot在FAT12文件系统下的根目录搜寻Loader.bin程序。其搜索方式是按扇区方式查找的。在七中已经对FAT12有了详细的介绍。找到Loader.bin 文件后。将它加载到指定的内存。并且跳转到Loader.bin文件里的第一个字节。到此为止Boot.bin的引导功能就全部完成了。
2、Loader.bin
它是被Boot.bin引导程序加载到内存的,它的主要功能也是在根目录下搜索文件。不过它搜索的是Kernel.bin 内核文件。当成功找到后。并不会马上跳转到Kernel.bin .而是在自己的段中进行386保护模式的初始化工作。进行 GDT加载。IDT加载,TSS加载、cr3分页开启等一系列保护模式需要的准备工作。当准备工作做完后 ,才考虑跳转到Kernel 内核中去。
第一步跳到Kernel时,它从第一个字节就开始运行了。不过在进入Kernel里面后就需要用到ELF格式的内核程序了。因为现在我们可以用C语言编写内核了。需要根据ELF文件格式找到对应代码段数据。并且把它装入到指定的内存中去 然后才交给它主控制权。
那么就在下章具体的了解 Loader.bin 跳转到 Kernel(ELF格式内核)。