32 bits time_t and Y2038 issue

Mar 11, 2025 Last reply: 1 anno fa 53 Replies

Do you run <msys>\usr\bin\make.exe directly from a cmd.exe shell? Or do you open a msys specific shell?

I tried to use make for Windows some time ago, but it was a mess. Maybe msys2 system is much more straightforward.

You're right.

What do you mean with "open the elf file in the debugger"?

:-)

I have seen problems when using tools that are build during the compile proess, used to generate further C code.

I would suggest using WSL instead of msys2. I have not used it for cross-compiling, but it works fine (except for file access performance) for my documentation process, which needs commandline pdf modification tools plus latex.

I don't do that anymore - wildcards in makefiles can lead to all kinds of strange behaviour due to files that are left/placed somewhere but are not really needed. I prefer to list the files I want compiled - it is not that much work.

cu Michael

If you are not invested in existing makefiles, have a look at cmake instead of make.

It is not that big a task, and you learn what kind of compiler flags, include paths etc. you really need - that helps a lot when you want to integrate those libraries into your own project in the next step.

My cmake files have functions like target_add_HAL_LL_STM32F0(target) or target_add_freertos(target) that take care of adding the right source files, include parameters etc. - it take a bit to set this up, but makes it easy to maintain multiple projects.

Sure. I have two targets in my makefiles - one starts openocd (needed once per debug session, keeps running), and one fires up the debugger (ddd) with the correct ELF file. That works a lot faster than firing up the debug session in any vendor eclipse I have seen.

Depends on what patches the vendor included, so I would suggest to do the switch early in the development cycle. I have not yet used vendor-patched ARM gcc versions - the upstream gcc versions worked just fine for STM32, SamD11, LPC8, LPC17xx, MM32, GD32, Luminary (now TI), TI TM4, TI MSPM0.

The RTC I used is a MicroCrystal RV3029 - low drift, low power, canned part, works great for this one-off project, and the ESP32 (I wanted NTP for time updates) would use too much power to run on battery, so I can live with the register interface using seconds/minutes/...

cu Michael

Either works fine.

So does running "make" from whatever IDE, editor or other tool you want to use.

I have been using "make" on DOS, 16-bit Windows, OS/2, Windows of many flavours, and Linux of all sorts for several decades. I really can't understand why some people feel it is difficult. (Older makes on DOS and Windows were more limited in their features, but worked well enough.)

These days I happily use it on Windows with recursive make (done /carefully/, as all recursive makes should be), automatic dependency generation, multiple makefiles, automatic file discovery, parallel builds, host-specific code (for things like the toolchain installation directory), and all sorts of other bits and pieces.

A generated elf file contains all the debug information, including symbol maps and pointers to source code, as well as the binary. Debuggers work with elf files - whether the debugger is included in an IDE or is stand-alone.

I have several projects where C code is generated automatically (such as with a Python script that turns an image file into a const uint8_t array).

In the days of FAT filesystems - 16-bit Windows and DOS, primarily - it was possible to get issues with that kind of thing along with the weaker "make" implementations included with Borland tools and the like. The big issue was the timestamp resolution for files was too coarse.

I would not suggest WSL unless you are trying to re-create a full Linux environment - since that is what WSL is. It is a virtual machine with Linux. If you have a complex setup with *nix features like soft links, or filenames with Windows-hostile characters, or want to use /dev/urandom to generate random data automatically in your build process, then use WSL. (Or just switch to Linux.)

msys2 is totally different. The binaries are all native Windows binaries, and they all work within the same Windows environment as everything else. There are no problems using Windows-style paths (though of course it is best to use relative paths and forward slashes in your makefiles, #include directives, etc., for cross-platform compatibility). You can use the msys2 programs directly from the normal Windows command window, or Powershell, or in batch files, or directly from other Windows programs.

I have also used pdf modification tools and pdfLaTeX (Miktex) on Windows, controlled by make, from msys. (It's been a while, so it was probably msys make rather than msys2 make.)

I do most of that kind of thing from Linux - amongst other things, it is faster on the same hardware for all of this stuff. But I like my builds to work on Windows as well as Linux.

I'm sure you can guess the correct way to handle that - don't leave files in the wrong places :-)

In a project of over 500 files in 70 directories, it's a lot more work than using wildcards and not keeping old unneeded files mixed in with source files.

Are the make recipes are run using a normal Unix shell (bash? ash? bourne?) with exported environment variables as expected when running 'make' on Unix?

The gnu make functions [e.g $(shell <whatever>)] all work as epected?

Or are there certain gnu make features you have to avoid for makefiles to work under msys2?

Am 18.03.2025 um 21:58 schrieb Grant Edwards:

Pretty much, yes. There are some gotchas in handling of path names, and particularly their passing to less-than-accomodating, native Windows compilers etc.. And the quoting of command line arguments can become even dicier than native Windows already is.

There be dragons, but MSYS2 will keep the vast majority of them out of your sight.

Yes, as long as you stay reasonable about the selection of things you try to run that way, and keep in mind you may have to massage command line arguments if <whatever> is a native Windows tool.

For reference, MSYS2 is also the foundation of Git Bash for MS Windows, which you might be familiar with already...

The underlying technology of MSYS2 is a fork of the Cygwin project, which is an environment that aims to provide the best emulation of a Unix environment they can, inside MS Windows. The key difference of the MSYS2 fork lies in a set of tweaks to resolve some of the corner cases more towards the Windows interpretation of things.

So, if your Makefiles are too Unix centric for even MSYS2 to handle, Cygwin can probably still manage. And it will do it for the small price of many of your relevant files needing to have LF-only line endings.

Here's a rough hierarchy of Unix-like-ness among Make implementations on a PC, assuming your actual compiler tool chain is a native Windows one:

0) your IDE's internal build system --- not even close 1) original DOS or Windows "make" tools 2) fully native ports of GNU make (predating MSYS) 3) GNU Make in MSYS2 4) GNU Make in Cygwin 5) WSL2 --- the full monty

I'll also second an earlier suggestion: for newcomers with little or no present skills in Makefile writing, CMake or Meson can be a much smoother entry into this world. Also, if you're going this route, I suggest to consider skipping Make and using Ninja instead.

As I said in another post - no, "make" can run in any way that is convenient. Windows command prompt, Powershell (which I seldom use), msys2 bash, started from a "tools" menu in an IDE - whatever.

Yes.

To be clear, that is not a feature I have used in a particularly advanced way - I haven't used the "shell" function in a way that relies on anything that relies on a specific shell type. But even on Windows, there's no problem with passing environment variables on to a program started in a subshell - such as an additional sub-make.

There is nothing that I have avoided.

It is possible that there /are/ features that I would have to avoid, if I used them - I can't claim to have made use of /all/ the features of gnu make. But I use a lot more advanced features than most make users, without trouble.

Exactly - there are a lot fewer dragons, and they are smaller, than with other solutions. If you try to use path names with very long names, spaces, names like "*", or with embedded quotation marks, or the dozen characters that Windows doesn't like, then you are asking for trouble. But those cause trouble on Windows no matter what.

You can certainly make your life easier by avoiding ridiculous path names. Put your compilers in directories under "c:/compilers/", or whatever, so that you can easily find them and refer to them. And put your projects in "c:/projects/". It is unfortunate that Windows uses downright insane paths by default for installed programs and for user documents, but you don't have to follow those.

You can still use msys2, and make, even with ridiculous path names, but you need to be more careful to get your filename quoting right.

msys2 / mingw-64 is the basis for most modern gcc usage on Windows (mingw-64 is the gcc "target" and has the standard library, while msys2 provides a substantial fraction of POSIX / Linux compatibility along with vast numbers of common utility programs and libraries). When you install the GNU ARM Embedded toolchain, it is built by and for mingw-64. When you install NXP's IDE, or Atmel Studio, or pretty much any other vendor development tool, all the *nix world tools on it will be compiled for old mingw or modern mingw-64, and many will be taken directly from old msys or modern msys2. There are a few IDE's that now use cmake and ninja, but for most of them, when you select "build" from the menus, the IDE generates makefiles then runs an mingw / msys build of gnu make. Those who think you have to use an IDE on Windows and make does not work, are already using make !

I believe they made more changes than that. Cygwin used to suffer from three major problems - it's focus on POSIX compatibility made it highly inefficient, it used unix-like behaviour that was alien to Windows (like /cygdrive/c/... paths), and it suffered from a level of DLL hell beyond anything seen on other Windows programs. This last point made things very difficult if you only wanted a few cygwin-based programs. The original msys and mingw projects were a lot simpler, but stagnated and failed to support 64-bit targets and even C99. msys2 and mingw-64 were made to get the best of both worlds, taking parts from each of these projects and adding their own.

There are certainly a few things that Cygwin can handle that msys2 cannot. For example, cygwin provides the "fork" system call that is very slow and expensive on Windows, but fundamental to old *nix software. msys2 (and msys before it) do not support "fork". This is not an issue for the solid majority of modern *nix software, because threading and "vfork / execve" replaced most use-cases for "fork" in the last twenty years.

But you can happily use lots of unix-like things from msys2. If you want 16 bytes of random data in your program, you can write:

head -c 16 /dev/random | hexdump -v -e '/i "%02x, "' > rand.inc

and use it as :

const uint8_t random_data = { #include "rand.inc" };

The "head..." line will work fine from the normal Windows command line, or an msys2 bash shell, or a makefile, or whatever.

In most cases - at least for IDE's I have had from microcontroller vendors - the IDE's internal build system /is/ make. It is a normal msys2 make (albeit often not the latest version). The IDE's "internal build" generates makefiles automatically (a bit slowly and inefficiently for big projects), then runs "make".

Of course, you don't get to work with these makefiles directly, so you can't use any of the more interesting features of make - any changes you make to the generated makefiles will be overwritten on the next build.

That varied from supplier to supplier, since DOS and Windows don't have any kind of native development tools. Borland and MS both provided "make" utilities with basic features but lots of limitations compared to the *nix world. Other tool vendors may have been different.

Ninja is the assembly language of build tools - it is meant to be fast to run, but people are not expected to write ninja files manually. You generate them with cmake or other tools.

cmake is certainly a popular modern build system, but I personally have never got into it. It strikes me as massively over-complex and very fragile - it always seems to need very specific versions of cmake, which in turn require very specific versions of a hundred different dependencies. Maybe in a decade or so it will have stabilised enough that the same cmake setup can be used reliably on multiple different systems, but it has a /long/ way to go before then. Perhaps I am being unfair to cmake here due to lack of experience, but I have yet to see a point to it.

Meanwhile, I can (and do) build my projects on four or five different Linux systems and a couple of Windows machines, all of wildly different generations, using the same makefile and a copy of either the Linux or the Windows directories containing the appropriate GNU ARM Embedded toolchain. All I need to modify is a host-specific pointer to the toolchain directory.

I believe Windows inherited that from VAX/VMS via Dave Cutler.

Back when the Earth was young I used to do embedded development on VMS. I was, however, a "Unix guy" so my usual work environment on VMS was "DEC/Shell" which was a v7 Bourne shell and surprisingly complete set of v7 command line utilities that ran on VMS. [Without DEC/Shell, I'm pretty sure I wouldn't have survived that project.] At one point I wrote some fairly complex shell/awk/grep scripts to analyze and cross-reference requirements documents written in LaTeX. The scripts would have taken a few minutes to run under v7 on an LSI-11, but they took hours on a VAX 780 under VMS DEC/Shell (and used up ridiculous amounts of CPU time). I was baffled. I eventually tracked it down to the overhead of "fork". A fork on Unix is a trivial operation, and when running a shell program it happens a _lot_.

On VMS, a fork() call in a C program had _huge_ overhead compared to Unix [but dog bless the guys in Massachusetts, it worked]. I'm not sure if it was the process creation itself, or the "duplication" of the parent that took so long. Maybe both. In the end it didn't matter: it was so much easier to do stuff under DEC/Shell than it was under DCL that we just ran the analysis scripts overnight.

I am always a bit wary of people saying features were copied from VMS into Windows NT, simply because the same person was a major part of the development. Windows NT was the descendent of DOS-based Windows, which in turn was the descendent of DOS. These previous systems had nothing remotely like "fork", but Windows already had multi-threading. When you have decent thread support, the use of "fork" is much lower - equally, in the *nix world at the time, the use-case for threading was much lower because they had good "fork" support. Thus Windows NT did not get "fork" because it was not worth the effort - making existing thread support better was a lot more important.

Yes, fork is relatively trivial (in terms of execution time and resources) on /most/ *nix systems. (On some, like ucLinux without an MMU, it is very expensive.) Basically, it is handled by making all read-write pages of the process read-only, duplicating the process structure, and then handling copying of what were writeable pages if and when the parent or child actually write to them. This has become more costly as applications get more advanced and have more memory pages than they used to, but is still relatively cheap.

However, true "fork" is very rarely useful, and is now rarely used in modern *nix programming. Most uses of "fork" are either followed immediately by an exec call to load and run a new executable (so vfork/execve is much cheaper), or they are duplicates of a server daemon and you can usually do the job more efficiently with multi-threading or asynchronous handling. It is typically only for servers that want to spawn duplicates that have isolation for security reasons (such as an ssh server) where it is worth using "fork".

So these days, bash does not use "fork" for starting all the subprocesses - it uses vfork() / execve(), making it more efficient and also conveniently more amenable to running on Windows.

On Windows with Cygwin, "fork" needs to copy all the writeable pages (and perhaps also non-writeable pages), as well as explicitly duplicate things like file handles. There is also a measurable overhead for processes even if they don't fork - for many types of resource allocation (such as file and network handles), the Cygwin layer has to track the resources just in case you fork later on. msys and msys2 don't support fork(), so their POSIX emulation layer is much thinner - in many cases it just translates POSIX-style calls into the Windows equivalent.

(For a more entertaining use of the term "fork", I recommend the Netflix series "The Good Place".)

The accounts I've read about NT say otherwise. They all claim that NT was a brand-new kernel written (supposedly from scratch) by Dave Cutler's team. They implemented some backwards compatible Windows APIs, but the OS kernel itself was based far more on VMS than Windows.

Quoting from

formatting link
Although NT was not an exact clone of Cutler's previous operating systems, DEC engineers almost immediately noticed the internal similarities. Parts of VAX/VMS Internals and Data Structures, published by Digital Press, accurately describe Windows NT internals using VMS terms. Furthermore, parts of the NT codebase's directory structure and filenames matched that of the MICA codebase.[10] Instead of a lawsuit, Microsoft agreed to pay DEC $65–100 million, help market VMS, train Digital personnel on Windows NT, and continue Windows NT support for the DEC Alpha.

That last sentence seems pretty damning to me.

But it did end up making support for the legacy fork() call used by many legacy Unix programs very expensive. I'm not claiming that fork() was a good idea in the first place, that it should have been implemented better in VMS or Windows, or that it should still be used.

I'm just claiming that

  1. Historically, fork() was way, way, WAY slower on Windows and VMS than on Unix. [Maybe that has improved on Windows.]
  2. 40 years ago, fork() was still _the_way_ to start a process in most all common Unix applications.

I didn't mean to imply that it was. However, back in the 1980s when I was running DEC/Shell with v7 Unix programs, fork() was still how the Bourne shell in DEC/Shell started execution of every command.

Those utilities were all from v7 Unix. That's before vfork() existed. vfork() wasn't introduced until 3BSD and then SysVr4.

formatting link

That's good news. You'd think it wouldn't be so slow. :)

The kernel itself was new - and perhaps was more "inspired" by VMS than some lawyers liked. But the way it was used - the API for programs, and the way programs were built up, and what users saw - was all based on existing Windows practice. In particular, it was important that the API for NT supported the multithreading from Win32s - thus it was not at all important that it could support "fork".

I'm sure there were plenty of similarities in the way things worked internally. And perhaps Cutler had some reason to dislike "fork", or perhaps simply felt that VMS hadn't needed it, and so NT would not need it. But NT /had/ to have multi-threading, and when you have multi-threading, "fork" is not nearly as useful or important.

Agreed.

Windows NT originally tried to be POSIX compliant (or at least, to have a POSIX "personality" - along with a Win32 "personality", and an OS/2 "personality"). That would mean that some level of "fork" would be needed. But the POSIX support aims were reduced over time. I don't know how much of Cygwin's "fork" support is implemented in Cygwin or how much is in the NT kernel.

However, it's worth remembering that MS was not nearly as nice a company at that time as it is now, and not nearly as much of a team player. The only thing better for MS than having Windows NT be unable to run ports of *nix software was to be able to run such software very badly. For example, if Oracle could run on Windows but was much slower than MS SQL server due to a poor "fork", that would be a bigger marketing win than simply not being able to run Oracle. But perhaps that is being a bit too paranoid and sceptical.

Agreed.

I remember the early days of getting gcc compiled for Windows (for the

68k target, in my case) - most of it was fine, but one program ("collect2" used by C++ to figure out template usage, if I remember correctly) used "fork" and that made things massively more complicated.

Yes, vfork() was a later addition.

I also remember endless battles about different threading systems for Linux before it all settled down.

Even without "fork" being involved, Windows is /much/ slower at starting new processes than Linux. It is also slower for file access, and has poorer multi-cpu support. (These have, I believe, improved somewhat in later Windows versions.) A decade or so ago I happened to be approximately in sync on the hardware for my Linux desktop and my Windows desktop (I use both systems at work), and tested a make + cross-gcc build of a project with a couple of hundred C and C++ files. The Linux build was close to twice the speed.

Actually, Microsoft folks say that Windows NT kernel supports fork. It was used to implement Posix subsystem. IIUC they claim that trouble is in upper layers: much of Windows API is _not_ kernel and implementing well behaving fork means that all layers below user program, starting from kernel would have to implement fork.

So this complicated layered structure seem to be main technical reason of not having fork at API level. And this structure is like VMS and Mica. Part of this layering could be motivated by early Windows split between DOS and Windows proper, but as Grant explained, VMS influence was stronger.

IIUC early NT developement was part of joint IBM-Microsoft effort to create OS/2, so clearly DOS and Windows influence were limited. Only later Microsoft decided to merge classic Windows and NT and effectively abandon other system iterfaces than Windows API.

DOS and Windows were a relevant part of OS/2 development too. Both IBM and MS were fully aware that if OS/2 and/or NT were to succeed, compatibility with existing software was essential. But more than that, compatibility with existing software /developers/ was essential.

But you are absolutely right that the NT kernel was originally intended to support different API's or "personalities" (I think that was the term used) - at least WinAPI, OS/2 and POSIX. It was also the intention that the OS/2 kernel would be similarly flexible, so that users could pick their base system and run all sorts of different software on top. IBM and MS worked together for interoperability. Having at least minimal support for "fork" would have been necessary (along with things like case-sensitive filename support).

However, it did not take long for MS to realise that they could stab IBM in the back and take everything for themselves - through a mixture of technical, economic, legal and illegal shenanigans, they killed off OS/2 as an OS and as an API, and dropped everything but the WinAPI interface. (As you point out, much of that was at a higher level than the kernel itself.)

There's a lot of interesting detail here from you and Grant, which I appreciate. However, we've strayed a long way from the OP's original question and topic, and it's not really about embedded systems any more. I hope Pozz got what he needed before we drifted!

I converted to the "recursive make considered harmful" group long ago. Having one makefile for the whole build makes it possible to have dependencies crossing directories, and gives better performance in parallel builds - with recursive make, the overhead for entering/exiting directories and waiting for sub-makes to finish piles up. If a compile takes 30 minutes on a fast 16-cpu machine, that does make a difference.

using ninja instead of make works even better in such a scenario.

cu Michael

Ninja works great, but I don't think you should write ninja scripts yourself. cmake can be used to generate ninja files - that's what I currently use for my ARM projects, this works great.

cu Michael

I have the same experience, about 20 years ago - the company was using a cygwin-based cross-gcc + make (I think some old borland make) on windows. I converted the makefiles to use GNU make on linux, and compile time was half that of the windows setup. That speed advantage was enough to (very) slowly convert colleagues to use Linux.

cu Michael

I fully agree with the points in "recursive make considered harmful", which I also read long ago. But that does not mean that recursive make can't be used well - it just means you have to use it appropriately, and carefully.

In particular, using one "outer" make to run make on makefiles in different directories is asking for trouble - you can easily get dependencies wrong or miss cross-directory dependencies. And it is often difficult to figure out what is happening if something fails in one of the builds. And with older makes (from the days when that paper was written), there was no inter-make job server meaning you either had to give each submake too few parallel jobs (and thus wait for some to finish), or too many (and slow the system down).

The way I use recursive makes is /really/ recursive - the main make (typically split into a few include makefiles for convenience, but only one real make) handles everything, and it does some of that be calling /itself/ recursively. It is quite common for me to build multiple program images from one set of source - perhaps for different variants of a board, with different features enabled, and so on. So I might use "make prog=board_a" to build the image for board a, and "make prog=board_b" for board b. Each build will be done in its own directory

- builds/build_a or builds/build_b. Often I will want to build for both boards - then I will do "make prog="board_a board_b"" (with a default setting for the most common images).

These different boards can require different settings for compiler flags, directories, and various other options. Rather than having to track multiple sets of variables in the makefiles for when multiple board images are being handled within the one make, I have a far simpler solution - if there is more than one image being build, I simply spin off recursive makes for each build - thus after "make prog="board_a board_b"", I start "make prog=board_a" and "make prog=board_b". Each make instance lives on its own, so I only need one set of flags at a time, compiling into one build directory - but they share a job server, and all the dependencies are correct.

It is not the only way to handle such things, but it is definitely a convenient and efficient method.

I support a product (ARM w/ RTOS) for which we put together an SDK that allowed customers to write custom firmware. The SDK was available for Windows+Cygwin and Linux. We had a half-dozen customers actually use the SDK to write custom firmware. They all chose to go the Windows+Cygwin Route. A few of them ended up maintaining their firmware for a fairly long period of time. Eventually, keeping Cygwin working on the customers machines, and the SDK working on Cygwin became too much hassle. We pointed them to instructions on installing Ubuntu on a VM inside Windows.

There were all amazed at

  1. How much less work installing Linux was than installing and troubleshooting Cygwin.
  2. How much faster a build ran under a Linux VM on Windows than it did under Cygwin on Windows.
  3. How convenient it was to be able to just archive the VM image so that the next time they needed to modify the firmware all they had to do was plop the VM image on whatever host machine they had handy.

Previously, they always seemed to lose track of their Windows/Cygwin development machine and would have to reinstall Cygwin and the SDK every time they wanted to change something (changes were usually several years apart).

So we stopped supporting the Cygwin version of the SDK. There are couple customers that are still maintaining their custom firmware after 20 years. I believe they've figured out how share a directory between Windows and the Linux VM, so they do all of their editing under Windows, and then just do a "make" in the Linux VM, then use tools under Windows to install/deploy the firmware. I told them they could even run the "make" via ssh from whatever Windows IDE/editor thingy they were using so that it could parse the make output and do nice IDE type stuff with it, but I don't know if they ever did that.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required