testdbg-测试调试器

2023-11-04

2011-02-24 14:07:00
标签: 测试  休闲  调试器  职场  testdbg
原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://baidutech.blog.51cto.com/4114344/743464

导读
你想要在没有源代码的情况下调试一个C/C++程序吗? 
你想要在不重新安装php的情况下调试php吗? 
你想要打印或者修改网络通信的数据包吗? 
你想要在异步或者多进程、多线程下跟踪调试吗? 
你想要随意修改程序的逻辑、数据,或异常测试,或破解? 
………… 
本文将通过testdbg的原理及实现来介绍如何做到这些,具体的使用说明可以参考testdbg工具。 
概要
testdbg是通过动态修改程序的内存映像来达到hook的目的,不会对程序的二进制文件有任何影响,所以程序裸跑的时候和原来行为完全一致。 
采用的是一种汇编指令级别的hook手段,也可以说是一种hack的方式,所以不仅可以实现API hook,还可以操控、改变程序的任何逻辑(一切都在掌握之中)。 
不需要root权限,不需要修改源代码,不需要重新编译源程序。 
原理
testdbg主要包含3个模块: 
• 主程序testdbg,负责加载hook主模块到目标程序内存空间,然后启动目标程序 
• 主模块hookmon.so,负责加载用户编写的桩模块,实现原代码到桩的内存hook 
• 桩模块hook.so,用户编写的桩 
内存分布
为了更好地说明它们和目标程序的关系,可以先看下程序单独运行以及用testdbg启动运行时的内存镜像: 
 




(a)中是程序单独启动时的内存情况,首先加载的是程序的.text和.data段,其次加载了依赖的系统共享库。 
(b)中用testdbg启动程序,首先加载的也是程序的代码段,接着优先加载了主模块hookmon.so,在其初始化的过程中加载了桩模块hook.so,同时修改内存建立hook,这样程序在运行的时候便会根据主模块修改的逻辑执行,完成之后才开始加载依赖的系统库。 
建立hook
主模块hookmon.so由testdbg加载到目标程序内存后,会先在堆里动态申请一段内存来保存被修改的原函数入口(简称为跳板),然后修改原函数入口令其跳转到指定的桩代码中执行,桩代码在处理完逻辑后再通过跳板来执行原函数,关系如下图所示: 
 




实现步骤
testdbg实现主要分为以下几个步骤: 
1. 将桩代码动态加载到程序的内存空间 
2. 在程序启动前获取控制权以便修改内存 
3. 分析ELF文件获取所需的符号信息 
4. 反汇编备份原指令并修改成远跳转到桩代码 
5. 桩代码通过跳板执行原指令 
桩代码动态加载到程序的内存空间
要将桩代码插入到源程序,可以考虑通过修改源代码、编译期插桩或者修改生成的可执行文件等实现,但这几种方法都存在一些缺点: 
• 直接修改源代码插桩需要考虑版本升级后完整的将桩代码移植 
• 编译期插桩需要重新编译源程序 
• 修改可执行文件难度大,而且破坏了原文件 
将桩动态插入到程序的内存空间可以实现桩的热加载,使用起来更灵活~ 
ptrace尝试
一开始考虑的是一种比较hack的方法,让源程序以子进程的方式启动,并设置PTRACE_TRACEME由父进程控制,父进程获取控制权时在源程序入口处下软中断以获取下一次的控制权,然后分析ELF获取动态链接器的内部结构link_map,该结构是一个链表,保存着已加载的动态链接库的信息,遍历link_map查找_dl_open内部函数(或dlopen函数,但前提是源程序有链接libdl共享库,否则无法找到),接着修改RIP执行_dl_open来加载桩程序到源程序的内存空间,但这种方法在新版本glibc已经不可用了_dl_open会验证调用者的返回地址是否在有效的内存地址以防止hack,如下检查调用者是否在libc或者libdl的内存空间: 
int
attribute_hidden
_dl_check_caller (const void *caller, enum allowmask mask)
{
static const char expected1[] = LIBC_SO;
static const char expected2[] = LIBDL_SO;
//……
for (Lmid_t ns = 0; ns < DL_NNS; ++ns)
for (struct link_map *l = GL(dl_ns)[ns]._ns_loaded; l != NULL;
l = l->l_next)
if (caller >= (const void *) l->l_map_start
&& caller < (const void *) l->l_text_end)
{
/* The address falls into this DSO's address range. Check the
name. */
if ((mask & allow_libc) && strcmp (expected1, l->l_name) == 0)
return 0;
if ((mask & allow_libdl) && strcmp (expected2, l->l_name) == 0)
return 0;
//……
}
return 1;
}
LD_REPLOAD
使用linux提供的LD_PRELOAD环境变量,它可以在程序所有共享库加载之前优先加载LD_PRELOAD设置的共享库到程序的内存空间,先看个例子,一个简单hello程序: 
#include<stdio.h>
#include<unistd.h>

int main()

printf("hello, world!\n");
pause();
return 0;
}
编译后ldd看一下都依赖了哪些共享库: 
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ ldd hello 
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003b8c100000)
libm.so.6 => /lib64/tls/libm.so.6 (0x0000003b8a500000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003b8c900000)
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003b8a200000)
/lib64/ld-linux-x86-64.so.2 (0x0000003b8a000000)
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ ./hello 
hello, world!
设置LD_PRELOAD变量,ldd发现多了./preload.so,再运行./hello调用printf时优先查找到了preload.so的printf函数,打印了"hack!!" 
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ cat preload.c 
int printf ( const char * format, ... )
{
puts("hack !!\n");
return 0;
}
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ export LD_PRELOAD="./preload.so"
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ ldd hello 
./preload.so (0x0000002a95557000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003b8c100000)
libm.so.6 => /lib64/tls/libm.so.6 (0x0000003b8a500000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003b8c900000)
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003b8a200000)
/lib64/ld-linux-x86-64.so.2 (0x0000003b8a000000)
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ ./hello 
hack !!
可以通过strace看下系统调用: 
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ strace ./hello
execve("./hello", ["./hello"], [/* 38 vars */]) = 0
uname({sys="Linux", node="db-testing-t61.db01.baidu.com", ...}) = 0
brk(0) = 0x501000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2a95556000
open("./preload.so", O_RDONLY) = 3
……
open("/usr/lib64/libstdc++.so.6", O_RDONLY) = 3
……
open("/lib64/tls/libm.so.6", O_RDONLY) = 3
……
LD_REPLOAD指定的./preload.so最先被加载,看下此时hello的内存map: 
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ pgrep hello 
24934
[xuanbiao@db-testing-t61.db01.baidu.com preload]$ cat /proc/24934/maps 
00400000-00401000 r-xp 00000000 08:03 42893328 /home/xuanbiao/studio/code/elf/preload/hello
00500000-00501000 rw-p 00000000 08:03 42893328 /home/xuanbiao/studio/code/elf/preload/hello
2a95556000-2a95557000 rw-p 2a95556000 00:00 0 
2a95557000-2a95558000 r-xp 00000000 08:03 42882221 /home/xuanbiao/studio/code/elf/preload/preload.so
2a95558000-2a95657000 ---p 00001000 08:03 42882221 /home/xuanbiao/studio/code/elf/preload/preload.so
2a95657000-2a95658000 rw-p 00000000 08:03 42882221 /home/xuanbiao/studio/code/elf/preload/preload.so
可以看到preload.so已经被加载到hello程序内存的0x2a95557000 - 0x2a95658000之间~ 
使用LD_REPLOAD有2个好处,一个好处是可以很轻易将桩程序加载到源程序的内存空间,另一个好处是当程序调用如malloc、printf、open、read、write等系统函数时会优先查找LD_PRELOAD设置的共享库,所以你的共享库如果存在和这些系统函数声明一致的函数则会优先被调用进而达到API Hook的目的,对于模拟磁盘满、内存分配失败、网络读写等异常通过这种方法就可以实现,但对于非共享库的函数(可执行文件及静态库的函数)则需要继续往下看。 
在程序启动前获取控制权
桩程序加载到源程序内存空间后需要在源程序启动前获取控制权,以便触发一些初始化操作,否则源程序还是会按原来的逻辑执行,除了系统函数的hook有效外没有其它改变,但这不是我们的目的。 
ptrace再次尝试
原以为linux的so共享库并没有类似于windows的dll动态库一样存在DllMain函数来执行初始化,还是考虑了上面提到ptrace方法,只要启动主程序与桩程序约定哪个函数作为初始化函数就可以了,但这种实现过于麻烦,而且容易引发兼容性问题,这里不做过多的介绍。 
共享库的构建与析构函数
共享库可以通过__attribute__((constructor))和__attribute__((destructor))分别定义构建和析构函数,构建函数会在dlopen返回前执行,如果是在加载期加载则会在main函数调用前执行,析构函数在dlclose返回前执行,如果是在加载期加载的则会在exit()或者main函数完成后执行,函数原型如下: 
void __attribute__ ((constructor)) my_init(void);
void __attribute__ ((destructor)) my_fini(void);
需要注意的是在gcc编译共享库时不能加入'-nostartfiles'和'-nostdlib'选项,否则将不会被执行。 
分析ELF文件获取所需的符号信息
testdbg里实现了非共享库函数的hook,在hook之前需要获取指定函数的入口虚拟地址(即内存地址),可以通过分析ELF文件(即程序的二进制文件,以前老的的格式这里不做讨论)的符号节.symtab来获取,该节保存了所有有用的符号信息(包括引用的外部共享库的符号),这里只需要过滤获取符合要求的函数符号,然后通过符号信息获取指定函数的入口虚拟地址即可。 
下面是demo1的代码及用readelf查看到的符号信息: 
#include <stdio.h>

int func(int val, char *str)
{
printf("val:%d, str:%s\n", val, str);
return 0;
}
int main()
{
func(10, "hello, world!");
func(20, "just a test!");
return 0;
}
readelf -s demo1查看符号信息: 
[xuanbiao@db-testing-t61.db01.baidu.com demo1]$ readelf -s demo1

Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
2: 0000000000000000 160 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 72 entries:
Num: Value Size Type Bind Vis Ndx Name
50: 0000000000400558 44 FUNC GLOBAL DEFAULT 12 _Z4funciPc

53: 0000000000000000 160 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5

57: 0000000000400584 41 FUNC GLOBAL DEFAULT 12 main
可以看到打印了2个symbol table,.dynsym动态符号节和.symtab符号节,.dynsym只包含引用的外部符号信息,.symtab则包含了所有的符号信息,在.symtab节中可以看到func、printf和main函数的符号信息,由于demo1是用g++编译的,func的符号名变成了_Z4funciPc,这是由于C++允许重载,所以采用了name mangling来为每个函数生成不同的符号名。 
用c++filt可以查看该符号名的原型: 
[xuanbiao@db-testing-t61.db01.baidu.com demo1]$ c++filt _Z4funciPc
func(int, char*)
反汇编备份原指令并修改成远跳转到桩代码
非共享库函数hook一个关键的地方就是修改原函数的入口来跳转到hook函数来执行,但这样就破坏了原函数,如果要在hook函数里调用原函数则需要在修改之前将原函数备份,由于原函数只是在入口处修改成一个跳转指令,所以可以通过动态创建一个"跳板"函数,保存被修改之前的指令,然后再跳转回剩下的指令执行,这就使得在hook函数里调用跳板函数和调用原函数一样了。 
函数hook关系图
原函数、hook函数及跳板函数的关系如下: 
 




汇编指令备份拷贝
原函数入口的汇编指令备份拷贝需要注意2个地方,一个是指令需对齐,不能中间截断,另一个是拷贝到新内存后需要将原来的相对地址重新计算。 
指令对齐需要先进行反汇编,可以使用libopcodes里的API实现,具体的细节可以直接看testdbg里的代码实现。 
包含相对地址的指令拷贝到新内存,如果该相对地址的实际跳转地址不在一起拷贝过去的内存中则会出错,所以需要将这些相对地址都重新计算。根据绝对地址的计算公式: 
绝对地址=当前指令所在地址+当前指令长度+相对地址 
很容易计算出实际的绝对地址,再根据新内存的地址和新的指令长度即可计算出新的相对地址,需要注意的是小于4字节的相对地址重新计算后可能变成了4字节的相对地址,这就需要将原指令的操作码都替换成对应的支持4字节的操作码,保持语义的一致性。 
修改内存实现远跳转
无条件JMP跳转指令的直接寻址跳转能力只有32位,而64位操作系统却有64位的寻址能力,而且在之前LD_PRELOAD一节中看到的程序内存map,共享库都被加载到了高位地址,超出了32位地址范围,所以JMP跳转只能通过间接寻址跳转,比如jmp %rax、jmp %rbx、jmp imm(%rip)等,考虑到不破坏原寄存器的值,这里用的是jmp 0(%rip),然后将要跳转的64位目标地址写入到紧挨该指令的内存地址中。 
demo程序的func原函数反汇编: 
(gdb) disass func
Dump of assembler code for function _Z4funciPc:
0x0000000000400558 <_Z4funciPc+0>: push %rbp
0x0000000000400559 <_Z4funciPc+1>: mov %rsp,%rbp
0x000000000040055c <_Z4funciPc+4>: sub $0x10,%rsp
0x0000000000400560 <_Z4funciPc+8>: mov %edi,0xfffffffffffffffc(%rbp)
0x0000000000400563 <_Z4funciPc+11>: mov %rsi,0xfffffffffffffff0(%rbp)
0x0000000000400567 <_Z4funciPc+15>: mov 0xfffffffffffffff0(%rbp),%rdx
0x000000000040056b <_Z4funciPc+19>: mov 0xfffffffffffffffc(%rbp),%esi
0x000000000040056e <_Z4funciPc+22>: mov $0x40069c,%edi
0x0000000000400573 <_Z4funciPc+27>: mov $0x0,%eax
0x0000000000400578 <_Z4funciPc+32>: callq 0x400480
0x000000000040057d <_Z4funciPc+37>: mov $0x0,%eax
0x0000000000400582 <_Z4funciPc+42>: leaveq 
0x0000000000400583 <_Z4funciPc+43>: retq 
End of assembler dump.
被修改后的func函数反汇编: 
(gdb) disass func
Dump of assembler code for function _Z4funciPc:
0x00000000004005a8 <_Z4funciPc+0>: jmpq *0(%rip) # 0x4005ae <_Z4funciPc+6>
0x00000000004005ae <_Z4funciPc+6>: movl %?,(%rax)
0x00000000004005b0 <_Z4funciPc+8>: (bad) 
0x00000000004005b1 <_Z4funciPc+9>: xchg %eax,%ebp
0x00000000004005b2 <_Z4funciPc+10>: sub (%rax),%al
0x00000000004005b4 <_Z4funciPc+12>: add %al,(%rax)
0x00000000004005b6 <_Z4funciPc+14>: lock mov 0xfffffffffffffff0(%rbp),%rdx
0x00000000004005bb <_Z4funciPc+19>: mov 0xfffffffffffffffc(%rbp),%esi
0x00000000004005be <_Z4funciPc+22>: mov $0x4006fc,%edi
0x00000000004005c3 <_Z4funciPc+27>: mov $0x0,%eax
0x00000000004005c8 <_Z4funciPc+32>: callq 0x4004b8
0x00000000004005cd <_Z4funciPc+37>: mov $0x0,%eax
0x00000000004005d2 <_Z4funciPc+42>: leaveq 
0x00000000004005d3 <_Z4funciPc+43>: retq 
End of assembler dump.
(gdb) x /xg 0x00000000004005ae
0x4005ae <_Z4funciPc+6>: 0x0000002a9582388c
func函数入口已经被修改为了jmpq *0(%rip),由于紧挨着该指令的内存为目标跳转地址,是数据不是指令,所以后面的指令反汇编后大部分是错的,0x00000000004005ae保存的0x0000002a9582388c即为hook函数的虚拟地址,再看下跳板函数的反汇编: 
(gdb) p /x old_func
$2 = 0x502720
(gdb) disass 0x502720 0x502738
Dump of assembler code from 0x502720 to 0x502738:
0x0000000000502720: push %rbp
0x0000000000502721: mov %rsp,%rbp
0x0000000000502724: sub $0x10,%rsp
0x0000000000502728: mov %edi,0xfffffffffffffffc(%rbp)
0x000000000050272b: mov %rsi,0xfffffffffffffff0(%rbp)
0x000000000050272f: jmpq *0(%rip) # 0x502735
0x0000000000502735: mov $0x5,%bh
0x0000000000502737: add %al,(%rax)
End of assembler dump.
(gdb) x /xg 0x0000000000502735 
0x502735: 0x00000000004005b7
func原函数入口的指令备份到了这里,然后紧接着又是一条jmp远跳转指令到0x00000000004005b7执行func剩下的部分。 
桩代码通过跳板执行原指令
跳板的原理前面也提到了,等价于备份的原指令+跳转到剩余指令,其作用和修改前的原指令是一样的,只是绕了个弯,所以hook函数里要调用原函数则可以直接调用跳板就可以了。 
后记
上面介绍的是只是针对非动态库函数的hook,根据这个原理可以衍生出更多的应用,比如: 
• 在任意地址注入一段代码。如testdbg函数调用跟踪的实现,只是比函数hook多了一步备份和恢复寄存器。 
• 反汇编修改原程序的逻辑。比如je改成jne等,cracker常用的破解手段,呵呵。 
• …… 
反馈建议

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

testdbg-测试调试器 的相关文章

  • gnu binutils

    The GNU Binutils are a collection of binary tools The main ones are ld the GNU linker as the GNU assembler But they also
  • C++动态分配指针数组

    C 动态分配指针数组 C Primer 4 34练习要求将存在vector
  • Linux动态库(一)之同名符号

    万事皆有缘由 还是先从我遇到的这个问题说起 问 有一个主执行程序main 其中实现了函数foo 同时调用动态库liba so中的函数bar 而动态库liba so中也实现了foo 函数 那么在执行的时候如果在bar 中调用foo 会调用到哪
  • Linux System Calls Hooking Method Summary LD_PRELOAD

    Linux System Calls Hooking Method Summary 相关学习资料 http xiaonieblog com post 121 http hbprotoss github io posts li yong ld
  • C++ new delete new[] delete[]重载实现

    篇博文主要是分析了C new 和构造函数的执行 delete 与析构函数的调用方式 通过全局重载和局部重载看清楚调用的关系 cpp view plain copy using namespace std include
  • nm 命令显示

    用途 显示关于对象文件 可执行文件以及对象文件库里的符号信息 语法 nm A C X 32 64 32 64 f h l p r T v B P e g u d o x t Format File 描述 nm 命令显示关于指定 File 中
  • Binutils-2.23.2

    http lamp linux gov cn Linux LFS 6 2 chapter06 binutils html http davidgao github io LFSCN chapter06 binutils html Linux
  • testdbg-测试调试器

    http baidutech blog 51cto com 4114344 743464 testdbg 测试调试器 2011 02 24 14 07 00 标签 测试 休闲 调试器 职场 testdbg 原创作品 允许转载 转载时请务必以
  • linux hook

    http www codeproject com Articles 33340 Code Injection into Running Linux Application
  • 动态链接库中函数的地址确定---PLT和GOT

    动态链接库中函数的地址确定 PLT和GOT 2012 09 16 20 27 42 分类 C C 前面写过动态链接库 延迟绑定的一篇博文 那篇文章我非常喜欢 但是当时刚搞清楚 自己写的比较凌乱 我最近学习了Ulrich Drepper的Ho
  • 加密so文件中指定的函数

    加密so文件中指定的函数 作者 0n1y3nd丶 分类 逆向学习 发布时间 2014 09 04 22 24 61条评论 前言 上一篇文章中详细分析了对so文件中自定义section的加密 这一篇来分析下对so文件中自定义函数的加密 原文地
  • top查询机器情况

    top命令 是Linux下常用的性能 分析工具 能够实时显示系统 中各个进程的资源占用状况 类似于Windows的任务管理 器 下面详细介绍它的使用方法 top 02 53 32 up 16 days 6 34 17 users load
  • 将静态库答案放在 flash 部分的开头

    我正在使用 atmelstudio 编译固件映像 并希望将静态库 包括 gnu 的 libc a 和 libgcc a 中的函数放在 text 部分的开头 后面的 text 属于我的项目源代码 现在发生的情况恰恰相反 这是我的链接器脚本 A
  • Binutils LD 创建巨大文件

    我正在尝试创建尽可能小的 ELF 我创建了一个像这样的测试文件 NASM 语法 SECTION text dd 0xdeadbeef 使用此链接描述文件 SECTIONS text text 然后我检查了平面二进制文件的大小 并通过两种方式
  • gnu ld/gdb:单独的调试文件。当有太多调试信息需要链接时如何生成调试文件?

    现在有gdb and binutils支持将调试信息与要调试的二进制文件分开 描述这一点的文档可以在以下位置找到 gdb 单独的调试文件 http sourceware org gdb onlinedocs gdb Separate Deb
  • 为什么 GNU ld 在链接可执行文件和共享对象时解析符号的方式不同?

    我有一段简单的 C 代码 看起来像这样 include
  • 在 OS X Yosemite 上构建 binutils 的交叉编译

    我正在尝试构建 binutils 以在 Mac OS X 上生成 MIPS 代码 我找到了这个网站 http www theairportwiki com index php Building a cross compile of GCC
  • 如何用IPC::Open2过滤大量数据?

    我的任务是使用外部实用程序 addr2line 从 perl 脚本中过滤一些数据 数据量相当大 我需要打印大量数据stdin程序并读回大量数据 来自stdout程序到我的脚本中 现在我这样做IPC Open2 但我不混合阅读和写作 这合法吗
  • 将 32 位和 64 位代码链接到一个二进制文件中

    在对此问题的评论中 内核空间 C 代码中简单指针算术的意外行为 https stackoverflow com questions 49470691 unexpected behaviour in simple pointer arithm
  • 为什么 GNU binutils 和 GDB 合并为一个包?

    https sourceware org git gitweb cgi p binutils gdb git https sourceware org git gitweb cgi p binutils gdb git 尤其是请参阅tags

随机推荐

  • 网络基本知识【数据传输流程】

    文章目录 一 网络基础 1 IP地址 2 子网掩码 3 MAC地址 二 网络设备及相关技术 集线器 主机 路由器 ARP缓存表 ARP寻址 交换机 路由器 路由 NAPT 三 网路数据传输流程 1 局域网传输流程 集线器 交换机 交换机 路
  • VC++判断CheckBox控件是否被勾选

    图示为CheckBox控件 控件重映射为m timed send 控件默认状态为未勾选 0 状态 所以勾选时取反即可 代码如下 void CHCCOMDlg OnTimedSend TODO Add your control notific
  • 御见安全态势感知:“哈里男孩”水坑攻击“脚本小子”

    欢迎大家前往腾讯云社区 获取更多腾讯海量技术实践干货哦 作者 cocoyan odaywang 导语 水坑攻击是一种常见的高级攻击方法 电脑管家安全感知系统最近捕获到一例 分析如下 门前大桥下 游过一群鸭 快来快来数一数 二四六七八 Duc
  • OpenCL快速入门教程

    OpenCL快速入门教程 OpenCL快速入门教程 原文地址 http opencl codeplex com wikipage title OpenCL 20Tutorials 20 201 翻译日期 2012年6月4日星期一 这是第一篇
  • web服务中API接口响应过慢问题排查

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 使用Nginx uWSGI搭建web服务已经有半年时间了 最近经常会出现在某些时间段接口响应过慢的问题 一般要10几秒才能返回 有时候甚至是20 30s 这对前端APP来说
  • Activiti 工作流引擎 详解

    Activiti 工作流引擎 详解 1 Activiti工作流概述 1 1 工作流概述 1 2 工作流系统 1 3 Activiti概述 1 4 BPM 2 Activiti工作流环境搭建 3 Activiti 类 配置文件之间的关系 3
  • 操作系统——文件的基本操作

    创建文件 create系统调用 进行Create系统调用时 需要提供的几个主要参数 1 所需的外存空间大小 如 一个盘块 即1KB 2 文件存放路径 D Demo 3 文件名 这个地方默认为 新建文本文档 txt 操作系统在处理Create
  • 【华为OD机试】荒岛求生【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 有一个荒岛 只有左右两个港口 只有一座桥连接这两个港口 现在有一群人需要从两个港口逃生 有的人往右逃生 有的往左逃生 如果两个人相遇 则PK 体力值大的能够打赢体力值
  • html中hover有静止的命令,我可以通过JavaScript禁用CSS:hover效果吗?

    恐怕没有一个纯JavaScript的通用解决scheme JavaScript不能closuresCSS hover状态本身 不过你可以尝试下面的替代方法 如果您不介意在HTML和CSS中进行一些操作 则无需通过JavaScript手动重置
  • linux分区方案 1t,linux CentOS WEB服务器分区方案

    分区类型 分区的实际大小 解析 SWAP分区 2G 内存为1G 一般为内存的2倍 1G 2G 最少要150 250MB boot 32M 100M 启动分区 最多只要100M左右 opt 100M 1G 附加应用程序 tmp 40M 100
  • APT组织Lazarus近期攻击变化阐述

    Lazarus是来自朝鲜的APT组织 该组织长期对韩国 美国进行渗透攻击 此外还对全球的金融机构进行攻击 堪称全球金融机构的最大威胁 下面为近半年该组织的一些最新动态以及所使用的技术手段 Manuscrypt是该组织最常用的恶意软件家族 此
  • OpenCV VideoCapture.get()参数详解

    param define cv2 VideoCapture get 0 视频文件的当前位置 播放 以毫秒为单位 cv2 VideoCapture get 1 基于以0开始的被捕获或解码的帧索引 cv2 VideoCapture get 2
  • 4个点让你彻底明白Redis的各项功能

    4个点让你彻底明白Redis的各项功能 前言 先看一下Redis是一个什么东西 官方简介解释到 Redis是一个基于BSD开源的项目 是一个把结构化的数据放在内存中的一个存储系统 你可以把它作为数据库 缓存和消息中间件来使用 同时支持str
  • SIGPIPE的设计意图

    SIGPIPE的设计意图 SIGPIPE 是为以下这种情况设计的 grep pattern lt reallyhugefile head grep可能会输出成千上万行文本 但 head 只会读取前10行然后就退出 一旦head退出 grep
  • 《Pytorch深度学习和图神经网络(卷 1)》学习笔记——第八章

    本书之后的内容与当前需求不符合不再学习 信息熵与概率的计算关系 联合熵 条件熵 交叉熵 相对熵 KL散度 JS散度 互信息 无监督学习 监督训练中 模型能根据预测结果与标签差值来计算损失 并向损失最小的方向进行收敛 无监督训练中 无法通过样
  • 如何添加PYNQ-Z2板文件到Vivado

    添加板文件到vivado 先下载pynq z2板文件 PYNQZ2板文件 含约束文件 原理图 zip 下载后将文件复制到 Vivado安装目录 2018 3 data boards board files 重启vivado 完成
  • 重磅!AI与区块链技术知识分享交流会!特邀贾志刚老师、双一流211高校研究生!

    重磅 AI与区块链技术第一次知识交流分享会即将拉开帷幕 本交流会旨在分享交流人工智能 区块链相关内容 包括基础知识分享 前沿论文分享 具体项目实战 提供一个相同领域学者 工作人员在线交流机会 更多精彩内容 尽在微信公众号 AI与区块链技术
  • PCA算法

    https www cnblogs com dengdan890730 p 5495078 html PCA算法是怎么跟协方差矩阵 特征值 特征向量勾搭起来的 PCA Principle Component Analysis 主成份分析 是
  • 2023年7月31日-8月6日,(上午熟悉公司代码,周一到周五晚上优先工作所急视频教程,其他业余时间进行ue视频教程,为独立游戏做准备,本周10小时,合计2199小时,剩余7801小时)

    按照规划 上午熟悉公司源码 下午进行filament和ue渲染 晚上写工作代码 回家后泛读pbrt或者其他书籍催眠 业余学习ue的各种视频教程 为独立游戏做准备 累了就学其他视频教程 随意 可以按照ue 渲染 gt ue osg gt ue
  • testdbg-测试调试器

    http baidutech blog 51cto com 4114344 743464 testdbg 测试调试器 2011 02 24 14 07 00 标签 测试 休闲 调试器 职场 testdbg 原创作品 允许转载 转载时请务必以