基础知识

学习网址:ctfwiki 安全客

Kernel:又称核心

维基百科:在计算机科学中是一个用来管理软件发出的数据I/O(输入与输出)要求的电脑程序,将这些要求转译为数据处理的指令并交由中央处理器(CPU)及电脑中其他电子组件进行处理,是现代操作系统中最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并由内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的。所以内核通常提供一种硬件抽象的方法,来完成这些操作。有了这个,通过进程间通信机制及系统调用,应用进程可间接控制所需的硬件资源(特别是处理器及IO设备)。

严格地说,内核并不是计算机系统中必要的组成部分。有些程序可以直接地被调入计算机中执行;这样的设计,说明了设计者不希望提供任何硬件抽象和操作系统的支持;它常见于早期计算机系统的设计中。但随着电脑技术的发展,最终,一些辅助性程序,例如程序加载器和调试器,被设计到机器内核当中,或者写入在只读记忆体里。这些变化发生时,操作系统内核的概念就渐渐明晰起来了!



kernel 最主要的功能有两点:

  1. 控制并与硬件进行交互
  2. 提供 application 能运行的环境

权限

intel CPU 将 CPU 的特权级别分为 4 个级别:Ring 0, Ring 1, Ring 2, Ring 3

Ring0 只给 OS 使用,Ring 3 所有程序都可以使用,内层 Ring 可以随便使用外层 Ring 的资源。

Loadable Kernel Modules(LKMs)

LKMs 的文件格式和用户态的可执行程序相同,Linux 下为 ELF,Windows 下为 exe/dll,mac 下为 MACH-O,因此我们可以用 IDA 等工具来分析内核模块。

相关指令

  1. insmod: 讲指定模块加载到内核中
  2. rmmod: 从内核中卸载指定模块
  3. lsmod: 列出已经加载的模块
  4. modprobe: 添加或删除模块,modprobe 在加载模块时会查找依赖关系

syscall

系统调用,指的是用户空间的程序向操作系统内核请求需要更高权限的服务,比如 IO 操作或者进程间通信。系统调用提供用户程序与操作系统间的接口,部分库函数(如 scanf,puts 等 IO 相关的函数实际上是对系统调用的封装 (read 和 write))。

/usr/include/x86_64-linux-gnu/asm/unistd_64.h/usr/include/x86_64-linux-gnu/asm/unistd_32.h 分别可以查看 64 位和 32 位的系统调用号。

ioctl

  1. #include <sys/ioctl.h>
  2. int ioctl(int fd, unsigned long request, ...);

ioctl 也是一个系统调用,用于与设备通信。

int ioctl(int fd, unsigned long request, ...) 的第一个参数为打开设备 (open) 返回的 文件描述符,第二个参数为用户程序对设备的控制命令,再后边的参数则是一些补充参数,与设备有关。

状态切换

user space to kernel space

当发生 系统调用产生异常外设产生中断等事件时,会发生用户态到内核态的切换,具体的过程为:

  1. 通过 swapgs 切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用。
  2. 将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入 rsp/esp。
  3. 通过 push 保存各寄存器值,具体的 代码 如下:
  1. ENTRY(entry_SYSCALL_64)
  2. /* SWAPGS_UNSAFE_STACK是一个宏,x86直接定义为swapgs指令 */
  3. SWAPGS_UNSAFE_STACK
  4. /* 保存栈值,并设置内核栈 */
  5. movq %rsp, PER_CPU_VAR(rsp_scratch)
  6. movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
  7. /* 通过push保存寄存器值,形成一个pt_regs结构 */
  8. /* Construct struct pt_regs on stack */
  9. pushq $__USER_DS /* pt_regs->ss */
  10. pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
  11. pushq %r11 /* pt_regs->flags */
  12. pushq $__USER_CS /* pt_regs->cs */
  13. pushq %rcx /* pt_regs->ip */
  14. pushq %rax /* pt_regs->orig_ax */
  15. pushq %rdi /* pt_regs->di */
  16. pushq %rsi /* pt_regs->si */
  17. pushq %rdx /* pt_regs->dx */
  18. pushq %rcx tuichu /* pt_regs->cx */
  19. pushq $-ENOSYS /* pt_regs->ax */
  20. pushq %r8 /* pt_regs->r8 */
  21. pushq %r9 /* pt_regs->r9 */
  22. pushq %r10 /* pt_regs->r10 */
  23. pushq %r11 /* pt_regs->r11 */
  24. sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
  1. 通过汇编指令判断是否为 x32_abi
  2. 通过系统调用号,跳到全局变量 sys_call_table 相应位置继续执行系统调用。

kernel space to user space

退出时,流程如下:

  1. 通过 swapgs 恢复 GS 值
  2. 通过 sysretq 或者 iretq 恢复到用户控件继续执行。如果使用 iretq 还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等)

struct cred

cred 结构体记录的是关于kernel进程的权限,每个进程中都有一个 cred 结构,这个结构保存了该进程的权限等信息(uid,gid 等),如果能修改某个进程的 cred,那么也就修改了这个进程的权限。

源码 如下:

  1. struct cred {
  2. atomic_t usage;
  3. #ifdef CONFIG_DEBUG_CREDENTIALS
  4. atomic_t subscribers; /* number of processes subscribed */
  5. void *put_addr;
  6. unsigned magic;
  7. #define CRED_MAGIC 0x43736564
  8. #define CRED_MAGIC_DEAD 0x44656144
  9. #endif
  10. kuid_t uid; /* real UID of the task */
  11. kgid_t gid; /* real GID of the task */
  12. kuid_t suid; /* saved UID of the task */
  13. kgid_t sgid; /* saved GID of the task */
  14. kuid_t euid; /* effective UID of the task */
  15. kgid_t egid; /* effective GID of the task */
  16. kuid_t fsuid; /* UID for VFS ops */
  17. kgid_t fsgid; /* GID for VFS ops */
  18. unsigned securebits; /* SUID-less security management */
  19. kernel_cap_t cap_inheritable; /* caps our children can inherit */
  20. kernel_cap_t cap_permitted; /* caps we're permitted */
  21. kernel_cap_t cap_effective; /* caps we can actually use */
  22. kernel_cap_t cap_bset; /* capability bounding set */
  23. kernel_cap_t cap_ambient; /* Ambient capability set */
  24. #ifdef CONFIG_KEYS
  25. unsigned char jit_keyring; /* default keyring to attach requested
  26. * keys to */
  27. struct key __rcu *session_keyring; /* keyring inherited over fork */
  28. struct key *process_keyring; /* keyring private to this process */
  29. struct key *thread_keyring; /* keyring private to this thread */
  30. struct key *request_key_auth; /* assumed request_key authority */
  31. #endif
  32. #ifdef CONFIG_SECURITY
  33. void *security; /* subjective LSM security */
  34. #endif
  35. struct user_struct *user; /* real user ID subscription */
  36. struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
  37. struct group_info *group_info; /* supplementary groups for euid/fsgid */
  38. struct rcu_head rcu; /* RCU deletion hook */
  39. } __randomize_layout;

主要提权手法:

  1. commit_creds(prepare_kernel_cred(0));

关于perpare_kernel_cred函数的定义如下:

  1. /**
  2. * prepare_kernel_cred - Prepare a set of credentials for a kernel service
  3. * @daemon: A userspace daemon to be used as a reference
  4. *
  5. * Prepare a set of credentials for a kernel service. This can then be used to
  6. * override a task's own credentials so that work can be done on behalf of that
  7. * task that requires a different subjective context.
  8. *
  9. * @daemon is used to provide a base for the security record, but can be NULL.
  10. * If @daemon is supplied, then the security data will be derived from that;
  11. * otherwise they'll be set to 0 and no groups, full capabilities and no keys.
  12. *
  13. * The caller may change these controls afterwards if desired.
  14. *
  15. * Returns the new credentials or NULL if out of memory.
  16. *
  17. * Does not take, and does not return holding current->cred_replace_mutex.
  18. */
  19. struct cred *prepare_kernel_cred(struct task_struct *daemon)
  20. {
  21. const struct cred *old;
  22. struct cred *new;
  23. new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
  24. if (!new)
  25. return NULL;
  26. kdebug("prepare_kernel_cred() alloc %p", new);
  27. if (daemon)
  28. old = get_task_cred(daemon);
  29. else
  30. old = get_cred(&init_cred);
  31. validate_creds(old);
  32. *new = *old;
  33. atomic_set(&new->usage, 1);
  34. set_cred_subscribers(new, 0);
  35. get_uid(new->user);
  36. get_user_ns(new->user_ns);
  37. get_group_info(new->group_info);
  38. #ifdef CONFIG_KEYS
  39. new->session_keyring = NULL;
  40. new->process_keyring = NULL;
  41. new->thread_keyring = NULL;
  42. new->request_key_auth = NULL;
  43. new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
  44. #endif
  45. #ifdef CONFIG_SECURITY
  46. new->security = NULL;
  47. #endif
  48. if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
  49. goto error;
  50. put_cred(old);
  51. validate_creds(new);
  52. return new;
  53. error:
  54. put_cred(new);
  55. put_cred(old);
  56. return NULL;
  57. }

注释中已经把函数功能描述得很具体了,简单来说,这个函数主要是生成一个cred结构体,主要根据传入的参数struct task_struct *daemon来确定一些内核服务的credentials,以便于给当前task提供在特定的context执行的权限。

在参数为NULL的情况下,也其实就是理解为把0号进程的task_struct作为参数的情况下,返回一个相应的cred结构体,这个结构体具有最高的root权限。

commit_creds函数定义为:

  1. /**
  2. * commit_creds - Install new credentials upon the current task
  3. * @new: The credentials to be assigned
  4. *
  5. * Install a new set of credentials to the current task, using RCU to replace
  6. * the old set. Both the objective and the subjective credentials pointers are
  7. * updated. This function may not be called if the subjective credentials are
  8. * in an overridden state.
  9. *
  10. * This function eats the caller's reference to the new credentials.
  11. *
  12. * Always returns 0 thus allowing this function to be tail-called at the end
  13. * of, say, sys_setgid().
  14. */
  15. int commit_creds(struct cred *new)
  16. {
  17. struct task_struct *task = current;
  18. const struct cred *old = task->real_cred;
  19. kdebug("commit_creds(%p{%d,%d})", new,
  20. atomic_read(&new->usage),
  21. read_cred_subscribers(new));
  22. BUG_ON(task->cred != old);
  23. #ifdef CONFIG_DEBUG_CREDENTIALS
  24. BUG_ON(read_cred_subscribers(old) < 2);
  25. validate_creds(old);
  26. validate_creds(new);
  27. #endif
  28. BUG_ON(atomic_read(&new->usage) < 1);
  29. get_cred(new); /* we will require a ref for the subj creds too */
  30. /* dumpability changes */
  31. if (!uid_eq(old->euid, new->euid) ||
  32. !gid_eq(old->egid, new->egid) ||
  33. !uid_eq(old->fsuid, new->fsuid) ||
  34. !gid_eq(old->fsgid, new->fsgid) ||
  35. !cred_cap_issubset(old, new)) {
  36. if (task->mm)
  37. set_dumpable(task->mm, suid_dumpable);
  38. task->pdeath_signal = 0;
  39. smp_wmb();
  40. }
  41. /* alter the thread keyring */
  42. if (!uid_eq(new->fsuid, old->fsuid))
  43. key_fsuid_changed(task);
  44. if (!gid_eq(new->fsgid, old->fsgid))
  45. key_fsgid_changed(task);
  46. /* do it
  47. * RLIMIT_NPROC limits on user->processes have already been checked
  48. * in set_user().
  49. */
  50. alter_cred_subscribers(new, 2);
  51. if (new->user != old->user)
  52. atomic_inc(&new->user->processes);
  53. rcu_assign_pointer(task->real_cred, new);
  54. rcu_assign_pointer(task->cred, new);
  55. if (new->user != old->user)
  56. atomic_dec(&old->user->processes);
  57. alter_cred_subscribers(old, -2);
  58. /* send notifications */
  59. if (!uid_eq(new->uid, old->uid) ||
  60. !uid_eq(new->euid, old->euid) ||
  61. !uid_eq(new->suid, old->suid) ||
  62. !uid_eq(new->fsuid, old->fsuid))
  63. proc_id_connector(task, PROC_EVENT_UID);
  64. if (!gid_eq(new->gid, old->gid) ||
  65. !gid_eq(new->egid, old->egid) ||
  66. !gid_eq(new->sgid, old->sgid) ||
  67. !gid_eq(new->fsgid, old->fsgid))
  68. proc_id_connector(task, PROC_EVENT_GID);
  69. /* release the old obj and subj refs both */
  70. put_cred(old);
  71. put_cred(old);
  72. return 0;
  73. }

从注释里也可以看到,这个函数的功能就是给当前task写入新的cred的结构体,从而改变了当前task的权限。

配合通过prepare_kernel_cred(0)得到的root权限的cred结构体,从而赋予当前task同样的root权限,这样就完成了提权。

内核态函数

相比用户态库函数,内核态的函数有了一些变化

  • printf() -> printk(),但需要注意的是 printk() 不一定会把内容显示到终端上,但一定在内核缓冲区里,可以通过 dmesg 查看效果

  • memcpy() -> copy_from_user()/copy_to_user()

    • copy_from_user() 实现了将用户空间的数据传送到内核空间
    • copy_to_user()实现了将内核空间的数据传送到用户空间

    Copy_to_user( to, &from, sizeof(from)

    To:用户空间函数 (可以是数组)

    From:内核空间函数(可以是数组)

    sizeof(from):表示从用户空间想内核空间拷贝数据的字节数。

    Copy_from_user(&from , to , sizeof(to) )

    To:用户空间函数 (可以是数组)

    From:内核空间函数(可以是数组)

    sizeof(from):内核空间要传递的数组的长度

    成功返回0,失败返回失败数目。

  • malloc() -> kmalloc(),内核态的内存分配函数,和 malloc() 相似,但使用的是 slab/slub 分配器

  • free() -> kfree(),同 kmalloc()

另外要注意的是,kernel 管理进程,因此 kernel 也记录了进程的权限。kernel 中有两个可以方便的改变权限的函数:

  • int commit_creds(struct cred *new)
  • struct cred* prepare_kernel_cred(struct task_struct* daemon)

从函数名也可以看出,执行 commit_creds(prepare_kernel_cred(0)) 即可获得 root 权限,0 表示 以 0 号进程作为参考准备新的 credentials。

更多关于 prepare_kernel_cred 的信息可以参考 源码

执行 commit_creds(prepare_kernel_cred(0)) 也是最常用的提权手段,两个函数的地址都可以在 /proc/kallsyms 中查看(较老的内核版本中是 /proc/ksyms)。

  1. post sudo grep commit_creds /proc/kallsyms
  2. [sudo] m4x 的密码:
  3. ffffffffbb6af9e0 T commit_creds
  4. ffffffffbc7cb3d0 r __ksymtab_commit_creds
  5. ffffffffbc7f06fe r __kstrtab_commit_creds
  6. post sudo grep prepare_kernel_cred /proc/kallsyms
  7. ffffffffbb6afd90 T prepare_kernel_cred
  8. ffffffffbc7d4f20 r __ksymtab_prepare_kernel_cred
  9. ffffffffbc7f06b7 r __kstrtab_prepare_kernel_cred

一般情况下,/proc/kallsyms 的内容需要 root 权限才能查看

Mitigation

canary, dep, PIE, RELRO 等保护与用户态原理和作用相同

  • smep: Supervisor Mode Execution Protection,当处理器处于 ring0 模式,执行 用户空间 的代码会触发页错误。(在 arm 中该保护称为 PXN)
  • smap: Superivisor Mode Access Protection,类似于 smep,通常是在访问数据时。
  • mmap_min_addr:

CTF kernel pwn 相关

给定的文件

一般会给三个或者四个文件:

  1. boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
  2. bzImage: kernel binary(打包的内核代码,可以用来寻找gadget)
  3. rootfs.cpio: 文件系统映像
  4. file.ko: 有bug的程序,可以用ida打开

qemu 启动的参数:

  • -initrd rootfs.cpio,使用 rootfs.cpio 作为内核启动的文件系统
  • -kernel bzImage,使用 bzImage 作为 kernel 映像
  • -cpu kvm64,+smep,设置 CPU 的安全选项,这里开启了 smep
  • -m 64M,设置虚拟 RAM 为 64M,默认为 128M 其他的选项可以通过 --help 查看。
  • -initrd 设置根文件系统
  • -vnc :2, 打开一个vnc,这样可以通过vncviewer访问 localhost:2看到VM的控制台
  • -S 表示启动后就挂起,等待gdb连接
  • -s 是-gdb :1234的缩小,就是打开1234这个gdb调试端口
  • -net nic 表示为虚拟机创建一个虚拟网卡
  • -net user 表示QEMU使用user模式
  • -append kernel的启动参数,后面最好用引号()引起来。
    • root=/dev/sda 告诉qemu单板运行内核镜像路径(指定根文件系统的挂载点,是在QEMU Guest OS上的位置)
    • console=ttyS0 告诉内核vexpress单板运行,串口设备是哪个tty。这个值可以从生成的.config文件CONFIG_CONSOLE宏找到。
    • nokaslr 是传递给内核的参数。表示禁用kaslr(Kernel Address Space Layout Randomization) 。(kaslr:kernel加载到内存后他的地址会进行随机化)
    • rw 文件系统读写权限,这里是可读可写。其他可选值有ro
    • oops linux内核的行为不正确,并产生了一份相关的错误日志
    • panic 操作系统在监测到内部的致命错误,并无法安全处理此错误时采取的动作。
  • -nographic 不使用图形化界面,只使用串口
  • -netdev user 配置内部用户网络,与其它任何vm和外部网络都不通,属于宿主host和qemu内部的网络通道。

Linux内核模块的若干知识

1.fop结构体

内核模块程序的结构中包括一些callback回调表,对应的函数存在一个file_operations(fop)结构体中,这也是对我们pwn手来说最重要的结构体;结构体中实现了的回调函数就会静态初始化上函数地址,而未实现的函数,值为NULL。

2.proc_create创建文件

3.数据的通信

小知识

查看装载驱动

  1. lsmod

查看所开保护

  1. cat /proc/cpuinfo

Kaslr 地址随机化

Smep 内核态不可执行用户态代码

Smap 内核态不可访问用户态内存

查看内核堆块

  1. cat /proc/slabinfo

查看prepare_kernel_cred和commit_creds地址

  1. grep prepare_kernel_cred /proc/kallsyms
  2. grep commit_creds /proc/kallsyms
  3. cat /proc/kallsyms | grep prepare_kernel_cred
  4. cat /proc/kallsyms | grep commit_creds

主要提权手法:

  1. 1 commit_creds(prepare_kernel_cred(0));
  2. 2 修改cred结构体

文件压缩命令:

  1. 打包操作:
  2. find . | cpio -o -H newc | gzip > ${myDIR}/initramfs.cpio.gz
  3. 或者 find . | cpio -o --format=newc > rootfs.cpio
  4. 解包操作:
  5. gunzip initramfs.cpio.gz
  6. cpio -idmv < initramfs.cpio

gdb调试:

  1. 添加-gdb tcp::1234
  2. set architecture i386:x86-64
  3. target remote:1234
  4. 下断
  5. c
  6. 运行.exp
  7. 调试

调试:

现在startvm.sh中添加-gdb tcp::1234(记得在上一行末添加/)

1、编译exp.c文件:musl-gcc -static -O2 exp.c -o exp

2、通过python solution.py连接和传送脚本。

3、打开gdb,设置x86_64架构:set architecture i386:x86-64

​ 连接gdb startvm.sh:target remote : 1234

4、gdb中下断,c,在连接的startvm中运行:./exp

查看startvm.sh文件:

loglevel调大 可以查看更多数据 nokaslr关闭保护

monitor 增加逃逸难度

cores=2,threads=2可能为double fetch

修改root.sh中的 -initrd initramfs.cpio 为-initrd root.cpio

修改root.cpiod的setsid cttyhack setuidgid 0100 sh为setsid cttyhack setuidgid 0000 sh

向startvm.sh 中添加-gdb tcp::1234添加gdb调试

对于exp文件:

solution.py脚本打包(包含远程连接、编译、加解密、加权限等)

  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # vim:fenc=utf-8
  4. #
  5. # Copyright 2019 saltedfish <17302010022@fudan.edu.cn>
  6. #
  7. # Distributed under terms of the MIT license.
  8. """
  9. """
  10. from pwn import *
  11. import sys
  12. import os
  13. context.log_level = 'debug'
  14. cmd = '$ '
  15. def exploit(r):
  16. r.sendlineafter(cmd, 'stty -echo') #关闭回显
  17. os.system('musl-gcc -static -O2 exp.c -o exp') #静态编译
  18. os.system('gzip -c exp > exp.gz') #压缩打包
  19. r.sendlineafter(cmd, 'cat <<EOF > exp.gz.b64') #heredoc
  20. r.sendline((read('exp.gz')).encode('base64')) #cat为可见字符,exp.gz多不可见字符,base64转换为可见字符
  21. r.sendline('EOF') #base64加密后可能会出错,出现中断
  22. r.sendlineafter(cmd, 'base64 -d exp.gz.b64 > exp.gz') #base64解码
  23. r.sendlineafter(cmd, 'gunzip exp.gz') #解压
  24. r.sendlineafter(cmd, 'chmod +x ./exp') #加权限
  25. r.interactive()
  26. io=process('startvm.sh')
  27. #io = remote('0.0.0.0',10001)
  28. exploit(io)
  29. #exploit(process('startvm.sh'))

kernel base的更多相关文章

  1. arm linux kernel 从入口到start_kernel 的代码分析

    参考资料: <ARM体系结构与编程> <嵌入式Linux应用开发完全手册> Linux_Memory_Address_Mapping http://www.chinaunix. ...

  2. qemu 模拟-arm-mini2440开发板-启动u-boot,kernel和nfs文件系统

    qemu 本文介绍了如何编译u-boot.linux kernel,然后用qemu启动u-boot和linux kernel,达到与开发板上一样的学习效果! 虽然已经买了2440开发板,但是在实际学习 ...

  3. qemu 模拟-arm-mini2440开发板-启动u-boot,kernel和nfs文件系统【转】

    转自:http://www.cnblogs.com/riskyer/p/3366001.html qemu 本文介绍了如何编译u-boot.linux kernel,然后用qemu启动u-boot和l ...

  4. linux kernel mini2440 start.S head-common.S 部分注释

    内核版本:2.6.32.2(mini2440光盘源码) github地址:https://github.com/guanglun/mini2440_uboot_linux (for_len分支 htt ...

  5. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...

  6. KVM安装部署

    KVM安装部署 公司开始部署KVM,KVM的全称是kernel base virtual machine,对KVM虚拟化技术研究了一段时间, KVM是基于硬件的完全虚拟化,跟vmware.xen.hy ...

  7. 蓝屏 Dump文件分析方法

    WinDbg使用有点麻烦,还要符号表什么的.试了下,感觉显示很乱,分析的也不够全面... 试试其他的吧!今天电脑蓝屏了,就使用其dump文件测试,如下: 1.首先,最详细的,要属Osr Online这 ...

  8. windbg无法下载符号文件

    symbol file path: srv*d:\symbolslocal*http://msdl.microsoft.com/download/symbols 即使设置是对的,但我用.reload, ...

  9. 谁在死锁Mutex——用Windbg查找Mutex死锁所有者线程

    Who is blocking that Mutex? - Fun with WinDbg, CDB and KD 05 Aug 2006 By Ingo Rammer I'm currently t ...

随机推荐

  1. NGK流动性挖矿为何会备受瞩目?

    随着越来越多资金的涌入,参与DeFi项目或挖矿的用户不难发现,使用体验不尽人意,在以太坊网络的DeFi时常需要漫长的等待确认和高昂的GAS费用,加上DeFi流动性挖矿需要相对较高的资金门槛和技术门槛, ...

  2. Egg.js 是什么?

    Egg.js 是什么? 阿里巴巴出 Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本. 注:Egg.js 缩写为 Egg 设 ...

  3. Python学习笔记_斐波那契数列

    """ 1.生成100项斐波那契数列 2.求第n项斐波那契数列的值是多少 3.给定终止值,生成此前斐波那契数列 """ # 求第n项斐波那契 ...

  4. Typescript快速入门

    目录 什么是Typescript 为什么学习Typescript 快速搭建开发环境 1.安装node.js 2.使用node自带的npm安装Typescript编译器 3.配置vscode编辑环境 4 ...

  5. scala函数至简原则是什么?

    1.return可以省略,Scala会使用函数体的最后一行代码作为返回值 2.如果函数体只有一行代码,可以省略花括号 3.返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略) 4.如果有 ...

  6. c# 全选和批量修改

    //全选 function checkAll(){ var items = document.getElementsByTagName("input"); for(var i =0 ...

  7. mybatis 一对多和多对一 简单案例笔记

    以案例说明(以下案例代码都敲过验证过) 多对一(多个学生对一个老师  即学生集合中都存一个老师对象) Mybatis多对一实现方式1: //定义Student 和 Teacher 实体 @Data p ...

  8. 导入Excel时,如果有多个投料信息,则循环导入

    List<Input> list = new ArrayList<Input>();for (int j = 0; j < 500; ) { String materia ...

  9. 按照阿里巴巴规范创建Java线程池

    前言 Executors Executors 是一个Java中的工具类.提供工厂方法来创建不同类型的线程池. 常用方法: 1.newSingleThreadExecutor   介绍:创建一个单线程的 ...

  10. Python处理不平衡数据

    参考文献 所谓的不平衡数据集指的是数据集各个类别的样本量极不均衡.以二分类问题为例,假设正类的样本数量远大于负类的样本数量,通常情况下通常情况下把多数类样本的比例接近100:1这种情况下的数据称为不平 ...