我正在调试仅在程序的PPC64端口中出现的问题。
我有一个测试用例,其中C库qsort
被赋予libffi生成的闭包作为字符串比较回调。字符串被正确地传递给回调,并且返回值被精确地存储到libffi传递给闭包函数的返回值缓冲区中。
但是,数组未按qsort
正确排序。此外,Valgrind报告C库qsort代码正在访问未初始化的内存,--track-orgins=yes
显示此内存是由Libffi堆栈分配的。我强烈怀疑这是返回值,因此垃圾比较导致排序不正确。
即。 Libffi为返回值分配了缓冲区,并将其值传播给回调调用方;但是我的闭包调度函数被赋予错误的指针,因此不会将返回值放在正确的位置。
出于某些奇怪的原因,Valgrind没有报告未初始化内存的地址,只报告代码中使用的位置和分配位置。
我只是想将该位置的地址与传递给闭包函数的指针进行比较:它们是否远程关闭?
有没有办法从Valgrind那里获取这些信息?
更新:我正在使用GCC Compile Farm机器,我没有root;安装的libffi没有调试信息。它是版本3.0.13。
然而,问题与我刚刚建立的libffi
git head一起重现。
我已经确认它是未初始化的返回值区域。
我向闭包调度汇编代码ffi_closure_LINUX64
添加了一条指令,用于初始化闭包调度堆栈帧RETVAL
部分底部的双字大小区域。这使得Valgrind错误消失了;但当然返回值是垃圾。它还确认了一个基本的健全性:调用闭包调度助手之前的代码和之后的代码指的是返回值的相同区域。 (堆栈指针没有意外移动,框架引用正确。)只要用户代码最终获得的任何地址都不指向该返回值。
接下来,我将返回区域的初始化向下移动到名为ffi_closure_helper_LINUX64
的C函数中,靠近函数的入口。这仍然会使未初始化的错误消失,确认帮助者通过%r6
(参数4)获得正确的返回值区域地址。
答案 0 :(得分:0)
valgrind中没有任何功能可以报告uninit内存的地址,因为这会(在大多数情况下)对用户没有帮助:堆栈地址或堆地址实际上并不能说明多少。
您可能通过在Valgrind报告的帧中设置断点来获得更多信息,并使用gdb + vgdb + memcheck monitor命令将堆栈的各个部分标记为已初始化。 将故障位置设置为初始化时,valgrind不应再报告错误。您可能需要进行多次运行,每次都标记堆栈的其他变量/区域。
请参阅http://www.valgrind.org/docs/manual/mc-manual.html#mc-manual.monitor-commands和GDB用户手册,了解在达到断点时如何编写(复杂)命令。
答案 1 :(得分:0)
由于某些奇怪的原因,Valgrind没有报告地址 未初始化的内存,仅在代码中使用的位置和 分配的地方。
这是Valgrind Memcheck工具的记录行为,请参阅manual关于--track-orgins=yes
的这一部分:
对于源自堆栈分配的未初始化值, Memcheck可以告诉你哪个函数分配了值,但没有更多 比那 - 通常它会显示开口的源位置 大括号的功能。所以你应该仔细检查所有的 函数的局部变量已正确初始化。
答案 2 :(得分:0)
好的,我调试了这个问题。
问题是LibFFI中的PPC64代码包含大端的案例,这些案例与我的期望不符。
我应用了这个测试补丁:
--- a/src/powerpc/linux64_closure.S
+++ b/src/powerpc/linux64_closure.S
@@ -27,7 +27,8 @@
#define LIBFFI_ASM
#include <fficonfig.h>
#include <ffi.h>
-
+#undef __LITTLE_ENDIAN__
+#define __LITTLE_ENDIAN__ 1
.file "linux64_closure.S"
#ifdef POWERPC64
我的所有测试都通过了。 __LITTLE_ENDIAN__
控件是有条件包含的代码块,如下所示:
# case FFI_TYPE_INT
# ifdef __LITTLE_ENDIAN__
lwa %r3, RETVAL+0(%r1)
# else
lwa %r3, RETVAL+4(%r1)
# endif
mtlr %r0
addi %r1, %r1, STACKFRAME
.cfi_def_cfa_offset 0
blr
.cfi_def_cfa_offset STACKFRAME
big endian上的客户端代码应该取代存储的返回值,以便它与8字节字的顶部对齐。
因此,为了存储int
(四个字节),代码应该*(int *)(retptr+4) = val
,而不仅仅是*(int *)retptr = val
,正如我的代码所做的那样。
似乎期望应用程序应该将8字节字存储到返回值中,而不管FFI类型如何:无论是char,short,int还是(64位)长。也就是说:
(int64_t)retptr = val; / val是char,short,无论* /
这样,值的最低有效字节位于retptr + 7
,因此如果实际类型为char
,则使用该地址;如果是retptr + 6
,则使用short
,依此类推。 FFI代码以这种方式有意义。问题是它不方便和不一致; FFI论证不需要这样对待。
例如,以下调用中的int
参数不会被4个字节取代;它只是写入给libffi
This is the TXR Lisp interactive listener of TXR 176.
Use the :quit command or type Ctrl-D on empty line to exit.
1> (with-dyn-lib nil (deffi printf "printf" int (str : int)))
#:lib-0185
2> (printf "foo %d\n" 1)
foo 1
0
但是,哦,看;返回值是假的!外部函数调用返回值也有类似的问题。
看起来我被一些libffi文档中的一个例子所欺骗,即这个:
#include <stdio.h>
#include <ffi.h>
int main()
{
ffi_cif cif;
ffi_type *args[1];
void *values[1];
char *s;
int rc;
/* ... abbreviated ... */
s = "This is cool!";
ffi_call(&cif, puts, &rc, values);
/* rc now holds the result of the call to puts */
/* ... */
}
事实证明,这是不正确的;其他一些libffi文档说必须使用类型ffi_arg
捕获返回值(这容易引起混淆,不用于参数)。因此,我认为上面的示例应该是这样的:
ffi_arg rc_buf;
int rc;
/*...*/
s = "Turned out uncool, but we promise this is really cool now!";
ffi_call(&cif, puts, &rc_buf, values);
rc = (int) rc_buf;