C variables representation in ELF 2 - Relocations
Previously, we've explored program and section headers and typical sections in ELF.
In this post, I'll try to explore the symbols and their relocation according to the symbol table and the assembly.
Previously, we've explored program and section headers and typical sections in ELF.
In this post, I'll try to explore the symbols and their relocation according to the symbol table and the assembly.
Previously, we've used gcc -S to stop assembling and gcc -c to stop linking, and explored C variables representation in assembly(gcc -S) and object(gcc -c).
The symbols in the intermediate object file (vars-section.o) are waiting to be relocated and resolved to determine their actual virtual address. It's the linker's turn.
After linking, all object files (*.o) are linked into one ELF. By default, it links dynamically and the type of outcome is DYN (position-independent executable file).
Compared to gcc -S, gcc -c will assemble and stop linking, and generate intermediate object file, see REL ELF Walkthrough.
Object files (usually ended with .o) contain machine instructions that are in principle executable by the processor.
Then we can use objdump to display information from object files and explore what's in them.
In this article I'll try to find out what C vars(symbols) are in the assembly generated by gcc -S.
The "happens before" relation is the only possible way to reason about timing between different threads. It is only established through synchronization that uses either atomic objects or very specific C library functions.
An atomic object can be used to synchronize two threads, if one thread writes a value and another thread reads the value that was written. Operations on atomics are guaranteed to be locally consistent.
Sequential consistency is the default consistency model for atomics, but not for other C library functions. It additionally assumes that all corresponding synchronization events are totally ordered.
With extended asm you can read and write C variables from assembler and perform jumps from assembler code to C labels.
volatile (computer programming) - Why is volatile needed in C?
volatile in C actually came into existence for the purpose of not caching the values of the variable automatically. It will tell the compiler not to cache the value of this variable. So it will generate code to take the value of the given volatile variable from the main memory every time it encounters it. This mechanism is used because at any time the value can be modified by the OS or any interrupt. So using volatile will help us accessing the value afresh every time.
Since pointers are variables themselves, they can be stored in arrays just as other variables can.
The pointer to a pointer in C is used when we want to store the address of another pointer.
There is yet another construct for which the address-of operator & can be used: functions.
A function pointer, also called a subroutine pointer or procedure pointer, is a pointer referencing executable code, rather than data. Dereferencing the function pointer yields the referenced function, which can be invoked and passed arguments just as in a normal function call. Such an invocation is also known as an "indirect" call, because the function is being invoked indirectly through a variable instead of directly through a fixed identifier or address.
Function pointers allow different code to be executed at runtime. They can also be passed to a function to enable callbacks.
The advantage of using function pointers is that multiple modules implementing the same function can be identified together, making maintenance easier and the system structure clearer. Or to summarise: it is easy to design layers, promotes system abstraction, reduces coupling and separates the interface from the implementation.
Previously, we've discussed const qualifier and Named Constants in C. Today let's go back to the root and have a look at null pointers and pointer validity. After all, everything starts at zero and disappears without a trace.