Home

Ingo
Attachments
codesys.png (355652 bytes)

Download file

Warning!!! The Information on this project is outdated. Since the release SP17, the install script doesn't work anymore, due to the modularization of CODESYS.

Preface

CODESYS 4 Linux is just a small install helper, to install CODESYS on top of wine. This works, since the release of version 3.5 SP13, more or less smoothly. You can't expect, that all features are working, or that the usability is as smooth as under windows. But big parts of the product are working, what makes it well usable for many scenarios.

Installation

This describes, the prefered way, using the install script.
To manually do the installation, you might want to take a look into this page.

Download

Download the install script. This can be done with

  • svn
  • from the website
  • wget

e.g.:

wget -c "https://forge.codesys.com/tol/codesys-4-linux/docker/ci/master/tree/codesys-ide/install2.sh?format=raw"

Prerequisites

Make sure, that you own a valid Windows license. Because the installer will install DotNet from Microsoft. And this software requires a valid Windows license.

Install

Change the rights of the script, and run it.

chmod u+x install2.sh
./install2.sh

Start CODESYS

After the installer finished, you should have a folder "~/.wine.cds/drive_c/CODESYS". From there you can start CODESYS like this...

export WINEPREFIX=~/.wine.cds
wine ~/.wine.cds/drive_c/CODESYS/CODESYS/Common/CODESYS.exe

Docker

IDE

You can build a docker container, which contains a CODESYS installation running on top of wine. The installation process is automated in the same way as described above. To build the docker image, you can simply use the Dockerfile from the Git repository:

sudo docker build https://forge.codesys.com/git/tol,codesys-4-linux,docker.git\#:codesys-ide -t codesys-ide

The image can be used to automate tasks. It contains a wrapper to start CODESYS, which contains already a few scripts, which can be used to automate specific tasks.

For example:

sudo docker run codesys-ide codesys compile-library

But again, be aware, that you need to own a Windows license to use the image, as it contains a DotNet installation from Microsoft.

Runtime

The CODESYS Control runtime is natively running on Linux. The Soft PLC CODESYS Control Linux SL contains a free 2h Demo mode. You can easily create a debian based docker inage, which contains this demo:

sudo docker build https://forge.codesys.com/git/tol,codesys-4-linux,docker.git\#:codesys-control -t codesys-control

The runtime can then be started inside the container. Note that the runtime needs to be started with root privileges. This is not very common for docker, but currently the easiest solution to get it running:

sudo docker run --privileged codesys-control

Related

Home: Manual Installation

Discussion

  • cedihegi - 2025-03-05

    Building the codesys-ide container does not work for me with the command you provided here. After downloading dotNetFx40_Full_x86_x64.exe, my build is simply stuck on the "Executing load_remove_mono internal" command. Do you have any idea what could cause this?

     
  • ben-merryman

    ben-merryman - 2025-08-15

    Thanks for providing this! I'm changing jobs soon and will be using codesys on Linux for my next round of development.

     
  • mondinmr

    mondinmr - 2026-10-30

    Hi everyone,

    I’ve successfully got CODESYS 64 3.5.21.30 (SP21 Patch 3) running on Linux!

    I’ve compiled and gone online with a very large, highly complex project containing hundreds of thousands of lines of code, with an architecture that makes extremely heavy use of polymorphism. Compilation succeeded, and I was able to connect to the PLC and work online.

    The Wine version used is Wine 11.20, built from development commit:

    66caeb46aea4aa8fe6f87b7a209e7867651c09dd

    This is a new WoW64 build supporting both 32-bit and 64-bit Windows applications, with two local patches:

    NCrypt: implements NCryptEnumAlgorithms and NCryptFreeBuffer, with regression tests.
    NTDLL: addresses a thread-suspension livelock affecting the x86 .NET runtime under Linux split-lock mitigation. This resolved the freeze when opening Change in CODESYS Installer. The kernel mitigation remains enabled.
    The environment also uses native .NET Framework 4.8 and Windows Desktop Runtime 8.0.33 x86 for CODESYS Installer.

    I’m attaching both patches with the exact Wine base commit, plus a screenshot showing CODESYS running on my Linux desktop. Project and device names have been blurred.

    These are locally tested patches; the NTDLL change is still experimental, and not every CODESYS feature has been validated.

    To patch wine:

    diff --git a/dlls/ncrypt/main.c b/dlls/ncrypt/main.c
    index 62b95aa8136981271339dcbda97090261640b58f..6e427ef374cbc8ce783039e698b2762c0f77cbcb 100644
    --- a/dlls/ncrypt/main.c
    +++ b/dlls/ncrypt/main.c
    @@ -340,11 +340,81 @@ SECURITY_STATUS WINAPI NCryptEncrypt(NCRYPT_KEY_HANDLE handle, BYTE *input, DWOR
         return map_ntstatus(status);
     }
    
    -SECURITY_STATUS WINAPI NCryptEnumAlgorithms(NCRYPT_PROV_HANDLE provider, DWORD alg_ops,
    +static BOOL match_algorithm_operations(const NCryptAlgorithmName *algorithm, DWORD operations)
    +{
    
    +    /* ECDH advertises signing operations, but is not included in a signature-only enumeration. */
    +
    +    DWORD mask = 1 << (algorithm->dwClass - 1);
    +
    +    if (algorithm->dwClass == BCRYPT_ASYMMETRIC_ENCRYPTION_INTERFACE)
    +        mask |= NCRYPT_SIGNATURE_OPERATION;
    +    return !operations || (operations & mask);
    +}
    +
    +SECURITY_STATUS WINAPI NCryptEnumAlgorithms(NCRYPT_PROV_HANDLE handle, DWORD alg_ops,
                                                 DWORD *alg_count, NCryptAlgorithmName **alg_list, DWORD flags)
     {
    -    FIXME("(%#Ix, %#lx, %p, %p, %#lx): stub\n", provider, alg_ops, alg_count, alg_list, flags);
    -    return NTE_NOT_SUPPORTED;
    +    static const NCryptAlgorithmName algorithms[] =
    +    {
    +        {(WCHAR *)BCRYPT_RSA_ALGORITHM, BCRYPT_ASYMMETRIC_ENCRYPTION_INTERFACE,
    +         NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION | NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_DH_ALGORITHM, BCRYPT_SECRET_AGREEMENT_INTERFACE, NCRYPT_SECRET_AGREEMENT_OPERATION},
    +        {(WCHAR *)BCRYPT_DSA_ALGORITHM, BCRYPT_SIGNATURE_INTERFACE, NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDH_ALGORITHM, BCRYPT_SECRET_AGREEMENT_INTERFACE,
    +         NCRYPT_SECRET_AGREEMENT_OPERATION | NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDH_P256_ALGORITHM, BCRYPT_SECRET_AGREEMENT_INTERFACE,
    +         NCRYPT_SECRET_AGREEMENT_OPERATION | NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDH_P384_ALGORITHM, BCRYPT_SECRET_AGREEMENT_INTERFACE,
    +         NCRYPT_SECRET_AGREEMENT_OPERATION | NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDH_P521_ALGORITHM, BCRYPT_SECRET_AGREEMENT_INTERFACE,
    +         NCRYPT_SECRET_AGREEMENT_OPERATION | NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDSA_ALGORITHM, BCRYPT_SIGNATURE_INTERFACE, NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDSA_P256_ALGORITHM, BCRYPT_SIGNATURE_INTERFACE, NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDSA_P384_ALGORITHM, BCRYPT_SIGNATURE_INTERFACE, NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_ECDSA_P521_ALGORITHM, BCRYPT_SIGNATURE_INTERFACE, NCRYPT_SIGNATURE_OPERATION},
    +        {(WCHAR *)BCRYPT_AES_ALGORITHM, BCRYPT_CIPHER_INTERFACE, NCRYPT_CIPHER_OPERATION},
    +        {(WCHAR *)BCRYPT_3DES_ALGORITHM, BCRYPT_CIPHER_INTERFACE, NCRYPT_CIPHER_OPERATION},
    +        {(WCHAR *)BCRYPT_DES_ALGORITHM, BCRYPT_CIPHER_INTERFACE, NCRYPT_CIPHER_OPERATION},
    +    };
    +    struct provider *provider = (struct provider *)handle;
    +    NCryptAlgorithmName *list;
    +    DWORD count = 0;
    +    unsigned int i, j;
    +    SIZE_T size = 0, len;
    +    WCHAR *name;
    +
    +    TRACE("(%#Ix, %#lx, %p, %p, %#lx)\n", handle, alg_ops, alg_count, alg_list, flags);
    +
    +    if (!provider || provider->obj.type != PROVIDER) return NTE_INVALID_HANDLE;
    +    if (!alg_count || !alg_list) return NTE_INVALID_PARAMETER;
    +
    +    *alg_count = 0;
    +    *alg_list = NULL;
    +    if (flags & ~NCRYPT_SILENT_FLAG) return NTE_BAD_FLAGS;
    +
    +    for (i = 0; i < ARRAY_SIZE(algorithms); ++i)
    +    {
    +        if (!match_algorithm_operations(&algorithms[i], alg_ops)) continue;
    +        size += sizeof(*list) + (wcslen(algorithms[i].pszName) + 1) * sizeof(WCHAR);
    +        ++count;
    +    }
    +    if (!count) return NTE_NOT_SUPPORTED;
    +    if (!(list = malloc(size))) return NTE_NO_MEMORY;
    +
    +    name = (WCHAR *)(list + count);
    +    for (i = 0, j = 0; i < ARRAY_SIZE(algorithms); ++i)
    +    {
    +        if (!match_algorithm_operations(&algorithms[i], alg_ops)) continue;
    +        list[j] = algorithms[i];
    +        list[j++].pszName = name;
    +        len = (wcslen(algorithms[i].pszName) + 1) * sizeof(WCHAR);
    +        memcpy(name, algorithms[i].pszName, len);
    +        name += len / sizeof(WCHAR);
    +    }
    +
    +    *alg_count = count;
    +    *alg_list = list;
    +    return ERROR_SUCCESS;
     }
    
     SECURITY_STATUS WINAPI NCryptEnumKeys(NCRYPT_PROV_HANDLE provider, const WCHAR *scope,
    @@ -386,8 +456,10 @@ SECURITY_STATUS WINAPI NCryptFinalizeKey(NCRYPT_KEY_HANDLE handle, DWORD flags)
    
     SECURITY_STATUS WINAPI NCryptFreeBuffer(PVOID buf)
     {
    
    -    FIXME("(%p): stub\n", buf);
    -    return NTE_NOT_SUPPORTED;
    +    TRACE("(%p)\n", buf);
    +
    +    free(buf);
    +    return ERROR_SUCCESS;
     }
    
     SECURITY_STATUS WINAPI NCryptFreeObject(NCRYPT_HANDLE handle)
    diff --git a/dlls/ncrypt/tests/ncrypt.c b/dlls/ncrypt/tests/ncrypt.c
    index 61c61abd07b81f0fb1e1500f72dd8a57d4ecb27d..e5d7aea6b511ec25ee6913bf822c0dc8373513e1 100644
    --- a/dlls/ncrypt/tests/ncrypt.c
    +++ b/dlls/ncrypt/tests/ncrypt.c
    @@ -898,8 +898,161 @@ static void test_NCryptImportKey(void)
         NCryptFreeObject(prov);
     }
    
    +static void test_enum_algorithms(void)
    +{
    
    +    static const DWORD operations[] =
    +    {
    +        0, NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION, NCRYPT_SIGNATURE_OPERATION,
    +        NCRYPT_SECRET_AGREEMENT_OPERATION, NCRYPT_CIPHER_OPERATION,
    +        NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION | NCRYPT_SIGNATURE_OPERATION, ~0u,
    +    };
    +    static const DWORD bad_flags[] = {1, NCRYPT_MACHINE_KEY_FLAG, NCRYPT_SILENT_FLAG | 1, 0x80000000};
    +    static const DWORD unsupported_ops[] = {NCRYPT_HASH_OPERATION, BCRYPT_RNG_OPERATION, 0x80000000};
    +    NCryptAlgorithmName *list, *second;
    +    NCRYPT_PROV_HANDLE prov;
    +    NCRYPT_KEY_HANDLE key;
    +    SECURITY_STATUS ret;
    +    DWORD count, second_count, i, j, k;
    +    BOOL rsa, ecdsa, ecdh;
    +
    +    ret = NCryptFreeBuffer(NULL);
    +    ok(!ret, "got %#lx\n", ret);
    +
    +    ret = NCryptOpenStorageProvider(&prov, NULL, 0);
    +    ok(!ret, "got %#lx\n", ret);
    +    if (ret) return;
    +
    +    count = 0xdeadbeef;
    +    list = (void *)0xdeadbeef;
    +    ret = NCryptEnumAlgorithms(0, 0, &count, &list, 0);
    +    ok(ret == NTE_INVALID_HANDLE, "got %#lx\n", ret);
    +    ok(count == 0xdeadbeef, "got count %lu\n", count);
    +    ok(list == (void *)0xdeadbeef, "got list %p\n", list);
    +
    +    ret = NCryptEnumAlgorithms(prov, 0, NULL, &list, 0);
    +    ok(ret == NTE_INVALID_PARAMETER, "got %#lx\n", ret);
    +    ok(list == (void *)0xdeadbeef, "got list %p\n", list);
    +    ret = NCryptEnumAlgorithms(prov, 0, &count, NULL, 0);
    +    ok(ret == NTE_INVALID_PARAMETER, "got %#lx\n", ret);
    +    ok(count == 0xdeadbeef, "got count %lu\n", count);
    +
    +    ret = NCryptCreatePersistedKey(prov, &key, BCRYPT_RSA_ALGORITHM, NULL, 0, 0);
    +    ok(!ret, "got %#lx\n", ret);
    +    if (!ret)
    +    {
    +        ret = NCryptEnumAlgorithms(key, 0, &count, &list, 0);
    +        ok(ret == NTE_INVALID_HANDLE, "got %#lx\n", ret);
    +        ok(count == 0xdeadbeef, "got count %lu\n", count);
    +        ok(list == (void *)0xdeadbeef, "got list %p\n", list);
    +        NCryptFreeObject(key);
    +    }
    +
    +    for (i = 0; i < ARRAY_SIZE(bad_flags); ++i)
    +    {
    +        count = 0xdeadbeef;
    +        list = (void *)0xdeadbeef;
    +        ret = NCryptEnumAlgorithms(prov, 0, &count, &list, bad_flags[i]);
    +        ok(ret == NTE_BAD_FLAGS, "flags %#lx: got %#lx\n", bad_flags[i], ret);
    +        ok(!count, "got count %lu\n", count);
    +        ok(!list, "got list %p\n", list);
    +    }
    +    for (i = 0; i < ARRAY_SIZE(unsupported_ops); ++i)
    +    {
    +        count = 0xdeadbeef;
    +        list = (void *)0xdeadbeef;
    +        ret = NCryptEnumAlgorithms(prov, unsupported_ops[i], &count, &list, 0);
    +        ok(ret == NTE_NOT_SUPPORTED, "operations %#lx: got %#lx\n", unsupported_ops[i], ret);
    +        ok(!count, "got count %lu\n", count);
    +        ok(!list, "got list %p\n", list);
    +    }
    +
    +    for (i = 0; i < ARRAY_SIZE(operations); ++i)
    +    {
    +        count = 0;
    +        list = NULL;
    +        ret = NCryptEnumAlgorithms(prov, operations[i], &count, &list, 0);
    +        if (operations[i] == NCRYPT_CIPHER_OPERATION && broken(ret == NTE_NOT_SUPPORTED))
    +            continue; /* Windows 7 */
    +        ok(!ret, "operations %#lx: got %#lx\n", operations[i], ret);
    +        if (ret) continue;
    +        ok(count && list, "got count %lu, list %p\n", count, list);
    +        rsa = ecdsa = ecdh = FALSE;
    +        for (j = 0; j < count; ++j)
    +        {
    +            ok(list[j].pszName && list[j].pszName[0], "got empty algorithm name\n");
    +            if (!list[j].pszName) continue;
    +            ok(!list[j].dwFlags, "%s: flags %#lx\n", debugstr_w(list[j].pszName), list[j].dwFlags);
    +            for (k = 0; k < j; ++k)
    +                ok(wcscmp(list[k].pszName, list[j].pszName), "duplicate %s\n", debugstr_w(list[j].pszName));
    +            ret = NCryptIsAlgSupported(prov, list[j].pszName, 0);
    +            ok(!ret, "%s: got %#lx\n", debugstr_w(list[j].pszName), ret);
    +            if (!wcscmp(list[j].pszName, BCRYPT_RSA_ALGORITHM))
    +            {
    +                rsa = TRUE;
    +                ok(list[j].dwClass == BCRYPT_ASYMMETRIC_ENCRYPTION_INTERFACE, "RSA class %lu\n", list[j].dwClass);
    +                ok(list[j].dwAlgOperations == (NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION | NCRYPT_SIGNATURE_OPERATION),
    +                   "RSA operations %#lx\n", list[j].dwAlgOperations);
    +            }
    +            if (!wcscmp(list[j].pszName, BCRYPT_ECDSA_P256_ALGORITHM))
    +            {
    +                ecdsa = TRUE;
    +                ok(list[j].dwClass == BCRYPT_SIGNATURE_INTERFACE, "ECDSA class %lu\n", list[j].dwClass);
    +                ok(list[j].dwAlgOperations == NCRYPT_SIGNATURE_OPERATION, "ECDSA operations %#lx\n", list[j].dwAlgOperations);
    +            }
    +            if (!wcscmp(list[j].pszName, BCRYPT_ECDH_P256_ALGORITHM))
    +            {
    +                ecdh = TRUE;
    +                ok(list[j].dwClass == BCRYPT_SECRET_AGREEMENT_INTERFACE, "ECDH class %lu\n", list[j].dwClass);
    +                ok(list[j].dwAlgOperations == (NCRYPT_SECRET_AGREEMENT_OPERATION | NCRYPT_SIGNATURE_OPERATION),
    +                   "ECDH operations %#lx\n", list[j].dwAlgOperations);
    +            }
    +            if (operations[i] == NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION)
    +                ok(list[j].dwClass == BCRYPT_ASYMMETRIC_ENCRYPTION_INTERFACE,
    +                   "%s: class %lu\n", debugstr_w(list[j].pszName), list[j].dwClass);
    +            if (operations[i] == NCRYPT_SIGNATURE_OPERATION)
    +                ok(list[j].dwClass == BCRYPT_SIGNATURE_INTERFACE || list[j].dwClass == BCRYPT_ASYMMETRIC_ENCRYPTION_INTERFACE,
    +                   "%s: class %lu\n", debugstr_w(list[j].pszName), list[j].dwClass);
    +            if (operations[i] == NCRYPT_SECRET_AGREEMENT_OPERATION)
    +                ok(list[j].dwClass == BCRYPT_SECRET_AGREEMENT_INTERFACE,
    +                   "%s: class %lu\n", debugstr_w(list[j].pszName), list[j].dwClass);
    +            if (operations[i] == NCRYPT_CIPHER_OPERATION)
    +                ok(list[j].dwClass == BCRYPT_CIPHER_INTERFACE,
    +                   "%s: class %lu\n", debugstr_w(list[j].pszName), list[j].dwClass);
    +        }
    +        if (!operations[i] || operations[i] & (NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION | NCRYPT_SIGNATURE_OPERATION))
    +            ok(rsa, "operations %#lx: RSA not enumerated\n", operations[i]);
    +        if (!operations[i] || operations[i] & NCRYPT_SIGNATURE_OPERATION)
    +            ok(ecdsa, "operations %#lx: ECDSA_P256 not enumerated\n", operations[i]);
    +        if (!operations[i] || operations[i] & NCRYPT_SECRET_AGREEMENT_OPERATION)
    +            ok(ecdh, "operations %#lx: ECDH_P256 not enumerated\n", operations[i]);
    +        ret = NCryptFreeBuffer(list);
    +        ok(!ret, "got %#lx\n", ret);
    +    }
    +
    +    ret = NCryptEnumAlgorithms(prov, 0, &count, &list, NCRYPT_SILENT_FLAG);
    +    ok(!ret, "got %#lx\n", ret);
    +    if (!ret)
    +    {
    +        ret = NCryptEnumAlgorithms(prov, 0, &second_count, &second, 0);
    +        ok(!ret, "got %#lx\n", ret);
    +        if (!ret)
    +        {
    +            ok(second != list, "same enumeration buffer\n");
    +            ok(second_count == count, "got %lu and %lu\n", second_count, count);
    +            NCryptFreeBuffer(second);
    +        }
    +        NCryptFreeObject(prov);
    +        for (i = 0; i < count; ++i)
    +            ok(list[i].pszName && list[i].pszName[0], "name lost after freeing provider\n");
    +        ret = NCryptFreeBuffer(list);
    +        ok(!ret, "got %#lx\n", ret);
    +    }
    +    else NCryptFreeObject(prov);
    +}
    +
     START_TEST(ncrypt)
     {
    +    test_enum_algorithms();
         test_key_import_rsa();
         test_free_object();
         test_get_property();
    diff --git a/include/ncrypt.h b/include/ncrypt.h
    index 4c99ac09bb722faf50083f90b9f2eb536a4dd474..46ef37264e689407eef60620e3586a1f48564235 100644
    --- a/include/ncrypt.h
    +++ b/include/ncrypt.h
    @@ -79,6 +79,12 @@ typedef ULONG_PTR NCRYPT_KEY_HANDLE;
     typedef ULONG_PTR NCRYPT_HASH_HANDLE;
     typedef ULONG_PTR NCRYPT_SECRET_HANDLE;
    
    +#define NCRYPT_CIPHER_OPERATION                 0x00000001
    +#define NCRYPT_HASH_OPERATION                   0x00000002
    +#define NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION  0x00000004
    +#define NCRYPT_SECRET_AGREEMENT_OPERATION       0x00000008
    +#define NCRYPT_SIGNATURE_OPERATION              0x00000010
    +
     #define NCRYPT_KEY_STORAGE_INTERFACE        0x00010001
     #define NCRYPT_SCHANNEL_INTERFACE           0x00010002
     #define NCRYPT_SCHANNEL_SIGNATURE_INTERFACE 0x00010003
    @@ -142,6 +148,7 @@ SECURITY_STATUS WINAPI NCryptCreatePersistedKey(NCRYPT_PROV_HANDLE, NCRYPT_KEY_H
     SECURITY_STATUS WINAPI NCryptDecrypt(NCRYPT_KEY_HANDLE, BYTE *, DWORD, void *, BYTE *, DWORD, DWORD *, DWORD);
     SECURITY_STATUS WINAPI NCryptDeleteKey(NCRYPT_KEY_HANDLE, DWORD);
     SECURITY_STATUS WINAPI NCryptEncrypt(NCRYPT_KEY_HANDLE, BYTE *, DWORD, void *, BYTE *, DWORD, DWORD *, DWORD);
    +SECURITY_STATUS WINAPI NCryptEnumAlgorithms(NCRYPT_PROV_HANDLE, DWORD, DWORD *, NCryptAlgorithmName **, DWORD);
     SECURITY_STATUS WINAPI NCryptEnumKeys(NCRYPT_PROV_HANDLE, const WCHAR *, NCryptKeyName **, void **, DWORD);
     SECURITY_STATUS WINAPI NCryptExportKey(NCRYPT_KEY_HANDLE, NCRYPT_KEY_HANDLE, const WCHAR *, NCryptBufferDesc *, BYTE *,
                                            DWORD, DWORD *, DWORD);
    diff --git a/dlls/ntdll/tests/thread.c b/dlls/ntdll/tests/thread.c
    index 7416fd6d18c83d5fdd41a6f45531833220c8fffc..ebdf416144d16691f2c36d55dadfd3e8bb745911 100644
    --- a/dlls/ntdll/tests/thread.c
    +++ b/dlls/ntdll/tests/thread.c
    @@ -517,6 +517,95 @@ static void test_arm64_skip_loader_init(void)
         CloseHandle( thread );
     }
    
    +#ifdef __i386__
    +struct split_lock_thread_data
    +{
    
    +    volatile LONGLONG *value;
    +    LONG stop;
    +    HANDLE ready;
    +};
    +
    +static DWORD WINAPI split_lock_thread( void *arg )
    +{
    +    struct split_lock_thread_data *data = arg;
    +
    +    SetEvent( data->ready );
    +    while (!InterlockedCompareExchange( &data->stop, 0, 0 ))
    +    {
    +        InterlockedIncrement64( data->value );
    +        SwitchToThread();
    +    }
    +    return 0;
    +}
    +
    +static void test_split_lock_suspend(void)
    +{
    +    static const unsigned int offsets[] = {0, 60};
    +    struct split_lock_thread_data data;
    +    LONGLONG initial, value, previous;
    +    HANDLE thread;
    +    CONTEXT context;
    +    BYTE *memory;
    +    DWORD ret;
    +    unsigned int i, j;
    +
    +    memory = VirtualAlloc( NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE );
    +    ok( !!memory, "VirtualAlloc failed, error %lu.\n", GetLastError() );
    +    if (!memory) return;
    +    data.ready = CreateEventW( NULL, TRUE, FALSE, NULL );
    +    ok( !!data.ready, "CreateEvent failed, error %lu.\n", GetLastError() );
    +    if (!data.ready) goto done;
    +
    +    for (i = 0; i < ARRAY_SIZE(offsets); i++)
    +    {
    +        data.value = (LONGLONG *)(memory + offsets[i]);
    +        *data.value = 0;
    +        data.stop = 0;
    +        ResetEvent( data.ready );
    +        thread = CreateThread( NULL, 0, split_lock_thread, &data, 0, NULL );
    +        ok( !!thread, "CreateThread failed, error %lu.\n", GetLastError() );
    +        if (!thread) continue;
    +        ret = WaitForSingleObject( data.ready, 3000 );
    +        ok( ret == WAIT_OBJECT_0, "Thread not ready, wait returned %#lx.\n", ret );
    +        initial = *data.value;
    +        value = initial;
    +        for (j = 0; j < 32; j++)
    +        {
    +            Sleep( 1 );
    +            ret = SuspendThread( thread );
    +            ok( !ret, "SuspendThread returned %#lx.\n", ret );
    +            if (ret == ~0u) break;
    +            memset( &context, 0, sizeof(context) );
    +            context.ContextFlags = CONTEXT_FULL;
    +            ret = GetThreadContext( thread, &context );
    +            ok( ret, "GetThreadContext failed, error %lu.\n", GetLastError() );
    +            if (ret)
    +                ok( !(context.EFlags & 0x100), "Internal single-step flag leaked: %#lx.\n", context.EFlags );
    +            value = *data.value;
    +            Sleep( 1 );
    +            previous = *data.value;
    +            ok( value == previous, "Suspended thread advanced from %s to %s.\n",
    +                wine_dbgstr_longlong(value), wine_dbgstr_longlong(previous) );
    +            ret = ResumeThread( thread );
    +            ok( ret == 1, "ResumeThread returned %#lx.\n", ret );
    +        }
    +        ok( value > initial, "No atomic progress at offset %u during repeated suspension.\n", offsets[i] );
    +        InterlockedExchange( &data.stop, 1 );
    +        ret = WaitForSingleObject( thread, 3000 );
    +        ok( ret == WAIT_OBJECT_0, "Worker did not finish, wait returned %#lx.\n", ret );
    +        if (ret != WAIT_OBJECT_0)
    +        {
    +            TerminateThread( thread, 1 );
    +            WaitForSingleObject( thread, INFINITE );
    +        }
    +        CloseHandle( thread );
    +    }
    +    CloseHandle( data.ready );
    +done:
    +    VirtualFree( memory, 0, MEM_RELEASE );
    +}
    +#endif
    +
     START_TEST(thread)
     {
         init_function_pointers();
    @@ -548,4 +637,7 @@ START_TEST(thread)
         test_thread_bypass_process_freeze();
         test_NtQueueApcThreadEx();
         test_skip_thread_attach();
    +#ifdef __i386__
    +    test_split_lock_suspend();
    +#endif
     }
    diff --git a/dlls/ntdll/unix/signal_x86_64.c b/dlls/ntdll/unix/signal_x86_64.c
    index f807caa43191fec95635bdb0e1068ffac77ceee3..51a32eb80b1f183be3c9f6532c40b14cbcf8fb66 100644
    --- a/dlls/ntdll/unix/signal_x86_64.c
    +++ b/dlls/ntdll/unix/signal_x86_64.c
    @@ -516,6 +516,7 @@ struct amd64_thread_data
         DWORD                 fs;            /* 0338 WOW TEB selector */
         DWORD                 mxcsr;         /* 033c Unix-side mxcsr register */
         char                  syscall_dispatch; /* 0340 */
    +    BOOL                  suspend_after_step; /* complete a split lock before suspension */
     };
    
     C_ASSERT( sizeof(struct amd64_thread_data) <= sizeof(((struct teb_data *)0)->cpu_data) );
    @@ -2270,6 +2271,55 @@ static inline BOOL check_invalid_gsbase( struct thread_data *data, ucontext_t *u
     }
    
    
    +/**********************************************************************
    
    + *              suspend_signal_context
    + */
    +static void suspend_signal_context( struct thread_data *data, ucontext_t *sigcontext )
    +{
    +    struct xcontext context;
    +
    +    save_context( data, &context, sigcontext );
    +    context.c.ContextFlags |= CONTEXT_EXCEPTION_REPORTING;
    +    if (is_wow64() && context.c.SegCs == cs64_sel) context.c.ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
    +    wait_suspend( &context.c );
    +    restore_context( data, &context, sigcontext );
    +}
    +
    +
    +/**********************************************************************
    + *              defer_split_lock_suspend
    + *
    + * Linux split-lock mitigation sleeps before retrying a faulting instruction.
    + * Repeated suspension can keep interrupting that sleep indefinitely. Complete
    + * the instruction with a single step before reporting the suspended context.
    + * RF identifies an instruction being retried after a fault; asynchronous signal
    + * contexts do not retain the original alignment-check trap number.
    + */
    +static BOOL defer_split_lock_suspend( struct thread_data *data, ucontext_t *sigcontext )
    +{
    +#ifdef __linux__
    +    struct amd64_thread_data *amd64_data = amd64_thread_data( data );
    +    BYTE instr[4];
    +
    +    if (amd64_data->suspend_after_step) return TRUE;
    +    if (CS_sig(sigcontext) != cs32_sel) return FALSE;
    +    if ((EFL_sig(sigcontext) & (0x10000 | 0x100)) != 0x10000) return FALSE;
    +    if (amd64_data->dr7 & 0xff) return FALSE;
    +    if (virtual_uninterrupted_read_memory( (void *)RIP_sig(sigcontext), instr, sizeof(instr) ) != sizeof(instr))
    +        return FALSE;
    +    if (instr[0] != 0xf0 || instr[1] != 0x0f || instr[2] != 0xc7 ||
    +        (instr[3] & 0x38) != 0x08 || (instr[3] & 0xc0) == 0xc0)
    +        return FALSE;
    +    TRACE_(seh)( "completing split lock at %p before suspension\n", (void *)RIP_sig(sigcontext) );
    +    amd64_data->suspend_after_step = TRUE;
    +    EFL_sig(sigcontext) |= 0x100;
    +    return TRUE;
    +#else
    +    return FALSE;
    +#endif
    +}
    +
    +
     /**********************************************************************
      *     segv_handler
      *
    @@ -2282,6 +2332,13 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *_sigcontext )
         struct xcontext context;
         EXCEPTION_RECORD rec = { .ExceptionAddress = (void *)RIP_sig(sigcontext) };
    
    
    +    if (data->teb && amd64_thread_data( data )->suspend_after_step)
    +    {
    +        amd64_thread_data( data )->suspend_after_step = FALSE;
    +        EFL_sig(sigcontext) &= ~0x100;
    +        suspend_signal_context( data, sigcontext );
    +        rec.ExceptionAddress = (void *)RIP_sig(sigcontext);
    +    }
         save_context( data, &context, sigcontext );
    
         switch(TRAP_sig(sigcontext))
    @@ -2375,6 +2432,16 @@ static void trap_handler( int signal, siginfo_t *siginfo, void *_sigcontext )
    
         if (handle_syscall_trap( data, sigcontext, siginfo )) return;
    
    +#ifdef __linux__
    
    +    if (data->teb && amd64_thread_data( data )->suspend_after_step &&
    +        TRAP_sig(sigcontext) == TRAP_x86_TRCTRAP && siginfo->si_code == TRAP_TRACE)
    +    {
    +        amd64_thread_data( data )->suspend_after_step = FALSE;
    +        EFL_sig(sigcontext) &= ~0x100;
    +        suspend_signal_context( data, sigcontext );
    +        return;
    +    }
    +#endif
         save_context( data, &context, sigcontext );
    
         switch (TRAP_sig(sigcontext))
    @@ -2599,13 +2666,8 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *_sigcontext )
         }
         else
         {
    
    -        struct xcontext context;
    -
    -        save_context( data, &context, sigcontext );
    -        context.c.ContextFlags |= CONTEXT_EXCEPTION_REPORTING;
    -        if (is_wow64() && context.c.SegCs == cs64_sel) context.c.ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
    -        wait_suspend( &context.c );
    -        restore_context( data, &context, sigcontext );
    +        if (defer_split_lock_suspend( data, sigcontext )) leave_handler( data, sigcontext );
    +        else suspend_signal_context( data, sigcontext );
         }
     }
    
    @@ -2734,6 +2796,7 @@ NTSTATUS signal_alloc_thread( TEB *teb )
             else thread_data->fs = fs32_sel;
         }
         thread_data->frame_size = frame_size;
    
    +    thread_data->suspend_after_step = FALSE;
         return STATUS_SUCCESS;
     }
    
     
  • mondinmr

    mondinmr - 2026-10-31

    Update: patched Wine packages for Ubuntu are now available

    I have now published Ubuntu amd64 packages of my patched Wine build for CODESYS, targeting Ubuntu 22.04, 24.04, 26.04 and 26.10.

    The packages are based on Wine 11.20 and include all five compatibility patches covering NCrypt, thread suspension, security tokens, service tokens and local WMI marshalling. Both 32-bit and 64-bit Windows applications are supported through WoW64.

    In my working environment, CODESYS 3.5 SP21 successfully compiled and went online with a very complex project containing hundreds of thousands of lines of code and extensive use of polymorphism.

    PPA and installation

    All four binary builds have completed successfully and are now published and available through APT.

    Wine ADV for CODESYS PPA

    sudo add-apt-repository ppa:mondinmr/wine-adv
    sudo apt update
    sudo apt install wine-adv
    

    The runtime installs under /opt/wine-adv and provides dedicated commands, allowing it to coexist with your existing Wine installation.

    For example, to launch CODESYS from an existing dedicated prefix:

    WINEPREFIX="$HOME/.wine.codesys" wine-adv \
      'C:\Program Files\CODESYS 3.5.21.30\CODESYS\Common\CODESYS.exe'
    

    Adjust the executable path to match your installed version. Use the same WINEPREFIX when running the CODESYS installer or installing its prerequisites.

    The package contains the patched Wine runtime. CODESYS and its required Microsoft .NET components must be installed separately inside the prefix.

    Source code, patches and packaging are available on the project page.

    This is an experimental community build. Feedback and test results from other users are welcome!

     
  • mondinmr

    mondinmr - 2026-10-31

    CODESYS Installer and CodeMeter licensing are also working in my setup.

    For CodeMeter, I use the native Linux CodeMeter runtime, while the Windows applications run under the patched Wine build.

     

Log in to post a comment.