Contract-Debugging

如何將 EVM 跟踪映射到合約源?

  • September 4, 2021

換句話說,如何獲得失敗交易的“經典堆棧跟踪”?

例如,我們有一個跟踪 (不要看“Missing opcode 0xfd” - 它是恢復指令)和合約可靠性。如何找出引發異常的原始碼的哪一行?

我設法組裝了契約(使用 solc –asm),但沒有 PC(程序計數器)提示,所以我找不到對應於 PC=557 的行。此外,我認為在編譯期間執行了優化,但儘管該程序集在某種程度上仍然是可讀的。

我正在使用 solc 0.4.16+commit.d7661dd9.Linux.g++。

提前致謝。

我去了這個兔子洞,最後得到了一個可以工作的概念證明。我不能推薦這次旅行。許多級別存在阻抗不匹配,需要進行大量格式轉換。最後,我的實現仍然沒有處理跨合約呼叫。(似乎沒有辦法確定特定程序計數器屬於哪個合約地址,除非解釋呼叫指令)。

我的實現太髒了,無法分享,但主要步驟是:

1)您需要solc生成一個執行時源圖。它不能直接輸出這個,但它可以將它作為“組合 json 輸出”的一部分輸出。為此,執行solc --combined-json bin-runtime,srcmap-runtime.

const srcmaps = JSON.parse(fs.readFileSync("./Contract.json"));
const srcmap =
 srcmaps.contracts["./contracts/Contract.sol:Contract"]["srcmap-runtime"];

const source = fs.readFileSync("./contracts/Contract.sol").toString();

const bin = Buffer.from(
 srcmaps.contracts["./contracts/Contract.sol:Contract"]["bin-runtime"],
 "hex"
);

2)sourcemap格式被壓縮,需要自己寫一個decoder。格式的規範在solidity 文件中。您現在可以將指令索引映射到源偏移量。

3)我們不想要源文件中的字節偏移量,而是行號和列號。為此,您需要解析源文件並創建從字節偏移到行/列對的映射。我決定暫時忽略這個並使用get-line-from-posnpm 包。

第 2 步和第 3 步一起是:

const parsed = srcmap
 .split(";")
 .map(l => l.split(":"))
 .map(([s, l, f, j]) => ({ s: s === "" ? undefined : s, l, f, j }))
 .reduce(
   ([last, ...list], { s, l, f, j }) => [
     {
       s: parseInt(s || last.s, 10),
       l: parseInt(l || last.l, 10),
       f: parseInt(f || last.f, 10),
       j: j || last.j
     },
     last,
     ...list
   ],
   [{}]
 )
 .reverse()
 .slice(1)
 .map(
   ({ s, l, f, j }) => `${srcmaps.sourceList[f]}:${getLineFromPos(source, s)}`
 );

4)源映射在指令號中,但我們需要字節碼地址。為了解決這個問題,我們需要建構一個從字節碼偏移到指令號(或其他方式)的映射。我發現自己解析執行時二進製文件最容易。PUSH_n所有指令都是1 字節長,除了長指令n+1

const isPush = inst => inst >= 0x60 && inst < 0x7f;

const pushDataLength = inst => inst - 0x5f;

const instructionLength = inst => (isPush(inst) ? 1 + pushDataLength(inst) : 1);

const byteToInstIndex = bin => {
 const result = [];
 let byteIndex = 0;
 let instIndex = 0;
 while (byteIndex < bin.length) {
   const length = instructionLength(bin[byteIndex]);
   for (let i = 0; i < length; i += 1) {
     result.push(instIndex);
   }
   byteIndex += length;
   instIndex += 1;
 }
 return result;
};

然後您需要獲取給定事務的回溯:

const promisify = func => async (...args) =>
 new Promise((accept, reject) =>
   func(...args, (error, result) => (error ? reject(error) : accept(result)))
 );

const rpcCommand = method => async (...params) =>
 (await promisify(web3.currentProvider.sendAsync)({
   jsonrpc: "2.0",
   method,
   params,
   id: Date.now()
 })).result;

const traceTransaction = rpcCommand("debug_traceTransaction");

一旦你擁有了所有這些,你就可以得到一個類似於經典 stracktrace 的東西:

const trace = await traceTransaction(result.tx);
trace.structLogs.forEach(({op, pc, gasCost}) =>
 console.log(
   `${pc}\t${op}\t${gasCost}\t${byteToInstr[pc]}\t${parsed[
     byteToInstr[pc]
   ]}`
 )
);

我希望盡快把它清理乾淨並把它變成一個圖書館。處理痕跡並將它們映射回solidity的能力有很多用途。

如果你能掌握合約的原始碼,你可以使用Hardhat來獲取 Solidity 堆棧跟踪。Hardhat Network 是一個調試優先的 EVM 實現,專為智能合約的低級開發而建構。

無恥外掛:從我的使用安全帽的 Solidity 模板開始:https://github.com/paulrberg/solidity-template

旁注:參見 Nomic Labs 的公告

引用自:https://ethereum.stackexchange.com/questions/25479