pythonctypes

Python Ctypes Segmentation Fault


With the help of folks here I was able to get most of my ctype integration working in my python script.

Python3 processing ctypes data

I am interfacing with the Fanuc Focas library to pull in data from CNC machines. All of the c functions that don't need me to send arguments work, but I am not able to get any of them with additional arguments to work.

So my question is, how to do I debug this? (or how do I fix it if you see the issue). I have tried using gdb, but can't seem to figure that out.

For example, I can run this with no issue:

focas.cnc_statinfo2(libh, statinfo)

I cannot get this one to work: (where I have to supply the spindle number)

spindleNumber = ctypes.c_short(1)
ret = focas.cnc_rdspload(libh, spindleNumber, sploadA)

I believe my problem lies with the "spindleNumber" variable. It is expecting a ctype short, I have tried:

focas.cnc_rdspload(libh, 1, sploadA)

focas.cnc_rdspload(libh, '1', sploadA)

focas.cnc_rdspload(libh, c_short(1), sploadA)

focas.cnc_rdspload(libh, ctypes.c_short(1), sploadA)

Every one just results in the vague "Segmentation Fault (Core Dumped) error" and it does not generate a core file anywhere that I can find. So I am struggling to do any troubleshooting.

If it helps, here is the prototype: (https://www.inventcom.net/fanuc-focas-library/position/cnc_rdspload)

FWLIBAPI short WINAPI cnc_rdspload(unsigned short FlibHndl, short sp_no, ODBSPN *serialspindle);


typedef struct odbspn {
    short   datano;                 /* Spindle number. */
    short   type;                   /* Not used. */
    short   data[MAX_SPINDLE];      /* Spindle data. */
} ODBSPN ;      /* MAX_SPINDLE is maximum number of spindle. */

Full Code:

#!/usr/bin/env python3
import ctypes
from pathlib import Path
import os

#load fanuc focus cpp library
libpath = ("libfwlib32.so")
focas = ctypes.cdll.LoadLibrary(libpath)
focas.cnc_startupprocess.restype = ctypes.c_short
focas.cnc_exitprocess.restype = ctypes.c_short
focas.cnc_allclibhndl3.restype = ctypes.c_short
focas.cnc_freelibhndl.restype = ctypes.c_short

focas.cnc_sysinfo_ex.restype = ctypes.c_short
focas.cnc_rdspload.argtype = ctypes.c_short
focas.cnc_rdspload.restype = ctypes.c_short

ret = focas.cnc_startupprocess(0, "focas.log")
if ret != 0:
    raise Exception(f"Failed to create required log file! ({ret})")

#machine connection info (load dynamically)
ip = "172.23.4.53"
port = 8193


while True:
    #start connection to machine
    timeout = 10
    libh = ctypes.c_ushort(0)

    print(f"connecting to machine at {ip}:{port}")
    ret = focas.cnc_allclibhndl3(
        ip.encode(),
        port,
        timeout,
        ctypes.byref(libh),
    )
    if ret != 0:
        time.sleep(30.0)
        raise Exception(f"Failed to connect to cnc! ({ret})")
        
    sysinfoex = (ctypes.c_uint16 * 8)()
    sploadA = (ctypes.c_short * 3)()
    
    try:
        while True:
            
            print('--------------')
            print('CLASS - SYSINFO EX')
            print('--------------')
            #get sysinfo data from fanuc focus
            ret = focas.cnc_sysinfo_ex(libh, sysinfoex)

            if ret != 0:
                time.sleep(30.0)
                raise Exception(f"Failed to read sysinfo_ex! ({ret})")

            print('maximum controlled axes '+str(sysinfoex[0]))
            print('maximum spindle number '+str(sysinfoex[1]))
            print('maximum path number '+str(sysinfoex[2]))
            print('maximum machining group number '+str(sysinfoex[3]))
            print('controlled axes number '+str(sysinfoex[4]))
            print('servo axis number '+str(sysinfoex[5]))
            print('spindle number '+str(sysinfoex[6]))
            print('path number '+str(sysinfoex[7]))

            print('--------------')
            print('CLASS - SPINDLE LOAD 1')
            print('--------------')

            #get spinde 1 data from fanuc focus
            spindleNumber = ctypes.c_short(1)
            ret = focas.cnc_rdspload(libh, spindleNumber, sploadA)

            
            time.sleep(2)
    except:    
        print('failed to connect')
        time.sleep(5)
    
focas.cnc_exitprocess()

Solution

  • I used the following resources (not sure which (if any) is official):

    1. [InventCom]: FWLIB32 | GENERAL (referenced in the question)

    2. [MultiDNC]: FOCAS2/Ethernet for Linux OPERATOR’S MANUAL

    3. [GitHub]: strangesast/fwlib - Fanuc FOCAS Library

    Also listing [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) (which this is a duplicate of), that also contains a brief Undefined Behavior explanation.

    I created a small example of what (I thought) you're trying to do.

    code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    
    
    DLL_NAME = "libfwlib32.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    MAX_PATH = 0x100  # @TODO - cfati: Common value (Win specific). To be replaced with Fanuc constant value
    
    MAX_SPINDLE = 8  # @TODO - cfati: Set it according to your model (https://www.inventcom.net/fanuc-focas-library/position/cnc_rdspload)
    ALL_SPINDLES = -1
    
    EW_OK = 0
    
    
    # Formatting capabilities proxy class. For a more detailed representation, check: https://github.com/CristiFati/cfpyutils/blob/master/ctypes.py (WiP).
    class Structure(cts.Structure):
        def __repr__(self):
            ret = [super().__repr__() + ":"]
            for name, _ in self._fields_:
                ret.append("  {:s}: {:}".format(name, getattr(self, name)))
            return "\n".join(ret) + "\n"
    
    
    class PATH(Structure):
        _fields_ = (
            ("system", cts.c_short),
            ("group", cts.c_short),
            ("attrib", cts.c_short),
            ("ctrl_axis", cts.c_short),
            ("ctrl_srvo", cts.c_short),
            ("ctrl_spdl", cts.c_short),
            ("reserved", cts.c_short),
        )
    
    class ODBSYSEX(Structure):
        _fields_ = (
            ("max_axis", cts.c_short),
            ("max_spdl", cts.c_short),
            ("max_path", cts.c_short),
            ("max_mchn", cts.c_short),
            ("ctrl_axis", cts.c_short),
            ("ctrl_srvo", cts.c_short),
            ("ctrl_spdl", cts.c_short),
            ("ctrl_path", cts.c_short),
            ("ctrl_mchn", cts.c_short),
            ("reserved", cts.c_short * 3),
            ("path", PATH * MAX_PATH),
        )
    
    class ODBSPN(Structure):
        _fields_ = (
            ("datano", cts.c_short),
            ("type", cts.c_short),
            ("data", cts.c_short * MAX_SPINDLE),
        )
    
    
    def print_err(func, code=0):
        print("{:s} returned: {:d}".format(getattr(func, "__name__", "NO FUNC"), code))
    
    
    def main(*argv):
        dll = cts.CDLL(DLL_NAME)
        cnc_startupprocess = dll.cnc_startupprocess
        cnc_startupprocess.argtypes = (cts.c_long, cts.c_char_p)
        cnc_startupprocess.restype = cts.c_short
        cnc_allclibhndl3 = dll.cnc_allclibhndl3
        cnc_allclibhndl3.argtypes = (cts.c_char_p, cts.c_ushort, cts.c_long, cts.POINTER(cts.c_ushort))
        cnc_allclibhndl3.restype = cts.c_short
        cnc_sysinfo_ex = dll.cnc_sysinfo_ex
        cnc_sysinfo_ex.argtypes = (cts.c_ushort, cts.POINTER(ODBSYSEX))
        cnc_sysinfo_ex.restype = cts.c_short
        cnc_rdspload = dll.cnc_rdspload
        cnc_rdspload.argtypes = (cts.c_ushort, cts.c_short, cts.POINTER(ODBSPN))
        cnc_rdspload.restype = cts.c_short
        cnc_freelibhndl = dll.cnc_freelibhndl
        cnc_freelibhndl.argtypes = (cts.c_ushort,)
        cnc_freelibhndl.restype = cts.c_short
        cnc_exitprocess = dll.cnc_exitprocess
        cnc_exitprocess.argtypes = ()
        cnc_exitprocess.restype = cts.c_short
    
        ret = cnc_startupprocess(0, b"focas.log")
        if ret != EW_OK:
            print_err(cnc_startupprocess, res)
            return -1
        ip = b"127.0.0.1"
        port = 22
        timeout = 2
        handle = cts.c_ushort(0)
    
        print(f"Creating handle for {ip}:{port}")
        res = cnc_allclibhndl3(ip, port, timeout, cts.byref(handle))
        if res != EW_OK:
            print_err(cnc_allclibhndl3, res)
            res = cnc_exitprocess()
            if res != EW_OK:
                print_err(cnc_exitprocess, res)
            return -2
    
        print(f"Lib handle: {handle.value:d}")
    
        odbsysex = ODBSYSEX()
        odbspn = ODBSPN()
    
        repeat = True
        while repeat:
            res = cnc_sysinfo_ex(handle, cts.byref(odbsysex))
            if res != EW_OK:
                print_err(cnc_sysinfo_ex, res)
                break
            print(f"ODBSYSEX data:\n{odbsysex:}")
    
            spindle = ALL_SPINDLES
            res = cnc_rdspload(handle, spindle, cts.byref(odbspn))
            if res != EW_OK:
                print_err(cnc_rdspload, res)
                break
            print(f"ODBSPN data:\n{odbspn:}")
    
            repeat = input("Repeat? ").lower() in ("y", "yes")
    
        res = cnc_freelibhndl()
        if res != EW_OK:
            print_err(cnc_freelibhndl, res)
            res = cnc_exitprocess()
            if res != EW_OK:
                print_err(cnc_exitprocess, res)
            return res
    
        res = cnc_exitprocess()
        if res != EW_OK:
            print_err(cnc_exitprocess, res)
        return res
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

    There are some @TODOs there that I wasn't able to figure out.
    Also (needless to say that) I don't have a CNC at my disposal, so I wasn't able to fully test the code.

    Output:

    py_pc064_03.08_test0_lancer) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q077288502]> . ~/sopr.sh
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [064bit prompt]> ls -lF
    total 1245
    -rw-r--r-- 1 cfati cfati    4497 oct 20 20:46 code00.py
    -rw-r--r-- 1 cfati cfati 1264296 oct 20 20:36 libfwlib32-linux-x64.so.1.0.5
    lrwxrwxrwx 1 cfati cfati      29 oct 20 20:37 libfwlib32.so -> libfwlib32-linux-x64.so.1.0.5
    [064bit prompt]> 
    [064bit prompt]> LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(pwd) python ./code00.py
    Python 3.8.18 (default, Aug 25 2023, 13:20:30) [GCC 11.4.0] 064bit on linux
    
    Creating handle for b'127.0.0.1':22
    cnc_allclibhndl3 returned: -17
    
    Done.