Locating Members of C Language Structures in the Kernel

When examining kernel code, there is a small trick regarding the C language that can be shared to facilitate code reading.

1. Directly Finding the Parent Pointer of a Structure

We know that kernel header files define structures, and when defining structures, the first item of important structures is typically set as a sub-structure. As shown in the example below:

struct __drm_planes_state {
        struct drm_plane *ptr;
        struct drm_plane_state *state, *old_state, *new_state;
};

Here we have a C language knowledge point as follows:

The address of struct drm_plane *ptr is the address of struct __drm_planes_state

In other words, if we are continuously operating on the ptr pointer, I can actually interact with the struct __drm_planes_state pointer at any time, as shown in the pseudocode below:

struct __drm_planes_state* stat = (struct __drm_planes_state*) &ptr;
if(stat->state){
        ......
}

It is crucial to understand that the ptr pointer must be a member of __drm_planes_state and cannot be a pointer value constructed and assigned from elsewhere. An incorrect example is as follows:

struct drm_plane *p1 = ptr;
stat = (struct __drm_planes_state*) &p1;

Here, we cannot directly take the address of p1 because it is not a member of __drm_planes_state; it is merely a regular stack address.

1.1 Test Program

#include <stdio.h>
#include <stdlib.h>

struct lower {
        int a;
};

struct upper  {
        struct lower *k;
        int b;
};

int main()
{
        struct upper *u = malloc(sizeof(struct upper));
        struct lower *l = u->k;
        u->b = 2;

        struct upper* t1 = (struct upper*)&u->k;
        struct upper* t2 = (struct upper*)&l;
        printf("t1=%p t2=%p t1->b=%d \n", t1, t2, t1->b);
}

Here we constructed an upper structure, set the value of member b to 2, then extracted t1 and t2, and printed the addresses, resulting in the following output:

# gcc test.c -o test && ./test
t1=0x558beacb70 t2=0x7ffd087628 t1->b=2

Notice that t1 is likely in the heap address range, while t2 is in the stack range. We can directly find b through t1, which has a value of 2.

Locating Members of C Language Structures in the Kernel

1.2 Summary

Here we learned a common kernel technique where we can often see direct type casting to obtain the pointer to the parent structure, making it very convenient for understanding kernel logic.

In the future, when looking at kernel code, such operations will not require pausing to think.

2. Finding the Parent Pointer of a Structure Using container_of

If you frequently read code, you may notice that the kernel is filled with numerous container_of functions. The purpose of this function is:

Input a member variable entity, a parent structure type, and a structure member variable declaration, output the parent structure pointer

Why is there such a function? Based on the previous discussion, we can easily raise a question:

If I want to find the parent structure pointer, then my member must be the first member, which is quite inconvenient. If this member is not the first, what good methods do we have to find the parent structure pointer?Regarding this, we need to prepare two knowledge points:

  1. Structure members are stored sequentially in memory
  2. The parent structure pointer can be located to the structure member using →

Based on this, it is easy to come up with the idea:

I know the address of the structure member, and then I can calculate how much offset I had before; directly subtracting this offset from my pointer gives me the address of the parent pointer.Thus, we start to analyze the macro definition of container_of as follows:

#define container_of(ptr, type, member) ({                              
        void *__mptr = (void *)(ptr);                                   
        ((type *)(__mptr - offsetof(type, member))); })

We also need to look at the definition of offsetof, which we can find in the kernel header files:

#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)

Here is a trick: using the 0 address for type casting and then accessing the member of the type allows us to obtain the offset of the member in the structure, which can then be forcibly type cast to size_t for pointer subtraction.

2.1 Test Program

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

#ifndef offsetof
#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)
#endif

#define container_of(ptr, type, member) ({                              
        void *__mptr = (void *)(ptr);                                   
        ((type *)(__mptr - offsetof(type, member))); })

struct lower {
        int a;
};

struct upper  {
        int c,d,e,f;
        struct lower *k;
        int b;
};

int main()
{
        struct upper *u = malloc(sizeof(struct upper));

        // struct upper* t = ({ void *__mptr = (void *)(&u->k); ((struct upper *)(__mptr - ((size_t)&((struct upper *)0)->k))); });
        struct upper* t = container_of(&u->k, struct upper, k);

        printf("t=%p up=%p k=%p\n", t, u, &u->k);
}

Here we pass the address of u->k, the definition of the upper structure, and the member k, allowing us to obtain the pointer t to upper.

At this point, we run the following:

# gcc test.c -o test && ./test
t=0x559829eb70 up=0x559829eb70 k=0x559829eb80

It can be observed that the addresses of t and up are completely equal, while k is just offset by the address, which is 4*4=16, the space occupied by int c,d,e,f;

Locating Members of C Language Structures in the Kernel

2.2 Summary

Here we learned about the common implementation of container_of in the kernel, which can directly obtain the pointer to the parent structure of a member.

3. Conclusion

Thus, we have learned a small trick for operating structures in the kernel. With this trick, we can easily find structure pointers and members. This is essential knowledge for anyone looking to understand the kernel.

# Building a World-Class Operating System

For more articles, please follow the public account: Qilin Embedded

Leave a Comment