EOS 调试合约之日志打印

EOS智能合约的开发,目前还没有办法像传统CPP开发一样通过lldb/gdb进行断点调试。目前只有通过
打日志的方式进行调试。若想在命令行中将合约日志打印出来,需要在启动nodeos的时候传入--contracts-console
选项,否则日志不会被打印。

基本C函数

在print.h文件中,提供了几个基本的C函数,因为C没有重载,所以其实可以认为是提供了print的统一桥接层。
这里为什么说是桥接层呢? 如果观察"eosiolib"目录的代码,大家会发现,这里大部分是函数的声明,而没有
实现,比如这里的print就没有找到的他的实现。

实际上print的实现下eos的代码的"libraries/chain/wasm_interface.cpp"这个文件中:

class console_api : public context_aware_api {
   public:
      console_api( apply_context& ctx )
      : context_aware_api(ctx,true)
      , ignore(!ctx.control.contracts_console()) {}

      // Kept as intrinsic rather than implementing on WASM side (using prints_l and strlen) because strlen is faster on native side.
      void prints(null_terminated_ptr str) {
         if ( !ignore ) {
            context.console_append<const char*>(str);
         }
      }
...
}

那懂CPP的肯定会说,这里是C++啊,还是个类,怎么能说是上面的C的实现呢?

原因是,其实我们的合约C++代码最终是要编译成WebAssembly代码,而WebAssembly在每个节点上执行的时候实际上是跑在一个runtime中,可以认为其
是一个沙盒或者说是虚拟机。这样在类比如Java里面的JNI(做Android开发的同学一定不陌生),或者JSBridge(做移动端开发的东西一定知道),从其他
语言调用C/C++,其接口层一般都是通过C类进行桥接的,因为其本质就是找到函数的代码段地址并执行,相对容易且稳定。

在上面的这个文件中:

REGISTER_INTRINSICS(console_api,
   (prints,                void(int)      )
   (prints_l,              void(int, int) )
   (printi,                void(int64_t)  )
   (printui,               void(int64_t)  )
   (printi128,             void(int)      )
   (printui128,            void(int)      )
   (printsf,               void(float)    )
   (printdf,               void(double)   )
   (printqf,               void(int)      )
   (printn,                void(int64_t)  )
   (printhex,              void(int, int) )
);

实现了对相关桥接调用的注册。

在上面的实现中,我们可以看到,print本质就是通过context.console_append<const char*>(str)将内容打印到控制台日志上。

这里来看pirnt.h提供的几个打印函数:

函数原型作用
void prints( const char* cstr );打印char *字符串
void prints_l( const char* cstr, uint32_t len);打印char *字符串中指定的长度
void printi( int64_t value );打印int64
void printui( uint64_t value );打印uint64
void printi128( const int128_t* value );打印int128
void printui128( const uint128_t* value );打印uint128
void printsf(float value);打印32位的float
void printdf(double value);打印double
void printqf(const long double* value);打印long double
void printn( uint64_t name );将一个uint64按照base32打印字符串内容
void printhex( const void* data, uint32_t datalen );将二进制内容按照hex编码打印

C++扩展

因为我们写合约一般是按照CPP来写的。所以eosiolib又为我们封装了一层CPP接口。比如上面的printxx都封装到了print这个函数中。这里只看一个实现:

inline void print( const char* ptr ) {
    prints(ptr);
}

这里重复利用了CPP重载函数的特性。

除了print函数,CPP的接口还提供了类似printf/cout这样的便利封装,比如:

inline void print( bool val ) {
    prints(val?"true":"false");
}

打印bool值的字符串表示。

template<typename T>
inline void print( T&& t ) {
    t.print();
}

通过模板来实现对类型print函数的调用。

以及牛逼的:

inline void print_f( const char* s ) {
    prints(s);
}

template <typename Arg, typename... Args>
inline void print_f( const char* s, Arg val, Args... rest ) {
    while ( *s != '\0' ) {
        if ( *s == '%' ) {
            print( val );
            print_f( s+1, rest... );
            return;
        }
     prints_l( s, 1 );
     s++;
    }
}  

这里通过模板实现了printf格式化输出的功能。可以像使用C里面的printf一样使用 print_f

如果你用过JavaScript的话,肯定会记得通过console.log(a,b,c)就可以把三个变量依次打印出来。这里CPP的接口也提供了类似的功能:

template<typename Arg, typename... Args>
void print( Arg&& a, Args&&... args ) {
     print(std::forward<Arg>(a));
     print(std::forward<Args>(args)...);
}

通过传递不定参数给print即可将他们都打印出来。

总结

EOS 合约开发目前只能通过打日志的方式来进行逻辑的测试和调试,因为需要给nodeos启动的时候传递选项,那么就需要有自己的测试节点环境(环境部署可以参考
之前的教程)。日志打印通过print系列函数,其CPP接口已经相当友好。基本通过print print_f
可以满足日常的调试需求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值