Resolving WinCC C-Script Random Crashes: Memory and Strings

David Krause15 min read
SiemensTroubleshootingWinCC
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Resolving WinCC C-Script Random Crashes: Memory and Strings

Random WinCC C-Script crashes that occur on different PDL screens, different mouse-click handlers, or different button invocations are almost always the symptom of one underlying fault class: an out-of-bounds memory access inside a C function called from a WinCC action. The crash is reported by the WinCC runtime as a hard process termination (RT process gone, CCWriteArchive.exe or PDLRT.exe exited unexpectedly, or the explorer.exe shell terminates and restarts) and is not a deterministic failure of a single C statement. This reference describes how to isolate the offending C function in a WinCC 7.x / WinCC Professional runtime, how to interpret the diagnostic artifacts in C:\Program Files (x86)\Siemens\WinCC\diagnose, and how to rewrite the C code so the random behavior disappears.

1. Problem Description

Typical symptoms reported in field installations:

  • WinCC Runtime crashes at random intervals — sometimes after a few minutes, sometimes after several hours.
  • Pressing a button bound to a C action (mouse click, value change) triggers a crash; the next click on the same button works fine.
  • Pressing a different button on a different PDL triggers the same crash.
  • The crash is reproducible across restarts but only after a non-deterministic number of events.
  • On a second engineering or operator station with the same project, the runtime may be stable, even though the project is byte-identical.
  • The Windows Event Viewer shows Application Error, faulting module CCScript.dll, PDLRT.exe, or kernel32.dll.

This pattern is characteristic of undefined behavior in C: the program writes into a memory region that is allocated but not large enough, into a region that was freed earlier, or into a region owned by another WinCC subsystem. The visible side-effect depends on whatever the corrupted bytes happen to be interpreted as — a button click handler, a user archive, or a screen painter call — which is why the same project crashes on different buttons or different PDLs at different times.

2. Root Cause Analysis

The four most common root cause families for random C-Script crashes in WinCC runtime are listed in the table below. Each is examined in detail in the following subsections.

Family Mechanism Symptom
String buffer overflow strcpy, sprintf, or strcat writes past the end of a fixed-size char array Crash on the call that overwrites adjacent memory; previous calls may succeed
Unterminated string Buffer is not null-terminated before printf-style functions or strlen read it strlen walks off the buffer, wsprintf produces garbage
UserArchive API misuse Archive handle opened but not closed, or TlgGet... called with wrong DW count Handle exhaustion, deadlock, or heap corruption after N calls
Reentrancy / global state Cyclic or event-triggered C action re-enters itself before the previous invocation finishes; globals are clobbered Crash only when the screen change is faster than the action's execution time

2.1 String Buffer Overflow

The C-Script editor of WinCC uses Microsoft Visual C runtime semantics. When a developer declares:

char szName[32];
sprintf(szName, "Archive.%s", GetProjectName());

the resulting string can be longer than 32 bytes if the project name is long or contains locale characters encoded in UTF-8 but interpreted as UTF-16. The overflow writes into the next variable on the stack frame, into another global, or into the runtime's internal heap metadata. The Windows heap manager detects the corruption on the next allocation or free and reports it as an access violation — but not necessarily on the same call that caused the corruption.

2.2 Unterminated String

Functions that read strings until they find \0 — strlen, strcpy, strcmp, wsprintf, the UserArchive API — all require a terminating zero byte. If the source is filled by a C action that copies a fixed number of bytes with memcpy or that receives data from a PLC tag whose length is unknown, the buffer may not contain a \0 at all, and the read walks off the end.

2.3 UserArchive API Misuse

The UserArchive API — UAGetFieldValue, UASetFieldValue, UAArchiveOpen, UAArchiveClose, UAArchiveInsert, UAArchiveDelete — allocates and frees internal buffers on every call. If a C action opens an archive, writes a row, and then exits without calling UAArchiveClose, the handle leaks. After roughly 200–500 leaked handles (depending on the WinCC build, e.g. V7.4 SP1 vs V7.5 SP2), the runtime exhausts its handle table and crashes the next time the API is invoked. The crash is therefore not on the first call but on a later one, which is what makes it look random.

2.4 Reentrancy

When a C action is configured with a 1-second trigger and the body of the action takes 1.5 seconds under load, the runtime starts a second instance of the C function before the first has returned. If both instances write to the same global char szBuffer[256], the second overwrites the first's intermediate state and the resulting string is corrupt. The first instance may then call UAArchiveInsert with a half-written buffer.

3. Diagnostic Toolchain

Before changing any code, capture the exact failure signature. The three artifacts that should be collected on every WinCC runtime station are listed below.

3.1 APDIAG — Live C Action Monitor

C:\Program Files (x86)\Siemens\WinCC\uTools\apdiag.exe is the WinCC-internal tool that connects to a running runtime and reports the evaluation state of every configured C action in the project. Run it on the operator station while the runtime is in process mode, then click through the suspect screens. APDIAG shows:

  • Action name and PDL
  • Trigger type (mouse click, value change, cyclic, picture change)
  • Last execution time, average execution time, and maximum execution time
  • Status of the action: OK, Aborted, Timeout

If an action reports Timeout but the runtime does not crash, the action is overrunning its scheduler slot — that is a reentrancy warning sign, not a memory bug, and the remediation is to reduce the trigger frequency or split the action.

3.2 Diagnose Folder — Crash Dumps and Logs

The folder C:\Program Files (x86)\Siemens\WinCC\diagnose contains the runtime's own log files and any *.dmp crash dumps produced when the WinCC process terminates abnormally. The key files are:

File What it contains
WinCC_Sys_.log Sequential list of system events, screen changes, archive calls, and the start/stop of C actions
WinCC_ServiceManager.log State transitions of the WinCC service layer, including PDLRT startup and crash
CCAlgRtSrv_* Alarm logging runtime events
*.dmp User-mode crash dump of PDLRT.exe or CCWriteArchive.exe; can be opened in WinDbg or Visual Studio
PDLRT.bak Last-known-good PDL cache; useful if a screen is corrupt

3.3 Windows Event Viewer

Open eventvwr.msc and navigate to Windows Logs > Application. Each WinCC crash produces an entry of type Application Error with the faulting module name. The example below is the typical signature for a heap corruption crash:

Faulting application name: PDLRT.exe, version: 7.5.2.10
Faulting module name: ntdll.dll, version: 10.0.19041.1288
Exception code: 0xC0000374 (Heap Corruption)
Fault offset: 0x00000000000AB123

Exception code 0xC0000374 is a STATUS_HEAP_CORRUPTION, which strongly indicates a C-side buffer overflow. Exception code 0xC0000005 (Access Violation) is a generic read/write to an invalid address, which can be a null pointer, a dangling pointer, or a freed block. Both codes are sufficient to start a Siemens support request with the corresponding *.dmp file attached.

4. Capture the C Action's Stack Trace

If a *.dmp file is generated, open it in WinDbg and run the following commands:

!analyze -v
.loadby sos clr
!clrstack
k L

The k L output shows the native stack. Look for any frame containing CCScript or PDLRT as the closest caller of ntdll!RtlpAllocateHeap or msvcrt!_calloc. That frame is the C action whose allocation was corrupted. Cross-reference its function name with the action names visible in APDIAG to find the offending C function.

5. Fix Patterns for Each Root Cause

5.1 Replace Fixed-Size Buffers with Bounded Snprintf

The Siemens-published recommendation in Siemens Support Entry 7929092 is to use snprintf with a destination size and to never use strcpy or strcat on user-controlled input. Rewrite all string-building code as follows.

Before (unsafe):

char szPath[MAX_PATH];
strcpy(szPath, "C:\\Program Files\\Siemens\\WinCC\\"); 
strcat(szPath, szProjectName); 
strcat(szPath, "\\archive");
SetTagChar("ArchivePath", szPath);

After (safe):

char szPath[MAX_PATH];
snprintf(szPath, sizeof(szPath),
         "C:\\Program Files\\Siemens\\WinCC\\%s\\archive",
         szProjectName);
szPath[sizeof(szPath) - 1] = '\0';
SetTagChar("ArchivePath", szPath);

The trailing explicit '\0' write is a safety net: snprintf only null-terminates when the formatted output is shorter than the buffer, so the assignment guarantees termination even if the format string expands unexpectedly.

5.2 Always Null-Terminate Strings Returned from PLC

When reading a string tag from the PLC, the C action receives a buffer whose used length is given by the tag's length, not by a \0. Force termination immediately:

char szOrder[64];
GetTagChar("OrderNumber", szOrder, sizeof(szOrder));
szOrder[sizeof(szOrder) - 1] = '\0';

The sizeof(szOrder) - 1 trick is more robust than szOrder[strlen(szOrder)] = '\0' because the latter still walks the buffer in case the PLC returned no terminator at all.

5.3 Pair Every UserArchive Open with a Close

Use the UAArchiveOpen / UAArchiveClose pair in a strict try/finally pattern. WinCC C-Script does not provide native try/finally, so encode it with a flag:

BOOL bArchiveOpen = FALSE;
long lArchiveID = 0;
lArchiveID = UAArchiveOpen("RecipeArchive", &lArchiveID);
if (lArchiveID > 0) {
    bArchiveOpen = TRUE;
    if (UAArchiveInsert(lArchiveID, ...) == FALSE) {
        // handle error
    }
}
if (bArchiveOpen) {
    UAArchiveClose(lArchiveID);
}

The flag pattern guarantees that the close call runs on every return path — including early return on error — which is the pattern that eliminates the handle-leak class of crashes.

5.4 De-Trigger C Actions While They Run

For actions that are bound to a value-change trigger on a tag, add a guard at the top of the function that disables the same trigger for the duration of the execution:

static BOOL bBusy = FALSE;
if (bBusy) return; 
bBusy = TRUE;
// ... action body ...
bBusy = FALSE;

The static flag is local to the action's compiled module, so it persists across calls and prevents reentrancy from the scheduler.

5.5 Avoid Allocating Memory Inside C Actions

The C-Script engine of WinCC 7.x has limited support for malloc/free. Prefer static or module-scope buffers and never free a buffer that you did not allocate in the same call frame. The Siemens-published guidance in Support Entry 7929092 states that any dynamic memory that survives the action boundary must be allocated and released with the WinCC-internal SysMalloc / SysFree pair, not with the standard C runtime.

6. Platform-Specific Caveats: Windows 10 / 11

The observation that "the same project crashes on one Windows 10 Pro workstation but not on another" is consistent with three Windows-side variables that change behavior between machines:

Variable Effect on C-Script Mitigation
DEP (Data Execution Prevention) policy If the C action writes executable bytes into a buffer, DEP terminates the process Disable DEP for PDLRT.exe via bcdedit /set {current} nx AlwaysOff, but only as a diagnostic; never in production
ASLR (Address Space Layout Randomization) Changes the layout of CCScript.dll between reboots, which changes the address that an overflow corrupts Set HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\PDLRT.exe\MitigationOptions if the same bug is reproducible on one machine and not another
UCRT version (Universal C Runtime) Different Windows 10 builds ship different ucrtbase.dll versions; snprintf semantics differ slightly across versions Pin the WinCC runtime to a specific Windows 10 build and validate on that build only
Antivirus / EDR file scanning An AV product that injects into PDLRT.exe can trigger access violations when it inspects a buffer the C action is writing to Add C:\Program Files (x86)\Siemens\WinCC\bin\* and C:\Program Files\Siemens\Automation\WinCC\bin\* to the AV exclusion list
Engineering tip: If the same project is stable on a clean Windows 10 22H2 image and unstable on an older 20H2 image that has an EDR agent installed, the EDR is the more likely root cause than the C code. Re-image the operator station and re-validate before changing any application code.

7. Project-Side Verification Procedure

After applying the fixes, follow this commissioning sequence to prove the runtime is now stable:

  1. Open the project in WinCC Explorer and rebuild the runtime database (menu Project > Rebuild).
  2. Start Runtime in Simulator mode (WinCC 7.x) or in RT Simulation (WinCC Professional / TIA Portal).
  3. Run apdiag.exe from uTools and confirm that every C action reports status OK with execution time below 250 ms.
  4. Use the WinCC internal Script Debugger (F9 in the script editor) to step through every C action and verify that no warning about unterminated strings or out-of-bounds writes is reported.
  5. Stress-test: cycle through every PDL in the project once per second for 30 minutes. Confirm that no *.dmp file is produced in the diagnose folder.
  6. Switch the trigger of every cyclic C action from 1 s to 100 ms for 10 minutes, then back to 1 s. Confirm no crash.
  7. Open Windows Event Viewer, filter to Application Error, and confirm that no entry of source Application Error and faulting module PDLRT.exe or CCScript.dll has been added during the stress test.

8. Preventive Coding Rules

The following rules eliminate the entire crash class and are mandatory for every new C action added to the project.

  • Never use strcpy, strcat, gets, or scanf("%s") inside a C action. Use the _s-suffix variants or snprintf with explicit size.
  • Declare every string buffer with a #define constant, not a magic number. Example: #define MAX_ORDER_LEN 64 and char szOrder[MAX_ORDER_LEN + 1]; — the +1 is the slot for the terminating \0.
  • Every UAArchiveOpen must be paired with a UAArchiveClose on every return path, including on error.
  • Every UAArchiveInsert and UAArchiveDelete must check the return value. A return value of FALSE is recoverable; an unchecked return value lets the runtime continue with a half-finished transaction and corrupts the archive.
  • Every C action that takes more than 100 ms of CPU on average (as reported by APDIAG) must be reviewed for redesign — usually it can be moved to a background tag or split.
  • Never call printf, MessageBox, or any UI function from a cyclic C action. Use printf only from event-driven actions.
  • Always reset all module-scope globals at the start of the action. A stale global from the previous call is a common source of intermittent crashes.

9. Diagnostic Decision Matrix

Symptom Check Likely Cause Fix
Crash on first mouse click of the day Event Viewer: STATUS_HEAP_CORRUPTION String overflow during screen open Snprintf with explicit size + null termination
Crash after exactly 200 archive writes Handle count in Process Explorer for PDLRT.exe UAArchiveOpen without UAArchiveClose Add flag-guarded close on every return path
Crash on screen change but not on value change APDIAG execution time for picture-change triggers Reentrancy between picture change and cyclic action Guard with static busy flag
Crash only on one operator station, stable on another DEP / ASLR / AV injection differences Platform-side issue, not code Re-image to a reference Windows 10 22H2 with EDR exclusions
Crash appears in CCWriteArchive.exe only Diagnose folder: CCWriteArchive_*.log Tag logging API misuse Check TlgGet... count parameter and archive handle reuse

10. Open a Siemens Support Request

If the crash still reproduces after all the fixes above, the remaining cases (e.g., a bug in CCScript.dll itself, an interaction with a specific TIA Portal version, or a corrupted WinCC installation) require a Siemens support ticket. Open it at the Siemens Industry Online Support portal. Attach the following artifacts:

  • The *.dmp file from C:\Program Files (x86)\Siemens\WinCC\diagnose
  • The full content of the WinCC_Sys_<date>.log covering the 30 minutes before the crash
  • The export of the project (WinCC 7.x: Project > Export; TIA Portal: archive the project)
  • The Windows 10 build number and the AV / EDR product name and version
  • The output of apdiag.exe with all actions in OK state

With these artifacts, Siemens support can correlate the crash against their internal known-issue database for CCScript.dll and PDLRT.exe and either provide a hotfix or confirm that the fix is a code-side change in the project.

What is the difference between a WinCC C action and a WinCC VBS action for crash analysis?

A C action is compiled to native code by the WinCC C-Script engine and runs inside CCScript.dll; a VBS action runs inside the Windows Script Host with full access to COM but no manual memory control. Random crashes in C actions are usually heap or stack corruptions; random crashes in VBS actions are usually null reference or type mismatch. The diagnostic flow is the same (APDIAG + diagnose folder), but the dump analysis tools differ.

Why does the same WinCC project crash on one operator station and not another?

Three Windows-side factors change behavior between machines: DEP policy, ASLR randomization, and AV/EDR file scanning of PDLRT.exe. The C code is the same, but the memory layout into which the overflow writes is different, so the corruption hits a critical address on one machine and a harmless one on the other. Re-image to a known clean Windows 10 22H2 and add the WinCC binary folders to the AV exclusion list before changing project code.

How do I open a WinCC crash dump?

Open the *.dmp file in WinDbg from the Windows Driver Kit (WinDbg download). Run !analyze -v first, then k L to inspect the native stack, and look for a frame containing CCScript or PDLRT as the closest caller of an ntdll!RtlpAllocateHeap or msvcrt!_calloc call. That frame is the C action that triggered the corruption.

How many C actions can WinCC 7.5 SP2 run concurrently before crashing?

The exact number depends on the action's stack size and on the UserArchive handle usage, but field-tested deployments on WinCC 7.5 SP2 on Windows 10 22H2 with 12–15 actions, each holding one or two archive handles, are stable. The limit is not the count of actions but the cumulative handle and stack usage. A C action that does not close its archive handles will exhaust the handle table after roughly 200–500 invocations regardless of the number of actions defined.

Is it safe to disable DEP for PDLRT.exe to isolate a C-Script crash?

Disabling DEP is safe as a diagnostic step on a test station only. It is not safe in production. If disabling DEP makes the crash disappear, the C action is writing executable bytes into a buffer — that is a code defect, not a DEP problem, and the action must be rewritten to not write executable bytes. After confirming the diagnosis, re-enable DEP and fix the C code.

Back to blog