I'm back! As I said before in previous article, we need to figure out what process does context switching in linux kernel consists of, to incorporate the system call switching process into kernel.
We need the pointer of system call table in the PCB so that each process can have different system call table. Linux has a structure called task_struct that contains all the information about a processor. I will just add a field that stores the pointer of a system call table.
task_struct is defined in include/linux/sched.h :
struct task_struct {
...
// system call table structure
unsigned long sys_call_tbl;
/*
* New fields for task_struct should be added above here, so that
* they are included in the randomized portion of task_struct.
*/
randomized_struct_fields_end
/* CPU-specific state of this task: */
struct thread_struct thread;
/*
* WARNING: on x86, 'thread_struct' contains a variable-sized
* structure. It *MUST* be at the end of 'task_struct'.
*
* Do not put anything below here!
*/
};
Somehow Linux violently coughed blood and died when I added a field at the front of the structure, so I just put it at the end of the structure(before the "danger zone").
Now that we have the space to point the table, we need to make kernel initialize this field also. For initial value of sys_call_tbl, I just made sys_tbl_register_systbls() function set a default system call table with some rudimentary functions.
/* dynamic_sys_call_table.h */
void sys_tbl_set_default_table(sys_call_ptr_t *table);
sys_call_ptr_t *sys_tbl_get_default_table(void);
/* dynamic_sys_call_table.c */
static sys_call_ptr_t *default_sys_call_tbl;
...
void sys_tbl_set_default_table(sys_call_ptr_t *table) { default_sys_call_tbl = table; }
sys_call_ptr_t *sys_tbl_get_default_table(void) { return default_sys_call_tbl; }
arch/x86/entry/syscall_64.c (Architecture-dependent function)
void sys_tbl_register_systbls(void) {
sys_tbl_add(sys_call_table_linux , "linux");
sys_tbl_add(sys_call_table_other , "mysyscalltable");
// set Linux's system call table as a default table
sys_tbl_set_default_table(sys_call_table_linux);
}
Upon researching about the initialization of a process, I found out that processes in Linux are created in very interesting way. The only way of creating a process is by calling fork() function which creates a new duplicate of the process. Specifically, fork() function copies the "parent" process and create a child process out of it. So, to make a process from an executable file, you need to call fork() to create the child and also call execve() to load the program into the child's memory(that sounds weird.) (This means that execve() function does Not create a new process but just load the program to the memory. Interesting!)
We need to figure out how fork() initializes the process to make our kernel also initialize the system call table field.
According to this book, fork works by these steps :
We just casually insert this code to dup_task_struct() in fork.c :
...
#include <linux/dynamic_sys_call_table.h>
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
...
// initialize system call table
tsk->sys_call_tbl = (unsigned long)sys_tbl_get_default_table();
...
}
...
I was kinda panicked because kernel just died of exception, but turns out I forgot to add this logical equation.. -_-
Sometimes I doubt whether I'm a normal human being
This is just simple. Find the context switching function and just insert code.
I made this function that switches the global system call table for context switching.
extern asmlinkage sys_call_ptr_t *sys_call_table;
sys_call_ptr_t *sys_tbl_set_global_table(sys_call_ptr_t *new_table) {
sys_call_ptr_t *old_table = sys_call_table;
sys_call_table = new_table;
return old_table;
}
Now we just use this function in switch_context() function in kernel/sched/core.c :
...
#include <linux/dynamic_sys_call_table.h>
/*
* context_switch - switch to the new MM and the new thread's register state.
*/
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
...
/* Set the global system call table to new task's table */
if(next->sys_call_tbl != 0x00) {
sys_tbl_set_global_table((sys_call_ptr_t *)next->sys_call_tbl);
}
...
}
...
Now we should set the system call table of the process by the table corresponding to their binary loader. I should figure out in what process does execve() function modify the memory of the processor so that we also modify the system call table of the process. (It shouldn't be that hard)
We need to focus on binary loader from now on!