J'ai jeté un coup d'œil à l'entrée d'appel système. (analyse des sources Linux)

Nombre de versions à étudier

linux-4.2(mainline)

D'où lisez-vous?

Vraiment gros. Au début, je ne sais pas où lire.

J'ai réfléchi à l'endroit où le lire, mais je pense que l'entrée depuis un fork ou un exec est plus susceptible de se propager que de démarrer. Je veux dire, je me demande si je veux lire d'ici. J'ai donc décidé de commencer par découvrir ce que dit l'entrée système. Lorsque j'ai cherché sur le net, j'ai trouvé le site suivant.

La table d'entrée des appels système semble être sys_call_table. Définition de sys_call_table


#undef __SYSCALL
#define __SYSCALL(nr, call) [nr] = (call),
void *sys_call_table[NR_syscalls] = {
        [0 ... NR_syscalls-1] = sys_ni_syscall,
#include <asm/unistd.h>
};     

Le contenu de la table d'entrée est dans #include <asm / unistd.h>. Regarde ça. Avant cela, dans la définition de sys_call_table, il y a [0 ... NR_syscalls-1] = sys_ni_syscall,. Aussi __SYSCALL (nr, call) [nr] = (call),. Cela devrait être le code qui initialise / définit les éléments du tableau. Expérimentons avec un petit exemple de code simple.

#include <stdio.h>

#define TBLSIZ  10
int     tbl[TBLSIZ] = {
	[0 ... TBLSIZ-1] = 123,
	[7] = 777,
};
 
int main()
{
	int     i, c = sizeof(tbl) / sizeof(tbl[0]);
	for (i = 0; i < c; i++) {
		printf("tbl[%d]=%d\n", i, tbl[i]);
	}
}      

Le résultat de l'exécution est le suivant.


kou77@ubuntu:~/test$ gcc tes015.c
kou77@ubuntu:~/test$ ./a.out
tbl[0]=123
tbl[1]=123
tbl[2]=123
tbl[3]=123
tbl[4]=123
tbl[5]=123
tbl[6]=123
tbl[7]=777
tbl[8]=123
tbl[9]=123

sys_call_table est un tableau de void *, mais comme la valeur du paramètre est facile à comprendre dans l'exemple de code, j'en ai fait un tableau de int. En conséquence, comme je m'y attendais. (L'index 7 contient 777, sinon 123 est inclus)

Pour revenir à l'enquête, reportez-vous à la définition de #include <asm / unistd.h>. On peut s'attendre à ce que chaque entrée utilise la macro __SYSCALL.

Si vous essayez de vérifier la définition de asm / unistd.h, vous trouverez de nombreux fichiers asm / unistd.h dans le dossier arch.

./arch/unicore32/include/uapi/asm/unistd.h
./arch/powerpc/include/asm/unistd.h
./arch/powerpc/include/uapi/asm/unistd.h
./arch/tile/include/asm/unistd.h
./arch/tile/include/uapi/asm/unistd.h
./arch/nios2/include/uapi/asm/unistd.h
./arch/openrisc/include/uapi/asm/unistd.h
./arch/microblaze/include/asm/unistd.h
./arch/microblaze/include/uapi/asm/unistd.h
./arch/arm/include/asm/unistd.h
./arch/arm/include/uapi/asm/unistd.h
./arch/c6x/include/uapi/asm/unistd.h
./arch/xtensa/include/asm/unistd.h
./arch/xtensa/include/uapi/asm/unistd.h
./arch/parisc/include/asm/unistd.h
./arch/parisc/include/uapi/asm/unistd.h
./arch/mips/include/asm/unistd.h
./arch/mips/include/uapi/asm/unistd.h
./arch/x86/include/asm/unistd.h
./arch/x86/include/uapi/asm/unistd.h
./arch/m32r/include/asm/unistd.h
./arch/m32r/include/uapi/asm/unistd.h
./arch/h8300/include/uapi/asm/unistd.h
./arch/s390/include/asm/unistd.h
./arch/s390/include/uapi/asm/unistd.h
./arch/hexagon/include/uapi/asm/unistd.h
./arch/mn10300/include/asm/unistd.h
./arch/mn10300/include/uapi/asm/unistd.h
./arch/metag/include/asm/unistd.h
./arch/metag/include/uapi/asm/unistd.h
./arch/avr32/include/asm/unistd.h
./arch/avr32/include/uapi/asm/unistd.h
./arch/sparc/include/asm/unistd.h
./arch/sparc/include/uapi/asm/unistd.h
./arch/sh/include/asm/unistd.h
./arch/sh/include/uapi/asm/unistd.h
./arch/blackfin/include/asm/unistd.h
./arch/blackfin/include/uapi/asm/unistd.h
./arch/m68k/include/asm/unistd.h
./arch/m68k/include/uapi/asm/unistd.h
./arch/frv/include/asm/unistd.h
./arch/frv/include/uapi/asm/unistd.h
./arch/score/include/uapi/asm/unistd.h
./arch/arm64/include/asm/unistd.h
./arch/arm64/include/uapi/asm/unistd.h
./arch/cris/include/asm/unistd.h
./arch/cris/include/arch-v32/arch/unistd.h
./arch/cris/include/uapi/asm/unistd.h
./arch/cris/include/arch-v10/arch/unistd.h
./arch/ia64/include/asm/unistd.h
./arch/ia64/include/uapi/asm/unistd.h
./arch/alpha/include/asm/unistd.h
./arch/alpha/include/uapi/asm/unistd.h
./arch/arc/include/uapi/asm/unistd.h
./include/asm-generic/unistd.h
./include/uapi/asm-generic/unistd.h
./include/uapi/linux/unistd.h

Puisqu'il y a probablement des fichiers dépendant de l'architecture dans arch, je vais les rechercher à partir de fichiers couramment référencés. Tout d'abord, asm / unistd.h est le contenu de la définition de sys_call_table, et il semble que la description utilisant la macro __SYSCALL soit répertoriée. Si vous regardez ./include/asm-generic/unistd.h,

#include <uapi/asm-generic/unistd.h>
#include <linux/export.h>
 
/*
 * These are required system calls, we should
 * invert the logic eventually and let them
 * be selected by default.
 */
#if __BITS_PER_LONG == 32
#define __ARCH_WANT_STAT64
#define __ARCH_WANT_SYS_LLSEEK
#endif 

Continuez avec ./include/uapi/asm-generic/unistd.h et ./include/linux/export.h. ./Include/uapi/asm-generic/unistd.h est un peu volumineux, car les entrées d'appel système doivent être définies. Il y a des définitions telles que les macros dans ./include/linux/export.h, et il ne semble pas y avoir de description du contenu de sys_call_table. En plus de cela, voici un extrait du code en ./include/uapi/asm-generic/unistd.h.

#include <asm/bitsperlong.h>
 
/*
 * This file contains the system call numbers, based on the
 * layout of the x86-64 architecture, which embeds the
 * pointer to the syscall in the table.
 *
 * As a basic principle, no duplication of functionality
 * should be added, e.g. we don't use lseek when llseek
 * is present. New architectures should use this file
 * and implement the less feature-full calls in user space.
 */
 
#ifndef __SYSCALL
#define __SYSCALL(x, y)
#endif
 
#if __BITS_PER_LONG == 32 || defined(__SYSCALL_COMPAT)
#define __SC_3264(_nr, _32, _64) __SYSCALL(_nr, _32)
#else
#define __SC_3264(_nr, _32, _64) __SYSCALL(_nr, _64)
#endif
 
#ifdef __SYSCALL_COMPAT
#define __SC_COMP(_nr, _sys, _comp) __SYSCALL(_nr, _comp)
#define __SC_COMP_3264(_nr, _32, _64, _comp) __SYSCALL(_nr, _comp)
#else
#define __SC_COMP(_nr, _sys, _comp) __SYSCALL(_nr, _sys)
#define __SC_COMP_3264(_nr, _32, _64, _comp) __SC_3264(_nr, _32, _64)
#endif
/*Omis en chemin ...*/
#define __NR_uselib 1077
__SYSCALL(__NR_uselib, sys_uselib)
#define __NR__sysctl 1078
__SYSCALL(__NR__sysctl, sys_sysctl)
 
#define __NR_fork 1079
#ifdef CONFIG_MMU
__SYSCALL(__NR_fork, sys_fork)
#else
__SYSCALL(__NR_fork, sys_ni_syscall)
#endif /* CONFIG_MMU */
/*Ce qui suit est omis ...*/     

Dans la définition de l'entrée fork, nous voyons define dans CONFIG_MMU. Je ne sais pas quelle sera la configuration (make), mais il est peu probable que le fork ne soit pas dans l'entrée d'appel système, donc Pour Linux fonctionnant normalement, CONFIG_MMU peut être défini.

À propos, la définition de sys_ni_syscall est la suivante. kernel\sys_ni.c(14): asmlinkage long sys_ni_syscall(void)

/*
 * Non-implemented system calls get redirected here.
 */
asmlinkage long sys_ni_syscall(void)
{
        return -ENOSYS;
}

Ce qui précède est la fonction qui sera appelée si l'entrée n'est pas implémentée. Vérifions la fonction enregistrée en utilisant l'entrée fork dans l'extrait de code ci-dessus comme exemple.

Définition de sys_fork

J'ai cherché avec sys_fork, mais je n'ai rien trouvé qui semble être une définition de fonction.

kou77@ubuntu:~/linux-4.2$ find . \( -name \*.c -o -name \*.h \) -exec grep 'sys_fork' {} /dev/null \;
./arch/openrisc/include/asm/syscalls.h:asmlinkage long __sys_fork(void);
./arch/openrisc/include/asm/syscalls.h:#define sys_fork __sys_fork
./arch/mips/kernel/syscall.c:save_static_function(sys_fork);
./arch/x86/um/sys_call_table_64.c:#define stub_fork sys_fork
./arch/x86/include/generated/asm/syscalls_32.h:__SYSCALL_I386(2, sys_fork, stub32_fork)
./arch/sparc/kernel/process_32.c: *       sys_fork invocation and when we reach here
./arch/arm64/include/asm/unistd32.h:__SYSCALL(__NR_fork, sys_fork)
./include/linux/syscalls.h:asmlinkage long sys_fork(void);
./include/uapi/asm-generic/unistd.h:__SYSCALL(__NR_fork, sys_fork)

Il y a une définition sous arc, mais il n'y a pas de définition du corps de fonction. l'arche sera différente. À la suite d'une recherche, pensant que cela n'est probablement pas directement visible avec les macros, etc., ce qui suit a été trouvé. ./kernel/fork.c:SYSCALL_DEFINE0(fork)

L'ensemble de cette définition

#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
        return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
        /* can not support in nommu mode */
        return -EINVAL;
#endif
}
#endif 

La définition de la macro SYSCALL_DEFINE0 est la suivante. ./include/linux/syscalls.h:178:#define SYSCALL_DEFINE0(sname) \

#define SYSCALL_DEFINE0(sname)                                  \
        SYSCALL_METADATA(_##sname, 0);                          \
        asmlinkage long sys_##sname(void)      

Sauf pour SYSCALL_METADATA, la partie asmlinkage long sys _ ## sname (void) est En prenant SYSCALL_DEFINE0 (fork) comme exemple, il remplace asmlinkage long sys_fork (void). Enfin, j'ai trouvé la définition de la fonction sys_fork. asmlinkage peut être appelé et référencé à partir d'une source écrite dans le compilateur C une fois compilé en C ++. Cela semble être une description qui permet d'écrire le symbole. La définition de asmlinkage est la suivante.

./tools/lib/lockdep/uinclude/linux/lockdep.h:13:#define asmlinkage
./arch/x86/include/asm/linkage.h:10:#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
./arch/mn10300/include/asm/linkage.h:15:#define asmlinkage
./arch/ia64/include/asm/linkage.h:6:#define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))
./include/linux/linkage.h:21:#define asmlinkage CPP_ASMLINKAGE

Jugé que la définition de ./include/linux/linkage.h est généralement valide. Pour x86, la définition de #define asmlinkage CPP_ASMLINKAGE attribute ((regparm (0))) peut être valide. Je ne sais pas. regparm semble passer des arguments sous forme de registres. (Vérifions séparément)

La définition de CPP_ASMLINKAGE est la suivante.

#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif 

code fourche (corps)

Jetons un coup d'œil au code du corps principal de fork. La définition de _do_fork est la suivante. ./kernel/fork.c:1679:long _do_fork(unsigned long clone_flags,

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long _do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr,
              unsigned long tls)
{
        struct task_struct *p;
        int trace = 0;
        long nr;
 
        /*
         * Determine whether and which event to report to ptracer.  When
         * called from kernel_thread or CLONE_UNTRACED is explicitly
         * requested, no event is reported; otherwise, report if the event
         * for the type of forking is enabled.
         */
        if (!(clone_flags & CLONE_UNTRACED)) {
                if (clone_flags & CLONE_VFORK)
                        trace = PTRACE_EVENT_VFORK;
                else if ((clone_flags & CSIGNAL) != SIGCHLD)
                        trace = PTRACE_EVENT_CLONE;
                else
                        trace = PTRACE_EVENT_FORK;
 
                if (likely(!ptrace_event_enabled(current, trace)))
                        trace = 0;
        }
 
        p = copy_process(clone_flags, stack_start, stack_size,
                         child_tidptr, NULL, trace, tls);
        /*
         * Do this prior waking up the new thread - the thread pointer
         * might get invalid after that point, if the thread exits quickly.
         */
        if (!IS_ERR(p)) {
                struct completion vfork;
                struct pid *pid;
 
                trace_sched_process_fork(current, p);
 
                pid = get_task_pid(p, PIDTYPE_PID);
                nr = pid_vnr(pid);
 
                if (clone_flags & CLONE_PARENT_SETTID)
                        put_user(nr, parent_tidptr);
 
                if (clone_flags & CLONE_VFORK) {
                        p->vfork_done = &vfork;
                        init_completion(&vfork);
                        get_task_struct(p);
                }
 
                wake_up_new_task(p);
 
                /* forking complete and child started to run, tell ptracer */
                if (unlikely(trace))
                        ptrace_event_pid(trace, pid);
 
                if (clone_flags & CLONE_VFORK) {
                        if (!wait_for_vfork_done(p, &vfork))
                                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
                }
 
                put_pid(pid);
        } else {
                nr = PTR_ERR(p);
        }
        return nr;
}

Cela semble certainement être le corps principal. Dans cet article, l'analyse des fourches n'est pas l'objectif, je n'irai donc pas plus loin. Fork n'a pas d'arguments, mais quelle est la fonction enregistrée dans l'entrée de l'appel système qui a un argument?

Prenons un échantillon de setgid, qui semble être une implémentation relativement simple.

/*
 * setgid() is implemented like SysV w/ SAVED_IDS
 *
 * SMP: Same implicit races as above.
 */
SYSCALL_DEFINE1(setgid, gid_t, gid)
{  
/*Ce qui suit est omis ...*/    
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)    
#define SYSCALL_DEFINEx(x, sname, ...)                          \
        SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \
        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)       
#define __SYSCALL_DEFINEx(x, name, ...)                                 \
        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \
                __attribute__((alias(__stringify(SyS##name))));         \
        static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__));  \
        asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__));      \
        asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__))       \
        {                                                               \
                long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));  \
                __MAP(x,__SC_TEST,__VA_ARGS__);                         \
                __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));       \
                return ret;                                             \
        }                                                               \
        static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))  

C'est moelleux. Pour écrire brièvement dans la plage examinée, dans le cas de setgid, la macro est développée avec le sentiment suivant.

--Définissez SyS_setgid avec un autre nom pour sys_setgid. sys_setgid n'a aucune substance. Au lieu de, l'entité SyS_setgid est définie. --SyS_setgid est défini par un alias et ne peut pas être référencé à partir d'autres sources. (La valeur par défaut est faible?) Le nom de sys_setgid peut être référencé à partir d'autres sources. --SYSC_setgid est appelé depuis SyS_setgid, et le contenu de la fonction SYSC_setgid est ./kernel/fork.c: conduit à la définition de SYSCALL_DEFINE0 (fork). Kanji.

Je ne l'ai pas encore regardé en détail, mais la façon de définir __MAP est très intéressante. La mise en œuvre ici est intéressante, je peux donc l'expliquer dans un autre article après avoir un peu plus compris le contenu.

Recommended Posts

J'ai jeté un coup d'œil à l'entrée d'appel système. (analyse des sources Linux)
analyse de la source linux (noyau): appel système
analyse de source linux (noyau): définition de la fonction d'entrée d'appel système