Linux Kernel Learning Notes

Linux Kernel Learning Notes

This article is an excellent piece from the Kanxue Forum.

Kanxue Forum Author ID: e*16 a

The following is based on the code of linux0.11.

1

Kernel's Five Major Structures

Linux Kernel Learning Notes

2

Interrupt Workflow

1. ARM Memory

(1) Convert CPU working mode

(2) Copy and stack registers

(3) Set interrupt vector table

(4) Save the return value of the normally running function

(5) Jump to the corresponding interrupt service function to run

(6) Restore mode and registers

(7) Jump back to the address of the normally working function to continue running

2. Interrupt Workflow in Linux

(1) Push all register values onto the stack

(2) Push the exception number onto the stack (interrupt number)

(3) Push the return address of the current function onto the stack

(4) Call the interrupt function

(5) Pop the return address from the stack

(6) Pop register values from the stack

3. Interrupt Source Code

Processing before and after the interrupt, execution of the interrupt

Processing of hardware interrupts: asm.s trap.c

Processing of software and system calls: system_call.s fork.c/signal.c/exit.c/sys.c

① asm.s code and trap.c analysis (OPENING)

② system_call.s code and fork.c/signal.c/exit.c/sys.c analysis

(1) fork.c

In system_call.s there exists a fork system call, first call _find_empty_process, then call _copy_process.

.align 2_sys_fork:    call _find_empty_process    testl %eax,%eax    js 1f    push %gs    pushl %esi    pushl %edi    pushl %ebp    pushl %eax    call _copy_process    addl $20,%esp1:    ret
#include <errno.h>#include <linux/sched.h>#include <linux/kernel.h>#include <asm/segment.h>#include <asm/system.h> extern void write_verify(unsigned long address); long last_pid=0; void verify_area(void * addr,int size){    unsigned long start;     start = (unsigned long) addr;    size += start & 0xfff;    start &= 0xfffff000;    start += get_base(current->ldt[2]);    while (size>0) {        size -= 4096;        write_verify(start);        start += 4096;    }} int copy_mem(int nr,struct task_struct * p){    unsigned long old_data_base,new_data_base,data_limit;    unsigned long old_code_base,new_code_base,code_limit;     code_limit=get_limit(0x0f);    data_limit=get_limit(0x17);    old_code_base = get_base(current->ldt[1]);    old_data_base = get_base(current->ldt[2]);    if (old_data_base != old_code_base)        panic("We don't support separate I&D");    if (data_limit < code_limit)        panic("Bad data_limit");    new_data_base = new_code_base = nr * 0x4000000;    p->start_code = new_code_base;    set_base(p->ldt[1],new_code_base);    set_base(p->ldt[2],new_data_base);    if (copy_page_tables(old_data_base,new_data_base,data_limit)) {        free_page_tables(new_data_base,data_limit);        return -ENOMEM;    }    return 0;} /* *  Ok, this is the main fork-routine. It copies the system process * information (task[nr]) and sets up the necessary registers. It * also copies the data segment in its entirety. */int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,        long ebx,long ecx,long edx,        long fs,long es,long ds,        long eip,long cs,long eflags,long esp,long ss){    struct task_struct *p;   // Create child process's task_struct structure    int i;    struct file *f;     p = (struct task_struct *) get_free_page();    if (!p)        return -EAGAIN;    task[nr] = p;   // Store the child process in the task list    *p = *current;    /* NOTE! this doesn't copy the supervisor stack */    // Begin setting the contents of the structure    p->state = TASK_UNINTERRUPTIBLE;    p->pid = last_pid;    p->father = current->pid;    p->counter = p->priority;    p->signal = 0;    p->alarm = 0;    p->leader = 0;        /* process leadership doesn't inherit */    p->utime = p->stime = 0;    p->cutime = p->cstime = 0;    p->start_time = jiffies;    p->tss.back_link = 0;    p->tss.esp0 = PAGE_SIZE + (long) p;    p->tss.ss0 = 0x10;    p->tss.eip = eip;    p->tss.eflags = eflags;    p->tss.eax = 0;    p->tss.ecx = ecx;    p->tss.edx = edx;    p->tss.ebx = ebx;    p->tss.esp = esp;    p->tss.ebp = ebp;    p->tss.esi = esi;    p->tss.edi = edi;    p->tss.es = es & 0xffff;    p->tss.cs = cs & 0xffff;    p->tss.ss = ss & 0xffff;    p->tss.ds = ds & 0xffff;    p->tss.fs = fs & 0xffff;    p->tss.gs = gs & 0xffff;    p->tss.ldt = _LDT(nr);    p->tss.trace_bitmap = 0x80000000;    if (last_task_used_math == current)        __asm__("clts ; fnsave %0"::"m" (p->tss.i387));  // If the parent process used the coprocessor, settings need to be made in the TSS segment    if (copy_mem(nr,p)) {  // Memory copy        task[nr] = NULL;        free_page((long) p);        return -EAGAIN;    }    for (i=0; i<NR_OPEN;i++)        if (f=p->filp[i])            f->f_count++;    if (current->pwd)        current->pwd->i_count++;    if (current->root)        current->root->i_count++;    if (current->executable)        current->executable->i_count++;    set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,& (p->tss));    set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,& (p->ldt));    p->state = TASK_RUNNING;    /* do this last, just in case */    return last_pid; } int find_empty_process(void){    int i;     repeat:        if ((++last_pid)<0) last_pid=1;        for(i=0 ; i<NR_TASKS ; i++)            if (task[i] && task[i]->pid == last_pid) goto repeat;    for(i=1 ; i<NR_TASKS ; i++)        if (!task[i])              return i;    return -EAGAIN;}

① Find an empty process slot in the task list to store

② Create a task_struct for the process

③ Set the task_struct

(2) signal.c

This is just a simple analysis, for detailed analysis please refer to Chapter 5.

#include <linux/sched.h>#include <linux/kernel.h>#include <asm/segment.h> #include <signal.h> volatile void do_exit(int error_code); int sys_sgetmask(){    return current->blocked;} int sys_ssetmask(int newmask){    int old=current->blocked;     current->blocked = newmask & ~(1<<(SIGKILL-1));    return old;} static inline void save_old(char * from,char * to){    int i;     verify_area(to, sizeof(struct sigaction));    for (i=0 ; i< sizeof(struct sigaction) ; i++) {        put_fs_byte(*from,to);        from++;        to++;    }} static inline void get_new(char * from,char * to){    int i;     for (i=0 ; i< sizeof(struct sigaction) ; i++)        *(to++) = get_fs_byte(from++);} int sys_signal(int signum, long handler, long restorer){    struct sigaction tmp;     if (signum<1 || signum>32 || signum==SIGKILL)        return -1;    tmp.sa_handler = (void (*)(int)) handler;    tmp.sa_mask = 0;    tmp.sa_flags = SA_ONESHOT | SA_NOMASK;    tmp.sa_restorer = (void (*)(void)) restorer;   // Set the sigaction structure    handler = (long) current->sigaction[signum-1].sa_handler;    current->sigaction[signum-1] = tmp; // Update the current process's corresponding signal structure    return handler; // Return the handler function} int sys_sigaction(int signum, const struct sigaction * action,    struct sigaction * oldaction){    struct sigaction tmp;     if (signum<1 || signum>32 || signum==SIGKILL)        return -1;    tmp = current->sigaction[signum-1];    get_new((char *) action,        (char *) (signum-1+current->sigaction));    if (oldaction)        save_old((char *) &tmp,(char *) oldaction);    if (current->sigaction[signum-1].sa_flags & SA_NOMASK)        current->sigaction[signum-1].sa_mask = 0;    else        current->sigaction[signum-1].sa_mask |= (1<<(signum-1));    return 0;} void do_signal(long signr,long eax, long ebx, long ecx, long edx,    long fs, long es, long ds,    long eip, long cs, long eflags,    unsigned long * esp, long ss){    unsigned long sa_handler;    long old_eip=eip;    struct sigaction * sa = current->sigaction + signr - 1;    int longs;    unsigned long * tmp_esp;     sa_handler = (unsigned long) sa->sa_handler;    if (sa_handler==1)        return;    if (!sa_handler) {        if (signr==SIGCHLD)            return;        else            do_exit(1<<(signr-1));    }    if (sa->sa_flags & SA_ONESHOT)        sa->sa_handler = NULL;    *(&eip) = sa_handler;    longs = (sa->sa_flags & SA_NOMASK)?7:8;    *(&esp) -= longs;    verify_area(esp,longs*4);    tmp_esp=esp;    put_fs_long((long) sa->sa_restorer,tmp_esp++);    put_fs_long(signr,tmp_esp++);    if (!(sa->sa_flags & SA_NOMASK))        put_fs_long(current->blocked,tmp_esp++);    put_fs_long(eax,tmp_esp++);    put_fs_long(ecx,tmp_esp++);    put_fs_long(edx,tmp_esp++);    put_fs_long(eflags,tmp_esp++);    put_fs_long(old_eip,tmp_esp++);    current->blocked |= sa->sa_mask;}

3. sa_restorer

/* If there is no mask, use this function as the restore function */sig_restore:    addl $4,%esp        /* Discard signr */    popl %eax        /* Restore system call return value to eax */    popl %ecx        /* Restore ecx, edx */    popl %edx    popfl            /* Restore eflags */    ret /* If there is a mask, use this function */masksig_restore:    addl $4,%esp    call ssetmask        /* Set signal mask */    addl $4,%esp        /* Discard mask */    popl %eax    popl %ecx    popl %edx    popfl    ret
6

File System

As the name suggests, it is a system composed of files, and in Linux, everything is a file, so the file system occupies a significant part of the kernel.

Linux boot process:

① After the PCB is powered on, the uboot initializes the board, then migrates the Linux kernel to memory for execution;② The Linux kernel performs initialization operations, mounting the first application, which is the root file system (linuxrc);③ The root file system provides disk management services (glibc, device nodes, configuration files, application shell commands).

1. Overview of File System

The file system mainly includes four parts: buffer management, low-level file operations, data access to files, and high-level access control for files.

(1) Low-level Functions of the File System

① bitmap.c

The program includes functions for releasing and occupying inode bitmaps and logical block bitmaps. The functions for operating inode bitmaps are free_inode() and new_inode(), while the functions for operating logical block bitmaps are free_block() and new_block().

② truncate.c

The program includes a function truncate() to truncate the length of a data file to 0, which sets the length of the file specified by the inode to 0 and releases the logical blocks occupied by the file data.

③ inode.c

The program includes functions for allocating inodes (iget()) and releasing memory inode access (iput()), as well as a function (bmap()) to obtain the logical block number corresponding to the file data block on the device based on inode information.

④ namei.c

The program mainly includes the function namei(), which uses iget(), iput(), and bmap() to map a given file path name to its inode.

⑤ super.c

The program is specifically used to handle the file system superblock, including functions such as get_super(), put_super(), and free_super(), as well as several file system loading/unloading handling functions and system calls like sys_mount().

(2) Data Access Operations in Files

① block_dev.c

The functions block_read() and block_write() in the program are used to read and write data from special files of block devices, with parameters specifying the device number, starting address, and length to access.

② file_dev.c

The functions file_read() and file_write() in the program are used to access general files, with parameters specifying the corresponding inode and file structure.

③ pipe.c

The file implements the pipe read and write functions (read_pipe() and write_pipe()), as well as the system call for creating unnamed pipes (pipe()).

④ char_dev.c

The system calls using read() and write() will call the rw_char() function in char_dev.c for operations. Character devices include console terminals, serial terminals, and memory character devices.

(3) System Calls for File and Directory Management

① open.c

The file implements system calls related to file operations, mainly for creating, opening, and closing files, modifying file owners and attributes, and changing file access permissions and operation times.

② exec.c

The program implements the loading and execution of binary executable files and shell script files, mainly through do_execve(), which is the C handling function called by the system interrupt call (int 0x80) for the function number __NR_execve(), and is the main implementation function for the exec() function family.

③ fcntl.c

Implements the system calls for file control operations (fcntl()) and two file handle (descriptor) copy system calls (dup() and dup2()), where dup2() specifies the new handle’s value, while dup() returns the smallest unused handle. The handle copy operation is mainly used in file standard input/output redirection and pipe operations.

④ ioctl.c

The file implements the input/output control system call (ioctl()), mainly calling the tty_ioctl() function to control terminal I/O.

⑤ stat.c

The file is used to implement the system calls for obtaining file status information, stat() and fstat(). stat() retrieves information using the file name, while fstat() retrieves information using the file handle.

2. Buffer Management (buffer.c)

The buffer is located between the kernel code and the main memory area, acting as a bridge between block devices and other kernel programs. If other kernel programs need to access data in block devices, they must operate through the buffer.

Linux Kernel Learning Notes

Kanxue ID: e*16 a

https://bbs.kanxue.com/user-home-922338.htm

*This article is original by Kanxue Forum e*16 a, please indicate the source from Kanxue Community when reprinting.Linux Kernel Learning Notes

# Previous Recommendations

1.CVE-2022-21882 Privilege Escalation Vulnerability Learning Notes

2.wibu Certificate – A Preliminary Exploration

3.Reverse Engineering APIC Interrupt and Experiments in Win10 1909

4.Analysis and Simulation of EAF Mechanism Under EMET

5.SQL Injection Learning Share

6.Issues and POCs of V8 Array.prototype.concat Function

Click on “Read Original” to learn more!

Leave a Comment