在某些情况下,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 函数。