Can't open file when calling golang function via Node - node.js

I followed the tutorial in https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf to make my node app able to call golang function. The provided example works like a charm.
I do, however, unable it to implement in another scenario. Here I want to open a file by providing only it's absolute path and call the Go's function, but it always told me that it can't find the file due to no such file. I'm trying to run it directly in Go and it works!
Am I doing it wrong or is it an actual bug/unfinished feature?
Here is the golang source that I've built to c-style lib :
package main
import "C"
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {}
//export ReadSomething
func ReadSomething(filePath string) {
file, err := os.Open(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
I built it with this command :
go build -buildmode=c-shared -o simpleread.so main.go
In case you're wondering what's the header output :
/* Created by "go tool cgo" - DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
typedef _GoString_ GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern void ReadSomething(GoString p0);
#ifdef __cplusplus
}
#endif
And then, below is how I call it via Node. I give the comment on the line where the error generated :
var ref = require("ref")
var ffi = require("ffi-napi")
var Struct = require("ref-struct")
var ArrayType = require("ref-array")
var LongArray = ArrayType(ref.types.longlong);
var GoString = Struct({
p: "string",
n: "longlong"
});
var simpleRead = ffi.Library("./simpleread.so", {
ReadSomething: ["void", [GoString]]
});
// error here, can't open the specified file
simpleRead.ReadSomething("/home/ivan/Documents/crashsite/node-go-crossfire/simpletext.txt")
I'm running it on Ubuntu 18.04 64bit.

Remember that strings in Go are like slices. They are composed of a pointer to the backing data and the length. This is why in your code, GoString is defined as:
var GoString = Struct({
p: "string", // pointer
n: "longlong" // length
});
I'd recommend you define a function for creating a GoString e.g.
function NewGoString(str) {
return new GoString({p: str, n: str.length})
}
Which you can use in your code like:
var simpleRead = ffi.Library("./simpleread.so", {
ReadSomething: ["void", [GoString]]
});
simpleRead.ReadSomething(NewGoString("/path/to/your/file"))

Related

nvcc under linux complains: Contains a vector, which is not supported in device code

I have the following code
#include <cuda.h>
#include <cuda_runtime.h>
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
//A bitset for the variable assignments
//The state for non existing variable 0 is stored as well, just to avoid +1/-1 adjustments
struct Atom_t {
enum where { device, host};
enum BoolOp {opXor, opOr, opAnd };
public: //TODO make private later
int VarCount;
bool isValid;
union {
uint32_t raw[1]; //don't worry about alignment, the compiler will not use aligned read/writes anyway.}
uint64_t raw64[1];
__m256i avxraw[1];
};
public:
__host__ __device__ friend bool operator==(const Atom_t& a, const Atom_t& b);
};
__host__ __device__ bool operator==(const Atom_t& a, const Atom_t& b) {
const auto IntCount = a.IntCount();
if (IntCount != b.IntCount()) { return false; }
#ifdef __CUDA_ARCH__
__shared__ bool isDifferent;
isDifferent = false;
for (auto i = ThreadId(); i < IntCount; i += BlockDim()) {
if (a.raw[i] != b.raw[i] || isDifferent) {
isDifferent = true;
break;
}
}
syncthreads();
return !isDifferent;
#else
auto result = true;
#ifdef _DEBUG
for (auto i = 0; i < IntCount; i++) {
if (a.raw[i] != b.raw[i]) { result = false; }
}
#endif
auto AvxCount = a.Avx2Count();
if (AvxCount != b.Avx2Count()) { if (result) { print("Atom_t == is incorrect"); } assert1(!result); return false; }
for (auto i = 0; i < AvxCount; i++) {
const auto packedCompare = _mm256_cmpeq_epi8(a.avxraw[i], b.avxraw[i]);
const auto bitmask = _mm256_movemask_epi8(packedCompare);
if (bitmask != -1) { if (result) { print("Atom_t == is incorrect"); } assert1(!result); return false; }
}
#endif
#ifndef __CUDA_ARCH__
assert(result);
#endif
return true;
}
The compiler complains
Description Resource Path Location Type
"__nv_bool (const Atom_t &, const Atom_t &)" contains a vector, which is not supported in device code
However, the vector is not in device code, only in the host code. How do I make this error go away in NSight Eclipse Edition 9.1 running CUDA 11.
I tried:
#ifdef __CUDA_ARCH__
# define DEAL_II_COMPILER_VECTORIZATION_LEVEL 0
#endif
But that does not work.
However, the vector is not in device code, only in the host code.
The error is coming about due to this line:
__m256i avxraw[1];
which is visible in both the host code and device code compilation trajectory.
According to my testing this may be a possible workaround:
$ cat t32.cpp
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#include <iostream>
typedef char dummy[sizeof(__m256i)];
struct Atom_t {
enum where { device, host};
enum BoolOp {opXor, opOr, opAnd };
public: //TODO make private later
int VarCount;
bool isValid;
union {
uint32_t raw[1];
uint64_t raw64[1];
#ifndef FOO //hide the vectorized datastruct from cuda's view
__m256i avxraw[1];
#else
alignas(32) dummy foo[1];
#endif
};
};
int main(){
std::cout << sizeof(__m256i) << std::endl;
std::cout << sizeof(Atom_t) << std::endl;
}
$ g++ t32.cpp -o t32
$ ./t32
32
64
$ g++ t32.cpp -o t32 -DFOO
$ ./t32
32
64
(Fedora Core 29)
The alignas(32) directive is still probably somewhat fragile if the definition of __m256i changes dramatically. And, clearly, the above is not CUDA code in the exact frame that was presented. It would need to be adapted (e.g. replace #ifndef FOO with #ifndef __CUDA_ARCH__)
I'm not suggesting that this code is correct, defect-free, or suitable for any particular purpose; it is mostly code provided by OP. My objective here is to identify issues that I see and are asked about in the question, and suggest possible ways to address those issues. Use this at your own risk.
Found it!
The problem is not the code in the method, the problem is the presence of the _m256i within view of cuda.
The following patch fixes the issue:
struct Atom_t {
enum where { device, host};
enum BoolOp {opXor, opOr, opAnd };
public: //TODO make private later
int VarCount;
bool isValid;
union {
uint32_t raw[1]; //don't worry about alignment, the compiler will not use aligned read/writes anyway.}
uint64_t raw64[1];
#ifndef __CUDA_ARCH__ //hide the vectorized datastruct from cuda's view
__m256i avxraw[1];
#endif
};
Now that nvcc does not see the vectorized datatype it will stop worrying.

How could I use `kallsyms_lookup_name` function to fix `unknown character` error when loading Linux kernel module?

I'm trying to complete a hooking sample attachment in a program for my uni assignment. The task requires to get a system call sys_rt_sigaction hooked when initiating a loadable module in Linux kernel (I use Ubuntu 18.04 LTS, kernel version is 5.0.0-23-generic). So, the case I'm struggling originates from an error could not insert module <module name>: Unknown symbol in module once I started sudo insmod <my module name>.ko.
After some googling, I see clear this problem arises due to missing sys_call_table export to run inserting as smoothly as well. Following this post, I want to cope that invoking kallsyms_lookup_name call before kicking off init procedure.
There is .c-file which provides with definitions of operations accessible by module (file name is buffer.c):
#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/unistd.h>
void * sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");// some wrongness here, but what exactly?
MODULE_LICENSE("GPL");
int (*real_rt_sigaction)(const char * path); // true syscall prototype
static int __init buffer_init_module(void);
static void __exit buffer_exit_module(void);
static int device_open(struct inode *, struct file *); // driver file opening
static int device_release(struct inode *, struct file *); // return of system resource control
static ssize_t device_read(struct file *, char *, size_t, loff_t *); // reading from driver file
static ssize_t device_write(struct file *, const char *, size_t, loff_t *); // writing into driver file
#define DEVICE_NAME "buffer"
#define BUF_LEN 80
// to be called instead
int alter_rt_sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact, size_t sigsetsize) {
printk(KERN_INFO "Syscall function hooked - you've lost control of your experience");
return 0;
}
static int Major;
static int Device_Open = 0;
static int total_open = 1;
static char Buf[BUF_LEN + 1] = "Buffer is empty, add some input\n";
static char *Msg_ptr;
static int Buf_Char = 50;
static int Bytes_Read = 0;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
static int __init buffer_init_module(void)
{
printk(KERN_INFO
"Device initializing in progress...");
Major = register_chrdev(0, DEVICE_NAME, &fops);
if(Major < 0) {
printk("Major number hasn't been assigned - Driver registration failed\n");
return Major;
}
printk(KERN_INFO "Registration success - device major number: %d\n", Major);
real_rt_sigaction=sys_call_table[__NR_rt_sigaction];
sys_call_table[__NR_rt_sigaction]=alter_rt_sigaction; // hooking implementation
return 0;
}
static void __exit buffer_exit_module(void)
{
unregister_chrdev(Major, DEVICE_NAME);
printk(KERN_INFO "Outside the module - exit successfully completed\n");
sys_call_table[__NR_rt_sigaction]=real_rt_sigaction; // original call reset
}
static int device_open(struct inode *inode, struct file *file)
{
if(Device_Open)
return -EBUSY;
Device_Open++;
printk(KERN_INFO "Device file has been accessed %d time(s)\n", total_open++);
Msg_ptr = Buf;
try_module_get(THIS_MODULE);
Bytes_Read = 0;
return 0;
}
static int device_release(struct inode * node, struct file * filep)
{
Device_Open--;
module_put(THIS_MODULE);
printk(KERN_INFO "Device file gets close\n");
return 0;
}
static ssize_t device_read(struct file * filep, char * buffer, size_t len, loff_t * offset)
{
int got_read = Bytes_Read;
if(Bytes_Read >= Buf_Char)
return 0;
while(len && (Bytes_Read < Buf_Char)) {
put_user(Msg_ptr[Bytes_Read], buffer+Bytes_Read);
len--;
Bytes_Read++;
}
return Bytes_Read-got_read;
}
static ssize_t device_write(struct file * filep, const char * buffer, size_t len, loff_t * offset)
{
Buf_Char = 0;
if(Buf_Char >= BUF_LEN) {
return 0;
}
while(len && (Buf_Char < BUF_LEN))
{
get_user(Msg_ptr[Buf_Char], buffer+Buf_Char);
len--;
Buf_Char++;
}
return Buf_Char;
}
module_init(buffer_init_module);
module_exit(buffer_exit_module);
Additively, there is code in Makefile:
obj-m += buffer.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
The painful moment here is an error message initializer element is not constant whenever I was trying to build module via sudo make in my project folder. As I follow the beginner's tutorials and need for some basic insight, it might be highly appreciable to see any help with solution or even some ideas how to handle the same problem more effectively, indeed.

How can i use skb->cb in my custom kernel modules

I build a new kernel module(2.6.32 CentOS6.5) named "xt_hello.ko", and I want to send some custom data to nflog, so I have changed skb->cb in my module, and nflog can read my data correctly.
Question: I found cb field has been used in tcp netlink and etc, can my module make some bad influence for them?
Definition of sk_buff:
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sk;
ktime_t tstamp;
struct net_device *dev;
unsigned long _skb_dst;
#ifdef CONFIG_XFRM
struct sec_path *sp;
#endif
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48];
... skip ...
Definition of cb in netlink:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
#define NETLINK_CREDS(skb) (&NETLINK_CB((skb)).creds)
Core code in my kernel module:
My definition:
struct xt_ndpi_cb {
u_int16_t protocol_detected ;
u_int16_t ndpi_proto;
}xt_ndpi_cb_t;
#define NDPI_CB(skb) (*(struct xt_ndpi_cb*)&((skb)->cb))
#define NDPI_CB_RECORD(skb,entry) NDPI_CB(skb).ndpi_proto = entry.VALUEA; NDPI_CB(skb).protocol_detected = entry.VALUEB;
/*core func*/
static bool ndpi_process_packet_tg(const struct sk_buff *_skb, const struct
xt_ndpi_tginfo *info, struct nf_conn *ct) {
...skip...
if (/*condition*/)
NDPI_CB_RECORD(_skb, entry)
}
Kernel module .c:
static bool ndpi_match(const struct sk_buff *skb, struct xt_action_param *par){
bool verdict;
struct nf_conn * ct;
enum ip_conntrack_info ctinfo;
const struct xt_ndpi_protocols *info = par->matchinfo;
ct = nf_ct_get(skb, &ctinfo);
if((ct == NULL) || (skb == NULL)) {
return(false);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
} else if (nf_ct_is_untracked(skb)) {
#else
} else if(nf_ct_is_untracked(ct)) {
#endif
return false;
}
/*change cb in this func*/
verdict = ndpi_process_packet(skb, info, ct );
return(verdict);
}
static struct xt_match ndpi_regs[] __read_mostly = {
{
.name = "ndpi",
.revision = 0,
.family = NFPROTO_IPV4,
.match = ndpi_match,
.matchsize = sizeof(struct xt_ndpi_protocols),
.me = THIS_MODULE,
}
};
Use in xt_NFLOG.c:
static unsigned int
nflog_tg(struct sk_buff *skb, const struct xt_target_param *par)
{
char buf[64+16]; /**my buf/
const struct xt_nflog_info *info = par->targinfo;
...skip...
sprintf(buf,"%s MYID=%u",info->prefix,NDPI_CB(_skb).ndpi_proto); /*NFLOG can get right cb value*/
...skip...
}

Why can't kprobe probe some functions in the kernel?

I tried to probe a simple function (e.g. myfunc) which I added in the kernel as following:
I created a file (myfile.c) under ~/source/kernel/ i.e. ~/source/kernel/myfile.c
I added a simple system call mysyscall and a local function myfunc in this file.
mysyscall function calls myfunc function.
I can get the address of the function using
cat /proc/kallsyms | grep myfunc
But the kprobe handler doesn't get called when I call the myfunc.
I can probe the system call 'mysyscall'. But when I try to probe 'myfunc', the handler doesn't get called.
Can anyone please explain why this is the behavior? Thanks.
As asked by Eugene, below is the code for kprobe and, mysyscall & myfunc.
The kprobe handler doesn't get called in the following code. But if i uncomment Line B and comment A in kprobe code given below, then kprobe handler gets called.
I used kernel version 4.8.
I added ~/source/kernel/myfile.c to write mysyscall and myfunc as given below:
#include <linux/linkage.h>
#include <linux/export.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/printk.h>
#include <linux/slab.h>
extern int myfunc(int ax)
{
int x = 6;
return x;
}
asmlinkage int* sys_mysyscall(int bx){
int *retval;
int ret = 0;
printk(KERN_ALERT "Hello World!\n");
ret = myfunc(10);
retval = kmalloc(sizeof(int), GFP_KERNEL);
*retval = 55;
printk("sum: %d\n", *retval);
printk("myfunc return value: %d\n", ret);
return retval;
}
EXPORT_SYMBOL(sys_mysyscall);
kprobe module code is as below:
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/kprobes.h>
//Line A
static const char *probed_func = "myfunc";
//Line B
//static const char *probed_func = "sys_mysyscall";
static unsigned int counter = 0;
int Pre_Handler(struct kprobe *p, struct pt_regs *regs){
printk("Pre_Handler: counter=%u\n",counter++);
return 0;
}
void Post_Handler(struct kprobe *p, struct pt_regs *regs, unsigned long flags){
printk("Post_Handler: counter=%u\n",counter++);
}
static struct kprobe kp;
int myinit(void)
{
int error;
printk("module inserted\n ");
kp.pre_handler = Pre_Handler;
kp.post_handler = Post_Handler;
kp.addr = (kprobe_opcode_t *)kallsyms_lookup_name(probed_func);
error = register_kprobe(&kp);
if(error)
{
pr_err("can't register_kprobe :(\n");
return error;
}
else
{
printk("probe registration successful\n");
}
return 0;
}
void myexit(void)
{
unregister_kprobe(&kp);
printk("module removed\n ");
}
module_init(myinit);
module_exit(myexit);
MODULE_AUTHOR("psin");
MODULE_DESCRIPTION("KPROBE MODULE");
MODULE_LICENSE("GPL");
I use a kernel module to call mysyscall as below:
sys_mysyscall(12);//12 is some random integer as parameter

How to use a seq_file in Linux kernel modules?

Hello all I'm new to Linux and wondering how to use a Linux sequence file in a module to traverse kernel objects.
What I know is I can use the command:
cat /proc/kallsyms
to view the available symbols and from what I've read on google, the symbols in the list that have a 'D' or 'd' are pointers to data structures.
Though I know the basics of how to create a module, the examples on the internet on how to use seq operations are not uniform and I'm getting a little confused.
If someone knows of any good doco that will help me understand how to create a seq file to traverse kernel objects and could post a link (or a quick example), I would be greatly appreciative.
Minimal runnable example
The kernel docs contain an example under Documentation/filesystems/seq_file.txt, but here is a runnable version of that with loop termination.
This example is behaves just like a file that contains:
0
1
2
However, we only store a single integer in memory
and calculate the file on the fly in an iterator fashion.
The file works for both read and lseek system calls, but there is no write system call equivalent:
How to implement a writable proc file by using seq_file in a driver module
Play around with the file with cat and dd skip= for the seeks.
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/printk.h> /* pr_info */
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
#include <linux/slab.h>
#include <uapi/linux/stat.h> /* S_IRUSR */
MODULE_LICENSE("GPL");
static int max = 2;
module_param(max, int, S_IRUSR | S_IWUSR);
static struct dentry *debugfs_file;
/* Called at the beginning of every read.
*
* The return value is passsed to the first show.
* It normally represents the current position of the iterator.
* It could be any struct, but we use just a single integer here.
*
* NULL return means stop should be called next, and so the read will be empty..
* This happens for example for an ftell that goes beyond the file size.
*/
static void *start(struct seq_file *s, loff_t *pos)
{
loff_t *spos;
pr_info("start pos = %llx\n", (unsigned long long)*pos);
spos = kmalloc(sizeof(loff_t), GFP_KERNEL);
if (!spos || *pos >= max)
return NULL;
*spos = *pos;
return spos;
}
/* The return value is passed to next show.
* If NULL, stop is called next instead of show, and read ends.
*
* Can get called multiple times, until enough data is returned for the read.
*/
static void *next(struct seq_file *s, void *v, loff_t *pos)
{
loff_t *spos;
spos = v;
pr_info("next pos = %llx\n", (unsigned long long)*pos);
if (*pos >= max)
return NULL;
*pos = ++*spos;
return spos;
}
/* Called at the end of every read. */
static void stop(struct seq_file *s, void *v)
{
pr_info("stop\n");
kfree(v);
}
/* Return 0 means success, SEQ_SKIP ignores previous prints, negative for error. */
static int show(struct seq_file *s, void *v)
{
loff_t *spos;
spos = v;
pr_info("show pos = %llx\n", (unsigned long long)*spos);
seq_printf(s, "%llx\n", (long long unsigned)*spos);
return 0;
}
static struct seq_operations my_seq_ops = {
.next = next,
.show = show,
.start = start,
.stop = stop,
};
static int open(struct inode *inode, struct file *file)
{
pr_info("open\n");
return seq_open(file, &my_seq_ops);
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.llseek = seq_lseek,
.open = open,
.read = seq_read,
.release = seq_release
};
static int myinit(void)
{
debugfs_file = debugfs_create_file(
"lkmc_seq_file", S_IRUSR, NULL, NULL, &fops);
if (debugfs_file) {
return 0;
} else {
return -EINVAL;
}
}
static void myexit(void)
{
debugfs_remove(debugfs_file);
}
module_init(myinit)
module_exit(myexit)
GitHub upstream.
Note how the seq_file API makes it much easier to write the read file operation.
single_open
If you have the entire read output upfront, single_open is an even more convenient version of seq_file.
This example behaves like a file that contains:
ab
cd
Code:
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/printk.h> /* pr_info */
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
#include <uapi/linux/stat.h> /* S_IRUSR */
MODULE_LICENSE("GPL");
static struct dentry *debugfs_file;
static int show(struct seq_file *m, void *v)
{
seq_printf(m, "ab\ncd\n");
return 0;
}
static int open(struct inode *inode, struct file *file)
{
return single_open(file, show, NULL);
}
static const struct file_operations fops = {
.llseek = seq_lseek,
.open = open,
.owner = THIS_MODULE,
.read = seq_read,
.release = single_release,
};
static int myinit(void)
{
debugfs_file = debugfs_create_file(
"lkmc_seq_file_single", S_IRUSR, NULL, NULL, &fops);
if (debugfs_file) {
return 0;
} else {
return -EINVAL;
}
}
static void myexit(void)
{
debugfs_remove(debugfs_file);
}
module_init(myinit)
module_exit(myexit)
GitHub upstream.
Tested on Linux 4.9.6.
It appears that starting from Linux 5, there was a backwards incompatible change that requires you to implement seq_file a bit differently, I think this talks about it: seq_file not working properly after next returns NULL and it appears that if you don't update this you get a warning:
seq_file: buggy .next function next [module-name] did not update position index

Resources