Fabrice Bellard is a French computer programmer known for writing FFmpeg, QEMU, and the Tiny C Compiler and 许许多多别的。
我修改了下他写的otccelfn.c,增加了一点点功能,增强了可读性。这个C编译器能编译像下面这样的C程序:
gcd(long a, long b) {
long i;
i = a - b;
if (i == 0) return a;
else if (i > 0) return gcd(i, b);
else return gcd(a, -i);
}
tonum(long s) {
long n, i;
for (n = 0; ;s++) {
if ((i = *(char*)s) == 0) break;
n = n * 10 + (i - '0');
}
return n;
}
main() {
long i, j;
long ;
i = tonum("20"); j = tonum("24");
printf("%ld\n", gcd(i, j));
return 0;
}
说明:
- 数据类型只能用long. x64下int是32位,long和void*都是64位,x86下它们都是32位。所以以上程序(gcd.c)用gcc -m32,-m64,或默认,都能编译运行。
- gcd是Greatest Common Divisor (最大公约数)的缩写。
编译器的全部源码:
// Adapted from otccelfn.c (written by Fabrice Bellard)
#include
#include
#include
#pragma warning(disable:4786)
#include
#include <string>
#include
编译器里:srcfp = fopen("gcd.c", "rt"); assert(srcfp); 这当然很好改了。
编译器的输出是code.bin和data.bin,也写死了,也好改。code.bin里是x86 32位机器码,在Linux下,可用objdump反汇编查看:
objdump -b binary -m i386 -D code.bin
如:
0: e8 18 00 00 00 call 0x1d
5: cd 80 int $0x80
7: 55 push %ebp
8: 89 e5 mov %esp,%ebp
a: 81 ec 00 00 00 00 sub $0x0,%esp
10: b8 03 00 00 00 mov $0x3,%eax
15: 89 85 08 00 00 00 mov %eax,0x8(%ebp)
1b: c9 leave
1c: c3 ret
1d: 55 push %ebp
1e: 89 e5 mov %esp,%ebp
20: 81 ec 04 00 00 00 sub $0x4,%esp
26: b8 04 00 00 00 mov $0x4,%eax
2b: 89 85 fc ff ff ff mov %eax,-0x4(%ebp)
31: 81 ec 04 00 00 00 sub $0x4,%esp
37: 8b 85 fc ff ff ff mov -0x4(%ebp),%eax
3d: 89 84 24 00 00 00 00 mov %eax,0x0(%esp)
44: e8 be ff ff ff call 0x7
49: 81 c4 04 00 00 00 add $0x4,%esp
4f: c9 leave
50: c3 ret
下面是个玩具虚拟机的全部代码(97行):
#include
#include
typedef unsigned char byte;
byte mem[65536];
void load(int i) {
FILE* fp = fopen(i ? "data.bin" : "code.bin", "rb");
fseek(fp, 0, SEEK_END); int n = ftell(fp); fseek(fp, 0, SEEK_SET);
fread(mem + i, 1, n, fp); fclose(fp);
}
int r32(byte* p) { return *p | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); } // little endian
int r32(int a) { return r32(mem + a); }
void w32(int a, int v) { *((int*)&mem[a]) = v; }
int reg[16];
#define eax reg[0]
#define ecx reg[2]
#define edx reg[3]
#define res reg[10]
#define eip reg[11]
#define ebp reg[14]
#define esp reg[15]
void set_al(int c) { eax = (eax & 0xffffff00) | (c ? 1 : 0); }
void print() {
printf("%3x: %2x | ax %8x cx %8x dx %4x bp %4x sp %4x | -4: ", eip, mem[eip], eax, ecx, edx, ebp, esp);
for (int j = esp - 4; j < esp + 12; j++) printf(mem[j] ? "%02x " : " ", mem[j]);
puts("+c");
//getchar();
}
void quit() { printf("Press Enter to quit."); getchar(); exit(0); }
#define _(i) break;
int main() {
load(0), load(0x6800);
for (esp = 65532;;) {
int oip = eip, i, j;
byte op = mem[eip], *p = &mem[eip];
print();
switch (op) {
case 0x01: eax += ecx; eip += 2; _(add %ecx %eax)
case 0x0f:
if (p[1] == 0x84) { eip += 6; if (res == 0) eip += r32(p+2); }
else if (p[1] == 0x9f) { set_al(res > 0); eip += 3; }
else if (p[1] == 0x94) { set_al(res == 0); eip += 3; }
else if (p[1] == 0xaf) { eax *= ecx; eip += 3; }
else if (p[1] == 0xbe) { eax = char(mem[eax]); eip += 3; }
break;
case 0x29: eax -= ecx; eip += 2; _(sub %ecx %eax)
case 0x31: eax ^= eax; eip += 2; _(xor %eax %eax)
case 0x39: res = ecx - eax; eip += 2; _(cmp %eax %ecx)
case 0x50: w32(esp -= 4, eax); ++eip; _(push %eax)
case 0x55: w32(esp -= 4, ebp); ++eip; _(push %ebp)
case 0x59: ecx = r32(esp); esp += 4; ++eip; _(pop %ecx)
case 0x81:
if (p[1] == 0xec) { esp -= r32(p+2); eip += 6; }
else if (p[1] == 0xc4) { esp += r32(p+2); eip += 6; }
break;
case 0x83: i = r32(p+2)+ebp; w32(i, r32(i)+p[6]); eip += 7; _(addl imm imm(%ebp))
case 0x85: res = eax & eax; eip += 2; _(test %eax %eax)
case 0x89:
if (p[1] == 0xe5) { ebp = esp; eip += 2; } // mov %esp,%ebp
else if (p[1] == 0x84) { w32(esp + r32(p+3), eax); eip += 7; } // mov %eax,imm(%esp)
else if (p[1] == 0x85) { w32(ebp + r32(p+2), eax); eip += 6; } // mov %eax,imm(%ebp)
break;
case 0x8b:
if (p[1] == 0x85) { eax = r32(ebp + r32(p+2)); eip += 6; } // mov imm(%ebp),%eax
break;
case 0x8d: eax = r32(p+2) + ebp; eip += 6; _(lea imm(%ebp) %eax)
case 0x91: i = eax; eax = ecx; ecx = i; ++eip; _(xchg %eax %ecx)
case 0x92: i = eax; eax = edx; edx = i; ++eip; _(xchg %eax %edx)
// cltd converts the signed long in EAX to a signed double long in EDX:EAX
case 0x99: edx = (eax < 0) ? -1 : 0; ++eip; _(cltd)
case 0xb8: eax = r32(p+1); eip += 5; _(mov imm %eax)
case 0xb9: ecx = r32(p+1); eip += 5; _(mov imm %ecx)
case 0xc3: { eip = r32(esp); esp += 4; } _(ret)
case 0xc9: { ebp = r32(esp = ebp); esp += 4; ++eip;} _(leave)
case 0xcd: quit();
case 0xe8:
eip += 5; // return to next instruction
j = eip + (i = r32(p + 1));
// callee: stack: LOW local_vars ebp ret_addr param1 param2 HIGH
// caller: sub $8, %esp; movl %eax, 0(%esp); movl %eax, 4(%esp); add $8, %esp
// cdecl: caller cleans stack; fastcall, pascal, WINAPI
if (!j) puts(""), printf((char*)&mem[r32(esp)], r32(esp + 4)), puts("");
else { w32(esp -= 4, eip); eip += i; }
break;
case 0xe9: eip += 5 + r32(p + 1); _(jmp)
case 0xf7:
if (p[1] == 0xd8) eax = -eax; // neg %eax
else if (p[1] == 0xf9 && ecx) { edx = eax % ecx; eax /= ecx; } // idiv %ecx
eip += 2; break;
}
if (oip == eip) quit();
}
return 0;
}
它读入code.bin和data.bin并解释执行,输出像这样:
205: e8 | ax 4 cx 0 dx 0 bp fff4 sp ffe4 | -4: 18 0c 68 04 18 +c
4
20a: 81 | ax 4 cx 0 dx 0 bp fff4 sp ffe4 | -4: 18 0c 68 04 18 +c
210: b8 | ax 4 cx 0 dx 0 bp fff4 sp ffec | -4: 04 18 14 +c
215: e9 | ax 0 cx 0 dx 0 bp fff4 sp ffec | -4: 04 18 14 +c
21a: c9 | ax 0 cx 0 dx 0 bp fff4 sp ffec | -4: 04 18 14 +c
21b: c3 | ax 0 cx 0 dx 0 bp 0 sp fff8 | -4: 05 +c
5: cd | ax 0 cx 0 dx 0 bp 0 sp fffc | -4: 05 +c
以上代码都既可以用Visual C++ 6,也可以用gcc/g++ version 10.2.1 20210110编译。