JIT脚本引擎:完成链接器的核心功能
光能编译汇编还是不行的,因为很多东西在编译的时候不知道,典型的比如放常量那部分的指针等等。主要原因还是因为x87(指FPU部分)没有指令包含浮点立即数,所有装载浮点常数的指令都要求提供指针。所以诸如double a=1.2;之类的代码,需要将1.2预先放置在一个地方然后确定指针的位置。
于是就遇到了一个问题,如何将编译后才知道的指针地址写进去呢?唯一的办法就是在二进制代码那里留空,然后使用一张新表记录哪些地方是需要链接的时候填充的。于是可以使用如下结构来构造一张链接符号表:
1
struct
LinkingRef
2 {
3 VInt ID;
4 VInt Offset;
5 VSize Position;
6 VByte Bits; // 0->8, 1->16, 2->32
7 VInt Instruction;
8 };
其中ID代表链接的对象,譬如可以将0作为常数缓冲区的指针,其他数字作为外部函数的地址等等。Offset代表一个常量偏移,可以将ID所代表的地址加上一个数字之后存放在指令缓冲区的Position偏移处。至于地址的大小使用Bits来表达(因为整数也可以如此搞)。Instruction记录的是使用这个链接单元的指令序号,用于生成错误信息。
2 {
3 VInt ID;
4 VInt Offset;
5 VSize Position;
6 VByte Bits; // 0->8, 1->16, 2->32
7 VInt Instruction;
8 };
于是在编译之后就可以填充链接信息,然后进行链接了。这个做完以后就只剩下一个问题了。我们不仅需要常量缓冲区、还需要变量缓冲区,那么如何在常量缓冲区填充之后锁定呢(为了在试图修改的时候抛出异常)?Windows API提供了VirtualAlloc、VirtualProtect和VirtualFree来帮我们完成这项工作。为了简化操作,使用以下的类用于控制内存空间:
1
struct
VL_AsmExecutable
2 {
3 private :
4 VPointer FConstantBuffer;
5 VPointer FVariableBuffer;
6 VPointer FInstructionBuffer;
7 VInt FConstantSize;
8 VInt FVariableSize;
9 VInt FInstructionSize;
10 VBool FAvailable;
11
12 public :
13 VL_AsmExecutable();
14 ~ VL_AsmExecutable();
15
16 void Allocate(VInt Constant , VInt Variable , VInt Instruction);
17 void Protect();
18 void Release();
19 VPointer GetConstant();
20 VPointer GetVariable();
21 VPointer GetInstruction();
22 };
2 {
3 private :
4 VPointer FConstantBuffer;
5 VPointer FVariableBuffer;
6 VPointer FInstructionBuffer;
7 VInt FConstantSize;
8 VInt FVariableSize;
9 VInt FInstructionSize;
10 VBool FAvailable;
11
12 public :
13 VL_AsmExecutable();
14 ~ VL_AsmExecutable();
15
16 void Allocate(VInt Constant , VInt Variable , VInt Instruction);
17 void Protect();
18 void Release();
19 VPointer GetConstant();
20 VPointer GetVariable();
21 VPointer GetInstruction();
22 };
这里是实现部分:
1
VL_AsmExecutable::VL_AsmExecutable()
2 {
3 FConstantBuffer = 0 ;
4 FVariableBuffer = 0 ;
5 FInstructionBuffer = 0 ;
6 FConstantSize = 0 ;
7 FVariableSize = 0 ;
8 FInstructionSize = 0 ;
9 FAvailable = false ;
10 }
11
12 VL_AsmExecutable:: ~ VL_AsmExecutable()
13 {
14 Release();
15 }
16
17 void VL_AsmExecutable::Allocate(VInt Constant , VInt Variable , VInt Instruction)
18 {
19 Release();
20 FConstantSize = Constant ? Constant: 1 ;
21 FVariableSize = Variable ? Variable: 1 ;
22 FInstructionSize = Instruction ? Instruction: 1 ;
23 FConstantBuffer = VirtualAlloc( 0 ,FConstantSize,MEM_COMMIT,PAGE_READWRITE);
24 FVariableBuffer = VirtualAlloc( 0 ,FVariableSize,MEM_COMMIT,PAGE_READWRITE);
25 FInstructionBuffer = VirtualAlloc( 0 ,FInstructionSize,MEM_COMMIT,PAGE_READWRITE);
26 FAvailable = true ;
27 }
28
29 void VL_AsmExecutable::Protect()
30 {
31 if (FAvailable)
32 {
33 DWORD OldProtect = 0 ;
34 VirtualProtect(FConstantBuffer,FConstantSize,PAGE_READONLY, & OldProtect);
35 VirtualProtect(FInstructionBuffer,FInstructionSize,PAGE_EXECUTE_READ, & OldProtect);
36 }
37 }
38
39 void VL_AsmExecutable::Release()
40 {
41 if (FAvailable)
42 {
43 VirtualFree(FConstantBuffer,FConstantSize,MEM_RELEASE);
44 VirtualFree(FVariableBuffer,FVariableSize,MEM_RELEASE);
45 VirtualFree(FInstructionBuffer,FInstructionSize,MEM_RELEASE);
46
47 FConstantBuffer = 0 ;
48 FVariableBuffer = 0 ;
49 FInstructionBuffer = 0 ;
50 FConstantSize = 0 ;
51 FVariableSize = 0 ;
52 FInstructionSize = 0 ;
53 FAvailable = false ;
54 }
55 }
56
57 VPointer VL_AsmExecutable::GetConstant()
58 {
59 return FConstantBuffer;
60 }
61
62 VPointer VL_AsmExecutable::GetVariable()
63 {
64 return FVariableBuffer;
65 }
66
67 VPointer VL_AsmExecutable::GetInstruction()
68 {
69 return FInstructionBuffer;
70 }
2 {
3 FConstantBuffer = 0 ;
4 FVariableBuffer = 0 ;
5 FInstructionBuffer = 0 ;
6 FConstantSize = 0 ;
7 FVariableSize = 0 ;
8 FInstructionSize = 0 ;
9 FAvailable = false ;
10 }
11
12 VL_AsmExecutable:: ~ VL_AsmExecutable()
13 {
14 Release();
15 }
16
17 void VL_AsmExecutable::Allocate(VInt Constant , VInt Variable , VInt Instruction)
18 {
19 Release();
20 FConstantSize = Constant ? Constant: 1 ;
21 FVariableSize = Variable ? Variable: 1 ;
22 FInstructionSize = Instruction ? Instruction: 1 ;
23 FConstantBuffer = VirtualAlloc( 0 ,FConstantSize,MEM_COMMIT,PAGE_READWRITE);
24 FVariableBuffer = VirtualAlloc( 0 ,FVariableSize,MEM_COMMIT,PAGE_READWRITE);
25 FInstructionBuffer = VirtualAlloc( 0 ,FInstructionSize,MEM_COMMIT,PAGE_READWRITE);
26 FAvailable = true ;
27 }
28
29 void VL_AsmExecutable::Protect()
30 {
31 if (FAvailable)
32 {
33 DWORD OldProtect = 0 ;
34 VirtualProtect(FConstantBuffer,FConstantSize,PAGE_READONLY, & OldProtect);
35 VirtualProtect(FInstructionBuffer,FInstructionSize,PAGE_EXECUTE_READ, & OldProtect);
36 }
37 }
38
39 void VL_AsmExecutable::Release()
40 {
41 if (FAvailable)
42 {
43 VirtualFree(FConstantBuffer,FConstantSize,MEM_RELEASE);
44 VirtualFree(FVariableBuffer,FVariableSize,MEM_RELEASE);
45 VirtualFree(FInstructionBuffer,FInstructionSize,MEM_RELEASE);
46
47 FConstantBuffer = 0 ;
48 FVariableBuffer = 0 ;
49 FInstructionBuffer = 0 ;
50 FConstantSize = 0 ;
51 FVariableSize = 0 ;
52 FInstructionSize = 0 ;
53 FAvailable = false ;
54 }
55 }
56
57 VPointer VL_AsmExecutable::GetConstant()
58 {
59 return FConstantBuffer;
60 }
61
62 VPointer VL_AsmExecutable::GetVariable()
63 {
64 return FVariableBuffer;
65 }
66
67 VPointer VL_AsmExecutable::GetInstruction()
68 {
69 return FInstructionBuffer;
70 }
到了这里就可以修改 上一篇文章列出的汇编代码了。我们将数组{10,1,2,3,4,5,6,7,8,9,10}存放进常量空间然后求和:
1
VL_AsmCompiled
*
Compile()
2 {
3 VL_AsmProgram Program;
4 {
5 VInt Params[] = { 10 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
6 Program.ConstantBuffer.Write( & Params, sizeof (Params));
7 }
8 {
9 // XOR EAX, EAX
10 INSTRUCTION(XOR)
11 PARAM_REG_32(EAX)
12 PARAM_REG_32(EAX)
13 // MOV EDI, #CONSTANT_BUFFER_POINTER
14 INSTRUCTION(MOV)
15 PARAM_REG_32(EDI)
16 PARAM_IMM_32( 0 )
17 LINK(VL_AsmProgram::CONSTANT_BUFFER_POINTER, 0 );
18 // MOV ECX, [EDI]
19 INSTRUCTION(MOV)
20 PARAM_REG_32(ECX)
21 PARAM_MI_32(NONE, 1 ,EDI, 0 )
22 // @BEGIN:
23 LABEL(BEGIN)
24 // CMP ECX, 0
25 INSTRUCTION(CMP)
26 PARAM_REG_32(ECX)
27 PARAM_IMM_32( 0 )
28 // JE @END
29 INSTRUCTION(JE)
30 PARAM_LABEL(END)
31 // ADD EAX, [ECX * 4 + EDI]
32 INSTRUCTION(ADD)
33 PARAM_REG_32(EAX)
34 PARAM_MI_32(ECX, 4 ,EDI, 0 )
35 // SUB ECX, 1
36 INSTRUCTION(SUB)
37 PARAM_REG_32(ECX)
38 PARAM_IMM_32( 1 )
39 // JMP @BEGIN
40 INSTRUCTION(JMP)
41 PARAM_LABEL(BEGIN)
42 // @END:
43 LABEL(END)
44 // RET
45 INSTRUCTION(RET)
46 }
47 VL_AsmCompiled * Compiled = Compile( & Program);
48 if (Compiled -> Errors.GetCount())
49 {
50 PrintErrors(Compiled);
51 delete Compiled;
52 return 0 ;
53 }
54 else
55 {
56 return Compiled;
57 }
58 }
2 {
3 VL_AsmProgram Program;
4 {
5 VInt Params[] = { 10 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
6 Program.ConstantBuffer.Write( & Params, sizeof (Params));
7 }
8 {
9 // XOR EAX, EAX
10 INSTRUCTION(XOR)
11 PARAM_REG_32(EAX)
12 PARAM_REG_32(EAX)
13 // MOV EDI, #CONSTANT_BUFFER_POINTER
14 INSTRUCTION(MOV)
15 PARAM_REG_32(EDI)
16 PARAM_IMM_32( 0 )
17 LINK(VL_AsmProgram::CONSTANT_BUFFER_POINTER, 0 );
18 // MOV ECX, [EDI]
19 INSTRUCTION(MOV)
20 PARAM_REG_32(ECX)
21 PARAM_MI_32(NONE, 1 ,EDI, 0 )
22 // @BEGIN:
23 LABEL(BEGIN)
24 // CMP ECX, 0
25 INSTRUCTION(CMP)
26 PARAM_REG_32(ECX)
27 PARAM_IMM_32( 0 )
28 // JE @END
29 INSTRUCTION(JE)
30 PARAM_LABEL(END)
31 // ADD EAX, [ECX * 4 + EDI]
32 INSTRUCTION(ADD)
33 PARAM_REG_32(EAX)
34 PARAM_MI_32(ECX, 4 ,EDI, 0 )
35 // SUB ECX, 1
36 INSTRUCTION(SUB)
37 PARAM_REG_32(ECX)
38 PARAM_IMM_32( 1 )
39 // JMP @BEGIN
40 INSTRUCTION(JMP)
41 PARAM_LABEL(BEGIN)
42 // @END:
43 LABEL(END)
44 // RET
45 INSTRUCTION(RET)
46 }
47 VL_AsmCompiled * Compiled = Compile( & Program);
48 if (Compiled -> Errors.GetCount())
49 {
50 PrintErrors(Compiled);
51 delete Compiled;
52 return 0 ;
53 }
54 else
55 {
56 return Compiled;
57 }
58 }
然后使用下面的代码执行编译后的机器码:
1
void
RunExecutable(VL_AsmExecutable
*
Executable)
2 {
3 VPointer Buffer = Executable -> GetInstruction();
4 VInt Output = 0 ;
5 __asm
6 {
7 PUSHAD
8 MOV EAX, dword ptr[Buffer]
9 INT 3
10 CALL EAX
11 MOV dword ptr[Output], EAX
12 POPAD
13 }
14 GetConsole() -> Write(L " 结果:EAX= " + VUnicodeString(Output) + L " \r\n " );
15 }
16
17 void vlmain()
18 {
19 GetConsole() -> SetTitle(L " Vczh Library++ 2.0 Assembler " );
20 GetConsole() -> SetTestMemoryLeaks( true );
21 GetConsole() -> SetPauseOnExit( true );
22
23 VL_AsmCompiled * Compiled = Compile();
24 if (Compiled)
25 {
26 VL_AsmExecutable * Executable = Link(Compiled);
27 if (Compiled -> Errors.GetCount())
28 {
29 PrintErrors(Compiled);
30 }
31 if (Executable)
32 {
33 RunExecutable(Executable);
34 delete Executable;
35 }
36 delete Compiled;
37 }
38 }
2 {
3 VPointer Buffer = Executable -> GetInstruction();
4 VInt Output = 0 ;
5 __asm
6 {
7 PUSHAD
8 MOV EAX, dword ptr[Buffer]
9 INT 3
10 CALL EAX
11 MOV dword ptr[Output], EAX
12 POPAD
13 }
14 GetConsole() -> Write(L " 结果:EAX= " + VUnicodeString(Output) + L " \r\n " );
15 }
16
17 void vlmain()
18 {
19 GetConsole() -> SetTitle(L " Vczh Library++ 2.0 Assembler " );
20 GetConsole() -> SetTestMemoryLeaks( true );
21 GetConsole() -> SetPauseOnExit( true );
22
23 VL_AsmCompiled * Compiled = Compile();
24 if (Compiled)
25 {
26 VL_AsmExecutable * Executable = Link(Compiled);
27 if (Compiled -> Errors.GetCount())
28 {
29 PrintErrors(Compiled);
30 }
31 if (Executable)
32 {
33 RunExecutable(Executable);
34 delete Executable;
35 }
36 delete Compiled;
37 }
38 }
链接器就完成了。