英文:
Segmentation violation error when calling fts_open via cgo
问题
我是你的中文翻译助手,以下是翻译好的内容:
我正在测试cgo,每个简单的“Hello World”代码都能正常工作。
但是我在下面的C代码中遇到了问题。
这段C代码用于遍历目录树并计算文件大小。
如果我使用go命令构建,构建过程没有错误。
但是在运行时,出现了“segmentation violation”错误。
bash$ ./walkdir
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x7f631e077c1a]
. . . .
package main
/*
#include <stdint.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(&path, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.get_total_size(C.CString("/usr")))
}
希望对你有帮助!
英文:
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
bash$./walkdir
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x7f631e077c1a]
. . . .
-------------------------------------------------------------
package main
/*
#include <stdint.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(&path, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.get_total_size(C.CString("/usr")))
}
答案1
得分: 4
fts_open
的定义如下:
fts_open()
函数接受一个指向字符指针数组的指针,该数组命名了一个或多个构成要遍历的逻辑文件层次结构的路径。数组必须以null
指针结尾。
C语言没有直接支持数组的功能,它只有指针。在你的情况下,你向fts_open
传递了一个有效的指针,但它并不位于一个具有NULL
指针作为紧随其后元素的数组中,因此fts_open
会继续扫描&path
之后的内存,寻找NULL
指针,并最终尝试读取一些被禁止访问的内存地址(通常是因为该地址的页面未分配)。
修复的方法是在C端创建该数组并进行初始化。看起来你正在使用一个相当新的C标准,所以让我们直接使用字面量来初始化数组:
package main
/*
#include <stddef.h> // for NULL
#include <stdint.h>
#include <stdlib.h> // for C.free
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
char * path_argv[2] = {path, NULL};
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cpath := C.CString("/usr")
defer C.free(unsafe.Pointer(cpath))
fmt.Println(C.get_total_size(cpath))
}
请注意,你的程序有一个错误和一个可能的问题:
- 错误是调用
C.CString
会通过调用链接的C库中的malloc(3)
来分配一块内存块,但你没有释放该内存块。 - 符号
NULL
在"stddef.h"中定义;在编译时可能会出现错误,也可能不会出现。
我在我的示例中修复了这两个问题。
对我们示例的进一步改进可能是利用fts_*
函数在单次运行中扫描多个路径的能力;如果我们要实现这个功能,那么在Go端为fts_open
的第一个参数分配数组可能更有意义:
package main
/*
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char * const *path_argv)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
fmt.Println(getTotalSize("/usr", "/etc"))
}
func getTotalSize(paths ...string) uint64 {
argv := make([]*C.char, len(paths)+1)
for i, path := range paths {
argv[i] = C.CString(path)
defer C.free(unsafe.Pointer(argv[i]))
}
return uint64(C.get_total_size(&argv[0]))
}
请注意,这里我们没有显式地将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 &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:
package main
/*
#include <stddef.h> // for NULL
#include <stdint.h>
#include <stdlib.h> // for C.free
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
char * path_argv[2] = {path, NULL};
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cpath := C.CString("/usr")
defer C.free(unsafe.Pointer(cpath))
fmt.Println(C.get_total_size(cpath))
}
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 tomalloc(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:
package main
/*
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char * const *path_argv)
{
uintmax_t total_size = 0;
FTS *fts = fts_open(path_argv, FTS_PHYSICAL, NULL);
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
fmt.Println(getTotalSize("/usr", "/etc"))
}
func getTotalSize(paths ...string) uint64 {
argv := make([]*C.char, len(paths)+1)
for i, path := range paths {
argv[i] = C.CString(path)
defer C.free(unsafe.Pointer(argv[i]))
}
return uint64(C.get_total_size(&argv[0]))
}
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之间的优化差异导致的(不太确定)。
为了修复代码,你需要添加一个数组指针。
以下是修复后的代码示例:
package main
/*
#include <stdint.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
char *argv[] = { path, NULL };
FTS *fts = fts_open(argv, FTS_PHYSICAL, NULL);
if (fts == NULL)
return 0;
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.get_total_size(C.CString("/usr")))
}
修复后的代码应该可以正常工作了。
英文:
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)
package main
/*
#include <stdint.h>
#include <fts.h>
#include <sys/stat.h>
uintmax_t get_total_size(char *path)
{
uintmax_t total_size = 0;
char *argv[] = { path, NULL };
FTS *fts = fts_open(argv, FTS_PHYSICAL, NULL);
if (fts == NULL)
return 0;
FTSENT *fent;
while ((fent = fts_read(fts)) != NULL)
if (fent->fts_info == FTS_F)
total_size += fent->fts_statp->st_size;
fts_close(fts);
return total_size;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.get_total_size(C.CString("/usr")))
}
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
#include <stdio.h>
#include <string.h>
void try(char **p)
{
while (*p != NULL)
{
printf("%zu\n", strlen(*p));
++p;
}
}
void get_total_size(char *path)
{
try(&path);
}
int main()
{
get_total_size("/usr");
}
c code (which works)
package main
/*
#include <stdio.h>
#include <string.h>
void try(char **p)
{
while (*p != NULL)
{
printf("%zu\n", strlen(*p));
++p;
}
}
void get_total_size(char *path)
{
try(&path);
}
*/
import "C"
func main() {
C.get_total_size(C.CString("/usr"))
}
same go code you will face error
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论