Oracle Solaris Studio 12.2 Discover 和 Uncover 用户指南

解释 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 提供了额外的调试信息,可帮助 Discover 识别位字段装入和初始化,从而可以排除大多数误报。如果出于某种原因无法使用 -g 选项编译,请使用 memset() 等函数初始化结构。例如:


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

可疑装入

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


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 并不知道该库已初始化内存,后续读取操作将生成未初始化内存错误 (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 函数,将具体事件告知 Discover,例如,向内存区写入 (__ped_memory_write()) 或从内存区读取 (__ped_memory read())。对于这两种情况,内存区的起始地址将通过 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

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