光能编译汇编还是不行的,因为很多东西在编译的时候不知道,典型的比如放常量那部分的指针等等。主要原因还是因为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记录的是使用这个链接单元的指令序号,用于生成错误信息。
于是在编译之后就可以填充链接信息,然后进行链接了。这个做完以后就只剩下一个问题了。我们不仅需要常量缓冲区、还需要变量缓冲区,那么如何在常量缓冲区填充之后锁定呢(为了在试图修改的时候抛出异常)?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 };
这里是实现部分:
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 }
到了这里就可以修改
上一篇文章列出的汇编代码了。我们将数组{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 }
然后使用下面的代码执行编译后的机器码:
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 }
链接器就完成了。
posted on 2009-02-22 22:41
陈梓瀚(vczh) 阅读(1919)
评论(1) 编辑 收藏 引用 所属分类:
JIT