调用cgo时出现分段错误错误,当调用fts_open函数时。

huangapple go评论119阅读模式
英文:

Segmentation violation error when calling fts_open via cgo

问题

我是你的中文翻译助手,以下是翻译好的内容:

我正在测试cgo,每个简单的“Hello World”代码都能正常工作。
但是我在下面的C代码中遇到了问题。
这段C代码用于遍历目录树并计算文件大小。
如果我使用go命令构建,构建过程没有错误。
但是在运行时,出现了“segmentation violation”错误。

  1. bash$ ./walkdir
  2. fatal error: unexpected signal during runtime execution
  3. [signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x7f631e077c1a]
  4. . . . .
  1. package main
  2. /*
  3. #include <stdint.h>
  4. #include <fts.h>
  5. #include <sys/stat.h>
  6. uintmax_t get_total_size(char *path)
  7. {
  8. uintmax_t total_size = 0;
  9. FTS *fts = fts_open(&path, FTS_PHYSICAL, NULL);
  10. FTSENT *fent;
  11. while ((fent = fts_read(fts)) != NULL)
  12. if (fent->fts_info == FTS_F)
  13. total_size += fent->fts_statp->st_size;
  14. fts_close(fts);
  15. return total_size;
  16. }
  17. */
  18. import "C"
  19. import "fmt"
  20. func main() {
  21. fmt.Println(C.get_total_size(C.CString("/usr")))
  22. }

希望对你有帮助!

英文:

I'm testing cgo and every simple hello world like code works well.
but i have a problem with C code below.
The C code is that traverse a directory tree and sums file size.
if i build with go command, then the build is OK with no error.
but when running, there is a "segmentation violation" error occurred

  1. bash$./walkdir
  2. fatal error: unexpected signal during runtime execution
  3. [signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x7f631e077c1a]
  4. . . . .
  5. -------------------------------------------------------------
  6. package main
  7. /*
  8. #include &lt;stdint.h&gt;
  9. #include &lt;fts.h&gt;
  10. #include &lt;sys/stat.h&gt;
  11. uintmax_t get_total_size(char *path)
  12. {
  13. uintmax_t total_size = 0;
  14. FTS *fts = fts_open(&amp;path, FTS_PHYSICAL, NULL);
  15. FTSENT *fent;
  16. while ((fent = fts_read(fts)) != NULL)
  17. if (fent-&gt;fts_info == FTS_F)
  18. total_size += fent-&gt;fts_statp-&gt;st_size;
  19. fts_close(fts);
  20. return total_size;
  21. }
  22. */
  23. import &quot;C&quot;
  24. import &quot;fmt&quot;
  25. func main() {
  26. fmt.Println(C.get_total_size(C.CString(&quot;/usr&quot;)))
  27. }

答案1

得分: 4

fts_open的定义如下:

fts_open()函数接受一个指向字符指针数组的指针,该数组命名了一个或多个构成要遍历的逻辑文件层次结构的路径。数组必须以null指针结尾。

C语言没有直接支持数组的功能,它只有指针。在你的情况下,你向fts_open传递了一个有效的指针,但它并不位于一个具有NULL指针作为紧随其后元素的数组中,因此fts_open会继续扫描&path之后的内存,寻找NULL指针,并最终尝试读取一些被禁止访问的内存地址(通常是因为该地址的页面未分配)。

修复的方法是在C端创建该数组并进行初始化。看起来你正在使用一个相当新的C标准,所以让我们直接使用字面量来初始化数组:

  1. package main
  2. /*
  3. #include <stddef.h> // for NULL
  4. #include <stdint.h>
  5. #include <stdlib.h> // for C.free
  6. #include <fts.h>
  7. #include <sys/stat.h>
  8. uintmax_t get_total_size(char *path)
  9. {
  10. uintmax_t total_size = 0;
  11. char * path_argv[2] = {path, NULL};
  12. FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
  13. FTSENT *fent;
  14. while ((fent = fts_read(fts)) != NULL)
  15. if (fent->fts_info == FTS_F)
  16. total_size += fent->fts_statp->st_size;
  17. fts_close(fts);
  18. return total_size;
  19. }
  20. */
  21. import "C"
  22. import (
  23. "fmt"
  24. "unsafe"
  25. )
  26. func main() {
  27. cpath := C.CString("/usr")
  28. defer C.free(unsafe.Pointer(cpath))
  29. fmt.Println(C.get_total_size(cpath))
  30. }

请注意,你的程序有一个错误和一个可能的问题:

  • 错误是调用C.CString会通过调用链接的C库中的malloc(3)来分配一块内存块,但你没有释放该内存块。
  • 符号NULL在"stddef.h"中定义;在编译时可能会出现错误,也可能不会出现。

我在我的示例中修复了这两个问题。

对我们示例的进一步改进可能是利用fts_*函数在单次运行中扫描多个路径的能力;如果我们要实现这个功能,那么在Go端为fts_open的第一个参数分配数组可能更有意义:

  1. package main
  2. /*
  3. #include <stddef.h>
  4. #include <stdint.h>
  5. #include <stdlib.h>
  6. #include <fts.h>
  7. #include <sys/stat.h>
  8. uintmax_t get_total_size(char * const *path_argv)
  9. {
  10. uintmax_t total_size = 0;
  11. FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
  12. FTSENT *fent;
  13. while ((fent = fts_read(fts)) != NULL)
  14. if (fent->fts_info == FTS_F)
  15. total_size += fent->fts_statp->st_size;
  16. fts_close(fts);
  17. return total_size;
  18. }
  19. */
  20. import "C"
  21. import (
  22. "fmt"
  23. "unsafe"
  24. )
  25. func main() {
  26. fmt.Println(getTotalSize("/usr", "/etc"))
  27. }
  28. func getTotalSize(paths ...string) uint64 {
  29. argv := make([]*C.char, len(paths)+1)
  30. for i, path := range paths {
  31. argv[i] = C.CString(path)
  32. defer C.free(unsafe.Pointer(argv[i]))
  33. }
  34. return uint64(C.get_total_size(&argv[0]))
  35. }

请注意,这里我们没有显式地将argv的最后一个参数清零,因为与C不同,Go会将每个分配的内存块初始化为零,所以一旦argv被分配,它的所有内存都已经被清零。

英文:

fts_open is defined like this:

> fts_open()
> The fts_open() function takes a pointer to an array of character
> pointers naming one or more paths which make up a logical file
> hierarchy to be traversed. The array must be terminated by a
> null pointer.

C does not have direct support for arrays; it only has pointers.
In your case you pass fts_open a single valid pointer but it is not located in an array which has a NULL pointer as the immediately following element, so fts_open continues to scan the memory past &amp;path — looking for a NULL pointer, — and eventually tries to read memory at some address it is forbidden to do so (usually because the page at that address was not allocated).

A way to fix it is to create that array and initialize it on the C side.
Looks like you're using a reasonably up-to-date standard of C, so let's just use direct literal to initialize the array:

  1. package main
  2. /*
  3. #include &lt;stddef.h&gt; // for NULL
  4. #include &lt;stdint.h&gt;
  5. #include &lt;stdlib.h&gt; // for C.free
  6. #include &lt;fts.h&gt;
  7. #include &lt;sys/stat.h&gt;
  8. uintmax_t get_total_size(char *path)
  9. {
  10. uintmax_t total_size = 0;
  11. char * path_argv[2] = {path, NULL};
  12. FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
  13. FTSENT *fent;
  14. while ((fent = fts_read(fts)) != NULL)
  15. if (fent-&gt;fts_info == FTS_F)
  16. total_size += fent-&gt;fts_statp-&gt;st_size;
  17. fts_close(fts);
  18. return total_size;
  19. }
  20. */
  21. import &quot;C&quot;
  22. import (
  23. &quot;fmt&quot;
  24. &quot;unsafe&quot;
  25. )
  26. func main() {
  27. cpath := C.CString(&quot;/usr&quot;)
  28. defer C.free(unsafe.Pointer(cpath))
  29. fmt.Println(C.get_total_size(cpath))
  30. }

Note that your program has one bug and one possible problem:

  • A bug is that the call C.CString allocates a chunk of memory by performing a call to malloc(3) from the linked C library, and you did not free that memory block.
  • The symbol NULL is defined in "stddef.h"; you might or might not get an error when compiling.

I've fixed both problems in my example.

A further improvement over our example might be leveraging the ability of fts_* functions to scan multiple paths in a single run; if we were to implement that, it would have more sense to allocate the array for the 1st argument of fts_open on the Go's side:

  1. package main
  2. /*
  3. #include &lt;stddef.h&gt;
  4. #include &lt;stdint.h&gt;
  5. #include &lt;stdlib.h&gt;
  6. #include &lt;fts.h&gt;
  7. #include &lt;sys/stat.h&gt;
  8. uintmax_t get_total_size(char * const *path_argv)
  9. {
  10. uintmax_t total_size = 0;
  11. FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
  12. FTSENT *fent;
  13. while ((fent = fts_read(fts)) != NULL)
  14. if (fent-&gt;fts_info == FTS_F)
  15. total_size += fent-&gt;fts_statp-&gt;st_size;
  16. fts_close(fts);
  17. return total_size;
  18. }
  19. */
  20. import &quot;C&quot;
  21. import (
  22. &quot;fmt&quot;
  23. &quot;unsafe&quot;
  24. )
  25. func main() {
  26. fmt.Println(getTotalSize(&quot;/usr&quot;, &quot;/etc&quot;))
  27. }
  28. func getTotalSize(paths ...string) uint64 {
  29. argv := make([]*C.char, len(paths)+1)
  30. for i, path := range paths {
  31. argv[i] = C.CString(path)
  32. defer C.free(unsafe.Pointer(argv[i]))
  33. }
  34. return uint64(C.get_total_size(&amp;argv[0]))
  35. }

Note that here we did not explicitly zero out the last argument of argv because — contrary to C, — Go initializes each allocated memory block with zeroes, so once argv is allocated, all its memory is already zeroed.

答案2

得分: 1

你遇到的错误是因为"fts_open"需要一个以NULL结尾的字符指针数组作为参数,例如char *argv[] = { path, NULL };。在使用GCC编译时,相同的代码可以正常工作,但是在使用cgo编译时,"fts_open"返回NULL。我猜测这可能是GCC和cgo之间的优化差异导致的(不太确定)。

为了修复代码,你需要添加一个数组指针。

以下是修复后的代码示例:

  1. package main
  2. /*
  3. #include <stdint.h>
  4. #include <fts.h>
  5. #include <sys/stat.h>
  6. uintmax_t get_total_size(char *path)
  7. {
  8. uintmax_t total_size = 0;
  9. char *argv[] = { path, NULL };
  10. FTS *fts = fts_open(argv, FTS_PHYSICAL, NULL);
  11. if (fts == NULL)
  12. return 0;
  13. FTSENT *fent;
  14. while ((fent = fts_read(fts)) != NULL)
  15. if (fent->fts_info == FTS_F)
  16. total_size += fent->fts_statp->st_size;
  17. fts_close(fts);
  18. return total_size;
  19. }
  20. */
  21. import "C"
  22. import "fmt"
  23. func main() {
  24. fmt.Println(C.get_total_size(C.CString("/usr")))
  25. }

修复后的代码应该可以正常工作了。

英文:

you are getting the error cause "fts_open" requires a character pointer to an array which is NULL terminating like char *argv[] = { path, NULL };..(https://linux.die.net/man/3/fts_open)

  1. package main
  2. /*
  3. #include &lt;stdint.h&gt;
  4. #include &lt;fts.h&gt;
  5. #include &lt;sys/stat.h&gt;
  6. uintmax_t get_total_size(char *path)
  7. {
  8. uintmax_t total_size = 0;
  9. char *argv[] = { path, NULL };
  10. FTS *fts = fts_open(argv, FTS_PHYSICAL, NULL);
  11. if (fts == NULL)
  12. return 0;
  13. FTSENT *fent;
  14. while ((fent = fts_read(fts)) != NULL)
  15. if (fent-&gt;fts_info == FTS_F)
  16. total_size += fent-&gt;fts_statp-&gt;st_size;
  17. fts_close(fts);
  18. return total_size;
  19. }
  20. */
  21. import &quot;C&quot;
  22. import &quot;fmt&quot;
  23. func main() {
  24. fmt.Println(C.get_total_size(C.CString(&quot;/usr&quot;)))
  25. }

so adding the array pointer will fix the code.

The same code works when compiled with GCC but fts_open returns NULL.I am guessing that there is some difference in optimization between gcc and cgo(not very sure)

I tried some test results and was able to find that when compiling with GCC the char **pointer is getting NULL-terminated but in the case of cgo it was not getting NULL-terminated so you were getting "SIGSEGV" as your code is reading invalid memory reference

  1. #include &lt;stdio.h&gt;
  2. #include &lt;string.h&gt;
  3. void try(char **p)
  4. {
  5. while (*p != NULL)
  6. {
  7. printf(&quot;%zu\n&quot;, strlen(*p));
  8. ++p;
  9. }
  10. }
  11. void get_total_size(char *path)
  12. {
  13. try(&amp;path);
  14. }
  15. int main()
  16. {
  17. get_total_size(&quot;/usr&quot;);
  18. }

c code (which works)

  1. package main
  2. /*
  3. #include &lt;stdio.h&gt;
  4. #include &lt;string.h&gt;
  5. void try(char **p)
  6. {
  7. while (*p != NULL)
  8. {
  9. printf(&quot;%zu\n&quot;, strlen(*p));
  10. ++p;
  11. }
  12. }
  13. void get_total_size(char *path)
  14. {
  15. try(&amp;path);
  16. }
  17. */
  18. import &quot;C&quot;
  19. func main() {
  20. C.get_total_size(C.CString(&quot;/usr&quot;))
  21. }

same go code you will face error

huangapple
  • 本文由 发表于 2021年8月15日 22:47:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/68792621.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定