Just disassemble/decompile some random firmware code... you wouldn't believe what one can find from those piles of err... binary dump.
If you know what you're doing (and you should) then C is the best tool for the job.
If we can get tools like Rust which have better memory safety guarantees while offering much of the same performance, then hell, why not? It's easy to see why just by looking at the hundreds of CVE's throughout the years, often caused by memory unsafe operations.
Not that I don't like C, but there's many better alternatives out there.
And this is coming from someone who stubbornly sticks to writing D in C-style, but knows what Rust brings to the table.
...and C is currently the best choice for systems like this because of reasons not primarily to do with the language.
Currently, if you suggest to use a language other than C you will be laughed out because the only available embedded guys are C based. Yes, momentum counts and C has massive momentum.
There are also issues such as toolchains and tooling, familiarity, community, and of course the massive existing set of libraries, codebases and knowledge.
Basically, C is 'good enough'. (although Rust is interesting and on the horizon, its a long way off yet).
Although technically a better language, C's shortcomings are greatly exaggerated, for example memory management. In general this is not an issue in bare metal embedded systems as you don't have a heap, everything is statically allocated by design. There are many flaws in the C language (as in every language), but in day-to-day use, they are very easy to manage.
So... don't believe the (bad) hype.
Social and economic factors that we C opponents say are why it remains and is often the pragmatic choice. These in no way show C language itself was well-designed, superior, etc. Just show how going mainstream can make things more practical. They did a great job on that part.
TBH, my ideal language for this job would probably be Rust, but that is just not a mature enough ecosystem currently.
Don't get me wrong, I would consider myself a C proponent currently, but I would love to see change in a Rust-like direction.
Despite you classing yourself as a C-opponent, I suspect we agree more than disagree.
Btw, what do you think of a reboot of Modula-2, Modula-3, or just Ada/SPARK? Your opinion on them would be interesting given your background. Closest thing to Modula's that's actively developed is Astrobe Oberon for embedded.
I started off with Turbo Pascal before graduating to Turbo C, so I do have a fondness for the Pascal family of languages. I also love type-safety.... TBH, I would prefer a hypothetical "fix" for the C language that improved type strictness/safety over "fixes" for resource/memory management issues which I consider should be bread and butter for any software engineer. So in that, I do not like large runtimes or garbage collection, more visibility to what the machine is really doing is needed. GC systems are also not predictable enough, deterministic runtime is absolutely essential to making reliable software.
I have poked my nose around Modula-2 in the past, but C took over for me personally. ADA I consider to be far to heavyweight for anything serious (and thats after being in a company that used it extensively in the defence world, tho I never had to touch it myself)
Currently Rust is my best hope for the future.... but C has survived well IMO.
For me a kind of escape path from C, back when I was into Turbo Pascal, was to move to Turbo C++. I only got to use Turbo C around one year before getting to learn about this new cool language called C++.
It allowed me to keep some of the Turbo Pascal safety around.
What are your approaches when using plain C, coming from "so I do have a fondness for the Pascal family of languages. I also love type-safety" ?
My solutions were:
- Use translation units as TP units, only exposing functions and struct accessors (macros if function calls were too expensive)
- Always make use of debug functions to validate pointers in debug builds (e.g. _malloc_dbg, _CrtIsValidPointer on Windows)
- make use of const as much as possible
- write my own wrappers around strncpy and friends
- compile warnings as errors
- if given the option, just use C++ instead and prefer library types to inherited C ones (string, array, references, RAII).
I'll be the first to admit that compatibility with an ecosystem, even if flawed, is important. (I work on web browsers, after all!) We can't change overnight, or even in two or three years. But we'll never get to a better future if we don't take the steps to start now.
Empirically, nobody knows what they're doing, if "knowing what you're doing" is defined as "writing large-scale C code without memory safety problems".
I hear a lot from C and C++ enthusiasts that there are lots of programmers out there who always write correct C and C++ code, and therefore we don't need new languages. But I've never found one of those programmers. Can you name one?
Basically, in day-to-day use, the flaws in the language are easily manageable. Resource (memory) management is a skill that should be a given.
Yes, there are well publicised failures in this regard, but consider the number of problems versus the actual number of lines of code written.
I think that's a pretty misleading metric when you take into account: (1) widely used codebases constantly increase in size constantly; (2) it only takes one exploitable mistake for an attacker to achieve remote code execution. One exploitable vulnerability per 10,000 lines means 1,200 remote code execution flaws in a codebase the size of Chromium.
I have worked with many who write large-scale C and C++ code with no undefined behavior. Is that what you meant?
I've never seen it. All large-scale C and C++ codebases I've ever seen, from large companies to small ones, have had memory safety/undefined behavior problems [1]. If "just hire better programmers" were a workable solution, surely one company out of {Google, Apple, Microsoft, Facebook} would have succeeded at that strategy by now.
It's very easy to write C and C++ code that looks like it's free of undefined behavior, but in every case I've seen they end up falling when attackers actively try to look for problems.
[1]: Maybe qmail is the one exception, though even that had a famous debate related to overflow.
Because I have, and it works incredibly well.
Of course, the caveat is that once you turn up the static analysis aggressiveness (assuming you use a good static analysis tool), you will need to put in plenty of assert(index < len), ownership annotations, and other such items in your code to satisfy the checks.
But once you do, it's really hard (probably not impossible, but really hard) to trigger undefined behavior without the static analysis tool catching it.
Because if the tool decides it can't prove there's no undefined behavior, it is configured to complain, and you adjust the code until it stops.
I'm not saying many teams do this, but I am disputing that no teams do this.
Btw, what utility tool are you licensing? What makes it irreplaceable? I know of only one in this field that I truly couldn't replace for compilers or static analysis. Even then, there's quite a few that handle the job well enough to not need it. So, I don't use it.
My hope is to some day open source our internal tool, but we have to wait until the OSS features we need catch up. Until then, we aren't willing to pay the high license fee to redistribute this utility freely.
I'm speaking a little cryptically because I'd rather not have this tied directly to any particular company (neither mine nor the one we license this code from). I'm not speaking on their behalves officially; only my own.
Coverity is actually used on browser engines, and it has not been able to stem the tide of exploitable security vulnerabilities. Sound static analysis is just too difficult on idiomatic C and C++: it's effectively impossible, as the language was just not designed for it.
If coverity isn't catching the bugs and they are undefined behavior bugs then coverity needs to be more aggressive.
But it probably doesn't support things like ownership annotations, and it probably gives up strict checking in order to avoid false positives.
The problem is developers. Developers are lazy. Most developers won't even turn on compiler warnings, never mind using the sanitizers.
So tool makers create sub-par tools to cater to them. Fewer false warnings means more customers, even if it also means fewer security holes.
It's why we use an in house tool and crank up the checks to 11.
If you watch Herb Sutter's talk at CppCon 2015, at a given point he asks the audience how many know and use such tools.
It is one of the most important C++ conferences, usually attended by the most savvy C++ developers in the world, the amount of the audience saying that they do use such tools was ruffly 1%!
1% shows how much the majority of C and C++ developers, or their employers, care about writing proper safe code in those languages.
The only way out is moving to programming languages whose safety must be explicitly turned off, and not having to be explicitly turned on, because most won't bother taking the effort to turning it on.
What I'm disputing is that no teams write good C or C++ code.
In fact, the 1% who raised their hands proves my point. It isn't 0%.
That's all I'm saying. Don't smear our names with the "all C and C++ developers write code with exploitable undefined behavior bugs in it" when you mean "most C and C++ developers write code with exploitable undefined behavior bugs in it".
Once upon a time I gave C++ class to first year students at the university where I took my degree. Worked with C++ at some well known companies and research institutions.
Still, I won't say I never write exploitable undefined behaviour free code in C++, let alone C. Because I cannot control the code that gets used by third party libraries, written by team members or even having the whole UB use cases on my head.
If you rely on binary dependencies which have quality problems, then u can't help you there. But that could be an attack vector in any language.
And if you are worried about the operating system itself, then there really is no easy way around it unless you want to run a unikernel for everything.
And even then you may hit a CPU microcode bug it hardware bug...
I hope to never own a microwave oven with a large scale codebase. Also my basement dehumidifier. And my waffle iron. And my clothes washer. And my digital dial caliper in my workroom.
There's a surprising amount of money in "you are now a timer" and "you are now a thermostat" and "you are now a thermometer" and very close analogies.
You'll definitely use or own something that has a somewhat large codebase.
That is both reliability and return on investment. :)
So, when we use evidence instead of feelings, what you said is a myth that's been debunked repeatedly in many ways for decades. And, yet, people repeat it. No, C use usually results in problems that safer, systems languages before and after it had less or none of. Despite professionals using it. The solid code is always an outlier.
Most amusing thing is Thompson and Ritchie, with help from Pike, later designed what they thought was perfect language with perfect set of features. The result, Go, was basically like Algol subsets that preceded C mixed with Pascal developed around same time. But, C compiled and ran on a PDP-11 fast. So, it's the best. ;)
And I am not claiming that C is the best tool for the job because of the language.... its because of the 'environmental' factors, human resources, community, momentum, codebases, portability, etc.
I whole-heartedly agree that there are better languages out there, but the fact remains that the flaws in C are easily managed.
It would also be interesting to produce a study of the use of C specifically in embedded systems where the style of code is very much different than large desktop systems (i.e. much less heap usage and dynamic memory allocation for example).
I agreed with you on that in another comment. Yep. It's also why NASA often uses it. The tooling covers dark corners pretty well these days.
"It would also be interesting to produce a study of the use of C specifically in embedded systems where the style of code is very much different than large desktop systems (i.e. much less heap usage and dynamic memory allocation for example)."
I'd love to replicate the old studies on modern languages with modern tooling for both systems programming and the embedded subsets like MISRA. We'd definitely learn some stuff from that. Further, I'd like to see specific metrics like in Ada/SPARK/CbyC example below on where defects were introduced or corrected with each technique and phase of lifecycle. Would tell us how low-level features, subset rules, and tooling interact with accurate assessment of how much problems they pose for real instead of in theory.
http://www.sis.pitt.edu/jjoshi/Devsec/CorrectnessByConstruct...
Modern embedded C programming has evolved significantly in the right direction since studies were done and so I would love to see someone spend the time looking at 'modern' embedded C codebases.
Static analysis is now widespread and not theoretical, coding standards now take security seriously (tho can be outdated). Drawbacks to certain techniques have been surfaced and recognised. Tooling is much better, portability is recognised as a good-thing, pointer usage is now minimised when possible and contained to areas where they are appropriate. Casting is frowned upon, macro-magic is frowned upon. 'Clever' code is frowned upon.
Basically, I would think that certainly in the embedded domain, standards are now such that the story would now be significantly different.
Dont trust just one.
I was hoping for something new.
I'll look into those.
Saturn http://saturn.stanford.edu/pages/overviewindex.html
CSolve http://goto.ucsd.edu/csolve/
Calysto http://www.domagoj-babic.com/index.php/ResearchProjects/Caly...
Most academics are instead making compilers that translate C code into something mostly or totally safe while pushing the performance hit downward. Here's two of the top for you to try on various codes (or improve):
At least if you use SSE2/FP/etc., you better ensure those FPU registers are saved and restored. But probably you don't want to do that in a IRQ handler! Save only SSE2 registers on an AVX system (higher 128 bits will be zeroed for currently executing usermode thread!) and receive "interesting" bug reports from the end users.
If you refer to a vm page that's not present... well, bad things might happen.
Etc.
It has been my experience that people who don't know or use the C stdlib end up unknowingly re-implementing stdlib functions over and over again.