热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

main方法启动job的shell脚本_CKB脚本编程简介第五弹:调试

Xuejie是CKB-VM的核心开发者,他在自己的博客「LessisMore」中,创作了一系列介绍CKB脚本编程的文章,用于补充白皮书中编
709b0062609656b444464e43e8374cae.gif
Xuejie 是 CKB-VM的核心开发者,他在自己的博客「Less is More」中,创作了一系列介绍 CKB 脚本编程的文章,用于补充白皮书中编写 CKB 脚本所需的所有缺失的细节实现。本文是该系列的第五篇,详细介绍了 CKB 的调试过程,快来一起查看吧 ~^.^~
作者:Xuejie原文链接:https://xuejie.space/2019_10_18_introduction_to_ckb_script_programming_debugging/译者:史迪仔c2464f84d74dfc53e90af743177a1c87.gif事实上,CKB 脚本工作的层级要比其他智能合约低很多,因此 CKB 的调试过程就显得相当神秘。在本文中,我们将展示如何调试 CKB 脚本。你会发现,其实调试 CKB 脚本和你日常调试程序并没有太大区别。本文建立在 ckb v0.23.0 之上。具体的,我在每个项目中使用的是如下版本的 commit:

•   ckb: 

7e2ad2d9ed6718360587f3762163229eccd2cf10

•   ckb-sdk-ruby: 

18a89d8c69e173ad59ce3e3b3bf79b5d11c5f8f8

•   ckb-duktape:

347bf730c08eb0aab7e56e0357945a4d6cee109a

•   ckb-standalone-debugger: 

2379e89ae285e4e639b961756c22d8e4fde4d6ab

使用 GDB 调试 C 程序

CKB 脚本调试的第一种方案,通常适用于 C、Rust 等编程语言。也许你已经习惯了写 C 的程序,而 GDB 也是你的好搭档。你想知道是不是可以用 GDB 来调试 C 程序,答案当然是:Yes!你肯定可以通过 GDB 来调试用 C 编写的 CKB 脚本!让我来演示一下:首先,我们还是用之前文章中用到的关于 carrot 的例子:

#include #include "ckb_syscalls.h"int main(int argc, char* argv[]) { int ret; size_t index = 0; uint64_t len = 0; unsigned char buffer[6]; while (1) { len = 6; memset(buffer, 0, 6); ret = ckb_load_cell_data(buffer, &len, 0, index, CKB_SOURCE_OUTPUT); if (ret == CKB_INDEX_OUT_OF_BOUND) { break; } int cmp = memcmp(buffer, "carrot", 6); if (cmp) { return -1; } index++; } return 0;}这里我进行了两处修改:

  • 首先我更新了这个脚本,让它可以兼容 ckb v0.23.0。在这个版本中,我们可以使用 ckb_load_cell_data 来获取 cell 的数据。
  • 我还在这段代码中加入了一个小 bug,这样我们等会儿就可以进行调试的工作流程。如果你非常熟悉 C,你可能已经注意到了,当然你没有在意到的话也完全不用担心,稍后我会解释的。
和往常一样,我们使用官方的 toolchain 来将其编译成 RISC-V 的代码:

$ lscarrot.c$ git clone https://github.com/nervosnetwork/ckb-system-scripts$ cp ckb-system-scripts/c/ckb_*.h ./$ lscarrot.c ckb_consts.h ckb_syscalls.h ckb-system-scripts/$ sudo docker run --rm -it -v `pwd`:/code nervos/ckb-riscv-gnu-toolchain:bionic-20191012 bashroot@3efa454be9af:/# cd /coderoot@3efa454be9af:/code# riscv64-unknown-elf-gcc carrot.c -g -o carrotroot@3efa454be9af:/code# exit请注意,当我编译脚本的时候,我添加了 -g,以便生成调试信息,这在 GDB 中非常有用。对于实际使用的脚本,你总是希望尽量地完善它们来尽量节省存储在链上的空间。现在,让我们将脚本部署到 CKB 上。保持 CKB 节点处于运行状态,并启动 Ruby SDK:

pry(main)> api = CKB::API.newpry(main)> wallet = CKB::Wallet.from_hex(api, "")pry(main)> wallet2 = CKB::Wallet.from_hex(api, CKB::Key.random_private_key)pry(main)> carrot_data = File.read("carrot")pry(main)> carrot_data.bytesize=> 19296pry(main)> carrot_tx_hash = wallet.send_capacity(wallet2.address, CKB::Utils.byte_to_shannon(20000), CKB::Utils.bin_to_hex(carrot_data), fee: 21000)pry(main)> carrot_data_hash = CKB::Blake2b.hexdigest(carrot_data)pry(main)> carrot_type_script = CKB::Types::Script.new(code_hash: carrot_data_hash, args: "0x")pry(main)> carrot_cell_dep = CKB::Types::CellDep.new(out_point: CKB::Types::OutPoint.new(tx_hash: carrot_tx_hash, index: 0))现在链上有了 carrot 的脚本,我们可以创建一笔交易来测试这个 carrot 脚本:

pry(main)> tx &#61; wallet.generate_tx(wallet2.address, CKB::Utils.byte_to_shannon(100), use_dep_group: false, fee: 5000)pry(main)> tx.outputs[0].type &#61; carrot_type_scriptpry(main)> tx.cell_deps < tx.witnesses[0] &#61; "0x"pry(main)> tx &#61; tx.sign(wallet.key, api.compute_transaction_hash(tx))pry(main)> api.send_transaction(tx)CKB::RPCError: jsonrpc error: {:code&#61;>-3, :message&#61;>"Script(ValidationFailure(-1))"}如果你仔细检查这笔交易&#xff0c;你会发现在输出的 cell 中&#xff0c;并没有以 carrot开头的数据。然而我们运行之后仍然是验证失败&#xff0c;这意味着我们的脚本一定存在 bug。先前&#xff0c;没什么别的办法&#xff0c;你可能需要返回去检查代码&#xff0c;希望可以找到出错的地方。但现在没有这个必要了&#xff0c;你可以跳过这里的交易&#xff0c;然后将其输入到一个独立的 CKB 调试器开始调试它&#xff01;首先&#xff0c;让我们将这笔交易连同使用的环境&#xff0c;都转存到一个本地文件中&#xff1a;

pry(main)> CKB::MockTransactionDumper.new(api, tx).write("carrot.json")在这里你还需要跟踪 carrot 类型脚本的哈希&#xff1a;

pry(main)> carrot_type_script.compute_hash&#61;> "0x039c2fba64f389575cdecff8173882b97be5f8d3bdb2bb0770d8a7e265b91933"请注意&#xff0c;你可能会得到和我这里不一样的哈希&#xff0c;这得看你使用的环境。现在&#xff0c;让我们来试试 ckb-standalone-debugger&#xff1a;

$ git clone https://github.com/nervosnetwork/ckb-standalone-debugger$ cd ckb-standalone-debugger/bins$ cargo build --release$ ./target/release/ckb-debugger -l 0.0.0.0:2000 -g type -h 0x039c2fba64f389575cdecff8173882b97be5f8d3bdb2bb0770d8a7e265b91933 -t carrot.json

注意&#xff0c;你可能需要根据你的环境&#xff0c;调整 carrot 类型脚本的哈希或者 carrot.json 的路径。现在让我们试试在一个不同的终端内通过 GDB 连接调试器&#xff1a;

$ sudo docker run --rm -it -v &#96;pwd&#96;:/code nervos/ckb-riscv-gnu-toolchain:bionic-20191012 bashroot&#64;66e3b39e0dfd:/# cd /coderoot&#64;66e3b39e0dfd:/code# riscv64-unknown-elf-gdb carrotGNU gdb (GDB) 8.3.0.20190516-gitCopyright (C) 2019 Free Software Foundation, Inc.License GPLv3&#43;: GNU GPL version 3 or later //gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.Type "show copying" and "show warranty" for details.This GDB was configured as "--host&#61;x86_64-pc-linux-gnu --target&#61;riscv64-unknown-elf".Type "show configuration" for configuration details.For bug reporting instructions, please see://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at: //www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from carrot...(gdb) target remote 192.168.1.230:2000Remote debugging using 192.168.1.230:20000x00000000000100c6 in _start ()(gdb)注意&#xff0c;这里的 192.168.1.230是我的工作站在本地网络中的 IP 地址&#xff0c;你可能需要调整该地址&#xff0c;因为你的计算机可能是不同的 IP 地址。现在我们可以试一下常见的 GDB 调试过程&#xff1a;

(gdb) b mainBreakpoint 1 at 0x106b0: file carrot.c, line 6.(gdb) cContinuing.Breakpoint 1, main (argc&#61;0, argv&#61;0x400000) at carrot.c:66 size_t index &#61; 0;(gdb) n7 uint64_t len &#61; 0;(gdb) n11 len &#61; 6;(gdb) n12 memset(buffer, 0, 6);(gdb) n13 ret &#61; ckb_load_cell_data(buffer, &len, 0, index, CKB_SOURCE_OUTPUT);(gdb) n14 if (ret &#61;&#61; CKB_INDEX_OUT_OF_BOUND) {(gdb) n18 int cmp &#61; memcmp(buffer, "carrot", 6);(gdb) n19 if (cmp) {(gdb) p cmp$1 &#61; -99(gdb) p buffer[0]$2 &#61; 0 &#39;\000&#39;(gdb) n20 return -1;这里我们可以看到哪里出问题了&#xff1a;buffer中第一个字节的值是 0&#xff0c;这和 c不同&#xff0c;因此我们的 buffer和 carrot不同。条件 if (cap) {没有跳转到下一个循环&#xff0c;而是跳到了 true 的情况&#xff0c;返回了 -1&#xff0c;表明与 carrot匹配。出现这样问题的原因是&#xff0c;当两个 buffers相等的时候&#xff0c;memcmp将会返回 0&#xff0c;当它们不相等的时候&#xff0c;将返回非零值。但是我们没有测试 memcmp的返回值是否为 0&#xff0c;就直接在 if条件中使用了它&#xff0c;这样 C 会把所有的非零值都视为 true&#xff0c;这里返回的 -99就会被判断为 true。对于初学者而言&#xff0c;这是在 C 中会遇到的典型的错误&#xff0c;我希望你不会再犯这样的错误。&#xff1a;)现在我们知道了错误的原因&#xff0c;接下来去修复 carrot 脚本中的错误就非常简单了。但是正如你看到的&#xff0c;我们设法从 CKB 上获取一笔错误交易在运行时的状态&#xff0c;然后通过 GDB(一个业界常见的工具)来对其进行调试。而且您在 GDB 上现有的工作流程和工具也可以在这里使用&#xff0c;是不是很棒&#xff1f;

基于 REPL 的开发/调试

然而&#xff0c;GDB 仅仅是现代软件开发中的一部分。动态语言在很大程度上占据了主导地位&#xff0c;很多程序员都使用基于 REPL 的开发/调试工作流。这与编译语言中的 GDB 完全不同&#xff0c;基本上你需要的是一个运行的环境&#xff0c;你可以输入任何你想要与环境进行交互的代码&#xff0c;然后得到不同的结果。正如我们将在这里展示的&#xff0c;CKB 也会支持这种类型的开发/调试工作流。: p在这里&#xff0c;我们将使用 ckb-duktape 来展示基于 Javascript 的 REPL。但是请注意&#xff0c;这只是一个 demo 用来演示一下工作流程&#xff0c;没有任何东西阻止您将自己喜爱的动态语言(不管是 Ruby、Rython、Lisp 等等)移植到 CKB 中去&#xff0c;并为该语言启动 REPL。首先&#xff0c;让我们尝试编译 duktape&#xff1a;

$ git clone https://github.com/nervosnetwork/ckb-duktape$ cd ckb-duktape$ sudo docker run --rm -it -v &#96;pwd&#96;:/code nervos/ckb-riscv-gnu-toolchain:bionic-20191012 bashroot&#64;982d1e906b76:/# cd /coderoot&#64;982d1e906b76:/code# makeriscv64-unknown-elf-gcc -Os -DCKB_NO_MMU -D__riscv_soft_float -D__riscv_float_abi_soft -Iduktape -Ic -Wall -Werror c/entry.c -c -o build/entry.oriscv64-unknown-elf-gcc -Os -DCKB_NO_MMU -D__riscv_soft_float -D__riscv_float_abi_soft -Iduktape -Ic -Wall -Werror duktape/duktape.c -c -o build/duktape.oriscv64-unknown-elf-gcc build/entry.o build/duktape.o -o build/duktape -lm -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,-sriscv64-unknown-elf-gcc -Os -DCKB_NO_MMU -D__riscv_soft_float -D__riscv_float_abi_soft -Iduktape -Ic -Wall -Werror c/repl.c -c -o build/repl.oriscv64-unknown-elf-gcc build/repl.o build/duktape.o -o build/repl -lm -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,-sroot&#64;982d1e906b76:/code# exit

你需要在这里生成 build/repl二进制文件。和 carrot 的例子类似&#xff0c;我们先将 duktape REPL 的二进制文件部署在 CKB 上:

pry(main)> api &#61; CKB::API.newpry(main)> wallet &#61; CKB::Wallet.from_hex(api, "")pry(main)> wallet2 &#61; CKB::Wallet.from_hex(api, CKB::Key.random_private_key)pry(main)> duktape_repl_data &#61; File.read("build/repl")pry(main)> duktape_repl_data.bytesize&#61;> 283048pry(main)> duktape_repl_tx_hash &#61; wallet.send_capacity(wallet2.address, CKB::Utils.byte_to_shannon(300000), CKB::Utils.bin_to_hex(duktape_repl_data), fee: 310000)pry(main)> duktape_repl_data_hash &#61; CKB::Blake2b.hexdigest(duktape_repl_data)pry(main)> duktape_repl_type_script &#61; CKB::Types::Script.new(code_hash: duktape_repl_data_hash, args: "0x")pry(main)> duktape_repl_cell_dep &#61; CKB::Types::CellDep.new(out_point: CKB::Types::OutPoint.new(tx_hash: duktape_repl_tx_hash, index: 0))我们还需要创建一笔包含 duktape 脚本的交易&#xff0c;我这里使用一个非常简单的脚本&#xff0c;当然你可以加入更多的数据&#xff0c;这样你就可以在 CKB 上玩起来了

pry(main)> tx &#61; wallet.generate_tx(wallet2.address, CKB::Utils.byte_to_shannon(100), use_dep_group: false, fee: 5000)pry(main)> tx.outputs[0].type &#61; duktape_repl_type_scriptpry(main)> tx.cell_deps < tx.witnesses[0] &#61; "0x"然后让我们把它转存到文件中&#xff0c;并检查 duktape 类型脚本的哈希&#xff1a;

pry(main)> CKB::MockTransactionDumper.new(api, tx).write("duktape.json")&#61;> 2765824pry(main)> duktape_repl_type_script.compute_hash&#61;> "0xa8b79392c857e29cb283e452f2cd48a8e06c51af64be175e0fe0e2902c482837"

与上面不同的是&#xff0c;我们不需要启动 GDB&#xff0c;而是可以直接启动程序&#xff1a;

$ ./target/release/ckb-debugger -g type -h 0xa8b79392c857e29cb283e452f2cd48a8e06c51af64be175e0fe0e2902c482837 -t duktape.jsonduk>你可以看到一个 duk>提示你输入 JS 代码&#xff01;同样&#xff0c;如果遇到错误&#xff0c;请检查是否需要更改类型脚本的哈希&#xff0c;或者使用正确的 duktape.json路径。我们看到常见的 JS 代码可以在这里工作运行&#xff1a;

duk> print(1 &#43; 2)3&#61; undefinedduk> function foo(a) { return a &#43; 1; }&#61; undefinedduk> foo(123)&#61; 124您还可以使用与 CKB 相关的功能&#xff1a;

duk> var hash &#61; CKB.load_script_hash()&#61; undefinedduk> function buf2hex(buffer) { return Array.prototype.map.call(new Uint8Array(buffer), function(x) { return (&#39;00&#39; &#43; x.toString(16)).slice(-2); }).join(&#39;&#39;); }&#61; undefinedduk> buf2hex(hash)&#61; a8b79392c857e29cb283e452f2cd48a8e06c51af64be175e0fe0e2902c482837请注意&#xff0c;我们在这里得到的脚本哈希正是我们当前执行的类型脚本的哈希&#xff01;这将证明 CKB 系统调试在这里是有效的&#xff0c;我们也可以尝试更多有趣的东西&#xff1a;

duk> print(CKB.SOURCE.OUTPUT)2&#61; undefinedduk> print(CKB.CELL.CAPACITY)0&#61; undefinedduk> capacity_field &#61; CKB.load_cell_by_field(0, 0, CKB.SOURCE.OUTPUT, CKB.CELL.CAPACITY)&#61; [object ArrayBuffer]duk> buf2hex(capacity_field)&#61; 00e40b5402000000这个 00e40b5402000000可能在一开始看起来有点神秘&#xff0c;但是请注意 RISC-V 使用的是 little endian(低字节序)&#xff0c;所以如果在这里我们将字节序列颠倒&#xff0c;我们将得到 00000002540be400&#xff0c;在十进制中正好是 10000000000。还要记住&#xff0c;在 CKB 中容量使用的单位是 shannons&#xff0c;所以 10000000000正好是 100个字节&#xff0c;这正是我们生成上面的交易时&#xff0c;想要发送的代币的数量&#xff01;现在你看到了如何在 duktape 环境中与 CKB 愉快地玩耍了 &#xff1a;)

结论

我们已经介绍了两种不同的在 CKB 中调试的过程&#xff0c;你可以随意使用其中一种(或者两种)。我已经迫不及待地想看你们在 CKB 上玩出花来啦&#xff01;dbd29ac77a33d9f8148e825c3195d114.gif

和 Xuejie 一起玩转 CKB&#xff01;

快来创建各种 Amazing 的应用吧&#xff01;

8af51451c281a55cf8de7c124b0a20e3.gif




推荐阅读
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • tcpdump 4.5.1 crash 深入分析
    tcpdump 4.5.1 crash 深入分析 ... [详细]
  • 本文介绍了使用Rust语言编写、保存和编译程序的简单步骤。首先,打开记事本文件并编写程序代码,然后将代码保存到一个以.rs为扩展名的文件中。接下来,使用rustc命令来编译运行程序。最后,通过命令行运行编译后的程序,得到输出结果。如果遇到编译错误,可以下载Build Tools for Visual Studio 2017来解决。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了在Ubuntu 11.10 x64环境下安装Android开发环境的步骤,并提供了解决常见问题的方法。其中包括安装Eclipse的ADT插件、解决缺少GEF插件的问题以及解决无法找到'userdata.img'文件的问题。此外,还提供了相关插件和系统镜像的下载链接。 ... [详细]
  • Harmony 与 Game Space 达成合作,在 Shard1 上扩展 Web3 游戏
    旧金山20 ... [详细]
author-avatar
青春脸001
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有