The Problem
I'm working on a large C project (C99) that makes heavy use of global variables (I know, I know). The program works fairly well, but it was originally designed to run once and exit.
As such, it relies on it's global/static memory to be initialized with 0 (or whatever value it was declared with), and during runtime it modifies these variables (as most programs do).
However, instead of exiting on completion, I want to run the program again. I want to make a parent program that has control and visibility into this large program. Having complete visibility into the running program is very important.
The solution needs to work on macOS, Linux, and Windows.
I've considered:
1. Forking it
Make a small wrapper program that serves as the "shell", and execute the large program as needed.
Pros
- OS does the hard work of resetting the memory to the correct values
- Guaranteed to operate as intended
Cons
- Lost visibility into the program
- Can't inspect memory of executing program from wrapper during runtime, harder to tweak settings before launching, harder to collect runtime information
- Need to implement a system to get internal data in/out of the program, potentially touching a lot of code
- Unified experience harder (sharing a GUI window, etc)
2. Identify critical structures manually
Peruse the source, run the program multiple times, wait for program to blow up on a sanity check or bad memory access.
Pros
- Easy to do
- Easy to start
- High visibility, code sharing, and unification
Cons
- Does not catch every case, very patchwork
- Time consuming
3. Refactor
Collect all globals into a single structure to memset
, create initializers for variables that are initialized with a value. Handle statics on a case-by-case basis.
Pros
- Conceptually easy, sledgehammer approach
- High visibility, code sharing, and unification
Cons
- Very time consuming, codebase large, would touch pretty much everything
4. Magic wand
Tell the OS to reinitialize global/static memory. If I need to save a value, I'll store it locally and then rewrite it when it's done.
Pros
- Mostly perfect :)
Cons
- Doesn't exist (?)
- Very black magic
- Probably not cross platform
- May anger 3rd-party libs
What I am doing now
I am going with option 2 right now, just making my way through the code, leaning on the program to crash and point me in the right direction.
I'd say this method has gotten me about 80% of the way there. I've identified and reinitialized enough things that the program, more or less, can be rerun. It's not as widespread as I thought, and it gives me a lot of hope.
Occasionally, strange things happen or it doesn't operate as intended, but it also doesn't crash. This makes tracking it down more difficult.
I just need something to get me that last 20%. Maybe some sort of static analysis tool, or something to help me go through the source and see where globals are touched.
To detect easily the global and static variables you can try CppDepend and execute a cqlinq query like this one
You can also modify the query if you want the variables used by a speific function or in a specific file.