• neohope
    置顶
    2021-08-17
    三、Demo部分 1、新建一个源码编译目录 mkdir kernelbuild 2、下载源码,解压 wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.10.59.tar.gz tar -xzf linux-5.10.59.tar.gz cd linux-5.10.59 3、清理 make mrproper 4、修改文件 4.1、arch/x86/entry/syscalls/syscall_64.tbl #在440后面增加一行 441 common get_cpus sys_get_cpus 4.2、include/linux/syscalls.h #在最后一个asmlinkage增加一行 asmlinkage long sys_get_cpus(void); 4.3、kernel/sys.c #在最后一个SYSCALL_DEFINE0后面增加下面几行 //获取系统中有多少CPU SYSCALL_DEFINE0(get_cpus) { return num_present_cpus(); } 5、内核配置 make menuconfig make oldconfig 6、修改.config,去掉一个证书 CONFIG_SYSTEM_TRUSTED_KEYS=“” 7、编译 make -j4 8、安装 sudo make modules_install sudo make install 9、测试 #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> int main(int argc, char const *argv[]) { //syscall就是根据系统调用号调用相应的系统调用 long cpus = syscall(441); printf("cpu num is:%d\n", cpus);//输出结果 return 0; } gcc cpus.c -o cpus ./cpus 在没有修改的内核上返回是-1 在修改过的为num_present_cpus数量,我的虚拟机返回的是1
    展开

    作者回复: 66666大佬 大佬

    
    9
  • neohope
    2021-08-17
    二、linux内核部分【下】 2、当产生系统调用时 2.1、应用直接syscall或通过glibc产生了syscall 2.2、cpu会产生类似于中断的效果,开始到entry_SYSCALL_64执行 //文件路径arch/x86/entry/entry_64.S SYM_CODE_START(entry_SYSCALL_64) //省略代码 call do_syscall_64 SYM_CODE_END(entry_SYSCALL_64) //文件路径arch/x86/entry/entry_64.S SYM_CODE_START(entry_SYSCALL_compat) call do_fast_syscall_32 SYM_CODE_END(entry_SYSCALL_compat) 2.3、调用do_syscall_64 #ifdef CONFIG_X86_64 __visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs) { nr = syscall_enter_from_user_mode(regs, nr); instrumentation_begin(); if (likely(nr < NR_syscalls)) { nr = array_index_nospec(nr, NR_syscalls); regs->ax = sys_call_table[nr](regs); } instrumentation_end(); syscall_exit_to_user_mode(regs); } #endif 2.4、根据sys_call_table调用对应的功能函数 sys_call_table[nr](regs) 如果我们传入257,就会调用__x64_sys_openat 如果我们传入441,就会调用__x64_sys_get_cpus 2.5、但咱们实际写的函数sys_get_cpus,好像和实际调用函数__x64_sys_get_cpus,差了一个__x64,这需要一个wrapper arch\x86\include\asm\syscall_wrapper.h #define SYSCALL_DEFINE0(sname) \ SYSCALL_METADATA(_##sname, 0); \ static long __do_sys_##sname(const struct pt_regs *__unused); \ __X64_SYS_STUB0(sname) \ __IA32_SYS_STUB0(sname) \ static long __do_sys_##sname(const struct pt_regs *__unused) #define __X64_SYS_STUB0(name) \ __SYS_STUB0(x64, sys_##name) #define __SYS_STUB0(abi, name) \ long __##abi##_##name(const struct pt_regs *regs); \ ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO); \ long __##abi##_##name(const struct pt_regs *regs) \ __alias(__do_##name); SYSCALL_DEFINE0(get_cpus),会展开成为 __X64_SYS_STUB0(get_cpus) 然后 __SYS_STUB0(x64, sys_get_cpus) 然后 long __x64_sys_get_cpus(const struct pt_regs *regs); 这样前后就对上了,glibc和linux内核就通了。
    展开

    作者回复: 是的

    
    4
  • neohope
    2021-08-17
    一、glibc部分【上】 1、应用程序调用open函数 //glibc/intl/loadmsgcat.c # define open(name, flags) __open_nocancel (name, flags) 2、展开后实际上调用了 __open_nocancel(name, flags) 3、而__open_nocancel 最终调用了INLINE_SYSCALL_CALL //glibc/sysdeps/unix/sysv/linux/open_nocancel.c __open_nocancel(name, flags) ->return INLINE_SYSCALL_CALL (openat, AT_FDCWD, file, oflag, mode); 4、宏展开【理解就好,不保证顺序】 4.1、初始为 INLINE_SYSCALL_CALL (openat, AT_FDCWD, file, oflag, mode); 4.2、第1次展开INLINE_SYSCALL_CALL #define INLINE_SYSCALL_CALL(...) \ __INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__) 展开得到: __INLINE_SYSCALL_DISP(__INLINE_SYSCALL, __VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】) 4.3、第2次展开__INLINE_SYSCALL_DISP #define __INLINE_SYSCALL_DISP(b,...) \ __SYSCALL_CONCAT (b,__INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__) 展开得到: __SYSCALL_CONCAT(b【__INLINE_SYSCALL】,__INLINE_SYSCALL_NARGS(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】))(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】) 4.4、第3次展开__INLINE_SYSCALL_NARGS __INLINE_SYSCALL_NARGS(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】) #define __INLINE_SYSCALL_NARGS(...) \ __INLINE_SYSCALL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,) 展开得到: __INLINE_SYSCALL_NARGS_X(openat, AT_FDCWD, file, oflag, mode,7,6,5,4,3,2,1,0,) 然后展开__INLINE_SYSCALL_NARGS_X #define __INLINE_SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n 展开得到参数个数:4 从而4.4的结果为 __SYSCALL_CONCAT(__INLINE_SYSCALL,4)(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】) 4.5、然后展开__SYSCALL_CONCAT,其实就是字符拼接 __SYSCALL_CONCAT(__INLINE_SYSCALL,4) #define __SYSCALL_CONCAT_X(a,b) a##b #define __SYSCALL_CONCAT(a,b) __SYSCALL_CONCAT_X (a, b) 展开得到: __INLINE_SYSCALL4 从而4.5的结果为 __INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode) 4.6、然后展开INTERNAL_SYSCALL4 #define __INLINE_SYSCALL4(name, a1, a2, a3, a4) \ INLINE_SYSCALL (name, 4, a1, a2, a3, a4) 展开得到: INLINE_SYSCALL(openat, 4, AT_FDCWD, file, oflag, mode)
    展开

    作者回复: 对的 就是这样

    
    3
  • neohope
    2021-08-17
    二、linux内核部分【上】 1、在make时,会通过syscall_64.tbl生成syscalls_64.h,然后包含到syscall_64.c,进行调用号与函数之间的绑定。 arch/x86/entry/syscalls/syscall_64.tbl arch/x86/include/generated/asm/syscalls_64.h arch/x86/entry/syscall_64.c 1.1、以sys_openat为例,在syscall_64.tbl中为 257 common openat sys_openat 441 common get_cpus sys_get_cpus 1.2、make后,在生成的syscalls_64.h中为 __SYSCALL_COMMON(257, sys_openat) 1.3 在syscall_64.c中,展开__SYSCALL_COMMON #define __SYSCALL_COMMON(nr, sym) __SYSCALL_64(nr, sym) 展开就是 __SYSCALL_64(257, sys_openat) 1.4、在syscall_64.c中,第一次展开__SYSCALL_64 #define __SYSCALL_64(nr, sym) extern long __x64_##sym(const struct pt_regs *); #include <asm/syscalls_64.h> #undef __SYSCALL_64 展开就是 extern long __x64_sys_openat(const struct pt_regs *); 也就是每个__SYSCALL_64都展开成了一个外部函数 1.5、在syscall_64.c中,第二次展开__SYSCALL_64 #define __SYSCALL_64(nr, sym) [nr] = __x64_##sym, asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { [0 ... __NR_syscall_max] = &__x64_sys_ni_syscall, #include <asm/syscalls_64.h> }; 展开其实就是指向了外部函数 [257]=__x64_sys_openat, 全部展开结果,都会被包含到sys_call_table中,从而完成了调用号与函数之间的绑定。
    展开

    作者回复: 是的 完全正确

    
    2
  • cugphoenix
    2021-08-14
    看了Linux 系统调用表的生成方式,又刷新了对宏定义的认知,灵活性极强,用起来可太花哨了

    作者回复: 是的

    
    2
  • Zhendicai
    2021-08-13
    int指令和syscall指令都会发生特权级切换吧,但是syscall能直接调定位到具体系统调用函数,int则需要经过中断门描述符表和分发器才行。int的步骤要多一些,那就是取指令次数要多一些?还有个问题使用int来执行系统调用是不是也会遇到中断优先级的问题?

    作者回复: 是的正确

    
    1
  • 凉人。
    2021-08-13
    搬了下答案 1.syscall syscall是x64的系统调用。其调用号通过rax进行传递。查看具体的调用号,linux环境下在unistd.h中定义。如果是64位,则可以查看/usr/include/asm/unistd_64.h,如果是32位,则查看/usr/include/unistd_32.h。 参数传递:处于用户态时,参数传递顺序为:rdi,rsi,rdx,rcx,r8,r9,处于内核态时,参数传递顺序:rdi,rsi,rdx,r10,r8,r9(补充:这是看别人文章是这么写的,但是我在实际操作中发现,我运行用户态汇编代码,通过rcx传递参数时函数返回错误,用ida查看,发现用户态的值传递其实是:rdi,rsi,rdx,r10,r8,r9(和前面提到的内核态的一致),内核的值传递我没有进行测试,欢迎各位大佬评论区补充) 2.int 80h int 80h 是32位x86的系统调用方式。同样通过ax传递调用号,参数传递顺序是:ebx,ecx,edx,esi,edi *** note *** intel体系的系统调用限制最多六个参数,没有任何一个参数是通过栈传递的。系统调用的返回结果存放在ax寄存器中,且只有整型和内存型可以传递给内核
    展开

    作者回复: 是的 正确

    
    1
  • 吴建平
    2022-03-29
    有个疑问,最后验证的应用例子,怎么链接到系统调用的呢,没看到链接过程,默认链接glibc么?

    作者回复: 要编译内核的

    
    
  • 罗 乾 林
    2021-08-13
    因为这里用到的指令是最新处理器为其设计的系统调用指令 syscall。这个指令和 int 指令一样,都可以让 CPU 跳转到特定的地址上,只不过不经过中断门,系统调用返回时要用 sysexit 指令

    作者回复: 对的

    
    
  • pedro
    2021-08-13
    实验太顶了,有时间一定搞一下,今天问题属于知识盲区,贴上链接:https://blog.csdn.net/sdulibh/article/details/50890250 不学习就变废物😂

    作者回复: 哈哈 这有点费时 没什么 难度

    
    