TCC

来自osdev
Zhang3讨论 | 贡献2022年3月31日 (四) 08:01的版本 (创建页面,内容为“{{In Progress}} 本文描述了如何使用FASMTiny C Compiler(又名TCC)制作一个示例ELF内核。 也可以使用NASM(Bare_Bones_With_NASM)。 TCC是一个小型且快速的C编译器,它产生x86,x86_64或ARM代码,并生成PE或ELF可执行文件。 TCC正朝着完全符合ISOC99的方向发展,并且可以像FASM一样自我编译。(译者注:自我编译,指用自身的源代码和编译器可以编译出一…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

这个页面正在建设中! 此页面或部分内容仍在改进中,因此可能还不完整。 其内容可能会在不久的将来更改。

本文描述了如何使用FASMTiny C Compiler(又名TCC)制作一个示例ELF内核。 也可以使用NASM(Bare_Bones_With_NASM)。 TCC是一个小型且快速的C编译器,它产生x86,x86_64或ARM代码,并生成PE或ELF可执行文件。 TCC正朝着完全符合ISOC99的方向发展,并且可以像FASM一样自我编译。(译者注:自我编译,指用自身的源代码和编译器可以编译出一个新的编译器)

TCC还包括一个链接器和一个汇编器(仅x86)。 但是此汇编器是有限的: 不支持16/64位,支持到MMX的指令。

注: Windows版本的TCC不生成ELF可执行文件,而只生成目标文件。 如果要在Windows上使用本教程,则需要在没有PE支持的情况下重新编译TCC。 如果不使用Windows,可以跳过此步骤。

构建TCC带ELF支持

Windows

1. 下载 TCC源代码32位TCC和(如果你有64位操作系统) 64位TCC

2. 解压源代码文件夹tcc-0.9.26。

3. 将32位TCC文件保存到TCC-0.9.26所在位置的名为“Win32”的文件夹中。 如果你有64位操作系统,请创建一个win64文件夹,并将64位文件保存到同一位置的名为 “win64” 的文件夹中。

4. 打开记事本或其他文本编辑器,然后输入以下内容:

@echo off

set \p VERSION = < .VERSION
echo > config.h #define TCC_VERSION "%VERSION%"

set targetP=I386
set B=32
goto begin

:x86_64
set targetP=X86_64
set B=64
goto begin

:begin
set targetF=PE
set CC=..\win%B%\tcc.exe -O0 -s -fno-strict-aliasing
set P=%B%

:start
if %targetF%==ELF set P=%B%-elf
set target=-DTCC_TARGET_%targetF% -DTCC_TARGET_%targetP%

:tools
%CC% %target% win%P%\tools\tiny_impdef.c -o win%P%\tiny_impdef.exe
%CC% %target% win%P%\tools\tiny_libmaker.c -o win%P%\tiny_libmaker.exe

:libtcc
if not exist win%P%\libtcc\nul mkdir win%P%\libtcc
copy libtcc.h win%P%\libtcc\libtcc.h
%CC% %target% -shared -DLIBTCC_AS_DLL -DONE_SOURCE libtcc.c -o win%P%\libtcc.dll
win%P%\tiny_impdef win%P%\libtcc.dll -o win%P%\libtcc\libtcc.def

:tcc
%CC% %target% tcc.c -o win%P%\tcc.exe -ltcc -Lwin%P%\libtcc

:copy_std_includes
copy include\*.h win%P%\include

:libtcc1.a
win%B%\tcc %target% -c lib\libtcc1.c -o win%P%\libtcc1.o
win%B%\tcc %target% -c win%P%\lib\crt1.c -o win%P%\crt1.o
win%B%\tcc %target% -c win%P%\lib\wincrt1.c -o win%P%\wincrt1.o
win%B%\tcc %target% -c win%P%\lib\dllcrt1.c -o win%P%\dllcrt1.o
win%B%\tcc %target% -c win%P%\lib\dllmain.c -o win%P%\dllmain.o
win%B%\tcc %target% -c win%P%\lib\chkstk.S -o win%P%\chkstk.o
goto lib%B%

:lib32
win%B%\tcc %target% -c lib\alloca86.S -o win%P%\alloca86.o
win%B%\tcc %target% -c lib\alloca86-bt.S -o win%P%\alloca86-bt.o
win%B%\tcc %target% -c lib\bcheck.c -o win%P%\bcheck.o
win%P%\tiny_libmaker win%P%\lib\libtcc1.a win%P%\libtcc1.o win%P%\alloca86.o win%P%\alloca86-bt.o win%P%\crt1.o win%P%\wincrt1.o win%P%\dllcrt1.o win%P%\dllmain.o win%P%\chkstk.o win%P%\bcheck.o
@goto the_end

:lib64
win%P%\tcc %target% -c lib\alloca86_64.S -o win%P%\alloca86_64.o
win%P%\tiny_libmaker win%P%\lib\libtcc1.a win%P%\libtcc1.o win%P%\alloca86_64.o win%P%\crt1.o win%P%\wincrt1.o win%P%\dllcrt1.o win%P%\dllmain.o win%P%\chkstk.o

:the_end
del win%P%\*.o

@if %targetF%==PE (
	@set targetF=ELF
	@goto start
)

@if %B%==64 goto finished&amp;#10;@if _%PROCESSOR_ARCHITEW6432%_==_AMD64_ goto x86_64
@if _%PROCESSOR_ARCHITECTURE%_==_AMD64_ goto x86_64

:finished

5. 使用你想要的名称保存到TCC-0.9.26,但扩展名必须是.bat。 如果使用记事本,则必须将保存对话框的类型从 “文本文档” 更改为 “所有文件”。

6. 运行脚本,确保所有内容都正确编译(请注意,可能会有来自不兼容指针类型和绑定检查的分配的警告,在特定环境中不支持malloc,但这些都可以接受),并且在win32-elf中,你应该有一个工作的elf编译器。 你还应该拥有用于32位和64位PE编译器的Win32和Win64,以及Win64-ELF中的64位ELF编译器。 注意: 64位编译器不会在非64位操作系统上编译。 如果编译TCC时出现错误,只需将“@echo off”更改为“@echo on”,然后再次运行脚本,看看哪里出了问题。

Linux

你的发行版可能已经提供了TCC的包。 如果没有,请从https://download.savannah.gnu.org/releases/tinycc/下载源代码,然后从那里继续。 你知道如何从头开始构建程序,对吗?

一个小内核示例

这个小例子以ELF格式构建了一个小内核,可以通过Grub引导。

start32.asm

;  Tutorial: A small kernel with Fasm & TCC
;  By Tommy.
 
        format elf
        use32

;
; Equates
;
MULTIBOOT_PAGE_ALIGN	    equ (1 shl 0)
MULTIBOOT_MEMORY_INFO	    equ (1 shl 1)
MULTIBOOT_AOUT_KLUDGE	    equ (1 shl 16)
MULTIBOOT_HEADER_MAGIC	    equ 0x1BADB002
MULTIBOOT_HEADER_FLAGS	    equ MULTIBOOT_PAGE_ALIGN or MULTIBOOT_MEMORY_INFO
MULTIBOOT_CHECKSUM          equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

        section '.text' executable
;
; Multiboot header
;
dd MULTIBOOT_HEADER_MAGIC
dd MULTIBOOT_HEADER_FLAGS
dd MULTIBOOT_CHECKSUM


;
; Kernel entry point.
;
        public  _start
        extrn   kmain
_start:
        ; Call the main kernel function.
        call    kmain
 
@@:
        jmp     @b

kernel.c

/*  Tutorial: A small kernel with Fasm & TCC
 *  By Tommy.
 */

/*
 * Main kernel function.
 */
void
kmain (void)
{
    *((unsigned char *) 0xB8000) = 'H';
    *((unsigned char *) 0xB8001) = 0x1F;
    *((unsigned char *) 0xB8002) = 'E';
    *((unsigned char *) 0xB8003) = 0x1F;
    *((unsigned char *) 0xB8004) = 'L';
    *((unsigned char *) 0xB8005) = 0x1F;
    *((unsigned char *) 0xB8006) = 'L';
    *((unsigned char *) 0xB8007) = 0x1F;
    *((unsigned char *) 0xB8008) = 'O';
    *((unsigned char *) 0xB8009) = 0x1F;
}

编译和链接

使用以下选项汇编start32.asm:

fasm start32.asm

编译kernel.c用:

tcc -c kernel.c

然后将整件东西链接起来:

tcc -nostdlib -Wl,-Ttext,0x100000 start32.o kernel.o -o kernel-i386.elf

如果你更喜欢二进制形式,例如,如果你正在使用自己的不支持ELF的引导加载程序,请使用以下链接方式:

tcc -nostdlib -Wl,-Ttext,0x100000 -Wl,--oformat,binary -static start32.o kernel.o -o kernel-i386.bin

就这样!

内联汇编

TCC支持内联GAS语法汇编,如GCC:

__asm__ __volatile__("hlt");

你可以用这个特性为你处理好很多事情,比如在Bochs调试:

#define breakpoint() __asm__ __volatile__("xchg %bx, %bx");

void bochs_print (char *string) {
	char *c = string;
	while (*c != '\0') {
		outb(0xE9, *c); // may be outportb
		c++;
	}
}

然后将其添加到你的bochsrc中。文本编辑器中的bxrc文件:

port_e9_hack: enabled=1
magic_break: enabled=1

并从Boch的安装位置执行bochsdbg.exe,而不是bochs.exe。

关于GDT和结构体的警告

根据TCC的创始人法布里斯·贝拉德(Fabrice Bellard)的说法,并且被(痛苦地)证明是正确的: "在TCC中,只有结构字段或变量声明支持‘packed’属性,整个结构体不支持。 因此,你的解决方案是将其添加到打包结构体的每个字段中。" 因此,如果使用结构体存储GDT条目或GDTR,请注意,如果没有正确指定结构的打包,那么在加载GDT时会遇到问题。 在创建结构体时,请使用类似以下内容:

// 我们使用属性 'packed' 告诉TCC不要更改结构中的任何对齐方式。
struct some_struct {
   unsigned char a __attribute__((packed));
   unsigned char b __attribute__((packed));
} __attribute__((packed));
// 最后一个属性可以保留,它不会干扰编译或输出,它可能对
// 保持与GCC的兼容性有用,只要上述属性不干扰GCC即可。

而不是这样:

//我们使用属性“packed”告诉GCC不要更改结构中的任何对齐方式。
struct some_struct {
   unsigned char a;
   unsigned char b;
} __attribute__((packed));


内联函数警告

TCC不支持函数内联,因为 'inline' 关键字会被忽略,所以如果一个函数需要内联,你必须使用defins预定义代替。

stdint.h

TCC不包括stdint.h、 但stddef.h中提供了所需的所有typedefs类型定义 要使用stdint.h,将以下代码作为stdint.h放入你的内核包含路径中。 这将使你的代码与gcc和tcc兼容。

/* stdint.h */

#ifdef __TINYC__
/* tcc */
#include <stddef.h>
#else
/* assume gcc */
#include_next <stdint.h>
#endif