光能編譯匯編還是不行的,因為很多東西在編譯的時候不知道,典型的比如放常量那部分的指針等等。主要原因還是因為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) 閱讀(1930)
評論(1) 編輯 收藏 引用 所屬分類:
JIT