UEFI开发探索26 – UEFI下观察汇编代码

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

上一篇博客中遇到了奇怪的问题,明明没有使用的函数,在编译的时候竟然报错,提示使用了此函数,而且导致程序无法链接。

这让我意识到,还是得建立完整的调试环境,帮助学习开发。

一直以来,我调试代码的方法大概有三种:

1) 打印输出。这是最常用的,也最方便的。不管是通过屏幕打印输出,还是通过串口、USB口等硬件设备打印,都能知道很多程序的内部信息;
2) 观察汇编代码。 主要用来准确的知道程序内部运行状况、库的链接、各种变量等,配合map、lst等文件综合分析。用来解决一些疑难杂症;
3) 断点调试。  这是最好的调试方法,不过视开发的产品,有时候很难实现。如果是调试Windows driver&app,VS系列产品以及Windbg能发挥很大作用;Linux下用gdb,也很好;调试DOS和Legacy bios时,我用debug.exe较多。开发固件代码时就比较麻烦了,要么用软件模拟器调试,要么用JTAG调试。

帮朋友打个小广告,建议读一读张银奎的《软件调试》, 目前他在筹划第二版四卷本的计划。第二版的第一卷已经出版,值得好好读一读。承张老师惠赠一本,正在拜读:

图1 软件调试第二版 卷1

UEFI下开发APP和Driver,以及我目前的目标Option ROM,在实际硬件中是没法使用断点调试的,至少目前我没有找到方法。实在很怀念用debug.exe调试DOS程序的日子,工具虽然简单,该有的功能都有,UEFI下怎么就没提供这种类似的工具呢?

我准备用几篇博客,谈一谈如何建立第二种观察汇编代码和第三种断点调试的方法。当然,断点调试只能在TianoCore(模拟环境)配合下工作,实际硬件就无能为力了。这就足够了,能大大加快各种代码的开发。

这篇介绍如何去观察汇编代码。

1 回顾问题

上一篇One More Thing的环节,我遇到了很奇怪的问题,再把图贴一下:

图2 编译错误

在代码中加了红线所标的一行代码,就出现了右边的编译错误提示。这行代码怎么看也不像会调用memset。

我的第一个反应就是把这个C文件的汇编代码抓出来看看,到底是怎么编译的。不过当时还没有找到方法反汇编,生成的中间文件中有obj文件,使用dumpbin /DISASM尝试一下,提示如下:

图3 dumpbin尝试反汇编obj文件

看来还是必须从编译选项下手,幸好编译器是VS2015,微软的资料比较全,可以从这儿着手。

2 如何打开汇编清单开关

我是使用VS2015+UDK2018进行开发的,编译用的工具链是VS2015x86。平常编译的时候,是直接使用build进行编译。

这是UDK2018中准备好的批处理工具,所有配置的文件可以再Conf文件夹下找到,它定义了很多宏,以支持在Windows和Linux下编译。

我把build的参数打印出来,仔细研究了一下。

比较有用的是-a、-p、-m、-t几个常用的参数,分别为指定目标架构、编译的Package、编译的模块以及工具链指定。其他参数中也没有与汇编清单相关的。

看来还得从编译工具本身入手。VS使用cl.exe、link、和nmake.exe进行编译,汇编清单的参数指定应该与cl.exe有关。 很快,就在微软提供的文档中找到了答案(cl.exe的帮助文档)。

图4 汇编清单开关

接下来的工作比较简单。

在UDK的Conf文件夹中,找到tools_def.txt文件,所有的工具链参数都在此配置。我的开发环境中,target.txt中设置为:TARGET = DEBUG,TOOL_CHAIN_TAG = VS2015x86。根据配置,可以在tool_def.txt中找到DEBUG_VS2015x86_IA32_CC_FLAGS,在最后添加/FAs参数。

运行build -m _LuoApp/Luo2/Luo2.inf,编译。汇编文件就生成了,根据个人配置不同,生成文件位置可能会有些差异,我的环境中生成的汇编代码都在C:\MyWorkspace\Build\AppPkg\DEBUG_VS2015x86\IA32\_LuoApp\Luo2\Luo2下。

3 寻找问题原因

打开问题文件Pictures.asm,找到使用了memset的地方:

图5 罪魁祸首找到了

让人困惑的是,并不是图2中语句bPixel=*tempFileImage++;调用了memset(),而是图4中标红的这句话调用了。

我在调试的时候,将bPixel设为了0xC2。代码很好理解,实现将值0xC2批量设置到目的地址中,所以memset是很好的选择。不过这造成了我的麻烦,我并没有引入Stdlib库,编译出错是必然。

Foxdisk开发中也遇到了类似的问题,比如32位的乘法(Foxdisk是在16位编译器上开发的),编译器会调用库函数来实现。Foxdisk是运行在无操作系统的环境下,运行的时候我到哪里去找这些库函数?我只能用汇编语言重写了32位的乘法以及其他四则运算。

问题找到,解决起来比较简单。把图4中红线部分的语句稍微修改一下,调用目前已包含库中的函数即可:

// while (–count >=0) *tempPcxImage++ = bPixel;
SetMem(tempPcxImage, count, bPixel);
tempPcxImage += count;

具体参照下百度云中提供的代码。

我一直有个习惯,喜欢通过汇编来观察程序的各个组成部分。这种方法能很容易了解各个库之间的关系、编译器对程序优化成什么样子了,对理解程序的细微之处很有帮助。今天终于可以在UEFI下使用这种方法了,对加快后面的开发非常有好处。

4 One More Thing

把工具链中用到的参数记录一下吧,方便后面使用(只针对Windows平台的工具链)。UEFI中的工具链截图:

图6 UEFI编译工具链

/nologo: 取消显示启动版权标志
/arch:IA32: 指定体系架构为IA32
/c: 编译但不链接
/WX: 将所有编译警告都视为错误
/GS-: 缓冲区安全检查,打开磁开关,保证应用程序不出现安全漏洞
/W4: 显示所有等级的警
/Gs32768: 控制堆栈检查调用,启动堆栈探测前局部变量可以占用32768个字节
/D UNICODE: 预处理符合定义,支持UNICODE字符
/O1b2: 生成最小文件而优化代码,允许对标记为inline、__inline、_forceiline的内联展开
/GL: 启用全程序优化
/FIAutoGen.h: 预处理AutoGen.h文件
/EHs-c-: 仅捕获同步C++结构化异常,并假定声明为extern “C”的函数从未引发异常
/GR-: 启用运行时类型信息
/GF: 消除重复的字符串
/Gy: 启用函数级链接,允许编译器以打包函数的形式对各个函数进行打包
/Zi: 生成完整的调试信息,包含在程序数据库PDB中,其中有供调试器使用的类型信息和符号化调试信息
/Gm: 启用最小重新生成,确定是否重新编译包含已更改的源文件
/Gw: 启用全程序全局数据优化
/FAs: 生成带源文件的汇编清单文件

以上主要是cl.exe的参数,其他编译工具的参数就不列出来了。更多的参数可以参考这个网页:https://docs.microsoft.com/zh-cn/previous-versions/fwkeyyhe%28v%3dvs.120%29

3,081 total views, 3 views today

发表评论

电子邮件地址不会被公开。