從solc編譯過程來理解solidity合約結構
現在以一個最簡單的程式碼來開始我們的逆向旅程,為了方便學習,所有的程式碼編譯和分析都在 ofollow,noindex" target="_blank">http://remix.ethereum.org/# 上進行.預設IDE 選項是關閉程式碼優化(Enable Optimization)和使用0.4.25 版本的solidity 編譯器編譯.
pragma solidity ^0.4.24; contract test { function a() { uint a = 123; } }
在Remix 上輸入上述程式碼,點選Start to Compile 對程式碼進行編譯,可以在Details 按鈕裡面獲取編譯出來的結果.結果包含如下:
METADATA編譯器元資料,包含:編譯器版本,編譯器設定,原始碼資訊等 BYTECODE合約編譯完整的位元組碼結果 ABI應用程式介面,用於標識合約提供了哪些函式給外部呼叫 WEB3DEPLOYWeb3js版合約部署程式碼 METADATAHASH元資料雜湊 GASESTIMATES編譯器計算函式呼叫需要消耗的Gas表 RUNTIME BYTECODE合約執行時位元組碼 ASSEMBLY位元組碼反彙編
讀者們會注意到,編譯結果裡面有ByteCode 和Runtime Bytecode ,分別表示什麼意思呢,在此先略過這個疑問,我們關注Runtime Bytecode 中返回的位元組碼.對程式碼結構分析和位元組碼分析的標註後所有程式碼如下:
000 PUSH1 80 002 PUSH1 40 004 MSTORE;把0x80 寫到[0x40 ,0x40 + 0x20] 這塊記憶體裡面,因為記憶體是空的,這會建立新的記憶體 005 PUSH1 04 007 CALLDATASIZE;獲取CALLDATA 的長度 008 LT;LT 和PUSH1 0x4 對應,判斷CALLDATA 的長度是否小於0x4 009 PUSH1 3f 011 JUMPI;如果小於0x4 就往下跳轉到0x3F 012 PUSH1 00 014 CALLDATALOAD;CALLDATALOAD 0x0 ,PUSH1 0x0 是給CALLDATALOAD 的引數,意思要獲取CALLDATA 資料的偏移位置 015 PUSH29 0100000000000000000000000000000000000000000000000000000000 045 SWAP1 046 DIV;DIV 和PUSH29 對應,意思是把上面的資料向左移28 位元組,剩下4 位元組是呼叫合約函式名的雜湊 047 PUSH4 ffffffff 052 AND;AND 和PUSH4 0xFFFFFFFF 對應,保留低位4 位元組資料,高位去處 053 DUP1 054 PUSH4 0dbe671f;這個是合約有的函式名,經過sha3() 精簡過的 059 EQ;判斷傳遞過來的函式呼叫名是否相等 060 PUSH1 44 062 JUMPI;如果兩值相等就往下跳轉到0x44 063 JUMPDEST;空指令 064 PUSH1 00 066 DUP1 067 REVERT;沒有匹配到相應的函式就撤銷所有操作,Revert(0,0) 068 JUMPDEST 069 CALLVALUE;獲取使用者轉帳數額 070 DUP1 071 ISZERO;如果使用者轉帳數額為0 072 PUSH1 4f 074 JUMPI;轉帳數額不為0 則跳到0x4F,否則就退出 075 PUSH1 00 077 DUP1 078 REVERT;因為呼叫函式a() 是不需要附加轉帳金額的,所以檢測到帶有附加金額的函式呼叫就退出,參考payable 關鍵字 079 JUMPDEST 080 POP 081 PUSH1 56 083 PUSH1 58 085 JUMP;跳轉到地址88 086 JUMPDEST 087 STOP;停止執行 088 JUMPDEST 089 PUSH1 00 091 PUSH1 7b 093 SWAP1 094 POP 095 POP 096 JUMP;跳轉到地址86 097 STOP ----合約程式碼結束分界線---- 098 LOG1 099 PUSH6 627a7a723058 106 SHA3 107 MUL 108 PUSH15 5fd8c2f6fe4103dba9baf9c48c052e 124 CALLDATALOAD 125 INVALID 126 PUSH1 d9 128 INVALID 129 INVALID 130 TIMESTAMP 131 STATICCALL 132 INVALID 133 INVALID 134 DUP13 135 INVALID 136 TIMESTAMP 137 INVALID 138 NUMBER 139 STOP 140 INVALID
標註資訊把合約程式碼的結構和執行過程的思路都理清了,但是讀者們會發現以下的問題:
1.為什麼會有合約程式碼結束分界線,多出來的程式碼究竟是什麼 2.為什麼會有很多多餘的跳轉 3.JUMPDEST 是無意思的位元組碼,為什麼會多次出現
要解決這些疑問,那麼就需要深入到solidity 編譯器的原始碼分析.在 https://github.com/ethereum/solidity 中找到solc 編譯器的原始碼,定位到libsolidityCodegenContractCompiler.cpp 檔案的ContractCompiler::compileContract() 函式,對該函式的分析如下:
void ContractCompiler::compileContract( ContractDefinition const& _contract, std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts )//合約編譯 { // ... initializeContext(_contract, _contracts);//初始化執行環境上下文 appendFunctionSelector(_contract);//根據合約內使用到的函式進行彙編構造 appendMissingFunctions();//連結不公開的函式(非public 聲名) }
initializeContext() 函式主要功能是初始化執行環境上下文,並把初始化的機器碼輸出到位元組碼快取.
void ContractCompiler::initializeContext( ContractDefinition const& _contract, map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts ) { m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); m_context.setCompiledContracts(_compiledContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); CompilerUtils(m_context).initialiseFreeMemoryPointer();//初始化EVM 記憶體指標 registerStateVariables(_contract); m_context.resetVisitedNodes(&_contract); } const size_t CompilerUtils::freeMemoryPointer = 64; const size_t CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; const size_t CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; void CompilerUtils::initialiseFreeMemoryPointer() { m_context << u256(generalPurposeMemoryStart);//generalPurposeMemoryStart 的值為0x80,輸出0x80 到位元組碼快取 storeFreeMemoryPointer(); } void CompilerUtils::storeFreeMemoryPointer() { m_context << u256(freeMemoryPointer) << Instruction::MSTORE;//freeMemoryPointer 的值為0x40 ,輸出0x40 和指令MSTORE 到位元組碼快取 }
根據上面的程式碼可以知道,ContractCompiler::initializeContext() 會輸出MSTORE 0x40,0x80 到合約位元組碼的頭部,也就是我們常看到合約機器碼的開頭部分:6080604052 .appendFunctionSelector() 函式是把合約裡面編譯好的函式和合約初始化檢測的程式碼組合在一起,如果沒有深入瞭解appendFunctionSelector() 的程式碼生成過程,那就很難理解solc 為什麼會這樣生成位元組碼.
void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract) { map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions();//合約中聲名的公開函式列表 map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints;//函式程式碼入口點 if (_contract.isLibrary())//判斷合約是否為庫程式碼 { solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected."); } FunctionDefinition const* fallback = _contract.fallbackFunction(); eth::AssemblyItem notFound = m_context.newTag();//建立新的彙編Tag ,Tag 的意義是用來標註彙編程式碼塊聲名和跳轉到某一段彙編用的 // directly jump to fallback if the data is too short to contain a function selector // also guards against short data m_context << u256(4) << Instruction::CALLDATASIZE << Instruction::LT;//判斷CALLDATA 內容長度是否大於等於4 位元組 m_context.appendConditionalJumpTo(notFound);//插入條件跳轉,LT 判斷不通過就跳轉到notFound 程式碼塊 // retrieve the function signature hash from the calldata if (!interfaceFunctions.empty()) CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true);//從CALLDATA 中提取要呼叫的函式雜湊 //構造程式碼PUSH 0x00 ,CALLDATALOAD ,PUSH29 0100000000000000000000000000000000000000000000000000000000 ,SWAP1 ,DIV ,PUSH4 0xFFFFFFFF ,AND // stack now is: <can-call-non-view-functions>? <funhash> for (auto const& it: interfaceFunctions) { callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag()));//對函式入口建立新彙編程式碼塊聲名 m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << Instruction::EQ;//構造程式碼:DUP1 ,PUSH4 函式雜湊 ,EQ m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first));//如果數值相同,則跳轉到目的函式地址,對應PUSH + JUMPI 指令 } m_context.appendJumpTo(notFound);//沒有找到的話就跳轉到notFound 觸發revert(0) 退出 m_context << notFound;//聲名notFound 的程式碼段 if (fallback) { solAssert(!_contract.isLibrary(), ""); if (!fallback->isPayable()) appendCallValueCheck(); solAssert(fallback->isFallback(), ""); solAssert(FunctionType(*fallback).parameterTypes().empty(), ""); solAssert(FunctionType(*fallback).returnParameterTypes().empty(), ""); fallback->accept(*this); m_context << Instruction::STOP; } else // TODO: error message here? m_context.appendRevert(); //對notFound 的程式碼進行填充,因為fallback=fakse ,執行m_context.appendRevert() ,所以notFound 的程式碼序列是 PUSH1 0x00 ,DUP1 ,REVERT .意思是revert(0x0) //上面是構造合約執行資料檢測的程式碼,下面是對各個公開呼叫(指public 聲名)的函式進行入口點構造 for (auto const& it: interfaceFunctions) { FunctionTypePointer const& functionType = it.second; solAssert(functionType->hasDeclaration(), ""); CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); m_context << callDataUnpackerEntryPoints.at(it.first); if (_contract.isLibrary() && functionType->stateMutability() > StateMutability::View)//庫函式且關鍵字聲名不是pure 和view 的函式 { // If the function is not a view function and is called without DELEGATECALL, // we revert. m_context << dupInstruction(2); m_context.appendConditionalRevert(); } m_context.setStackOffset(0); // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. if (!functionType->isPayable() && !_contract.isLibrary())//如果函式沒有啟用Payable 關鍵字或者這是庫函式的話,都不支援接收合約呼叫攜帶轉帳金額 appendCallValueCheck();//新增對轉帳金額檢測程式碼 // Return tag is used to jump out of the function. eth::AssemblyItem returnTag = m_context.pushNewTag();//對函式建立返回程式碼段聲名 if (!functionType->parameterTypes().empty())//如果函式有引數的話 { // Parameter for calldataUnpacker m_context << CompilerUtils::dataStartOffset;//CompilerUtils::dataStartOffset 指的是函式引數資料在TXDATA 裡的偏移 m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB;//計算函式引數的大小 CompilerUtils(m_context).abiDecode(functionType->parameterTypes());//從TXDATA 中獲取引數 } m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration()));//調轉到函式入口點 m_context << returnTag;//聲名函式返回的程式碼段 // Return tag and input parameters get consumed. m_context.adjustStackOffset( CompilerUtils(m_context).sizeOnStack(functionType->returnParameterTypes()) - CompilerUtils(m_context).sizeOnStack(functionType->parameterTypes()) - 1 ); // Consumes the return parameters. appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary());//建構函式返回值處理 } }
上面的程式碼分析可能看起來有些晦澀難懂,作者把上面分析到的編譯過程一一對應到編譯結果並標註,彙編程式碼如下:
-----initializeContext() -> initialiseFreeMemoryPointer()---- 000 PUSH1 80;initialiseFreeMemoryPointer(),m_context << u256(0x80) 002 PUSH1 40;storeFreeMemoryPointer() 004 MSTORE;storeFreeMemoryPointer(), m_context << u256(0x40) <<Instruction::MSTORE; -----initialiseFreeMemoryPointer()----- -----appandFunctionSelector() Create Code Start --- 005 PUSH1 04;/-- 007 CALLDATASIZE;| 008 LT;|檢測CallData 是否合法,CallData 會帶有4 位元組函式雜湊 009 PUSH1 3f;| 011 JUMPI;-- 012 PUSH1 00;/-- 014 CALLDATALOAD;| 015 PUSH29 0100000000000000000000000000000000000000000000000000000000 045 SWAP1;| 046 DIV;|處理CallData 裡面帶入的函式雜湊.預設讀出來資料是在高位,現在處理成低位 047 PUSH4 ffffffff;| 052 AND;-- 053 DUP1;/-- 054 PUSH4 0dbe671f;| 059 EQ;| 060 PUSH1 44;|根據函式雜湊找入口 062 JUMPI;| 063 JUMPDEST;-- 064 PUSH1 00;/-- 066 DUP1;|notFound 程式碼填充 067 REVERT;-- -----appandFunctionSelector() 會給每個函式根據引數呼叫來分配函式頭入口初始化檢測程式碼 --- 068 JUMPDEST;Address 0x44 , function a() Entry .. 069 CALLVALUE;/-- 070 DUP1;| 071 ISZERO;| 072 PUSH1 4f;| 074 JUMPI;|solc/libsolidity/Codegen/ContractCompiler.cpp:appendCallValueCheck() 075 PUSH1 00;| 077 DUP1;| 078 REVERT;-- -----Tag function_A_pre_JumpTo_function_A_Tag_Code---- -----m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); 079 JUMPDEST;JUMPDEST 是無意義的程式碼,它的唯一意義是用來標識這是一個Label 起始頭 080 POP 081 PUSH1 56;eth::AssemblyItem returnTag = m_context.pushNewTag();0x56 is Return Address 083 PUSH1 58;/ 085 JUMP;m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); -----Tag function_A_return_Code---- 086 JUMPDEST;Address 0x56 087 STOP;ContractCompiler::appendReturnValuePacker(); -----Tag function_A_Main_Code---- -----CompilerContext 是儲存每一個函式編譯好的程式碼 088 JUMPDEST;Address 0x58 , m_context.functionEntryLabel("a"); .. 089 PUSH1 00 091 PUSH1 7b 093 SWAP1;0x7B ,0x00== SWAP => 0x00 ,0x7B .意義是對棧進行平衡 094 POP 095 POP 096 JUMP;Jump 0x56 097 STOP; -----appandFunctionSelector() Create Code End --- 098 LOG1 099 PUSH6 627a7a723058 106 SHA3 107 MUL 108 PUSH15 5fd8c2f6fe4103dba9baf9c48c052e 124 CALLDATALOAD 125 INVALID 126 PUSH1 d9 128 INVALID 129 INVALID 130 TIMESTAMP 131 STATICCALL 132 INVALID 133 INVALID 134 DUP13 135 INVALID 136 TIMESTAMP 137 INVALID 138 NUMBER 139 STOP 140 INVALID
結合編譯器的編譯過程和編譯出來的結果來閱讀理解程式碼之後,可以知道合約彙編程式碼的結構:
1.初始化EVM memory 2.檢測TXDATA 裡面是否帶有合法的函式雜湊 3.函式跳轉 4.函式預校驗程式碼 5.函式引數獲取程式碼 6.函式返回程式碼 7.函式主體程式碼
讀者可能會有一個疑問,為什麼在彙編程式碼097 STOP 的後面還有多餘的程式碼呢,這些程式碼的意義何在.我們來閱讀CompilerStack::compileContract() 的程式碼:
void CompilerStack::compileContract( ContractDefinition const& _contract, map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts ) { // ... shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns); compiledContract.compiler = compiler; string metadata = createMetadata(compiledContract);//建立元資料 compiledContract.metadata = metadata; bytes cborEncodedMetadata = createCBORMetadata(//生成CBOR 元資料 metadata, !onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures) ); try { // Run optimiser and compile the contract. compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);//編譯合約 } catch(eth::OptimizerException const&) { solAssert(false, "Optimizer exception during compilation"); } // ... }
可以看到,編譯合約的時候cborEncodedMetadata 的資料也帶入compileContract() ,compileContract() 程式碼如下:
void Compiler::compileContract( ContractDefinition const& _contract, std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts, bytes const& _metadata ) { ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);//初始化合約編譯類 runtimeCompiler.compileContract(_contract, _contracts);//編譯合約程式碼 m_runtimeContext.appendAuxiliaryData(_metadata);//把CBOR 元資料附加在編譯之後合約程式碼末端 // ... }
那麼回來閱讀createCBORMetadata() 的程式碼,發現它其實是使用了元資料來構造出的資料.
bytes CompilerStack::createCBORMetadata(string _metadata, bool _experimentalMode) { bytes cborEncodedHash = // CBOR-encoding of the key "bzzr0" bytes{0x65, 'b', 'z', 'z', 'r', '0'}+ // CBOR-encoding of the hash bytes{0x58, 0x20} + dev::swarmHash(_metadata).asBytes(); bytes cborEncodedMetadata; if (_experimentalMode) cborEncodedMetadata = // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true} bytes{0xa2} + cborEncodedHash + bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5}; else cborEncodedMetadata = // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)} bytes{0xa1} + cborEncodedHash; solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large"); // 16-bit big endian length cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); return cborEncodedMetadata; }
我們從Bytecode 中提取出CBOR 編碼,資料為a165627a7a723058207ba6766efb653d5e4d3b7d5893d345b79718b5513bd5a87d5bf8256fa895c58d0029 ,對它的標註如下:
a1;Flag : experimental = False 65627a7a72305820;CBOREncodeHash 7ba6766efb653d5e4d3b7d5893d345b79718b5513bd5a87d5bf8256fa895c58d;BZZA hash 0029;hash 長度
弄懂solidity 的編譯過程和為什麼會編譯出這樣的結果之後,現在回來解答前面提出的兩個問題:
1.為什麼會有合約程式碼結束分界線,多出來的程式碼究竟是什麼 多出來的程式碼是CBOR 元資料編碼 2.為什麼會有很多多餘的跳轉 多餘的跳轉是因為appendFunctionSelector() 會幫助函式去構造預處理程式碼,引數提取程式碼和返回程式碼,最後才跳轉到函式的主體程式碼.solidity 和x86 arm 等的彙編不同,它的對函式的引數和返回值處理都不是由函式主體來完成的. 3.JUMPDEST 是無意思的位元組碼,為什麼會多次出現 編譯器在生成編譯程式碼時,可以看到JUMP 和JUMPI 指令會跳轉到JUMPDEST 指令.JUMPDEST 指令是solidity 編譯器用來標識彙編程式碼的區段聲名(Tag).所以後面的示例彙編程式碼都會在JUMPDEST 前記錄程式碼區段的意思標註.
至此,本文還有一個疑問沒有被解決:ByteCode 和Runtime Bytecode 分別表示什麼意思呢?我們來回顧Compiler::compileContract() 的完整程式碼:
void Compiler::compileContract( ContractDefinition const& _contract, std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts, bytes const& _metadata ) { ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);//編譯runtime 程式碼 runtimeCompiler.compileContract(_contract, _contracts); m_runtimeContext.appendAuxiliaryData(_metadata);//插入CBOR 編碼 // This might modify m_runtimeContext because it can access runtime functions at // creation time. ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize);//編譯creation 程式碼 m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); m_context.optimise(m_optimize, m_optimizeRuns);//優化彙編程式碼 }
可以看到Compiler::compileContract() 裡面分兩部分來編譯合約程式碼:runtime 的程式碼指的是合約編寫邏輯的程式碼;creation 的程式碼指的是constructor() 的程式碼.我們回來看ByteCode 和RuntimeCode 的彙編程式碼來做對比
ByteCode 彙編:
----Binary---- 6080604052348015600f57600080fd5b50608d8061001e6000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f146044575b600080fd5b348015604f57600080fd5b5060566058565b005b6000607b9050505600a165627a7a72305820026e5fd8c2f6fe4103dba9baf9c48c052e35ca60d9cdee42faca258c284224430029 ----Constructor() Code---- PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x8D DUP1 PUSH2 0x1E PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP ----Contract Code---- PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH1 0x3F JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xDBE671F EQ PUSH1 0x44 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x4F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x56 PUSH1 0x58 JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 PUSH1 0x7B SWAP1 POP POP JUMP STOP
RuntimeCode 彙編:
----Binary---- 608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f146044575b600080fd5b348015604f57600080fd5b5060566058565b005b6000607b9050505600a165627a7a72305820026e5fd8c2f6fe4103dba9baf9c48c052e35ca60d9cdee42faca258c284224430029 ----Contract Code---- PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH1 0x3F JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xDBE671F EQ PUSH1 0x44 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x4F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x56 PUSH1 0x58 JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 PUSH1 0x7B SWAP1 POP POP JUMP STOP
所以,我們可以明白在部署合約的時候,EVM 執行Constructor() 函式的程式碼初始化合約資料,後續使用者通過Web3 呼叫節點上的合約函式時,是直接在RuntimeCode 中開始執行合約程式碼.TXDATA 中是帶有使用者希望呼叫函式的雜湊和函式的引數資料的,接下來合約程式碼初始EVM Memory 之後,根據TXDATA 裡面指向使用者希望呼叫的函式雜湊來進行程式碼跳轉.執行函式主體程式碼前做一些預檢測並從TXDATA 中提取函式引數到棧,最後執行函式主體程式碼並退出.