您尚未登录。

#1 2021-02-18 12:30:49

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

众所周知,C/C++是编译型语言,但若有人向你提及C++的解释器,你也不必认为他在痴人说梦——确实有一个C++的解释器项目存在,这就是cling。

什么是Cling

Cling是一个基于LLVM和Clang的现代C++解释器,作为CERN基于C++的数据分析框架Root的一部分被开发。CERN就是用大型强子对撞机(aka LHC)证实了希格斯玻色子存在的那个CERN,Root是负责对包括这项工作在内的LHC产生的实验数据进行分析的套件。Cling是用户和Root动态交互的界面,不同于那些prove-of-concept的玩具,cling实现了stack trace,variable shadowing(新声明的变量覆盖已有的同名变量)和语义化的对象打印等脚本语言解释器的功能。Cling是我见过的最具想象力的程序之一,因为兼容C ABI,他可以直接执行系统调用,这基本上意味着你可以利用他以一种动态的、交互式的模式来做任何事!

Cling on ARM

令人稍感困惑的是这么nb的一个程序似乎并不太为人所知。除开别的应用不谈,对于嵌入式开发来说,一个C/C++解释器应该有非比寻常的意义:对SoC上各个外设的控制本质上是通过kernel提供的系统调用实现的,而通常开发这种C程序的流程是:写代码—编译—debug—再编译—debug……当项目很大时,编译这一步会很耗时,无法敏捷地迭代真正感兴趣的那部分代码。如果有一个C++解释器的话,你可以动态地执行外设的API,写完一行代码敲下回车就能实时获得外设的响应。这和microPython之类的项目的概念有些类似,但因为这里你直接面对的是最底层的C API,就不会受限于只能调用别的框架封装好的API。就像上面提到的,你可以动态地做任何事!
遗憾的是,CERN Root官网只提供了X86/64平台上预编译的压缩包下载。为了能让cling运行在arm平台上,我在一个chroot环境下从源码编译了cling可独立运行的二进制,和编译脚本一起发布在GitHub上:
仓库链接
目前只编译了armhf(armv7)的版本,可以直接进入release下载,也可以clone仓库后运行build.sh自己编译。 cling的可执行文件是

./release/bin/cling

最近编辑记录 SdtElectronics (2021-02-18 12:59:48)

离线

#2 2021-02-18 12:50:24

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

一些Note

nb的功能,nb的开销
和cling强大的功能相称的是其巨大的体积(我编译出来的版本整个文件夹达到了400+M,这个大小包括了一个完整的LLVM和clang,因此可能有可以精简的文件)以及相对低的解释执行性能,所以尽量不要在一个配置过低的平台上运行它。我在一张A20(dual a7@1G)+128M的板子上可以正常运行,一条简单语句的解释时间接近1s。

从源码编译
如果你想从源码编译,有一些要注意的地方:
依赖:libxml2 cmake git python2 libffi(libffi-dev on Debian-based distros)
系统配置:编译llvm是一项极其消耗计算资源的工作,我在i5-8300H平台上的QEMU下开j4进行编译,消耗五个半小时左右。之所以不开j8是因为编译的空间开销也很大,线程过多容易爆内存。注意编译脚本默认是开

$nproc

个线程的,如果要指定线程数,可以传一个参数给脚本:

./build.sh 4

网络:整个项目的源码体积在100+M,建议shell开代理,不然会很慢。

串口设备上的问题
cing似乎会打印一些和串口不兼容的字符,如果你在使用串口终端的设备上遇到问题,可以尝试用以下命令运行cling:

script -qc ./cling | cat 

离线

#3 2021-02-18 13:41:20

微凉VeiLiang
会员
所在地: 深圳
注册时间: 2018-10-28
累计积分: 473
个人网站

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

感谢分享,学习下

离线

#4 2021-02-18 17:02:53

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

cling快速入门:

hello world:

[cling]$ #include <stdio.h>
[cling]$ 
[cling]$ printf("hello world\n");
hello world

cling可以直接include在系统搜索路径里的头文件,包括kernel headers,其中声明的函数就能立即使用了

获取表达式的值:
表达式的末尾不写分号会怎么样?报错?不,cling会回显该表达式的值。写过matlab的人应该对此不陌生:语句末带分号,静默语句的值在console中的输出,反之则不静默:

[cling]$ __cplusplus
(long) 201703

cling可以语义化地回显一些对象和对象引用表达式的值,这些对象基本是stl容器:

[cling]$ #include <string>
[cling]$ 
[cling]$ std::string {"str"}
(std::string) "str"
[cling]$ 
[cling]$ #include <vector>
[cling]$ 
[cling]$ std::vector<char> ve{'c', 'h', 'a', 'r'} 
(std::vector<char> &) { 'c', 'h', 'a', 'r' }
[cling]$ 
[cling]$ #include <map>
[cling]$ 
[cling]$ std::map<int, char> mp{std::pair<int,char>()}
(std::map<int, char> &) { 0 => '0x00' }

函数名也是表达式。输入函数名会怎么样呢?

[cling]$ atoi
(int (*)(const char *) throw()) Function @0xb6c7bcd1
  at /usr/include/stdlib.h:104:
extern int atoi (const char *__nptr)
     __THROW __attribute_pure__ __nonnull ((1))

函数签名被打印了出来!
我编译的版本开启了RTTI,因此还可以通过typeid动态审查变量的类型:

[cling]$ auto b = std::string ("typed")
(std::basic_string<char, std::char_traits<char>, std::allocator<char> > &) "typed"
[cling]$ typeid(b).name()
(const char *) "NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"

离线

#5 2021-02-18 17:25:57

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

cling进阶:
除了标准的C++语法,cling还支持一些内置的命令。我们来看的第一条命令,是可以列出其他命令用法的:

.help
[cling]$ .help

 Cling (C/C++ interpreter) meta commands usage
 All commands must be preceded by a '.', except
 for the evaluation statement { }
 ==============================================================================
 Syntax: .Command [arg0 arg1 ... argN]

   .L <filename>                - Load the given file or library

   .(x|X) <filename>[args]      - Same as .L and runs a function with
                                  signature: ret_type filename(args)

   .> <filename>                - Redirect command to a given file
      '>' or '1>'               - Redirects the stdout stream only
      '2>'                      - Redirects the stderr stream only
      '&>' (or '2>&1')          - Redirects both stdout and stderr
      '>>'                      - Appends to the given file

   .undo [n]                    - Unloads the last 'n' inputs lines

   .U <filename>                - Unloads the given file

   .I [path]                    - Shows the include path. If a path is given -
                                  adds the path to the include paths

   .O <level>                   - Sets the optimization level (0-3)
                                  (not yet implemented)

   .class <name>                - Prints out class <name> in a CINT-like style

   .files                       - Prints out some CINT-like file statistics

   .fileEx                      - Prints out some file statistics

   .g                           - Prints out information about global variable
                                  'name' - if no name is given, print them all

   .@                           - Cancels and ignores the multiline input

   .rawInput [0|1]              - Toggle wrapping and printing the
                                  execution results of the input

   .dynamicExtensions [0|1]     - Toggles the use of the dynamic scopes and the
                                  late binding

   .printDebug [0|1]            - Toggles the printing of input's corresponding
                                  state changes

   .storeState <filename>       - Store the interpreter's state to a given file

   .compareState <filename>     - Compare the interpreter's state with the one
                                  saved in a given file

   .stats [name]                - Show stats for internal data structures
                                  'ast'  abstract syntax tree stats
                                  'asttree [filter]'  abstract syntax tree layout
                                  'decl' dump ast declarations
                                  'undo' show undo stack

   .help                        - Shows this information

   .q                           - Exit the program

英文看得头晕转向?没事,常用的命令只有几个:
.q, 退出cling。等效于Ctrl+D。

.L, 加载文件,之后这个文件里的符号就可以被调用了。

# echo 'int val = 666;' > tst.cpp
# ./cling
****************** CLING ******************
* Type C++ code and press enter to run it *
*             Type .q to exit             *
*******************************************
[cling]$ 
[cling]$ .L tst.cpp
[cling]$ val
(int) 666

注意,如果你加载的文件还依赖别的文件中的符号,你需要把依赖的文件也通过.L加载。cling没法帮你处理依赖关系,毕竟它不知道依赖的文件在哪。

类似的命令还有一个.x,他会加载文件之后执行文件中和文件同名的那个函数:

# echo 'int tst(){return 666;}' > tst.cpp
# ./cling
****************** CLING ******************
* Type C++ code and press enter to run it *
*             Type .q to exit             *
*******************************************
[cling]$ 
[cling]$ .x tst.cpp
(int) 666

.I 显示或添加包含路径

[cling]$ .I
-cxx-isystem
/usr/include/c++/10
-cxx-isystem
/usr/include/arm-linux-gnueabihf/c++/10
-cxx-isystem
/usr/include/c++/10/backward
-isystem
/usr/local/include
-isystem
/home/cling-src/release/bin/../lib/clang/5.0.0/include
-extern-c-isystem
/usr/include/arm-linux-gnueabihf
-extern-c-isystem
/include
-extern-c-isystem
/usr/include
-I
/home/cling-src/release/include
-I
/home/cling-src/llvm/tools/cling/include
-I
.
-resource-dir
/home/cling-src/release/bin/../lib/clang/5.0.0
-nostdinc++

.class, 非常强大的命令,打印出某个class的layout:

[cling]$ #include<limits>
[cling]$ 
[cling]$ .class std::numeric_limits<char>
===========================================================================
struct std::numeric_limits<char>
SIZE: 1 FILE: limits LINE: 453
List of member variables --------------------------------------------------
limits          455 0x0      public: static constexpr _Bool is_specialized
limits          468 0x0      public: static constexpr int digits
limits          469 0x0      public: static constexpr int digits10
limits          471 0x0      public: static constexpr int max_digits10
limits          473 0x0      public: static constexpr _Bool is_signed
limits          474 0x0      public: static constexpr _Bool is_integer
limits          475 0x0      public: static constexpr _Bool is_exact
limits          476 0x0      public: static constexpr int radix
limits          484 0x0      public: static constexpr int min_exponent
limits          485 0x0      public: static constexpr int min_exponent10
limits          486 0x0      public: static constexpr int max_exponent
limits          487 0x0      public: static constexpr int max_exponent10
limits          489 0x0      public: static constexpr _Bool has_infinity
limits          490 0x0      public: static constexpr _Bool has_quiet_NaN
limits          491 0x0      public: static constexpr _Bool has_signaling_NaN
limits          492 0x0      public: static constexpr enum std::float_denorm_style has_denorm
limits          494 0x0      public: static constexpr _Bool has_denorm_loss
limits          508 0x0      public: static constexpr _Bool is_iec559
limits          509 0x0      public: static constexpr _Bool is_bounded
limits          510 0x0      public: static constexpr _Bool is_modulo
limits          512 0x0      public: static constexpr _Bool traps
limits          513 0x0      public: static constexpr _Bool tinyness_before
limits          514 0x0      public: static constexpr enum std::float_round_style round_style
List of member functions :---------------------------------------------------
filename     line:size busy function type and name
(compiled)     (NA):(NA) 0 public: static constexpr char min() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char max() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char lowest() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char epsilon() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char round_error() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char infinity() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char quiet_NaN() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char signaling_NaN() noexcept;
(compiled)     (NA):(NA) 0 public: static constexpr char denorm_min() noexcept;

最近编辑记录 SdtElectronics (2021-02-18 17:38:52)

离线

#6 2021-02-18 17:36:55

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

还想了解更多?
令人哭笑不得的是,Root团队好像全然没有意识到他们搞出了个什么神仙玩意,他们的主要精力投入在了Root本体的开发上,而cling则一直被当成一个附属项目对待。因此,想探索cling的更多功能,最好的地方是CERN Root的官网,以及CERN Root的官方community。Root包括cling至今还在被高度活跃的社区不断地开发和完善中,在其组织的一些会议中的幻灯片展示了将来的Roadmap,还会有更多的黑魔法被加入到其中,最受关注的一些特性包括C++20的modules。

离线

#7 2021-02-18 18:33:02

novice
会员
注册时间: 2019-07-26
累计积分: 36

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

C++本地编译的速度都那么慢了,解释C++的速度能让人满意吗?解释型C脚本的速度都比js/lua慢那么多,何况是C++?

离线

#8 2021-02-18 18:44:53

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

novice 说:

C++本地编译的速度都那么慢了,解释C++的速度能让人满意吗?解释型C脚本的速度都比js/lua慢那么多,何况是C++?

你说的很对,总体来说解释C++的开销是很大的,但是否让人满意要看实际场景了。一方面,C++有模板、constexpr之类的编译开销很大的特性,大量使用这些特性的代码会很慢。另一方面,cling宣称自己是jit解释器,这意味着热路径上的代码不会有重复解释的开销,一定程度上优化了性能。cling的目的不是代替编译好的程序,而是在原型验证阶段提供一个强大的武器:写过脚本语言的人都知道,有一个REPL对于开发阶段来说是多大的帮助,这种交互式的开发体验是一般的编译型语言难以得到的,而cling为C/C++提供了这个宝贵的能力

离线

#9 2021-02-19 09:58:08

演技担当黄晓明
会员
注册时间: 2017-10-17
累计积分: 137

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

搞一个C++解释器意义何在?都用了脚本了,Lua  python之类的东西应该更适应存在

离线

#10 2021-02-19 18:35:20

SdtElectronics
会员
注册时间: 2020-07-27
累计积分: 50

Re: 使用C++解释器动态调试你的嵌入式C程序:cling the C++ interpreter on ARM

C++解释器的意义,特别是对嵌入式开发领域而言,我已经在1楼说得很清楚了。当然,缺少脚本语言或者和硬件交互的开发经历的人可能不太好理解。

补充几个特性:
其实cling也支持tab补全,但他的match机制还不是很完善,现在不太好用。

cling支持自动的多行模式,当输入一个 { 而不在本行闭合它的时候,就会自动进入多行模式。多行模式的提示符会多一个'?'

.L 命令除了支持加载C/C++源码,还支持直接加载动态库,前提是先导入了对应的头文件,才能使用动态库里的符号。

来一个展示上面两个特性的例子,用cling和内核新的GPIO接口libgpiod交互:

[cling]$ #include <gpiod.hpp>
[cling]$ 
[cling]$ .L /usr/lib/arm-linux-gnueabihf/libgpiod.so.2
[cling]$ 
[cling]$ .L /usr/lib/arm-linux-gnueabihf/libgpiodcxx.so
[cling]$ 
[cling]$ gpiod::chip chip("/dev/gpiochip0")
(gpiod::chip &) @0xb6fa100c
[cling]$ 
[cling]$ auto lines = chip.get_line(236)
(gpiod::line &) @0xb6fa1014
[cling]$ 
[cling]$ lines.request({.consumer = std::string("tst"),
[cling]$ ?      
[cling]$ ?      .request_type = gpiod::line_request::DIRECTION_OUTPUT,
[cling]$ ?      
[cling]$ ?      .flags = gpiod::line_request::FLAG_OPEN_DRAIN})
[cling]$

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn