在调试C/C++程序时,经常需要查看和操作数组和向量。本文将展示如何利用GDB调试器的Python API来实现这一功能。将以一个二维数组的本征值可视化为例,演示如何进行操作。
首先,需要确保安装了Python和GDB调试器。此外,还需要安装numpy和matplotlib库,这两个库将用于数组操作和数据可视化。
# 安装numpy和matplotlib
pip install numpy matplotlib
假设有一个名为mat的二维数组,想要在调试会话中可视化其本征值。以下是如何操作的步骤:
# 定义数组
float mat[10][10] = ...;
# 使用Python代码创建numpy数组
(gdb) py
> import gdb_numpy
> import numpy as np
> import matplotlib.pyplot as plt
# 将C/C++数组转换为numpy数组
> mat = gdb_numpy.to_array("mat") # 创建一个与变量mat对应的numpy数组
# 打印数组形状
> print(mat.shape
(10,10)
# 计算本征值
> y = np.linalg.eigvalsh(mat)
# 绘制本征值
> plt.plot(y)
> plt.show() # 显示图形
以上代码将显示mat数组的本征值的图形。
在使用matplotlib.pyplot或matplotlib.pylab时,必须调用show方法来显示图形。此外,在图形关闭之前,GDB不会响应任何命令。
上述代码依赖于numpy库,它可以从C/C++指针、数组和STL向量创建numpy数组。有关如何安装和使用numpy的更多信息,请访问其官方网站。
# 安装代码
python setup.py install
在GDB控制台中导入gdb_numpy模块:
(gdb) py import gdb_numpy
从C/C++指针/数组/向量类型创建numpy数组,将变量名作为字符串传递给gdb_numpy模块中的to_array函数:
(gdb) py vec = gdb_numpy.to_array("vec") # vec现在是一个numpy数组
如果vec是一个STL向量或内置数组,这将创建一个适当形状的numpy数组。然而,如果vec是一个指针,那么用户必须提供第二个参数来指示其维度。
# 定义指针
float** mat = ...;
# 提供维度
(gdb) py
> mat = gdb_numpy.to_array("mat", (10,10))
即使只有一个维度,也必须以元组的形式传递:
float* vec = ...;
(gdb) py vec = gdb_numpy.to_array("vec", (10))
该方法还支持嵌套类型:
std::vector> mat = ...;
> py mat = gdb_numpy.to_array("mat") # mat是一个2D numpy数组
将简要介绍一些gdb-python API(在gdb 7之后支持),这些API在代码中使用。有关gdb中Python API的详细信息,可以在gdb文档中找到。
在gdb控制台中,可以通过命令python(或py)后跟Python命令来访问Python解释器:
(gdb) py print 1 + 2
3
如果没有为命令python提供参数,则将进入多行模式:
(gdb) py
> x = 1 + 2
> print x
> end
3
正在调试的C/C++程序中的变量可以在Python中使用gdb模块的parse_and_eval方法访问,该模块在通过gdb访问Python解释器时自动导入。
(gdb) py my_var = gdb.parse_and_eval("my_var")
parse_and_eval返回gdb.Value类型的实例,其中包含C/C++变量的信息。例如,可以通过type成员访问C/C++类型的名称:
(gdb) py
> my_array = gdb.parse_and_eval("my_array")
> print my_array.type
> end
double[10]
类成员可以通过索引操作符访问:
(gdb) py
> my_class = gdb.parse_and_eval("my_class")
> my_data = my_class['data'] # 获取my_class.data
如果变量是指针类型,则可以使用索引来解引用它:
(gdb) py print my_data[10]
该模块可以扩展以适应自定义容器类型。这涉及到从模块deref中的DeRefBase类派生。假设有一个用户定义的矩阵类型,希望扩展该模块以使其工作:
template
class MyMatrix {
public:
T& operator()(int i, int j) {
return data[i*columns+j];
}
const T& operator()(int i, int j) const {
return data[i*columns+j];
}
private:
T* data;
int rows;
int columns;
};
该类型将其底层数据存储在成员data中,以便如果M是MyMatrix的实例,则M(i,j)由*(M.data+i*M.columns+j)给出。
首先,需要覆盖DeRefBase中的deref方法,该方法用于解引用容器。这是通过解引用MyMatrix实例的成员data来完成的。
def deref(self, val, indices):
data = val['data']
columns = int(val['columns'])
return data[indices[0] * columns + indices[1]]
例如,以下代码解引用了矩阵:
# derefMyMat是一个适当的DeRef类实例
(gdb) py print derefMyMat.deref(Mat,(i,j)) # 给出Mat(i,j)
接下来,需要更新并初始化类的某些成员。成员bounds存储矩阵的维度。
def __init__(self, Mat, shape_ind, shape):
super(DeRefMyMatrix, self).__init__(Mat, shape_ind, shape)
self.val = self.deref(Mat, [0,0]) # 更新为解引用类型
self.bounds = [Mat['rows'], Mat['columns']] # 矩阵的维度
其他需要更新的类成员是val。这应该是一个gdb.Value实例,对应于解引用后的对象。
self.val = self.deref(Mat,(0,0))
由于self.val的值不会被使用,所以在deref方法中使用什么索引并不重要,只要它是一个有效的索引即可。例如,也可以这样使用:
self.val = self.deref(Mat, (self.bounds[0]-1, self.bounds[1]-1)) # 只要索引有效就可以
最后,需要提供一个正则表达式来识别类。这应该是与类的类型名称匹配的内容,可以通过gdb.Value实例的type成员访问。
(gdb) py my_mat = gdb.parse_and_eval("my_mat")
(gdb) py print my_mat.type
MyMatrix
因此,在案例中,模式可以是^MyMatrix。
class DeRefMyMatrix(DeRefBase):
pattern = re.compile('^MyMatrix')
...
需要编写的Python类是:
class DeRefMyMatrix(DeRefBase):
pattern = re.compile('^MyMatrix')
def __init__(self, Mat, shape_ind, shape):
super(DeRefMyMatrix, self).__init__(Mat, shape_ind, shape)
self.val = self.deref(Mat, [0,0]) # 更新为解引用类型
self.bounds = [Mat['rows'], Mat['columns']] # 矩阵的维度
def deref(self, val, indices):
data = val['data']
columns = int(val['columns'])
return data[indices[0] * columns + indices[1]]
要在gdb_numpy模块中使用此类,需要将其添加到模块中的_container_list变量。
_container_list = [... ,deref.DeRefMyMatrix]
现在可以使用gdb_numpy.to_array方法与MyMatrix类一起使用。它还将自动支持嵌套类型,例如:
MyMatrix> 4DTensor = ...;
std::vector> 3DTensor = ...;
MyMatrix> Another3DTensor = ...;