CN4321258/TP  ISSN10072130X     计算机工程与科学
COMPU TER EN GIN EERIN G&SCIENCE
2009年第31卷第3期 
 Vol131,No13,2009 
文章编号:10072130X(2009)0320066202
一个可编程C语言图像处理软件中
跟踪调试功能的实现3
A Met hod of Implementing t he Debugging and
Tracing Techniques in t he Image Processing Software Based o n t he C Co mpiler
台继荣1,刘吉平2
TAI Ji2rong1,L IU Ji2ping2
(1.武汉理工大学外国语学院,湖北武汉430070;2.武汉大学资源与环境学院,湖北武汉430070)
(1.School of Foreign Langu ages,Wuhan U niversity of T echnology,Wuhan430070;
2.School of R esources and E nvironmental Science,Wuh an U niversity,Wuhan430070,China)
摘 要:通过在C语言编译器产生的汇编代码中增加“软中断”指令和模拟设置断点,回避了Windows调试A PI函数的局限,实现了图像处理软件中编译器的跟踪调试功能。通过追踪编译器生成的局部变量和全局变量内存分配表,实现了断点追踪过程中的数据实时查询功能;通过查特定的函数头标志,实现了call stack功能。本文所述软件模拟实现了常用编译器调试功能中的基本功能。
Abstract:Through adding a“soft interrupt”instruction in the asmbel code produced by the C compiler,and simulating the setting of break points to avoid the Windows’limitation of debugging the A PI f unction,we realize the dubugging and tracing f unction of the compiler in image processing software.Through tracing the local variables and global variables mem2 ory allocation table,we get the real2time inquiry f unction of data in tracing the break points.Through finding the specific f unction head,we realize the function of call stack.The software presented in the paper basically realizes the key technology of debugging in the compiler.
关键词:C编译器;图像处理;调试跟踪;Call Stack;Debug
K ey w ords:C compiler;image processing;tracing;Call Stack;Debug
中图分类号:TP314文献标识码:A
1 引言
我们研制了一个基于C语言编译器的图像处理软件,通过内置的C语言编译器对用户输入的C语言代码编译执行,控制和操作软件平台中的图像,实现图像处理的功能。软件小巧灵活,又可实现复杂编程才能获得的效果,是一个学习图像处理不可多得的教学软件。
早期的版本中存在一些不足,尤其是缺乏调试功能。C语言本身具备灵活的指针操作能力,而用户代码中难免会有内存误操作,都将导致程序崩溃。若程序能像Visual C++一样设置断点,用户就可任意暂停目标代码的执行,通过调试跟踪手段查看局部或全局变量、甚至用户内存,极大地提高了代码纠错效率。
本文主要介绍如何在前期编译器基础上实现简单的跟踪调试功能。
2 程序调试的相关技术:Debug函数介绍
  常用程序可借助Visual C++的调试API函数在指定进程中设置断点,即注入3号中断指令0xCC。这样,程序
3收稿日期:2007211227;修订日期:2008202228
作者简介:台继荣(1981),女,湖北荆门人,硕士生,研究方向为计算机辅助语言测试与编译原理。
通讯地址:430070湖北省武汉市武汉理工大学外国语学院鉴湖校区A2193信箱;T el:(027)63734670;E2mail:ednatjr@yahoo Address:Mail Box A2193,Jianhu Campus,School of Foreign Languages,Wuhan University of Technology,Wuhan,Hubei430070,P.
R.China
运行到该地址时将产生调试中断信息并通知调试程序(编译程序),设计者只要在调试程序中编写相应的事件处理,就可实现各种Debug功能。
程序大体结构如下
m_hProcess=OpenProcess(PROCESS_ALL_ACCESS| PROCESS_VM_OPERA TION,0,mPID);
DebugActiveProcess(mPID)
while(1){
if(Wait ForDebugEvent(&dbg_evt,DEBU G LOOP_WAIT_ TIME)){
OnDebugEvent(dbg_evt);
ContinueDebugEvent(mPID,dbg_evt.dw ThreadId,DB G_ CON TINU E);
}
}
RemoveAllBreakPoint s();
框架中通过CreateProcess函数启动一个新的程序,设置必要的参数,并利用DebugActiveProcess函数就可以捕获目标进程,目标进程即进入被调试状态。调试程序(即监视程序或编译程序)负责对被调试的程序进行调度。调试程序通过Wait ForDebugEvent函数获得来自被调试程序的消息,并根据这些消息代码进行不同的处理;处理完成后,调试程序通过ContinueDebugEvent函数启动被调试程序继续运行。
断点设置需要将0xCC(int3)写入指定的地址。因此,为了设置断点,,在产生中断后,将原指令写回被调试程序,继续其运行。但是,为了保证再次运行到此断点时中断能继续产生,设计者必须在将原地址代码写回被调试程序后让被调试程序以单步的方式运行,再次产生一个单步中断。在这个单步中断处理中,设计者需再次将0xCC代码写入该地址,即可保证下次中断产生。
单步执行是x86芯片组的一个特征。通过设置处理器的TRAP FL A G标志,使得在一次中断后只有一个单独的指令被执行。此方法解决了断点重入问题。
程序实现中因为需要修改内存数据(如注入0xCC指令),用到了VirtualQueryEx、ReadProcessMemory、Virtual2 Protect Ex、WriteProcessMemory等内存函数修改存储器页面保护状态。
2.1 采用Debug函数存在的问题
本图像处理软件内嵌C语言编译器,直接在内存中将用户代码编译,并执行其编译产生的汇编代码。用户可直接借助C语言指针对图像数据进行编程操作。而Win2 dows调试A PI必须针对的是进程,若将图像处理主体程序和编译器编译后的汇编代码分为两个进程处理,首先要考虑如何将编译代码转化成单独进程;其次实现的进程要想继续能借助指针灵活操作图像数据,需考虑复杂的内存共享,实现较为复杂。
若采用int3中断,通过编写驱动修改中断服务程序,同样需要设计中断服务程序和图像处理主程序间的通信、内存共享。
2.2 本文断点的实现方法
处理的本质:在用户C语言代码的每个语句后调用一次检测函数,检测函数的功能就是判断此处用户是否设置了断点。如果有断点,则进入跟踪调试内核,否则继续向下执行。增加检测函数可以直接在用户C语言代码中添加,也可以在汇编代码生成的过程中添加。
本文如下实现:
Pushfd:保存寄存器状态;
Call GetCurrRunInfo:调用检测函数;
popfd:恢复寄存器状态,类似中断返回继续执行。
当用户代码执行时,每行都会进入“中断函数”――G etCurrRunInfo。若设置了断点,程序进入G etCurrRunIn2 fo循环,直至获得F5或F10按键消息触发继续执行。
void GetCurrRunInfo()
{
long m_ebp;
__asm{
 mov eax,ebp
 mov m_ebp,eax
}
gCurr EBP =3((long3)m_ebp); //得到函数堆栈指针gCurrCodeP T=Get CallerAddr(m_ebp); 
//根据堆栈指针,在[ebp+4]处获得函数的返回地址
//检查是否为有效断点,若不是返回,继续执行用户代码
if(!IsBreakP TValide(gCurrCodeP T))
 return;
InDebug=true;
//进入代码调试循环
while(InDebug){
 msg=Get Message();//接受界面层用户输入消息
 如果是F5按键消息:InDebug=false,跳出调试循环,继续向下执行;
 如果是F9按键消息:设置新的断点,调试循环继续;
 如果是F10按键消息:将设置下一语句断点有效,跳出调试循环,继续执行;
 如果是鼠标消息并且移动到局部或全局变量上:显示变量实时信息。
 }
}
2.3 断点的管理和单步执行的实现
编译程序中建有“断点信息表”,表中为每一断点记录有C语言行号、汇编指令地址、有效标志三个量。如上所述,每行C语句后都设置了断点,在代码插入GetCurrRun2 Info过程中把对应的C语言行号(即目前正在编译的是第几行C语言代码)与生成的汇编指令指针地址记录下来,保存到断点信息表中。保存的断点表是所有潜在断点的集合,仅当用户在界面按下F9后才生效。IsBreakPTValide 就是根据断点信息表,通过当前汇编指令地址查断点有效标志来决定是否进入代码调试循环。
2.4 获取中断时刻运行信息
2.4.1 断点地址的获取
程序中call指令执行前,被调用函数的参数被顺序推入堆栈。然后,函数返回地址亦进入堆栈,常用函数中的第一条指令是push ebp;同时,ebp寄存器被赋予当前堆栈指针(即存放上次ebp的内存地址)。显然,获得调用函数的地址并不困难,3(ebp+4)指向调用函数代码内部。
G etCurrRunInfo函数中首先获得当前ebp,gCur2 rCodePT在[ebp+4]处获得函数GetCurrRunInfo的返回地址,在此向上六个字节就是断点地址(Pushfd和Call G etCurrRunInfo占6个字节)。可参见图1。
(下转第131页)
参考文献:
[1] Zuker M.Computer Prediction of RNA Struct ure [J ].Met h 2
ods Enzymology ,1989,180:2622288.
[2] 冀铁亮,穗志方.词汇化句法分析与子语类框架获取的互动
方法[J ].中文信息学报,2007,2(1):1202126.
[3] Magerman D M.Statistical Decision 2Tree Models for Parsing
[C]∥Proc of t he 33t h Annual Meeting of t he ACL ,1995:2762283.
[4] Collins M J.A New Statistical Parser Based on Bigram Lexi 2
cal Dependencies[C ]∥Proc of t he 34t h Annual Meeting of t he ACL ,1996:1842191.
[5] Uemura Y ,Hasegawa A ,K obayashi S.Tree Adjoining
Grammars for RNA Structure Prediction [J ].Theoretical Computer Science ,1999,210:2772303.
[6] Baldi P ,Bi ’unak S.Bioinformatics :The Machine Learning
Approach[M ].2nd ed.MIT Press ,2001.
[7] 董启文,王晓龙,林磊,等.蛋白质二级结构预测:基于词条
编写c语言的软件
的最大熵马尔科夫方法[J ].中国科学C
辑,2005,35(1):872
96.
[8] McCallum A ,Freitag D ,Pereira F.Maximum Entropy Markov
Models for Information Extraction and Segmentation[C]∥Proc of the 17th Int ’l C onf on Machine Learning ,2002:5912598.[9] Durbin R ,Eddy S R ,Krogh A ,et al.Biological Sequence A 2
nalysis :Probabilistic Models of Proteins and Nucleic Acids [M ].Cambridge University Press ,1998.
(上接第67页)
图1 堆栈示意图
  (1)(ebp )调用函数的帧指针信息;
(2)(ebp +4)函数返回地址(即程序在调用函数后应执行的代码地址);
(3)(ebp +4+4×i )函数参数(参数类型不同会有所区别,假定每个参数占用4字节);
(4)(ebp 2off set ):局部变量区域。
2.4.2 函数堆栈call stack 信息的获取
ebp 为当前函数帧指针,又3(ebp )存放的是调用函数(上一层函数)帧指针,由此循环可以到函数堆栈中所有
函数的帧指针链。
依据ebp 是可以得到函数名称的。在[ebp +4]处获得函数的返回地址,由地址顺序向下查,获得调用函数地址。编译器中函数头都由特定的代码构成,如VC 下为:
push   ebp mov   ebp ,esp
注:不同的编译器产生的代码有所不同。
根据编译器编译过程中生成的标识符表(
包含函数名
和函数地址信息),就可得到更加直观的函数堆栈call
stack 。
2.4.3 全局变量和局部变量的读取
在编译的过程中,编译器在编译局部代码时会建立局部变量标识符表。每个变量包含变量名称、变量类型、变量在当前函数帧(函数堆栈)中的偏移地址。
鼠标移动到局部变量上时,编译程序由当前ebp 即gCurr EBP 和变量在当前函数帧中的偏移地址采用适当类型指针,读取局部堆栈帧中的变量值。
全局变量查看也是利用全局变量标识符表读取对应内存地址来实现的。
2.4.4 程序实现效果
如图2所示,软件在C 语言代码执行中停留在断点前,
通过鼠标挪动可以获得当前函数参数、函数局部变量、全局变量信息,并在软件编译平台下方有当前函数执行堆栈跟踪信息。软件通过F9任意设置断点,通过F5继续执行。
图2 程序执行效果图
3 结束语
目前的设计覆盖了程序调试器的基本功能。由于设计
中采用软件方式模拟中断过程,在每行C 语言代码后插入了断点确认代码,所以相对于Windows 的API 调试函数,本系统增加了冗余代码,执行效率降低,这是不足之处。
参考文献:
[1] 尹作为.基于C 编译器的遥感图像分析软件初步设计:[硕士
学位论文][D ].武汉:武汉大学,2006.
[2] 弗雷泽.可变目标C 编译器———设计与实现[M ].王挺等译.
北京:电子工业出版社,2005.
[3] Aho A ,Set hi R ,Ullman D.编译原理[M ].李建中,姜守旭译.
北京:机械工业出版社,2003.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。