If there’s one thing C is known and (in)famous for, it’s the ease of shooting yourself in the foot with it. And there’s indeed no denying that the freedom C offers comes with the price of making it our own responsibility to tame and keep the language under control. On the bright side, since the language’s flaws are so well known, we have a wide selection of tools available that help us to eliminate the most common problems and blunders that could come back to bite us further down the road. The catch is, we have to really want it ourselves, and actively listen to what the tools have to say.
We often look at this from a security point of view and focus on exploitable vulnerabilities, which you may not see as valid threat or something you need to worry about in your project. And you are probably right with that, not every flaw in your code will lead to attackers taking over your network or burning down your house, the far more likely consequences are a lot more mundane and boring. But that doesn’t mean you shouldn’t care about them.
Buggy, unreliable software is the number one cause for violence against computers, and whether you like it or not, people will judge you by your code quality. Just because Linus Torvalds wants to get off Santa’s naughty list, doesn’t mean the technical field will suddenly become less critical or loses its hostility, and in a time where it’s never been easier to share your work with the world, reliable, high quality code will prevail and make you stand out from the masses.
Shared libraries are our best friends to extend the functionality of C programs without reinventing the wheel. They offer a collection of exported functions, variables, and other symbols that we can use inside our own program as if the content of the shared library was a direct part of our code. The usual way to use such libraries is to simply link against them at compile time, and let the linker resolve all external symbols and make sure everything is in place when creating our executable file. Whenever we then run our executable, the loader, a part of the operating system, will try to resolve again all the symbols, and load every required library into memory, along with our executable itself.
But what if we didn’t want to add libraries at compile time, but instead load them ourselves as needed during runtime? Instead of a predefined dependency on a library, we could make its presence optional and adjust our program’s functionality accordingly. Well, we can do just that with the concept of dynamic loading. In this article, we will look into dynamic loading, how to use it, and what to do with it — including building our own plugin system. But first, we will have a closer look at shared libraries and create one ourselves.
But data isn’t the only thing residing in memory. All the program code is accessible through either the RAM or some other executable type of memory, giving each function a specific address inside that memory as entry point. Once again, pointers are simply memory addresses, and to fully utilize this similarity, C provides the concept of function pointers. Function pointers provide us with ways to make conditional code execution faster, implement callbacks to make code more modular, and even provide a foothold into the running machine code itself for reverse engineering or exploitation. So read on!
In general, function pointers aren’t any more mysterious than data pointers: the main difference is that one references variables and the other references functions. If you recall from last time how arrays decay into pointers to their first element, a function equally decays into a pointer to the address of its entry point, with the () operator executing whatever is at that address. As a result, we can declare a function pointer variable fptr and assign a function func() to it: fptr = func;. Calling fptr(); will then resolve to the entry point of function func() and execute it.
Admittedly, the idea of turning a function into a variable may seem strange at first and might require some getting used to, but it gets easier with time and it can be a very useful idiom. The same is true for the function pointer syntax, which can be intimidating and confusing in the beginning. But let’s have a look at that ourselves.
In our first part on pointers, we covered the basics and common pitfalls of pointers in C. If we had to break it down into one sentence, the main principle of pointers is that they are simply data types storing a memory address, and as long as we make sure that we have enough memory allocated at that address, everything is going to be fine.
In this second part, we are going to continue with some more advanced pointer topics, including pointer arithmetic, pointers with another pointer as underlying data type, and the relationship between arrays and pointers. But first, there is one particular pointer we haven’t talked about yet.
The one proverbial exception to the rule that pointers are just memory addresses is the most (in)famous pointer of all: the NULL pointer. Commonly defined as preprocessor macro (void *) 0, we can assign NULL like any other pointer.
Pointers — you either love them, or you haven’t fully understood them yet. But before you storm off to the comment section now, pointers are indeed a polarizing subject and are both C’s biggest strength, and its major source of problems. With great power comes great responsibility. The internet and libraries are full of tutorials and books telling about pointers, and you can randomly pick pretty much any one of them and you’ll be good to go. However, while the basic principles of pointers are rather simple in theory, it can be challenging to fully wrap your head around their purpose and exploit their true potential.
So if you’ve always been a little fuzzy on pointers, read on for some real-world scenarios of where and how pointers are used. The first part starts with regular pointers, their basics and common pitfalls, and some general and microcontroller specific examples.
Programming C without variables is like, well, programming C without variables. They are so essential to the language that it doesn’t even require an analogy here. We can declare and use them as wildly as we please, but it often makes sense to have a little bit more structure, and combine data that belongs together in a common collection. Arrays are a good start to bundle data of the same type, especially when there is no specific meaning of the array’s index other than the value’s position, but as soon as you want a more meaningful association of each value, arrays will become limiting. And they’re useless if you want to combine different data types together. Luckily, C provides us with proper alternatives out of the box.
This write-up will introduce structures and unions in C, how to declare and use them, and how unions can be (ab)used as an alternative approach for pointer and bitwise operations.
[Scott Harden] is working on a research project involving optogenetics. From what we were able to piece together optogenetics is like this: someone genetically modifies a mouse to have cell behaviors which can activated by light sensitive proteins. The mice then have a frikin’ lasers mounted on their heads, but pointing inwards towards their brains not out towards Mr. Bond’s.
Naturally, to make any guesses about the resulting output behavior from the mouse the input light has to be very controlled and exact. [Scott] had a laser and he had a driver, but he didn’t have a controller to fire the pulses. To make things more difficult, the research was already underway and the controller had to be built
The expensive laser driver had a bizarre output of maybe positive 28 volts or, perhaps, negative 28 volts… at eight amps. It was an industry standard in a very small industry. He didn’t have a really good way to measure or verify this without either destroying his measuring equipment or the laser driver. So he decided to just build a voltage-agnostic input on his controller. As a bonus the opto-isolated input would protect the expensive controller.
The output is handled by an ATtiny85. He admits that a 555 circuit could generate the signal he needed, but to get a precision pulse it was easier to just hook up a microcontroller to a crystal and know that it’s 100% correct. Otherwise he’d have to spend all day with an oscilloscope fiddling with potentiometers. Only a few Hackaday readers relish the thought as a relaxing Sunday afternoon.
He packaged everything in a nice project box. He keeps them on hand to prevent him from building circuits on whatever he can find. Adding some tricks from the ham-radio hobby made the box look very professional. He was pleased and surprised to find that the box worked on his first try.