linux下spy++实现
发布者:
Echo
目的
在wxWidgets GUI开发过程中,经常需要查找某窗口对应的C+ +类,通常做法是根据窗口的标签、行为、父窗口关系等在代码中直接搜索,效率很低。为此开发了一个类似于windows下Spy+ +的小工具。 在GUI程序中按下开关快捷键,开启Spy模式,移动鼠标,会高亮所在窗口,并使用tooltip显示窗口对应C++类名。
主要思路
wxWidgets提供了wxFindWindowAtPoint函数可以获取位于某坐标的窗口指针,然后使用typeid(object).name()函数获取类名,并显示.
关键点
- 如何实现不需要重新编译GUI程序的情况下就可以使用该工具
- 如何实现随鼠标移动对窗口spy
- 高亮窗口的刷新
1. 程序HOOK
实现
当在UNIX
下使用动态链接库时,通过设置LD_PRELOAD
可以定义程序运行前优先加载的动态连接库。通过这个环境变量可以实现程序的代码注入。
举个例子:程序A引用了动态连接库lib_real.so
中的函数void Add(int a, int b)
,我们可以定制一个动态链接库lib_inject.so也导出相同签名的Add函数,并设置环境变量LD_PRELOAD="lib_inject.so"
。
当运行程序A时,系统会首先加载lib_inject.so
,并修改函数导入表中的函数指针指向lib_inject.so
中的Add函数。
使用这个方法我们可以实现对应用程序的HOOK.
由于我们目标是linux gtk下使用wxWidgets
开发的GUI程序,所以这里就拿gtk的函数开刀:
extern "C" void gtk_main(void)
{
typedef void (*FuncGtkMain)(void);
static FuncGtkMain real_gtk_main = NULL;
if(real_gtk_main == NULL)
{
real_gtk_main = (FuncGtkMain)dlsym(RTLD_NEXT, "gtk_main");
}
wxMessageBox("hooked successfully");
init_hook();
real_gtk_main();
}
代码中首先使用dlsym
函数获取真实的gtk_main
函数地址,然后执行我们的代码,最后调用真正的gtk_main
。
在init_hook
函数中我们就可以为所欲为了:
void init_hook()
{
handler->SetContainer(wxTheApp->GetTopWindow());
wxTheApp->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(HotKeyHandler::OnKeyDown), NULL, handler);
}
这里我们在app中插入了我们自己的按键事件响应,以响应Spy开关快捷键操作。在HotKeyHandler::OnKeyDown开始Spy:
class HotKeyHandler : public wxEvtHandler
{
public:
void OnKeyDown(wxKeyEvent& event)
{
if(event.GetKeyCode() == WXK_F7) // 快捷键设为F7
{
WidgetSpy::StartShowTip(m_container);
}
else
{
event.Skip();
}
}
// ...
};
2. 鼠标HOOK实现
Windows下有钩子可以实现鼠标的HOOK,但是linux没有,不过wxWindow::CaptureMouse
函数也可以满足我们的需要,在这里创建一个隐藏的wxWindow,用于捕获鼠标消息:
void WidgetSpy::StartSpy(wxWindow *parent_win)
{
m_capture_win = new MouseCaptureWindow(parent_win); // 创建隐藏的鼠标捕获窗口
m_capture_win->Hide();
m_capture_win->GetEventHandler()->Connect(wxEVT_MOTION,
wxMouseEventHandler(WidgetSpy::OnMouseMotion), NULL, this);
m_capture_win->CaptureMouse();
}
在Mouse Motion事件响应函数中高亮窗口、获取窗口类名并显示:
void OnMouseMotion()
{
wxWindow *target_win = ::wxFindWindowAtPoint(::wxGetMousePosition()); // 查找窗口
if (target_win == NULL) return;
wxString class_name = GetClassNameOf(target_win); // 获取窗口类名
if (target_win != m_last_window && m_last_window != NULL)
{
UpdateWindow(m_last_window); // 刷新旧窗口
}
HighlightWindow(target_win); // 在目标窗口上绘制高亮框
ShowSpyTip(m_parent_window, class_name, this, this); // 使用tooltip显示类名
m_last_window = target_win;
}
由于typename(object).name()
获取的是唯一标识符,编译器会对其做一些修饰,gcc提供了相应的函数来获取真正的类名:
wxString GetClassNameOf(wxWindow *win)
{
if (NULL == win)
{
return "";
}
int status;
const char *name = abi::__cxa_demangle(typeid(*win).name(), 0, 0, &status);
if (status != 0)
{
return "";
}
return name;
}
3. 高亮窗口的刷新
高亮窗口不仅需要在未知任意窗口上绘图,还需要在鼠标移开时刷新原窗口。绘图操作可以使用wxScreenDC
,但是刷新操作使用wxWidgets提供的wxWindow::Update()
, wxWindow::Refresh()
很多时候并不能工作,所以这里采用手动截取图像,刷新时贴图的方法解决。
在Spy开启时,保存整个Frame
的截图:
wxBitmap CaptureWindow(wxWindow *win)
{
wxRect rc = win->GetScreenRect();
wxMemoryDC memDC;
wxBitmap image = wxBitmap(rc.width, rc.height);
memDC.SelectObject(image);
wxScreenDC srcDC;
memDC.Blit(0, 0, rc.width, rc.height, &srcDC, rc.GetLeft(), rc.GetTop());
return image;
}
高亮窗口时:
void HighligthWindowInMemory(wxWindow *win)
{
wxRect rc = win->GetScreenRect();
wxScreenDC dc;
dc.SetBrush(*wxTRANSPARENT_BRUSH);
wxPen *pen = wxThePenList->FindOrCreatePen(*wxRED, 3, wxSOLID);
dc.SetPen(*pen);
rc.Deflate(2, 2);
wxScreenDC.DrawRectangle(2, 2, rc.width, rc.height);
}
刷新窗口时:
void UpdateWindow(wxWindow *win)
{
wxRect rc_win = win->GetScreenRect();
wxRect rc_main = m_parent_window->GetScreenRect();
wxScreenDC src_dc;
wxMemoryDC mem_dc;
mem_dc.SelectObject(m_image_main);
src_dc.Blit(rc_win.x, rc_win.y, rc_win.width, rc_win.height,
&mem_dc, rc_main.x - rc_main.x, rc_main.y - rc_main.y);
}
另外,为了避免窗口绘图时的闪烁,实现时采用了双缓冲。
结果
编译出动态连接库libspy.so,Spy窗口时只需要设置LD_PRELOAD
指向该动态链接库,就可以在GUI程序中使用快捷键开启spy。
缺点:由于libspy.so
和GUI应用程序都需要链接到wxWidgets
,当GUI静态链接到wxWidgets
时,libspy.so
就不能正确链接。所以这种方法只能在GUI动态链接到wxWidgets
时使用。
展望
目前只是简单实现了获取窗口的类名的功能,并没有实现Spy++中获取窗口树形结构、属性等功能。但是我们已经可以向GUI程序注入任意代码(包括wxWidgets
调用),进一步的实现也是水到渠成的了。
睿初科技软件开发技术博客,转载请注明出处
blog comments powered by Disqus
发布日期
标签
最近发表
- volatile与多线程
- TDD practice in UI: Develop and test GUI independently by mockito
- jemalloc源码解析-核心架构
- jemalloc源码解析-内存管理
- boost::bind源码分析
- 小试QtTest
- 一个gtk下的目录权限问题
- Django学习 - Model
- Code snippets from C & C++ Code Capsule
- Using Eclipse Spy in GUI products based on RCP
文章分类
- cpp 3
- wxwidgets 4
- swt/jface 1
- chrome 3
- memory_management 5
- eclipse 1
- 工具 4
- 项目管理 1
- cpplint 1
- 算法 1
- 编程语言 1
- python 5
- compile 1
- c++ 7
- 工具 c++ 1
- 源码分析 c++ 3
- c++ boost 2
- data structure 1
- wxwidgets c++ 1
- template 1
- boost 1
- wxsocket 1
- wxwidget 2
- java 2
- 源码分析 1
- 网路工具 1
- eclipse插件 1
- django 1
- gtk 1
- 测试 1
- 测试 tdd 1
- multithreading 1