How to navigate C source code? - c

I was writing much java code lately and got quite used to IDE features like jumping to definitions.
Now I'm working on C (reading some kernel code) and wonder if there is any similar tooling for that language, too.
I am aware of ctags, cscope, and lxr, but they seem to rely much on pattern matching and do not really understand the code.
For example I have some code using struct file, how do I navigate to the definition of that struct in order to determine the available fields?
Even if I know that it is defined in fs.h and it contains a field of type fmode_t defined in types.h as unsigned __bitwise__, is there a way to get the semantics of the single bits? (Probably by finding the FMODE_* constants in fs.h again.)
Is this kind of (eclipse-like) direct navigating possible, or do I really have to do much guessing if I do not know all of this before?

If you like eclipse try nsight. It's for cuda, but I think it gives you what you want.

Try QtCreator for the Linux kernel. I wrote how to use it here.
It is the best IDE for Linux kernel development I have ever seen.

Related

Cross-Platform C single header file and multiple implementations

I am working on an open source C driver for a cheap sensor that is used mostly for Arduino projects. The project is set up in such a way that it is possible to support multiple platforms outside the Arduino ecosystem, like the Raspberry Pi.
The project is set up with a platform.h file, with the intention of having different implementations of this header file. Like the example below:
platform.h
platform_arduino.c
platform_rpi.c
platform_windows.c
There is this (Cross-Platform C++ code and single header - multiple implementations) Stack Overflow post that goes fairly in depth in how to handle this for C++ but I feel like none of those examples really apply to this C implementation.
I have come up with some solutions like just adding the requirements for each platform at the top of the file.
#if SOME_REQUIREMENT
#include "platform.h"
int8_t t_open(void)
{
// Implementation here
}
#endif //SOME_REQUIREMENT
But this seems like a clunky solution.
It impacts readability of the code.1
It will probably make debugging conflicting requirements a nightmare.
1 Many editors (Like VS Code) try to gray out code which does not match requirements. While I want this most of the time, it is really annoying when working on cross-platform drivers. I could just disable it for the entirety of the project, but in other parts of the project it is useful. I understand that it could probably be solved using VS Code thing. However, I am asking for alternative methods of selecting the right file/code for the platform because I am interested in seeing what other strategies there are.
Part of the "problem" is that support for Arduino is the primary focus, which means it can't easily be solved with makefile magic. My question is, what are alternative ways of implementing a solution to this problem, that are still readable?
If it cannot be done without makefile magic, then that is an answer too.
For reference, here is a simplified example of the header file and implementation
platform.h
#ifndef __PLATFORM__
#define __PLATFORM__
int8_t t_open(void);
#endif //__PLATFORM__
platform_arduino.c
#include "platform.h"
int8_t t_open(void)
{
// Implementation here
}
this (Cross-Platform C++ code and single header - multiple implementations) Stack Overflow post that goes fairly in depth in how to handle this for C++ but I feel like none of those examples really apply to this C implementation.
I don't see why you say that. The first suggestions in the two highest-scoring answers are variations on the idea of using conditional macros, which not only is valid in C, but is a traditional approach. You yourself present an alternative along these lines.
Part of the "problem" is that support for Arduino is the primary focus, which means it can't easily be solved with makefile magic.
I take you to mean that the approach to platform adaptation has to be encoded somehow into the C source, as opposed to being handled via the build system. Frankly, this is an unusual constraint, except inasmuch as it can be addressed by use of the various system-identification macros provided by C compilers of interest.
Even if you don't want to rely specifically on makefiles, you should consider attributing some responsibility to the build system, which you can do even without knowing specifically what build system that is. For example, you can designate macro names, such as for_windows, etc that request builds for non-default platforms. You then leave it to the person building an instance of the driver to figure out how to configure their tools to provide the appropriate macro definition for their needs (which generally is not hard), based on your build documentation.
My question is, what are alternative ways of implementing a solution to this problem, that are still readable?
If the solution needs to be embodied entirely in the C source, then you have three main alternatives:
write code that just works correctly on all platforms, or
perform runtime detection and adaptation, or
use conditional compilation based on macros automatically defined by supported compilers.
If you're prepared to rely on macro definitions supplied by the user at build time, then the last becomes simply
use conditional compilation
Do not dismiss the first out of hand, but it can be a difficult path, and it might not be fully possible for your particular problem (and probably isn't if you're writing a driver or other code for a freestanding implementation).
Runtime adaptation could be viewed as a specific case of code that just works, but what I have in mind for this is a higher level of organization that performs runtime analysis of the host environment and chooses function variants and internal parameters suited to that, as opposed to those choices being made at compile time. This is a real thing that is occasionally done, but it may or may not be viable for your particular case.
On the other hand, conditional compilation is the traditional basis for platform adaptation in C, and the general form does not have the caveat of the other two that it might or might not work in your particular situation. The level of readability and maintainability you achieve this way is a function of the details of how you implement it.
I have come up with some solutions like just adding the requirements for each platform at the top of the file. [...] But this seems like a clunky solution.
If you must include a source file in your build but you don't want anything in it to actually contribute to the target then that's exactly what you must do. You complain that "It will probably make debugging conflicting requirements a nightmare", but to the extent that that's a genuine issue, I think it's not so much a question of syntax as of the whole different code for different platforms plan.
You also complain that the conditional compilation option might be a practical difficulty for you with your choice of development tools. It certainly seems to me that there ought to be good workarounds for that available from your tools and development workflow. But if you must have a workaround grounded only in the C language, then there is one (albeit a bad one): introduce a level of preprocessing indirection. That is, put the conditional compilation directives in a different source file, like so:
platform.c
#if defined(for_windows)
#include "platform_windows.c"
#else
#if defined(for_rpi)
#include "platform_rpi.c"
#else
#include "platform_arduino.c"
#endif
#endif
You then designate platform.c as a file to be built, but not (directly) any of the specific-platform files.
This solves your tool-presentation issue because when you are working on one of the platform-specific .c files, the editor is unlikely to be able to tell whether it would actually be included in a build or not.
Do note well that it is widely considered bad practice to #include files containing function implementations, or those not ending with an extension conventionally designating a header. I don't say otherwise about the above, but I would say that if the whole platform.c contains nothing else, then that's about the least bad variation that I can think of within the category.

How to put custom DWARF in C resulting binary?

I have two questions:
Is it possible to add custom DWARF on the resulting binary of a C program? (I explain later why i want to do this)
How does DWARF work?
First of all, i don't understand DWARF. I tried to read some docs on dwarfstd.org, but i think it's to high for me. Maybe someone could give me some basic instructions which helps me to dig deeper (the entry point is a bit difficult for me).
Why i want to do this? I like playing around with writing my own compiler, implementing my own language. My goal is to write a compiled language and not an interpreted or jitted one. So i have several options as a backend: C, Opcodes, ASM, LLVM and maybe there are a lot more.
Because LLVM is a C++ library (and i have no clue about C++) i tried it a little bit using the C wrapper. Since i'm a newbie on C too i didn't got it working easily (but i didn't investigate a lot). The problem with Opcodes and ASM is, that the learning curve is higher than LLVM and i'm even more than a newbie on that topic.
So, i would like to use C as a backend... but i think about some problems: Debugging info. The resulting C file would have different function names than my source language and even different line numbers. I know that line numbers could be fixed using the #line directive in C but it's not 100% perfect, though. So i'm looking for a really good solution for this before i start implementing something odd. I stumbled upon DWARF and the i got those question.
If anyone knows a well documented alternative to LLVM which would fit my requirements, your welcome to tell me :)
My requirements for target platform are at least: x86, x64 and ARM

why are there so many versions of header files in my system?

I learned to program with Pascal in high school, and more recently I decided to get out of the sandbox and try to figure out how my computer actually works. So I installed ubuntu on my iMac (i686) and started learning C, which seemed like a good way to get "under the hood."
One of the basic things I'm trying to figure out is where the kernel ends and the standard libraries begin. A book told me that the linux system calls (which I understand to be the interface between the kernel and the libraries) could be found in the header file unistd.h, so this seemed like a good place to start. But when I tried to find the header on my system (using locate unistd.h), I got this result:
/usr/include/unistd.h
/usr/include/asm-generic/unistd.h
/usr/include/i386-linux-gnu/asm/unistd.h
/usr/include/i386-linux-gnu/bits/unistd.h
/usr/include/i386-linux-gnu/sys/unistd.h
/usr/include/linux/unistd.h
/usr/lib/syslinux/com32/include/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/alpha/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/arm/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/avr32/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/blackfin/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/c6x/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/cris/include/arch-v10/arch/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/cris/include/arch-v32/arch/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/cris/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/frv/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/h8300/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/hexagon/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/ia64/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/m32r/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/m68k/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/microblaze/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/mips/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/mn10300/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/openrisc/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/parisc/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/powerpc/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/s390/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/score/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/sh/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/sparc/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/tile/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/unicore32/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/x86/include/asm/ia32_unistd.h
/usr/src/linux-headers-3.5.0-27/arch/x86/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/arch/xtensa/include/asm/unistd.h
/usr/src/linux-headers-3.5.0-27/include/asm-generic/unistd.h
/usr/src/linux-headers-3.5.0-27/include/linux/unistd.h
/usr/src/linux-headers-3.5.0-27-generic/include/linux/unistd.h
Why the heck are there so many versions of this file--and other header files--in my system? Some of them seem to be for other CPUs (like sparc), so why did ubuntu bother to install them on my computer? And how does the all of this fit with what Eric Raymond calls the SPOT rule: "every piece of knowledge must have a single, unambiguous, authoritative representation within a system." (The Art of Unix Programming, p. 91.)
Thanks in advance for any help. I'm happy to read big books if necessary.
I think these header files are directly from linux-3.5.0-27 source code. Ubuntu developers didn't know what kind of target they are dealing with. Maybe Intel x86/powerPC/ or even a mobile hand set(ARM), so they just copy all the head files and make a simple link.

Using ext2 file system variant on Linux

I'm a newbie to kernel programming, and I'm stuck on something, so I'd appreciate some help. I appologize in advance if something similar was asked before, I did not find any relevant post, and could find explanations on the web which were simple enough for someone unexperienced as myself in this field to understand.
I want to experiment with my own version of ext2.
I've got the source files from kernel.org, and made the proper changes. Nothing fancy, just to check something I had in mind.
Now I want to insert it to my linux kernel (ubuntu 2.6.31-14-generic-pae if it matters).
How can I do this?
My (obviously naive) initial thought was to simply use the makefile that comes along with it (after manually setting various flags there so it has obj-m/obj-y where needed) and compile it as a kernel module.
However I keep getting errors during compile time about redifining macros, implicit declarations of functions etc. For example
ext2.h:181:1: warning: "ext2_find_first_zero_bit" redefined
balloc.c:574: error: implicit declaration of function dquot_free_block_nodirty
Obviously this is not the way to go. I guess worst case scenario is compiling the entire kernel again (with my modified ext2 code instead of the original) so it creates the relevant library with my own ext2, and rebooting from the new image. I find it hard to believe this is the best approach.
Is it even possible for a new file system to be inserted as a kernel module?
Myabe I should put my modified ext2 code in /usr/src and somehow compile only the relevant library which contains the current ext2 code?
Anyway, I'd appreciate any help on what should I be doing.
Thank you
Do a search and replace of ext2 with my_awesome_filesystem or some such.

Coming from an OOP background, what would be some C programs/libraries to help me get the "C way"?

I have been doing OOP (C++/Java/PHP/Ruby) for a long time and really have a hard time imagining how large programs and libraries such as Linux or Apache can be written entirely in an imperative style. What would be small open source C projects I could look at to get a feel of how things are done in C?
Bonus points if the project is hosted on GitHub.
Things are done exactly the same way in C, but with less overt support from the language. Instead of creating a class to encapsulate some state, you create a struct. Instead of creating class members, with implicit this parameters, you create functions that you explicitly pass a struct* as the first parameter, that then operate on the struct.
To ensure that encapsulation is not broken you can declare the struct in a header, but only define it in the .c file where it is used. Virtual functions require more work - but again, its just a case of putting function pointers in the struct. Which is actually more convenient in C than C++ because in C you get to fill in your vtables manually, getting quite a fine level of control over which part of code implements part of what COM interface (if you are into COM in C of course).
You might find the ccan (Comprehensive C Archive Network, modeled after Perl's CPAN) interesting.
It's small at the moment, but the contributions are of high quality. Many of the contributions are by linux kernel developers.
Almost everything in there falls into the "few thousand LOC" or less category, too.
If you want a small example to start with, try looking at the source for the basic Linux CLI utilities. GNU binutils, make, or any of the other GNU utilities have full source code available and are relatively small code bases (some are larger than others). The easiest thing is usually to start with a utility that you have used before and are already familiar with.
Look at GLib for an almost canonical example of how to do object oriented programming in C.

Resources