Oracle® Developer Studio 12.5:Discover 和 Uncover 用户指南

退出打印视图

更新时间: 2016 年 6 月
 
 

解释 discover 错误消息

在某些情况下,discover 可能报告实际上不是错误的错误。此类情况称为误报。 discover 实用程序在检测时分析代码,与类似工具相比可以减少误报的发生,但在某些情况下误报仍可能发生。本节提供一些提示,可能有助于您识别并可能避免 discover 报告中的误报。

部分初始化内存

可以在 C 和 C++ 中使用位字段创建紧凑数据类型。例如:

struct my_struct {
unsigned int valid : 1;
char         c;
};

在本例中,结构成员 my_struct.valid 在内存中仅占用一位。但是,在 SPARC 平台上,CPU 只能以字节为单位修改内存,因此,只有装入含有 struct.valid 的整个字节才能访问或修改该结构成员。此外,编译器有时可能会一次装入多个字节(例如,由四个字节构成的计算机字)。当 discover 检测到此类装入且没有其他信息时,假设会使用全部四个字节。例如,如果字段 my_struct.valid 已初始化,但字段 my_struct.c 未初始化,并且已装入包含这两个字段的计算机字,则 discover 会标记部分初始化内存读取 (PIR)。

误报的另一个原因是位字段初始化。要写入某个字节的一部分,编译器必须首先生成用于装入该字节的代码。如果该字节不是在读取之前写入的,将生成未初始化内存读取 (UMR) 错误。

要避免位字段误报,请在编译时使用 –g 选项或 –g0 选项。这些选项向 discover 提供额外的调试信息,以帮助其识别位字段装入和初始化,这将消除大多数误报。如果出于某种原因无法使用 –g 选项进行编译,请使用 memset() 等函数初始化结构。例如:

...
struct my_struct s;
/* Initialize structure prior to use */
memset(&sm 0, sizeof(struct my_struct));
...

可疑装入

有时,当装入的结果对某些程序路径无效时,编译器会从已知的内存地址生成装入。这种情况经常发生在 SPARC 平台上,因为此类装入指令可以放置在分支指令的延迟槽中。例如,请看以下 C 语言代码片段:

int i'
if (foo(&i) != 0) { /* foo returns nonzero if it has initialized i */
printf("5d\n", i);
}

根据此代码,编译器可能会生成与以下示例等效的代码:

int i;
int t1, t2'
t1 = foo(&i);
t2 = i; /* value in i is loaded */
if (t1 != 0) {
printf("%d\n", t2);
}

假定本例中的函数 foo() 返回了 0 且未初始化 i。仍生成来自 i 的装入,即使并未使用它时也是如此。不过,由于 discover 会看到该装入,因此将报告未初始化的变量装入 (UMR)。

discover 实用程序会尽量使用数据流分析来识别此类情况,但有时无法检测到此类情况。

使用较低的优化级别进行编译可以减少这些类型的误报的发生。

未检测的代码

discover 有时无法检测整个程序,尤其是当部分代码来自汇编语言源文件或无法重新编译的第三方库,从而无法进行检测时。在某些情况下,discover 无法检测未检测的代码正在访问和修改的内存块。例如,假定某个第三方共享库中的某个函数初始化了一个内存块,主程序(检测过的程序)稍后读取了该内存块。如果 discover 无法检测到该库已初始化内存,后续读取操作将生成未初始化内存错误 (uninitialized memory error, UMR)。

为解决此类问题,discover API 中包含了下列函数:

void __ped_memory_write(unsigned long addr, long size, unsigned long pc);
void __ped_memory_read(unsigned long addr, long size, unsigned long pc);
void __ped_memory_copy(unsigned long src, unsigned lond dst, long size, unsigned long pc);

您可以从程序调用这些 API 函数,以便将特定事件(如向内存区写入 (__ped_memory_write()) 或从内存区读取 (__ped_memory read()))告知 discover。对于这两种情况,内存区的起始地址将通过 addr 参数传递,内存区的大小通过 size 参数传递。将 pc 参数设置为 0

使用 __ped_memory_copy 函数向 discover 通知正从一个位置复制到另一个位置的内存。源内存的起始地址将通过 src 参数传递,目标区的起始地址通过 dst 参数传递,大小通过 size 参数传递。将 pc 参数设置为 0

要使用 API,请在程序中将这些函数声明为弱函数。例如,在源代码中包含以下代码片段。

#ifdef __cplusplus
extern "C" {
#endif

extern void __ped_memory_write(unsigned long addr, long size, unsigned long pc);
extern void __ped_memory_read(unsigned long addr, long size, unsigned long pc);
extern void __ped_memory_copy(unsigned long src, unsigned long dst, long size, unsigned long pc);

#prgama weak __ped_memory_write
#pragma weak __ped_memory_read
#pragma weak __ped_memory_copy

#ifdef __cplusplus
}
#endif

内部 discover 库(该库在检测时与您的程序链接)定义 API 函数。但是,如果未检测程序,则不会链接该库,这样,对 API 函数的所有调用都会导致应用程序挂起。因此,如果不是在 discover 下运行程序,则必须禁用这些函数。另外,您也可以使用 API 函数的空定义创建一个动态库,并将其链接到程序。在此情况下,如果您未在 discover 下运行程序,将使用您的库,但如果在 discover 下运行程序,将自动调用实际的 API 函数。